release: 2.0.0发布

This commit is contained in:
kailong321200875 2021-10-24 12:25:30 +08:00
parent 0e3eb4ba8b
commit b9f96d485f
42 changed files with 10256 additions and 185 deletions

View File

@ -6,5 +6,5 @@
# Format and submit code according to lintstagedrc.js configuration
npm run lint:lint-staged
npm run ts:check
npm run lint:pretty

View File

@ -4,4 +4,5 @@
/public/*
/docs/*
/vite.config.ts
/src/env.d.ts
/src/env.d.ts
/docs/**/*

View File

@ -3,3 +3,4 @@
public/*
/dist*
/src/env.d.ts
/docs/**/*

13
CHANGLOG.md Normal file
View File

@ -0,0 +1,13 @@
# 更新日志
## 2.0.0
`2021-10-24`
### Features
- vite 版本发布
### Docs
- 更新对应文档

View File

@ -1,11 +1,72 @@
# Vue 3 + Typescript + Vite
# vue-element-plus-admin
This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
> 一套基于 vue3 & typeScript4 & vite2 的后台集成方案
## Recommended IDE Setup
## 介绍
vue-element-plus-admin 是一套基于 vue3 & typeScript4 & vite2 的后台集成方案,内置了动态路由,权限验证,典型的业务模型,丰富的功能组件,开箱即用,可以用来作为项目的启动模版。它可以帮助你快速搭建企业级中后台产品原型,也可以作为一个示例,用于学习。
vue-element-plus-admin的定位是后台集成方案不太适合当基础模板来进行二次开发。因为集成了很多你可能用不到的功能会造成不少的代码冗余。如果你的项目不关注这方面的问题也可以直接基于它进行二次开发。
[在线预览地址](http://8.133.179.48:4000/dist-pro/#/login)
如需要基础模版,请切换到`tempalte`分支。
## 前序准备
你需要在本地安装[node](https://nodejs.org/en/)和[git](https://git-scm.com/)。本项目技术栈基于[ES2015+](https://es6.ruanyifeng.com/)、[typescript](https://www.typescriptlang.org/)、[vue](https://cn.vuejs.org/index.html)、[pinia](https://pinia.esm.dev/)、[vue-router](https://router.vuejs.org/zh/)、[axios](https://github.com/axios/axios)和[element-plus](https://github.com/element-plus/element-plus),所有的请求数据都使用[Mock.js](https://github.com/nuysoft/Mock)进行模拟,提前了解和学习这些知识会对使用本项目有很大的帮助。
## 目录结构
```sh
.
├── public # 静态资源
├── src # 项目代码
│ ├── assets # 静态资源
│ ├── axios-config # axios配置
│ ├── components # 公用组件
│ ├── directive # 自定义指令
│ ├── hooks # 常用hooks
│ ├── layout # 布局组件
│ ├── plugins # 外部插件
│ ├── mock # 模拟数据
│ ├── router # 路由配置
│ ├── store # 状态管理
│ ├── styles # 全局样式
│ ├── utils # 全局工具类
│ ├── views # 路由页面
│ ├── vue-bus # 跨组件通信
│ ├── App.vue # 入口vue文件
│ ├── env.d.ts # vue全局声明
│ ├── global.d.ts # 业务代码全局声明
│ ├── main.ts # 主入口文件
│ └── permission.ts # 路由拦截
├── .babelrc # babel配置
├── .editorconfig # 编辑器配置
├── .eslintignore # eslint 忽略配置项
├── .eslintrc # eslint 配置文件
├── .gitignore # git 忽略提交配置文件
├── .postcssrc.js # postcss 配置文件
├── .prettierrc.js # 代码格式 配置文件
├── .stylelintignore # stylelint 忽略文件
├── CHANGLOG.md # 更新日志
├── commitlint.config.js # git commit 配置文件
├── package.json
├── README.md
├── stylelint.config # stylelint配置文件
├── tsconfig.json # ts 配置文件
├── vite.config.ts # vite配置
└── yarn.lock
```
## 浏览器支持
由于 Vue 3 不再支持 IE11vue-element-plus-admin 也不支持 IE。
| [<img src="https://cdn.jsdelivr.net/npm/@browser-logos/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Safari |
| --- | --- | --- | --- |
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 64 | Safari ≥ 12 |
## IDE推荐设置
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's `.vue` type support plugin by running `Volar: Switch TS Plugin on/off` from VSCode command palette.

22
docs/.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
.DS_Store
node_modules
/dist
/dist*
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*

View File

@ -0,0 +1,107 @@
module.exports = {
title: 'vue-element-plus-admin',
description: '种一棵树最好的时间是十年前,其次就是现在。',
base: process.argv[2] === 'dev' ? '/' : '/dist-doc/', // 这是部署到github相关的配置
// base: '/',
// base: '/dist-doc2.0/',
// base: '/ue/2019/doc/vue-standard/dist/',
markdown: {
lineNumbers: false // 代码块显示行号
},
themeConfig: {
search: true, // 是否开启搜索
searchMaxSuggestions: 10, // 搜索最大条数
// 侧边栏
sidebar: {
'/guide/': [
{
title: '基础',
collapsable: false,
children: [
'/guide/',
'/guide/quick-start',
'/guide/configuration',
'/guide/router',
'/guide/mock'
]
},
{
title: '进阶',
collapsable: false,
children: ['/guide/standard', '/guide/style', '/guide/role']
}
],
'/components/': [
{
title: '组件',
collapsable: false,
children: ['/components/']
},
{
title: '全局组件',
collapsable: false,
children: [
'/components/svg-icon',
'/components/com-table',
'/components/com-search',
'/components/com-dialog',
'/components/com-detail'
]
},
{
title: '普通组件',
collapsable: false,
children: [
'/components/echart',
'/components/preview',
'/components/avatars',
'/components/highlight',
'/components/editor',
'/components/qrcode'
]
}
]
},
// 导航栏
nav: [
{
text: '首页',
link: '/'
},
{
text: '指南',
link: '/guide/'
},
{
text: '功能组件',
link: '/components/'
},
{
text: '更新日志',
link: '/changLog/changLog'
},
{
text: '预览',
link: 'http://8.133.179.48:4000/dist-pro/'
},
{
text: 'Github',
link: 'https://github.com/kailong321200875/vue-element-plus-admin'
},
{
text: 'Gitee',
link: 'https://gitee.com/kailong110120130/vue-element-plus-admin'
}
]
},
plugins: [
[
'vuepress-plugin-typescript',
{
tsLoaderOptions: {
// ts-loader 的所有配置项
}
}
]
]
}

View File

19
docs/docs/README.md Normal file
View File

@ -0,0 +1,19 @@
---
home: true
actionText: 快速上手 →
actionLink: /guide/
features:
- title: 技术栈
details: 基于vue3、typescript4、vite2、element-plus构建
- title: 组件封装
details: 对常用功能进行组件化封装,统一维护。
- title: 权限验证
details: 根据权限动态加载路由,渲染侧边栏
- title: 说明
details: 本文档归纳出一些常见的开发规范,注重团队之间的协同开发,保证代码的可读性。
- title: 积累
details: 本文档会定期积累常见问题,知识点。
- title: 持续更新
details: 本文档将会持续更新,当做一个长期项目来维护,期望这个文档能够给开发人员带来帮助。
footer: MIT Licensed | Copyright © 2021-present Archer
---

View File

@ -0,0 +1,13 @@
# 更新日志
## 2.0.0
`2021-10-24`
### Features
- vite 版本发布
### Docs
- 更新对应文档

View File

@ -0,0 +1,46 @@
# 介绍
为了更好演示,`vue-element-plus-admin`对`element-ui`的所有组件进行了全局的按需引入注册,这样也就导致了打包之后可能会出现不需要的组件代码,开发者可以根据实际情况,去掉全局的按需引入,改为局部引入,或者删除不必要的全局注册,减少打包体积。具体代码可查看`src/element-ui/index.ts`。
除了 `element-ui` 中的组件进行了全局注册,本项目还全局注册了使用率比较高的组件,具体代码可查看`src/components/index.ts`,如下
```javaScript
import Vue from 'vue'
import SvgIcon from './SvgIcon/index.vue' // svg组件
import Table from './Table/index.vue' // ComTable组件
import Search from './Search/index.vue' // Search组件
import Dialog from './Dialog/index.vue' // Dialog组件
import Detail from './Detail/index.vue' // Detail组件
// register globally
Vue.component('SvgIcon', SvgIcon)
Vue.component('ComTable', Table)
Vue.component('ComSearch', Search)
Vue.component('ComDialog', Dialog)
Vue.component('ComDetail', Detail)
import '@/assets/icons' // icon
```
`vue-element-plus-admin`的组件大部分没有进行全局注册。采用了哪里使用哪里引入的方式,如下
```vue
<template>
<div>
<qrcode text="vue-element-plus-admin" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
components: {
Qrcode: () => import('_c/Qrcode/index.vue')
}
})
</script>
<style></style>
```

View File

@ -0,0 +1,58 @@
# Avatars 头像组
头像组组件。
`Avatars` 组件代码位于`src/components/Avatars`
## 使用
```vue
<template>
<avatars :data="data" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { AvatarConfig } from '_c/Avatars/types'
export default defineComponent({
components: {
Avatars: () => import('_c/Avatars/index.vue')
},
setup() {
const data = ref<AvatarConfig[]>([
{ text: '陈某某' },
{ text: '李某某', type: 'success' },
{ text: '张某某', type: 'danger' },
{ text: '王某某', type: 'warning' },
{ text: '龙某某' },
{ text: '孙某某' },
{ text: '刘某某' },
{ text: '赵某某' }
])
return {
data
}
}
})
</script>
<style></style>
```
## Attributes
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| ---------- | ------------------------------------- | ------- | ---------- | ------ |
| data | 展示的数据,具体配置详见[下表](#Data) | array | — | — |
| max | 最大展示数量0 为不设置 | number | — | 0 |
| showAvatar | 是否使用头像 | boolean | true/false | false |
| tooltip | 是否显示完整名称 | boolean | false/true | true |
## Data<span id="Data"></span>
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| ---- | ---------- | ------ | ---------------------- | ------ |
| text | 展示的文字 | string | — | — |
| type | 头像类型 | string | success/danger/warning | — |
| url | 头像地址 | string | — | — |

View File

@ -0,0 +1,95 @@
# ComDetail 详情
用于详情预览的组件,基于`element-ui`的`Row Col`组件进行二次封装。
`ComDetail` 组件位于`src/components/Detail`
## 使用
```vue
<template>
<div>
<com-detail :data="data" :schema="schema" title="基础示例" message="辅助文字" />
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from 'vue'
export default defineComponent({
setup() {
const data = reactive<IObj>({
username: 'chenkl',
nickName: '梦似花落。',
age: 26,
phone: '13655971xxxx',
email: '502431556@qq.com',
addr: '这是一个很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的地址',
sex: '男',
certy: '35058319940712xxxx'
})
const schema = ref<IObj>([
{
field: 'username',
label: '用户名'
},
{
field: 'nickName',
label: '昵称'
},
{
field: 'phone',
label: '联系电话'
},
{
field: 'email',
label: '邮箱'
},
{
field: 'addr',
label: '地址',
span: 24
}
])
return {
data,
schema
}
}
})
</script>
<style></style>
```
## Attributes
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| ----------- | ----------------------------------------- | ------- | ----------------- | ------- |
| title | 详情标题 | string | — | — |
| collapsed | 是否可折叠 | boolean | true/false | true |
| message | 辅助提示 | string | — | — |
| border | 是否需要边框 | boolean | true/false | true |
| data | 需要展示的数据 | object | — | — |
| schema | 布局展示的数据,具体项详见[下表](#Schema) | array | — | [] |
| vertical | 是否标题和内容各占一行 垂直布局 | boolean | false/true | false |
| labelWidth | 标题宽度 | string | — | — |
| labelAlign | 标题位置 | string | left/right/center | left |
| borderColor | 边框颜色 | string | — | #f0f0f0 |
| labelBg | 标题背景颜色 | string | — | #fafafa |
## Schema<span id="Schema"></span>
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| ----- | ---------------------------------- | ------ | ------- | ------ |
| field | 字段名,与 data 中的数据项字段对应 | string | — | — |
| label | 标题 | string | — | — |
| span | 在一行中的占比 | number | [1, 24] | 12 |
## Slot
| name | 说明 |
| -------------- | ---------------------- |
| {field} | 标题内容 |
| {field}Content | 对应字段需要展示的内容 |

View File

@ -0,0 +1,30 @@
# ComDialog 弹窗
为了保证 `弹窗` 样式的统一性,对 `element-ui``Dialog` 组件进行二次封装。
`ComDialog` 组件位于`src/components/Dialog`
## 使用
``` vue
<com-dialog v-model="visible" title="提示">
<div style="height: 1000px;">
我是弹窗内容
</div>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="visible = false">确定</el-button>
</template>
</com-dialog>
```
## Attributes
::: tip 提示
除以下参数外,官方文档内的 `Attributes` 都支持,具体可以参考 [element Dailog](https://element.eleme.cn/#/zh-CN/component/dialog)
:::
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |---------- |---------- |
| showFullscreen | 是否显示全屏按钮 | boolean | false/true | true |
| draggable | 是否可拖拽 | boolean | false/true | true |

View File

@ -0,0 +1,53 @@
# ComSearch 查询
针对常用的查询业务场景,对 `element``Form` 组件进行二次封装。
只需传入 `data` 即可渲染整个查询组件。
`Search` 组件代码位于`src/components/Search`
## 使用
``` vue
<div class="searh">
<com-search
:data="data"
@search-submit="searchSubmit"
@reset-submit="resetSubmit"
/>
</div>
```
## Attributes
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |---------- |---------- |
| labelWidth | 表单域标签的宽度,例如 '50px'。作为 Form 直接子元素的 form-item 会继承该值。支持 auto。 | string | — | - |
| labelPosition | 表单域标签的宽度的文字布局 | string | left/center/right | right |
| hideRequiredAsterisk | 隐藏所有表单项的必选标记 | boolean | true/false | false |
| data | 表单数据,详见[下表](#Data) | array | — | [] |
| rules | 表单验证规则 | object | — | — |
| showReset | 是否显示重置按钮 | boolean | false/true | true |
| layout | 布局风格 | string | right/bottom/classic | classic |
### <span id="Data">Data</span>
::: tip 提示
除以下参数外,官方文档内的 `表单组件``Attributes` 也都支持,具体可以参考 [form-item](https://element.eleme.cn/#/zh-CN/component/form)
:::
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |---------- |---------- |
| field | 每个查询项的字段名 | string | — | — |
| label | 每个查询项的标题 | string | — | — |
| value | 每个查询项的值 | any | — | — |
| itemType | 每个查询项的类型,根据此值渲染不同的组件,开发者可以进行扩展 | string | switch/input/select/radio/datePicker | — |
| onChange | 是否为当前项进行数据变化监听 | boolean | true/false | false |
## Events
| 事件名 | 说明 | 参数 |
|---------- |-------- |-------- |
| searchSubmit | 点击查询时的回调事件 | data |
| resetSubmit | 点击重置时的回调事件 | data |
| change | 查询项改变时的回调事件 | { field, value } |

View File

@ -0,0 +1,98 @@
# ComTable 表格
`element-ui` 中的 `Table` 组件进行二次封装,并把 `分页` 给结合进 `ComTable` 组件中。
数据渲染采用的是数据驱动,只需传入 `columns``data` 就可以进行最简单的数据展示。
`ComTable` 组件代码位于`src/components/Table`
更多示例,可以查看在线预览。
## 使用
```vue
<template>
<div>
<com-table :columns="columns" :data="tableData" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: 'address',
label: '地址'
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
setup() {
return {
columns,
tableData
}
}
})
</script>
<style></style>
```
## Attributes
::: tip 提示
除以下参数外,官方文档内的 `Attributes` 也都支持,具体可以参考 [element Table](https://element.eleme.cn/#/zh-CN/component/table)
:::
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| columns | 表格列的配置描述,具体项见[下表](#Columns) | array | — | [] |
| data | 需要渲染的表格数据 | array | — | [] |
| selection | 是否多选 | boolean | true | false |
| pagination | 是否展示分页,默认为 false如果为 object 类型,则支持[element Pagination](https://element.eleme.cn/#/zh-CN/component/pagination)所有参数 | object/boolean | — | false |
| reserveSelection | 仅对 type=selection 的列有效,类型为 Boolean为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key | boolean | true | false |
## Columns<span id="Columns"></span>
::: tip 提示
除以下参数外,官方文档内的 `Table-column``Attributes` 也都支持,具体可以参考 [element Table](https://element.eleme.cn/#/zh-CN/component/table)
:::
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| field | 每列需要展示的字段名 | string | — | — |
| label | 每列的表头名 | string | — | — |
| slots | 使用 columns 时,可以通过该属性配置支持 slot 的属性,如 slots: { header: 'XXX', default: 'xxx' } | object | — | — |

View File

@ -0,0 +1,29 @@
# 图片裁剪 组件
`Cropper` 组件位于[src/components/Cropper](http://192.168.169.57:9000/scm/index.html#contentPanel;9IRMTD52f3;null;vue-doc/src/components/Cropper/index.vue;content)
## 使用
``` vue
// 引入
import Cropper from '_c/Cropper'
// 使用
<cropper @on-crop="handleCroped" />
```
## Props
| 属性 | 类型 | 默认值 | 可选值 | 说明 |
|---------- |-------- |---------- |---------- |---------- |
| src | string | — | — | 默认展示的图片地址 |
| preview | boolean | true | false | 是否展示预览区域 |
| moveStep | number | false | true | 是否禁用 |
| clearable | boolean | 4 | — | 每次移动多少距离 |
| cropButtonText | string | 裁剪 | — | 裁剪按钮文字 |
## 事件
| 名称 | 说明 | 参数 |
|---------- |-------- |-------- |
| on-crop | 裁剪时的回调事件 | base64 |

View File

@ -0,0 +1,47 @@
# Echart 图表
基于`echarts`二次封装的图表的组件,内置了主题色。
`Echart` 组件代码位于`src/components/Echart`
::: tip 提示
需要给一个固定高度,才能展示出图表
:::
## 使用
```vue
<template>
<div>
<echart :options="pieOptions" :height="'300px'" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { pieOptions } from './echart-data'
export default defineComponent({
components: {
Echart: () => import('_c/Echart/index.vue')
},
setup() {
return {
pieOptions
}
}
})
</script>
<style></style>
```
## Attributes
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| className | 图表类名 | string | — | chart |
| width | 图表宽度 | string | — | 100% |
| height | 图表高度 | string | — | 200px |
| theme | 主题 | string | light/dark/default | default |
| options | 图表的配置项,具体可参考[echarts](https://echarts.apache.org/zh/api.html#echarts) | object | — | — |

View File

@ -0,0 +1,69 @@
# Editor 富文本
基于 `wangeditor` 进行二次封装的富文本组件。
`Editor` 组件位于`src/components/Editor`
::: tip 提示
如果该富文本组件无法满足业务需求,可以自行引入其他富文本插件。
:::
## 使用
```vue
<template>
<editor :value="content" @change="handleChange" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
components: {
Editor: () => import('_c/Editor/index.vue')
},
setup() {
const content = ref<string>('默认展示数据')
function handleChange(html: string, text: string) {
console.log(html, text)
}
return {
content,
handleChange
}
}
})
</script>
```
## Attributes
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| --------- | ------------------------------------------ | ------ | --------- | ------ |
| value | 富文本内容 | string | — | — |
| valueType | 富文本绑定的值的类型 | string | html/text | html |
| config | wangeditor 配置项,具体详见[下表](#Config) | object | — | — |
## Config<span id="Config"></span>
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| height | 富文本高度 | number | — | 500 |
| zIndex | 层级 | number | — | 0 |
| placeholder | 提示文字 | string | — | 请输入文本 |
| focus | 是否聚焦 | boolean | true/false | false |
| onchangeTimeout | 几秒监听一次变化 | number | — | 1000 |
| customAlert | 自定义提示 | function | — | — |
| menus | 按钮菜单 | array | — | ['head','bold','fontSize','fontName','italic','underline','strikeThrough','indent','lineHeight','foreColor','backColor','link','list','justify','quote','emoticon','image','video','table','code','splitLine','undo','redo'] |
| colors | 颜色 | array | — | ['#000000','#eeece0','#1c487f','#4d80bf'] |
| fontNames | 字体 | array | — | ['黑体','仿宋','楷体','标楷体','华文仿宋','华文楷体','宋体','微软雅黑','Arial','Tahoma','Verdana','Times New Roman','Courier New'] |
| lineHeights | 行间距 | array | — | ['1','1.15','1.6','2','2.5','3'] |
| showFullScreen | 是否全屏 | boolean | true/false | false |
## Methods
| 方法名 | 说明 | 参数 |
| ------- | --------- | ---------------------- |
| getHtml | 获取 HTML | Function(html: string) |
| getText | 获取 text | Function(text: string) |
| getJson | 获取 Json | Function(json: object) |

View File

@ -0,0 +1,39 @@
# Highlight 文字高亮
文字高亮组件,用于突出显示文字。
`Highlight` 组件代码位于`src/components/Highlight`
## 使用
```vue
<template>
<highlight :keys="['vue-element-plus-admin']">
vue-element-plus-admin内置了动态路由权限验证典型的业务模型丰富的功能组件并且提供了多页配置开箱即用可以用来作为项目的启动模版。它可以帮助你快速搭建企业级中后台产品原型也可以作为一个示例用于学习。
</highlight>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
export default defineComponent({
components: {
Highlight: () => import('_c/Highlight/index.vue')
}
})
</script>
```
## Attributes
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| ----- | ------------ | ------ | ------ | ------- |
| tag | 标签类型 | string | — | span |
| keys | 关键字 | array | — | — |
| color | 文字高亮颜色 | string | — | #2d8cf0 |
## Events
| 事件名称 | 说明 | 回调参数 |
| -------- | ------------------ | -------- |
| click | 点击高亮文字的回调 | key |

View File

@ -0,0 +1,36 @@
# MarkDown编辑器 组件
基于 `tui-editor` 进行二次封装的 `MarkDown编辑器` 组件。
`MarkDown` 组件位于[src/components/MarkDown](http://192.168.169.57:9000/scm/index.html#contentPanel;9IRMTD52f3;null;vue-doc/src/components/MarkDown/index.vue;content)
## 使用
``` vue
// 引入
import MarkDown from '_c/MarkDown'
// 使用
<mark-down ref="markdown" :content="content" height="600px" />
```
## Props
| 属性 | 类型 | 默认值 | 可选值 | 说明 |
|---------- |-------- |---------- |---------- |---------- |
| content | string | — | — | 编辑器内容 |
| height | string | 400px | — | 编辑器高度 |
| initialEditType | string | markdown | wysiwyg | 编辑器类型 |
| hideToolBar | boolean | true | false | 是否隐藏工具栏 |
| hideFooter | boolean | false | true | 是否隐藏底部操作栏 |
| hideHeader | boolean | false | true | 是否隐藏头部操作栏 |
| placeholder | string | 请输入文章标题 | — | 提示语 |
| btnText | string | 发布 | — | 发布按钮文字 |
| hideUi | boolean | false | true | 隐藏预览区域 |
## 事件
| 名称 | 说明 | 参数 |
|---------- |-------- |-------- |
| change | 编辑器内容改变时的回调 | data |
| setMarkdown | 点击发布时的回调 | { title, value, html } |

View File

@ -0,0 +1,30 @@
# More 组件
显示更多组件。
`More` 组件位于[src/components/More](http://192.168.169.57:9000/scm/index.html#contentPanel;9IRMTD52f3;null;vue-doc/src/components/More/index.vue;content)
## 使用
``` vue
// 引入
import More from '_c/More/index.vue'
// 使用
<More
:style-obj="{
width: '600px',
height: '100px',
lineHeight: '28px'
}"
content="有两个年轻人同在一家车行里工作,两个人关系很好,以兄弟相称。他们在这家车行已经做了两年了,每天除了修理汽车外什么也没有。哥哥总不肯闲着,他一会儿扫地,一会儿擦玻璃,有时还帮助别人干活儿。弟弟却不这么勤快,没有急活儿的时候他总是懒洋洋地躺着。一天,车行里来了一位中年主顾,他说汽车出了点毛病,让他们给修理一下。弟弟刚刚吃完饭,正在休息呢,哪里肯干活。于是,哥哥走了过去,把弟弟手中的抹布接过来,给汽车做了检查。车子没什么大问题,就是很长时间没修养过了,于是他对那位先生说:“您放心地交给我吧,车子明天一定能修好。”客人听到这话,放心地走了。哥哥一刻不停地忙了起来,他不但修理好了汽车的毛病,还把汽车里里外外擦得一尘不染。这时,躺在一旁的弟弟嘲笑他说:“老兄,别太傻了,不该干的活儿也干了,那么勤快有什么用!”哥哥却笑了笑说:“反正我也没事做,擦擦车我并没有受损失呀,等明天顾客来取车时看到车子焕然一新心里一定很高兴。”第二天,那个顾客来取车了,他看到修好的汽车后非常吃惊,连声感谢修车的哥哥,并对他说:“我是一个大公司的董事长,你为我修车的这种勤快、细致、周到的精神,使我深受感动。我认为你是一个优秀的人,你愿意到我的公司去工作吗?”哥哥的命运从此发生了改变,不久,经过努力他当上了这个公司的部门经理;而弟弟却仍然在车行里做着他觉得枯燥的工作。"
/>
```
## Props
| 属性 | 类型 | 默认值 | 可选值 | 说明 |
|---------- |-------- |---------- |---------- |---------- |
| content | string | — | — | 内容 |
| lineClamp | number | 1 | — | 默认超过几行之后隐藏 |
| styleObj | object | — | — | 内容样式 |

View File

@ -0,0 +1,48 @@
# Preview 预览
图片预览组件,实现函数式调用组件,无需基于图片进行点击预览。
`Preview` 组件代码位于`src/components/Preview`
## 使用
```vue
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { createImgPreview } from '_c/Preview'
export default defineComponent({
setup() {
const imgList = ref<IObj>([
'https://img1.baidu.com/it/u=657828739,1486746195&fm=26&fmt=auto&gp=0.jpg',
'https://img0.baidu.com/it/u=3114228356,677481409&fm=26&fmt=auto&gp=0.jpg',
'https://img1.baidu.com/it/u=508846955,3814747122&fm=26&fmt=auto&gp=0.jpg',
'https://img1.baidu.com/it/u=3536647690,3616605490&fm=26&fmt=auto&gp=0.jpg',
'https://img1.baidu.com/it/u=4087287201,1148061266&fm=26&fmt=auto&gp=0.jpg',
'https://img2.baidu.com/it/u=3429163260,2974496379&fm=26&fmt=auto&gp=0.jpg'
])
function showHasImg(i: number) {
createImgPreview({
index: i,
imageList: imgList.value
})
}
return {
imgList,
showHasImg
}
}
})
</script>
```
## Attributes
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| -------- | ------------------ | -------- | ---------- | ------ |
| index | 默认展示第几张图片 | number | — | 0 |
| list | 需要预览的图片列表 | array | — | [] |
| zIndex | 层级 | number | — | 2000 |
| infinite | 是否无缝 | boolean | false/true | true |
| onClose | 关闭时的回调 | function | — | — |
| onSelect | 点击时的回调 | function | — | — |

View File

@ -0,0 +1,58 @@
# Qrcode 二维码
基于 `qrcode.js` 封装的二维码组件。
`Qrcode` 组件代码位于`src/components/Qrcode`
## 使用
```vue
<template>
<div>
<qrcode text="vue-element-plus-admin" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
components: {
Qrcode: () => import('_c/Qrcode/index.vue')
}
})
</script>
<style></style>
```
## Attributes
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| text | 二维码内容 | string/array | — | — |
| tag | 组件标签 | string | — | canvas |
| options | qrcode.js 配置项 | object | — | — |
| width | 二维码宽度 | number | — | 200 |
| disabled | 是否过期 | boolean | true/false | false |
| disabledText | 过期提示内容 | string | — | 二维码已失效 |
| logo | logo 图标,如果为 object 类型,配置项详见[下表](#Logo) | object/string | — | — |
## Logo<span id="Logo"></span>
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
| ------------ | -------------------- | ------ | ------ | --------- |
| logoSize | 二维码 logo 尺寸 | number | — | 0.15 |
| bgColor | 二维码 logo 背景颜色 | string | — | #ffffff |
| borderSize | 二维码 logo 边框尺寸 | number | — | 0.05 |
| crossOrigin | 二维码 logo 跨域设置 | string | — | anonymous |
| borderRadius | 二维码 logo 边框圆角 | number | — | 8 |
| logoRadius | 二维码 logo 圆角 | number | — | 0 |
## Events
| 事件名称 | 说明 | 回调参数 |
| ------------- | ---------------------- | -------- |
| done | 二维码加载成功后的回调 | dataURL |
| click | 二维码点击后的回调 | — |
| disabledClick | 二维码失效点击后的回调 | — |

View File

@ -0,0 +1,18 @@
# SvgIcon 图标
用于项目内组件的展示,基本支持所有图标库
`SvgIcon` 组件代码位于`src/components/SvgIcon`
## 使用
``` vue
<svg-icon icon-class="dashboard" class-name="disabled" />
```
## Attributes
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |---------- |---------- |
| iconClass | svg文件名必填项 | string | - | - |
| className | 自定义类名 | string | - | - |

134
docs/docs/guide/README.md Normal file
View File

@ -0,0 +1,134 @@
# 介绍
[vue-element-plus-admin](https://github.com/kailong321200875/vue-element-plus-admin)是一个基于[vue3](https://cn.vuejs.org/index.html)、[typescript4](https://www.typescriptlang.org/)、[vite2](https://vitejs.cn/)、[element-ui](https://github.com/ElemeFE/element)的后台集成方案,内置了动态路由,权限验证,典型的业务模型,丰富的功能组件,开箱即用,可以用来作为项目的启动模版。它可以帮助你快速搭建企业级中后台产品原型,也可以作为一个示例,用于学习。
::: tip 建议
`vue-element-plus-admin`的定位是后台集成方案,不太适合当基础模板来进行二次开发。因为集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。如果需要二次开发,推荐使用`template`分支下的基础模版进行开发。
:::
## 前序准备
你需要在本地安装[node](https://nodejs.org/en/)和[git](https://git-scm.com/)。本项目技术栈基于[ES2015+](https://es6.ruanyifeng.com/)、[typescript](https://www.typescriptlang.org/)、[vue](https://cn.vuejs.org/index.html)、[pinia](https://pinia.esm.dev/)、[vue-router](https://router.vuejs.org/zh/)、[axios](https://github.com/axios/axios)和[element-plus](https://github.com/element-plus/element-plus),所有的请求数据都使用[Mock.js](https://github.com/nuysoft/Mock)进行模拟,提前了解和学习这些知识会对使用本项目有很大的帮助。
## 功能
```sh
- 登录 / 注销
- 权限验证
- 页面权限
- 按钮权限
- 权限配置
- 多环境
- 全局功能
- 三种不同风格 layout 布局
- 引导页
- 动态侧边栏(支持多级路由嵌套)
- 动态面包屑
- 快捷导航(标签页)
- Svg Sprite 图标
- 本地 mock 数据
- Screenfull全屏
- 功能组件
- 图表
- 图片预览
- 消息提示
- 弹窗
- 详情
- 头像组
- 文字高亮
- 查询
- 富文本编辑器
- 二维码
- 水印
- 表格
- 基础表格
- 分页表格
- 带斑马纹表格
- 带边框表格
- 带状态表格
- 固定表头
- 固定列
- 固定列和表头
- 流体高度
- 多级表头
- 单选
- 多选
- 排序
- 筛选
- 展开行
- 树形数据与懒加载
- 自定义表头
- 表尾合计行
- 合并行或列
- 自定义索引
- 多级菜单缓存
- 综合实例
- 列表综合实例-弹窗
- 列表综合实例-页面
- 权限管理
- 用户管理
- 角色管理
```
## 目录结构
```sh
.
├── public # 静态资源
├── src # 项目代码
│ ├── assets # 静态资源
│ ├── axios-config # axios配置
│ ├── components # 公用组件
│ ├── directive # 自定义指令
│ ├── hooks # 常用hooks
│ ├── layout # 布局组件
│ ├── plugins # 外部插件
│ ├── mock # 模拟数据
│ ├── router # 路由配置
│ ├── store # 状态管理
│ ├── styles # 全局样式
│ ├── utils # 全局工具类
│ ├── views # 路由页面
│ ├── vue-bus # 跨组件通信
│ ├── App.vue # 入口vue文件
│ ├── env.d.ts # vue全局声明
│ ├── global.d.ts # 业务代码全局声明
│ ├── main.ts # 主入口文件
│ └── permission.ts # 路由拦截
├── .babelrc # babel配置
├── .editorconfig # 编辑器配置
├── .eslintignore # eslint 忽略配置项
├── .eslintrc # eslint 配置文件
├── .gitignore # git 忽略提交配置文件
├── .postcssrc.js # postcss 配置文件
├── .prettierrc.js # 代码格式 配置文件
├── .stylelintignore # stylelint 忽略文件
├── CHANGLOG.md # 更新日志
├── commitlint.config.js # git commit 配置文件
├── package.json
├── README.md
├── stylelint.config # stylelint配置文件
├── tsconfig.json # ts 配置文件
├── vite.config.ts # vite配置
└── yarn.lock
```
## 浏览器支持
由于 Vue 3 不再支持 IE11vue-element-plus-admin 也不支持 IE。
| [<img src="https://cdn.jsdelivr.net/npm/@browser-logos/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Safari |
| --- | --- | --- | --- |
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 64 | Safari ≥ 12 |
## IDE推荐设置
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)

View File

@ -0,0 +1,154 @@
# 项目配置
## 环境变量配置
在项目中,难免会碰到需要在不同环境下做不同的事,这时候,就需要自己去配置环境变量。目前`vue-element-plus-admin`提供了四种环境变量。除了`.env.base`是针对开发的时候,其他三种环境变量是针对打包的时候去构建不同环境的。目前主要的一个区别就是为了区分出接口前缀。
开发人员可以根据实际项目情况去进行扩展改造。
环境变量文件都存放更目录下的`.env.xx`文件中。
::: tip 提示
获取全局环境变量import.meta.env
:::
### .env.base
当运行`npm run dev`的时候,默认取的是`.env.base`中的环境变量以及参数。
```javascript
// 环境
NODE_ENV=development
// 接口环境
VITE_API_BASEPATH='base'
```
### .env.dev
当运行`npm run build:dev`的时候,默认取的是`.env.dev`中的环境变量以及参数,打包开发环境的项目代码,适用于开发人员的自我测试。
```javascript
// 环境
NODE_ENV=production
// 接口环境
VITE_API_BASEPATH='dev'
```
### .env.test
当运行`npm run build:test`的时候,默认取的是`.env.test`中的环境变量以及参数,打包测试环境的项目代码,适用于测试人员进行项目测试。
```javascript
// 环境
NODE_ENV=production
// 接口环境
VITE_API_BASEPATH='test'
```
### .env.pro
当运行`npm run build:pro`的时候,默认取的是`.env.pro`中的环境变量以及参数,打包生产环境的项目代码,用于上线交付。
```javascript
// 环境
NODE_ENV=production
// 接口环境
VITE_API_BASEPATH='pro'
```
## 主题色和样式配置
主要用于配置侧边栏菜单和顶部菜单的主题色以及`layout`整体布局
侧边栏菜单和顶部菜单的样式主要代码位置位于`src/styles/sidebar.less`
目前`vue-element-plus-admin`的布局和主题采用的是`css变量`,如果需要更,可在`src/styles/var.less`中进行更改,更改后将影响整个项目的主题和布局。
::: tip 提示
目前并没有开放主题更改,只能手动在代码上进行变更,后续也会针对这个需求去进行主题的改造。
:::
```css
:root {
--main-color: #018ffb; // 主颜色
// Silder
--menu-text-color: #bfcbd9; // 左侧菜单字体颜色
--menu-active-text-color: #fff; // 左侧菜单选中字体颜色
--menu-active-background-color: #2d8cf0; // 左侧菜单选中背景颜色
--menu-background-color: #001529; // 左侧菜单背景颜色
--sub-menu-background-color: #1f2d3d; // 左侧子菜单背景颜色
--sub-menu-hover-color: #2d8cf0; // 左侧子菜单悬停字体颜色
--sub-menu-active-text-color: #fff; // 左侧子菜单选中字体颜色
--menu-width: 200px; // 左侧菜单宽度
--menu-min-width: 64px; // 左侧菜单最小宽度
// topSider
--top-sider-height: 60px; // 顶部菜单高度最低60pxelement的最小高度。
--top-menu-text-color: #303133; // 顶部菜单字体颜色
--top-menu-active-text-color: #2d8cf0; // 顶部菜单选中字体颜色
--top-menu-active-background-color: #fff; // 顶部菜单选中背景颜色
--top-menu-background-color: #fff; // 顶部菜单背景颜色
--top-sub-menu-background-color: #1f2d3d; // 顶部子菜单背景颜色
--top-sub-menu-hover-color: #2d8cf0; // 顶部子菜单悬停字体颜色
--top-sub-menu-active-text-color: #2d8cf0; // 顶部子菜单选中字体颜色
// meunTab
--menu-tab-width: 90px; // 菜单tab宽度
--menu-tab-item-height: 70px; // 菜单子tab高度
--menu-tab-background-color: #fff; // 菜单tab背景颜色
--menu-tab-text-color: #000; // 菜单tab字体颜色
--menu-tab-active-background-color: #2d8cf0; // 菜单tab选中背景颜色
--menu-tab-active-text-color: #fff; // 菜单tab选中字体颜色
// menuTopTab
--menu-top-tab-width: 120px; // 顶部菜单tab宽度
--menu-top-tab-background-color: #fff; // 顶部菜单tab背景颜色
--menu-top-tab-text-color: #000; // 顶部菜单tab字体颜色
--menu-top-tab-active-background-color: #2d8cf0; // 顶部菜单选中背景颜色
--menu-top-tab-active-text-color: #fff; // 顶部菜单选中字体颜色
// navbar
--navbar-height: 40px; // navbar高度
// tagsView
--tags-view-height: 40px; // 标签页高度
--tag-background-color: #fbfbfb; // 标签页背景颜色
--tag-active-background-color: #fff; // 标签页选中背景颜色
// content
--content-background-color: #fff; // 展示区域背景颜色
--app-background-color: #f5f7f9; // 整体背景颜色
// html body
--content-min-width: 992px; // 项目最小宽度
}
```
## 项目配置
::: tip 提示
项目配置用于配置项目内展示的内容/布局/文本等效果,存于 `pinia` 中。
文件位置`src/store/modules/app.ts`,更改保存之后将会立即作用于整个项目中,开发者可根据实际项目进行扩展。
:::
```javaScript
collapsed: false, // 菜单栏是否栏缩收
showLogo: true, // 是否显示logo
showTags: true, // 是否显示标签栏
showNavbar: true, // 是否显示navbar
fixedHeader: true, // 是否固定header
layout: 'Classic', // layout布局
showBreadcrumb: true, // 是否显示面包屑
showHamburger: true, // 是否显示侧边栏缩收按钮
showScreenfull: true, // 是否全屏按钮
showUserInfo: true, // 是否显示用户头像
title: 'vue-element-plus-admin', // 标题
logoTitle: 'vue-ElPlus-admin', // logo标题
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
showBackTop: true, // 是否显示回到顶部
showMenuTab: false, // 是否固定一级菜单
requestTime: false // 是否在接口调用时添加时间戳避免IE缓存
```

364
docs/docs/guide/mock.md Normal file
View File

@ -0,0 +1,364 @@
# Mock 与联调
## Mock
本项目中的接口采用的是`Mock.js`配合`axios`进行接口对接模拟,默认是所有环境下都开启`mock`。如需不需要,可以删除`src/main.ts`中引入`mock`的代码。
具体配置代码可查看`src/mock/index.ts`
::: warning 注意
自定义`mock`接口的时候,可能会出现跨域的问题,这时候开发者可以自行在`vite.config.ts`中自行代理。
Mock的模拟请求在浏览器中是看不到请求记录的这点也是一个弊端开发者只能通过代码打印的形式去查看返回的结果后续考虑还有没有更优的方案。
:::
`mock`接口的写法可参考`src/mock/example/index.ts`
```javaScript
import Mock from 'mockjs'
import { toAnyString } from '@/utils'
let List: any[] = []
const count = 100
const baseContent =
'<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
for (let i = 0; i < count; i++) {
List.push(
Mock.mock({
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)'
// image_uri
})
)
}
export default [
// 列表接口
{
url: 'http://mockjs.test.cn/example/list',
type: 'get',
response: (config: any) => {
const { title, pageIndex, pageSize } = config.query
const mockList = List.filter((item) => {
if (title && item.title.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(item, index) =>
index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: '0000',
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 删除接口
{
url: 'http://mockjs.test.cn/example/delete',
type: 'post',
response: (config: any) => {
const ids = config.body.ids
if (!ids) {
return {
code: '500',
message: '请选择需要删除的数据'
}
} else {
let i = List.length
while (i--) {
if (ids.indexOf(List[i].id) !== -1) {
List.splice(i, 1)
}
}
return {
code: '0000',
data: 'success'
}
}
}
},
// 详情接口
{
url: 'http://mockjs.test.cn/example/detail',
type: 'get',
response: (config: any) => {
const { id } = config.query
for (const example of List) {
if (example.id === id) {
return {
code: '0000',
data: example
}
}
}
}
},
// 保存接口
{
url: 'http://mockjs.test.cn/example/save',
type: 'post',
response: (config: any) => {
const data = config.body
if (!data.id) {
List = [
Object.assign(data, {
id: toAnyString(),
importance: Number(data.importance)
})
].concat(List)
return {
code: '0000',
data: 'success'
}
} else {
List.map((item) => {
if (item.id === data.id) {
for (const key in item) {
if (key === 'importance') {
item[key] = Number(data[key])
} else {
item[key] = data[key]
}
}
}
})
return {
code: '0000',
data: 'success'
}
}
}
}
]
```
## Axios
本项目中的所有接口请求都是基于[axios.js](https://github.com/axios/axios)来进行的。
为了便于后期的维护,本项目中对`axios`进行了二次封装,从而可以对接口请求进行统一拦截。
### 配置项
同时,可供了一些`axios`的全局配置,具体代码`src/axios-config/config.ts`,开发者可自行更改扩展。
```typeScript
/**
* request全局配置
*/
const config: {
base_url: {
base: string
dev: string
pro: string
test: string
}
result_code: number | string
default_headers:
| 'application/json'
| 'application/x-www-form-urlencoded'
| 'multipart/form-data'
request_timeout: number
} = {
/**
* api请求基础路径
*/
base_url: {
// 开发环境接口前缀
base: 'http://mockjs.test.cn',
// 打包开发环境接口前缀
dev: 'http://mockjs.test.cn',
// 打包生产环境接口前缀
pro: 'http://mockjs.test.cn',
// 打包测试环境接口前缀
test: 'http://mockjs.test.cn'
},
/**
* 接口成功返回状态码
*/
result_code: '0000',
/**
* 接口请求超时时间
*/
request_timeout: 60000,
/**
* 默认接口请求类型
* 可选值application/x-www-form-urlencoded multipart/form-data
*/
default_headers: 'application/json'
}
export default config
```
### 请求方法
为了便于维护,本项目中对所有请求方式的接口进行封装,开发者只需要参数对应参数即可使用如`get`、`post`请求。
具体代码`src/axios-config/index.ts`,开发者可自行更改扩展。
```typeScript
import request from './request'
import { appStore } from '@/store/modules/app'
import config from './config'
import { AxiosPromise, ResponseType } from 'axios'
const { default_headers } = config
export interface Config {
params?: any
data?: any
url?: string
method?: 'get' | 'post' | 'delete' | 'put'
headersType?: string
responseType?: ResponseType
}
function fetch({
url,
method,
params,
data,
headersType,
responseType
}: Config): AxiosPromise {
return request({
url: url,
method,
params: appStore.requestTime
? { time: new Date().getTime(), ...(params || {}) }
: params,
data,
responseType: responseType,
headers: {
'Content-Type': headersType || default_headers
}
})
}
export default fetch
```
### 请求拦截
具体代码`src/axios-config/request.ts`,开发者可自行更改扩展。
``` javascript
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { Message } from '_c/Message'
import qs from 'qs'
import config from './config'
const { result_code, base_url } = config
export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH as string]
// 创建axios实例
const service: AxiosInstance = axios.create({
baseURL: PATH_URL, // api 的 base_url
timeout: config.request_timeout // 请求超时时间
})
// request拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
if (
config.method === 'post' &&
(config.headers as any)['Content-Type'] === 'application/x-www-form-urlencoded'
) {
config.data = qs.stringify(config.data)
}
// get参数编码
if (config.method === 'get' && config.params) {
let url = config.url as string
url += '?'
const keys = Object.keys(config.params)
for (const key of keys) {
if (config.params[key] !== void 0 && config.params[key] !== null) {
url += `${key}=${encodeURIComponent(config.params[key])}&`
}
}
url = url.substring(0, url.length - 1)
config.params = {}
config.url = url
}
return config
},
(error: AxiosError) => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// response 拦截器
service.interceptors.response.use(
(response: AxiosResponse<any>) => {
if (response.config.responseType === 'blob') {
// 如果是文件流,直接过
return response
} else if (response.data.code === result_code) {
return response.data
} else {
Message.error(response.data.message)
}
},
(error: AxiosError) => {
console.log('err' + error) // for debug
Message.error(error.message)
return Promise.reject(error)
}
)
export default service
```
## Api管理
在之前,我一直在纠结,`api`接口到底是集中管理,还是分模块各自管理,在之后的几个公司项目的实践中,在项目越来越大,一个模块有可能调用了几十个接口,那这时候在集中管理`api`,会导致`api`的来源不明确,不利于查找对应`api`接口。所以现在比较偏向分模块进行`api`管理,这样更便于后期的维护,各模块只关心和维护模块中使用的`api`接口。
```typeScript
import fetch from '@/axios-config'
export const loginApi = ({ data }: any) => {
return fetch({ url: '/user/login', method: 'post', data })
}
export const getRoleDetApi = ({ params }: any) => {
return fetch({ url: '/role/detail', method: 'get', params })
}
```

View File

@ -0,0 +1,101 @@
# 快速上手
::: tip 提示
本项目需要[Node.js](https://nodejs.org/en/)版本>=14
:::
## 代码获取
::: warning 注意
注意存放代码的目录及所有父级目录不能存在中文、韩文、日文以及空格,否则安装依赖后启动会出错。
:::
```sh
# clone 代码
git https://github.com/kailong321200875/vue-element-plus-admin.git
```
## 安装
### 安装 Node.js
如果您电脑未安装[Node.js](https://nodejs.org/en/),请安装它。
#### 验证
```sh
# 出现相应npm版本即可
npm -v
# 出现相应node版本即可
node -v
```
### 安装依赖
推荐使用[Yarn](https://github.com/yarnpkg/yarn)进行依赖安装。
在项目根目录下,打开命令窗口执行,耐心等待安装完成即可
```sh
# 安装依赖
yarn
```
::: tip 提示
安装依赖时`husky`安装失败
请查看你的源码是否从`github`直接下载的,直接下载是没有`.git`文件夹的,而`husky`需要依赖`git`才能安装。此时需使用`git init`初始化项目,再尝试重新安装即可。
或者,当前安装依赖的命令框,没有`git`环境,请确保存在`git`环境。
:::
## 运行项目
```sh
# 运行项目
npm run dev
```
## npm scripts
```sh
"scripts": {
# 安装所有依赖
"i:all": "npm run i && npm run docs:i",
# 安装项目依赖
"i": "yarn install",
# 启动项目
"dev": "vite --mode base",
# 打包生产环境
"build:pro": "vue-tsc --noEmit && vite build --mode pro",
# 打包开发环境
"build:dev": "vue-tsc --noEmit && vite build --mode dev",
# 打包测试环境
"build:test": "vue-tsc --noEmit && vite build --mode test",
# 本地预览打包后的文件
"serve": "vite preview",
# 检测所有依赖更新
"check": "npx npm-check-updates",
# 删除所有的node包
"clean": "npx rimraf docs/node_modules && npx rimraf node_modules",
# 删除文档的node包
"clean:docs": "npx rimraf docs/node_modules",
# 删除主依赖
"clean:main": "npx rimraf node_modules",
# eslint检测修复
"lint": "eslint --fix --ext .js,.ts,.vue ./src",
# prettier格式化
"format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
# stylelint 格式化
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
# 提交前的统一格式化
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
# 只对已提交文件进行格式化
"lint:pretty": "pretty-quick --staged",
# husky初始化
"postinstall": "husky install",
# ts 类型检测
"ts:check": "vue-tsc --noEmit"
}
```

169
docs/docs/guide/role.md Normal file
View File

@ -0,0 +1,169 @@
# 权限配置
## 介绍
在实际开发中,我们经常需要根据项目需求进行一个权限的控制,例如:菜单的权限,页面的按钮权限,这样做的好处是可以让每个用户都能专注于自己的业务。
本系项目前提供了两种权限控制的示例,当然,都是比较简单的,只是给开发者提供一点思路和借鉴而已。一般来讲,权限控制可以是前端控制,也可以是后端控制。两种都有自个的优势:
前端控制权限,那本质上就是以静态路由表为准,后端接口只提供简单的配置规则,然后有前端进行循环遍历,筛选出所需要的路由,然后进行动态路由的生成。优势在于比较快捷,所有的配置都已经在静态路由表中配置好了。劣势就是不太灵活,假如需要换下菜单的顺序或者更改菜单的名称,都需要前端这边修改完之后重新打包部署。
## 前端控制权限
我们可以看下本项目的前端路由控制逻辑,具体代码在`src/views/role-demo/role/components/InfoWrite.vue`
![](https://sanyuanda.oss-cn-hangzhou.aliyuncs.com/imgs/1.jpg)
前端控制的例子的界面相对简单,我们直接看保存之后的逻辑:
``` JavaScript
// 获取所有被选中节点,由于是前端渲染,所以只要保存一维数组就行
form.checkedNodes = (tree.value as any).getCheckedNodes(false, true)
console.log(JSON.stringify(form.checkedNodes))
// 获取所有被选中的keys便于渲染是否选中
form.checkedkeys = (tree.value as any).getCheckedKeys()
console.log(JSON.stringify(form.checkedkeys))
```
我们通过`Element-ui Tree`组件提供的API可以轻松的获取到被选中的数据。之后就可以调用后端接口进行权限数据的保存了当然开发者可以根据实际情况进行扩展流程大体是一致的。
之后,用户登录的时候,我们就可以拿到对应的权限数据,进行动态渲染了。权限过滤的逻辑主要在`src/store/modules/permission.ts`中的`generateRoutes`方法:
``` JavaScript
// 路由过滤,主要用于权限控制
function generateRoutesFn(routes: AppRouteRecordRaw[], basePath = '/'): AppRouteRecordRaw[] {
const res: AppRouteRecordRaw[] = []
for (const route of routes) {
// skip some route
if (route.meta && route.meta.hidden && !route.meta.showMainRoute) {
console.log(route)
continue
}
let onlyOneChild: Nullable<string> = null
if (route.children && route.children.length === 1 && !route.meta.alwaysShow) {
onlyOneChild = (
isExternal(route.children[0].path)
? route.children[0].path
: path.resolve(path.resolve(basePath, route.path), route.children[0].path)
) as string
}
let data: Nullable<IObj> = null
// 如不需要路由权限,可注释以下逻辑
// 权限过滤,通过获取登录信息里面的角色权限,动态的渲染菜单。
const list = wsCache.get(appStore.getUserInfo).checkedNodes
// 开发者可以根据实际情况进行扩展
for (const item of list) {
// 通过路径去匹配
if (isExternal(item.path) && (onlyOneChild === item.path || route.path === item.path)) {
data = Object.assign({}, route)
} else {
const routePath = path.resolve(basePath, onlyOneChild || route.path)
if (routePath === item.path || (route.meta && route.meta.followRoute === item.path)) {
data = Object.assign({}, route)
}
}
}
// 如不需要路由权限,解注释下面一行
// data = Object.assign({}, route)
// recursive child routes
if (route.children && data) {
data.children = generateRoutesFn(route.children, path.resolve(basePath, data.path))
}
if (data) {
res.push(data as AppRouteRecordRaw)
}
}
return res
}
```
::: tip 提示
由于是前端控制所以我们存储在后端的权限数据可以是一维数组或者直接拿被选中的keys进行过滤这个也是完全根据实际情况来的。
:::
## 后端控制权限
由于在静态路由表配置中,我们会把一些相关展示配置放在`meta`中,如果是以后端控制为主,我们项目中的静态路由表基本上没什么作用了。我们需要把`meta`中的配置放到后端中,有后端去统一管理。基本上,前端静态路由表就没什么用了。
我们可以看下本项目的后路由控制逻辑,具体代码在`src/views/role-demo/role/components/InfoWrite2.vue`
![](https://sanyuanda.oss-cn-hangzhou.aliyuncs.com/imgs/QQ%E5%9B%BE%E7%89%8720210112203233.png)
由于是后端控制为主,所以存储在后端就是以树形的方式存储了,便于我们拿到数据之后直接渲染,格式大致如下:
``` JavaScript
{
path: '/components-demo',
component: '#',
redirect: '/components-demo/echarts',
name: 'ComponentsDemo',
meta: {
title: '功能组件',
icon: 'component',
alwaysShow: true
},
children: [
{
path: 'echarts',
component: 'views/components-demo/echarts/index',
name: 'EchartsDemo',
meta: {
title: '图表'
}
}
]
}
```
`component`不再是直接加载路由文件了,而是以字符串的形式存在,之后当我们在渲染的时候,才会动态去加载对应的路由文件。
权限过滤的逻辑主要在`src/store/modules/permission.ts`中的`getFilterRoutes`方法:
``` JavaScript
// 模拟后端过滤路由
function getFilterRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] {
const res: AppRouteRecordRaw[] = []
for (const route of routes) {
const data: AppRouteRecordRaw | IObj = {
path: route.path,
name: route.name,
redirect: route.redirect
}
data.meta = Object.assign({}, route.meta || {}, { title: route.meta.title })
if (route.component) {
// 动态加载路由文件,可根据实际情况进行自定义逻辑
data.component = (
(route.component as any) === '#'
? Layout
: (route.component as any).includes('##')
? getParentLayout((route.component as any).split('##')[1])
: modules[`../../${route.component}.vue`]
) as any
}
// recursive child routes
if (route.children) {
data.children = getFilterRoutes(route.children)
}
res.push(data as AppRouteRecordRaw)
}
return res
}
```
::: tip 提示
后端控制的权限,我们只需要关注`component`的动态生成就行。
:::
## 总结
其实我们不用太纠结到底是前端控制还是后端控制,主要还是要根据实际情况来。一般来讲,大多数都是比较推崇使用后端控制,这样就不需要前端修改完配置之后在重新打包部署。
本项目中的例子是相对比较简单的,因为不想给出太复杂的而让开发者有无从下手的感觉。但是流程基本上是一致的,开发者是可以在本项目原有的基础上去扩展。

80
docs/docs/guide/router.md Normal file
View File

@ -0,0 +1,80 @@
# 路由配置和侧边栏
路由和侧边栏是组织起一个后台应用的关键骨架。
本项目侧边栏和路由是绑定在一起的,所以你只有在`src/router/index.ts`下面配置对应的路由,侧边栏就能动态的生成了。大大减轻了手动重复编辑侧边栏的工作量。当然这样就需要在配置路由的时候遵循一些约定的规则。
## 配置项
首先我们了解一下本项目配置路由时提供了哪些配置项。
```javaScript
{
redirect: 'noredirect', // 当设置 noredirect 的时候该路由在面包屑导航中不可被点击
name:'router-name' , // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
meta : {
hidden: true , // 当设置 true 的时候该路由不会再侧边栏出现 如404login等页面(默认 false)
alwaysShow: true, // 当你一个路由下面的 children 声明的路由大于1个时自动会变成嵌套的模式
// 只有一个时,会将那个子路由当做根路由显示在侧边栏,
// 若你想不管路由下面的 children 声明的个数都显示你的根路由,
// 你可以设置 alwaysShow: true这样它就会忽略之前定义的规则
// 一直显示根路由(默认 false)
title: 'title', // 设置该路由在侧边栏和面包屑中展示的名字
icon: 'svg-name', // 设置该路由的图标
noCache: true, // 如果设置为true则不会被 <keep-alive> 缓存(默认 false)
breadcrumb: false, // 如果设置为false则不会在breadcrumb面包屑中显示(默认 true)
affix: true, // 如果设置为true则会一直固定在tag项中(默认 false)
noTagsView: true, // 如果设置为true则不会出现在tag中(默认 false)
activeMenu: '/dashboard', // 显示高亮的路由路径
followAuth: '/dashboard', // 跟随哪个路由进行权限过滤
showMainRoute: true, // 设置为true即使hidden为true也依然可以进行路由跳转(默认 false)
followRoute: '/dashboard',// 为路由设置跟随其他路由的权限
}
}
```
## 路由
这里的路由分为两种,`constantRoutes` 和 `asyncRoutes`
`constantRoutes` 代表那些不需要动态判断权限的路由如登录页、404、等通用页面。
`asyncRoutes` 代表那些需求动态判断权限并通过 `addRoutes` 动态添加的页面。
## 侧边栏
本项目侧边栏主要基于 `element-plus``el-menu` 改造。
侧边栏是通过读取路由并结合权限判断而动态生成的,而且还需要支持路由无限嵌套,所以这里还使用到了递归组件。
具体代码可查看`src/layout/components/Sider/index.vue`
## 多级目录(嵌套路由)
如果你的路由是多级目录,如本项目 `src/views/level` 那样,有三级路由嵌套的情况下,不要忘记还要手动在二级目录的根文件下添加一个 `<router-view />`
## 路由缓存
::: tip 提示
本项目中路由都是默认缓存的,这里涉及到了嵌套路由的缓存问题。为了保证多级路由缓存可以正常使用,本项目中在动态路由中,把二级以下的路由全部转换成二级路由,菜单栏还是遵循开发者的嵌套结构来渲染。
具体过滤代码可以查看`src/store/modules/permission.ts`
:::
## 面包屑
::: tip 提示
本项目中也封装了一个面包屑导航,它也是通过 `watch $route` 变化动态生成的。它和 `menu` 也一样,也可以通过之前那些配置项控制一些路由在面包屑中的展现。大家也可以结合自己的业务需求增改这些自定义属性。比如可以在路由中声明`breadcrumb:false`,让其不在 `breadcrumb` 面包屑显示。
具体代码可以查看`src/layout/components/Breadcrumb/index.vue`
:::
## 标签页
::: tip 提示
本项目中每进行一次路由跳转,都会相应的把跳转的路由进行存储,进而在 `标签页` 中展示,让开发者可以快速的进行路由历史记录的操作。如果不需要,可以在路由配置中设置`noTagsView: true`即可隐藏。
组件具体代码可查看`src/layout/components/TagsView/index.vue`
标签页过滤代码可查看`src/store/modules/tags-view.ts`
:::

147
docs/docs/guide/standard.md Normal file
View File

@ -0,0 +1,147 @@
# 项目规范
## ESlint
### 介绍
::: tip
使用 lint 的好处不管是多人合作还是个人项目具备基本工程素养的同学都会注重编码规范而代码风格检查Code Linting简称 Lint是保障代码规范一致性的重要手段。
遵循相应的代码规范有以下好处
- 较少 bug 错误率
- 高效的开发效率
- 更高的可读性
:::
### 配置项
所有的配置文件都在`.eslintrc.js`中。 本项目基本规范是依托于 `vue` 官方的 `eslint` 规则 [eslint-config-vue](https://github.com/vuejs/eslint-config-vue) 做了少许的修改。大家可以按照自己的需求进行定制化配置。
比如:我个人或者项目组习惯于使用两个空格,但你可能觉得四个空格更顺眼,你可以做如下修改。 进入项目 `.eslintrc.js` 中,找到 `indent`,然后修改为 `4` 即可。 还有各种各样的配置信息,详情见 [ESLint](https://eslint.org/docs/rules/) 文档。
默认情况下使用了最严格的`plugin:vue/recommended`来校验代码,若你觉得太严格可自行修改。
```javaScript
eslintrc.js
module.exports = {
extends: ['plugin:vue/recommended', 'eslint:recommended']
//你可以修改为 extends: ['plugin:vue/essential', 'eslint:recommended']
}
```
### 自动修复
运行如下命令,`eslint` 会自动修复一些简单的错误。
```sh
npm run lint
```
## CommitLint
### 介绍
在一个团队中,每个人的`git`的`commit`信息都不一样,五花八门,没有一个机制很难保证规范化,如何才能规范化呢?可能你想到的是`git`的`hook`机制,去写`shell`脚本去实现。这当然可以,其实`JavaScript`有一个很好的工具可以实现这个模板,它就是`commitlint`(用于校验 git 提交信息规范)。
### 配置项
commit-lint 的配置位于项目根目录下`commitlint.config.js`
### 提交规范
- feat 新功能(feature)
- fix 修补 bug
- docs 文档(documentation)
- style 格式、样式(不影响代码运行的变动)
- refactor 重构(即不是新增功能,也不是修改 BUG 的代码)
- perf 优化相关,比如提升性能、体验
- test 添加测试
- build 编译相关的修改,例如发布版本、对项目构建或者依赖的改动
- ci 持续集成修改
- chore 构建过程或辅助工具的变动
- revert 回滚到上一个版本
- workflow 工作流改进
- mod 不确定分类的修改
- wip 开发中
- types 类型修改
- release 版本发布
### 如何关闭
在`.husky/commit-msg`内注释以下代码即可
```sh
# npx --no-install commitlint --edit "$1"
```
### 提交示例
```sh
git commit -m 'feat(home): add home page'
```
## Stylelint
### 介绍
`stylelint`用于校验项目内部 css 的风格,加上编辑器的自动修复,可以很好的统一项目内部 css 风格
### 配置
`stylelint` 配置位于根目录下 stylelint.config.js
### 编辑器配合
如果您使用的是 `vscode` 编辑器的话,只需要安装[StyleLint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint)插件,即可在保存的时候自动格式化文件内部 css 样式。
## Prettier
### 介绍
`prettier`可以用于统一项目代码风格,统一的缩进,单双引号,尾逗号等等风格。
### 配置
`prettier` 配置文件位于项目根目录下 prettier.config.js
### 编辑器配合
如果您使用的是 `vscode` 编辑器的话,只需要安装[Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)插件,即可在保存的时候自动格式化文件内部 `js | ts` 格式。
## Git Hooks
很多时候我们 lint 的校验是放在持续集成阶段,大概流程如下:
::: tip
流程代码提交 --> 跑 CI 发现问题(远程) --> 本地修复问题 --> 重新提交 --> 通过检查(远程)
:::
但这样有一个问题,我们的 `CI(持续集成)` 往往不是仅仅只做 `Lint`工作,它还有会有很多其它的任务(如打包文件,静态资源上传 `CDN` 等),这样就导致特别的浪费时间,往往可能需要几分钟之后你才会发现问题,或者有的时候你根本就没有发现你的 `CI` 没有跑通过。
常见的流程:本地写好了代码,提交,开始跑 `lint`,发现不通过,本地修改代码,再提交,再等待 `CI` 的结果,若还有问题再重复之前的操作。
### husky
最有效的解决方案就是将 `Lint` 校验放到本地,常见做法是使用 [husky](https://github.com/typicode/husky) 或者 [pre-commit](https://github.com/observing/pre-commit) 在本地提交之前先做一次 `Lint`校验。
最后尝试 Git 提交,你就会很快收到反馈:
```sh
git commit -m "Keep calm and commit"
```
但这样会有一个问题,就是这次提交,我可能只修改了一个文件,比如我就修改了 `foo.js` 的内容,但它依然会校验所有`src` 下面的`.js`文件,非常的不友好。导致的问题就是:每次我提交我写的代码,还先要帮人的代码问题解决了才能顺利提交,而且当项目大了之后,检查速度也会变得越来越慢了。
### lint-staged
解决上面的痛点就需要使用 [lint-staged](https://github.com/okonet/lint-staged) 了。它只会校验检查你提交或者说你修改的部分内容。
如上配置,每次它只会在你本地 `commit` 之前,校验你提交的内容是否符合你本地配置的 `eslint`规则,如果符合规则,则会提交成功。如果不符合它会自动执行 `eslint --fix` 尝试帮你自动修复,如果修复成功则会帮你把修复好的代码提交,如果失败,则会提示你错误,让你修好这个错误之后才能允许你提交代码。
### 总结
最佳的 `lint` 规范流程就是推荐团队成员先在自己的编辑器中配置 `eslint`,这样平时写的时候编辑器就能帮你修正一些简单的格式错误,同时提醒你有哪些不符合 `lint` 规范的的地方,并在命令行中提示你错误。。
但这并不是强制的,有些团队成员或者说刚来的实习生没有在编辑器中配置或者无视命令行中提示的错误,这时候就需要配置`pre-commit` 这种强制性校验的东西,保证所有提交到远程仓库的内容都是符合团队规范的。

42
docs/docs/guide/style.md Normal file
View File

@ -0,0 +1,42 @@
# 风格指南
## 介绍
::: tip 推荐
本项目的风格指南主要是参照 `vue` 官方的[风格指南](https://cn.vuejs.org/v2/style-guide/index.html)。在真正开始使用该项目之前建议先阅读一遍指南,这能帮助让你写出更规范和统一的代码。当然每个团队都会有所区别。其中大部分规则也都配置在了[eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue)之中,当没有遵循规则的时候会报错,详细内容见[eslint](http://192.168.169.57/ue/2019/doc/vue-standard/dist/advanced/eslint.html#介绍)章节。
当然也有一些特殊的规范,是不能被 `eslint` 校验的。需要人为的自己注意,并且来遵循。最主要的就是文件的命名规则,这里以本项目来举例。
:::
## Component
所有的`Component`文件都是以大写开头 `(PascalCase)`,这也是官方所推荐的。
但除了 `index.vue`
- @/views/example-demo/example-dialog/components/Detail.vue
- @/components/Charts/index.vue
## JS | TS 文件
所有的`.js .ts`文件都遵循横线连接 `(kebab-case)`
例子:
- @/utils/dom-uitls.ts
## Views
在`views`文件下,代表路由的`.vue`文件都使用横线连接 `(kebab-case)`,代表路由的文件夹也是使用同样的规则。
例子:
- @/views/icons/index.vue
使用横线连接 `(kebab-case)`来命名`views`主要是出于以下几个考虑。
横线连接 `(kebab-case)` 也是官方推荐的命名规范之一 [文档](https://cn.vuejs.org/v2/style-guide/index.html#%E5%8D%95%E6%96%87%E4%BB%B6%E7%BB%84%E4%BB%B6%E6%96%87%E4%BB%B6%E7%9A%84%E5%A4%A7%E5%B0%8F%E5%86%99%E5%BC%BA%E7%83%88%E6%8E%A8%E8%8D%90)
`views`下的`.vue`文件代表的是一个路由,所以它需要和`component`进行区分(component 都是大写开头)
页面的`url` 也都是横线连接的,比如`https://www.xxx.admin/export-excel`,所以路由对应的`view`应该要保持统一
没有大小写敏感问题。

16
docs/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "vue-standard",
"version": "1.0.0",
"description": "'vue规范文档'",
"main": "index.js",
"scripts": {
"dev": "vuepress dev docs",
"build": "vuepress build docs"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"vuepress": "^1.8.2"
}
}

8043
docs/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,12 @@
{
"name": "vue-element-plus-admin",
"version": "1.0.0",
"description": "一套基于vue3、element-plus、typesScript、vite2的后台集成方案",
"version": "2.0.0",
"description": "一套基于vue3、element-plus、typesScript4、vite2的后台集成方案",
"author": "Archer <502431556@qq.com>",
"private": false,
"scripts": {
"i:all": "npm run i && npm run docs:i",
"i": "yarn install",
"dev": "vite --mode base",
"build:pro": "vue-tsc --noEmit && vite build --mode pro",
"build:dev": "vue-tsc --noEmit && vite build --mode dev",
@ -83,7 +85,6 @@
"vite": "^2.6.10",
"vite-plugin-commonjs-externals": "^0.1.1",
"vite-plugin-eslint": "^1.3.0",
"vite-plugin-style-import": "^1.2.1",
"vite-plugin-svg-icons": "^1.0.5",
"vite-plugin-vue-setup-extend": "^0.1.0",
"vue-eslint-parser": "^7.11.0",

View File

@ -7,7 +7,6 @@
import { reactive, computed, watch, onMounted, unref, toRef } from 'vue'
import { countToProps } from './props'
import { isNumber } from '@/utils/validate'
import { requestAnimationFrame, cancelAnimationFrame } from '@/utils/animation'
const props = defineProps(countToProps)

View File

@ -13,7 +13,6 @@ import { useAppStoreWithOut } from '@/store/modules/app'
const appStore = useAppStoreWithOut()
const modules = import.meta.glob('../../views/**/*.vue')
console.log(modules)
/* Layout */
const Layout = () => import('@/layout/index.vue')

View File

@ -1,51 +0,0 @@
import { isServer } from './validate'
let lastTime = 0
const prefixes = 'webkit moz ms o'.split(' ')
let requestAnimationFrame: any
let cancelAnimationFrame: any
;(() => {
const NO_LOOP = () => {}
const getWindowFrame = (name: any) => {
return name
}
if (isServer) {
requestAnimationFrame = cancelAnimationFrame = NO_LOOP
} else {
requestAnimationFrame = window.requestAnimationFrame
cancelAnimationFrame = window.cancelAnimationFrame
let prefix
for (let i = 0; i < prefixes.length; i++) {
if (requestAnimationFrame && cancelAnimationFrame) {
break
}
prefix = prefixes[i]
requestAnimationFrame =
requestAnimationFrame || window[getWindowFrame(prefix + 'RequestAnimationFrame')]
cancelAnimationFrame =
cancelAnimationFrame ||
window[getWindowFrame(prefix + 'CancelAnimationFrame')] ||
window[getWindowFrame(prefix + 'CancelRequestAnimationFrame')]
}
// If the current browser does not support requestAnimationFrame and cancelAnimationFrame, it will fall back to setTimeout
if (!requestAnimationFrame || !cancelAnimationFrame) {
requestAnimationFrame = function (callback: Fn) {
const currTime = new Date().getTime()
const timeToCall = Math.max(0, 16 - (currTime - lastTime))
const id = window.setTimeout(() => {
/* eslint-disable-next-line */
callback(currTime + timeToCall)
}, timeToCall)
lastTime = currTime + timeToCall
return id
}
cancelAnimationFrame = function (id: any) {
window.clearTimeout(id)
}
}
}
})()
export { requestAnimationFrame, cancelAnimationFrame }

View File

@ -1,110 +0,0 @@
/**
* JS实现
*/
let enquire: any
if (typeof window !== 'undefined') {
const matchMediaPolyfill = (mediaQuery: string) => {
return {
media: mediaQuery,
matches: false,
addListener() {},
removeListener() {}
}
}
window.matchMedia = window.matchMedia || matchMediaPolyfill
// eslint-disable-next-line global-require
enquire = require('enquire.js')
}
/**
* 'xxl', 'xl', 'lg', 'md', 'sm', 'xs'
* @type {string[]}
*/
export const responsiveArray = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs']
export const responsiveMap = {
xs: '(max-width: 575px)',
sm: '(min-width: 576px)',
md: '(min-width: 768px)',
lg: '(min-width: 992px)',
xl: '(min-width: 1200px)',
xxl: '(min-width: 1600px)'
}
let subscribers: any[] = [] // [{token, func}]
let subUid = -1
let screens = {}
const responsiveObserve = {
/**
*
* @param {Object} pointMap {xxl:false, xl:true}
* @return {boolean}
*/
fire(pointMap: any) {
screens = pointMap
if (subscribers.length < 1) {
return false
}
subscribers.forEach((item) => {
item.func(screens)
})
return true
},
/**
*
* @param {Function} func
* @return {string}
*/
on(func: Fn) {
if (subscribers.length === 0) {
this.register()
}
const token = (++subUid).toString()
subscribers.push({
token,
func
})
func(screens)
return token
},
/**
*
* @param {string} token
*/
off(token: string) {
subscribers = subscribers.filter((item) => item.token !== token)
if (subscribers.length === 0) {
this.unregister()
}
},
register() {
Object.keys(responsiveMap).forEach((screen) => {
enquire.register(responsiveMap[screen], {
match: () => {
const ponitMap = {
...screens,
[screen]: true
}
this.fire(ponitMap)
},
unmatch: () => {
const ponitMap = {
...screens,
[screen]: false
}
this.fire(ponitMap)
},
// Keep a empty destroy to avoid triggering unmatch when unregister
destroy() {}
})
})
},
unregister() {
Object.keys(responsiveMap).map((screen) => enquire.unregister(responsiveMap[screen]))
}
}
export default responsiveObserve

View File

@ -23,7 +23,7 @@ module.exports = {
'font-family-no-missing-generic-family-keyword': null,
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
'declaration-block-trailing-semicolon': 'always',
'declaration-block-trailing-semicolon': null,
'rule-empty-line-before': [
'always',
{

View File

@ -6,7 +6,6 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
import eslintPlugin from 'vite-plugin-eslint'
// import styleImport from 'vite-plugin-style-import'
import ElementPlus from 'unplugin-element-plus/vite'
import viteSvgIcons from 'vite-plugin-svg-icons'
import commonjsExternals from 'vite-plugin-commonjs-externals'
@ -17,18 +16,11 @@ function pathResolve(dir: string) {
// https://vitejs.dev/config/
export default defineConfig({
// base: '/dist-pro/',
plugins: [
vue(),
vueJsx(),
vueSetupExtend(),
// styleImport({
// libs: [{
// libraryName: 'element-plus',
// resolveStyle: (name) => {
// return `element-plus/es/components/${name.split('el-')[1]}/style/css`
// }
// }]
// }),
ElementPlus({
useSource: false
}),