# 前端手册
# 项目结构
以下目录均在src目录下
目录 | 说明 |
---|---|
api | 存放接口定义文件 |
assets | 存放静态资源 |
components | 存放组件 |
directives | 存放自定义指令 |
filters | 存放自定义过滤器 |
layouts | 存放布局组件 |
plugins | 存放插件 |
router | 存放路由文件 |
store | 存放vuex store文件 |
utils | 存放工具脚本 |
views | 存放页面 |
# DEBUG模式
如果接口进行了加密,那么查看请求参数和响应结果是非常不方便的。Eva为诸如此类的情况提供了DEBUG模式,开启DEBUG模式只需要调整一下配置即可。如下
# DEBUG模式,on开启,off关闭
VUE_APP_DEBUG = 'on'
2
这样在每次请求时都会在控制台打印请求参数和响应结果明文。你也可以用其来控制更多的内容,在继承了BasePage
的组件中,你可以像下面这样来判断是否为debug模式。
import BasePage from '@/components/base/BasePage'
export default {
extends: BasePage,
created () {
// 获取是否为debug模式
console.log(this.isDebug())
}
}
2
3
4
5
6
7
8
9
# 父组件
# BasePage/页面父组件
BasePage为页面组件的父组件。为了使得权限验证更灵活,在该组件中提供了containRoles(判断是否包含角色)
和containPermissions(判断是否包含权限)
方法。所以,当你的页面需要使用权限验证时,你可以通过Vue的extends
属性来继承该组件。
# BaseTable/列表页父组件
BaseTable为列表页面组件的父组件,她继承了BasePage
组件。为了使得开发列表页面更简单,在BaseTable中封装了分页、删除、批量删除等功能,列表页面组件只需继承她后进行简单的配置即可完成这些功能。
# BaseOpera/新建&编辑页父组件
BaseOpera为新建&编辑页的父组件,在Eva中,新建和编辑被单独抽离成组件,以将展示和数据操作功能分离,这样可以更好的进行代码复用和维护。而BaseOpera则默认实现了新建/编辑功能,操作页面组件只需继承她后进行简单的配置即可完成这些功能。
# 全局对象/方法
全局通用的方法都在plugins目录下以插件的方式进行定义,Eva将通用的方法归类成对象。如下
# $tip/提示
$tip对象用于提示信息,它定义在plugins/message.js
中,它有如下方法
方法 | 说明 | 参数 |
---|---|---|
apiSuccess | 接口调用成功提示 | 提示消息 |
apiFailed | 接口调用失败提示 | 错误对象 |
info | 信息提示 | 详见Message组件 (opens new window) |
success | 成功提示 | 详见Message组件 (opens new window) |
warning | 警告提示 | 详见Message组件 (opens new window) |
error | 错误提示 | 详见Message组件 (opens new window) |
示例1: 以下代码为接口调用的信息提示
// 调用创建接口
create()
.then(() => {
// 接口调用成功
this.$tip.apiSuccess('创建成功')
})
.catch(e => {
// 接口调用失败
this.$tip.apiFailed(e)
})
2
3
4
5
6
7
8
9
10
示例2: 以下代码为各方法调用示例,与element-ui的Message用法保持一致
this.$tip.info('信息')
this.$tip.success('成功')
this.$tip.warning('警告')
this.$tip.error('错误')
2
3
4
更多用法可查阅element-ui Message组件 (opens new window)。
为什么需要apiSuccess和apiFailed
项目开发中不难遇到难搞的产品经理,TA可能会要求你修改接口调用后的提示形式(如操作失败了需要一个二次确认框,以防止用户没有留意到错误提示)。这样我们可以直接调整apiFailed方法的实现来快速完成。
# $dialog/对话框
$dialog对象用于做对话框的提醒,如二次确认等,它定义在plugins/messagebox.js
文件中,它有如下方法
方法 | 说明 | 参数 |
---|---|---|
deleteConfirm | 删除操作二次确认对话框 | message确认消息 |
disableConfirm | 禁用操作二次确认对话框 | message确认消息 |
exportConfirm | 导出操作二次确认对话框 | message确认消息 |
attentionConfirm | 重要提醒 | message确认消息;title标题,默认"重要提醒";confirmButtonText确认按钮文案,默认"知道了" |
alert | 提示对话框 | 详见MessageBox组件 (opens new window)。 |
confirm | 确认对话框 | 详见MessageBox组件 (opens new window)。 |
prompt | 携带输入框的对话框 | 详见MessageBox组件 (opens new window)。 |
close | 关闭对话框 | 详见MessageBox组件 (opens new window)。 |
示例: 删除确认和禁用确认
// 唤起删除确认对话框
this.$dialog.deleteConfirm ('确认删除该记录吗?')
.then(() => {
// 处理确认逻辑
})
// 唤起禁用确认对话框
this.$dialog.disableConfirm ('确认禁用该记录吗?')
.then(() => {
// 处理禁用逻辑
})
// 唤起禁用确认对话框
this.$dialog.exportConfirm('确认导出吗?')
.then(() => {
// 处理导出逻辑
})
// 唤起重要提醒确认对话框
this.$dialog.attentionConfirm('操作成功,用户需重新登录后生效')
.then(() => {})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
更多用法可查阅element-ui MessageBox组件 (opens new window)。
# $cache/缓存
$cache对象用于处理缓存。我们并不建议您直接使用sessionStorage或localStorage,因为项目的缓存策略可能发生变化,通过$cache对象做一层调用代理则是一个不错的选择。
方法列表
方法 | 说明 | 参数 |
---|---|---|
set | 添加/覆盖缓存 | key缓存键;value缓存值;timeout超时时间,单位毫秒,-1表示不超时 |
get | 从缓存中获取值 | key缓存键 |
remove | 删除缓存记录 | key缓存键 |
属性列表
对象名称 | 缓存类型 |
---|---|
session | 会话级缓存,通过sessionStorage实现 |
local | 本地级缓存,通过localStorage实现(默认) |
示例
// 写入字符串
this.$cache.set('key1', 'value1')
// 写入数字
this.$cache.set('key2', 1)
// 写入布尔
this.$cache.set('key3', true)
// 写入对象
this.$cache.set('key4', { value: 'value4' })
// 写入数组
this.$cache.set('key5', [{ index: 0 }])
// 写入日期
this.$cache.set('key6', new Date(1650767673917))
// 设置超时时间
this.$cache.set('key7', 'value7', 3000)
// 获取缓存值
console.log('key1', this.$cache.get('key1')) // 'value1'
console.log('key2', this.$cache.get('key2')) // 1
console.log('key3', this.$cache.get('key3')) // true
console.log('key4', this.$cache.get('key4')) // { value: 'value4' }
console.log('key5', this.$cache.get('key5')) // [{ index: 0 }]
console.log('key6', this.$cache.get('key6')) // Date:1650767673917
console.log('key7', this.$cache.get('key7')) // 'value7'
setTimeout(() => {
console.log('key7', this.$cache.get('key7')) // null
}, 3000)
// 写入sessionStorage
this.$cache.session.set('key8', 'value8')
// 从sessionStorage中获取值
console.log(this.$cache.session.get('key8')) // 'value8'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# $consts/常量
$consts对象用于存放项目中的常量。将常量定义在项目/src/plugins/consts
文件中即可。
定义常量
export default {
TEST: 'TEST VALUE'
}
2
3
读取常量
console.log(this.$consts.TEST)
# download/下载文件
download方法用于下载文件流,当接口返回流时,可通过该方法进行下载处理。该方法需要结合接口download
配置使用,详见标记为导出/下载接口。该方法参数如下
参数 | 说明 | 默认值 |
---|---|---|
response | 接口响应对象 | |
decode | 是否需通过decodeURI解码文件名称 | true |
mime | 流的类型 | application/octet-stream |
解码文件名称?
导出/下载接口返回流的同时会标记流对应的文件名称,为了保证文件名称不存在乱码的问题,文件名称在后端通过encodeURI来转码。并且将其写入响应头eva-download-filename
属性中。download方法将自动从该响应头属性中获取并对齐解码。
# 列表页的实现
列表页包含分页 & 删除 & 批量删除 & 条件搜索 & 导出 & 重置代码功能,虽然这些可以自动生成,但我们仍有必要为您讲解其实现步骤。
HTML
<TableLayout>
<!-- 搜索条件 -->
<el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="80px" inline>
<el-form-item label="条件1" prop="condition">
<el-input v-model="searchForm.condition" v-trim placeholder="请输入搜索条件" @keypress.enter.native="search"/>
</el-form-item>
<section>
<el-button type="primary" icon="el-icon-search" @click="search">搜索</el-button>
<el-button type="primary" icon="el-icon-search" @click="exportExcel">搜索</el-button>
<el-button @click="reset">重置</el-button>
</section>
</el-form>
<!-- 表格部分 -->
<template v-slot:table-wrap>
<el-table
v-loading="isWorking.search"
:data="tableData.list"
:default-sort="{prop: 'createTime', order: 'descending'}"
stripe
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
></el-table>
<!-- 表格分页 -->
<pagination
@size-change="handleSizeChange"
@current-change="handlePageChange"
:pagination="tableData.pagination"
></pagination>
</template>
</TableLayout>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
JAVASCRIPT
// 引入BaseTable组件
import BaseTable from '@/components/base/BaseTable'
export default {
...
// 继承BaseTable
extends: BaseTable,
data () {
return {
// 搜索条件
searchForm: {
condition: ''
}
}
},
created () {
// 配置页面
this.config({
module: '订单',
api: '/order', // 对应/api/order.js
sorts: [{
property: 'CREATE_TIME', // 数据库字段名称
direction: 'DESC' // 排序方式,ASC升序,DESC降序
}],
'field.id': 'orderId', // 主键,默认为'id'
'field.main': 'orderNo' // 主字段,默认为'name'
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
HTML代码中所使用到的数据和方法均来源于BaseTable
组件,如果默认的方法实现不能满足您的需求,您可以直接在methods
中重写。下面是this.config
参数列表和继承的方法列表。
# config方法参数
参数 | 说明 |
---|---|
module | 模块名称 |
api | 接口文件地址,指定api下的路径即可 |
sorts | 默认排序 |
field.id | 主键字段 |
field.main | 主字段,删除操作时用于做提示内容的字段 |
# 继承的方法
方法名称 | 说明 | 参数 |
---|---|---|
config | 配置页面 | 见config方法参数 |
search | 列表搜索 | |
exportExcel | 导出Excel | |
reset | 搜索重置 | |
handleSizeChange | 页容量变化处理函数,用于绑定pagination组件的size-change事件 | pageSize页容量(一页展示多少条记录) |
handlePageChange | 页码变化处理函数,用于绑定pagination组件的current-change事件 | pageIndex变化后的页码 |
handleSelectionChange | 行选中事件处理函数,用于绑定el-table的selection-change事件 | selectedRows已选中的行对象 |
handleSortChange | 排序事件处理函数,用于绑定el-table的sort-change事件 | sortData排序字段信息 |
deleteById | 根据ID删除,用于绑定行删除按钮click事件 | row当前删除的行,childConfirm当列表为树列表时,删除父元素时是否弹出确认删除字元素的窗口 |
deleteByIdInBatch | 根据已选中的行进行删除删除,用于绑定批量删除按钮click事件 | childConfirm当列表为树列表时,存在选中父元素进行删除时是否弹出确认删除字元素的窗口 |
__afterDelete | 内置方法,用于做删除后处理 | deleteCount删除数 |
如果某些操作跟业务不匹配,可以直接在页面文件中覆盖对应的方法。例如当前页面无需分页,则可以覆盖handlePageChange
方法,丢弃页码直接查询所有即可。
约定
- 在接口文件中应该按以下命名和参数进行接口定义
- 分页接口:
export function fetchList (data)
- 导出Excel接口:
export function fetchExcel (data)
- 根据ID删除:
export function deleteById (id)
- 批量删除:
export function deleteByIdInBatch (ids)
- 查询条件对象应该在data中定义为
searchForm
。且用于做搜索的表单组件应按下列属性进行定义(ref固定为searchForm,slot固定为search-form)
<el-form ref="searchForm" slot="search-form" ...></el-form>
# 新增 & 编辑的实现
跟分页 & 删除 & 批量删除一样,虽然新增 & 编辑的代码页可以自动生成,但我们仍有必要为您讲解其实现步骤。
HTML
<GlobalWindow
:title="title"
:visible.sync="visible"
:confirm-working="isWorking"
@confirm="confirm"
>
<el-form :model="form" ref="form" :rules="rules"></el-form>
</GlobalWindow>
2
3
4
5
6
7
8
JAVASCRIPT
// 引入BaseOpera组件
import BaseOpera from '@/components/base/BaseOpera'
export default {
...
// 继承BaseOpera
extends: BaseOpera,
data () {
return {
// 表单数据
form: {
orderId: null,
otherField: ''
},
// 验证规则
rules: {
}
}
},
created () {
// 配置组件接口文件
this.config({
api: '/order', // 对应/api/order.js
'field.id': 'orderId'
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
HTML代码中所使用到的数据和方法均来源于BaseOpera
组件,如果默认的方法实现不能满足您的需求,您可以直接在methods
中重写。下面是this.config
参数列表和继承的方法列表。
# config方法参数
参数 | 说明 |
---|---|
api | 接口文件地址,指定api下的路径即可 |
field.id | 主键字段 |
# 继承的方法
方法名称 | 说明 | 参数 |
---|---|---|
config | 配置页面 | 见config方法参数 |
open | 打开窗口方法 | title窗口标题,target编辑的对象 |
confirm | 确认操作方法,用于绑定确认按钮click事件 | |
__confirmCreate | 确认新建方法 | |
__confirmEdit | 确认编辑方法 |
约定
- Eva约定在接口文件中应该按以下命名和参数进行接口定义
- 新建:
export function create (data)
- 修改:
export function updateById (data)
- 新建/编辑表单的数据应该在data中定义为
form
,并且表单组件应按下列属性进行定义
<el-form ref="form"></el-form>
# 接口的定义
虽然我们可以为您生成很多功能,但我们并不能揣测您的页面中存在的业务功能。如果需要为这些业务功能添加接口,那么你需要在src/api
目录下找到对应接口定义文件(js文件),然后添加对应的接口定义。如下
export function myInterface (data) {
return request.post('/xxx/myInterface', data, {
// config
})
}
2
3
4
5
# 接口参数的自动去空
有时候我们希望接口的参数自动去掉两侧的空格,那么可以添加trim
参数,如下
export function myInterface (data) {
return request.post('/xxx/myInterface', data, {
trim: true
})
}
2
3
4
5
# 标记为导出/下载接口
当我们的接口为一个导出/下载接口时,意味着接口返回的是一个流,此时我们需要调整接口的responseType,为了使用方便和增强统一维护的能力。Eva提供了download标识。如下
export function exportExcel (data) {
return request.post('/xxx/myInterface', data, {
download: true
})
}
2
3
4
5
标记为download后将自动为接口设置responseType为blob
。下载接口在调用完成后需要对流进行下载(详见download方法),如下:
exportExcel({...})
.then(response => {
this.download(response)
})
.catch(e => {
this.$tip.apiFailed(e)
})
2
3
4
5
6
7
# 接口数据缓存处理
版本:v1.4.1,如您需要使用此方案且您使用的是v1.4.1之前的版本,请在gitee issue中提出。
有时候我们希望部分接口的数据可以缓存下来,在下次调用时优先从缓存中获取。Eva提供了这样的处理方案,并且十分好用。
# 定义接口
export function fetchList () {
return request.cache('MY_CACHE_KEY').get('/myinterface')
}
2
3
# 调用接口
fetchList()
.cache()
.then(data => {
// data为优先从缓存中获取的数据
})
2
3
4
5
保留原始调用
如果在某些情况下我们希望不从缓存中获取,那么我们在调用时去掉cache即可。如下
fetchList()
.then(data => {
// data始终为从接口中获取的数据
})
2
3
4
# 接口加密处理
# 单个接口的加密
如果只是部分接口需要加密请求参数,则可以在定义接口时标记一下。如下
// GET
export function getData (data) {
return request.get('/xxx/xxxx', {
params: data,
encrypt: true
})
}
// POST
export function postData (data) {
return request.post('/xxx/xxxx', data, {
encrypt: true
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这样在调用接口时会自动为参数加密,并且也会自动为响应解密。
注意
- 如果是上传接口,即contentType以'multipart/form-data'开头的,都是不会加密的。
- 如果是下载接口,即响应头包含eva-opera-type为download的接口,都是不会解密的。
# 全局接口加密
如果希望所有接口都进行加密,那么可以在配置文件中开启全局加密。如下
.env
# 全局请求加密,on开启,off关闭
VUE_APP_ENCRYPT_REQUEST = 'on'
2
注意
- 如果是上传接口,即contentType以'multipart/form-data'开头的,都是不会加密的。
- 如果是下载接口,即响应头包含eva-opera-type为download的接口,都是不会解密的。
# 修改密钥
密钥应当与后端保持一致,修改密钥如下
.env
# 加密请求密钥
VUE_APP_ENCRYPT_REQUEST_KEY = '0000111100001111' # 16位
VUE_APP_ENCRYPT_REQUEST_IV = '1111222211112222' # 16位
2
3
前端密钥应当与后端密钥保持一致,否则无法正确的加解密。
# 开启2FA认证
跟处理加密一样,也只是在定义接口时处理下即可,如下
// 任意请求方式
export function create (data) {
return request.twoFA().post('/xxx/xxxx', data)
}
2
3
4
这样在调用接口时将自动弹出2FA认证窗口,并在发送请求时自动添加认证参数。也可以为2FA认证窗口设置参数,如下
// 任意请求方式
export function create (data) {
return request.twoFA({
title: '确认删除',
message: '输入密码以继续删除',
confirmText: '确认删除'
}).post('/xxx/xxxx', data)
}
2
3
4
5
6
7
8
参数说明详见2FA认证窗口
# 权限控制
Eva提供了v-roles
, v-permissions
指令和全局方法containRoles
和containPermissions
。
- v-roles: 用于验证是否包含某角色,当值存在多个角色时为"或者"关系。
<button v-roles="['admin']"></button>
- containRoles: 用于验证是否包含某角色,当值存在多个角色时为"或者"关系。
this.containRoles(['admin', 'test'])
- v-permissions: 用于验证是否包含某权限,当值存在多个角色时为"或者"关系。
<button v-permissions="['system:user:create']"></button>
- containPermissions: 用于验证是否包含某权限,当值存在多个权限时为"或者"关系。
this.containPermissions(['system:user:create'])
注意
No 1. containRoles和containPermissions定义在BasePage.vue中,使用时请确保您的页面继承了BasePage.vue组件(继承BaseTable.vue也可,因为BaseTable继承了BasePage)。
No 2. el-table-column无法使用v-roles和v-permissions来完成列的控制,当出现这种问题时,需要结合v-if和containRoles、containPermissions来代替指令。
# 数据权限自定义数据处理
在components/system/dataPermissions
目录下存在CustomSelect
组件,该组件基于Vue动态组件实现了不同模块的自定义数据。相信你在阅读该组件的代码后自然会添加新的自定义数据控件。
# 字段排序的实现
在实现字段排序前,请确保您的列表页已继承BaseTable
组件。
表格添加监听排序变化事件
<el-table
...
@sort-change="handleSortChange"
></el-table>
2
3
4
其中handleSortChange
来自BaseTable
组件。
在列上标记排序字段
<el-table-column
...
sortable="custom"
sort-by="EMP_NO"
></el-table-column>
2
3
4
5
其中sortable
属性用于标记列可排序,sort-by
属性用于设置排序的字段名称。
默认排序
如果需要给列表添加默认排序,则需要调整两处地方。如下
No 1: 列表上添加默认排序字段,使排序箭头默认按照设置高亮。
<el-table
...
:default-sort="{prop: 'createTime', order: 'descending'}"
>
</el-table>
2
3
4
5
No 2: 配置默认排序字段,使列表初始化时就按照指定字段进行排序。
export default {
created () {
this.config({
...
sorts: [{
property: 'CREATE_TIME',
direction: 'DESC'
}]
})
}
}
2
3
4
5
6
7
8
9
10
11
# 代码格式化
在项目根目录下执行以下命令即可。
npm run fix
# 关闭EsLint
删除package.json文件中eslintConfig属性即可。
# 项目上下文路径的配置
如果发布项目时需要携带项目路径,如/myproject,则可以通过.env文件进行配置,如下
# 环境通用配置
# 项目上下文路径
VUE_APP_CONTEXT_PATH = '/'
# 接口前缀
VUE_APP_API_PREFIX = '/api'
2
3
4
5
6
7
注意,前端框架在v2.3.0之前的版本需要执行coderd eva fix-client
命令,问题编号选择OPT-context-path
以优化项目上下文处理后才可进行此配置。
# 接口代理配置
接口前缀配置
接口前缀指的是代理接口的路径,如/api
,前缀配置在环境配置文件的VUE_APP_API_PREFIX
变量中。调整后需重启服务。
接口地址配置
接口地址配置在文件vue.config.js
的devServer.proxy.target
属性中。调整后需重启服务。
# 环境配置及环境变量的命名规范
默认情况下,Eva拥有三个环境,但拥有四个环境配置文件。如下
- .env: 用于配置所有环境公用的环境变量
- .env.development:配置开发环境的环境变量
- .env.staging:配置测试环境的环境变量
- .env.production:配置生产环境的环境变量
注意
环境变量必须以VUE_APP_
开头,且除了开发环境外,其他环境的NODE_ENV都应该为production
。详细内容请参考vue-cli官方:模式和环境变量部分 (opens new window)
# 自定义图标
如果系统图标和Element-UI的图标不能满足您的需求,您可以前往iconfont (opens new window)选取您喜欢的图标。并下载png或svg文件后放入项目src/assets/icons/ext目录下。然后在index.scss文件中编写以下内容。
// 我的图标
.eva-icon-myicon {
background-image: url("assets/icons/ext/myicon.svg");
}
2
3
4
使用eva-icon-开头
自定义的图标请使用eva-icon-
开头,因为以eva-icon-开头的class我们已经为您编写好了默认样式。
# 自定义菜单图标
菜单图标定义在src/utils/icons.js
文件中,添加对应的图标class即可。
# 输入框自动去除两侧空格
添加v-trim指令即可,如下
<el-input v-trim/>
v-trim生效的组件
v-trim在原生input和textarea以及Element-UI的<el-textarea/>
和<el-input/>
组件生效。
# 内容复制
<el-button
v-clipboard:copy="formattedContent"
v-clipboard:success="copySuccess"
v-clipboard:error="copyFailed"
>复制</el-button>
2
3
4
5
参数 | 说明 |
---|---|
v-clipboard:copy | 需要复制的内容 |
v-clipboard:success | 复制成功处理函数 |
clipboard:error | 复制失败处理函数 |