Skip to content

介绍

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>

Released under the Apache License 2.0