大事件项目(记录一次完整的项目制作流程)(半途而废版)

一、创建项目

1.pnpm create vue

执行cd Vue3-big-event-admin、pnpm install

运行项目用pnpm dev命令

2.ESlint配置代码风格

在.eslintrc.cjs文件中module.exports中添加以下属性:

rules: {
  'prettier/prettier': [
    'warn',
    {
      singleQuote: true, // 单引号
      semi: false, // 无分号
      printWidth: 80, // 每行宽度至多80字符
      trailingComma: 'none', // 不加对象|数组最后逗号
      endOfLine: 'auto' // 换行符号不限制(win mac 不一致)
    }
  ],
  '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'
}

在设置的settings.json中修改:

eslint:规范

prettier:美观

3.基于husky的代码检查工作流

husky 是一个 git hooks 工具 ( git的钩子工具,可以在特定时机执行特定的命令 )

打开bash终端

初始化:终端中输入git init

在终端中输入

pnpm dlx husky-init && pnpm install

则会在项目中自动生成一个.husky文件夹

将.husky/pre-commit中npm test改为pnpm lint(我也不知道为啥要改)(这一步不重要,反正之后又会将pnpm lint改为pnpm lint-staged)

如果在终端中输入pnpm lint检查代码,这个是全量检查,有耗时问题、历史问题

lint-staged 配置:

pnpm i lint-staged -D

配置package.json

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

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

将.husky/pre-commit中pnpm lint改为pnpm lint-staged

pnpm lint-staged只会检查暂存区新添加的代码,而不是像pnpm lint一样全量检查

 4.目录调整

该删删该加加(api文件夹、utils文件夹(vant使用))

5.安装sass预处理器支持

6.使用element plus

(1)装包

# 选择一个你喜欢的包管理器

# NPM
$ npm install element-plus --save

# Yarn
$ yarn add element-plus

# pnpm
$ pnpm install element-plus

 (2)按需导入(自动导入)

npm install -D unplugin-vue-components unplugin-auto-import

在vite.config.js中引入下列代码

// vite.config.ts
import { defineConfig } from 'vite'
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: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

就可以直接使用element-plus中的组件了

另外,在component中定义的组件也可以直接使用

如果要使用图标,还要执行以下命令安装图标库:

pnpm i @element-plus/icons-vue

并在需要用的组件里导入:

import { User, Lock } from '@element-plus/icons-vue'

7.安装pinia持久化插件

8.实现pinia独立维护和仓库统一导出

独立维护:将关于仓库的核心代码全写在stores/index.js中

统一导出:将各个模块全导入到index中,并在index中导出,这样做的好处是,在其他组件中使用数据时,不管是哪个仓库的数据,都是从stores/index中导入

原本的stores文件夹:

现在的:

stores/index.js:

import { createPinia } from 'pinia' //创建状态管理库
import persist from 'pinia-plugin-persistedstate' //实现数据持久化

const pinia = createPinia().use(persist)

export default pinia

// import { useUserStore } from '@/stores/modules/user'
// export { useUserStore }
// import { useCountStore } from '@/stores/modules/count'
// export { useCountStore }

// 上面可以被下列替代掉
export * from '@/stores/modules/user'
export * from '@/stores/modules/counter'

main.js:

//···

import pinia from '@/stores/index'
const app = createApp(App)
app.use(pinia)

//···

 modules/use.js:

import { defineStore } from 'pinia' //创建仓库
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
  const token = ref('Bear abcdefg')
  const setToken = (newToken) => {
    token.value = newToken
  }
  return {
    token,
    setToken
  }
})

9.axios配置:创建axios实例、请求拦截器、响应拦截器

创建文件utils/request.js:

import axios from 'axios'

const baseURL = 'http://big-event-vue-api-t.itheima.net'

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

// 请求拦截器
instance.interceptors.request.use(
  (config) => {
    // TODO 2. 携带token
    return config
  },
  (err) => Promise.reject(err)
)
// 响应拦截器
instance.interceptors.response.use(
  (res) => {
    // TODO 3. 处理业务失败
    // TODO 4. 摘取核心响应数据
    return res
  },
  (err) => {
    // TODO 5. 处理401错误
    return Promise.reject(err)
  }
)

export default instance

这个项目具体的配置代码: 

import { useUserStore } from '@/stores/user'
import axios from 'axios'
import router from '@/router'
import { ElMessage } from 'element-plus'

const baseURL = 'http://big-event-vue-api-t.itheima.net'

const instance = axios.create({
  baseURL,
  timeout: 100000
})

instance.interceptors.request.use(
  (config) => {
    const userStore = useUserStore()
    if (userStore.token) {
      config.headers.Authorization = userStore.token
    }
    return config
  },
  (err) => Promise.reject(err)
)

instance.interceptors.response.use(    
  (res) => {
    if (res.data.code === 0) {
      return res
    }
    ElMessage({ message: res.data.message || '服务异常', type: 'error' })
    return Promise.reject(res.data)
  },
  (err) => {
    ElMessage({ message: err.response.data.message || '服务异常', type: 'error' })
    console.log(err)
    if (err.response?.status === 401) {
      router.push('/login')
    }
    return Promise.reject(err)
  }
)

export default instance
export { baseURL }

二、一些知识

1.关于路由

(1)创建路由

createRouter:创建路由

createWebHistory:默认,history模式,没有#

createWebHashHistory:hash模式,有#

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

//createRouter({})对应的是vue2中的new vue()
const router = createRouter({
//createWebHistory:默认,history模式,没有#
//也有可选项createWebHashHistory
//括号中的内容默认是'/',表示的是路由前面加的东西,就是vite.config.js中的base配置项
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: []
})

export default router

(3)route和router

在vue3的CompositionApi中

<script setup>
import { useRouter, useRoute } from 'vue-router'
//跳转页面等操作
const router = useRouter()
//路由信息
const route = useRoute()
const goList = () => {
  // 路由跳转
  router.push('list')
}
console.log(route)
</script>

三.项目实践

1.一些注意点

(1)关于api下的文件内部函数的命名应遵循一定的规则,如userRegisterService表示api/user.js文件中关于注册的请求。

(2)eslintrc.cjs 中声明全局变量名, 解决 ElMessage 报错问题

不然会因为ElMessage未声明但使用了而报错

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

2.关于<el-form>的使用&注册登录功能

正则中:\S表示非空字符

/焦虑啊焦虑啊,不在焦虑中爆发,就在焦虑中灭亡/

(1)嵌套关系

<el-form>//这里写form表单中所有项双向绑定到的数据和校验规则是什么
    <el-form-item>//这里写这一项的校验规则是哪个
        <el-input>//这里写这一项的绑定的数据是哪个
        </el-input>
    </el-form-item>
</el-form>

(2)具体使用

<script setup>
const formModel = ref({
  username: ''
})
const rules = {
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 5, max: 10, message: '用户名必须是5-10位的字符', trigger: 'blur' },
    {
      pattern: /^\S{6,15}$/,
      message: '密码必须是6-15位的非空字符',
      trigger: 'blur'
    },
    {
      validator: (rule, value, callback) => {
        // 注意这里的函数,有三个参数
        if (value !== formModel.value.password) {
          callback(new Error('两次输入密码不一致!'))
        } else {
          callback()
        }
      },
      trigger: 'blur'
    }
  ]
}
const form=ref(null)
</script>
<el-form
  :model="formModel"
  :rules="rules"
  ref="form"
>
  <el-form-item prop="username">
    <el-input
      v-model="formModel.username"
      :prefix-icon="User"
      placeholder="请输入用户名"
    ></el-input>
  </el-form-item>
</el-form>

(3)校验

非空、长度、正则、自定义

(4)预校验

<el-button
  @click="register"
>
  注册
</el-button>
const register = async () => {
// 这个方法是validate组件内部的方法,要在组件以外使用,就必须在组件内部声明。
// 而在element - plus官网中可查明validate方法是可以在外部使用的,并且是异步的,所以要使用async&await
  await form.value.validate()
  console.log('开始注册请求')
}

(5)登录

登录和注册绑定同一份数据和同一个校验规则,所以在切换登录或注册时,要重置绑定的数据。登录成功之后将token存在仓库中,并通过插件实现持久化。

3.<el-menu>&<el-menu-item>

(1)el-menu

有几个属性:active-text-colorbackground-color:default-active="$route.path"、text-color

(2)el-menu-item

属性有:index

(3)el-sub-menu二级菜单

用具名插槽配置菜单名

(4)示例代码

<el-menu
active-text-color="#ffd04b"// 选中时文字颜色
background-color="#232323"// 背景色
:default-active="$route.path"// 默认(刚进入页面时选中的是哪一项)
text-color="#fff"// 未选中时文字颜色
router
>
<el-menu-item index="/article/manage">// <!--index值就表示选中时跳转至的路由-->
    <el-icon>
    <Promotion />
    </el-icon>
    <span>文章管理</span>
</el-menu-item>
<el-sub-menu index="/user">
    <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-sub-menu>
</el-menu>

4.全局前置守卫

任何页面被访问之前要通过这里

在router/index.js文件中

语法:

router.beforeEach((to, from) => {
  const userStore = useUserStore()
  if (!userStore.token && to.path !== '/login') return '/login'
// 返回值:
// 1.没有返回(相当于返回undefined)或true:放行
// 2.返回false:回到之前的页面
// 3.返回路径或者对象:跳转至相应页面
// 如:'/loign'或{name:'login'}
})

5.下拉菜单

大概结构:

<el-dropdown @command="onCommand">
    <!--这里是下拉之前展示出来的内容-->
    <span>
    下拉菜单
    <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>
const onCommand = async (command) => {
  if (command === 'logout') {
    await ElMessageBox.confirm('你确认退出大事件吗?', '温馨提示', {
      type: 'warning',
      confirmButtonText: '确认',
      cancelButtonText: '取消'
    })
    userStore.removeToken()
    userStore.setUser({})
    router.push(`/login`)
  } else {
    router.push(`/user/${command}`)
  }
}

用法示例:

<el-dropdown placement="bottom-end">
    <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>

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值