Merge pull request #139 from cshaptx4869/patch-95

refactor: ♻️ 规整CURD组件
This commit is contained in:
Ray Hao
2024-06-13 16:27:19 +08:00
committed by GitHub
15 changed files with 438 additions and 469 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

@@ -84,30 +84,19 @@
<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 { reactive, ref, watch, watchEffect } from "vue";
import { IObject, IPageForm } 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);
const formData = reactive<IObject>({});
const formRules: FormRules = {};
const prepareFuncs = [];
//
for (const item of formItems) {
item.initFn && item.initFn(item);
formData[item.prop] = item.initialValue ?? "";
@@ -126,12 +115,8 @@ for (const item of formItems) {
if (item.computed !== undefined) {
prepareFuncs.push(() => {
formData[item.prop] = computed({
get() {
return item.computed ? item.computed(formData) : undefined;
},
// TODO
set() {},
watchEffect(() => {
item.computed && (formData[item.prop] = item.computed(formData));
});
});
}
@@ -145,10 +130,12 @@ for (const item of formItems) {
}
}
prepareFuncs.forEach((func) => func());
//
function getFormData(key?: string) {
return key === undefined ? formData : formData[key] ?? undefined;
}
//
function setFormData(data: IObject) {
for (const key in formData) {
@@ -160,6 +147,7 @@ function setFormData(data: IObject) {
formData[props.pk] = data[props.pk];
}
}
//
function setFormItemData(key: string, value: any) {
formData[key] = value;

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,custominput)
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,7 +265,6 @@ 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);
formData[item.prop] = item.initialValue ?? "";
@@ -371,6 +299,37 @@ for (const item of formItems) {
}
prepareFuncs.forEach((func) => func());
//
function getFormData(key?: string) {
return key === undefined ? formData : formData[key] ?? undefined;
}
//
function setFormData(data: IObject) {
for (const key in formData) {
if (Object.hasOwn(formData, key) && key in data) {
formData[key] = data[key];
}
}
if (Object.hasOwn(data, pk)) {
formData[pk] = data[pk];
}
}
//
function setFormItemData(key: string, value: any) {
formData[key] = value;
}
// modal
function setModalVisible(data: IObject = {}) {
modalVisible.value = true;
// nextTick
nextTick(() => {
Object.values(data).length > 0 && setFormData(data);
});
}
//
const handleSubmit = useThrottleFn(() => {
formRef.value?.validate((valid: boolean) => {
@@ -396,6 +355,7 @@ const handleSubmit = useThrottleFn(() => {
}
});
}, 3000);
//
function handleCloseModal() {
modalVisible.value = false;
@@ -404,33 +364,6 @@ function handleCloseModal() {
formRef.value?.clearValidate();
});
}
// modal
function setModalVisible(data: IObject = {}) {
modalVisible.value = true;
// nextTick
nextTick(() => {
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) {
if (Object.hasOwn(formData, key) && key in data) {
formData[key] = data[key];
}
}
if (Object.hasOwn(data, pk)) {
formData[pk] = data[pk];
}
}
//
function setFormItemData(key: string, value: any) {
formData[key] = value;
}
//
defineExpose({ setModalVisible, getFormData, setFormData, setFormItemData });

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-tagjoin,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

@@ -1,15 +1,16 @@
import { ref } from "vue";
import type PageSearch from "@/components/PageSearch/index.vue";
import type PageContent from "@/components/PageContent/index.vue";
import type PageModal from "@/components/PageModal/index.vue";
import type { IOperatData, IObject } from "@/components/PageContent/index.vue";
export type { IOperatData, IObject };
import type {
IObject,
PageContentInstance,
PageModalInstance,
PageSearchInstance,
} from "./types";
function usePage() {
const searchRef = ref<InstanceType<typeof PageSearch>>();
const contentRef = ref<InstanceType<typeof PageContent>>();
const addModalRef = ref<InstanceType<typeof PageModal>>();
const editModalRef = ref<InstanceType<typeof PageModal>>();
const searchRef = ref<PageSearchInstance>();
const contentRef = ref<PageContentInstance>();
const addModalRef = ref<PageModalInstance>();
const editModalRef = ref<PageModalInstance>();
// 搜索
function handleQueryClick(queryParams: IObject) {

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;
}>;

View File

@@ -325,14 +325,12 @@ declare module "vue" {
interface GlobalComponents {}
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<(typeof import("vue"))["EffectScope"]>;
readonly ElForm: UnwrapRef<(typeof import("element-plus/es"))["ElForm"]>;
readonly ElMessage: UnwrapRef<
(typeof import("element-plus/es"))["ElMessage"]
>;
readonly ElMessageBox: UnwrapRef<
(typeof import("element-plus/es"))["ElMessageBox"]
>;
readonly ElTree: UnwrapRef<(typeof import("element-plus/es"))["ElTree"]>;
readonly acceptHMRUpdate: UnwrapRef<
(typeof import("pinia"))["acceptHMRUpdate"]
>;
@@ -1055,14 +1053,12 @@ declare module "@vue/runtime-core" {
interface GlobalComponents {}
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<(typeof import("vue"))["EffectScope"]>;
readonly ElForm: UnwrapRef<(typeof import("element-plus/es"))["ElForm"]>;
readonly ElMessage: UnwrapRef<
(typeof import("element-plus/es"))["ElMessage"]
>;
readonly ElMessageBox: UnwrapRef<
(typeof import("element-plus/es"))["ElMessageBox"]
>;
readonly ElTree: UnwrapRef<(typeof import("element-plus/es"))["ElTree"]>;
readonly acceptHMRUpdate: UnwrapRef<
(typeof import("pinia"))["acceptHMRUpdate"]
>;

View File

@@ -11,6 +11,7 @@ declare module "vue" {
AppMain: (typeof import("./../layout/components/AppMain/index.vue"))["default"];
BarChart: (typeof import("./../views/dashboard/components/BarChart.vue"))["default"];
Breadcrumb: (typeof import("./../components/Breadcrumb/index.vue"))["default"];
CURD: (typeof import("./../components/CURD/index.vue"))["default"];
DeptTree: (typeof import("./../views/system/user/components/dept-tree.vue"))["default"];
Dictionary: (typeof import("./../components/Dictionary/index.vue"))["default"];
DictItem: (typeof import("./../views/system/dict/components/dict-item.vue"))["default"];
@@ -53,16 +54,13 @@ declare module "vue" {
ElSwitch: (typeof import("element-plus/es"))["ElSwitch"];
ElTable: (typeof import("element-plus/es"))["ElTable"];
ElTableColumn: (typeof import("element-plus/es"))["ElTableColumn"];
ElTabPane: (typeof import("element-plus/es"))["ElTabPane"];
ElTabs: (typeof import("element-plus/es"))["ElTabs"];
ElTag: (typeof import("element-plus/es"))["ElTag"];
ElText: (typeof import("element-plus/es"))["ElText"];
ElTooltip: (typeof import("element-plus/es"))["ElTooltip"];
ElTree: (typeof import("element-plus/es"))["ElTree"];
ElTreeSelect: (typeof import("element-plus/es"))["ElTreeSelect"];
ElUpload: (typeof import("element-plus/es"))["ElUpload"];
ElWatermark: (typeof import("element-plus/es"))["ElWatermark"];
Form: (typeof import("./../components/PageModal/Form.vue"))["default"];
Form: (typeof import("./../components/CURD/Form.vue"))["default"];
FunnelChart: (typeof import("./../views/dashboard/components/FunnelChart.vue"))["default"];
GithubCorner: (typeof import("./../components/GithubCorner/index.vue"))["default"];
Hamburger: (typeof import("./../components/Hamburger/index.vue"))["default"];
@@ -70,26 +68,17 @@ declare module "vue" {
IEpArrowDown: (typeof import("~icons/ep/arrow-down"))["default"];
IEpArrowUp: (typeof import("~icons/ep/arrow-up"))["default"];
IEpClose: (typeof import("~icons/ep/close"))["default"];
IEpDelete: (typeof import("~icons/ep/delete"))["default"];
IEpDownload: (typeof import("~icons/ep/download"))["default"];
IEpEdit: (typeof import("~icons/ep/edit"))["default"];
IEpPlus: (typeof import("~icons/ep/plus"))["default"];
IEpPosition: (typeof import("~icons/ep/position"))["default"];
IEpQuestionFilled: (typeof import("~icons/ep/question-filled"))["default"];
IEpRefresh: (typeof import("~icons/ep/refresh"))["default"];
IEpRefreshLeft: (typeof import("~icons/ep/refresh-left"))["default"];
IEpSearch: (typeof import("~icons/ep/search"))["default"];
IEpTop: (typeof import("~icons/ep/top"))["default"];
IEpUploadFilled: (typeof import("~icons/ep/upload-filled"))["default"];
LangSelect: (typeof import("./../components/LangSelect/index.vue"))["default"];
LayoutSelect: (typeof import("./../layout/components/Settings/components/LayoutSelect.vue"))["default"];
MultiUpload: (typeof import("./../components/Upload/MultiUpload.vue"))["default"];
NavBar: (typeof import("./../layout/components/NavBar/index.vue"))["default"];
NavbarLeft: (typeof import("./../layout/components/NavBar/components/NavbarLeft.vue"))["default"];
NavbarRight: (typeof import("./../layout/components/NavBar/components/NavbarRight.vue"))["default"];
PageContent: (typeof import("./../components/PageContent/index.vue"))["default"];
PageModal: (typeof import("./../components/PageModal/index.vue"))["default"];
PageSearch: (typeof import("./../components/PageSearch/index.vue"))["default"];
PageContent: (typeof import("./../components/CURD/PageContent.vue"))["default"];
PageForm: (typeof import("./../components/CURD/PageForm.vue"))["default"];
PageModal: (typeof import("./../components/CURD/PageModal.vue"))["default"];
PageSearch: (typeof import("./../components/CURD/PageSearch.vue"))["default"];
Pagination: (typeof import("./../components/Pagination/index.vue"))["default"];
PieChart: (typeof import("./../views/dashboard/components/PieChart.vue"))["default"];
RadarChart: (typeof import("./../views/dashboard/components/RadarChart.vue"))["default"];

View File

@@ -2,7 +2,7 @@ import DeptAPI from "@/api/dept";
import RoleAPI from "@/api/role";
import UserAPI from "@/api/user";
import type { UserForm } from "@/api/user/model";
import type { IModalConfig } from "@/components/PageModal/index.vue";
import type { IModalConfig } from "@/components/CURD/types";
const modalConfig: IModalConfig<UserForm> = {
pageName: "sys:user",

View File

@@ -1,7 +1,7 @@
import UserAPI from "@/api/user";
import RoleAPI from "@/api/role";
import type { UserQuery } from "@/api/user/model";
import type { IContentConfig } from "@/components/PageContent/index.vue";
import type { IContentConfig } from "@/components/CURD/types";
const contentConfig: IContentConfig<UserQuery> = {
pageName: "sys:user",

View File

@@ -1,11 +1,13 @@
import type { IContentConfig } from "@/components/PageContent/index.vue";
import type { IContentConfig } from "@/components/CURD/types";
const contentConfig: IContentConfig = {
pageName: "sys:user",
table: {
showOverflowTooltip: true,
},
toolbar: [],
indexAction: function (params) {
// 模拟发起网络请求获取列表数据
// console.log("indexAction:", params);
return Promise.resolve({
total: 2,
@@ -44,11 +46,13 @@ const contentConfig: IContentConfig = {
});
},
modifyAction(data) {
console.log("modifyAction:", data);
// 模拟发起网络请求修改字段
// console.log("modifyAction:", data);
ElMessage.success(JSON.stringify(data));
return Promise.resolve(null);
},
cols: [
{ type: "selection", width: 50, align: "center" },
{ type: "index", width: 50, align: "center" },
{ label: "ID", align: "center", prop: "id", show: false },
{ label: "用户名", align: "center", prop: "username" },
{ label: "图片", align: "center", prop: "avatar", templet: "image" },
@@ -106,14 +110,6 @@ const contentConfig: IContentConfig = {
templet: "date",
dateFormat: "YYYY/MM/DD HH:mm:ss",
},
{
label: "操作",
align: "center",
fixed: "right",
width: 150,
templet: "tool",
operat: ["edit", "delete"],
},
],
};

View File

@@ -2,7 +2,7 @@ import DeptAPI from "@/api/dept";
import RoleAPI from "@/api/role";
import UserAPI from "@/api/user";
import type { UserForm } from "@/api/user/model";
import type { IModalConfig } from "@/components/PageModal/index.vue";
import type { IModalConfig } from "@/components/CURD/types";
import { DeviceEnum } from "@/enums/DeviceEnum";
import { useAppStore } from "@/store";

View File

@@ -1,5 +1,5 @@
import DeptAPI from "@/api/dept";
import type { ISearchConfig } from "@/components/PageSearch/index.vue";
import type { ISearchConfig } from "@/components/CURD/types";
const searchConfig: ISearchConfig = {
pageName: "sys:user",

View File

@@ -1,72 +1,88 @@
<template>
<div class="app-container">
<el-link
href="https://gitee.com/youlaiorg/vue3-element-admin/blob/master/src/views/demo/curd/index.vue"
type="primary"
target="_blank"
class="mb-10"
>
示例源码 请点击>>>>
</el-link>
<!-- 搜索 -->
<page-search
ref="searchRef"
:search-config="searchConfig"
@query-click="handleQueryClick"
@reset-click="handleResetClick"
/>
<div class="flex-x-between mb-10">
<el-link
href="https://gitee.com/youlaiorg/vue3-element-admin/blob/master/src/views/demo/curd/index.vue"
type="primary"
target="_blank"
>
示例源码 请点击>>>>
</el-link>
<el-button type="primary" plain round size="small" @click="isA = !isA">
切换示例
</el-button>
</div>
<!-- 列表 -->
<page-content
ref="contentRef"
:content-config="contentConfig"
@add-click="handleAddClick"
@edit-click="handleEditClick"
@export-click="handleExportClick"
@search-click="handleSearchClick"
@toolbar-click="handleToolbarClick"
@operat-click="handleOperatClick"
@filter-change="handleFilterChange"
>
<template #status="scope">
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
</el-tag>
</template>
</page-content>
<template v-if="isA">
<!-- 搜索 -->
<page-search
ref="searchRef"
:search-config="searchConfig"
@query-click="handleQueryClick"
@reset-click="handleResetClick"
/>
<!-- 新增 -->
<page-modal
ref="addModalRef"
:modal-config="addModalConfig"
@submit-click="handleSubmitClick"
>
<template #gender="scope">
<dictionary v-model="scope.formData[scope.prop]" type-code="gender" />
</template>
</page-modal>
<!-- 列表 -->
<page-content
ref="contentRef"
:content-config="contentConfig"
@add-click="handleAddClick"
@edit-click="handleEditClick"
@export-click="handleExportClick"
@search-click="handleSearchClick"
@toolbar-click="handleToolbarClick"
@operat-click="handleOperatClick"
@filter-change="handleFilterChange"
>
<template #status="scope">
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
</el-tag>
</template>
</page-content>
<!-- 编辑 -->
<page-modal
ref="editModalRef"
:modal-config="editModalConfig"
@submit-click="handleSubmitClick"
>
<template #gender="scope">
<dictionary v-model="scope.formData[scope.prop]" type-code="gender" />
</template>
</page-modal>
<!-- 新增 -->
<page-modal
ref="addModalRef"
:modal-config="addModalConfig"
@submit-click="handleSubmitClick"
>
<template #gender="scope">
<dictionary v-model="scope.formData[scope.prop]" type-code="gender" />
</template>
</page-modal>
<!-- 编辑 -->
<page-modal
ref="editModalRef"
:modal-config="editModalConfig"
@submit-click="handleSubmitClick"
>
<template #gender="scope">
<dictionary v-model="scope.formData[scope.prop]" type-code="gender" />
</template>
</page-modal>
</template>
<template v-else>
<page-content ref="contentRef" :content-config="contentConfig2">
<template #status="scope">
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
</el-tag>
</template>
</page-content>
</template>
</div>
</template>
<script setup lang="ts">
import UserAPI from "@/api/user";
import type { IObject, IOperatData } from "@/hooks/usePage";
import usePage from "@/hooks/usePage";
import type { IObject, IOperatData } from "@/components/CURD/types";
import usePage from "@/components/CURD/usePage";
import addModalConfig from "./config/add";
import contentConfig from "./config/content";
// import contentConfig from "./config/content2";
import contentConfig2 from "./config/content2";
import editModalConfig from "./config/edit";
import searchConfig from "./config/search";
@@ -120,4 +136,7 @@ function handleOperatClick(data: IOperatData) {
});
}
}
// 切换示例
const isA = ref(true);
</script>