https://www.yuque.com/maofu-rzqcp/snisqw/mtopy4gfmqezmoyd
依赖名 | 应用于哪些分支 | 官网 |
---|---|---|
vue3 | all | https://cn.vuejs.org/guide/introduction.html |
vue-cli | all | Vue CLI |
element-plus | all | 快速开始 | Element Plus |
axios | all | Axios 中文文档 | Axios 中文网 | Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js |
vuex 4 | all | Vuex 是什么? | Vuex |
vue router | all | Vue Router |
mock.js (模拟api接口服务器数据) |
除了包含mockjs-pro的分支 | Getting Started |
mockjs-pro ( 该项目 fork 自 mockjs,修复了 mockjs 中 Mock.mock()不能使用 Promise 和 async 异步函数的 bug。 ) |
version-RBAC_page version-RBAC_page-S-T version-RBAC_page-S-T-i18n |
mockjs-pro |
dexie (用于操作IndexedDB数据库) |
version-RBAC_page version-RBAC_page-S-T version-RBAC_page-S-T-i18n |
Dexie.js - Minimalistic IndexedDB Wrapper |
md5 (加密密码传输到服务器) |
all | md5 |
screenfull | all | screenfull |
vue-i18n | version-xxx-i18n | Vue I18n | Vue I18n |
day.js | version-RBAC_page version-RBAC_page-S-T version-RBAC_page-S-T-i18n |
Day.js · 2kB JavaScript date utility library |
xlsx | version-RBAC_page version-RBAC_page-S-T version-RBAC_page-S-T-i18n |
xlsx |
file-saver (文件下载) |
version-RBAC_page version-RBAC_page-S-T version-RBAC_page-S-T-i18n |
file-saver |
fuse.js(模糊搜索) | version-xxx-search version-xxx-S-T |
fuse.js |
例如
git clone https://gitee.com/tmaofu/back_office_management.git
进入项目目录,打开终端
先安装yarn包管理器
npm install -g yarn
再安装项目依赖
yarn install
npm start
npm run build
请根据需要修改.eslintrc.js
配置文件。
全局安装Commitizen
npm install -g commitizen@4.2.4
修改文件代码
添加到暂存区:
git add .
使用 git cz
代替 git commit
:
git cz
当你已经根据api接口文档,完成了一部分api接口的时候,你想要使用自己的api接口进行测试,那么这时候你就可以删除mock的接口。
例如你已经完成了注册接口,那么你就可以删除以下mock的接口:
当你已经完成了所有接口时,你可以删除整个mock 文件夹,并在main.js中解除引入:
dist:打包后的文件
node_modules:npm包
public:静态资源,不会被压缩
src:
- api:api接口统一管理
- assets:存放所需的静态资源
- components:存放公用组件
- constant:存放常量,如一些键值
- directives:存放自定义vue指令
- filter:存放过滤器
- i18n:实现国际化
- layout:实现layout布局页面
- mock:拦截api请求,放回mock的数据
- router:app路由
- store:状态仓库
- style:存放通用样式文件
- utility:存放工具性质的文件
- validator:存放合法性校验的文件
- views:存放路由页面
- App.vue:根组件
- main.js:入口文件
- settings.js:该应用的一些配置,可根据需要修改
├─api
│ permission-manage.js //权限列表管理api
│ read.md
│ role-manage.js //角色列表管理api
│ sys.js
│ user-manage.js //员工列表管理api
├─assets
│ │ logo.png
│ │
│ ├─icon
│ │ │ index.js //使webpack打包svg图标
│ │ │
│ │ └─svg
//存放svg图标
│ │
│ ├─img
//存放图片
│ │
│ └─scss
│ reset.scss //清除浏览器默认样式
使用方式:
使用icon 这个props进行传参,可接收:
1.图标字体类名
2.svg的url
3.'my-'+本地src/assets/icon/svg中的svg名称,例如:icon="my-login"
export const TOKEN = 'token'
export const USERLANGUAGE = 'user-language'
import dayjs from 'dayjs'
export function dateFilter(val, format = 'YYYY-MM-DD') {
if (!isNaN(val)) {
val = parseInt(val)
}
return dayjs(val).format(format)
}
export default function useFilter(vueApp) {
vueApp.config.globalProperties.$filters = {
dateFilter
}
}
├─mock
│ │ index.js //入口文件
│ │ permission-list.js //拦截权限列表相关的api
│ │ role-manage.js //拦截角色列表相关的api
│ │ sys.js //拦截登录,注册相关的api
│ │ user-manage.js //拦截员工列表相关的api
│ │
│ └─IndexedDB
│ index.js //操作IndexedDB数据库的方法
├─router
│ index.js //入口文件,配置路由对象
│ permission.js //定义全局前置导航守卫
│ routes.js //定义 公有路由和私有路由
├─store
│ │ getters.js //类似计算属性
│ │ index.js //入口文件
│ │
│ └─modules
│ layout.js //关于layout布局页面的状态仓库
│ routes.js //关于路由的状态仓库
│ user.js //关于用户的状态仓库
├─style
│ index.scss
│ mobile.scss //一些通用的全局移动端样式
│ theme-dark.scss //暗夜主题样式
│ theme-filter-invert-dark.scss //使用css-filter 实现暗夜主题样式
│ theme-filter-invert-light.scss //使用css-filter 实现明亮主题样式
│ theme-light.scss //明亮主题样式
│ variables-dark.scss //暗夜主题使用的颜色变量
│ variables-light.scss //明亮主题使用的颜色变量
│ variables.scss //一些样式变量
├─utility
│ export2Excel.js //导出为excel
│ get-screen-info.js //获取屏幕信息,判断是否是移动端
│ request.js //封装axios
│ resolveBugs.js //解决element-plus 报错问题
│ set-document-title.js //设置文档标题
│ storage.js //封装localStorage
├─validator
│ index.js //包含 登录注册密码校验规则
<template>
<div class="app-container">
<router-view></router-view>
</div>
</template>
<style lang="scss" scoped>
.app-container {
width: 100vw;
height: 100vh;
overflow: hidden;
}
</style>
import i18n from '@/i18n'
export default {
/**
* 页面标题
*/
pageTile: i18n.$t('src.settings.808362-0'),
/**
* 侧边栏
*/
sideBar: {
// sideBarLogo: {
// isShow: true,
// type: 'text', // 'text'or'img'
// value: '后台管理系统'
// },
sideBarLogo: {
isShow: true,
type: 'img', // 'text'or'img'
value: '/favicon.ico'
},
uniqueOpened: true, // 是否只保持一个子菜单的展开
// 侧边栏宽度
initWidth: 200, // 初始宽度,填写整数,单位是px
minDargWidth: 180, // 最小宽度
maxDargWidth: 250, // 最大宽度
mobileCollapseToZero: true // 为移动端页面时,收缩侧边栏宽度到0
},
/**
* 顶部导航栏
*/
narBarFixed: true, // 是否固定导航条
narBarSeparator: '/' // 面包屑分隔符
}
路由表的配置在 src\router\routes.js
中,如果要添加公共路由记录请添加在 publicRoutes
中,如果要添加私有路由记录请添加在 privateRoutes
中。
{
path: 'client',
name: 'client-management',
meta: {
title: '客户管理',//会显示为菜单名称
icon: 'my-user'//配置菜单名称左侧的图标,使用自定义SvgIcon组件实现,可选值可以查看SvgIcon组件的说明。
},
component: () => import('@/views/Client')
}
效果 | 实现方式 |
---|---|
在 src\\router\\routes.js 的路由表中添加类似这样一条记录```javascript |
{
path: '/',
component: Layout,
children: [
{
path: 'client',
name: 'client-management',
meta: {
title: i18n.$t('router.routes.817884-1'),
icon: 'my-user'
},
component: () => import('@/views/Client')
}
]
},
|
<a name="f0ExB"></a>
### 子级菜单
| **效果** | **实现方式** |
| --- | --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2023/png/34576819/1694154280116-c4e82107-80c4-46cc-a21b-a7d76f02e2c0.png#averageHue=%23fcfbfb&clientId=uc900d602-642b-4&from=paste&height=611&id=u272590e6&originHeight=916&originWidth=298&originalType=binary&ratio=1.5&rotation=0&showTitle=false&size=23792&status=done&style=none&taskId=u5e052a1f-ced6-49c1-88c2-f2642104247&title=&width=198.66666666666666) | 在 `src\\router\\routes.js`的路由表中添加类似这样一条记录```javascript
{
path: '/info',
component: Layout,
redirect: '/info/info',
meta: {
title: i18n.$t('router.routes.817884-0'),
icon: 'my-info'
},
children: [
{
path: 'info',
name: 'info',
component: () => import('@/views/Info'),
meta: {
title: i18n.$t('router.routes.817884-0'),
icon: 'my-info'
}
}
]
},
|
效果 | 实现方式 |
---|---|
在 src\\router\\routes.js 的路由表中添加类似这样一条记录```javascript |
|
{ |
path: '/info',
component: Layout,
redirect: '/info/info',
meta: {
title: i18n.$t('router.routes.817884-0'),
icon: 'my-info'
},
children: [ { path: 'info', name: 'info', component: () => import('@/views/Info'), meta: { title: i18n.$t('router.routes.817884-0'), icon: 'my-info' }, children: [ { path: 'info', name: 'info', component: () => import('@/views/Info'), meta: { title: i18n.$t('router.routes.817884-0'), icon: 'my-info' } } ] } ] },
|
<a name="HKVhp"></a>
## 页面搜索功能
只要配置了路由表,并且改登录用户拥有该路由记录,就可以进行模糊搜索。具体实现可以查看:`src\components\PageSearch`
<a name="QCE6s"></a>
## 动态路由表/RBAC功能
首先,请查看`src\router\permission.js`中 路由导航守卫的逻辑
```javascript
...
// 需要需要权限的页面
else {
// 有token,已经登录过
if (store.getters.token) {
// 状态仓库中是否有用户信息,
// 如果有:代表没有刷新页面|不是重新访问web应用,这时是不需要判断token是否过期的。
if (store.getters.userInfo) {
next()
// eslint-disable-next-line brace-style
}
// 没有用户信息,代表用户刚刚访问本站
else {
// 用已有的token去获取用户信息,看token是否过期
const ok = await store.dispatch('user/getUserInfo')
if (ok) {
// 没有过期,放行
// 由于在导航守卫中添加了动态路由,所以需要重定向,而不是直接next()
next(to.fullPath)
} else {
// token过期,需要用户重新登录
// 先清除token,才能跳转登录页
ElMessage.warning(i18n.t('router.permission.808363-0'))
store.commit('user/SET_token', '')
next('/login')
}
}
// eslint-disable-next-line brace-style
}
// 无token,没有登录过,需要让用户登录
else {
next('/login')
}
}
...
当用户首次访问该网站是由于没有token,会先跳转到登录页,进行登录
// 处理登录
function handleLogin() {
// 参数校验
if (loginFromRef.value) {
loginFromRef.value.validateField().then(() => {
// 动画
isLoading.value = true
// 发请求
store
.dispatch('user/login', loginForm)
.then(() => {
// 如果有重定向参数redirect,将进行重定向
router.push({
path: route.query.redirect ? route.query.redirect : '/'
})
ElMessage.success({ message: i18n.t('Login.index.808362-5') })
})
.finally(() => {
isLoading.value = false
})
})
}
}
store.dispatch('user/login', loginForm)
的实现如下:
login({ commit }, payload) {
return new Promise((resolve, reject) => {
login(payload)
.then((data) => {
// 登录成功,保存token
commit('SET_token', data.token)
resolve()
})
.catch((error) => reject(error))
})
},
当登录成功后会保存服务器返回的token,并跳转首页,这时会再次进入导航守卫:
// 用已有的token去获取用户信息,看token是否过期
const ok = await store.dispatch('user/getUserInfo')
由于有了token,会利用token去获取用户信息:
import { login, getUserInfo, logout } from '@/api/sys'
。。。
getUserInfo({ commit, dispatch }) {
return new Promise((resolve, reject) => {
getUserInfo()
.then(async (data) => {
// 保存用户信息到仓库
commit('SET_userInfo', data)
// 等待路由计算完成
// 启用了命名空间的 getter 和 action 会收到局部化的 getter,dispatch 和 commit。
// 若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
await dispatch('routes/computedUserRoutes', data.permission.menus, {
root: true
})
resolve(true)
})
.catch(() => {
// eslint-disable-next-line prefer-promise-reject-errors
reject(false)
})
})
},
。。。
在这时候会根据用户的权限信息 data.permission.menus
去计算用户所拥有的路由记录,这是实现权限控制的关键。服务端返回的 data 数据结构应该如下所示:
"data": {
"id": 0,
"role": [
2
],
"openTime": "2023-05-04",
"username": "admin",
"avatar": "/favicon.ico",
"permission": {
"menus": [
"acl",
"user-list",
"role-list",
"menu-list",
"client-management"
],
"points": [
"excel-import",
"delete-user",
"assign-role",
"add-role",
"delete-role",
"assign-perm",
"add-root-perm",
"add-children-perm",
"delete-perm"
]
}
},
menus
保存了可以访问的路由的name
,这个值应该是唯一的。points
保存了可以使用的按钮权限。
根据用户的权限信息 data.permission.menus
去计算用户所拥有的路由记录的核心实现如下:
完整实现请查看:src\store\modules\routes.js
。。。
computedUserRoutes({ commit }, menus) {
return new Promise((resolve, reject) => {
// 根据户信息计算出用户所拥有的路由表
const userPrivateRoutes = getUserPrivateRoutes(privateRoutes(), menus)
// 将计算出的私有路由表添加到vue-router
addRoutes(userPrivateRoutes)
// 将用户拥有的私有路由与公有路由合并,保存到state.routes
// state.routes主要用于:侧边栏渲染,页面搜索,
commit('SET_routes', [...publicRoutes(), ...userPrivateRoutes])
commit('SET_userPrivateRoutes', userPrivateRoutes)
resolve()
})
},
。。。
/**
* 根据户信息计算出用户所拥有的路由表
* @param {*} flatPrivateRoutes
* @param {*} menus
*/
function getUserPrivateRoutes(privateRoutes, menus) {
// 不要在数组迭代中删除数组该数组中元素
const newList = [...privateRoutes]
newList.forEach((route) => {
// 从子路由开始向上遍历
if (route.children && route.children.length) {
getUserPrivateRoutes(route.children, menus)
}
if (!(route.children && route.children.length)) {
// 因为先过滤的子路由
// 如果过滤后,该路由还有子路由,不管你有没有该路由页面权限,都应该保留,否则看不到(不能添加到vue-router)子路由
// 如果没有子路由,我就要看看是否过滤该路由
const exist = menus.includes(route.name)
if (!exist) {
privateRoutes.splice(privateRoutes.indexOf(route), 1)
}
}
})
return privateRoutes
}
当获取用户信息成功,动态路由也计算完成后,再回到我们的导航守卫中:
// 用已有的token去获取用户信息,看token是否过期
const ok = await store.dispatch('user/getUserInfo')
if (ok) {
// 没有过期,放行
// 由于在导航守卫中添加了动态路由,所以需要重定向,而不是直接next()
next(to.fullPath)
}
此时就会跳转到要去的页面了。
所以,要使用该项目的RBAC功能,请配合后端规划好API接口以及请求/响应数据。请查看API接口文档了解更多。
SvgIcon组件已经在main.js 中进行全局注册,可以直接使用:
// 注册SvgIcon为全局组件
import SvgIcon from '@/components/SvgIcon'
const app = createApp(App)
.use(store)
.use(router)
.use(I18N)
.use(ElementPlus, elementPlusConf)
app.component('SvgIcon', SvgIcon)
具体实现方式可以查看:**src\components\SvgIcon\index.vue**
。
使用方式:
使用hoverScale
这个props
可以让鼠标悬浮图标时有缩放效果。
使用icon
这个props
进行传参,可接收:
1.图标字体类名
2.svg的url
3.'my-
'+本地src/assets/icon/svg
中的svg名称,例如:icon="my-login"
<SvgIcon icon="my-login"></SvgIcon>
明暗切换组件在 src\components\LightDarkSwitch\index.vue
const isDark = useDark({
selector: 'html', // 要为哪一个元素添加属性,默认添加的属性名为class。element-plus的drak类必须添加在html标签上
valueDark: props.normalMode
? 'theme-dark dark'
: 'theme-filter-invert-light theme-filter-invert-dark', // 添加的属性值,drak是 element-plus 的暗夜样式的类(https://element-plus.gitee.io/zh-CN/guide/dark-mode.html)
valueLight: props.normalMode ? 'theme-light' : 'theme-filter-invert-light'
})
当切换主题时,会更改html元素上的css类名,以更改不同的样式类,实现不同的样式效果。
具体的明暗css样式实现,可以查看和修改 src\style
中的文件。
├─style
│ index.scss
│ mobile.scss //一些通用的全局移动端样式
│ theme-dark.scss //暗夜主题样式
│ theme-filter-invert-dark.scss //使用css-filter 实现暗夜主题样式
│ theme-filter-invert-light.scss //使用css-filter 实现明亮主题样式
│ theme-light.scss //明亮主题样式
│ variables-dark.scss //暗夜主题使用的颜色变量
│ variables-light.scss //明亮主题使用的颜色变量
│ variables.scss //一些样式变量
import i18n from '@/i18n'
export default {
/**
* 页面标题
*/
pageTile: i18n.$t('src.settings.808362-0'),
/**
* 侧边栏
*/
sideBar: {
// sideBarLogo: {
// isShow: true,
// type: 'text', // 'text'or'img'
// value: '后台管理系统'
// },
sideBarLogo: {
isShow: true,
type: 'img', // 'text'or'img'
value: '/favicon.ico'
},
uniqueOpened: true, // 是否只保持一个子菜单的展开
// 侧边栏宽度
initWidth: 200, // 初始宽度,填写整数,单位是px
minDargWidth: 180, // 最小宽度
maxDargWidth: 250, // 最大宽度
mobileCollapseToZero: true // 为移动端页面时,收缩侧边栏宽度到0
},
/**
* 顶部导航栏
*/
narBarFixed: true, // 是否固定导航条
narBarSeparator: '/' // 面包屑分隔符
}
pageTile
文档标题后缀,可以自行修改,前面会拼接上菜单项的名称,可以在 src\utility\set-document-title.js
中修改拼接行为。
可以自行修改 src\layout
中的布局文件。
关于i18n的实现,可以查看如下文件:src\layout\NavBar\index.vue
src\components\LanguageSwitch\index.vue
src\i18n\index.js
在src\i18n\locale\en.json
添加英文译文
在src\i18n\locale\zh.json
中添加中文译文
注意,此处的key
均为:404.index.808362-0
<p>{{ $t('404.index.808362-0') }}</p>
此处的$t
方法已经挂载到了全局
<script setup>
import i18n from '@/i18n'
let str = i18n.$t('404.index.808362-0')
<script>
import i18n from '@/i18n'
let str = i18n.$t('404.index.808362-0')
如果你想要将项目中的中文快速提取并翻译成英文,你可以尝试以下vscode插件:
Du I18N (https://marketplace.visualstudio.com/items?itemName=DewuTeam.du-i18n)
使用帮助:(https://juejin.cn/post/7191769910685466679)
i18n pro(https://marketplace.visualstudio.com/items?itemName=wumo1016.i18n-pro)
使用帮助:(https://juejin.cn/post/7213665579701731385)
它fork自i18n Ally,提供了百度翻译的API接口配置,对中国开发者来说更加方便.
如果你想要将项目中的英文快速提取并翻译成中文,你可以尝试以下这一个vscode插件:
i18n Ally
如果你使用i18n Ally
或者 i18n pro
,你可以在项目根目录的.vscode
中的settings.json
中进行如下配置:
{
"i18n-ally.localesPaths": ["src/i18n/locale"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
// "i18n-ally.namespace": true,
// "i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["json"],
"i18n-ally.sourceLanguage": "zh",
"i18n-ally.displayLanguage": "zh",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"i18n-ally.extract.autoDetect": true,
// i18n pro
"i18n-ally.translate.engines": ["baidu"],
"i18n-ally.translate.baidu.appid": "百度平台APPID",
"i18n-ally.translate.baidu.apiSecret": "百度平台分配的密钥",
"vue.features.codeActions.enable": false
}
404,登录,注册中使用的图片均来自该网站
Free to Use Clip Art Images & Vector Illustrations | ManyPixels
Illustration Gallery是来自ManyPixels设计团队,完全免费且可商用的一个插画库,可搜索关键字,可以任意改颜色,插画形式也非常舒服,重点是可下载格式有svg和png,非常方便。
该项目中所使用的svg图标均来自该网站
Heroicons
Heroicons 是一套由 Tailwind CSS 制作的精美的手绘风格 SVG 图标,适用于各种网站和应用。Heroicons 有三种风格:Outline(描边),Solid(填充)和 Mini(迷你),每种风格都有 292 个图标,共计 876 个图标。Heroicons 支持 React 和 Vue 库,也可以直接在 HTML 中使用。Heroicons 的许可证是 MIT,可以免费使用和修改。