前言
和后端一样,前端也有对应的分层和CURD,本文主要是定制前端CURD模板样例,规范化代码,也为后续的代码生成器做铺垫。
功能分析
接口分析
我们先拿一个简单的CURD接口来做一下简单的分析,下面以用户管理接口为例:
功能 | 接口地址 |
---|---|
添加用户 | /sys/user/save |
修改用户 | /sys/user/update |
删除用户 | /sys/user/delete |
通过id获取用户 | /sys/user/get |
分页查询用户列表 | /sys/user/list |
页面分析
功能 | 页面类型 | 操作 |
---|---|---|
用户列表 | 页面 | 添加、修改、删除、查询(刷新)、重置 |
添加用户 | 弹窗 | 提交、取消 |
修改用户 | 弹窗 | 提交、取消 |
操作分析
这里的方法即js的方法。
方法名 | 方法说明 |
---|---|
requestData | 请求获取用户列表 |
getDetails | 通过id获取用户信息 |
handleSearch | 处理查询操作 |
handleReset | 重置搜索表单 |
handleOpenAddDialog | 打开添加用户弹出框 |
handleOpenEditDialog | 打开修改用户弹出框 |
handleOpenDetailsDialog | 打开用户详情弹出框 |
handleCancel | 取消提交 |
handleSubmit | 提交用户信息-添加/修改 |
handleRemove | 删除操作 |
框架分层
上一篇对路由进行了模块化,也简单地对页面进行了模块化,本文会进一步对单个模块的页面进行说明。
目录结构
├── src/api
├── sys
├── sys.user.service.js 用户接口服务,对应sys-用户管理接口
└── ...
└── ...
├── src/views/modules
├── sys 系统管理模块
├── user 用户管理
├── componets
├── form.vue 添加/修改表单组件
└── search.vue 搜索表单组件
├── add.vue 添加-引入form.vue
└── details.vue 详情-仅显示
├── edit.vue 修改-引入form.vue
└── index.vue 用户管理页-引入add/details/edit/search
└── ...
└── ...
上面就是对一个单表操作要创建的全部文件,其实就简单性而已,只放在一个index.vue上,CV操作会更方便点,不过因为后面会配套有代码生成器,所以这样分层也并不是很麻烦。
文件详解
src/views/modules/sys/user/index.vue
用户管理首页,这里可以重点看一下弹框的处理方式,使用到了vue的动态组件。
<template>
<div class="app-container">
<!--start========头部折叠面板===========start-->
<el-collapse accordion value="1" style="padding:0px;">
<el-collapse-item name="1">
<template slot="title">
<el-page-header title="返回" content="搜索条件" @back="goBack"></el-page-header>
</template>
<!--搜索模块-->
<m-search ref="searchForm" @on-search="handleSearch" />
</el-collapse-item>
</el-collapse>
<!--start========头部折叠面板===========start-->
<!--start========顶部工具栏===========start-->
<el-row :gutter="10" class="mb8 mt10">
<el-col :span="1.5">
<el-button type="primary" icon="el-icon-plus" size="small" @click="handleOpenAddDialog">
添加
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" icon="el-icon-edit" size="small" :disabled="single" @click="handleOpenEditDialog">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" icon="el-icon-delete" size="small" :disabled="multiple" @click="handleRemove">
删除
</el-button>
</el-col>
</el-row>
<!--end========顶部工具栏===========end-->
<!--start========表格列表===========start-->
<el-table stripe :header-cell-style="{background:'#eef1f6',color:'#606266'}" v-loading="loading" :data="tableData" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="userName" label="用户名">
<template slot-scope="scope">
{{ scope.row.userName }}
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名">
<template slot-scope="scope">
{{ scope.row.realName }}
</template>
</el-table-column>
<el-table-column prop="mobilePhone" label="手机号">
<template slot-scope="scope">
{{ scope.row.mobilePhone }}
</template>
</el-table-column>
<el-table-column prop="sex" label="性别">
<template slot-scope="scope">
<span v-if="scope.row.sex === 1">男</span>
<span v-else-if="scope.row.sex === 2">女</span>
<span v-else>未知</span>
</template>
</el-table-column>
<el-table-column prop="isLocked" label="是否锁定">
<template slot-scope="scope">
<span v-if="scope.row.isLocked === 1">是</span>
<span v-else-if="scope.row.isLocked === 2">否</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间">
<template slot-scope="scope">
{{ scope.row.createTime }}
</template>
</el-table-column>
<el-table-column
label="操作"
align="center">
<template slot-scope="scope">
<!-- click.native.stop==>阻止单击事件冒泡 -->
<el-button type="text" size="small" icon="el-icon-view" @click.native.stop="handleOpenDetailsDialog(scope.row)">查看</el-button>
<el-button type="text" size="small" icon="el-icon-edit" @click.native.stop="handleOpenEditDialog(scope.row)">修改</el-button>
<el-button type="text" size="small" icon="el-icon-delete" @click.native.stop="handleRemove(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--end========表格列表===========end-->
<!--start========分页===========start-->
<pagination
v-show="recordCount>0"
:total="recordCount"
:page.sync="pageNum"
:limit.sync="pageSize"
@pagination="requestData"
/>
<!--end========分页===========end-->
<!--start========弹框===========start-->
<el-dialog :title="title" :visible.sync="isOpenDialog" width="500px" append-to-body @close="handleCancel">
<!--动态组件-->
<component :ref="currentView" :is="currentView" :id="id"></component>
<div slot="footer" class="dialog-footer">
<el-button type="primary" v-if="showOk" :loading="submitLoading" @click="handleSubmit">确 定</el-button>
<el-button @click="handleCancel">取 消</el-button>
</div>
</el-dialog>
<!--end========弹框===========end-->
</div>
</template>
<script>
// 搜索组件
import MSearch from './components/search'
// 添加
import Add from './add'
// 修改
import Edit from './edit'
// 详情
import Details from './details'
// 接口服务
import { list as listUser, remove as removeUser } from '@/api/sys/sys.user.service.js'
export default {
components: {
MSearch,
Add,
Edit,
Details
},
data() {
return {
// 当前id
id: undefined,
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 加载中
loading: false,
// 总记录数
recordCount: 0,
// 当前页
pageNum: 1,
// 每页大小
pageSize: 10,
// 列表数据
tableData: [],
// 当前勾选行id
ids: [],
// 当前勾选行集合
selection: [],
// 当前弹出框页面
currentView: 'add',
// 弹框标题
title: '我是标题',
// 是否打开弹出框
isOpenDialog: false,
// 是否显示弹出框确认按钮
showOk: true,
// 提交加载中
submitLoading: false
}
},
created() {
this.requestData()
},
methods: {
// 查询
handleSearch() {
this.requestData()
},
// 打开添加弹出框
handleOpenAddDialog() {
this.title = '添加用户'
this.isOpenDialog = true
this.showOk = true
this.currentView = 'Add'
},
// 打开修改弹出框
handleOpenEditDialog(row) {
if (row.id) {
this.id = row.id
} else {
this.id = this.ids.length ? this.ids[0] : row.id
}
this.title = '修改用户'
this.isOpenDialog = true
this.showOk = true
this.currentView = 'Edit'
},
// 打开详情
handleOpenDetailsDialog(row) {
this.id = row.id
this.title = '用户详情'
this.isOpenDialog = true
this.showOk = false
this.currentView = 'Details'
},
// 删除
handleRemove(row) {
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
var ids = []
if (row.id) {
ids.push(row.id)
} else {
ids = this.ids
}
removeUser({
ids: ids
}).then(res => {
if (res.code === 0) {
this.$message({ message: '删除成功', type: 'success' })
this.requestData()
} else {
this.$message({ message: res.msg || '删除失败', type: 'error' })
}
})
}).catch(() => {
this.$message({ type: 'info', message: '已取消删除' })
})
},
// 表格选中事件
handleSelectionChange(selection) {
this.selection = selection
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
},
// 请求数据
requestData(page) {
if (!page) {
page = {
page: this.pageNum,
limit: this.pageSize
}
}
this.loading = true
listUser({
pageNum: page.page,
pageSize: page.limit,
...this.$refs['searchForm'] ? this.$refs['searchForm'].getData() : {}
}).then(res => {
this.loading = false
if (res.code === 0) {
this.tableData = res.data.rows
this.recordCount = res.data.recordCount
}
}).catch(() => {
this.loading = false
})
},
// 表单提交
handleSubmit() {
this.submitLoading = true
this.$refs[this.currentView].submit().then(() => {
this.submitLoading = false
this.handleCancel()
this.requestData()
}).catch(() => {
this.submitLoading = false
})
},
// 取消提交
handleCancel() {
this.id = undefined
this.isOpenDialog = false
}
}
}
</script>
src/api/sys/sys.user.service.js
用户接口服务,对应sys-用户管理接口
import request from '@/utils/request'
/**
* 添加用户
* @param {*} data
*/
export function save(data) {
return request({
url: '/sys/user/save',
method: 'post',
data
})
}
/**
* 修改用户
* @param {*} data
*/
export function update(data) {
return request({
url: '/sys/user/update',
method: 'post',
data
})
}
/**
* 删除用户
* @param {*} data
*/
export function remove(data) {
return request({
url: '/sys/user/remove',
method: 'post',
data
})
}
/**
* 通过id获取用户
* @param {*} data
*/
export function get(data) {
return request({
url: '/sys/user/get',
method: 'post',
data
})
}
/**
* 分页查询用户列表
* @param {*} data
*/
export function list(data) {
return request({
url: '/sys/user/list',
method: 'post',
data
})
}
src/views/modules/sys/user/componets/form.vue
<template>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="用户名" prop="userName">
<el-input v-model="form.userName" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="mobilePhone">
<el-input v-model="form.mobilePhone" placeholder="请输入手机号"></el-input>
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="form.sex">
<el-option label="男" :value="1"></el-option>
<el-option label="女" :value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="是否锁定" prop="isLocked">
<el-select v-model="form.isLocked">
<el-option label="是" :value="2"></el-option>
<el-option label="否" :value="1"></el-option>
</el-select>
</el-form-item>
</el-form>
</template>
<script>
import { save as saveUser, update as updateUser, get as getUser } from '@/api/sys/sys.user.service.js'
export default {
props: {
isEdit: {
type: Boolean,
default: false
},
id: {
type: [String, Number],
default: undefined
}
},
data() {
return {
form: {
id: undefined,
userName: undefined,
realName: undefined,
email: undefined,
mobilePhone: undefined,
sex: 1,
isLocked: 2
},
rules: {
userName: [
{ required: true, message: '用户名不能为空', trigger: 'blur' }
],
realName: [
{ required: true, message: '姓名不能为空', trigger: 'blur' }
],
mobilePhone: [
{ required: true, message: '手机号不能为空', trigger: 'blur' }
],
sex: [
{ required: true, message: '性别不能为空', trigger: 'blur' }
],
isLocked: [
{ required: true, message: '是否锁定不能为空', trigger: 'blur' }
]
}
}
},
watch: {
id(n, o) {
// 因为共用一个dialog,所以需要通过监听id变化来调用接口
this.getDetails()
}
},
mounted() {
this.getDetails()
},
methods: {
submit(isShowMessage = 1) {
// isShowMessage=>是否显示消息提示,默认1
return new Promise((resolve, reject) => {
this.$refs['form'].validate((valid) => {
if (valid) {
if (this.isEdit) {
// 调用修改用户接口服务
updateUser(this.form).then(res => {
if (isShowMessage) {
if (res.code === 0) {
this.$message({
message: res.msg || '操作成功',
type: 'success'
})
}
}
resolve(res)
}).catch(e => {
reject(e)
})
} else {
delete this.form.id
// 调用添加用户接口服务
saveUser(this.form).then(res => {
if (isShowMessage) {
if (res.code === 0) {
this.$message({
message: res.msg || '操作成功',
type: 'success'
})
}
}
resolve(res)
}).catch(e => {
reject(e)
})
}
} else {
reject(new Error('error'))
}
})
})
},
resetFields() {
// 重置一下表单
this.$refs['form'].resetFields()
// 重置一下校验
this.$refs['form'].clearValidate()
},
getDetails() {
if (this.isEdit && this.id) {
// 当然,肯定是修改模式下且id存在的情况下才会调用接口
getUser({
id: this.id
}).then(res => {
this.form = this.$util.copy(res.data, this.form)
})
}
}
}
}
</script>
src/views/modules/sys/user/componets/search.vue
搜索组件这里使用特殊的带m_前辍的参数,目的是给全局的src/utils/request.js处理成后端的数据,如:
m_EQ_userName====>
{
operateType: "EQ",
propertyName: "userName",
propertyValue: value
}
<template>
<el-form ref="form" :model="form" :inline="true">
<el-form-item label="用户名" prop="m_EQ_userName">
<el-input v-model="form.m_EQ_userName" placeholder="请输入用户名" size="small" style="width: 240px"></el-input>
</el-form-item>
<el-form-item label="姓名" prop="m_EQ_realName">
<el-input v-model="form.m_EQ_realName" placeholder="请输入姓名" size="small" style="width: 240px"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="m_EQ_mobilePhone">
<el-input v-model="form.m_EQ_mobilePhone" placeholder="请输入手机号" size="small" style="width: 240px"></el-input>
</el-form-item>
<el-form-item label="是否锁定" prop="m_EQ_isLocked">
<el-select v-model="form.m_EQ_isLocked" size="small">
<el-option label="所有" :value="undefined">所有</el-option>
<el-option label="是" :value="1"></el-option>
<el-option label="否" :value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="m_BT_createTime">
<el-date-picker
v-model="form.m_BT_createTime"
size="small"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="small" @click="handleSearch">查询</el-button>
<el-button icon="el-icon-refresh" size="small" @click="resetform">重置</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
props: {
},
data() {
return {
form: {
m_EQ_userName: undefined,
m_EQ_realName: undefined,
m_EQ_mobilePhone: undefined,
m_EQ_isLocked: undefined,
m_BT_createTime: undefined
}
}
},
methods: {
// 搜索
handleSearch(e) {
this.$emit('on-search', this.form, e)
},
// 重置
resetform(e) {
this.$refs['form'].resetFields()
},
// 获取表单数据
getData() {
var data = Object.assign({}, this.form)
return data
}
}
}
</script>
src/views/modules/sys/user/add.vue
添加页,这里主要引入./componts/form.vue组件
<template>
<m-form ref="form" :is-edit="false" :id="id"></m-form>
</template>
<script>
import MForm from './components/form'
export default {
components: { MForm },
props: {
id: {
type: [String, Number],
default: undefined
}
},
methods: {
submit() {
return this.$refs.form.submit()
},
resetFields() {
this.$refs.form.resetFields()
}
}
}
</script>
src/views/modules/sys/user/details.vue
详情组件,暂时还是使用./componts/form.vue组件
<template>
<m-form ref="form" :is-edit="true" :id="id"></m-form>
</template>
<script>
import MForm from './components/form'
export default {
components: { MForm },
props: {
id: {
type: [String, Number],
default: undefined
}
},
methods: {
}
}
</script>
src/views/modules/sys/user/edit.vue
修改页,同添加页一样引入./componts/form.vue组件
<template>
<m-form ref="form" :is-edit="true" :id="id"></m-form>
</template>
<script>
import MForm from './components/form'
export default {
components: { MForm },
props: {
id: {
type: [String, Number],
default: undefined
}
},
methods: {
submit() {
return this.$refs.form.submit()
},
resetFields() {
this.$refs.form.resetFields()
}
}
}
</script>
- src/utils/request.js
这里新增了特殊的请求参数处理,代码片段
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// 存在token,就放到请求头中
// 这里修改一下请求头与后端一致,X-Token->Auth-Token
config.headers['Auth-Token'] = getToken()
}
if (config.data) {
// 这里对全局的请求参数做处理,主要是拼接查询条件
var whereParams = []
Object.keys(config.data).forEach(item => {
if (item.startsWith('m_')) {
// 处理以m_开头的参数
var value = config.data[item]
if (value) {
var arr = item.split('_')
if (arr.length === 3) {
whereParams.push({
operateType: arr[1],
propertyName: arr[2],
propertyValue: value
})
}
}
// 处理完后就删掉
delete config.data[item]
}
})
if (whereParams.length) {
config.data.whereParams = whereParams
}
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
效果图
- 列表页
- 添加弹框
-
修改弹框
-
查看详情(还未细做)
小结
本文只是先简单地过一下前端的CURD模板样例,现在这个版本的页面还不是最终版本,还不能作为代码生成器的基础模板,后面的文章会更细化来讲解。这里先简单做个预告:
-
如何优雅的处理表单的一行一列、一行多列;
-
如何制作一些常用的业务组件,如上传、字典、下拉关联表(单选、多选)、富文本等
-
按钮级权限的设计
-
当然,最最核心的还是如何做前端的代码生成器
-
……
项目源码地址
- 后端
- 前端