Vue3-项目

大事件项目

eslint+prettierrc

/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
  root: true,
  extends: [
    'plugin:vue/vue3-essential',
    'eslint:recommended',
    '@vue/eslint-config-prettier/skip-formatting'
  ],
  parserOptions: {
    ecmaVersion: 'latest'
  },
  rules: {
    //前置条件
    //关闭prettier  format on save 关闭
    //安装eslints
    '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 解构的校验
    // 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
    'no-undef': 'error'
  },
  globals: {
    ElMessage: 'readonly',
    ElMessageBox: 'readonly',
    ElLoading: 'readonly'
  }
}

prettierrc.json

{

  "$schema": "https://json.schemastore.org/prettierrc";,
  "semi": false,
  "tabWidth": 2,
  "singleQuote": true,
  "printWidth": 100,
  "trailingComma": "none"
}

husky检查工作流

  1. git初始化 git init

  2. 初始化 husky 工具配置  https://typicode.github.io/husky/

pnpm dlx husky-init && pnpm install
  1. 修改 .husky/pre-commit 文件

pnpm lint

问题:默认进行的是全量检查,耗时问题,历史问题。

<br/>

暂存区的eslint校验

lint-staged 配置

  1. 安装

pnpm i lint-staged -D
  1. 配置 package.json

{
  // ... 省略 ...
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix"
    ]
  }
}

{
  "scripts": {
    // ... 省略 ...
    "lint-staged": "lint-staged"
  }
}

  1. 修改 .husky/pre-commit 文件

pnpm lint-staged

路由

import { createRouter, createWebHistory } from 'vue-router'

// createRouter 创建路由实例,===> new VueRouter()
// 1. history模式: createWebHistory()   http://xxx/user
// 2. hash模式: createWebHashHistory()  http://xxx/#/user

// vite 的配置 import.meta.env.BASE_URL 是路由的基准地址,默认是 ’/‘
// https://vitejs.dev/guide/build.html#public-base-path

// 如果将来你部署的域名路径是:http://xxx/my-path/user
// vite.config.ts  添加配置  base: my-path,路由这就会加上 my-path 前缀了

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: []
})

export default router

// 组合式api
// 1.获取路由对象router useRouter
// const router = useRouter()
// 2.获取路由参数 route useRoute
// const route = userRoute()
<el-button @click="userStore.setToken('Bearer sidnflsngsskfanlfsa')">
  登录
</el-button>
  <el-button @click="userStore.removeToken()">退出</el-button>

引入 element-ui 组件库

官方文档: https://element-plus.org/zh-CN/

  • 安装

$ pnpm add element-plus

自动按需:

  1. 安装插件

pnpm add -D unplugin-vue-components unplugin-auto-import
  1. 然后把下列代码插入到你的 ViteWebpack 的配置文件中

...
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    ...
    AutoImport({
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      resolvers: [ElementPlusResolver()]
    })
  ]
})

  1. 直接使用

<template>
  <div>
    <el-button type="primary">Primary</el-button>
    <el-button type="success">Success</el-button>
    <el-button type="info">Info</el-button>
    <el-button type="warning">Warning</el-button>
    <el-button type="danger">Danger</el-button>
    ...
  </div>
</template>

彩蛋:默认 components 下的文件也会被自动注册~

封装request

import axios from 'axios'
import { useUserStore } from '@/stores'
import { ElMessage } from 'element-plus'
import router from '@/router' //js文件中导入router
const baseURL = 'http://big-event-vue-api-t.itheima.net';

const instance = axios.create({
  // TODO 1. 基础地址,超时时间
  baseURL,
  timeout: 10000
})

// 请求拦截器
instance.interceptors.request.use(
  (config) => {
    // TODO 2. 携带token
    const userStore = useUserStore()
    if (userStore.token) {
      config.headers.Authorization = userStore.token
    }
    return config
  },
  (err) => Promise.reject(err)
)

// 响应拦截器
instance.interceptors.response.use(
  (res) => {
    // TODO 4. 摘取核心响应数据
    if (res.data.code === 0) {
      return res
    }
    // TODO 3. 处理业务失败
    // 处理业务失败,给错误提示,抛出错误
    ElMessage.error(res.data.message || '服务异常')
    return Promise.reject(res.data)
  },
  (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 instance
export { baseURL }

注册登录表单校验

就四个点:formnModel(表单对象)、ruler(规则)、v-model(绑定表单对象某一属性)、prop(对应规则)

结构相关:
el-row 一行 一行分为24份
el-col 表示列 
  (1):span="12" 表示一行内12份 50% 
  (2):span="6" 表示在一行内6份 25%
  (3):offset="3" 表示左侧的margin份数

el-form 整个表单组件
el-form-item 表单的一行(一个表单域)
el-input 表单元素(输入框)

校验相关:
(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中的字段对应)

// 整个表单的校验规则
// 1.非空校验 required  message消息提示 trigger 触发时机 blur和change
// 2.长度校验 min:xxx max:xxx
// 3.正则校验 pattern:正则校验  \S非空字符

自定义校验

注册预校验

elmessage等三个组件 不用局部导入直接使用

module.exports = {
  ...
  globals: {
    ElMessage: 'readonly',
    ElMessageBox: 'readonly',
    ElLoading: 'readonly'
  }
}

登录功能

实现登录校验

【需求说明】给输入框添加表单校验

  1. 用户名不能为空,用户名必须是5-10位的字符,失去焦点 和 修改内容时触发校验

  2. 密码不能为空,密码必须是6-15位的字符,失去焦点 和 修改内容时触发校验

操作步骤:

  1. model 属性绑定 form 数据对象,直接绑定之前提供好的数据对象即可

<el-form :model="formModel" >
  1. rules 配置校验规则,共用注册的规则即可

<el-form :rules="rules" >
  1. v-model 绑定 form 数据对象的子属性

<el-input
  v-model="formModel.username"
  :prefix-icon="User"
  placeholder="请输入用户名"
></el-input>
<el-input
  v-model="formModel.password"
  name="password"
  :prefix-icon="Lock"
  type="password"
  placeholder="请输入密码"
></el-input>
  1. prop 绑定校验规则

<el-form-item prop="username">
  <el-input
    v-model="formModel.username"
    :prefix-icon="User"
    placeholder="请输入用户名"
  ></el-input>
</el-form-item>
...
  1. 切换的时候重置

watch(isRegister, () => {
  formModel.value = {
    username: '',
    password: '',
    repassword: ''
  }
})

<br/>

登录前的预校验 & 登录成功

【需求说明1】登录之前的预校验

  • 登录请求之前,需要对用户的输入内容,进行校验

  • 校验通过才发送请求

【需求说明2】登录功能

  1. 封装登录API,点击按钮发送登录请求

  2. 登录成功存储token,存入pinia 和 持久化本地storage

  3. 跳转到首页,给提示

【测试账号】

  • 登录的测试账号:  shuaipeng

  • 登录测试密码:  123456

PS: 每天账号会重置,如果被重置了,可以去注册页,注册一个新号

<br/>

实现步骤:

  1. 注册事件,进行登录前的预校验 (获取到组件调用方法)

<el-form ref="form">
    
const login = async () => {
  await form.value.validate()
  console.log('开始登录')
}
  1. 封装接口 API

export const userLoginService = ({ username, password }) =>
  request.post('api/login', { username, password })
  1. 调用方法将 token 存入 pinia 并 自动持久化本地

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('/')
}

渲染用户列表

退出功能

返回promise都需要async await

文章分类

pageContainer

文章分类渲染

添加/编辑分类-弹层封装

弹窗表单规则验证

效果:

编辑回显

<br/>

添加分类

删除分类

// 删除文章分类
export const artDelChannelService = (id) =>
  // get delete 第二个参数都是config params
  request.delete('/my/cate/del', {
    params: { id }
  })
//删除按钮绑onDelChannel方法
const onDelChannel = async (row) => {
  await ElMessageBox.confirm('你确认要删除吗?', '温馨提示', {
    type: 'waring',
    confirmButtonText: '确认',
    cancelButtonText: '取消'
  })
  await artDelChannelService(row.id)
  ElMessage.success('输出成功')
  getChannelList()
}

文章管理

<!-- 表单区域 -->
    <el-form inline>
      <el-form-item label="文章分类">
        <!-- label展示给用户,value是提交服务器 -->
        <el-select style="width: 150px">
          <el-option label="新闻" value="1110"></el-option>
          <el-option label="体育" value="129"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="发布状态">
        <el-select style="width: 150px">
          <el-option label="已发布" value="已发布"></el-option>
          <el-option label="草稿" value="草稿"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary">搜索</el-button>
        <el-button>重置</el-button>
      </el-form-item>
    </el-form>

<br/>

const getArtList = async () => {
  loading.value = true
  const res = await artGetListService(params.value)
  articleList.value = res.data.data
  total.value = res.data.total
  loading.value = false
}
  接收数据给articleList
  articleList
  表格el-table :data 绑定
  下面的el-table-column 的prop绑定对应渲染数据

配置中文

<script setup>
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
</script>

<template>
  <div>
    <el-config-provider :locale="zhCn">
      <router-view></router-view
    ></el-config-provider>
  </div>
</template>

<style scoped></style>

文章分类组件封装

文章列表渲染

处理分页逻辑

添加loading效果

请求前后一开一关

  1. 准备数据

const loading = ref(false)
  1. el-table上面绑定

<el-table v-loading="loading" > ... </el-table>
  1. 发送请求时添加 loading

const getArticleList = async () => {
  loading.value = true
    
  ...
  
  loading.value = false
}
getArticleList()

搜索/重置功能

总的来说就是改变params的条件

实现不同的渲染

传给articleList 进而传给页面

// 搜索功能  ->安装最新的条件检索 从第一页开始展示
const onSearch = () => {
  params.value.pagenum = 1 //重置页面  ,v-model="params.state"通过id绑定请求 故不用传其他值
  getArtList()
}
//重置功能  条件清空重新检索渲染
const onReset = () => {
  params.value.pagenum = 1
  params.value.cate_id = ''
  params.value.state = ''
  getArtList()
}

文章新增

封装抽屉组件

直接设置无效

<channel-select
          v-model="formModel.cate_id"
          width="100%"
        ></channel-select>

子传父

上传文件(预览图片)
<el-upload
  class="avatar-uploader"
  :auto-upload="false"
  :show-file-list="false"
  :on-change="onUploadFile"
  >
  <img v-if="imgUrl" :src="imgUrl" class="avatar" />
  <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>

富文本编辑器 [ vue-quill ]

官网地址:https://vueup.github.io/vue-quill/

  1. 安装包

pnpm add @vueup/vue-quill@latest
  1. 注册成局部组件

import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
  1. 页面中使用绑定

<div class="editor">
  <quill-editor
    theme="snow"
    v-model:content="formModel.content"
    contentType="html"
  >
  </quill-editor>
</div>
  1. 样式美化

.editor {
  width: 100%;
  :deep(.ql-editor) {
    min-height: 200px;
  }
}

添加文章

文章编辑如果是编辑操作,一打开抽屉,就需要发送请求,获取数据进行回显

  1. 封装接口,根据 id 获取详情数据

export const artGetDetailService = (id) =>
  request.get('my/article/info', { params: { id } })
  1. 页面中调用渲染

const open = async (row) => {
  visibleDrawer.value = true
  if (row.id) {
    console.log('编辑回显')
    const res = await artGetDetailService(row.id)
    formModel.value = res.data.data
    imgUrl.value = baseURL + formModel.value.cover_img
    // 提交给后台,需要的是 file 格式的,将网络图片,转成 file 格式
    // 网络图片转成 file 对象, 需要转换一下
    formModel.value.cover_img = await imageUrlToFile(imgUrl.value, formModel.value.cover_img)
  } else {
    console.log('添加功能')
    ...
  }
}

个人中心

修改个人信息

<script setup>
import { userUpdateInfoService } from '@/api/user'
import { useUserStore } from '@/stores'
import { ref } from 'vue'
const {
  user: { username, nickname, email, id },
  getUser
} = useUserStore()

const userInfo = ref({ username, nickname, email, id })

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' }
  ]
})
const formRef = ref(null)
const submitForm = async () => {
  // 等待校验结果
  await formRef.value.validate()
  //提交修改
  await userUpdateInfoService(userInfo.value)
  // 通知user模块更新
  getUser()
  // 提示用户修改成功
  ElMessage.success('修改成功')
}
</script>

<template>
  <page-container title="基本资料">
    <el-row>
      <el-col :span="12">
        <el-form
          :model="userInfo"
          :rules="rules"
          ref="formRef"
          label-width="100px"
          size="large"
        >
          <el-form-item label="登录名称">
            <el-input v-model="userInfo.username" disabled></el-input>
          </el-form-item>
          <el-form-item label="用户昵称" prop="nickname">
            <el-input v-model="userInfo.nickname"></el-input>
          </el-form-item>
          <el-form-item label="用户邮箱" prop="email">
            <el-input v-model="userInfo.email"></el-input>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="submitForm">提交修改</el-button>
          </el-form-item>
        </el-form>
      </el-col>
    </el-row>
  </page-container>
</template>

更换头像

更新密码

<script setup>
import { ref } from 'vue'
import { userUpdatePasswordService } from '@/api/user'
import { useUserStore } from '@/stores'
import { useRouter } from 'vue-router'

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-form
          ref="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>

<br/>

已有 5 条评论

  1. 新车上路,只带前10个人

  2. 新项目准备上线,寻找志同道合的合作伙伴

  3. 2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
    新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
    新车首发,新的一年,只带想赚米的人coinsrore.com
    新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
    做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
    新车上路,只带前10个人coinsrore.com
    新盘首开 新盘首开 征召客户!!!coinsrore.com
    新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
    新车即将上线 真正的项目,期待你的参与coinsrore.com
    新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
    新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com

  4. 2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
    新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
    新车首发,新的一年,只带想赚米的人coinsrore.com
    新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
    做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
    新车上路,只带前10个人coinsrore.com
    新盘首开 新盘首开 征召客户!!!coinsrore.com
    新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
    新车即将上线 真正的项目,期待你的参与coinsrore.com
    新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
    新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com

  5. 果博东方客服开户联系方式【182-8836-2750—】?薇- cxs20250806】
    果博东方公司客服电话联系方式【182-8836-2750—】?薇- cxs20250806】
    果博东方开户流程【182-8836-2750—】?薇- cxs20250806】
    果博东方客服怎么联系【182-8836-2750—】?薇- cxs20250806】

发表评论:

陕ICP备2023000669号-1