# 前端手册 🎬

# 项目结构

以下目录均在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'
1
2

这样在每次请求时都会在控制台打印请求参数和响应结果明文。你也可以用其来控制更多的内容,在继承了BasePage的组件中,你可以像下面这样来判断是否为debug模式。

import BasePage from '@/components/base/BasePage'

export default {
  extends: BasePage,
  created () {
    // 获取是否为debug模式
    console.log(this.isDebug())
  }
}
1
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)
  })
1
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('错误')
1
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(() => {})
1
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'
1
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'
}
1
2
3

读取常量

console.log(this.$consts.TEST)
1

# 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>
1
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'
    })
  }
}
1
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方法,丢弃页码直接查询所有即可。

约定

  1. 在接口文件中应该按以下命名和参数进行接口定义
  • 分页接口:export function fetchList (data)
  • 导出Excel接口:export function fetchExcel (data)
  • 根据ID删除:export function deleteById (id)
  • 批量删除:export function deleteByIdInBatch (ids)
  1. 查询条件对象应该在data中定义为searchForm。且用于做搜索的表单组件应按下列属性进行定义(ref固定为searchForm,slot固定为search-form)
<el-form ref="searchForm" slot="search-form" ...></el-form>
1

# 新增 & 编辑的实现

跟分页 & 删除 & 批量删除一样,虽然新增 & 编辑的代码页可以自动生成,但我们仍有必要为您讲解其实现步骤。

HTML

<GlobalWindow
  :title="title"
  :visible.sync="visible"
  :confirm-working="isWorking"
  @confirm="confirm"
>
  <el-form :model="form" ref="form" :rules="rules"></el-form>
</GlobalWindow>
1
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'
    })
  }
}
1
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 确认编辑方法

约定

  1. Eva约定在接口文件中应该按以下命名和参数进行接口定义
  • 新建:export function create (data)
  • 修改:export function updateById (data)
  1. 新建/编辑表单的数据应该在data中定义为form,并且表单组件应按下列属性进行定义
<el-form ref="form"></el-form>
1

# 接口的定义

虽然我们可以为您生成很多功能,但我们并不能揣测您的页面中存在的业务功能。如果需要为这些业务功能添加接口,那么你需要在src/api目录下找到对应接口定义文件(js文件),然后添加对应的接口定义。如下

export function myInterface (data) {
  return request.post('/xxx/myInterface', data, {
    // config
  })
}
1
2
3
4
5

# 接口参数的自动去空

有时候我们希望接口的参数自动去掉两侧的空格,那么可以添加trim参数,如下



 



export function myInterface (data) {
  return request.post('/xxx/myInterface', data, {
    trim: true
  })
}
1
2
3
4
5

# 标记为导出/下载接口

当我们的接口为一个导出/下载接口时,意味着接口返回的是一个流,此时我们需要调整接口的responseType,为了使用方便和增强统一维护的能力。Eva提供了download标识。如下



 



export function exportExcel (data) {
  return request.post('/xxx/myInterface', data, {
    download: true
  })
}
1
2
3
4
5

标记为download后将自动为接口设置responseType为blob。下载接口在调用完成后需要对流进行下载(详见download方法),如下:

exportExcel({...})
  .then(response => {
    this.download(response)
  })
  .catch(e => {
    this.$tip.apiFailed(e)
  })
1
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')
}
1
2
3

# 调用接口

fetchList()
  .cache()
  .then(data => {
    // data为优先从缓存中获取的数据
  })
1
2
3
4
5

保留原始调用

如果在某些情况下我们希望不从缓存中获取,那么我们在调用时去掉cache即可。如下

fetchList()
  .then(data => {
    // data始终为从接口中获取的数据
  })
1
2
3
4

# 安全通讯(参数加解密)🎬

👉 视频教程 (opens new window)

# 单个接口的加密

如果只是部分接口需要加密请求参数,则可以在定义接口时标记一下。如下

// 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
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这样在调用接口时会自动为参数加密,并且也会自动为响应解密。

注意

  1. 如果是上传接口,即contentType以'multipart/form-data'开头的,都是不会加密的。
  2. 如果是下载接口,即响应头包含eva-opera-type为download的接口,都是不会解密的。

# 全局接口加密

如果希望所有接口都进行加密,那么可以在配置文件中开启全局加密。如下

.env

# 全局请求加密,on开启,off关闭
VUE_APP_ENCRYPT_REQUEST = 'on'
1
2

注意

  1. 如果是上传接口,即contentType以'multipart/form-data'开头的,都是不会加密的。
  2. 如果是下载接口,即响应头包含eva-opera-type为download的接口,都是不会解密的。

# 修改密钥

密钥应当与后端保持一致,修改密钥如下

.env

# 加密请求密钥
VUE_APP_ENCRYPT_REQUEST_KEY = '0000111100001111' # 16位
VUE_APP_ENCRYPT_REQUEST_IV = '1111222211112222' # 16位
1
2
3

前端密钥应当与后端密钥保持一致,否则无法正确的加解密。

# 开启2FA认证 🎬

👉 视频教程 (opens new window)

跟处理加密一样,也只是在定义接口时处理下即可,如下

// 任意请求方式
export function create (data) {
  return request.twoFA().post('/xxx/xxxx', data)
}
1
2
3
4

这样在调用接口时将自动弹出2FA认证窗口,并在发送请求时自动添加认证参数。也可以为2FA认证窗口设置参数,如下

// 任意请求方式
export function create (data) {
  return request.twoFA({
    title: '确认删除',
    message: '输入密码以继续删除',
    confirmText: '确认删除'
  }).post('/xxx/xxxx', data)
}
1
2
3
4
5
6
7
8

参数说明详见2FA认证窗口

# 权限控制

Eva提供了v-roles, v-permissions指令和全局方法containRolescontainPermissions

  • v-roles: 用于验证是否包含某角色,当值存在多个角色时为"或者"关系。
<button v-roles="['admin']"></button>
1
  • containRoles: 用于验证是否包含某角色,当值存在多个角色时为"或者"关系。
this.containRoles(['admin', 'test'])
1
  • v-permissions: 用于验证是否包含某权限,当值存在多个角色时为"或者"关系。
<button v-permissions="['system:user:create']"></button>
1
  • containPermissions: 用于验证是否包含某权限,当值存在多个权限时为"或者"关系。
this.containPermissions(['system:user:create'])
1

注意

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>
1
2
3
4

其中handleSortChange来自BaseTable组件。

在列上标记排序字段



 
 


<el-table-column
  ...
  sortable="custom"
  sort-by="EMP_NO"
></el-table-column>
1
2
3
4
5

其中sortable属性用于标记列可排序,sort-by属性用于设置排序的字段名称。

默认排序

如果需要给列表添加默认排序,则需要调整两处地方。如下

No 1: 列表上添加默认排序字段,使排序箭头默认按照设置高亮。



 



<el-table
  ...
  :default-sort="{prop: 'createTime', order: 'descending'}"
>
</el-table>
1
2
3
4
5

No 2: 配置默认排序字段,使列表初始化时就按照指定字段进行排序。





 
 
 
 




export default {
  created () {
    this.config({
      ...
      sorts: [{
        property: 'CREATE_TIME',
        direction: 'DESC'
      }]
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 代码格式化

在项目根目录下执行以下命令即可。

npm run fix
1

# 关闭EsLint

删除package.json文件中eslintConfig属性即可。

# 项目上下文路径的配置

如果发布项目时需要携带项目路径,如/myproject,则可以通过.env文件进行配置,如下

# 环境通用配置

# 项目上下文路径
VUE_APP_CONTEXT_PATH = '/'

# 接口前缀
VUE_APP_API_PREFIX = '/api'
1
2
3
4
5
6
7

注意,前端框架在v2.3.0之前的版本需要执行coderd eva fix-client命令,问题编号选择OPT-context-path以优化项目上下文处理后才可进行此配置。

# 接口代理配置

接口前缀配置

接口前缀指的是代理接口的路径,如/api,前缀配置在环境配置文件的VUE_APP_API_PREFIX变量中。调整后需重启服务。

接口地址配置

接口地址配置在文件vue.config.jsdevServer.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");
}
1
2
3
4

使用eva-icon-开头

自定义的图标请使用eva-icon-开头,因为以eva-icon-开头的class我们已经为您编写好了默认样式。

# 自定义菜单图标

菜单图标定义在src/utils/icons.js文件中,添加对应的图标class即可。

# 输入框自动去除两侧空格

添加v-trim指令即可,如下

<el-input v-trim/>
1

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>
1
2
3
4
5
参数 说明
v-clipboard:copy 需要复制的内容
v-clipboard:success 复制成功处理函数
clipboard:error 复制失败处理函数