vue3【组件封装】信息管理 S-comMangeInfo (含多条件搜索、分页表格、自带增删改查、重置密码等)
最终效果
安装依赖
npm i dayjs
格式化时间
代码实现
components/SUI/S-comMangeInfo.vue
<script lang="ts" setup>
import dayjs from "dayjs";
import type { DrawerProps } from "element-plus";// 类型声明
type GenericObject = {[key: string]: any;
};
type RecordObject = Record<string, any>;const emit = defineEmits<{handle_add: [];handle_edit: [row: GenericObject];getList_done: [data: RecordObject[], total: number];
}>();// 父组件传参
const { Model, PageConfig, tableDataFormate } = defineProps<{Model: {[key: string]: GenericObject;};PageConfig: GenericObject;tableDataFormate?: (row: RecordObject) => RecordObject;
}>();// 父组件传参--二次加工
const propData = computed(() => {let searchForm_formItemConfigList: GenericObject[] = [];let form_customFormItemConfigList: GenericObject[] = [];let defaultFormData: RecordObject = {};let table_columnConfigList: GenericObject[] = [];let top4_searchFieldList: string[] = [];for (const [key, value] of Object.entries(Model)) {if ("search" in value && value.search) {searchForm_formItemConfigList.push({prop: key,...(value as object),searchOrder: value.searchOrder || 99,});}if (value.type === "custom") {form_customFormItemConfigList.push({prop: key,...(value as object),});}if (value.defaultValue) {defaultFormData[key] = value.defaultValue;}if (!value.tableHide) {table_columnConfigList.push({prop: key,...(value as object),tableOrder: value.tableOrder || 999,});}}searchForm_formItemConfigList = searchForm_formItemConfigList.sort((a, b) => a.searchOrder - b.searchOrder);searchForm_formItemConfigList.forEach((item, index) => {if (index < 4) {top4_searchFieldList.push(item.prop);if (item.type === "dateRange") {top4_searchFieldList.push(item.prop + "_start");top4_searchFieldList.push(item.prop + "_end");}}});table_columnConfigList = table_columnConfigList.sort((a, b) => a.tableOrder - b.tableOrder);return {searchForm_formItemConfigList,form_customFormItemConfigList,defaultFormData,table_columnConfigList,top4_searchFieldList,};
});const direction = ref<DrawerProps["direction"]>("rtl");const show_allSearch = ref(false);const callbackMessage = ref({show: false,valid: true,content: "",
});// 当前操作状态
const action = ref("search");// 操作状态字典
const actionDic: GenericObject = {add: "新增",edit: "修改",detail: "详情",
};const pageData = reactive<{currentPage: number;pageSize: number;total: number;searchformData: RecordObject;tableData: RecordObject[];loadData: boolean;
}>({currentPage: 1,pageSize: 10,total: 0,searchformData: {},tableData: [],loadData: false,
});const { currentPage, pageSize, total, searchformData, tableData, loadData } =toRefs(pageData);// 排序配置
const sort = ref({column: "createdAt",direction: "desc",
});// 日期选择组件配置
const shortcuts = [{text: "上周",value: () => {const end = new Date();const start = new Date();start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);return [start, end];},},{text: "上个月",value: () => {const end = new Date();const start = new Date();start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);return [start, end];},},{text: "三个月前",value: () => {const end = new Date();const start = new Date();start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);return [start, end];},},
];// 搜索表单宽度样式
const formItemWidthClass = "w-260px!";// 编辑表单数据
let formData: GenericObject = {};// 是否添加了更多搜索条件
const hasMoreSearchCondition = computed(() => {let result = false;for (const key in pageData.searchformData) {if (!propData.value.top4_searchFieldList.includes(key) &&(pageData.searchformData[key] || pageData.searchformData[key] === 0)) {result = true;break;}}return result;
});// 自适配保存的API
const saveAPI = computed(() => {if (action.value === "add") {if (PageConfig.api && PageConfig.api.add) {return PageConfig.api.add;} else {return `/${PageConfig.entity}/add`;}} else {if (PageConfig.api && PageConfig.api.edit) {return PageConfig.api.edit;} else {return `/${PageConfig.entity}/edit`;}}
});// 获取分页表格数据
const getList = async () => {loadData.value = true;let searchformData_temp: RecordObject = JSON.parse(JSON.stringify(pageData.searchformData));for (const key in searchformData_temp) {if (typeof searchformData_temp[key] === "object") {delete searchformData_temp[key];}}const res: GenericObject = await $fetch(`/api/${PageConfig.entity}/list`, {query: {orderBy: sort.value.column,order: sort.value.direction,currentPage: pageData.currentPage,pageSize: pageData.pageSize,...searchformData_temp,},});if (res) {tableData.value = res.data.map((row: RecordObject) => {row.createdAt = dayjs(row!.createdAt).format("YYYY-MM-DD HH:mm");row.updatedAt = dayjs(row!.updatedAt).format("YYYY-MM-DD HH:mm");return tableDataFormate ? tableDataFormate(row) : row;});total.value = res.total;}loadData.value = false;emit("getList_done", tableData.value, total.value);
};// 按钮--取消(多条件搜索抽屉中)
function cancelMoreSearch() {show_allSearch.value = false;
}// 按钮--查询(多条件搜索抽屉中)
function confirmMoreSearch() {getList();show_allSearch.value = false;
}// 切换分页-每页条数
const handleSizeChange = (val: number) => {pageSize.value = val;getList();
};// 点击分页-页码
const handleCurrentChange = (val: number) => {currentPage.value = val;getList();
};// 点击按钮-重置
const reset = () => {pageData.currentPage = 1;pageData.pageSize = 10;pageData.searchformData = {};getList();
};// 点击按钮-新增
const handle_add = () => {formData = JSON.parse(JSON.stringify(propData.value.defaultFormData));action.value = "add";emit("handle_add");
};// 点击按钮-编辑
const handle_edit = (row: GenericObject) => {formData = row;action.value = "edit";emit("handle_edit", row);
};// 点击按钮-删除
const handle_del = async (id: string) => {try {await useFetch(`/api/${PageConfig.entity}/${id}`, {method: "DELETE",});callbackMessage.value = {show: true,valid: true,content: "操作成功",};getList();} catch (e: any) {callbackMessage.value = {show: true,valid: false,content: e.data.message,};}
};// 点击按钮-重置密码
const handle_resetPassword = async (id: string) => {try {await useFetch(`/api/user/${id}`, {method: "PATCH",query: {handleType: "resetPassword",},});callbackMessage.value = {show: true,valid: true,content: "操作成功",};} catch (e: any) {callbackMessage.value = {show: true,valid: false,content: e.data.message,};}
};// 点击按钮-详情
const handle_detail = (row: GenericObject) => {formData = row;action.value = "detail";
};// 回调函数--保存成功
const saveOK = async () => {setTimeout(() => {action.value = "search";getList();}, 500);
};const dateRangeChange = (newValue: string[] | null, prop: string) => {if (newValue) {pageData.searchformData[prop + "_start"] = newValue[0];pageData.searchformData[prop + "_end"] = newValue[1];} else {delete pageData.searchformData[prop + "_start"];delete pageData.searchformData[prop + "_end"];}
};// 处理 before-change 事件的方法
const beforeSwitchChange = async (oldValue: boolean | number | string,prop: string
) => {if (PageConfig.entity === "user" && prop === "disabled") {try {// 弹出确认框await ElMessageBox.confirm(`确定${oldValue ? "启用" : "禁用"}吗?`,"提示",{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",});// 用户点击了确定,允许状态改变return true;} catch (error) {// 用户点击了取消,阻止状态改变return false;}} else {return true;}
};const switchChange = async (newValue: boolean | number | string,id: string
) => {try {await useFetch(`/api/user/${id}`, {method: "PATCH",query: {handleType: newValue ? "disable" : "enable",},});callbackMessage.value = {show: true,valid: true,content: "操作成功",};} catch (e: any) {callbackMessage.value = {show: true,valid: false,content: e.data.message,};}
};onMounted(() => {getList();
});// 对外暴露的变量和方法
defineExpose({saveOK,getList,
});
</script>
<template><div class="relative"><!-- 过渡动画--从右向左滑入 --><transition name="slide-in"><div class="bg-white" v-if="['add', 'edit', 'detail'].includes(action)"><el-page-header @back="action = 'search'"><template #content><span class="text-large font-600 mr-3"><span v-if="actionDic[action] !== `详情`">{{actionDic[action]}}</span><span>{{ PageConfig.entityName }}</span><span v-if="actionDic[action] === `详情`">{{actionDic[action]}}</span></span></template></el-page-header><S-form:Model="Model"class="m-4":PageConfig="PageConfig":action="action":disabled="action === 'detail'":cancel="() => {action = 'search';}":local_save="PageConfig.local_save":saveOK="saveOK":saveAPI="saveAPI"v-model="formData"><templatev-for="formItemConfig in propData.form_customFormItemConfigList":key="formItemConfig.prop"#[formItemConfig.prop]><slot :name="formItemConfig.prop" /></template></S-form></div></transition><!-- 过渡动画--向中心缩小消失 --><transition name="shrink-to-center"><div v-show="action === 'search'"><!-- 搜索表单 --><el-form class="mt-2" :inline="true" :model="searchformData"><el-row :span="24"><templatev-for="(formItemConfig, index) in propData.searchForm_formItemConfigList"><el-colv-if="!formItemConfig.tableHide && index < 4":span="12":key="formItemConfig.prop"class="text-center even:ml-[-60px]!"><el-form-item :label="formItemConfig.label" :label-width="160"><el-date-pickerv-if="formItemConfig.type === 'date'"v-model="searchformData![formItemConfig.prop as string]"type="date"placeholder="选择日期"v-bind="formItemConfig":class="formItemWidthClass"/><el-date-pickerv-else-if="formItemConfig.type === 'dateRange'"v-model="searchformData![formItemConfig.prop as string]"type="daterange"unlink-panelsrange-separator="至"start-placeholder="开始日期"end-placeholder="结束日期":shortcuts="shortcuts"value-format="YYYY-MM-DD"@change="dateRangeChange($event, formItemConfig.prop)":class="formItemWidthClass"/><el-date-pickerv-else-if="formItemConfig.type === 'dateTimeRange'"v-model="searchformData![formItemConfig.prop as string]"type="datetimerange"unlink-panelsrange-separator="至"start-placeholder="开始时间"end-placeholder="结束时间":shortcuts="shortcuts"value-format="YYYY-MM-DD"@change="dateRangeChange($event, formItemConfig.prop)":class="formItemWidthClass"/><el-tree-selectv-else-if="formItemConfig.type === 'treeSelect'"v-model="searchformData![formItemConfig.prop as string]":data="formItemConfig.treeData":render-after-expand="false"placeholder="请选择":class="formItemWidthClass"filterableclearable:node-key="formItemConfig.key"default-expand-all/><el-inputv-elsev-model="searchformData![formItemConfig.prop as string]"v-bind="formItemConfig":class="formItemWidthClass"clearable/></el-form-item></el-col></template></el-row></el-form><!-- 查询按钮组 --><div class="flex justify-center relative"><el-button type="primary" @click="getList">查询</el-button><el-button type="primary" @click="reset">重置</el-button><el-buttonv-if="propData.searchForm_formItemConfigList.length > 4"class="absolute right-4"type="primary"text@click="show_allSearch = true"><span class="text-red" v-if="hasMoreSearchCondition">(已添加)</span>更多搜索条件 >></el-button></div><!-- 全部搜索条件抽屉 --><el-drawer v-model="show_allSearch" :direction="direction"><template #header><h4 class="font-bold">全部搜索条件</h4></template><template #default><!-- 搜索表单 --><el-form :inline="true" :model="searchformData"><el-row :span="24"><templatev-for="formItemConfig in propData.searchForm_formItemConfigList"><el-colv-if="!formItemConfig.tableHide":span="24":key="formItemConfig.prop"><el-form-item:label="formItemConfig.label":label-width="100"><el-date-pickerv-if="formItemConfig.type === 'date'"v-model="searchformData![formItemConfig.prop as string]"type="date"placeholder="选择日期"v-bind="formItemConfig":class="formItemWidthClass"/><el-date-pickerv-else-if="formItemConfig.type === 'dateRange'"v-model="searchformData![formItemConfig.prop as string]"type="daterange"unlink-panelsrange-separator="至"start-placeholder="开始日期"end-placeholder="结束日期":shortcuts="shortcuts"value-format="YYYY-MM-DD"@change="dateRangeChange($event, formItemConfig.prop)":class="formItemWidthClass"/><el-tree-selectv-else-if="formItemConfig.type === 'treeSelect'"v-model="searchformData![formItemConfig.prop as string]":data="formItemConfig.treeData":render-after-expand="false"placeholder="请选择":class="formItemWidthClass"filterableclearable:node-key="formItemConfig.key"default-expand-all/><el-inputv-elsev-model="searchformData![formItemConfig.prop as string]"v-bind="formItemConfig":class="formItemWidthClass"clearable/></el-form-item></el-col></template></el-row></el-form></template><template #footer><div style="flex: auto"><el-button @click="cancelMoreSearch">取消</el-button><el-button type="primary" @click="reset">重置</el-button><el-button type="primary" @click="confirmMoreSearch">查询</el-button></div></template></el-drawer><!-- 新增按钮 --><div class="m-2 mb-4 flex"><el-buttonv-if="!PageConfig.disAdd"v-permission="`${PageConfig.entity}:add`"type="primary"@click="handle_add">新增</el-button></div><!-- 表格 --><el-tableheight="360":data="tableData"style="width: 96%"empty-text="暂无数据"v-loading="loadData"><el-table-columnv-for="tableColumnConfig in propData.table_columnConfigList":key="tableColumnConfig.prop"v-bind="tableColumnConfig":align="tableColumnConfig.align || 'center'"show-overflow-tooltip><template #default="scope"><span v-if="tableColumnConfig.tableTransProp">{{ scope.row[tableColumnConfig.tableTransProp] }}</span><el-switchv-if="tableColumnConfig.type === 'switch'"v-model="scope.row[tableColumnConfig.prop]":before-change="() =>beforeSwitchChange(scope.row[tableColumnConfig.prop],tableColumnConfig.prop)"@change="switchChange($event, scope.row._id)"/><slotv-if="tableColumnConfig.type === 'custom'":name="`table_${tableColumnConfig.prop}`":row="scope.row"></slot></template></el-table-column><el-table-columnv-if="!PageConfig.hideHandle"fixed="right"label="操作"min-width="120"align="center"><template #default="scope"><el-buttonv-if="!PageConfig.hideDetailBtn &&!(PageConfig.entity === 'role' &&scope.row.name === '超级管理员')"linktype="primary"size="small"@click="handle_detail(scope.row)"v-permission="`${PageConfig.entity}:detail`">详情</el-button><el-buttonv-if="!PageConfig.hideEditBtn &&!(PageConfig.entity === 'role' &&scope.row.name === '超级管理员')"linktype="primary"size="small"@click="handle_edit(scope.row)"v-permission="`${PageConfig.entity}:edit`">编辑</el-button><el-popconfirmtitle="确定删除吗?"@confirm="handle_del(scope.row._id)"><template #reference><el-buttonv-if="!PageConfig.hideDelBtn &&!(PageConfig.entity === 'role' &&scope.row.name === '超级管理员')"linktype="danger"size="small"v-permission="`${PageConfig.entity}:del`">删除</el-button></template></el-popconfirm><el-popconfirmv-if="PageConfig.showResetPasswordBtn"title="确定重置吗?"@confirm="handle_resetPassword(scope.row._id)"><template #reference><el-buttonlinktype="danger"size="small"v-permission="`${PageConfig.entity}:resetPassword`">重置密码</el-button></template></el-popconfirm><slot name="myHandle" :row="scope.row" /></template></el-table-column></el-table><!-- 分页 --><div class="mt-4 flex justify-end"><el-paginationv-model:current-page="currentPage"v-model:page-size="pageSize":page-sizes="[5, 10, 20, 30]"layout="total, sizes, prev, pager, next, jumper":total="total"size="small"@size-change="handleSizeChange"@current-change="handleCurrentChange"/></div></div></transition><S-msgWin :msg="callbackMessage" /></div>
</template>
<style scoped>
/* 过渡动画 -- 从右向左滑入 */
.slide-in-enter-active {animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {0% {transform: translateX(100%);opacity: 0;}100% {transform: translateX(0);opacity: 1;}
}
/* 过渡动画 -- 向中心缩小消失 */
.shrink-to-center-enter-active {animation: shrinkToCenter 0.5s reverse;
}
.shrink-to-center-leave-active {animation: shrinkToCenter 0.5s;
}
@keyframes shrinkToCenter {0% {transform: scale(1);opacity: 1;}100% {transform: scale(0);opacity: 0;}
}
</style>
依赖组件
S-form
https://blog.csdn.net/weixin_41192489/article/details/149717692
S-msgWin
https://blog.csdn.net/weixin_41192489/article/details/149717948
页面使用
<S-comMangeInfo :Model="Model" :PageConfig="PageConfig" />
核心传参 Model
配置语法(仅列出了部分核心字段)
// header-align 表头对齐方式 left center right
// search 是否显示搜索框
// tableHide 是否在表格中隐藏
// formHide 是否在表单中隐藏, "all" 时,表单中隐藏,数组时,如["edit", "detail"],则修改和详情不显示
// formDisable 是否在表单中禁用, 数组时,如["edit"],则修改时禁用
// formRules 表单验证规则
// searchFromRules 搜索表单验证规则
// unit 单位
// width 列表的列宽,数字
// defaultValue 表单的默认值
// searchOrder 搜索框的排序
// tableOrder 表格的排序
范例一 岗位管理
const Model: {[key: string]: any;
} = {name: {label: "岗位名称",search: true,require: true,},EnglishName: {label: "岗位英文",search: true,},order: {label: "岗位顺序",type: "number",},desc: {label: "岗位描述",tableHide: true,type: "textarea",},createdAt: {label: "创建时间",formHide: "all",},updatedAt: {label: "修改时间",formHide: "all",},
};
范例二 用户管理
const Model: {[key: string]: any;
} = {createdAt: {width: 150,formHide: "all",label: "注册日期",type: "dateRange",search: true,searchOrder: 1,tableOrder: 2,},avatar: {group: "基本信息",label: "头像",type: "custom",span: 24,tableHide: true,},account: {group: "账号信息",label: "账号",require: true,formDisable: ["edit"],search: true,searchOrder: 3,tableOrder: 1,},password: {group: "账号信息",label: "密码",type: "password",require: true,tableHide: true,formHide: ["edit", "detail"],},name: {group: "基本信息",label: "姓名",search: true,searchOrder: 4,formRules: [{ min: 2, message: "姓名不能少于 2 个字符", trigger: "blur" }],},nickName: {group: "基本信息",label: "昵称",search: true,},gender: {group: "基本信息",label: "性别",type: "select",options: [],tableHide: true,},age: {group: "基本信息",label: "年龄",type: "number",min: 0,max: 160,// 单位unit: "岁",tableHide: true,},address: {group: "基本信息",label: "地址",tableHide: true,},disabled: {group: "账号信息",label: "禁用",type: "switch",},roles: {group: "权限信息",label: "角色",type: "select",options: [],multSelect: true,tableHide: true,},department: {group: "权限信息",label: "部门",type: "treeSelect",treeData: [],// 树的键key: "code",search: true,searchOrder: 2,// 表格中显示的字段tableTransProp: "department_desc",},positions: {group: "权限信息",label: "岗位",type: "select",options: [],multSelect: true,tableHide: true,},
};
核心传参 PageConfig
范例一 岗位管理
const PageConfig = {//被操作实体的名称entity: "position",entityName: "岗位",
};
范例二 用户管理
const PageConfig = {//被操作实体的名称entity: "user",entityName: "用户",// 表单是否分组formGrouped: true,// 未命名分组的默认名称groupName_default: "其他信息",api: {//获取详情detail: "/user/detail",//新增add: "/user/add",//修改edit: "/user/edit",},// 展示重置密码按钮showResetPasswordBtn: true,
};