当前位置: 首页> 文旅> 艺术 > [vue3]大事件-admin

[vue3]大事件-admin

时间:2025/7/10 20:09:30来源:https://blog.csdn.net/CSDN20221005/article/details/139760287 浏览次数:0次

介绍

pnpm

pnpm是最新的包管理器, 比同类工具快2倍, 更节省磁盘空间

  1. npm->yarn->pnpm
  2. 官网 https://www.pnpm.cn/
  3. 安装 npm install -g pnpm
  4. 创建项目 pnpm create vue

Eslint代码校验

Eslint专注于代码规范校验, prettier专注于代码风格校验(格式化)

prettier官网: Prettier · Opinionated Code Formatter

前置操作

  • 安装eslint插件, 配置保存时自动修复(参考前端环境)
  • 禁用格式化插件 prettier
  • 关闭编辑器的保存格式代码选项 (参考前端环境)

  • 确保项目中安装了 prettier 依赖包

代码风格介绍

  1. 单引号
  2. 不使用分号
  3. 代码宽度80字符,超出换行
  4. 不加对象|数组最后逗号
  5. 换行符不限制(win mac不一致)

代码规范介绍

  1. vue组件名称必须多单词组成(忽略index.vue)
  2. props解构警告(关闭)

代码配置

module.exports = {... ...rules: {// prettier专注于代码的美观度 (格式化工具)'prettier/prettier': ['warn',{singleQuote: true, // 单引号semi: false, // 无分号printWidth: 80, // 每行宽度至多80字符trailingComma: 'none', // 不加对象|数组最后逗号endOfLine: 'auto' // 换行符号不限制(win mac 不一致)}],// ESLint关注于规范, 如果不符合规范,报错'vue/multi-word-component-names': ['warn',{ignores: ['index'] // vue组件名称多单词组成(忽略index.vue)}],'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验 (props解构丢失响应式)// 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。'no-undef': 'error'},
... ...
}

提交代码检查

保证提交到仓库的代码是符合规范的, 没有错误的, husky是一个git hooks工具 (git钩子工具, 在特定时机执行特定的命令)

  1. 官网: Husky
  2. 初始化git仓库( Git Bash终端 ), 命令: git init
  3. 初始化husky工具配置, 命令: pnpm dlx husky-init && pnpm install
  4. 修改配置文件
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"pnpm lint
  1. 执行流程: 提交代码触发per-commit文件执行, 文件中配置了pnpm lint命令, 该命令执行后, 会检查配置范围内的文件, 是否符合规范, 不合格会尝试处理, 合格后才可以提交代码
{"scripts": {... ..."lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",... ...},
}
  1. pnpm lint是全量检查, 存在耗时问题, 历史问题,
  2. 如果希望团队的每个人只对自己的代码负责, 往下看

暂存区eslint校验

  1. 安装包: pnpm i lint-staged -D
  2. 配置命令
{"scripts": {... ..."lint-staged": "lint-staged"},... ..."lint-staged": {"*.{js,ts,vue}": ["eslint --fix"]}
}
  1. 修改配置文件
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"pnpm lint-staged

规范目录

默认生成的目录结构不完全满足开发的需求, 需要做一些自定义改动

  1. 删除项目多余文件和默认样式文件
  2. 清除不必要的代码
  3. 新增utils文件夹和api文件夹
  4. 拷贝初始化样式, 并在main.js引用

import '@/assets/main.scss'

  1. 安装sass预处理器

pnpm add sass -D

ElementPlus

根据官网, 按需引入elementPlus

官网: 一个 Vue 3 UI 框架 | Element Plus

步骤

  1. 安装 pnpm install element-plus
  2. 配置按需引入
  • 安装插件 npm install -D unplugin-vue-components unplugin-auto-import
// elementPlus按需引入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'export default defineConfig({plugins: [vue(),AutoImport({resolvers: [ElementPlusResolver()]}),Components({resolvers: [ElementPlusResolver()]})],
})
  1. 直接使用组件
  2. 彩蛋: component中的所有组件也会被自动注册, 使用更方便

构建仓库

1.0创建仓库

import { defineStore } from 'pinia'
import { ref } from 'vue'export const useUserStore = defineStore('big-user',() => {// token相关const token = ref('')const setToken = (newToken) => {token.value = newToken}const removeToken = () => {token.value = ''}return {token,setToken,removeToken,}},{persist: true}
)

2.0管理仓库

// 创建仓库实例
// 配置持久化插件
// 目的: 简化main.js中的代码
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate' //pinia持久化插件const pinia = createPinia()
pinia.use(persist)export default pinia// 统一导出
// 目的:  简化导入仓库的路径
// 解释:  所有的仓库在该文件统一导出, 导入时无需纠结路径问题
// 示例:  import { useUserStore } from '@/stores'
export * from './modules/user'

3.0挂载仓库

... ...
import pinia from '@/stores/index'
appp.use(pinia)
... ...

封装axios

1.0常见需求

2.0安装命令

  • pnpm add axios

3.0模块代码

import axios from 'axios'
import { useUserStore } from '@/stores'
import { ElMessage } from 'element-plus'
import router from '@/router'
const baseURL = 'http://big-event-vue-api-t.itheima.net'const request = axios.create({// TODO 1. 基础地址,超时时间baseURL,timeout: 10000
})// 请求拦截器
request.interceptors.request.use(// 对请求做处理(config) => {// TODO 2. 携带tokenconst useStore = useUserStore()if (useStore.token) {config.headers.Authorization = useStore.token}return config},// 对错误做处理(err) => Promise.reject(err)
)// 响应拦截器
request.interceptors.response.use(// 2xx的响应(res) => {// TODO 4. 摘取核心响应数据if (res.data.code === 0) {return res}// TODO 3. 处理业务失败// 处理业务失败, 给错误提示,抛出错误ElMessage.error(res.data.message || '服务异常')return Promise.reject(res.data)},// 超出2xx的响应(err) => {// TODO 5. 处理401错误// 错误的特殊情况 => 401 权限不足 或 token 过期 => 拦截到登录if (err.response?.status === 401) {router.push('/login')}// 错误的默认情况 => 只要给提示ElMessage.error(err.response.data.message || '服务异常')return Promise.reject(err)}
)export default request
export { baseURL }

路由设计

1.0路由划分

  1. 登录页 -> 一级路由
  2. layout -> 一级路由
  • 文章分类 -> 二级路由
  • 文章管理 -> 二级路由
  • 基本资料 -> 二级路由
  • 更换头像 -> 二级路由
  • 重置密码 -> 二级路由

2.0约定规则

3.0配置规则和路由出口

import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores'// createRouter()创建路由对象
const router = createRouter({// 设置路由模式,并设置基路径history: createWebHistory(import.meta.env.BASE_URL),//路由规则routes: [{ path: '/login', component: () => import('@/views/login/LoginPage.vue') }, // 登录页{path: '/',component: () => import('@/views/layout/LayoutContainer.vue'),redirect: '/article/manage',children: [{path: '/article/manage',component: () => import('@/views/article/ArticleManage.vue')},{path: '/article/channel',component: () => import('@/views/article/ArticleChannel.vue')},{path: '/user/profile',component: () => import('@/views/user/UserProfile.vue')},{path: '/user/avatar',component: () => import('@/views/user/UserAvatar.vue')},{path: '/user/password',component: () => import('@/views/user/UserPassword.vue')}]}]
})export default router
<template><RouterView></RouterView>
</template>
<template><div>layout架子</div><div><!-- 二级路由出口 --><router-view></router-view><div>
</template>

4.0手动修改路由,进行测试

登录注册

1.0效果展示

2.0需求分析
  1. 注册登录页的静态结构, 切换效果
  2. 注册功能(校验+注册)
  3. 登录功能(校验+登录+存token)
3.0搭建静态
  1. 使用布局组件辅助布局
  • el-row表示一行,一行分成24份
  • el-col表示一列

(1) :span="12" 代表在一行中,占12份 (50%)

(2) :span="6" 表示在一行中,占6份 (25%)

(3) :offset="3" 代表在一行中,左侧margin份数

  1. 安装需要的图标库
  • pnpm i @element-plus/icons-vue
  1. 使用el-form 表单组件

(1)el-form-item 表单的一行 (一个表单域)

(2)el-input 表单元素(输入框)

  1. 通过v-if切换表单组件
<script setup>
import { User, Lock } from '@element-plus/icons-vue'
const isRegister = ref(false)
</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 1,注册表单 --><el-form:model="formModel":rules="rules"ref="form"size="large"autocomplete="off"v-if="isRegister">......</el-form><!-- 2,登录表单 --><el-form:model="formModel":rules="rules"ref="form"size="large"autocomplete="off"v-else>... ...</el-form></el-col></el-row>
</template>
4.0注册功能

完成表单校验

<script setup>
import { ref, watch } from 'vue'// 整个的用于提交的form数据对象
const formModel = ref({username: 'dsj007',password: 'dsj007',repassword: ''
})// 表单的校验规则
// 1. 非空校验 required: true      message消息提示,  trigger触发校验的时机 blur change
// 2. 长度校验 min:xx, max: xx
// 3. 正则校验 pattern: 正则规则    \S 非空字符
// 4. 自定义校验 => 自己写逻辑校验 (校验函数)
//    validator: (rule, value, callback)
//    (1) rule  当前校验规则相关的信息
//    (2) value 所校验的表单元素目前的表单值
//    (3) callback 无论成功还是失败,都需要 callback 回调
//        - callback() 校验成功
//        - callback(new Error(错误信息)) 校验失败
const rules = {username: [{ required: true, message: '请输入用户名', trigger: 'blur' },{ min: 5, max: 10, message: '用户名必须是 5-10位 的字符', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{pattern: /^\S{6,15}$/,message: '密码必须是 6-15位 的非空字符',trigger: 'blur'}],repassword: [{ required: true, message: '请输入密码', trigger: 'blur' },{pattern: /^\S{6,15}$/,message: '密码必须是 6-15位 的非空字符',trigger: 'blur'},{validator: (rule, value, callback) => {// 判断 value 和 当前 form 中收集的 password 是否一致if (value !== formModel.value.password) {callback(new Error('两次输入密码不一致'))} else {callback() // 就算校验成功,也需要callback}},trigger: 'blur'}]
}
</script><template><!-- 表单校验相关步骤(1) el-form => :model="ruleForm"      绑定的整个form的数据对象 { xxx, xxx, xxx }(2) el-form => :rules="rules"         绑定的整个rules规则对象  { xxx, xxx, xxx }(3) 表单元素 => v-model="ruleForm.xxx" 给表单元素,绑定form的子属性(4) el-form-item => prop配置生效的是哪个校验规则 (和rules中的字段要对应)--><el-row class="login-page"><el-col :span="6" :offset="3" class="form"><!-- 注册相关表单 --><el-form:model="formModel":rules="rules"ref="form"size="large"autocomplete="off"v-if="isRegister"><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username"><el-inputv-model="formModel.username":prefix-icon="User"placeholder="请输入用户名"></el-input></el-form-item><el-form-item prop="password"><el-inputv-model="formModel.password":prefix-icon="Lock"type="password"placeholder="请输入密码"></el-input></el-form-item><el-form-item prop="repassword"><el-inputv-model="formModel.repassword":prefix-icon="Lock"type="password"placeholder="请输入再次密码"></el-input></el-form-item></el-form><!-- 登录相关表单 -->... ...</el-col></el-row>
</template>

注册请求

<script setup>
import { userRegisterService } from '@/api/user.js'
const form = ref()// 整个的用于提交的form数据对象
const formModel = ref({username: 'dsj007',password: 'dsj007',repassword: ''
})// 注册
const register = async () => {// 注册成功之前,先进行校验,校验成功 → 请求,校验失败 → 自动提示await form.value.validate()await userRegisterService(formModel.value)ElMessage.success('注册成功')isRegister.value = false
}
</script><template><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册相关表单 --><el-form:model="formModel":rules="rules"ref="form"size="large"autocomplete="off"v-if="isRegister">... ...<el-form-item><el-button@click="register"class="button"type="primary"auto-insert-space>注册</el-button></el-form-item>... ...</el-form></el-col></el-row>
</template>
import request from '@/utils/request'// 注册接口
export const userRegisterService = ({ username, password, repassword }) =>request.post('/api/reg', { username, password, repassword })

解决报错

原因: 配置了组件自动按需导入, 使用ElMessage组件不需要手动导入, 但是eslint工具会认为该组件未声明

解决: eslintrc中声明全局变量名

module.exports = {... ...// 声明全局变量, 解决误报错globals: {ElMessage: 'readonly',ElMessageBox: 'readonly',ElLoading: 'readonly'}
}
5.0登录功能

字段校验

<script setup>
const rules = {username: [{ required: true, message: '请输入用户名', trigger: 'blur' },{ min: 5, max: 10, message: '用户名必须是 5-10位 的字符', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{pattern: /^\S{6,15}$/,message: '密码必须是 6-15位 的非空字符',trigger: 'blur'}],
}
</script><template><el-row class="login-page"><el-col :span="6" :offset="3" class="form"><!-- 登录相关表单 --><el-form:model="formModel":rules="rules"ref="form"size="large"autocomplete="off"v-else><el-form-item prop="username"><el-inputv-model="formModel.username":prefix-icon="User"placeholder="请输入用户名"></el-input></el-form-item><el-form-item prop="password"><el-inputv-model="formModel.password"name="password":prefix-icon="Lock"type="password"placeholder="请输入密码"></el-input></el-form-item></el-form></el-col></el-row>
</template>

登录请求

<script setup>
import { userLoginService } from '@/api/user.js'
import { ref, watch } from 'vue'
import { useUserStore } from '@/stores'
import { useRouter } from 'vue-router'const isRegister = ref(false)
const form = ref()// 整个的用于提交的form数据对象
const formModel = ref({username: 'dsj007',password: 'dsj007',repassword: ''
})// 登录
const userStore = useUserStore()
const router = useRouter()
const login = async () => {await form.value.validate()const res = await userLoginService(formModel.value)userStore.setToken(res.data.token)ElMessage.success('登录成功')router.push('/')
}// 切换的时候,重置表单内容
watch(isRegister, () => {formModel.value = {username: '',password: '',repassword: ''}
})
</script><template><el-row class="login-page">... ...<!-- 登录相关表单 --><el-form:model="formModel":rules="rules"ref="form"size="large"autocomplete="off"v-else>... ...<el-form-item><el-button@click="login"class="button"type="primary"auto-insert-space>登录</el-button></el-form-item></el-form></el-row>
</template>
import request from '@/utils/request'// 登录接口
export const userLoginService = ({ username, password }) =>request.post('/api/login', { username, password })
import { defineStore } from 'pinia'
import { ref } from 'vue'export const useUserStore = defineStore('big-user',() => {// token相关const token = ref('')const setToken = (newToken) => {token.value = newToken}const removeToken = () => {token.value = ''}return {token,setToken,removeToken,}},{persist: true}
)

首页

1.0需求分析
  1. 基本架子拆解(菜单组件的使用)
  2. 登录访问拦截
  3. 用户基本信息获取&渲染
  4. 退出功能
2.0静态拆解
<template><!-- el-menu 整个菜单组件:default-active="$route.path"  配置默认高亮的菜单项router  router选项开启,el-menu-item 的 index 就是点击跳转的路径el-menu-item 菜单项index="/article/channel" 配置的是访问的跳转路径,配合default-active的值,实现高亮--><el-container class="layout-container"><el-aside width="200px"><div class="el-aside__logo"></div><el-menuactive-text-color="#ffd04b"background-color="#232323":default-active="$route.path"text-color="#fff"router><el-menu-item index="/article/channel"><el-icon><Management /></el-icon><span>文章分类</span></el-menu-item><el-menu-item index="/article/manage"><el-icon><Promotion /></el-icon><span>文章管理</span></el-menu-item><el-sub-menu index="/user"><!-- 多级菜单的标题 - 具名插槽 title --><template #title><el-icon><UserFilled /></el-icon><span>个人中心</span></template><!-- 展开的内容 - 默认插槽 --><el-menu-item index="/user/profile"><el-icon><User /></el-icon><span>基本资料</span></el-menu-item><el-menu-item index="/user/avatar"><el-icon><Crop /></el-icon><span>更换头像</span></el-menu-item><el-menu-item index="/user/password"><el-icon><EditPen /></el-icon><span>重置密码</span></el-menu-item></el-sub-menu></el-menu></el-aside><el-container><el-header><div>黑马程序员:<strong>{{userStore.user.nickname || userStore.user.username}}</strong></div><el-dropdown placement="bottom-end" @command="handleCommand"><!-- 展示给用户,默认看到的 --><span class="el-dropdown__box"><el-avatar :src="userStore.user.user_pic || avatar" /><el-icon><CaretBottom /></el-icon></span><!-- 折叠的下拉部分 --><template #dropdown><el-dropdown-menu><el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item><el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item><el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item><el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></el-header><el-main><router-view></router-view></el-main><el-footer>大事件 ©2023 Created by 黑马程序员</el-footer></el-container></el-container>
</template>
3.0登录访问拦截

router4与rouer3的导航守卫写法略有区别

import { useUserStore } from '@/stores'
... ...
// 路由前置导航守卫
// 不再用next()方法放行-> 改用return返回值控制
// 返回值:
// 1. undefined || true: 放行
// 2. false: 阻止(返回来时地址)
// 3. '.login' || { path: '/login' } : 跳转页面
router.beforeEach((to) => {// 没有token且去往非登录页 => 强制去登录页const useStore = useUserStore()if (!useStore.token && to.path !== '/login') return '/login'
})
4.0基本信息获取&渲染
<script setup>
// 默认头像
import avatar from '@/assets/default.png'
import { useUserStore } from '@/stores'
import { onMounted } from 'vue'
// 获取用户信息
onMounted(() => {userStore.getUser()
})
</script><template><el-container class="layout-container"><!-- 侧边栏 -->... ...<el-container><!-- 头部区域 --><!-- 渲染用户信息 --><el-header><div>黑马程序员:<strong>{{userStore.user.nickname || userStore.user.username}}</strong></div><el-dropdown placement="bottom-end" @command="handleCommand"><!-- 展示给用户,默认看到的 --><span class="el-dropdown__box"><el-avatar :src="userStore.user.user_pic || avatar" /><el-icon><CaretBottom /></el-icon></span><!-- 折叠的下拉部分 --><template #dropdown>... ...</template></el-dropdown></el-header><!-- 中间区域 -->... ...<!-- 底部区域 -->... ...</el-container></el-container>
</template>
import request from '@/utils/request'// 获取用户基本信息
export const userGetInfoService = () => request.get('/my/userinfo')
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { userGetInfoService } from '@/api/user'export const useUserStore = defineStore('big-user',() => {// 用户基本信息const user = ref({})const getUser = async () => {const res = await userGetInfoService() // 请求获取数据user.value = res.data.data}const setUser = (obj) => {user.value = obj}return {user,getUser,setUser}},{persist: true}
)
5.0退出功能
<script setup>
import { useUserStore } from '@/stores'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()// 下拉菜单
const handleCommand = async (key) => {if (key === 'logout') {// 退出操作await ElMessageBox.confirm('你确认要进行退出么', '温馨提示', {type: 'warning',confirmButtonText: '确认',cancelButtonText: '取消'})// 清除本地的数据 (token + user信息)userStore.removeToken()userStore.setUser({})router.push('/login')} else {// 跳转操作router.push(`/user/${key}`)}
}
</script><template><el-container class="layout-container">... ...<el-container><el-header><div>黑马程序员:<strong>{{userStore.user.nickname || userStore.user.username}}</strong></div><el-dropdown placement="bottom-end" @command="handleCommand"><!-- 展示给用户,默认看到的 --><span class="el-dropdown__box"><el-avatar :src="userStore.user.user_pic || avatar" /><el-icon><CaretBottom /></el-icon></span><!-- 折叠的下拉部分 --><template #dropdown><el-dropdown-menu><el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item><el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item><el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item><el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></el-header>... ...</el-container></el-container>
</template>

文章分类页

需求

  1. 基本架子 PageContainer 封装
  2. 文章分类渲染 &loading处理
  3. 文章分类添加编辑
  4. 文章分类删除
1.0封装PageContainer

<script setup>
defineProps({title: {required: true,type: String}
})
</script><template><el-card class="page-container"><template #header><div class="header"><span>{{ title }}</span><slot name="extra"></slot></div></template><slot></slot></el-card>
</template><style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;justify-content: space-between;align-items: center;}
}
</style>
2.0文章渲染和loading
import request from '@/utils/request'
// 分类:获取文章分类
export const artGetChannelsService = () => request.get('/my/cate/list')
<script setup>
import { onMounted, ref } from 'vue'
import { Edit, Delete } from '@element-plus/icons-vue'
import { artGetChannelsService, artDelChannelService } from '@/api/article'
const loading = ref(false) // 控制loading// 分类列表
const channels = ref([])// 1.获取分类列表
const getChannels = async () => {loading.value = trueconst res = await artGetChannelsService()channels.value = res.data.dataloading.value = false
}onMounted(() => {getChannels()
})
</script><template><PageContainer title="文章分类"><template #extra><el-button @click="addChannel" type="primary">添加分类</el-button></template><template #default><!-- 2.渲染表格内容 --><!-- 3.添加表格loading  --><el-table v-loading="loading" :data="channels" style="width: 100%"><el-table-columntype="index"label="序号"width="100"></el-table-column><el-table-column prop="cate_name" label="分类名称"></el-table-column><el-table-column prop="cate_alias" label="分类别名"></el-table-column><el-table-column label="操作" width="150"><template #default="{ row }"><el-button:icon="Edit"circleplaintype="primary"></el-button><el-button:icon="Delete"circleplaintype="danger"></el-button></template></el-table-column>// 4.添加空提示<template #empty><el-empty description="没有数据"></el-empty></template></el-table></template></PageContainer>
</template>
3.0封装弹层组件
<script setup>
import { ref } from 'vue'
const dialogVisible = ref(false) // 控制弹出框// 组件对外暴露一个方法 open,基于open传来的参数,区分添加还是编辑
// open({})  => 表单无需渲染,说明是添加
// open({ id, cate_name, ... })  => 表单需要渲染,说明是编辑
// open调用后,可以打开弹窗
const open = (row) => {dialogVisible.value = true
}// 向外暴露方法
defineExpose({open
})
</script><template><el-dialogv-model="dialogVisible":title="formModel.id ? '编辑分类' : '添加分类'"width="30%">测试内容<template #footer><span class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="onSubmit"> 确认 </el-button></span></template></el-dialog>
</template>
<script setup>
import ChannelDialog from './component/ChannelDialog.vue'
const dialog = ref(null) // 弹窗组件DOM
// 添加
const addChannel = () => {dialog.value.open({})
}// 编辑
const onEdit = (row) => {dialog.value.open(row)
}onMounted(() => {getChannels()
})
</script><template><PageContainer title="文章分类"><template #extra><el-button @click="addChannel" type="primary">添加分类</el-button></template><template #default><el-table v-loading="loading" :data="channels" style="width: 100%"><el-table-column label="操作" width="150"><template #default="{ row }"><el-button:icon="Edit"circleplaintype="primary"@click="onEdit(row)"></el-button><el-button:icon="Delete"circleplaintype="danger"@click="onDel(row)"></el-button></template></el-table-column></el-table>// 弹层组件<ChannelDialog ref="dialog" @success="success"></ChannelDialog></template></PageContainer>
</template>
4.0完成添加编辑
<script setup>
import { artEditChannelService, artAddChannelService } from '@/api/article.js'
const dialogVisible = ref(false) // 控制弹出框
const formRef = ref() // 表单DOM, 用于预校验
// 表单数据
const formModel = ref({cate_name: '',cate_alias: ''
})
// 1.检验规则
const rules = {cate_name: [{ required: true, message: '请输入分类名称', trigger: 'blur' },{pattern: /^\S{1,10}$/,message: '分类名必须是 1-10 位的非空字符',trigger: 'blur'}],cate_alias: [{ required: true, message: '请输入分类别名', trigger: 'blur' },{pattern: /^[a-zA-Z0-9]{1,15}$/,message: '分类名必须是 1-15 位的字母或数字',trigger: 'blur'}]
}// 2.表单提交
const emit = defineEmits(['success'])
const onSubmit = async () => {// 提交前校验await formRef.value.validate()// 区分是否编辑const isEdit = formModel.value.idif (isEdit) {await artEditChannelService(formModel.value)ElMessage.success('编辑成功')} else {await artAddChannelService(formModel.value)ElMessage.success('添加成功')}dialogVisible.value = false// 通知父组件更新数据emit('success')
}
</script><template><el-dialogv-model="dialogVisible":title="formModel.id ? '编辑分类' : '添加分类'"width="30%"><el-formref="formRef":model="formModel":rules="rules"label-width="100px"style="padding-right: 30px"><el-form-item label="分类名称" prop="cate_name"><el-inputv-model="formModel.cate_name"placeholder="请输入分类名称"></el-input></el-form-item><el-form-item label="分类别名" prop="cate_alias"><el-inputv-model="formModel.cate_alias"placeholder="请输入分类别名"></el-input></el-form-item></el-form><template #footer><span class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="onSubmit"> 确认 </el-button></span></template></el-dialog>
</template>
import request from '@/utils/request'
// 分类:添加文章分类
export const artAddChannelService = (data) => request.post('/my/cate/add', data)
// 分类:编辑文章分类
export const artEditChannelService = (data) => request.put('/my/cate/info', data)
<script setup>
... ...
// 操作成功,更新数据
const success = () => {getChannels()
}
</script><template><PageContainer title="文章分类">... ...<ChannelDialog ref="dialog" @success="success"></ChannelDialog></template></PageContainer>
</template>
5.0删除文章分类
import request from '@/utils/request'
// 分类:删除文章分类
export const artDelChannelService = (id) =>request.delete('/my/cate/del', {params: { id }
})
<script setup>
import { artGetChannelsService, artDelChannelService } from '@/api/article'// 删除
const onDel = async (row) => {await ElMessageBox.confirm('你确认要删除该分类么', '温馨提示', {type: 'warning',confirmButtonText: '确认',cancelButtonText: '取消'})await artDelChannelService(row.id)ElMessage.success('删除成功')getChannels()
}
</script><template><PageContainer title="文章分类"><template #default><el-table v-loading="loading" :data="channels" style="width: 100%"><el-table-column label="操作" width="150"><template #default="{ row }"><el-button... ...@click="onDel(row)"></el-button></template></el-table-column></el-table></template></PageContainer>
</template>

文章管理页

需求

  1. 文章列表渲染(搜索 & 分页)
  2. 添加文章(抽屉&文件上传&富文本)
  3. 编辑文章(共用抽屉)
  4. 删除文章
1.0静态结构搭建

<template><PageContainer title="文章管理"><template #extra><el-button type="primary" @click="addArticle">添加文章</el-button></template><template #default><!-- 筛选 --><el-form inline><el-form-item label="文章分类:"><!--vue2: v-model => :value + @input --><!--vue3: v-model => :modelValue + @update:modelValue --><ChannelSelect v-model="params.cate_id"></ChannelSelect></el-form-item><el-form-item label="发布状态:"><el-select v-model="params.state"><el-option label="已发布" value="已发布"></el-option><el-option label="草稿" value="草稿"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="onSearch">搜索</el-button><el-button @click="onReset">重置</el-button></el-form-item></el-form><!-- 表格 --><el-table :data="articleList" v-loading="loading"><el-table-column label="文章标题"><template #default="{ row }"><el-link type="primary" :underline="false">{{ row.title }}</el-link></template></el-table-column><el-table-column label="分类" prop="cate_name"></el-table-column><el-table-column label="发表时间" prop="pub_date"><template #default="{ row }">{{ formatTime(row.pub_date) }}</template></el-table-column><el-table-column label="状态" prop="state"></el-table-column><el-table-column label="操作"><template #default="{ row }"><!-- 编辑按钮 --><el-buttoncircleplaintype="primary":icon="Edit"@click="onEdit(row)"></el-button><el-buttoncircleplaintype="danger":icon="Delete"@click="onDel(row)"></el-button></template></el-table-column></el-table><!-- 分页 --><el-paginationv-model:current-page="params.pagenum"v-model:page-size="params.pagesize":page-sizes="[2, 3, 5, 10]":background="true"layout="jumper,total,sizes,prev,pager,next":total="total"@size-change="onSizeChange"@current-change="onCurrentChange"style="margin-top: 20px; justify-content: flex-end"></el-pagination></template></PageContainer>
</template>
2.0中文国际化处理
<script setup>
import zhCn from 'element-plus/dist/locale/zh-cn'
</script><template><!-- 语言包配置 --><el-config-provider :locale="zhCn"><RouterView></RouterView></el-config-provider>
</template>
3.0文章分类下拉组件
<script setup>
import { ref, onMounted } from 'vue'
import { artGetChannelsService } from '@/api/article'defineProps({modelValue: {type: [Number, String]},width: {type: String}
})
const emit = defineEmits(['update:modelValue'])
// 分类列表
const channelList = ref([])
// 获取分类列表
const getChannelList = async () => {const res = await artGetChannelsService()channelList.value = res.data.data
}onMounted(() => {getChannelList()
})
</script><template><el-select:modelValue="modelValue"@update:modelValue="emit('update:modelValue', $event)":style="{ width }"><el-optionv-for="item in channelList":key="item.id":label="item.cate_name":value="item.id"></el-option></el-select>
</template>
<template><PageContainer title="文章管理"><template #default><!-- 筛选 --><el-form inline><el-form-item label="文章分类:"><!--vue2: v-model => :value + @input --><!--vue3: v-model => :modelValue + @update:modelValue --><ChannelSelect v-model="params.cate_id"></ChannelSelect></el-form-item></el-form></template></PageContainer>
</template>
4.0文章列表渲染
<script setup>
import { ref, onMounted } from 'vue'
import ChannelSelect from './component/ChannelSelect.vue'
import { formatTime } from '@/utils/format.js'
import { artGetListService, artDelService } from '@/api/article.js'
const articleList = ref([]) // 文章列表
const total = ref(0) // 总条数
const loading = ref(false) // loading状态// 请求参数对象
const params = ref({pagenum: 1,pagesize: 5,cate_id: '',state: ''
})// 基于params参数,获取文章列表
const getArticleList = async () => {loading.value = trueconst res = await artGetListService(params.value)articleList.value = res.data.datatotal.value = res.data.totalloading.value = false
}// 每页展示条数变化
const onSizeChange = (size) => {// 每页条数变化后回到第一页params.value.pagenum = 1// 更新每页条数params.value.pageSize = sizegetArticleList()
}// 页码变化
const onCurrentChange = (page) => {params.value.pagenum = pagegetArticleList()
}// 搜索
const onSearch = () => {params.value.pagenum = 1getArticleList()
}// 重置搜索
const onReset = () => {params.value.pagenum = 1params.value.cate_id = ''params.value.state = ''getArticleList()
}onMounted(() => {getArticleList()
})
</script><template><PageContainer title="文章管理"><template #default><!-- 表格 --><el-table :data="articleList" v-loading="loading"><el-table-column label="文章标题"><template #default="{ row }"><el-link type="primary" :underline="false">{{ row.title }}</el-link></template></el-table-column><el-table-column label="分类" prop="cate_name"></el-table-column><el-table-column label="发表时间" prop="pub_date"><!-- 时间格式化 --><template #default="{ row }">{{ formatTime(row.pub_date) }}</template></el-table-column><el-table-column label="状态" prop="state"></el-table-column></el-table><!-- 分页 --><el-paginationv-model:current-page="params.pagenum"v-model:page-size="params.pagesize":page-sizes="[2, 3, 5, 10]":background="true"layout="jumper,total,sizes,prev,pager,next":total="total"@size-change="onSizeChange"@current-change="onCurrentChange"style="margin-top: 20px; justify-content: flex-end"></el-pagination></template></PageContainer>
</template>
import { dayjs } from 'element-plus'export const formatTime = (time) => dayjs(time).format('YYYY年MM月DD日')
5.0封装抽屉组件
<script setup>
import { ref } from 'vue'
// 控制抽屉显示隐藏
const visibleDrawer = ref(false)// 组件对外暴露一个方法 open,基于open传来的参数,区分添加还是编辑
// open({})  => 表单无需渲染,说明是添加
// open({ id, ..., ... })  => 表单需要渲染,说明是编辑
// open调用后,可以打开抽屉
const editorRef = ref()
const open = async (row) => {visibleDrawer.value = true // 显示抽屉
}defineExpose({open
})
</script><template><el-drawerv-model="visibleDrawer":title="formModel.id ? '编辑文章' : '添加文章'"direction="rtl"size="50%">测试内容</el-drawer>
</template>
6.0添加文章

a.嵌入常规表单

<script setup>
import { ref } from 'vue'
import ChannelSelect from './channelselect.vue'
// 控制抽屉显示隐藏
const visibleDrawer = ref(false)
// 默认数据(用于重置)
const defaultForm = {title: '', // 标题cate_id: '', // 分类idcover_img: '', // 封面图片 file 对象content: '', // string 内容state: '' // 状态
}// 准备数据(用于提交)
const formModel = ref({ ...defaultForm })// open调用后,可以打开抽屉
const editorRef = ref()
const open = async (row) => {visibleDrawer.value = true // 显示抽屉if (row.id) {// 编辑操作// 传过来的数据row, 无法直接回显// 基于 row.id 发送请求,获取编辑对应的详情数据,进行回显console.log('编辑回显', row.id)} else {// 添加操作// 重置form数据,基于默认的数据formModel.value = { ...defaultForm } // 这里重置了表单的数据,但是图片上传img地址,富文本编辑器内容 => 需要手动重置imgUrl.value = ''editorRef.value.setHTML('')}
}defineExpose({open
})
</script><template><el-drawerv-model="visibleDrawer":title="formModel.id ? '编辑文章' : '添加文章'"direction="rtl"size="50%"><!-- 发表文章表单 --><el-form :model="formModel" ref="formRef" label-width="100px"><el-form-item label="文章标题" prop="title"><el-input v-model="formModel.title" placeholder="请输入标题"></el-input></el-form-item><el-form-item label="文章分类" prop="cate_id"><channel-selectv-model="formModel.cate_id"width="100%"></channel-select></el-form-item><el-form-item label="文章封面" prop="cover_img">文件上传站位</el-form-item><el-form-item label="文章内容" prop="content"><div class="editor">富文本编辑器站位</div></el-form-item><el-form-item><el-button @click="onPublish('已发布')" type="primary">发布</el-button><el-button @click="onPublish('草稿')" type="info">草稿</el-button></el-form-item></el-form></el-drawer>
</template>
<script setup>
const drawer = ref()
// 添加
const addArticle = () => {drawer.value.open({})
}// 编辑
const onEdit = (row) => {drawer.value.open(row)
}
</script><template><PageContainer title="文章管理"><template #extra><el-button type="primary" @click="addArticle">添加文章</el-button></template><template #default><!-- 表格 --><el-table :data="articleList" v-loading="loading"><el-table-column label="操作"><template #default="{ row }"><!-- 编辑按钮 --><el-buttoncircleplaintype="primary":icon="Edit"@click="onEdit(row)"></el-button></el-table-column></el-table><!-- 添加/编辑抽屉 --><ArticleEdit ref="drawer" @success="onSuccess"></ArticleEdit></template></PageContainer>
</template>

b.嵌入上传文件(本地预览)

文件上传有两种提交方式, 一种是前端本地预览, 一种是文件上传后预览, 本项目采用前端本地预览

有两种方式可以实现本地预览:

方式1:URL.createObjectURL(...) 创建一个本地预览的地址, 最终提交的是原始的file对象

方式2: 生成base64图片实现本地预览, 本项目/个人中心页/更换头像, 可以前去查看

<script setup>
// 图片上传相关逻辑
const imgUrl = ref('')
const onSelectFile = (uploadFile) => {// 创建本地预览地址imgUrl.value = URL.createObjectURL(uploadFile.raw) // 立刻将图片对象,存入 formModel.value.cover_img 将来用于提交formModel.value.cover_img = uploadFile.raw
}
</script><template><el-drawerv-model="visibleDrawer":title="formModel.id ? '编辑文章' : '添加文章'"direction="rtl"size="50%"><!-- 发表文章表单 --><el-form :model="formModel" ref="formRef" label-width="100px"><el-form-item label="文章封面" prop="cover_img"><!-- 本项目采用前端本地预览方案 只需要做前端的本地预览图片即可,无需在提交前上传图片所以需要关闭 element-plus 的自动上传,不需要配置 action 等参数语法:URL.createObjectURL(...) 创建本地预览的地址,来预览--><el-uploadclass="avatar-uploader":show-file-list="false":auto-upload="false":on-change="onSelectFile"><img v-if="imgUrl" :src="imgUrl" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload></el-form-item></el-form></el-drawer>
</template>

c.嵌入富文本编辑器

官网: VueQuill | Rich Text Editor Component for Vue 3

安装: pnpm add @vueup/vue-quill@latest

<script setup>
// 1.引入编辑器组件和样式
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
</script><template><el-drawer><!-- 发表文章表单 --><el-form :model="formModel" ref="formRef" label-width="100px"><el-form-item label="文章内容" prop="content"><div class="editor"><!--  --><!--2.嵌入文本编辑器content-type="html  设置编辑器的文本格式(必须)theme="snow"   设置编辑器主题样式(必须)--><quill-editorref="editorRef"v-model:content="formModel.content"content-type="html"theme="snow"></quill-editor></div></el-form-item></el-form></el-drawer>
</template><style lang="scss" scoped>
<!-- 3.修改默认样式 -->
.editor {width: 100%;:deep(.ql-editor) {min-height: 200px;}
}
</style>

d.完成表单提交

<script setup>
// 控制抽屉显示隐藏
const visibleDrawer = ref(false)// 默认数据
const defaultForm = {title: '', // 标题cate_id: '', // 分类idcover_img: '', // 封面图片 file 对象content: '', // string 内容state: '' // 状态
}// 准备数据
const formModel = ref({ ...defaultForm })// 提交
const emit = defineEmits(['success'])
const onPublish = async (state) => {// 将已发布还是草稿状态,存入 formModelformModel.value.state = state// 注意:当前接口,需要的参数是 formData对象格式// 将普通对象 => 转换成 => formData对象const fd = new FormData()for (let key in formModel.value) {fd.append(key, formModel.value[key])}// 发请求if (formModel.value.id) {// 编辑操作console.log('编辑操作')} else {// 添加操作await artPublishService(fd)ElMessage.success('添加成功')visibleDrawer.value = false// 通知到父组件,添加成功了emit('success', 'add')}
}
</script><template><el-drawer><!-- 发表文章表单 --><el-form :model="formModel" ref="formRef" label-width="100px"><el-form-item><el-button @click="onPublish('已发布')" type="primary">发布</el-button><el-button @click="onPublish('草稿')" type="info">草稿</el-button></el-form-item></el-form></el-drawer>
</template>
<script setup>
const total = ref(0) // 总条数
// 请求参数对象
const params = ref({pagenum: 1,pagesize: 5,cate_id: '',state: ''
})// 添加或者编辑 成功的回调
const onSuccess = (type) => {if (type === 'add') {// 如果是添加,渲染最后一页, 方便用户看到// 计算最大页码值const lastPage = Math.ceil((total.value + 1) / params.value.pagesize)// 更新成最大页码数,再渲染params.value.pagenum = lastPage}// 更新列表数据getArticleList()
}
</script>
7.0编辑文章
<script setup>
import { baseURL } from '@/utils/request'
import axios from 'axios'
// 提交
const emit = defineEmits(['success'])
const onPublish = async (state) => {// 发请求if (formModel.value.id) {// 编辑操作await artEditService(fd)ElMessage.success('修改成功')visibleDrawer.value = falseemit('success', 'edit')} else {// 添加操作... ...}
}// open调用后,可以打开抽屉
const editorRef = ref()
const open = async (row) => {visibleDrawer.value = true // 显示抽屉if (row.id) {// 编辑操作// 需要基于 row.id 发送请求,获取编辑对应的详情数据,进行回显const res = await artGetDetailService(row.id)formModel.value = res.data.data// 返回的图片地址不完整, 所以需要手动补全, 单独处理回显imgUrl.value = baseURL + formModel.value.cover_img// 注意:提交给后台,需要的数据格式,是file对象格式// 需要将网络图片地址 => 转换成 file对象,存储起来, 将来便于提交const file = await imageUrlToFileObject(imgUrl.value,formModel.value.cover_img)formModel.value.cover_img = file} else {// 新增操作... ...}
}// 将网络图片地址转换为 File 对象的函数
async function imageUrlToFileObject(imageUrl, filename) {try {// 使用 Axios 下载图片数据const response = await axios.get(imageUrl, { responseType: 'arraybuffer' })// 将下载的数据转换成 Blob 对象const blob = new Blob([response.data], {type: response.headers['content-type']})// 创建 File 对象const file = new File([blob], filename, {type: response.headers['content-type']})return file} catch (error) {console.error('Error converting image URL to File object:', error)return null}
}
</script>

个人中心页

1.0认识AI助手

AI互动的过程中, 容易出现的问题

  1. AI未能理解问题的核心要点
  2. AI的回答过于宽泛或者过于具体
  3. AI提供了错误的信息或观点
  4. AI未能提供有价值的建议或解决方案

通过优化Prompt(提示词), 提高AI问答的准确性

  1. 明确提问: 关键字越准确,回答越精准
  2. 细化需求: 大问题拆分小问题
  3. 添加背景信息: 预设角色/描述场景
  4. 适当引导: 例如/请注意/请使用
  5. 限制范围: 长度/条数/范围

ChatGPT: 科学上网, 官网注册, 会有IP/手机号限制

国产ChatGPT: 谷歌搜索chatgpt免费网站列表

一些推荐: https://github.com/LiLittleCat/awesome-free-chatgpt

Copilot代码生成

安装: 登录github, 适用Copilot, 打开vscode, 搜索安装插件 Copilot

使用: 删除键: 不接受, Tab键: 接受, Ctrl+enter: 查看更多方案

2.0基本资料
<script setup>
import PageContainer from '@/components/PageContainer.vue'
import { ref } from 'vue'
import { useUserStore } from '@/stores'
import { userUpdateInfoService } from '@/api/user'// 数据回显
// 使用仓库中数据的初始值 (无需响应式) 解构无问题
const {user: { email, id, nickname, username },getUser
} = useUserStore()// 表单数据
const form = ref({id,username,nickname,email
})// 校验规则
const rules = ref({nickname: [{ required: true, message: '请输入用户昵称', trigger: 'blur' },{pattern: /^\S{2,10}/,message: '昵称长度在2-10个非空字符',trigger: 'blur'}],email: [{ required: true, message: '请输入用户邮箱', trigger: 'blur' },{type: 'email',message: '请输入正确的邮箱格式',trigger: ['blur', 'change']}]
})// 表单提交
const formRef = ref()
const submitForm = async () => {// 等待校验结果(预校验)await formRef.value.validate()// 提交修改await userUpdateInfoService(form.value)// 通知 user 模块,进行数据的更新getUser()// 提示用户ElMessage.success('修改成功')
}
</script><template><page-container title="基本资料"><template #default><!-- 表单部分 --><el-form ref="formRef" :model="form" :rules="rules" label-width="100px"><el-form-item label="登录名称"><el-input v-model="form.username" disabled></el-input></el-form-item><el-form-item label="用户昵称" prop="nickname"><el-input v-model="form.nickname"></el-input></el-form-item><el-form-item label="用户邮箱" prop="email"><el-input v-model="form.email"></el-input></el-form-item><el-form-item><el-button type="primary" @click="submitForm">提交修改</el-button></el-form-item></el-form></template></page-container>
</template>
3.0更换头像

new FileReader() 创建base64格式的图片, 最终提交的是创建出来的base64图片

<script setup>
import { ref } from 'vue'
import { Plus, Upload } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores'
import { userUpdateAvatarService } from '@/api/user'
const userStore = useUserStore()
const imgUrl = ref(userStore.user.user_pic) //回显头像
const uploadRef = ref() // 上传组件的ref// 选择图片
const onSelectFile = (uploadFile) => {// 基于 FileReader 读取图片做预览const reader = new FileReader()reader.readAsDataURL(uploadFile.raw)// 得到base64格式的图片reader.onload = () => {imgUrl.value = reader.result}
}// 保存头像
const onUpdateAvatar = async () => {// 发送请求更新头像await userUpdateAvatarService(imgUrl.value)// userStore 重新渲染await userStore.getUser()// 提示用户ElMessage.success('头像更新成功')
}
</script><template><page-container title="更换头像"><el-uploadref="uploadRef":auto-upload="false"class="avatar-uploader":show-file-list="false":on-change="onSelectFile"><img v-if="imgUrl" :src="imgUrl" class="avatar" /><el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon></el-upload><br /><el-button@click="uploadRef.$el.querySelector('input').click()"type="primary":icon="Plus"size="large">选择图片</el-button><el-button@click="onUpdateAvatar"type="success":icon="Upload"size="large">上传头像</el-button></page-container>
</template>
4.0重置密码

<script setup>
import { ref } from 'vue'
import { userUpdatePasswordService } from '@/api/user'
import { useUserStore } from '@/stores'
import { useRouter } from 'vue-router'
// 表单 ref
const formRef = ref()
// 表单数据
const pwdForm = ref({old_pwd: '',new_pwd: '',re_pwd: ''
})// 表单校验
const checkDifferent = (rule, value, callback) => {// 校验新密码和原密码不能一样if (value === pwdForm.value.old_pwd) {callback(new Error('新密码不能与原密码一样'))} else {callback()}
}
// 表单校验
const checkSameAsNewPwd = (rule, value, callback) => {// 校验确认密码必须和新密码一样if (value !== pwdForm.value.new_pwd) {callback(new Error('确认密码必须和新密码一样'))} else {callback()}
}
// 表单校验
const rules = ref({old_pwd: [{ required: true, message: '请输入原密码', trigger: 'blur' },{ min: 6, max: 15, message: '原密码长度在6-15位之间', trigger: 'blur' }],new_pwd: [{ required: true, message: '请输入新密码', trigger: 'blur' },{ min: 6, max: 15, message: '新密码长度在6-15位之间', trigger: 'blur' },{ validator: checkDifferent, trigger: 'blur' }],re_pwd: [{ required: true, message: '请再次输入新密码', trigger: 'blur' },{ min: 6, max: 15, message: '确认密码长度在6-15位之间', trigger: 'blur' },{ validator: checkSameAsNewPwd, trigger: 'blur' }]
})// 表单提交
const userStore = useUserStore()
const router = useRouter()
const submitForm = async () => {await formRef.value.validate() // 表单预校验await userUpdatePasswordService(pwdForm.value)ElMessage.success('密码修改成功')// 密码修改成功后,退出重新登录// 清空本地存储的 token 和 个人信息userStore.setToken('')userStore.setUser({})// 拦截登录router.push('/login')
}// 表单重置
const resetForm = () => {formRef.value.resetFields()
}
</script><template><page-container title="修改密码"><el-row><el-col :span="12"><el-formref="formRef":model="pwdForm":rules="rules"label-width="100px"><el-form-item label="原密码" prop="old_pwd"><el-input v-model="pwdForm.old_pwd" show-password></el-input></el-form-item><el-form-item label="新密码" prop="new_pwd"><el-input v-model="pwdForm.new_pwd" show-password></el-input></el-form-item><el-form-item label="确认密码" prop="re_pwd"><el-input v-model="pwdForm.re_pwd" show-password></el-input></el-form-item><el-form-item><el-button type="primary" @click="submitForm">修改密码</el-button><el-button @click="resetForm">重置</el-button></el-form-item></el-form></el-col></el-row></page-container>
</template>
关键字:[vue3]大事件-admin

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: