基于 vuestic-ui 实战教程 - 登录篇

news2024/11/27 19:54:36

1. 简介

登录做为一个系统的门面,也是阻挡外界的一道防线,那在vuestic-ui中如何做登录功能呢。在这里就之间沿用初始版本的Login页面,作为一个演示模板,后续需要改进的读者可以在此篇文章的基础上修改。

在这里插入图片描述

2. 登录接口相关api 与 type编写

在上一篇获取动态数据中 我们已经定义好了与ts整合的axios,实现发送异步请求与远程服务器交互(对于ts语法像是函数定义、基本数据类型还是有不懂的读者可以跳转到上一篇的2.1再学习学习)这里就直接引入登录接口的api编写, 具体位置如下我个人习惯创建一个api文件夹,里面专门存放一些与后端交互的api方法和类型定义(初始quickstart版本是写在上面的page中的,就看个人的编写习惯吧😁只要功能实现了就没问题)

在这里插入图片描述

对于index.ts中主要实现了三个基本的方法,登录登出和获取用户信息

这里没有实现注册功能,因为我实现该网站主要是做一个流量监控的系统,注册功能对于用户不多的情况下其实不太需要,管理员可以直接操作加入数据库中,要是对这快感兴趣的读者也可以自己尝试尝试注册模块的功能实现
本质就是add一个user到数据库中,不过需要注意的是添加验证码等防护措施,防止有不法分子大量注册短时间内打爆服务器!!

import { http } from '../../../utils/request'
import type { LoginData , UserInfoRes} from './types'
 
const requestContent = '/simple/cloud/access'
/**
 * 登录
 */
export function login(loginVo: LoginData) {
  return http.post<UserInfoRes>(`${requestContent}/login`, loginVo);
}
 
/**
 * 获取登录用户信息
 */
export function getUserInfo() {
  return http.post<UserInfoRes>(`${requestContent}/info`)
}  

/**
 * 退出登录
 */
export function logout() {
  return http.post<string>(`${requestContent}/logout`)
}  

假设访问的后端服务器使用URL - http:localhost:9001/simple/cloud/access/login 这里由于uri前缀是一样的都是 ‘/simple/cloud/access’ 所以把它提取出来做为一个常量简化编写。只需要在使用的地方通过变量占位符引入就好啦(注意不是 ‘’ ,刚开始也踩过这个坑在vscode中看到上面的requestContent 由灰色变成高亮则说明引用成功)

`${key}`

而对于api中引入的数据类型定义在types.ts中,其中的返回值类型就根据后端提供的接口方法来写,像是我后端返回的类型为一个Map<String,Object> 类型的对象如下图所示,那我就根据这个map中的key和value一一对应写出如下的接口UserInfoRes,然后使用export导出给外部使用
在这里插入图片描述

注意编写的过程中只用指定ts基本的类型(java的List对应的就是ts中的数组 - 使用 [ ] 进行初始化 ),而要是需要返回一个User类型的对象,那就需要重新定义一个UserInfoInterface ,或者在user对应的api处定义types.ts 再在该文件下在引入(我是更推荐这种做法👍

/* 登录接口参数类型 */
export interface LoginData {
  email: string,
  password: string,
}
  
/* 用户信息接口返回值类型 */
export interface UserInfoRes {
  routers: [],
  buttons: [],
  roles: [],
  name: string,
  token: string,
}

3. 修改Login.vue

定义好与后端交互的方法api后,我们就可以回到前面的Login.vue处修改具体登录逻辑啦,由于初始版本使用的全是静态数据,所以很多功能其实都是不用的,具体删除修改后的模板如下(只保留了一个忘记密码的选项,该功能后续再完善😭先把主要的逻辑跑通先,感兴趣的读者可以先占个坑,后续我一定会回来填坑的!)

<template>
  <VaForm ref="form" @submit.prevent="submit">
    <h1 class="font-semibold text-4xl mb-4">Log in</h1>
    
    <VaInput
      v-model="formData.email"
      :rules="[validators.required, validators.email]"
      class="mb-4"
      label="Email"
      type="email"
    />
    <VaValue v-slot="isPasswordVisible" :default-value="false">
      <VaInput
        v-model="formData.password"
        :rules="[validators.required]"
        :type="isPasswordVisible.value ? 'text' : 'password'"
        class="mb-4"
        label="Password"
        @clickAppendInner.stop="isPasswordVisible.value = !isPasswordVisible.value"
      >
        <template #appendInner>
          <VaIcon
            :name="isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'"
            class="cursor-pointer"
            color="secondary"
          />
        </template>
      </VaInput>
    </VaValue>

    <div class="auth-layout__options flex flex-col sm:flex-row items-start sm:items-center justify-between">
      <RouterLink :to="{ name: 'recover-password' }" class="mt-2 sm:mt-0 sm:ml-1 font-semibold text-primary">
        Forgot password?
      </RouterLink>
    </div>

    <div class="flex justify-center mt-4">
      <VaButton class="w-full" @click="submit"> Login</VaButton>
    </div>
  </VaForm>
</template>

重写绑定的submit点击事件逻辑

const submit = () => {
	if (validate()) {
	  login(formData)
	    .then((data: UserInfoRes) => {
	      if (data) {
	        // 在这里添加需要执行的操作
	        const token = data.token;
	
	        // 将token存储到authStore中
	        const authStore = useAuthStore()
	        authStore.setToken(token)
	        authStore.setIsAuthenticated(true)
	         window.sessionStorage.setItem('isAuthenticated', 'true')
	        authStore.setName(data.name)
	        authStore.setButtons(data.buttons)
	        authStore.setRoles(data.roles)
	        authStore.setRouters(data.routers)
	
	        init({ message: "logged in success", color: 'success' });
	        // 登陆成功后就重定向到主页面dashboard
	        push({ name: 'dashboard' })
	
	      }
	    })
	    .catch(() => {
	      init({ message: "logged in fail , please check carefully!", color: '#FF0000' });
	    });
	
	}else{
	  Message.error('error submit!!')
	  return false
	}
}

看到这里我相信你肯定会疑惑,为什么我需要获取到数据又存储到store中,那这个store又在哪里定义的呢,作者也没讲啊😡
别急别急,请听我细细道来

4. store实现

在Vue应用程序中,当需要管理共享状态时,通常会使用Vuex库,而store就是Vuex中用于存储这些状态的地方,而我们登录后自然需要围护当前登录角色的一些关键信息(权限,姓名等等)需要的时候就直接到store中拿去,而不是反复的去数据库中查找,废话不多说下面就来定义一个store ,在初始版本中就已经定义好了store,只不过这个store里面是没东西的,如下图所示

在这里插入图片描述

那我们就可以在原有的基础上添加修改,下面的代码都是在index.ts中实现的,如下代码就是一个模板,对应pinia库的描述如下
Pinia是Vue的另一种状态管理方案,与Vuex类似,但设计上更简洁、更易于上手。以下是关于Pinia的一些详细说明:

  • 简单易用:Pinia的目标是提供一个更简单的状态管理解决方案,它的API设计非常直观,使得开发者可以快速上手并有效地管理状态。
  • 独立模块:与Vuex不同,Pinia中的每个store都是一个独立的模块,它们可以单独导入和导出,这有助于更好地组织和维护代码。
  • 响应式:Pinia中的状态是响应式的,当状态发生变化时,依赖于这些状态的组件会自动更新。
  • Devtools支持:Pinia具有良好的Devtools支持,可以帮助开发者更方便地跟踪和调试状态变化。
  • 插件化:Pinia被设计为一个插件,可以轻松地集成到现有的Vue应用中。
  • 与Vuex兼容:虽然Pinia是一个全新的状态管理库,但它也允许与Vuex共存于同一个项目中,方便开发者逐步迁移。

本次项目中store就基于Pinia实现,首先通过defineStore方法定义一个全局可供调用的store, 其中包括了一些属性像是

  1. id (自己设定,但是要保证全局唯一)
  2. state (定义的所有状态)
  3. getters (获取状态的方法)
  4. actions (有获取肯定就有设置的方法啦)
// store.ts
import { createPinia, defineStore } from 'pinia'
 
export const useAuthStore = defineStore({
  id: 'auth',
  state: () => ({

  }),
  getters: {

  },
  actions: {
    
  },
})

export default createPinia()

4.1 state

在state中定义的状态就是在一个浏览器会话内需要存储的用户信息(登录后赋值,登出或者会话结束就销毁)根据第2点中types定义的UserInfoRes 可以设计出来, 由于ts不像js一样是弱语言,ts是有类型的上一讲也提到过,所以为了能在后续的get set中拿到指定和设置其中的属性值,我们需要通过as 参数类型的方式来指定

isAuthenticated 本意是为了阻止用户登录前就访问其他的页面(会被驳回,重定向到登录页面)后面发现存到浏览器缓存中也是可以的,这里就做个备选,看读者喜欢哪一种方式

state: () => ({
    token : '',
    isAuthenticated : false,
    routers : [] as RouterVo[],
    buttons : [] as string[],
    name : '',
    roles : [] as RoleData[],
  }),

这里的RoleDta和RouterVo就分别对应了角色和菜单列表,具体实现如下(编写在types.ts文件中,具体位置看下边4.4的总体代码)


/* sysUser参数类型 */
export interface RoleData {
  id: number,
  roleName: string,
  roleCode: string,
  description: string
}

 /* RouterVo参数类型 */
export interface RouterVo {
  path: string,
  hidden: boolean,
  alwaysShow: boolean,
  meta: MetaVo,
  children: RouterVo[],
}

4.2 getters

根据如下的指定格式获取存在store中的参数

getters: {
  getButtons: (state) => state.buttons,
  getToken: (state) => state.token,
  getIsAuthenticated: (state) => state.isAuthenticated,
  getRouters: (state) => state.routers,
  getName: (state) => state.name,
  getRoles: (state) => state.roles,
},

4.3 actions

actions中定义了一系列set方法,可以发现这里()内的参数都是指定类型的,如果我们在定义的时候不指定类型这就会报错!!

actions: {
 setRoles(roles : RoleData[]) {
    this.roles = roles
  },
  
  setButtons(buttons : string[]) {
    this.buttons = buttons
  },
  
  setRouters(routers : RouterVo[]) {
    this.routers = routers
  },
  
  setName(name : string) {
    this.name = name
  },
  
  setToken(token : string) {
    this.token = token
  },
  
  setIsAuthenticated(isAuthenticated : boolean){
    this.isAuthenticated = isAuthenticated
  },
	// 登出后的资源重置
  reset(){
    this.roles = []
    this.name = ''
    this.buttons = []
    this.routers = []
    this.isAuthenticated = false
    this.token = ''
  },
},

4.4 总体代码

// store.ts
import { createPinia, defineStore } from 'pinia'
import { RoleData } from '@/api/system/sysRole/types'
import { RouterVo } from '@/api/system/sysMenu/types'
 
export const useAuthStore = defineStore({
  id: 'auth',
  state: () => ({
    token : '',
    isAuthenticated : false,
    routers : [] as RouterVo[],
    buttons : [] as string[],
    name : '',
    roles : [] as RoleData[],
  }),
  getters: {
    getButtons: (state) => state.buttons,
    getToken: (state) => state.token,
    getIsAuthenticated: (state) => state.isAuthenticated,
    getRouters: (state) => state.routers,
    getName: (state) => state.name,
    getRoles: (state) => state.roles,
  },
  actions: {
    setRoles(roles : RoleData[]) {
      this.roles = roles
    },
    
    setButtons(buttons : string[]) {
      this.buttons = buttons
    },
    
    setRouters(routers : RouterVo[]) {
      this.routers = routers
    },
    
    setName(name : string) {
      this.name = name
    },
    
    setToken(token : string) {
      this.token = token
    },
    
    setIsAuthenticated(isAuthenticated : boolean){
      this.isAuthenticated = isAuthenticated
    },

    reset(){
      this.roles = []
      this.name = ''
      this.buttons = []
      this.routers = []
      this.isAuthenticated = false
      this.token = ''
    },
  },
})
// 记得要导出,不在就白定义了 外部通过调用createPinia() 获取示例
export default createPinia()

4.5 main.ts中App引入

在Vue中引入App是因为App.vue通常作为项目的主组件和页面入口文件,负责构建定义及页面组件的归集和切换。定义的组件自然要添加到其中,在初始化的时候就一同创建。在文件原有基础上添加如下代码

import stores from './stores'
import { createPinia } from 'pinia'

app.use(createPinia)
app.use(stores)

最后保存就好啦,到这里在回看第3点的submit方法是不是就一目了然
这里提炼出使用store的核心代码,有需要的读者可以直接复制使用😁

// 导入刚刚定义的方法
import { useAuthStore } from '@/stores'

// 外部调用创建一个示例(唯一的)
const authStore = useAuthStore()
// 在对应的操作方法里面使用我们在getters和actions中定义的方法
// set
authStore.setToken(token)
// get
const token = authStore.getToken

5. vue限制实现不登录无法进入其他页面

这个模块可用的方法有很多网上也是有各种各样的教程,在这里使用的是设置路由守卫的方法,在router/index.ts下修改,具体做三种判断

  1. 防止重复登录: 登录后的用户不能在登录了,只能主动退出或者关闭浏览器(token失效也是一个,这个后面讲)
  2. 白名单直接放行:对于可以供给全部用户访问的一些静态资源、页面(比如登录页面,和一些docs帮助文档是可以直接访问的)
  3. 没有登录:对于没有登录的用户无法访问系统的资源,为了提防有些通过导航栏修改URL的方法访问
// 设置哪些页面是属于白名单的
const witheList = ["/auth/login"];
 
function isWitheRoute(path : string) {
  return witheList.includes(path);
}
 
// 全局前置守卫
router.beforeEach((to, from, next) => {
  const isAuthenticated =  window.sessionStorage.getItem('isAuthenticated');

  //防止重复登录
  if (isAuthenticated && (to.path === "/auth/login"))  {
    Message.info("You have successfully logged in. Please avoid logging in repeatedly! (You can log out if you wish)");
    return next({ path: from.path ? from.path : "/" });
  }

  // 判断如果是白名单就直接放行
  if (isWitheRoute(to.path)) {
    next();
    return;
  }
  // 没有登录,强制跳转到登录页面
  if (!isAuthenticated && to.path != "/auth/login") {
    Message.info("Please logging first");
    next({ path: "/auth/login" });
    return;
  }  
  next()
});

5.1. 浏览器缓存

上边埋了一个坑,可以使用浏览器缓存的方法实现该功能,上边代码也看到了window.sessionStorage. 那么这到底是嘛玩意,作用范围生命周期又是什么呢?下面将一一解答:

  1. sessionStorage为Web开发者提供了一种在用户的浏览器中临时存储数据的方式。这种存储方式特定于用户打开的特定窗口或标签页,并且数据只在这个特定的窗口或标签页有效。当用户关闭这个窗口或标签页时,存储在sessionStorage中的所有数据将被清除。这就意味着不同的浏览器窗口或标签页,即使是打开相同的网页,它们之间的sessionStorage数据是不共享的。
  2. sessionStorage的生命周期与用户打开的窗口或标签页的持续时间同步。只要窗口或标签页保持打开状态,即便是进行页面刷新或切换到同源的其他页面,sessionStorage中的数据都将持续存在。然而,一旦窗口或标签页被关闭,sessionStorage中的所有数据将立即失效并被清除。

可以见得通过该方法保存用户的登录状态也是不错之选,而且非正常退出时候也不用担心数据泄露(会自动销毁,后端的数据就需要通过勾子函数回调,或者直接设置redis过期时间就等它自动过期)下边就是三个常用的方法:

对于我们的登录功能来说,在登录成功后设置为true,此时路由守卫判断时候就能获取到该值,而在登出的时候就删除掉该数据,这样就能保证统一

//设置对应的key-value
window.sessionStorage.setItem('isAuthenticated', 'true');

//通过getItem获取 (取不到时为null)
const isAuthenticated =  window.sessionStorage.getItem('isAuthenticated');

//去除浏览器缓存
window.sessionStorage.removeItem('isAuthenticated')

6. 登出功能实现

登出功能本质上是跟登录没什么区别的,就是后端清除存储的数据token , reids中权限数据等,前端清除login获取到的所有数据(回到出厂设置的感觉)在初始版本中是没有登出这个按钮的,经常登录网页的朋友都知道,登出的按钮一般是在右上角,那这里我们就遵循惯例先找找最上边的栏目是在哪一个vue页面里面(最笨的方法就是一个一个去搜索是否有相应的字眼)

那么我就以我的理解来告诉大家如何快速找到相应的模块。首先要知道的是所有的组件都是放在src/components文件夹下的,那我们就去下边找,一展开就很明显看到navbar的字眼(导航栏嘛,也就是我们要找的上边栏所在位置)点开后发现又有个components(根据上面的知识不用我说都知道这是放组件的吧)点开就看到GitHubButton 这不就是我们要找的上边栏上的github按钮吗,说明我们找对地方了,最终就锁定范围在这两个vue文件中,是不是一下子节省很多工作量😁 , 具体示例文件所在处如下图所示
在这里插入图片描述

找到这个文件后我们预期的效果是跟下图这样加一个Logout 按钮 用户点击就可以退出登录
在这里插入图片描述
在AppNavbarActions这个文件中点开就发现其实实现起来很简单,就是依葫芦画瓢,照抄原来有的button组件就好啦,具体代码如下

<VaButton
  v-if="!isMobile"
  preset="secondary"
  @click="logoutOper" <!--自定义点击事件-->
  target="_blank"
  color="textPrimary"
  class="app-navbar-actions__item flex-shrink-0 mx-0"
>
  {{ t('Logout') }}
</VaButton>

因为我们绑定了点击事件,自然要实现的啦(如下代码在script中原有的基础上添加)

import { logout } from '@/api/system/auth/index'
import { useAuthStore } from '@/stores'
import { useToast } from 'vuestic-ui'
import { useRouter } from 'vue-router'
const { push } = useRouter()
const { init } = useToast()

const logoutOper = () => {
  logout().then(() => {
    const store = useAuthStore() // 获取store实例
    store.reset() // 重置store

    //去除浏览器缓存
    window.sessionStorage.removeItem('isAuthenticated')

    //跳转路由
    init({ message: "logout success", color: 'success' });
    push({ name: 'login' })

  }).catch(() => {
    init({ message: "logged out fail , please contact administration", color: '#FF0000' });
  });
}

7. 每次请求时带上token访问服务器

由于加入了权限认证功能,所以登录后的每一次请求都必须携带上token(这里的token就遵循OAuth2的规范以"Bearer "开头),不然会认为没有登录跳转的登录页面重新登录,在每一次请求中添加请求头是不是就是定义一个全局filter,也就是在上一讲中提到的axios请求拦截器,那如下代码就在utils/request.ts下修改(还没有的请看上一讲)

import { useAuthStore } from '@/stores'

/* 请求拦截器 */
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {  
    const authStore = useAuthStore()
    if (authStore != undefined) {
      //获取token
      const token = authStore.getToken
      config.headers.Authorization = `Bearer ${token}`;
    } else {
      // 如果不存在 token,则拒绝请求并跳转到登录页面
      window.location.href = '/auth/login';

      //去除浏览器缓存
      window.sessionStorage.removeItem('isAuthenticated')
      return Promise.reject('Authenticated fail');
    }

  return config;  

}, (error: AxiosError) => {
  Message.error(error.message);
  return Promise.reject(error)
})

终于讲完啦,这篇内容挺多的,给看到这里的读者点赞👍,希望能够对你们有所帮助(本篇主要实现前端的功能,后续会结合权限管理给出后端认证授权功能实现,敬请期待…)


各位读者我回来填坑啦,对于上面的后端实现我又写了点自己的想法,感兴趣的读者可以点击查阅后端认证授权功能实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1708499.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024-5-28 石群电路-16

2024-5-28&#xff0c;星期二&#xff0c;20:14&#xff0c;天气&#xff1a;晴&#xff0c;心情&#xff1a;晴。今天没有什么特别的事情发生&#xff0c;不过返校假期已经开始啦&#xff0c;和女朋友逛了街&#xff0c;吃了好吃的&#xff0c;学习也当然不能落下啦&#xff0…

LeetCode/NowCoder-栈和队列OJ练习

孜孜不倦&#xff1a;孜孜&#xff1a;勤勉&#xff0c;不懈怠。指工作或学习勤奋不知疲倦。&#x1f493;&#x1f493;&#x1f493; 目录 说在前面 题目一&#xff1a;括号匹配问题 题目二&#xff1a;用队列实现栈 题目三&#xff1a;用栈实现队列 题目四&#xff1a;设…

Javascript--词法作用域

词法作用域 词法阶段 大部分标准化语言编辑器的第一个工作阶段叫做词法化&#xff0c;词法化会对源代码中的字符进行检查&#xff0c;如果是有状态的解析过程&#xff0c;还会赋予单词语义。 简单来说&#xff0c;词法作用域就是在词法阶段的作用域&#xff0c; function fo…

《架演》共创者第一次线上沟通会议总结

《架演》共创者第一次线上沟通——启动会 会议主题&#xff1a;《架演》共创启动会议会议时间&#xff1a;2024年5月28日&#xff0c;20:00 - 21:00会议地点&#xff1a;腾讯会议主持人&#xff1a;寒山参会人员&#xff1a; 夏军、mirror、刘哥、悟缺席人员&#xff1a;可心、…

为表格添加背景色:\rowcolor, \columncolor,\cellcolor

设置行的背景 \rowcolor 是 LaTeX 中用于设置表格行的背景色的命令。它可以使表格更加美观和易于阅读。rowcolor 命令通常与 colortbl 宏包一起使用。 语法如下&#xff1a; \rowcolor{<color>}其中 表示要设置的背景色&#xff0c;可以是预定义的颜色名称&#xff08…

BIO/NIO学习

在传送文件的时候常常出现这么一个问题&#xff0c;就是当客户端的文件全部传送完了之后&#xff0c;服务器没有接收到客户端那边传过的停止信号&#xff0c;所以服务器也就跟着客户端停止运行了&#xff0c;我们可以使用 try {socket.shutdownOutput();} catch (IOException e…

项目构建工具maven

一、概述 1、maven是apache的一个开源项目&#xff0c;是一个优秀的项目构建/管理工具 2、apache(软件基金会、非盈利组织、管理维护一些开源项目) 二、功能 1、管理项目中jar包和jar包与jar包之间的依赖 2、完成项目编译、测试、打包 三、核心文件 pom.xml:在里面配置相…

华为机考入门python3--(26)牛客26-字符串排序

分类&#xff1a;字符串 知识点&#xff1a; 字符串是否仅由字母构成 my_str.isalpha() 字母列表按小写排序 letters.sort(keylambda x: x.lower()) 题目来自【牛客】 def custom_sort(input_str):letters []non_letters []for char in input_str:if char.isalpha…

华语电影新力量用短片讲述:一部好电影,影响深远

近日&#xff0c;上汽大众杯澳涞坞全球青年电影短片大赛的公益短片《首映》在澳门澳涞坞首映发布&#xff0c;这一作品不仅展示了电影人的真实生活&#xff0c;更深刻地传达了对华语电影的敬意以及对青年电影人的殷切期望。 短片《首映》的制作团队堪称豪华。资深导演杨枫担任…

IT人的拖延——一放松就停不下来,耽误事?

拖延的表现 在我们的日常工作中&#xff0c;经常会面对这样一种情况&#xff1a;因为要做的Sprint ticket比较复杂或者长时间的集中注意力后&#xff0c;本来打算休息放松一下&#xff0c;刷刷剧&#xff0c;玩玩下游戏&#xff0c;但却一个不小心&#xff0c;没控制住时间&am…

IGMP——组播成员端网络协议

目录 一.IGMP基本概念 &#xff08;1&#xff09;组播转发困境 &#xff08;2&#xff09;感知组播成员方式 &#xff08;3&#xff09;IGMP版本 二.IGMP各版本的区别与联系 &#xff08;1&#xff09;IGMPV1 1.普遍组查询报文 2.成员关系报告报文 3.IGMPV1报文格式 4…

生成验证码的奥秘:从列表到字符串的魔法转换

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;验证码生成的背景与需求 二、生成验证码的方法一&#xff1a;列表生成…

ACM Proceedings Template 使用方法

模板导入 打开ACM Primary Article Template官网&#xff0c;可以看到自带overleaf模板&#xff0c;接下来我们使用overleaf来自动导入模板。 选择你需要的ACM Conference or Journals模板&#xff0c;然后Open as Template 栏目说明 接下来依次解释一下左边栏目的作用 …

装机必备——360压缩安装教程

装机必备——360压缩安装教程 软件下载 软件名称&#xff1a;360压缩 软件语言&#xff1a;简体中文 软件大小&#xff1a;3.38M 系统要求&#xff1a;Windows7或更高&#xff0c; 32/64位操作系统 硬件要求&#xff1a;CPU2GHz &#xff0c;RAM4G或更高 下载通道①迅雷云盘丨…

14、类与对象(采用图解方式分析内存结构)①

在idea中创建一个新文件&#xff0c;名称为Hello.java 其中&#xff0c;Hello就是一个类&#xff0c;main是这个类里面的方法&#xff0c;这意味着我们在学习的时候已经在使用类了。 对象和类 一、概念二、⭐内存分配机制分析Ⅰ、基本内存结构⭐⭐Ⅱ、调用类方法的内存分析&am…

HCIP的学习(24)

第七章&#xff0c;VLAN—虚拟局域网 ​ 通过在交换机上部署VLAN技术&#xff0c;将一个规模较大的广播域在逻辑上划分成若干个不同的、规模较小的广播域。 ​ IEEE 802.1Q标准----虚拟桥接局域网标准----Dot1Q标准 标签协议标识符&#xff1a;0x8011&#xff08;代表数据帧是8…

MySQL数据库案例实战教程:数据类型、语法与高级查询详解

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

【Python-OS】os.path.splitext()

作用&#xff1a;将文件路径分割成文件名和扩展名两部分。 slide_id, _ os.path.splitext(slide) print("slide:") print(slide) print("slide_id:") print(slide_id)注&#xff1a; slide是文件名&#xff0c;可以自行赋值

美光EMMC芯片丝印型号查询 8LK17/D9PSK, OXA17/JY997

问题说明 最近在使用美光EMMC的时候&#xff0c;发现通过芯片丝印查询不到 芯片的规格说明书&#xff1b; 经过查阅资料&#xff0c;发现美光的EMMC芯片 “由于空间限制&#xff0c;FBGA 封装组件具有与部件号不同的缩写部件标记”&#xff0c;需要通过官网查询丝印的FBGA cod…

极验3逆向 JS逆向最新点选验证码 逆向分析详解

目录 声明&#xff01; 一、请求流程分析 二、w参数生成位置 三、主要问题 四、结果展示 原创文章&#xff0c;请勿转载&#xff01; 本文内容仅限于安全研究&#xff0c;不公开具体源码。维护网络安全&#xff0c;人人有责。 声明&#xff01; 本文章中所有内容仅供学习交流…