feat: 优化表格组件
parent
e6a83318bc
commit
6ce573fda7
1
.env
1
.env
|
|
@ -24,7 +24,6 @@ VITE_API_BASE_URL = http://127.0.0.1:3030
|
||||||
VITE_API_PROXY_URL = /api
|
VITE_API_PROXY_URL = /api
|
||||||
|
|
||||||
# API文档地址(开发环境) 备注:需为openapi规范的json文件
|
# API文档地址(开发环境) 备注:需为openapi规范的json文件
|
||||||
# VITE_API_DOCS_URL = http://127.0.0.1:3030/openapi-json
|
|
||||||
VITE_API_DOCS_URL = https://petstore.swagger.io/v2/swagger.json
|
VITE_API_DOCS_URL = https://petstore.swagger.io/v2/swagger.json
|
||||||
|
|
||||||
# 端口号(开发环境)
|
# 端口号(开发环境)
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,8 @@
|
||||||
import { FormItem as BaseFormItem, FieldRule, FormItemInstance, SelectOptionData } from "@arco-design/web-vue";
|
import { FormItem as BaseFormItem, FieldRule, FormItemInstance, SelectOptionData } from "@arco-design/web-vue";
|
||||||
import { NodeType, NodeUnion, nodeMap } from "./form-node";
|
import { NodeType, NodeUnion, nodeMap } from "./form-node";
|
||||||
|
import { RuleMap } from "./form-rules";
|
||||||
|
|
||||||
const defineRuleMap = <T extends Record<string, FieldRule>>(ruleMap: T) => ruleMap;
|
export type FieldStringRule = keyof typeof RuleMap;
|
||||||
|
|
||||||
const ruleMap = defineRuleMap({
|
|
||||||
required: {
|
|
||||||
required: true,
|
|
||||||
message: "该项不能为空",
|
|
||||||
},
|
|
||||||
string: {
|
|
||||||
type: "string",
|
|
||||||
message: "请输入字符串",
|
|
||||||
},
|
|
||||||
number: {
|
|
||||||
type: "number",
|
|
||||||
message: "请输入数字",
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: "email",
|
|
||||||
message: "邮箱格式错误,示例: xx@abc.com",
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
type: "url",
|
|
||||||
message: "URL格式错误, 示例: www.abc.com",
|
|
||||||
},
|
|
||||||
ip: {
|
|
||||||
type: "ip",
|
|
||||||
message: "IP格式错误, 示例: 101.10.10.30",
|
|
||||||
},
|
|
||||||
phone: {
|
|
||||||
match: /^(?:(?:\+|00)86)?1\d{10}$/,
|
|
||||||
message: "手机格式错误, 示例(11位): 15912345678",
|
|
||||||
},
|
|
||||||
idcard: {
|
|
||||||
match: /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/,
|
|
||||||
message: "身份证格式错误, 长度为15或18位",
|
|
||||||
},
|
|
||||||
alphabet: {
|
|
||||||
match: /^[a-zA-Z]\w{4,15}$/,
|
|
||||||
message: "请输入英文字母, 长度为4~15位",
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
match: /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/,
|
|
||||||
message: "至少包含大写字母、小写字母、数字和特殊字符",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -67,11 +23,11 @@ export const FormItem = (props: any, { emit }: any) => {
|
||||||
const rules = computed(() => {
|
const rules = computed(() => {
|
||||||
const result = [];
|
const result = [];
|
||||||
if (item.required) {
|
if (item.required) {
|
||||||
result.push(ruleMap.required);
|
result.push(RuleMap.required);
|
||||||
}
|
}
|
||||||
item.rules?.forEach((rule: any) => {
|
item.rules?.forEach((rule: any) => {
|
||||||
if (typeof rule === "string") {
|
if (typeof rule === "string") {
|
||||||
result.push(ruleMap[rule as FieldStringRule]);
|
result.push(RuleMap[rule as FieldStringRule]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!rule.disable) {
|
if (!rule.disable) {
|
||||||
|
|
@ -144,9 +100,9 @@ type FormItemBase = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始值
|
* 初始值
|
||||||
* @description 默认值为undefined,优先级比model中的同名属性高。
|
* @description 若指定该参数,将覆盖model中的同名属性。
|
||||||
*/
|
*/
|
||||||
initialValue?: any;
|
initial?: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标签名
|
* 标签名
|
||||||
|
|
@ -172,14 +128,16 @@ type FormItemBase = {
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* rules: [
|
* rules: [
|
||||||
* 'idcard', // 内置的身份证号校验规则
|
* // 内置
|
||||||
|
* 'idcard',
|
||||||
|
* // 自定义
|
||||||
* {
|
* {
|
||||||
* match: /\d+/,
|
* match: /\d+/,
|
||||||
* message: '请输入数字',
|
* message: '请输入数字',
|
||||||
* },
|
* },
|
||||||
* ]
|
* ]
|
||||||
*```
|
*```
|
||||||
* @see https://arco.design/vue/component/form#FieldRule
|
* @see https://arco.design/vue/component/form#FieldRule
|
||||||
*/
|
*/
|
||||||
rules?: FieldRuleType[];
|
rules?: FieldRuleType[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,29 +17,27 @@ import {
|
||||||
const initOptions = ({ item, model }: any) => {
|
const initOptions = ({ item, model }: any) => {
|
||||||
if (Array.isArray(item.options)) {
|
if (Array.isArray(item.options)) {
|
||||||
item.nodeProps.options = item.options;
|
item.nodeProps.options = item.options;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (typeof item.options !== "function") {
|
if (typeof item.options === "function") {
|
||||||
return;
|
item.nodeProps.options = reactive([]);
|
||||||
|
const fetchData = item.options;
|
||||||
|
item._updateOptions = async () => {
|
||||||
|
let data = await fetchData({ item, model });
|
||||||
|
if (Array.isArray(data?.data)) {
|
||||||
|
data = data.data.map((i: any) => ({ label: i.name, value: i.id }));
|
||||||
|
}
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
item.nodeProps.options.splice(0);
|
||||||
|
item.nodeProps.options.push(...data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
item._updateOptions();
|
||||||
}
|
}
|
||||||
item.nodeProps.options = reactive([]);
|
|
||||||
const fetchData = item.options;
|
|
||||||
item._updateOptions = async () => {
|
|
||||||
let data = await fetchData({ item, model });
|
|
||||||
if (Array.isArray(data?.data)) {
|
|
||||||
data = data.data.map((i: any) => ({ label: i.name, value: i.id }));
|
|
||||||
}
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
item.nodeProps.options.splice(0);
|
|
||||||
item.nodeProps.options.push(...data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
item._updateOptions();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const defineNodeMap = <T extends { [key: string]: { component: any, nodeProps: any } }>(map: T) => {
|
const defineNodeMap = <T extends { [key: string]: { component: any; nodeProps: any } }>(map: T) => {
|
||||||
return map
|
return map;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单项组件映射
|
* 表单项组件映射
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { FieldRule } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
const defineRuleMap = <T extends Record<string, FieldRule>>(ruleMap: T) => ruleMap;
|
||||||
|
|
||||||
|
export const RuleMap = defineRuleMap({
|
||||||
|
required: {
|
||||||
|
required: true,
|
||||||
|
message: "该项不能为空",
|
||||||
|
},
|
||||||
|
string: {
|
||||||
|
type: "string",
|
||||||
|
message: "请输入字符串",
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
type: "number",
|
||||||
|
message: "请输入数字",
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: "email",
|
||||||
|
message: "邮箱格式错误,示例: xx@abc.com",
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: "url",
|
||||||
|
message: "URL格式错误, 示例: www.abc.com",
|
||||||
|
},
|
||||||
|
ip: {
|
||||||
|
type: "ip",
|
||||||
|
message: "IP格式错误, 示例: 101.10.10.30",
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
match: /^(?:(?:\+|00)86)?1\d{10}$/,
|
||||||
|
message: "手机格式错误, 示例(11位): 15912345678",
|
||||||
|
},
|
||||||
|
idcard: {
|
||||||
|
match: /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/,
|
||||||
|
message: "身份证格式错误, 长度为15或18位",
|
||||||
|
},
|
||||||
|
alphabet: {
|
||||||
|
match: /^[a-zA-Z]\w{4,15}$/,
|
||||||
|
message: "请输入英文字母, 长度为4~15位",
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
match: /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/,
|
||||||
|
message: "至少包含大写字母、小写字母、数字和特殊字符",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -4,6 +4,8 @@ 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";
|
||||||
|
|
||||||
|
type SubmitFn = (arg: { model: Record<string, any>; items: IFormItem[] }) => Promise<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单组件
|
* 表单组件
|
||||||
*/
|
*/
|
||||||
|
|
@ -28,7 +30,7 @@ export const Form = defineComponent({
|
||||||
* 提交表单
|
* 提交表单
|
||||||
*/
|
*/
|
||||||
submit: {
|
submit: {
|
||||||
type: Function as PropType<(arg: { model: Record<string, any>; items: IFormItem[] }) => Promise<any>>,
|
type: Function as PropType<SubmitFn>,
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 传给Form组件的参数
|
* 传给Form组件的参数
|
||||||
|
|
@ -68,6 +70,21 @@ export const Form = defineComponent({
|
||||||
return model;
|
return model;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setModel = (model: Record<string, any>) => {
|
||||||
|
for (const key of Object.keys(props.model)) {
|
||||||
|
if (/[^:]+:[^:]+/.test(key)) {
|
||||||
|
const [key1, key2] = key.split(":");
|
||||||
|
props.model[key] = [model[key1], model[key2]];
|
||||||
|
} else {
|
||||||
|
props.model[key] = model[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetModel = () => {
|
||||||
|
assign(props.model, model);
|
||||||
|
};
|
||||||
|
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
if (await formRef.value?.validate()) {
|
if (await formRef.value?.validate()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -82,21 +99,6 @@ export const Form = defineComponent({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetModel = () => {
|
|
||||||
assign(props.model, model);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setModel = (model: Record<string, any>) => {
|
|
||||||
for (const key of Object.keys(props.model)) {
|
|
||||||
if (/.+:.+/.test(key)) {
|
|
||||||
const [key1, key2] = key.split(":");
|
|
||||||
props.model[key] = [model[key1], model[key2]];
|
|
||||||
} else {
|
|
||||||
props.model[key] = model[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formRef,
|
formRef,
|
||||||
loading,
|
loading,
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,13 @@ export const useForm = (options: Options) => {
|
||||||
}
|
}
|
||||||
if (/(.+)\?(.+)/.test(item.field)) {
|
if (/(.+)\?(.+)/.test(item.field)) {
|
||||||
const [field, condition] = item.field.split("?");
|
const [field, condition] = item.field.split("?");
|
||||||
model[field] = item.initialValue ?? model[item.field];
|
model[field] = item.initial ?? model[item.field];
|
||||||
const params = new URLSearchParams(condition);
|
const params = new URLSearchParams(condition);
|
||||||
for (const [key, value] of params.entries()) {
|
for (const [key, value] of params.entries()) {
|
||||||
model[key] = value;
|
model[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model[item.field] = model[item.field] ?? item.initialValue;
|
model[item.field] = model[item.field] ?? item.initial;
|
||||||
const _item = { ...item };
|
const _item = { ...item };
|
||||||
items.push(_item);
|
items.push(_item);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,71 +1,62 @@
|
||||||
import { Button } from "@arco-design/web-vue";
|
import { Button } from "@arco-design/web-vue";
|
||||||
import { IconRefresh, IconSearch } from "@arco-design/web-vue/es/icon";
|
import { IconRefresh, IconSearch } from "@arco-design/web-vue/es/icon";
|
||||||
|
|
||||||
/**
|
export const config = {
|
||||||
* 搜索表单默认参数
|
searchFormProps: {
|
||||||
*/
|
labelAlign: "left",
|
||||||
export const TABLE_SEARCH_DEFAULTS = {
|
autoLabelWidth: true,
|
||||||
labelAlign: "left",
|
model: {},
|
||||||
autoLabelWidth: true,
|
|
||||||
model: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表格列默认参数
|
|
||||||
*/
|
|
||||||
export const TABLE_COLUMN_DEFAULTS = {
|
|
||||||
ellipsis: true,
|
|
||||||
tooltip: true,
|
|
||||||
render: ({ record, column }: any) => record[column.dataIndex] || "-",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 行操作按钮默认参数
|
|
||||||
*/
|
|
||||||
export const TABLE_ACTION_DEFAULTS = {
|
|
||||||
buttonProps: {
|
|
||||||
type: "primary",
|
|
||||||
},
|
},
|
||||||
};
|
searchItemSubmit: {
|
||||||
|
field: "id",
|
||||||
/**
|
type: "custom",
|
||||||
* 删除弹窗默认参数
|
itemProps: {
|
||||||
*/
|
class: "table-search-item col-start-4 !mr-0 grid grid-cols-[0_1fr]",
|
||||||
export const TABLE_DELTE_DEFAULTS = {
|
hideLabel: true,
|
||||||
title: "删除确认",
|
},
|
||||||
content: "确认删除当前数据吗?",
|
component: () => {
|
||||||
modalClass: "text-center",
|
const tableRef = inject<any>("ref:table");
|
||||||
hideCancel: false,
|
return (
|
||||||
maskClosable: false,
|
<div class="w-full flex gap-x-2 justify-end">
|
||||||
};
|
{(tableRef.search?.items?.length || 0) > 3 && (
|
||||||
|
<Button disabled={tableRef?.loading.value} onClick={() => tableRef?.reloadData()}>
|
||||||
export const TALBE_INDEX_DEFAULTS = {
|
{{ icon: () => <IconRefresh></IconRefresh>, default: () => "重置" }}
|
||||||
title: "#",
|
</Button>
|
||||||
width: 60,
|
)}
|
||||||
align: "center",
|
<Button type="primary" loading={tableRef?.loading.value} onClick={() => tableRef?.loadData()}>
|
||||||
render: ({ rowIndex }: any) => rowIndex + 1,
|
{{ icon: () => <IconSearch></IconSearch>, default: () => "查询" }}
|
||||||
};
|
|
||||||
|
|
||||||
export const searchItem = {
|
|
||||||
field: "id",
|
|
||||||
type: "custom",
|
|
||||||
itemProps: {
|
|
||||||
class: "table-search-item col-start-4 !mr-0 grid grid-cols-[0_1fr]",
|
|
||||||
hideLabel: true,
|
|
||||||
},
|
|
||||||
component: () => {
|
|
||||||
const tableRef = inject<any>("ref:table");
|
|
||||||
return (
|
|
||||||
<div class="w-full flex gap-x-2 justify-end">
|
|
||||||
{(tableRef.search?.items?.length || 0) > 3 && (
|
|
||||||
<Button disabled={tableRef?.loading.value} onClick={() => tableRef?.reloadData()}>
|
|
||||||
{{ icon: () => <IconRefresh></IconRefresh>, default: () => "重置" }}
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</div>
|
||||||
<Button type="primary" loading={tableRef?.loading.value} onClick={() => tableRef?.loadData()}>
|
);
|
||||||
{{ icon: () => <IconSearch></IconSearch>, default: () => "查询" }}
|
},
|
||||||
</Button>
|
},
|
||||||
</div>
|
pagination: {
|
||||||
);
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 300,
|
||||||
|
showTotal: true,
|
||||||
|
},
|
||||||
|
columnBase: {
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
render: ({ record, column }: any) => record[column.dataIndex] || "-",
|
||||||
|
},
|
||||||
|
columnIndex: {
|
||||||
|
title: "序号",
|
||||||
|
width: 60,
|
||||||
|
align: "center",
|
||||||
|
render: ({ rowIndex }: any) => rowIndex + 1,
|
||||||
|
},
|
||||||
|
columnButtonBase: {
|
||||||
|
buttonProps: {
|
||||||
|
type: "primary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columnButtonDelete: {
|
||||||
|
title: "删除确认",
|
||||||
|
content: "确认删除当前数据吗?",
|
||||||
|
modalClass: "text-center",
|
||||||
|
hideCancel: false,
|
||||||
|
maskClosable: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import {
|
import { TableColumnData as BaseColumn, TableData as BaseData, Table as BaseTable } from "@arco-design/web-vue";
|
||||||
TableColumnData as BaseColumn,
|
import { merge } from "lodash-es";
|
||||||
TableData as BaseData,
|
|
||||||
Table as BaseTable,
|
|
||||||
Divider,
|
|
||||||
} from "@arco-design/web-vue";
|
|
||||||
import { PropType, computed, defineComponent, reactive, ref, watch } from "vue";
|
import { PropType, computed, defineComponent, reactive, ref, watch } from "vue";
|
||||||
import { Form, FormInstance, FormModal, FormModalInstance, FormModalProps, FormProps } from "../form";
|
import { Form, FormInstance, FormModal, FormModalInstance, FormModalProps, FormProps } from "../form";
|
||||||
|
import { config } from "./table.config";
|
||||||
|
|
||||||
|
type DataFn = (search: Record<string, any>, paging: { page: number; size: number }) => Promise<any>;
|
||||||
|
type Data = BaseData[] | DataFn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表格组件
|
* 表格组件
|
||||||
|
|
@ -18,9 +18,7 @@ export const Table = defineComponent({
|
||||||
* 表格数据
|
* 表格数据
|
||||||
*/
|
*/
|
||||||
data: {
|
data: {
|
||||||
type: [Array, Function] as PropType<
|
type: [Array, Function] as PropType<Data>,
|
||||||
BaseData[] | ((search: Record<string, any>, paging: { page: number; size: number }) => Promise<any>)
|
|
||||||
>,
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 表格列设置
|
* 表格列设置
|
||||||
|
|
@ -34,7 +32,7 @@ export const Table = defineComponent({
|
||||||
*/
|
*/
|
||||||
pagination: {
|
pagination: {
|
||||||
type: Object as PropType<any>,
|
type: Object as PropType<any>,
|
||||||
default: () => reactive({ current: 1, pageSize: 10, total: 300, showTotal: true }),
|
default: () => reactive(config.pagination),
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 搜索表单配置
|
* 搜索表单配置
|
||||||
|
|
@ -73,25 +71,17 @@ export const Table = defineComponent({
|
||||||
const createRef = ref<FormModalInstance>();
|
const createRef = ref<FormModalInstance>();
|
||||||
const modifyRef = ref<FormModalInstance>();
|
const modifyRef = ref<FormModalInstance>();
|
||||||
const renderData = ref<BaseData[]>([]);
|
const renderData = ref<BaseData[]>([]);
|
||||||
const inlineSearch = computed(() => (props.search?.items?.length || 0) < 4);
|
const inlined = computed(() => (props.search?.items?.length ?? 0) < 4);
|
||||||
|
const reloadData = () => loadData({ current: 1, pageSize: 10 });
|
||||||
Object.assign(props.columns, { getInstance: () => getCurrentInstance() });
|
const openModifyModal = (data: any) => modifyRef.value?.open(data.record);
|
||||||
|
|
||||||
const getPaging = (pagination: Partial<any>) => {
|
|
||||||
const { current: page, pageSize: size } = { ...props.pagination, ...pagination } as any;
|
|
||||||
return { page, size };
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadData = async (pagination: Partial<any> = {}) => {
|
const loadData = async (pagination: Partial<any> = {}) => {
|
||||||
if (!props.data) {
|
const merged = { ...props.pagination, ...pagination };
|
||||||
return;
|
const paging = { page: merged.current, size: merged.pageSize };
|
||||||
}
|
const model = searchRef.value?.getModel() ?? {};
|
||||||
if (Array.isArray(props.data)) {
|
if (Array.isArray(props.data)) {
|
||||||
if (!props.search?.model) {
|
const filters = Object.entries(model);
|
||||||
return;
|
const data = props.data.filter((item) => {
|
||||||
}
|
|
||||||
const filters = Object.entries(props.search?.model || {});
|
|
||||||
const data = props.data?.filter((item) => {
|
|
||||||
return filters.every(([key, value]) => {
|
return filters.every(([key, value]) => {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return item[key].includes(value);
|
return item[key].includes(value);
|
||||||
|
|
@ -99,59 +89,35 @@ export const Table = defineComponent({
|
||||||
return item[key] === value;
|
return item[key] === value;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
renderData.value = data || [];
|
renderData.value = data;
|
||||||
props.pagination.total = renderData.value.length;
|
props.pagination.total = renderData.value.length;
|
||||||
props.pagination.current = 1;
|
props.pagination.current = 1;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (typeof props.data !== "function") {
|
if (typeof props.data === "function") {
|
||||||
return;
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const resData = await props.data(model, paging);
|
||||||
|
const { data = [], meta = {} } = resData || {};
|
||||||
|
const { page: pageNum, total } = meta;
|
||||||
|
renderData.value = data;
|
||||||
|
props.pagination.total = total;
|
||||||
|
props.pagination.current = pageNum;
|
||||||
|
} catch (error) {
|
||||||
|
console.log("table error", error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const model = searchRef.value?.getModel() || {};
|
|
||||||
const paging = getPaging(pagination);
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
const resData = await props.data(model, paging);
|
|
||||||
const { data = [], meta = {} } = resData || {};
|
|
||||||
const { page: pageNum, total } = meta;
|
|
||||||
renderData.value = data;
|
|
||||||
Object.assign(props.pagination, { current: pageNum, total });
|
|
||||||
} catch (error) {
|
|
||||||
console.log("table error", error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const reloadData = () => {
|
|
||||||
loadData({ current: 1, pageSize: 10 });
|
|
||||||
};
|
|
||||||
|
|
||||||
const openModifyModal = (data: any) => {
|
|
||||||
modifyRef.value?.open(data.record);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPageChange = (current: number) => {
|
|
||||||
loadData({ current });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCreateOk = () => {
|
|
||||||
reloadData();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onModifyOk = () => {
|
|
||||||
reloadData();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.data,
|
() => props.data,
|
||||||
(data) => {
|
(data) => {
|
||||||
if (!Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
return;
|
renderData.value = data;
|
||||||
|
props.pagination.total = data.length;
|
||||||
|
props.pagination.current = 1;
|
||||||
}
|
}
|
||||||
renderData.value = data;
|
|
||||||
props.pagination.total = data.length;
|
|
||||||
props.pagination.current = 1;
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
|
@ -162,19 +128,20 @@ export const Table = defineComponent({
|
||||||
loadData();
|
loadData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (props.search) {
|
||||||
|
merge(props.search, { formProps: { layout: "inline" } });
|
||||||
|
}
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
loading,
|
loading,
|
||||||
|
inlined,
|
||||||
searchRef,
|
searchRef,
|
||||||
createRef,
|
createRef,
|
||||||
modifyRef,
|
modifyRef,
|
||||||
renderData,
|
renderData,
|
||||||
inlineSearch,
|
|
||||||
loadData,
|
loadData,
|
||||||
reloadData,
|
reloadData,
|
||||||
openModifyModal,
|
openModifyModal,
|
||||||
onPageChange,
|
|
||||||
onCreateOk,
|
|
||||||
onModifyOk,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
provide("ref:table", { ...state, ...props });
|
provide("ref:table", { ...state, ...props });
|
||||||
|
|
@ -185,52 +152,33 @@ export const Table = defineComponent({
|
||||||
(this.columns as any).instance = this;
|
(this.columns as any).instance = this;
|
||||||
return (
|
return (
|
||||||
<div class="bh-table w-full">
|
<div class="bh-table w-full">
|
||||||
{!this.inlineSearch && (
|
{!this.inlined && (
|
||||||
<div class="">
|
<div class="pb-5 border-b border-slate-200 mb-5">
|
||||||
<Form ref={(el: any) => (this.searchRef = el)} class="grid grid-cols-4 gap-x-4" {...this.search}></Form>
|
<Form ref="searchRef" class="grid grid-cols-4 gap-x-4" {...this.search}></Form>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!this.inlineSearch && <Divider class="mt-0 border-gray-200" />}
|
|
||||||
<div class={`mb-2 flex justify-between ${!this.inlineSearch && "mt-2"}`}>
|
<div class={`mb-2 flex justify-between ${!this.inlined && "mt-2"}`}>
|
||||||
<div class="flex-1 flex gap-2">
|
<div class="flex-1 flex gap-2">
|
||||||
{this.create && (
|
{this.create && <FormModal ref="createRef" onOk={this.reloadData} {...(this.create as any)}></FormModal>}
|
||||||
<FormModal
|
|
||||||
ref={(el: any) => (this.createRef = el)}
|
|
||||||
onOk={this.onCreateOk}
|
|
||||||
{...(this.create as any)}
|
|
||||||
></FormModal>
|
|
||||||
)}
|
|
||||||
{this.modify && (
|
{this.modify && (
|
||||||
<FormModal
|
<FormModal ref="modifyRef" onOk={this.reloadData} trigger={false} {...(this.modify as any)}></FormModal>
|
||||||
ref={(el: any) => (this.modifyRef = el)}
|
|
||||||
onOk={this.onModifyOk}
|
|
||||||
trigger={false}
|
|
||||||
{...(this.modify as any)}
|
|
||||||
></FormModal>
|
|
||||||
)}
|
)}
|
||||||
{this.$slots.action?.()}
|
{this.$slots.action?.()}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>{this.inlined && <Form ref="searchRef" {...this.search}></Form>}</div>
|
||||||
{this.inlineSearch && (
|
|
||||||
<Form
|
|
||||||
ref={(el: any) => (this.searchRef = el)}
|
|
||||||
{...{ ...this.search, formProps: { layout: "inline" } }}
|
|
||||||
></Form>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseTable
|
|
||||||
row-key="id"
|
|
||||||
bordered={false}
|
|
||||||
{...this.tableProps}
|
|
||||||
loading={this.loading}
|
|
||||||
pagination={this.pagination}
|
|
||||||
data={this.renderData}
|
|
||||||
columns={this.columns}
|
|
||||||
onPageChange={this.onPageChange}
|
|
||||||
></BaseTable>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<BaseTable
|
||||||
|
row-key="id"
|
||||||
|
bordered={false}
|
||||||
|
{...this.tableProps}
|
||||||
|
loading={this.loading}
|
||||||
|
pagination={this.pagination}
|
||||||
|
data={this.renderData}
|
||||||
|
columns={this.columns}
|
||||||
|
onPageChange={(current: number) => this.loadData({ current })}
|
||||||
|
></BaseTable>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,8 @@
|
||||||
import { Link, Message, Modal, TableColumnData } from "@arco-design/web-vue";
|
import { Link, Message, Modal, TableColumnData } from "@arco-design/web-vue";
|
||||||
import { defaultsDeep, isArray, isFunction, mergeWith, omit } from "lodash-es";
|
import { defaultsDeep, isArray, isFunction, mergeWith } from "lodash-es";
|
||||||
import { reactive } from "vue";
|
import { reactive } from "vue";
|
||||||
import { TableInstance } from "./table";
|
import { TableInstance } from "./table";
|
||||||
import {
|
import { config } from "./table.config";
|
||||||
TABLE_ACTION_DEFAULTS,
|
|
||||||
TABLE_COLUMN_DEFAULTS,
|
|
||||||
TABLE_DELTE_DEFAULTS,
|
|
||||||
TALBE_INDEX_DEFAULTS,
|
|
||||||
searchItem,
|
|
||||||
} from "./table.config";
|
|
||||||
import { UseTableOptions } from "./use-interface";
|
import { UseTableOptions } from "./use-interface";
|
||||||
|
|
||||||
const merge = (...args: any[]) => {
|
const merge = (...args: any[]) => {
|
||||||
|
|
@ -34,18 +28,15 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
||||||
|
|
||||||
const getTable = (): TableInstance => (columns as any).instance;
|
const getTable = (): TableInstance => (columns as any).instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格列处理
|
||||||
|
*/
|
||||||
options.columns.forEach((column) => {
|
options.columns.forEach((column) => {
|
||||||
// 序号
|
|
||||||
if (column.type === "index") {
|
if (column.type === "index") {
|
||||||
defaultsDeep(column, TALBE_INDEX_DEFAULTS);
|
defaultsDeep(column, config.columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 操作
|
|
||||||
if (column.type === "button" && isArray(column.buttons)) {
|
if (column.type === "button" && isArray(column.buttons)) {
|
||||||
if (options.detail) {
|
|
||||||
column.buttons.unshift({ text: "详情", onClick: (data) => {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.modify) {
|
if (options.modify) {
|
||||||
const modifyAction = column.buttons.find((i) => i.type === "modify");
|
const modifyAction = column.buttons.find((i) => i.type === "modify");
|
||||||
if (modifyAction) {
|
if (modifyAction) {
|
||||||
|
|
@ -68,11 +59,10 @@ 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 = (data) => {
|
||||||
Modal.warning({
|
Modal.warning({
|
||||||
...TABLE_DELTE_DEFAULTS,
|
...config.columnButtonDelete,
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
const resData: any = await action?.onClick?.(data);
|
const resData: any = await action?.onClick?.(data);
|
||||||
resData.msg && Message.success(resData?.msg || "");
|
resData.msg && Message.success(resData?.msg || "");
|
||||||
|
|
@ -81,27 +71,26 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return { ...config.columnButtonBase, ...action, onClick } as any;
|
||||||
return { ...TABLE_ACTION_DEFAULTS, ...action, onClick } as any;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
column.render = (columnData) =>
|
column.render = (columnData) => {
|
||||||
column.buttons?.map((action) => {
|
return column.buttons?.map((btn) => {
|
||||||
const onClick = () => action.onClick?.(columnData);
|
const onClick = () => btn.onClick?.(columnData);
|
||||||
const omitKeys = ["text", "render", "api", "action", "onClick", "disabled"];
|
const disabled = () => btn.disabled?.(columnData);
|
||||||
const disabled = () => action.disabled?.(columnData);
|
if (btn.visible && !btn.visible(columnData)) {
|
||||||
if (action.visible && !action.visible(columnData)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Link onClick={onClick} disabled={disabled()} {...omit(action as any, omitKeys)}>
|
<Link onClick={onClick} disabled={disabled()} {...btn.buttonProps}>
|
||||||
{action.text}
|
{btn.text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
columns.push({ ...TABLE_COLUMN_DEFAULTS, ...column });
|
columns.push({ ...config.columnBase, ...column });
|
||||||
});
|
});
|
||||||
|
|
||||||
const itemsMap = options.common?.items?.reduce((map, item) => {
|
const itemsMap = options.common?.items?.reduce((map, item) => {
|
||||||
|
|
@ -114,28 +103,34 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
||||||
*/
|
*/
|
||||||
if (options.search && options.search.items) {
|
if (options.search && options.search.items) {
|
||||||
const searchItems: any[] = [];
|
const searchItems: any[] = [];
|
||||||
options.search.items.forEach((item) => {
|
for (const item of options.search.items) {
|
||||||
if (typeof item === "string") {
|
if (typeof item === "string") {
|
||||||
if (!itemsMap[item]) {
|
if (!itemsMap[item]) {
|
||||||
throw new Error(`search item ${item} not found in common items`);
|
throw new Error(`search item ${item} not found in common items`);
|
||||||
}
|
}
|
||||||
searchItems.push(itemsMap[item]);
|
searchItems.push(itemsMap[item]);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
if ("extend" in item && item.extend && itemsMap[item.extend]) {
|
if ("extend" in item && item.extend && itemsMap[item.extend]) {
|
||||||
searchItems.push(merge({}, itemsMap[item.extend], item));
|
searchItems.push(merge({}, itemsMap[item.extend], item));
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
searchItems.push(item);
|
searchItems.push(item);
|
||||||
});
|
}
|
||||||
searchItems.push(searchItem);
|
searchItems.push(config.searchItemSubmit);
|
||||||
options.search.items = searchItems;
|
options.search.items = searchItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增表单处理
|
||||||
|
*/
|
||||||
if (options.create && propTruly(options.create, "extend")) {
|
if (options.create && propTruly(options.create, "extend")) {
|
||||||
options.create = merge(options.common, options.create);
|
options.create = merge(options.common, options.create);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改表单处理
|
||||||
|
*/
|
||||||
if (options.modify && propTruly(options.modify, "extend")) {
|
if (options.modify && propTruly(options.modify, "extend")) {
|
||||||
options.modify = merge(options.common, options.modify);
|
options.modify = merge(options.common, options.modify);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { ContentType, api } from "@/api";
|
|
||||||
import { Table, useTable } from "@/components";
|
import { Table, useTable } from "@/components";
|
||||||
|
|
||||||
const table = useTable({
|
const table = useTable({
|
||||||
data: async (model, paging) => ({ data: [{}], meta: { total: 0 } }),
|
data: async (model, paging) => ({ data: [{}], meta: { total: 0 } }),
|
||||||
columns: [
|
columns: [
|
||||||
|
{
|
||||||
|
type: "index",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "姓名",
|
title: "姓名",
|
||||||
dataIndex: "username",
|
dataIndex: "username",
|
||||||
|
|
@ -66,7 +68,13 @@ const table = useTable({
|
||||||
{
|
{
|
||||||
label: "头像",
|
label: "头像",
|
||||||
field: "avatar?avatarUrl",
|
field: "avatar?avatarUrl",
|
||||||
type: "input",
|
type: "select",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "startTime:endTime",
|
||||||
|
label: "日期范围",
|
||||||
|
type: "dateRange",
|
||||||
|
nodeProps: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
modalProps: {
|
modalProps: {
|
||||||
|
|
@ -89,16 +97,14 @@ const table = useTable({
|
||||||
create: {
|
create: {
|
||||||
title: "新建用户",
|
title: "新建用户",
|
||||||
submit: ({ model }) => {
|
submit: ({ model }) => {
|
||||||
return api.user.createUser(model as any, {
|
console.log(model);
|
||||||
type: ContentType.FormData,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modify: {
|
modify: {
|
||||||
extend: true,
|
extend: true,
|
||||||
title: "修改用户",
|
title: "修改用户",
|
||||||
submit: ({ model }) => {
|
submit: ({ model }) => {
|
||||||
return api.user.updateUser(model.id, model);
|
console.log(model);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import "uno.css";
|
import "uno.css";
|
||||||
import { Plugin } from "vue";
|
import { Plugin } from "vue";
|
||||||
import "./arco-design.less";
|
import "./css-arco.less";
|
||||||
import "./style.less";
|
import "./css-base.less";
|
||||||
import "./transition.less";
|
import "./css-transition.less";
|
||||||
import "./uno.less";
|
import "./css-unocss.less";
|
||||||
|
|
||||||
export const style: Plugin = {
|
export const style: Plugin = {
|
||||||
install(app) {},
|
install(app) {},
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export default defineConfig(({ mode }) => {
|
||||||
less: {
|
less: {
|
||||||
javascriptEnabled: true,
|
javascriptEnabled: true,
|
||||||
modifyVars: {
|
modifyVars: {
|
||||||
hack: `true; @import (reference) "${resolve("src/style/arco-design.less")}";`,
|
hack: `true; @import (reference) "${resolve("src/style/css-arco.less")}";`,
|
||||||
arcoblue: "#66f",
|
arcoblue: "#66f",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue