介绍
dvlyadmin-mini可以基于配置和深度自定义快速的实现页面的crud
前端VUE页面CRUD
具体可参考【部门管理】或 【用户管理】等,查看具体实现的方式,这里做下实现【部门管理】CRUD介绍
CRUD目录文件介绍
dept/ # 外部目录
├── components/ # crud组件存放目录(后续该页面自定义的组件建议放在此目录下)
│ ├── moduleSave.vue # 弹窗form组件(新增、编辑)
├── api.js # 本页面需要的api接口文件
├── crud.js # 本页面的CRUD配置文件
├── deptManage.vue # 部门管理主体文件(文件名与路由名称保持一致,因此需确保全局唯一)
效果预览
可实现基本的crud功能如:增、删、改、查、导出、导入等

api.js
import {ajaxGet,ajaxPost,ajaxDelete,ajaxPut,ajaxPatch,uploadImg,ajaxGetDetailByID,ajaxDownloadExcel,uploadFileParams,getDownloadFile,downloadFile} from '@/api/request.js';
const api = {}
// 部门管理
api.list = params => ajaxGet({url: `/api/system/dept/`,params})
// 部门管理 -- 新增
api.add = params => ajaxPost({url: `/api/system/dept/`,params})
// 部门管理 -- 编辑
api.edit = params => ajaxPut({url: `/api/system/dept/`,params})
// 部门管理 -- 删除
api.del = params => ajaxDelete({url: `/api/system/dept/`,params})
// 部门管理 -- 禁用启用
api.setStatus = params => ajaxPost({url: `/api/system/dept/set_status/`,params})
// 部门管理 -- 导出
api.export = (queryParams,params) => ajaxDownloadExcel({url: `/api/system/dept/export_data/`,queryParams:queryParams,params})
// 部门管理 -- 导入
api.import = params => uploadFileParams({url: `/api/system/dept/import_data/`,params})
// 部门管理 -- 下载导入模板
api.downloadTemplate = params => downloadFile({url: `/api/system/dept/download_template/`,params})
export default api
crud.js
import api from "./api.js"
/**
* 创建CRUD配置,支持覆盖配置
* @param {Object} options - 自定义配置选项
* @returns {Object} CRUD配置对象
*/
export function createCrudConfig(options = {}){
//需修改:命名规则(文件名+Table)
let tableName = "deptManageTable"
//crud请求方法配置
const request = {
add:api.add,
del:api.del,
edit:api.edit,
list:api.list,
export:api.export,
detail:null,
import:api.import,
dltemplate:api.downloadTemplate,
setStatus:api.setStatus,
...(options.request || {}) // 允许覆盖请求方法
}
//table列配置
const defaultColumns = [
{
label: "部门名称",
prop: "name",
minWidth: "130"
},
{
label: "标识字符",
prop: "key",
minWidth: "100",
},
{
label: "负责人",
prop: "owner",
minWidth: "100"
},
{
label: "状态",
prop: "status",
width: "100"
},
{
label: "排序",
prop: "sort",
width: "100"
},
{
label: "创建时间",
prop: "create_datetime",
minWidth: "180"
}
]
return {
crudOptions:{
//接口配置
request: request,
//搜索栏目配置
searchBar:{
showSearchBar:true,//显示搜索栏目
...(options.searchBar || {})
},
//分页
pagination:{
hidePagination:true,
...(options.pagination || {})
},
//crud按钮配置
rowHandle:{
width: 150,//操作列宽度,0表示不显示表格操作列
fixed:"right",//固定操作列在右侧
...(options.rowHandle || {})
},
//table属性
table: {
tableName:tableName,
pageSize:999,
rowKey: 'id',
lazy: false,
isTree:true,
treeProps: { children: 'children', hasChildren: 'hasChildren' },
border:true,
defaultExpandAll:true,
showSelectable:true,//表格显示复选项框
hideImport:true,
hideExport:false,
...(options.table || {})
},
//table列字段
columns:options.columns || defaultColumns
}
}
}
deptManage.vue
<template>
<div :class="{'ly-is-full':isFull}" class="lycontainer">
<el-card class="tableSelect" ref="tableSelect" shadow="hover" v-if="crudOptions.searchBar.showSearchBar">
<lySearchBar :model="formInline" @search="search" @reset="handleEdit('','reset')">
<!-- 自定义默认搜索项 -->
<template #default>
<el-form-item label="部门名称">
<el-input v-model.trim="formInline.name" maxlength="60" clearable style="width:160px" placeholder="部门名称"></el-input>
</el-form-item>
</template>
</lySearchBar>
</el-card>
<el-card class="lytable" shadow="hover">
<ly-table v-bind="tableBindProps" ref="tableref" @selection-change="selectionChange">
<template v-slot:topbar>
<el-button type="primary" icon="plus" @click="handleAddClick" v-auth="'Create'">新增</el-button>
<el-button type="danger" plain icon="delete" :disabled="selection.length==0" title="批量删除" @click="batch_del" v-auth="'Delete'"></el-button>
</template>
<template #status="scope">
<el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949" @change="changeStatus(scope.row)" :disabled="!hasBtnPermission('SetStatus')"></el-switch>
</template>
<el-table-column label="操作" :fixed="crudOptions.rowHandle.fixed" :width="crudOptions.rowHandle.width">
<template #header>
<div style="display: flex;justify-content: space-between;align-items: center;">
<div>操作</div>
<div @click="setFull">
<el-tooltip content="全屏" placement="bottom">
<el-icon style="cursor: pointer;"><full-screen /></el-icon>
</el-tooltip>
</div>
</div>
</template>
<template #default="scope">
<span class="table-operate-btn" @click="handleEdit(scope.row,'edit')" v-auth="'Update'">编辑</span>
<span class="table-operate-btn delete" @click="handleEdit(scope.row,'delete')" v-auth="'Delete'">删除</span>
</template>
</el-table-column>
</ly-table>
</el-card>
<saveDialog ref="saveDialogRef" @refreshData="getData" v-if="isDialogVisible" @closed="isDialogVisible = false"></saveDialog>
</div>
</template>
<script setup name="deptManage">
import { ref, onMounted, onUnmounted, nextTick,computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import saveDialog from "./components/moduleSave.vue"
import lySearchBar from '@/components/lySearchBar.vue'
import { createCrudConfig } from './crud.js'
import { useUserState } from '@/store/userState'
import { useRoute } from 'vue-router'
const route = useRoute()
const userState = useUserState()
let crudOptions = ref(createCrudConfig().crudOptions)
const hasBtnPermission = (btnCode) => {
return userState.hasButtonPermission(route.name, btnCode)
}
// 状态管理
const isFull = ref(false)
const isDialogVisible = ref(false)
const formInline = ref({})
const tableSelect = ref(null)
const tableref = ref(null)
const saveDialogRef = ref(null)
let selection = ref([])
function selectionChange(selections){
selection.value = selections;
}
let tableBindProps= computed(() => (
{
...crudOptions.value.table,
...crudOptions.value.pagination,
apiObj:crudOptions.value.request.list,
apiImportObj:crudOptions.value.request.import,
apiExportObj:crudOptions.value.request.export,
apiTemplateObj:crudOptions.value.request.dltemplate,
params:formInline.value,
column:crudOptions.value.columns,
hideExport:!hasBtnPermission('Export'),
hideImport:!hasBtnPermission('Import'),
}
))
// 方法
const setFull = () => {
isFull.value = !isFull.value
window.dispatchEvent(new Event('resize'))
}
const handleAddClick = () => {
isDialogVisible.value = true
nextTick(() => {
saveDialogRef.value.handleOpen(null, "add")
})
}
function batch_del(){
ElMessageBox.confirm(`确定删除选中的 ${selection.value.length} 项吗?`, '提示', {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: 'warning'
}).then(() => {
let ids = selection.value.map(item => item.id);
crudOptions.value.request.del({id:ids}).then(res=>{
if(res.code == 2000) {
search()
ElMessage.success("操作成功")
} else {
ElMessage.warning(res.msg)
}
})
}).catch(() => {
})
}
const handleEdit = (row, flag) => {
switch (flag) {
case 'edit':
isDialogVisible.value = true
nextTick(() => {
saveDialogRef.value.handleOpen(row, "edit")
})
break
case 'delete':
ElMessageBox.confirm('您确定要删除选中的数据吗?', "警告", {
closeOnClickModal: false,
type: "warning",
}).then(() => {
crudOptions.value.request.del({ id: row.id }).then(res => {
if (res.code == 2000) {
ElMessage.success(res.msg)
search()
} else {
ElMessage.warning(res.msg)
}
})
}).catch(() => {})
break
case 'reset':
formInline.value = {}
search()
break
}
}
const changeStatus = (row) => {
let originalStatus = row.status
row.status = !row.status
ElMessageBox.confirm('确定修改状态吗?', '提醒', {
closeOnClickModal: false,
type: "warning"
}).then(() => {
crudOptions.value.request.setStatus({ id: row.id }).then(res => {
if (res.code == 2000) {
originalStatus ? row.status = true : row.status = false
ElMessage.success(res.msg)
getData()
} else {
ElMessage.warning(res.msg)
}
})
})
}
const search = () => {
tableref.value.reload(formInline.value)
}
const getData = () => {
tableref.value.getData()
}
onMounted(() => {
})
onUnmounted(() => {
})
</script>
components/moduleSave.vue
<template>
<ly-dialog :title="titleMap[mode]" v-model="visible" width="500px" destroy-on-close @closed="emits('closed')">
<el-form :model="formData" :rules="rules" :disabled="mode=='detail'" ref="dialogForm" label-width="auto">
<el-form-item label="上级部门" prop="parent">
<el-tree-select v-model="formData.parent" node-key="id" :data="groups" default-expand-all
check-strictly filterable clearable :render-after-expand="false"
:props="{label:'name',value: 'id'}"
style="width: 100%" placeholder="请选择/为空则为顶级" >
<template #default="{ data: { name,sort } }">
{{ name }}
</template>
</el-tree-select>
</el-form-item>
<el-form-item label="部门名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入部门名称" clearable></el-input>
</el-form-item>
<el-form-item label="标识字符" prop="key">
<el-input v-model="formData.key" placeholder="请输入标识字符" clearable></el-input>
</el-form-item>
<el-form-item label="负责人" prop="owner">
<el-input v-model="formData.owner" placeholder="请输入负责人" clearable></el-input>
</el-form-item>
<!-- <el-form-item label="联系电话" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入联系电话" clearable></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" clearable></el-input>
</el-form-item> -->
<el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="1" style="width: 100%;"></el-input-number>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="formData.status" inline-prompt active-text="启用" inactive-text="禁用"></el-switch>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible=false">取 消</el-button>
<el-button v-if="mode!='detail'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
</template>
</ly-dialog>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import lyDialog from "@/components/dialog/dialog.vue"
import {deepClone} from "@/utils/util.js"
import Api from "@/api/api.js"
import { ElMessage, ElMessageBox } from 'element-plus'
import XEUtils from "xe-utils";
import { createCrudConfig } from '../crud.js'
const emits = defineEmits(['refreshData', 'closed'])
let crudOptions = ref(createCrudConfig().crudOptions)
let mode = ref("add")
const titleMap = {
add: '新增',
edit: '编辑',
detail: '查看'
}
let visible = ref(false)
let isSaveing = ref(false)
let dialogForm = ref(null)
// 表单数据
let formData = ref({
parent: "",
name: "",
sort: 1,
email:"",
owner:"",
phone:"",
status: true,
})
// 验证规则
let rules = {
sort: [{required: true, message: '请输入排序', trigger: 'change'}],
name: [{required: true, message: '请输入部门名称'}],
key: [{ required: true, message: '请输入标识字符', trigger: 'blur' }],
}
// 所需数据选项
const groups = ref([])
// 方法
const handleOpen = (item = null,modeType = 'add') => {
mode.value = modeType
visible.value = true
if(item){
formData.value = deepClone(item)
}
}
const getGroup = async () => {
crudOptions.value.request.list({page:1,limit:999,status:1}).then(res=>{
if(res.code === 2000){
groups.value = XEUtils.toArrayTree(res.data.data, { parentKey: 'parent', strict: false })
}
})
}
const submit = () => {
dialogForm.value.validate(async (valid) => {
if (valid) {
isSaveing.value = true
let apiObj = crudOptions.value.request.add
if(mode.value == "edit"){
apiObj = crudOptions.value.request.edit
}
const res = await apiObj(formData.value)
isSaveing.value = false
if(res.code == 2000) {
emits('refreshData')
visible.value = false
ElMessage.success("操作成功")
} else {
ElMessageBox.alert(res.msg, "提示", {type: 'error'})
}
}
})
}
onMounted(() => {
getGroup()
})
defineExpose({
handleOpen
})
</script>
<style>
</style>