refactor: ♻️ 规整CURD组件

This commit is contained in:
cshaptx4869
2024-06-13 12:58:03 +08:00
parent cda7a94758
commit 178617d8c2
15 changed files with 327 additions and 369 deletions

View File

@@ -472,160 +472,22 @@
</template>
<script setup lang="ts">
import ExcelJS from "exceljs";
import { ref, reactive } from "vue";
import { useDateFormat, useThrottleFn } from "@vueuse/core";
import { hasAuth } from "@/plugins/permission";
import SvgIcon from "@/components/SvgIcon/index.vue";
import { hasAuth } from "@/plugins/permission";
import { useDateFormat, useThrottleFn } from "@vueuse/core";
import {
type TableProps,
type PaginationProps,
genFileId,
type FormInstance,
type FormRules,
type UploadInstance,
type UploadUserFile,
type UploadRawFile,
genFileId,
type UploadUserFile,
} from "element-plus";
import ExcelJS from "exceljs";
import { reactive, ref } from "vue";
import type { IContentConfig, IObject, IOperatData } from "./types";
// 对象类型
export type IObject = Record<string, any>;
// 定义接收的属性
export interface IOperatData {
name: string;
row: any;
column: any;
$index: number;
}
export interface IContentConfig<T = any> {
// 页面名称(参与组成权限标识,如sys:user:xxx)
pageName: string;
// table组件属性
table?: Omit<TableProps<any>, "data">;
// pagination组件属性
pagination?:
| boolean
| Partial<
Omit<
PaginationProps,
"v-model:page-size" | "v-model:current-page" | "total" | "currentPage"
>
>;
// 列表的网络请求函数(需返回promise)
indexAction: (queryParams: T) => Promise<any>;
// 默认的分页相关的请求参数
request?: {
pageName: string;
limitName: string;
};
// 数据格式解析的回调函数
parseData?: (res: any) => {
total: number;
list: IObject[];
[key: string]: any;
};
// 修改属性的网络请求函数(需返回promise)
modifyAction?: (data: {
[key: string]: any;
field: string;
value: boolean | string | number;
}) => Promise<any>;
// 删除的网络请求函数(需返回promise)
deleteAction?: (ids: string) => Promise<any>;
// 后端导出的网络请求函数(需返回promise)
exportAction?: (queryParams: T) => Promise<any>;
// 前端全量导出的网络请求函数(需返回promise)
exportsAction?: (queryParams: T) => Promise<IObject[]>;
// 前端导入模板
importsTemplate?: string | (() => Promise<any>);
// 前端导入的网络请求函数(需返回promise)
importsAction?: (data: IObject[]) => Promise<any>;
// 主键名(默认为id)
pk?: string;
// 表格工具栏(默认支持add,delete,export,也可自定义)
toolbar?: Array<
| "add"
| "delete"
| "export"
| {
auth: string;
icon?: string;
name: string;
text: string;
}
>;
// 表格工具栏右侧图标
defaultToolbar?: Array<
| "refresh"
| "filter"
| "imports"
| "exports"
| "search"
| {
name: string;
icon: string;
title?: string;
auth?: string;
}
>;
// table组件列属性(额外的属性templet,operat,slotName)
cols: Array<{
type?: "default" | "selection" | "index" | "expand";
label?: string;
prop?: string;
width?: string | number;
align?: "left" | "center" | "right";
columnKey?: string;
reserveSelection?: boolean;
// 列是否显示
show?: boolean;
// 模板
templet?:
| "image"
| "list"
| "url"
| "switch"
| "input"
| "price"
| "percent"
| "icon"
| "date"
| "tool"
| "custom";
// image模板相关参数
imageWidth?: number;
imageHeight?: number;
// list模板相关参数
selectList?: Record<string, any>;
// switch模板相关参数
activeValue?: boolean | string | number;
inactiveValue?: boolean | string | number;
activeText?: string;
inactiveText?: string;
// input模板相关参数
inputType?: string;
// price模板相关参数
priceFormat?: string;
// date模板相关参数
dateFormat?: string;
// tool模板相关参数
operat?: Array<
| "edit"
| "delete"
| {
auth: string;
icon?: string;
name: string;
text: string;
}
>;
// filter值拼接符
filterJoin?: string;
[key: string]: any;
// 初始化数据函数
initFn?: (item: IObject) => void;
}>;
}
const props = defineProps<{
contentConfig: IContentConfig;
}>();
@@ -676,8 +538,6 @@ const cols = ref(
);
// 加载状态
const loading = ref(false);
// 删除ID集合 用于批量删除
const removeIds = ref<(number | string)[]>([]);
// 列表数据
const pageData = ref<IObject[]>([]);
// 显示分页
@@ -704,14 +564,18 @@ const request = props.contentConfig.request ?? {
// 行选中
const selectionData = ref<IObject[]>([]);
// 删除ID集合 用于批量删除
const removeIds = ref<(number | string)[]>([]);
function handleSelectionChange(selection: any[]) {
selectionData.value = selection;
removeIds.value = selection.map((item) => item[pk]);
}
// 刷新
function handleRefresh() {
fetchPageData(lastFormData);
}
// 删除
function handleDelete(id?: number | string) {
const ids = [id || removeIds.value].join(",");
@@ -735,6 +599,7 @@ function handleDelete(id?: number | string) {
}
});
}
// 导出表单
const fields: string[] = [];
cols.value.forEach((item) => {
@@ -825,6 +690,7 @@ function handleExports() {
.catch((error) => console.log(error));
}
}
// 导入表单
const uploadRef = ref<UploadInstance>();
const importsModalVisible = ref(false);
@@ -943,6 +809,7 @@ function handleImports() {
}
};
}
// 操作栏
function handleToolbar(name: string) {
switch (name) {
@@ -972,6 +839,7 @@ function handleToolbar(name: string) {
break;
}
}
// 操作列
function handleOperat(data: IOperatData) {
switch (data.name) {
@@ -986,6 +854,7 @@ function handleOperat(data: IOperatData) {
break;
}
}
// 属性修改
function handleModify(
field: string,
@@ -1002,6 +871,7 @@ function handleModify(
ElMessage.error("未配置modifyAction");
}
}
// 分页切换
function handleSizeChange(value: number) {
pagination.pageSize = value;
@@ -1011,6 +881,7 @@ function handleCurrentChange(value: number) {
pagination.currentPage = value;
fetchPageData(lastFormData);
}
// 远程数据筛选
let filterParams: IObject = {};
function handleFilterChange(newFilters: any) {
@@ -1033,6 +904,7 @@ function handleFilterChange(newFilters: any) {
function getFilterParams() {
return filterParams;
}
// 获取分页数据
let lastFormData = {};
function fetchPageData(formData: IObject = {}, isRestart = false) {
@@ -1069,6 +941,7 @@ function fetchPageData(formData: IObject = {}, isRestart = false) {
});
}
fetchPageData();
// 导出Excel
function exportPageData(formData: IObject = {}) {
if (props.contentConfig.exportAction) {
@@ -1083,6 +956,7 @@ function exportPageData(formData: IObject = {}) {
ElMessage.error("未配置exportAction");
}
}
// 浏览器保存文件
function saveXlsx(fileData: BlobPart, fileName: string) {
const fileType =

View File

@@ -85,22 +85,12 @@
<script setup lang="ts">
import type { FormInstance, FormRules } from "element-plus";
import { reactive, ref, watch, computed, watchEffect } from "vue";
import { IForm, IFormItems, IObject } from "./types";
import { IPageForm, IObject } from "./types";
// 定义接收的属性
const props = withDefaults(
defineProps<{
// 主键名(主要用于编辑数据,默认为id)
pk?: string;
// form组件属性
form?: IForm;
// 表单项
formItems: IFormItems;
}>(),
{
pk: "id",
}
);
const props = withDefaults(defineProps<IPageForm>(), {
pk: "id",
});
const formRef = ref<FormInstance>();
const formItems = reactive(props.formItems);

View File

@@ -244,83 +244,12 @@
</template>
<script setup lang="ts">
import type {
FormInstance,
FormRules,
FormItemRule,
FormProps,
DialogProps,
DrawerProps,
} from "element-plus";
import { useThrottleFn } from "@vueuse/core";
import { reactive, ref, watch, watchEffect, nextTick } from "vue";
import type { FormInstance, FormRules } from "element-plus";
import { nextTick, reactive, ref, watch, watchEffect } from "vue";
import type { IModalConfig, IObject } from "./types";
// 对象类型
export type IObject = Record<string, any>;
// 定义接收的属性
export interface IModalConfig<T = any> {
// 页面名称
pageName?: string;
// 主键名(主要用于编辑数据,默认为id)
pk?: string;
// 组件类型
component?: "dialog" | "drawer";
// dialog组件属性
dialog?: Partial<Omit<DialogProps, "modelValue">>;
// drawer组件属性
drawer?: Partial<Omit<DrawerProps, "modelValue">>;
// form组件属性
form?: Partial<Omit<FormProps, "model" | "rules">>;
// 表单项
formItems: Array<{
// 组件类型(如input,select,radio,custom等,默认input)
type?:
| "input"
| "select"
| "radio"
| "checkbox"
| "tree-select"
| "date-picker"
| "input-number"
| "text"
| "custom";
// 组件属性
attrs?: IObject;
// 组件可选项(适用于select,radio,checkbox组件)
options?: Array<{
label: string;
value: any;
disabled?: boolean;
[key: string]: any;
}>;
// 插槽名(适用于组件类型为custom)
slotName?: string;
// 标签文本
label: string;
// 标签提示
tips?: string;
// 键名
prop: string;
// 验证规则
rules?: FormItemRule[];
// 初始值
initialValue?: any;
// 是否隐藏
hidden?: boolean;
// 监听函数
watch?: (newValue: any, oldValue: any, data: T, items: IObject[]) => void;
// 计算属性函数
computed?: (data: T) => any;
// 监听收集函数
watchEffect?: (data: T) => void;
// 初始化数据函数扩展
initFn?: (item: IObject) => void;
}>;
// 提交之前处理
beforeSubmit?: (data: T) => void;
// 提交的网络请求函数(需返回promise)
formAction: (data: T) => Promise<any>;
}
const props = defineProps<{
modalConfig: IModalConfig;
}>();
@@ -336,6 +265,7 @@ const formItems = reactive(props.modalConfig.formItems);
const formData = reactive<IObject>({});
const formRules: FormRules = {};
const prepareFuncs = [];
// 初始化
for (const item of formItems) {
item.initFn && item.initFn(item);
@@ -396,6 +326,7 @@ const handleSubmit = useThrottleFn(() => {
}
});
}, 3000);
// 关闭弹窗
function handleCloseModal() {
modalVisible.value = false;
@@ -404,6 +335,7 @@ function handleCloseModal() {
formRef.value?.clearValidate();
});
}
// 显示modal
function setModalVisible(data: IObject = {}) {
modalVisible.value = true;
@@ -412,10 +344,12 @@ function setModalVisible(data: IObject = {}) {
Object.values(data).length > 0 && setFormData(data);
});
}
// 获取表单数据
function getFormData(key?: string) {
return key === undefined ? formData : formData[key] ?? undefined;
}
// 设置表单值
function setFormData(data: IObject) {
for (const key in formData) {
@@ -427,6 +361,7 @@ function setFormData(data: IObject) {
formData[pk] = data[pk];
}
}
// 设置表单项值
function setFormItemData(key: string, value: any) {
formData[key] = value;

View File

@@ -116,42 +116,12 @@
<script setup lang="ts">
import type { FormInstance } from "element-plus";
import { reactive, ref } from "vue";
import type { IObject, ISearchConfig } from "./types";
// 对象类型
type IObject = Record<string, any>;
// 定义接收的属性
export interface ISearchConfig {
// 页面名称(参与组成权限标识,如sys:user:xxx)
pageName: string;
// 表单项
formItems: Array<{
// 组件类型(如input,select等)
type?: "input" | "select" | "tree-select" | "date-picker" | "input-tag";
// 标签文本
label: string;
// 标签提示
tips?: string;
// 键名
prop: string;
// 组件属性(input-tag组件支持join,btnText,size属性)
attrs?: IObject;
// 初始值
initialValue?: any;
// 可选项(适用于select组件)
options?: { label: string; value: any }[];
// 初始化数据函数扩展
initFn?: (formItem: IObject) => void;
}>;
// 是否开启展开和收缩
isExpandable?: boolean;
// 默认展示的表单项数量
showNumber?: number;
}
interface IProps {
const props = defineProps<{
searchConfig: ISearchConfig;
}
const props = defineProps<IProps>();
}>();
// 自定义事件
const emit = defineEmits<{
queryClick: [queryParams: IObject];
@@ -218,23 +188,28 @@ for (const item of formItems) {
queryParams[item.prop] = item.initialValue ?? "";
}
}
// 重置操作
function handleReset() {
queryFormRef.value?.resetFields();
emit("resetClick", queryParams);
}
// 查询操作
function handleQuery() {
emit("queryClick", queryParams);
}
// 获取分页数据
function getQueryParams() {
return queryParams;
}
// 显示/隐藏 SearchForm
function toggleVisible() {
visible.value = !visible.value;
}
// 关闭标签
function handleCloseTag(prop: string, tag: string) {
inputTagMap[prop].data.splice(inputTagMap[prop].data.indexOf(tag), 1);

View File

@@ -0,0 +1,262 @@
import type {
DialogProps,
DrawerProps,
FormItemRule,
FormProps,
PaginationProps,
TableProps,
} from "element-plus";
import PageContent from "./PageContent.vue";
import PageForm from "./PageForm.vue";
import PageModal from "./PageModal.vue";
import PageSearch from "./PageSearch.vue";
export type PageSearchInstance = InstanceType<typeof PageSearch>;
export type PageContentInstance = InstanceType<typeof PageContent>;
export type PageModalInstance = InstanceType<typeof PageModal>;
export type PageFormInstance = InstanceType<typeof PageForm>;
export type IObject = Record<string, any>;
export interface IOperatData {
name: string;
row: any;
column: any;
$index: number;
}
export interface ISearchConfig {
// 页面名称(参与组成权限标识,如sys:user:xxx)
pageName: string;
// 表单项
formItems: Array<{
// 组件类型(如input,select等)
type?: "input" | "select" | "tree-select" | "date-picker" | "input-tag";
// 标签文本
label: string;
// 标签提示
tips?: string;
// 键名
prop: string;
// 组件属性(input-tag组件支持join,btnText,size属性)
attrs?: IObject;
// 初始值
initialValue?: any;
// 可选项(适用于select组件)
options?: { label: string; value: any }[];
// 初始化数据函数扩展
initFn?: (formItem: IObject) => void;
}>;
// 是否开启展开和收缩
isExpandable?: boolean;
// 默认展示的表单项数量
showNumber?: number;
}
export interface IContentConfig<T = any> {
// 页面名称(参与组成权限标识,如sys:user:xxx)
pageName: string;
// table组件属性
table?: Omit<TableProps<any>, "data">;
// pagination组件属性
pagination?:
| boolean
| Partial<
Omit<
PaginationProps,
"v-model:page-size" | "v-model:current-page" | "total" | "currentPage"
>
>;
// 列表的网络请求函数(需返回promise)
indexAction: (queryParams: T) => Promise<any>;
// 默认的分页相关的请求参数
request?: {
pageName: string;
limitName: string;
};
// 数据格式解析的回调函数
parseData?: (res: any) => {
total: number;
list: IObject[];
[key: string]: any;
};
// 修改属性的网络请求函数(需返回promise)
modifyAction?: (data: {
[key: string]: any;
field: string;
value: boolean | string | number;
}) => Promise<any>;
// 删除的网络请求函数(需返回promise)
deleteAction?: (ids: string) => Promise<any>;
// 后端导出的网络请求函数(需返回promise)
exportAction?: (queryParams: T) => Promise<any>;
// 前端全量导出的网络请求函数(需返回promise)
exportsAction?: (queryParams: T) => Promise<IObject[]>;
// 前端导入模板
importsTemplate?: string | (() => Promise<any>);
// 前端导入的网络请求函数(需返回promise)
importsAction?: (data: IObject[]) => Promise<any>;
// 主键名(默认为id)
pk?: string;
// 表格工具栏(默认支持add,delete,export,也可自定义)
toolbar?: Array<
| "add"
| "delete"
| "export"
| {
auth: string;
icon?: string;
name: string;
text: string;
}
>;
// 表格工具栏右侧图标
defaultToolbar?: Array<
| "refresh"
| "filter"
| "imports"
| "exports"
| "search"
| {
name: string;
icon: string;
title?: string;
auth?: string;
}
>;
// table组件列属性(额外的属性templet,operat,slotName)
cols: Array<{
type?: "default" | "selection" | "index" | "expand";
label?: string;
prop?: string;
width?: string | number;
align?: "left" | "center" | "right";
columnKey?: string;
reserveSelection?: boolean;
// 列是否显示
show?: boolean;
// 模板
templet?:
| "image"
| "list"
| "url"
| "switch"
| "input"
| "price"
| "percent"
| "icon"
| "date"
| "tool"
| "custom";
// image模板相关参数
imageWidth?: number;
imageHeight?: number;
// list模板相关参数
selectList?: Record<string, any>;
// switch模板相关参数
activeValue?: boolean | string | number;
inactiveValue?: boolean | string | number;
activeText?: string;
inactiveText?: string;
// input模板相关参数
inputType?: string;
// price模板相关参数
priceFormat?: string;
// date模板相关参数
dateFormat?: string;
// tool模板相关参数
operat?: Array<
| "edit"
| "delete"
| {
auth: string;
icon?: string;
name: string;
text: string;
}
>;
// filter值拼接符
filterJoin?: string;
[key: string]: any;
// 初始化数据函数
initFn?: (item: IObject) => void;
}>;
}
export interface IModalConfig<T = any> {
// 页面名称
pageName?: string;
// 主键名(主要用于编辑数据,默认为id)
pk?: string;
// 组件类型
component?: "dialog" | "drawer";
// dialog组件属性
dialog?: Partial<Omit<DialogProps, "modelValue">>;
// drawer组件属性
drawer?: Partial<Omit<DrawerProps, "modelValue">>;
// form组件属性
form?: IForm;
// 表单项
formItems: IFormItems<T>;
// 提交之前处理
beforeSubmit?: (data: T) => void;
// 提交的网络请求函数(需返回promise)
formAction: (data: T) => Promise<any>;
}
export type IForm = Partial<Omit<FormProps, "model" | "rules">>;
// 表单项
export type IFormItems<T = any> = Array<{
// 组件类型(如input,select,radio,custom等,默认input)
type?:
| "input"
| "select"
| "radio"
| "checkbox"
| "tree-select"
| "date-picker"
| "input-number"
| "text"
| "custom";
// 组件属性
attrs?: IObject;
// 组件可选项(适用于select,radio,checkbox组件)
options?: Array<{
label: string;
value: any;
disabled?: boolean;
[key: string]: any;
}>;
// 插槽名(适用于组件类型为custom)
slotName?: string;
// 标签文本
label: string;
// 标签提示
tips?: string;
// 键名
prop: string;
// 验证规则
rules?: FormItemRule[];
// 初始值
initialValue?: any;
// 是否隐藏
hidden?: boolean;
// 监听函数
watch?: (newValue: any, oldValue: any, data: T, items: IObject[]) => void;
// 计算属性函数
computed?: (data: T) => any;
// 监听收集函数
watchEffect?: (data: T) => void;
// 初始化数据函数扩展
initFn?: (item: IObject) => void;
}>;
export interface IPageForm {
// 主键名(主要用于编辑数据,默认为id)
pk?: string;
// form组件属性
form?: IForm;
// 表单项
formItems: IFormItems;
}

View File

@@ -0,0 +1,73 @@
import { ref } from "vue";
import type {
IObject,
PageContentInstance,
PageModalInstance,
PageSearchInstance,
} from "./types";
function usePage() {
const searchRef = ref<PageSearchInstance>();
const contentRef = ref<PageContentInstance>();
const addModalRef = ref<PageModalInstance>();
const editModalRef = ref<PageModalInstance>();
// 搜索
function handleQueryClick(queryParams: IObject) {
const filterParams = contentRef.value?.getFilterParams();
contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true);
}
// 重置
function handleResetClick(queryParams: IObject) {
const filterParams = contentRef.value?.getFilterParams();
contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true);
}
// 新增
function handleAddClick() {
//显示添加表单
addModalRef.value?.setModalVisible();
}
// 编辑
function handleEditClick(row: IObject) {
//显示编辑表单 根据数据进行填充
editModalRef.value?.setModalVisible(row);
}
// 表单提交
function handleSubmitClick() {
//根据检索条件刷新列表数据
const queryParams = searchRef.value?.getQueryParams();
contentRef.value?.fetchPageData(queryParams, true);
}
// 导出
function handleExportClick() {
// 根据检索条件导出数据
const queryParams = searchRef.value?.getQueryParams();
contentRef.value?.exportPageData(queryParams);
}
// 搜索显隐
function handleSearchClick() {
searchRef.value?.toggleVisible();
}
// 涮选数据
function handleFilterChange(filterParams: IObject) {
const queryParams = searchRef.value?.getQueryParams();
contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true);
}
return {
searchRef,
contentRef,
addModalRef,
editModalRef,
handleQueryClick,
handleResetClick,
handleAddClick,
handleEditClick,
handleSubmitClick,
handleExportClick,
handleSearchClick,
handleFilterChange,
};
}
export default usePage;

View File

@@ -1,64 +0,0 @@
import type {
DialogProps,
DrawerProps,
FormProps,
FormItemRule,
} from "element-plus";
// dialog组件属性
export type IDialog = Partial<Omit<DialogProps, "modelValue">>;
// drawer组件属性
export type IDrawer = Partial<Omit<DrawerProps, "modelValue">>;
// form组件属性
export type IForm = Partial<Omit<FormProps, "model" | "rules">>;
// 对象类型
export type IObject = Record<string, any>;
// 表单项
export type IFormItems<T = any> = Array<{
// 组件类型(如input,select,radio,custom等,默认input)
type?:
| "input"
| "select"
| "radio"
| "checkbox"
| "tree-select"
| "date-picker"
| "input-number"
| "text"
| "custom";
// 组件属性
attrs?: IObject;
// 组件可选项(适用于select,radio,checkbox组件)
options?: Array<{
label: string;
value: any;
disabled?: boolean;
[key: string]: any;
}>;
// 插槽名(适用于组件类型为custom)
slotName?: string;
// 标签文本
label: string;
// 标签提示
tips?: string;
// 键名
prop: string;
// 验证规则
rules?: FormItemRule[];
// 初始值
initialValue?: any;
// 是否隐藏
hidden?: boolean;
// 监听函数
watch?: (newValue: any, oldValue: any, data: T, items: IObject[]) => void;
// 计算属性函数
computed?: (data: T) => any;
// 监听收集函数
watchEffect?: (data: T) => void;
// 初始化数据函数扩展
initFn?: (item: IObject) => void;
}>;