feat: pageModal组件,添加二级弹窗与使用案例

This commit is contained in:
超凡
2025-04-21 22:12:58 +08:00
parent 9e47f25520
commit 4b1614952b
7 changed files with 331 additions and 229 deletions

View File

@@ -503,19 +503,24 @@ function handleDelete(id?: number | string) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(function () {
if (props.contentConfig.deleteAction) {
props.contentConfig.deleteAction(ids).then(() => {
ElMessage.success("删除成功");
removeIds.value = [];
//清空选中项
tableRef.value?.clearSelection();
handleRefresh(true);
});
} else {
ElMessage.error("未配置deleteAction");
}
});
})
.then(function () {
if (props.contentConfig.deleteAction) {
props.contentConfig
.deleteAction(ids)
.then(() => {
ElMessage.success("删除成功");
removeIds.value = [];
//清空选中项
tableRef.value?.clearSelection();
handleRefresh(true);
})
.catch(() => {});
} else {
ElMessage.error("未配置deleteAction");
}
})
.catch(() => {});
}
// 导出表单
@@ -777,7 +782,14 @@ function handleToolbar(name: string) {
// 操作列
function handleOperate(data: IOperateData) {
emit("operateClick", data);
switch (data.name) {
case "delete":
props.contentConfig?.deleteAction && handleDelete(data.row[pk]);
break;
default:
emit("operateClick", data);
break;
}
}
// 属性修改

View File

@@ -1,147 +1,151 @@
<template>
<!-- drawer -->
<template v-if="modalConfig.component === 'drawer'">
<el-drawer
v-model="modalVisible"
v-bind="{ destroyOnClose: true, ...modalConfig.drawer }"
@close="handleClose"
>
<!-- 表单 -->
<el-form ref="formRef" v-bind="modalConfig.form" :model="formData" :rules="formRules">
<el-row :gutter="20">
<template v-for="item in formItems" :key="item.prop">
<el-col v-show="!item.hidden" v-bind="item.col">
<el-form-item :label="item.label" :prop="item.prop">
<!-- Label -->
<template #label>
<span>
{{ item?.label || "" }}
<el-tooltip v-if="item?.tips" v-bind="getTooltipProps(item.tips)">
<QuestionFilled class="w-4 h-4 mx-1" />
</el-tooltip>
<span v-if="modalConfig.colon" class="ml-0.5">:</span>
</span>
</template>
<!-- components -->
<template v-if="item.type === 'custom'">
<slot
:name="item.slotName ?? item.prop"
:prop="item.prop"
:form-data="formData"
:attrs="item.attrs"
style="width: 100%"
></slot>
</template>
<component
:is="componentMap.get(item.type)"
v-else
v-model.trim="formData[item.prop]"
v-bind="{ style: { width: '100%' }, ...item.attrs }"
>
<template v-if="['select', 'radio', 'checkbox'].includes(item.type)">
<component
:is="childrenMap.get(item.type)"
v-for="opt in item.options"
:label="opt.label"
:value="opt.value"
></component>
<div>
<!-- drawer -->
<template v-if="modalConfig.component === 'drawer'">
<el-drawer
v-model="modalVisible"
v-bind="{ destroyOnClose: true, ...modalConfig.drawer }"
@close="handleClose"
>
<!-- 表单 -->
<el-form ref="formRef" v-bind="modalConfig.form" :model="formData" :rules="formRules">
<el-row :gutter="20">
<template v-for="item in formItems" :key="item.prop">
<el-col v-show="!item.hidden" v-bind="item.col">
<el-form-item :label="item.label" :prop="item.prop">
<!-- Label -->
<template #label>
<span>
{{ item?.label || "" }}
<el-tooltip v-if="item?.tips" v-bind="getTooltipProps(item.tips)">
<QuestionFilled class="w-4 h-4 mx-1" />
</el-tooltip>
<span v-if="modalConfig.colon" class="ml-0.5">:</span>
</span>
</template>
<template v-if="item?.slotName && $slots[item.slotName]" #[item.slotName]>
<slot :name="item.slotName" />
<!-- components -->
<template v-if="item.type === 'custom'">
<slot
:name="item.slotName ?? item.prop"
:prop="item.prop"
:form-data="formData"
:attrs="item.attrs"
></slot>
</template>
</component>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<el-button v-if="!formDisable" type="primary" @click="handleSubmit"> </el-button>
<el-button @click="handleClose">{{ !formDisable ? " " : "关闭" }}</el-button>
</template>
</el-drawer>
</template>
<!-- dialog -->
<template v-else>
<el-dialog
v-model="modalVisible"
v-bind="{ destroyOnClose: true, appendToBody: true, ...modalConfig.dialog }"
@close="handleClose"
>
<!-- 表单 -->
<el-form ref="formRef" v-bind="modalConfig.form" :model="formData" :rules="formRules">
<el-row :gutter="20">
<template v-for="item in formItems" :key="item.prop">
<el-col v-show="!item.hidden" v-bind="item.col">
<el-form-item :label="item.label" :prop="item.prop">
<!-- Label -->
<template #label>
<span class="flex-y-center">
{{ item?.label || "" }}
<el-tooltip v-if="item?.tips">
<QuestionFilled class="w-4 h-4 mx-1" />
</el-tooltip>
<span v-if="modalConfig.colon" class="ml-0.5">:</span>
</span>
</template>
<component
:is="componentMap.get(item.type)"
v-else
v-model.trim="formData[item.prop]"
v-bind="{ style: { width: '100%' }, ...item.attrs }"
>
<template v-if="['select', 'radio', 'checkbox'].includes(item.type)">
<component
:is="childrenMap.get(item.type)"
v-for="opt in item.options"
:label="opt.label"
:value="opt.value"
></component>
</template>
<!-- components -->
<template v-if="item.type === 'custom'">
<slot
:name="item.slotName ?? item.prop"
:prop="item.prop"
:form-data="formData"
:attrs="item.attrs"
></slot>
</template>
<component
:is="componentMap.get(item.type)"
v-else
v-model.trim="formData[item.prop]"
v-bind="{ style: { width: '100%' }, ...item.attrs }"
>
<template v-if="['select', 'radio', 'checkbox'].includes(item.type)">
<component
:is="childrenMap.get(item.type)"
v-for="opt in item.options"
:label="opt.label"
:value="opt.value"
>
<slot v-if="item.slotName" :name="item.slotName"></slot>
</component>
<template v-if="item?.slotName && $slots[item.slotName]" #[item.slotName]>
<slot :name="item.slotName" />
</template>
</component>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<el-button v-if="!formDisable" type="primary" @click="handleSubmit"> </el-button>
<el-button @click="handleClose">{{ !formDisable ? " " : "关闭" }}</el-button>
</template>
</el-drawer>
</template>
<!-- dialog -->
<template v-else>
<el-dialog
v-model="modalVisible"
v-bind="{ destroyOnClose: true, ...modalConfig.dialog }"
@close="handleClose"
>
<!-- 表单 -->
<el-form ref="formRef" v-bind="modalConfig.form" :model="formData" :rules="formRules">
<el-row :gutter="20">
<template v-for="item in formItems" :key="item.prop">
<el-col v-show="!item.hidden" v-bind="item.col">
<el-form-item :label="item.label" :prop="item.prop">
<!-- Label -->
<template #label>
<span>
{{ item?.label || "" }}
<el-tooltip v-if="item?.tips" v-bind="getTooltipProps(item.tips)">
<QuestionFilled class="w-4 h-4 mx-1" />
</el-tooltip>
<span v-if="modalConfig.colon" class="ml-0.5">:</span>
</span>
</template>
</component>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<el-button v-if="!formDisable" type="primary" @click="handleSubmit"> </el-button>
<el-button @click="handleClose">{{ !formDisable ? " " : "关闭" }}</el-button>
</template>
</el-dialog>
</template>
<!-- components -->
<template v-if="item.type === 'custom'">
<slot
:name="item.slotName ?? item.prop"
:prop="item.prop"
:form-data="formData"
:attrs="item.attrs"
></slot>
</template>
<component
:is="componentMap.get(item.type)"
v-else
v-model.trim="formData[item.prop]"
v-bind="{ style: { width: '100%' }, ...item.attrs }"
>
<template v-if="['select', 'radio', 'checkbox'].includes(item.type)">
<component
:is="childrenMap.get(item.type)"
v-for="opt in item.options"
:label="opt.label"
:value="opt.value"
></component>
</template>
<template v-if="item?.slotName && $slots[item.slotName]" #[item.slotName]>
<slot :name="item.slotName" />
</template>
</component>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<el-button v-if="!formDisable" type="primary" @click="handleSubmit"> </el-button>
<el-button @click="handleClose">{{ !formDisable ? " " : "关闭" }}</el-button>
</template>
</el-dialog>
</template>
</div>
</template>
<script setup lang="ts">
import { useThrottleFn } from "@vueuse/core";
import type { FormInstance, FormRules } from "element-plus";
import type { IModalConfig, IObject } from "./types";
import type { IComponentType, IModalConfig, IObject } from "./types";
import InputTag from "@/components/InputTag/index.vue";
import IconSelect from "@/components/IconSelect/index.vue";
defineSlots<{ [key: string]: (_args: any) => any }>();
// 定义接收的属性
const props = defineProps<{ modalConfig: IModalConfig }>();
// 自定义事件
const emit = defineEmits<{ submitClick: [] }>();
const emit = defineEmits<{ submitClick: []; customSubmit: [queryParams: IObject] }>();
// 组件映射表
/* eslint-disable */
const componentMap = new Map([
const componentMap = new Map<IComponentType, any>([
// @ts-ignore
["input", markRaw(ElInput)], // @ts-ignore
["select", markRaw(ElSelect)], // @ts-ignore
@@ -160,7 +164,7 @@ const componentMap = new Map([
["icon-select", markRaw(IconSelect)], // @ts-ignore"
["custom", ""],
]);
const childrenMap = new Map([
const childrenMap = new Map<IComponentType, any>([
// @ts-ignore
["select", markRaw(ElOption)], // @ts-ignore
["radio", markRaw(ElRadio)], // @ts-ignore"
@@ -168,13 +172,13 @@ const childrenMap = new Map([
]);
/* eslint-enable */
const pk = props.modalConfig.pk ?? "id";
const modalVisible = ref(false);
const formRef = ref<FormInstance>();
const formItems = reactive(props.modalConfig.formItems ?? []);
const formData = reactive<IObject>({});
const formRules: FormRules = {};
const formDisable = ref(false);
const pk = props.modalConfig.pk ?? "id"; // 主键名,用于表单数据处理
const modalVisible = ref(false); // 弹窗显示状态
const formRef = ref<FormInstance>(); // 表单实例
const formItems = reactive(props.modalConfig.formItems ?? []); // 表单配置项
const formData = reactive<IObject>({}); // 表单数据
const formRules: FormRules = {}; // 表单验证规则
const formDisable = ref(false); // 表单禁用状态
// 获取tooltip提示框属性
const getTooltipProps = (tips: string | IObject) => {
@@ -204,17 +208,16 @@ const handleSubmit = useThrottleFn(() => {
props.modalConfig.beforeSubmit(formData);
}
if (!props.modalConfig?.formAction) {
ElMessage.error("未配置exportAction");
emit("customSubmit", formData);
handleClose();
return;
}
props.modalConfig.formAction(formData).then(() => {
let msg = "操作成功";
if (props.modalConfig.component === "drawer") {
msg = `${props.modalConfig.drawer?.title}成功`;
ElMessage.success(`${props.modalConfig.drawer?.title}成功`);
} else {
msg = `${props.modalConfig.dialog?.title}成功`;
ElMessage.success(`${props.modalConfig.dialog?.title}成功`);
}
ElMessage.success(msg);
emit("submitClick");
handleClose();
});

View File

@@ -13,11 +13,11 @@ export type PageFormInstance = InstanceType<typeof PageForm>;
export type IObject = Record<string, any>;
type DataComponent = "date-picker" | "time-picker" | "time-select" | "custom-tag" | "input-tag";
type DateComponent = "date-picker" | "time-picker" | "time-select" | "custom-tag" | "input-tag";
type InputComponent = "input" | "select" | "input-number" | "cascader" | "tree-select";
type OtherComponent = "text" | "radio" | "checkbox" | "switch" | "icon-select" | "custom";
export type ISearchComponent = DataComponent | InputComponent;
export type IComponentType = DataComponent | InputComponent | OtherComponent;
export type ISearchComponent = DateComponent | InputComponent;
export type IComponentType = DateComponent | InputComponent | OtherComponent;
type ToolbarLeft = "add" | "delete" | "import" | "export";
type ToolbarRight = "refresh" | "filter" | "imports" | "exports" | "search";
@@ -44,26 +44,7 @@ export interface ISearchConfig {
// 标签冒号(默认false)
colon?: boolean;
// 表单项(默认:[])
formItems?: Array<{
// 组件类型(如input,select等)
type?: ISearchComponent;
// 标签文本
label?: string;
// 标签提示
tips?: string | IObject;
// 键名
prop: string;
// 组件属性(input-tag组件支持join,btnText,size属性)
attrs?: IObject;
// 初始值
initialValue?: any;
// 可选项(适用于select组件)
options?: Array<{ label: string; value: any }>;
// 组件事件
events?: Record<string, (...args: any) => void>;
// 初始化数据函数扩展
initFn?: (formItem: IObject) => void;
}>;
formItems?: IFormItems;
// 是否开启展开和收缩(默认true)
isExpandable?: boolean;
// 默认展示的表单项数量(默认3)
@@ -199,36 +180,36 @@ export interface IModalConfig<T = any> {
// 提交之前处理
beforeSubmit?: (data: T) => void;
// 提交的网络请求函数(需返回promise)
formAction: (data: T) => Promise<any>;
formAction?: (data: T) => Promise<any>;
}
export type IForm = Partial<Omit<FormProps, "model" | "rules">>;
// 表单项
export type IFormItems = Array<{
// 组件类型(如input,select,radio,custom等默认input)
// 组件类型(如input,select,radio,custom等)
type: IComponentType;
// 标签提示
tips?: string | IObject;
// 标签文本
label: string;
// 键名
prop: string;
// 组件属性
attrs?: IObject;
// 组件可选项(适用于select,radio,checkbox组件)
// 组件可选项(适用于select,radio,checkbox组件)
options?: Array<{
label: string;
value: any;
disabled?: boolean;
[key: string]: any;
}>;
// 插槽名(适用于组件类型为custom)
slotName?: string;
// 标签文本
label: string;
// 标签提示
tips?: string | IObject;
// 键名
prop: string;
// 验证规则
rules?: FormItemRule[];
// 初始值
initialValue?: any;
// 插槽名(适用于自定义组件设置类型为custom)
slotName?: string;
// 是否隐藏
hidden?: boolean;
// layout组件Col属性

View File

@@ -18,23 +18,50 @@ function usePage() {
contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true);
}
// 新增
function handleAddClick() {
//显示添加表单
addModalRef.value?.setModalVisible();
function handleAddClick(RefImpl?: Ref<PageContentInstance>) {
if (RefImpl) {
RefImpl?.value.setModalVisible();
RefImpl?.value.handleDisabled(false);
} else {
addModalRef.value?.setModalVisible();
addModalRef.value?.handleDisabled(false);
}
}
// 编辑
async function handleEditClick(row: IObject, callback?: (result?: IObject) => IObject) {
editModalRef.value?.setModalVisible();
editModalRef.value?.handleDisabled(false);
let from = await (callback?.(row) ?? Promise.resolve(row));
editModalRef.value?.setFormData(from ? from : row);
async function handleEditClick(
row: IObject,
callback?: (result?: IObject) => IObject,
RefImpl?: Ref<PageContentInstance>
) {
if (RefImpl) {
RefImpl.value?.setModalVisible();
RefImpl.value?.handleDisabled(false);
let from = await (callback?.(row) ?? Promise.resolve(row));
RefImpl.value?.setFormData(from ? from : row);
} else {
editModalRef.value?.setModalVisible();
editModalRef.value?.handleDisabled(false);
let from = await (callback?.(row) ?? Promise.resolve(row));
editModalRef.value?.setFormData(from ? from : row);
}
}
// 编辑
async function handleViewClick(row: IObject, callback?: (result?: IObject) => IObject) {
editModalRef.value?.setModalVisible();
editModalRef.value?.handleDisabled(true);
let from = await (callback?.(row) ?? Promise.resolve(row));
editModalRef.value?.setFormData(from ? from : row);
// 查看
async function handleViewClick(
row: IObject,
callback?: (result?: IObject) => IObject,
RefImpl?: Ref<PageContentInstance>
) {
if (RefImpl) {
RefImpl.value?.setModalVisible();
RefImpl.value?.handleDisabled(true);
let from = await (callback?.(row) ?? Promise.resolve(row));
RefImpl.value?.setFormData(from ? from : row);
} else {
editModalRef.value?.setModalVisible();
editModalRef.value?.handleDisabled(true);
let from = await (callback?.(row) ?? Promise.resolve(row));
editModalRef.value?.setFormData(from ? from : row);
}
}
// 表单提交
function handleSubmitClick() {