feat: 优化表单组件取值/获值功能

master
luoer 2023-08-08 11:32:30 +08:00
parent dc178f5b60
commit 8a2b29ef01
15 changed files with 180 additions and 89 deletions

2
.env
View File

@ -2,7 +2,7 @@
# 应用配置 # 应用配置
# ===================================================================================== # =====================================================================================
# 网站标题 # 网站标题
VITE_TITLE = 绝弹中心 VITE_TITLE = 绝弹管理系统
# 网站副标题 # 网站副标题
VITE_SUBTITLE = 快速开发web应用的模板工具 VITE_SUBTITLE = 快速开发web应用的模板工具
# API接口前缀参见 axios 的 baseURL # API接口前缀参见 axios 的 baseURL

View File

@ -55,7 +55,7 @@ const run = async () => {
// ] // ]
// for (const file of files) { // for (const file of files) {
// createFile(file) // createFile(file)
// } // } pathParams, queryParams, bodyParams, headerParams, formDataParams, responses, method, url
debugger debugger
return output; return output;
}; };

View File

@ -63,6 +63,7 @@ export interface User {
* @example "example@mail.com" * @example "example@mail.com"
*/ */
email: string; email: string;
/** 用户角色ID */
roleIds: number[]; roleIds: number[];
} }
@ -196,6 +197,7 @@ export interface LoginedUserVo {
* @example "example@mail.com" * @example "example@mail.com"
*/ */
email: string; email: string;
/** 用户角色ID */
roleIds: number[]; roleIds: number[];
} }

View File

@ -1,5 +1,77 @@
export const config = { export const config = {
/**
* API
*/
getApiErrorMessage(error: any) { getApiErrorMessage(error: any) {
return error?.response?.data?.message || error?.message || "Error"; return error?.response?.data?.message || error?.message || "Error";
}, },
/**
*
*/
setModel: function setModel(model: any, data: any) {
for (const key of Object.keys(model)) {
// 数组类型
if (/^\[.+\]$/.test(key)) {
const subkeysStr = key.replaceAll(/\s/g, "").match(/^\[(.+)\]$/)?.[1];
if (!subkeysStr) {
model[key] = data[key];
continue;
}
const subkeys = subkeysStr.split(",");
const value = new Array(subkeys.length);
subkeys.forEach((subkey, index) => {
if (/.+:number$/.test(subkey)) {
subkey = subkey.replace(/:number$/, "");
value[index] = Number(data[subkey]);
return;
}
if (/.+:boolean$/.test(subkey)) {
subkey = subkey.replace(/:boolean$/, "");
value[index] = Boolean(data[subkey]);
return;
}
value[index] = data[subkey];
});
model[key] = value;
continue;
}
// 默认类型
model[key] = data[key];
}
return model;
},
/**
*
*/
getModel: function getModel(model: Record<string, any>) {
const data: any = {};
for (const [key, val] of Object.entries(model)) {
// 数组类型
if (/^\[.+\]$/.test(key)) {
const subkeysStr = key.replaceAll(/\s/g, "").match(/^\[(.+)\]$/)?.[1];
if (!subkeysStr) {
data[key] = val;
continue;
}
const subkeys = subkeysStr.split(",");
subkeys.forEach((subkey, index) => {
if (/(.+)?:number$/.test(subkey)) {
subkey = subkey.replace(/:number$/, "");
data[subkey] = val?.[index] && Number(val[index]);
return;
}
if (/(.+)?:boolean$/.test(subkey)) {
subkey = subkey.replace(/:boolean$/, "");
data[subkey] = val?.[index] && Boolean(val[index]);
return;
}
data[subkey] = val?.[index];
});
continue;
}
// 默认类型
data[key] = val;
}
return data;
},
}; };

View File

@ -3,11 +3,9 @@ import { NodeType, NodeUnion, nodeMap } from "./form-node";
import { RuleMap } from "./form-rules"; import { RuleMap } from "./form-rules";
export type FieldStringRule = keyof typeof RuleMap; export type FieldStringRule = keyof typeof RuleMap;
export type FieldObjectRule = FieldRule & { export type FieldObjectRule = FieldRule & {
disable?: (arg: { item: IFormItem; model: Record<string, any> }) => boolean; disable?: (arg: { item: IFormItem; model: Record<string, any> }) => boolean;
}; };
export type FieldRuleType = FieldStringRule | FieldObjectRule; export type FieldRuleType = FieldStringRule | FieldObjectRule;
/** /**
@ -56,7 +54,7 @@ export const FormItem = (props: any, { emit }: any) => {
} }
return ( return (
<BaseFormItem rules={rules.value} disabled={disabled.value} field={item.field} {...item.itemProps}> <BaseFormItem {...item.itemProps} rules={rules.value} disabled={disabled.value} field={item.field}>
{{ {{
default: () => { default: () => {
if (item.component) { if (item.component) {

View File

@ -46,7 +46,7 @@ export const FormModal = defineComponent({
* *
*/ */
model: { model: {
type: Object as PropType<Record<string, any>>, type: Object as PropType<Record<any, any>>,
required: true, required: true,
}, },
/** /**
@ -81,9 +81,7 @@ export const FormModal = defineComponent({
const open = async (data: Record<string, any> = {}) => { const open = async (data: Record<string, any> = {}) => {
visible.value = true; visible.value = true;
await nextTick(); await nextTick();
for (const key in data) { config.setModel(props.model, data);
props.model[key] = data[key];
}
}; };
const onBeforeOk = async () => { const onBeforeOk = async () => {

View File

@ -3,6 +3,7 @@ import { assign, cloneDeep, defaultsDeep } from "lodash-es";
import { PropType } from "vue"; import { PropType } from "vue";
import { FormItem, IFormItem } from "./form-item"; import { FormItem, IFormItem } from "./form-item";
import { NodeType, nodeMap } from "./form-node"; import { NodeType, nodeMap } from "./form-node";
import { config } from "./form-config";
type SubmitFn = (arg: { model: Record<string, any>; items: IFormItem[] }) => Promise<any>; type SubmitFn = (arg: { model: Record<string, any>; items: IFormItem[] }) => Promise<any>;
@ -16,7 +17,7 @@ export const Form = defineComponent({
* *
*/ */
model: { model: {
type: Object as PropType<Record<string, any>>, type: Object as PropType<Record<any, any>>,
default: () => reactive({}), default: () => reactive({}),
}, },
/** /**
@ -55,30 +56,11 @@ export const Form = defineComponent({
}; };
const getModel = () => { const getModel = () => {
const model: Record<string, any> = {}; return config.getModel(props.model);
for (const key of Object.keys(props.model)) {
if (/[^:]+:[^:]+/.test(key)) {
const keys = key.split(":");
const vals = cloneDeep(props.model[key] || []);
for (const k of keys) {
model[k] = vals.shift();
}
} else {
model[key] = cloneDeep(props.model[key]);
}
}
return model;
}; };
const setModel = (model: Record<string, any>) => { const setModel = (data: Record<string, any>) => {
for (const key of Object.keys(props.model)) { config.setModel(props.model, data);
if (/[^:]+:[^:]+/.test(key)) {
const [key1, key2] = key.split(":");
props.model[key] = [model[key1], model[key2]];
} else {
props.model[key] = model[key];
}
}
}; };
const resetModel = () => { const resetModel = () => {

View File

@ -1,5 +1,6 @@
import { FormInstance } from "@arco-design/web-vue"; import { FormInstance } from "@arco-design/web-vue";
import { IFormItem } from "./form-item"; import { IFormItem } from "./form-item";
import { merge } from "lodash-es";
export type Options = { export type Options = {
/** /**
@ -28,34 +29,29 @@ export const useForm = (options: Options) => {
const { model = { id: undefined } } = options; const { model = { id: undefined } } = options;
const items: IFormItem[] = []; const items: IFormItem[] = [];
options.items.forEach((item) => { for (const item of options.items) {
if (!item.nodeProps) { if (!item.nodeProps) {
item.nodeProps = {} as any; item.nodeProps = {} as any;
} }
if (/(.+)\?(.+)/.test(item.field)) {
const [field, condition] = item.field.split("?");
model[field] = item.initial ?? model[item.field];
const params = new URLSearchParams(condition);
for (const [key, value] of params.entries()) {
model[key] = value;
}
}
model[item.field] = model[item.field] ?? item.initial; model[item.field] = model[item.field] ?? item.initial;
const _item = { ...item }; items.push(item);
items.push(_item); }
});
if (options.submit) { if (options.submit) {
const submit = items.find((item) => item.type === "submit"); const submit = items.find((item) => item.type === "submit") || {};
if (!submit) { items.push(
items.push({ merge(
field: "id", {},
type: "submit", {
itemProps: { field: "id",
hideLabel: true, type: "submit",
itemProps: {
hideLabel: true,
},
}, },
}); submit
} ) as any
);
} }
return reactive({ ...options, model, items }) as any; return reactive({ ...options, model, items }) as any;

View File

@ -1,10 +1,11 @@
import { Link, Message, Modal, TableColumnData } from "@arco-design/web-vue"; import { Link, Message, TableColumnData } from "@arco-design/web-vue";
import { defaultsDeep, isArray, isFunction, merge } from "lodash-es"; import { defaultsDeep, isArray, merge } from "lodash-es";
import { reactive } from "vue"; import { reactive } from "vue";
import { useFormModal } from "../form"; import { useFormModal } from "../form";
import { TableInstance } from "./table"; import { TableInstance } from "./table";
import { config } from "./table.config"; import { config } from "./table.config";
import { UseTableOptions } from "./use-interface"; import { UseTableOptions } from "./use-interface";
import { modal } from "@/utils/modal";
/** /**
* hook * hook
@ -43,25 +44,21 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
column.buttons = column.buttons?.map((action) => { column.buttons = column.buttons?.map((action) => {
let onClick = action?.onClick; let onClick = action?.onClick;
if (action.type === "delete") { if (action.type === "delete") {
onClick = (data) => { onClick = async (data) => {
Modal.warning({ await modal.delConfirm();
...config.columnButtonDelete, try {
onOk: async () => { const resData: any = await action?.onClick?.(data);
try { const message = resData?.data?.message;
const resData: any = await action?.onClick?.(data); if (message) {
const message = resData?.data?.message; Message.success(`提示:${message}`);
if (message) { }
Message.success(`提示:${message}`); getTable()?.loadData();
} } catch (error: any) {
getTable()?.loadData(); const message = error.response?.data?.message;
} catch (error: any) { if (message) {
const message = error.response?.data?.message; Message.warning(`提示:${message}`);
if (message) { }
Message.warning(`提示:${message}`); }
}
}
},
});
}; };
} }
return { ...config.columnButtonBase, ...action, onClick } as any; return { ...config.columnButtonBase, ...action, onClick } as any;

View File

@ -8,7 +8,7 @@
<ACheckbox></ACheckbox> <ACheckbox></ACheckbox>
<AInput class="inline-block w-80" placeholder="输入名称关键字"></AInput> <AInput class="inline-block w-80" placeholder="输入名称关键字"></AInput>
</div> </div>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4 text-gray-500">
<ADropdown> <ADropdown>
<span class="cursor-pointer"> <span class="cursor-pointer">
上传者 上传者

View File

@ -35,19 +35,26 @@ const table = useTable({
{ {
title: "操作", title: "操作",
type: "button", type: "button",
width: 70, width: 136,
buttons: [ buttons: [
{ {
type: "modify", type: "modify",
text: "修改", text: "修改",
}, },
{
type: "delete",
text: "删除",
onClick: ({ record }) => {
return api.post.delPost(record.id);
},
},
], ],
}, },
], ],
search: { search: {
items: [ items: [
{ {
extend: "name", extend: "title",
required: false, required: false,
}, },
], ],
@ -85,7 +92,7 @@ const table = useTable({
}, },
], ],
submit: ({ model }) => { submit: ({ model }) => {
return api.role.addRole(model); return api.post.addPost(model);
}, },
}, },
modify: { modify: {

View File

@ -43,12 +43,25 @@ const table = useTable({
{ {
title: "操作", title: "操作",
type: "button", type: "button",
width: 70, width: 184,
buttons: [ buttons: [
{ {
type: "modify", type: "modify",
text: "修改", text: "修改",
}, },
{
text: '分配权限',
onClick: ({ record }) => {
console.log(record);
},
},
{
text: "删除",
type: "delete",
onClick: ({ record }) => {
return api.role.delRole(record.id);
},
}
], ],
}, },
], ],
@ -88,12 +101,14 @@ const table = useTable({
}, },
{ {
field: "permissions", field: "permissions",
label: "关联角色", label: "关联权限",
type: "select", type: "select",
options: () => api.permission.getPermissions(), options: () => api.permission.getPermissions(),
nodeProps: {
multiple: true,
},
}, },
], ],
submit: ({ model }) => { submit: ({ model }) => {
return api.role.addRole(model); return api.role.addRole(model);
}, },

View File

@ -123,7 +123,7 @@ const table = useTable({
type: "select", type: "select",
}, },
{ {
field: "startTime:endTime", field: "[startTime,endTime]",
label: "日期范围", label: "日期范围",
type: "dateRange", type: "dateRange",
nodeProps: {}, nodeProps: {},

View File

@ -1,7 +0,0 @@
import { Message } from "@arco-design/web-vue";
export const message = {
warning() {
Message.warning(``)
}
}

31
src/utils/modal.ts Normal file
View File

@ -0,0 +1,31 @@
import { Modal, ModalConfig } from "@arco-design/web-vue";
import { merge } from "lodash-es";
export const modal = {
delConfirm(config: Partial<Omit<ModalConfig, "onOk" | "onCancel">> = {}) {
return new Promise<void>((resolve, reject) => {
const mergedConfig = merge(
{
title: "提示",
titleAlign: "start",
width: 432,
content: "确定删除该数据吗?注意:该操作不可恢复!",
maskClosable: false,
closable: false,
okText: "确定删除",
okButtonProps: {
status: "danger",
},
onOk: () => {
resolve();
},
onCancel: () => {
reject();
},
},
config
);
Modal.open(mergedConfig);
});
},
};