feat: 优化表单组件取值/获值功能
parent
dc178f5b60
commit
8a2b29ef01
2
.env
2
.env
|
|
@ -2,7 +2,7 @@
|
||||||
# 应用配置
|
# 应用配置
|
||||||
# =====================================================================================
|
# =====================================================================================
|
||||||
# 网站标题
|
# 网站标题
|
||||||
VITE_TITLE = 绝弹中心
|
VITE_TITLE = 绝弹管理系统
|
||||||
# 网站副标题
|
# 网站副标题
|
||||||
VITE_SUBTITLE = 快速开发web应用的模板工具
|
VITE_SUBTITLE = 快速开发web应用的模板工具
|
||||||
# API接口前缀:参见 axios 的 baseURL
|
# API接口前缀:参见 axios 的 baseURL
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 () => {
|
||||||
|
|
|
||||||
|
|
@ -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 = () => {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
上传者
|
上传者
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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: {},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import { Message } from "@arco-design/web-vue";
|
|
||||||
|
|
||||||
export const message = {
|
|
||||||
warning() {
|
|
||||||
Message.warning(``)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue