本项目使用 Vue + element-ui + axios + testing library + JSON Server 开发的简单 CRUD Web 应用。麻雀虽小,五脏俱全。感兴趣的话,可以访问该项目的 GitHub 查看。‘
这是最终的效果图:
应用特性
本开源项目虽小但也有不少特性。
- 完整的 CRUD
没有像 Shop 系列项目 使用后台 API,取而代之的是 JSON Server。JSON Server 是以一个在本地运行,可以存储 json 数据的 server,最关键的是,它能支持 CRUD 操作,打破了 Node.js + Express + MongoDB 这些后台技术壁垒,与后台协商好数据结构后,前端就可以随性所欲的模拟服务端接口数据,放飞自己了。
CRUD 代码集中在 App.vue
中。比如:
fetchData(params) {
this.loading = true
getReceiptList(params).then((data) => {
this.loading = false
this.tableData = data.list
this.total = data.total
})
.catch(() => {
this.loading = false
})
},
toAdd() {
this.adding = true
this.dialogVisible = true
this.form = {}
},
toEdit(row) {
this.adding = false
this.dialogVisible = true
this.form = deepClone(row)
},
toDelete(id) {
MessageBox.confirm('确定删除 ?', {
title: '提示',
type: 'warning',
beforeClose: (action, instance, done) => {
...
})
},
doAdd(data) {
this.saveLoading = true
addReceipt(data).then(() => {
this.saveLoading = false
this.refresh()
})
.catch(() => {
this.saveLoading = false
})
},
doEdit(data) {
this.saveLoading = true
editReceipt(data).then(() => {
...
}).catch(() => {
this.saveLoading = false
})
}
对于中台系统来说,很多表单模块都有这种类似的 CRUD,收货表单和发货表单完全可以复用这部分逻辑。对于 Vue 应用,可以使用 mixins
来复用。
- 支持基本的搜索、分页、排序
可以按搜索姓名、手机号、收货日期、区域来搜索发货。分页、排序这些基本上都是 Element 自带组件的功能,开箱即用。
- 中国市区级联、自定义组件/数据访问/表单验证
考虑到代码复用,我们对很多 Element 组件进行了二次封装。BaseInput.vue
、BaseSelect.vue
、BaseDatePicker.vue
、BasePagination
、DateColumn.vue
这些自定义组件大都通过自定义v-module
实现了组件双向绑定。感兴趣的同学可以访问 Vue 双向数据绑定 了解更多。
AreaCascader 级联选择组件时,我们绑定了 area
区号,省市区名称可以通过 getCheckedNodes
方法获取选中的节点,然后通过节点的 athLabels
数组来拼接省市区名称,最终用 areaName
来保存。所以我们不仅需要为级联选择组件自定义 v-module
来绑定区号,还要为它自定义 select
事件将 areaName
值传递到父组件中。
- 脚本片段
handleChange(value) {
this.$emit('input', value)
const node = this.$refs.area.getCheckedNodes()?.[0]
if (node) {
this.$emit('select', [...new Set(node.pathLabels)])
}
}
- 调用示例
<area-cascader
v-model="form.area"
@select="form.areaName=$event.join('')"
/>
省市区数据保存在area.js
中,其实时一个数组结构的数据模块,查询时的选择组件和表单操作时的级联选择组件都会用到它。根据区号读取相应的名称,只要遍历数组就行,不用来回查询数据库。如下代码所示:
export function getAreaNameByCode(list, value) {
if(Array.isArray(list)) {
for (const item of list) {
if (item.value === value) {
return item.label
} else if (item.children?.length > 0) {
return getAreaNameByCode(item.children, value)
}
}
}
return ''
}
- 响应式布局
适当使用 CSS3 Media Query,响应式布局能适配桌面和多数移动设备。
@media screen and (max-width: 767px) {
.el-dialog {
width: 92% !important;
}
}
- 单元测试
出于学习 testing library 初衷,本项目的单元测试写的比较系统。网上的 Vue 教程多数时使用 Vue Test Utils,库依赖性太强,没法在 React 项目复用。感兴趣的同学可以访问 Testing Library 学习指南 了解更多。
运行项目
- 安装依赖
npm install
- 编译前运行 JSON Server
npm run mock
- 编译和热重载用于开发
npm run serve
- 单元测试
npm run test:unit
评论 (0)