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
|
||||
|
||||
# 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
|
||||
|
||||
# 端口号(开发环境)
|
||||
|
|
|
|||
|
|
@ -1,52 +1,8 @@
|
|||
import { FormItem as BaseFormItem, FieldRule, FormItemInstance, SelectOptionData } from "@arco-design/web-vue";
|
||||
import { NodeType, NodeUnion, nodeMap } from "./form-node";
|
||||
import { RuleMap } from "./form-rules";
|
||||
|
||||
const defineRuleMap = <T extends Record<string, FieldRule>>(ruleMap: T) => 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 FieldStringRule = keyof typeof RuleMap;
|
||||
|
||||
export type FieldObjectRule = FieldRule & {
|
||||
disable?: (arg: { item: IFormItem; model: Record<string, any> }) => boolean;
|
||||
|
|
@ -67,11 +23,11 @@ export const FormItem = (props: any, { emit }: any) => {
|
|||
const rules = computed(() => {
|
||||
const result = [];
|
||||
if (item.required) {
|
||||
result.push(ruleMap.required);
|
||||
result.push(RuleMap.required);
|
||||
}
|
||||
item.rules?.forEach((rule: any) => {
|
||||
if (typeof rule === "string") {
|
||||
result.push(ruleMap[rule as FieldStringRule]);
|
||||
result.push(RuleMap[rule as FieldStringRule]);
|
||||
return;
|
||||
}
|
||||
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
|
||||
* ```typescript
|
||||
* rules: [
|
||||
* 'idcard', // 内置的身份证号校验规则
|
||||
* // 内置
|
||||
* 'idcard',
|
||||
* // 自定义
|
||||
* {
|
||||
* match: /\d+/,
|
||||
* message: '请输入数字',
|
||||
* },
|
||||
* ]
|
||||
*```
|
||||
* @see https://arco.design/vue/component/form#FieldRule
|
||||
*```
|
||||
* @see https://arco.design/vue/component/form#FieldRule
|
||||
*/
|
||||
rules?: FieldRuleType[];
|
||||
|
||||
|
|
|
|||
|
|
@ -17,29 +17,27 @@ import {
|
|||
const initOptions = ({ item, model }: any) => {
|
||||
if (Array.isArray(item.options)) {
|
||||
item.nodeProps.options = item.options;
|
||||
return;
|
||||
}
|
||||
if (typeof item.options !== "function") {
|
||||
return;
|
||||
if (typeof item.options === "function") {
|
||||
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) => {
|
||||
return map
|
||||
}
|
||||
const defineNodeMap = <T extends { [key: string]: { component: any; nodeProps: any } }>(map: T) => {
|
||||
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 { 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: {
|
||||
type: Function as PropType<(arg: { model: Record<string, any>; items: IFormItem[] }) => Promise<any>>,
|
||||
type: Function as PropType<SubmitFn>,
|
||||
},
|
||||
/**
|
||||
* 传给Form组件的参数
|
||||
|
|
@ -68,6 +70,21 @@ export const Form = defineComponent({
|
|||
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 () => {
|
||||
if (await formRef.value?.validate()) {
|
||||
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 {
|
||||
formRef,
|
||||
loading,
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ export const useForm = (options: Options) => {
|
|||
}
|
||||
if (/(.+)\?(.+)/.test(item.field)) {
|
||||
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);
|
||||
for (const [key, value] of params.entries()) {
|
||||
model[key] = value;
|
||||
}
|
||||
}
|
||||
model[item.field] = model[item.field] ?? item.initialValue;
|
||||
model[item.field] = model[item.field] ?? item.initial;
|
||||
const _item = { ...item };
|
||||
items.push(_item);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,71 +1,62 @@
|
|||
import { Button } from "@arco-design/web-vue";
|
||||
import { IconRefresh, IconSearch } from "@arco-design/web-vue/es/icon";
|
||||
|
||||
/**
|
||||
* 搜索表单默认参数
|
||||
*/
|
||||
export const TABLE_SEARCH_DEFAULTS = {
|
||||
labelAlign: "left",
|
||||
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",
|
||||
export const config = {
|
||||
searchFormProps: {
|
||||
labelAlign: "left",
|
||||
autoLabelWidth: true,
|
||||
model: {},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除弹窗默认参数
|
||||
*/
|
||||
export const TABLE_DELTE_DEFAULTS = {
|
||||
title: "删除确认",
|
||||
content: "确认删除当前数据吗?",
|
||||
modalClass: "text-center",
|
||||
hideCancel: false,
|
||||
maskClosable: false,
|
||||
};
|
||||
|
||||
export const TALBE_INDEX_DEFAULTS = {
|
||||
title: "#",
|
||||
width: 60,
|
||||
align: "center",
|
||||
render: ({ rowIndex }: any) => rowIndex + 1,
|
||||
};
|
||||
|
||||
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: () => "重置" }}
|
||||
searchItemSubmit: {
|
||||
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 type="primary" loading={tableRef?.loading.value} onClick={() => tableRef?.loadData()}>
|
||||
{{ icon: () => <IconSearch></IconSearch>, default: () => "查询" }}
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" loading={tableRef?.loading.value} onClick={() => tableRef?.loadData()}>
|
||||
{{ icon: () => <IconSearch></IconSearch>, default: () => "查询" }}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
</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 {
|
||||
TableColumnData as BaseColumn,
|
||||
TableData as BaseData,
|
||||
Table as BaseTable,
|
||||
Divider,
|
||||
} from "@arco-design/web-vue";
|
||||
import { TableColumnData as BaseColumn, TableData as BaseData, Table as BaseTable } from "@arco-design/web-vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { PropType, computed, defineComponent, reactive, ref, watch } from "vue";
|
||||
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: {
|
||||
type: [Array, Function] as PropType<
|
||||
BaseData[] | ((search: Record<string, any>, paging: { page: number; size: number }) => Promise<any>)
|
||||
>,
|
||||
type: [Array, Function] as PropType<Data>,
|
||||
},
|
||||
/**
|
||||
* 表格列设置
|
||||
|
|
@ -34,7 +32,7 @@ export const Table = defineComponent({
|
|||
*/
|
||||
pagination: {
|
||||
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 modifyRef = ref<FormModalInstance>();
|
||||
const renderData = ref<BaseData[]>([]);
|
||||
const inlineSearch = computed(() => (props.search?.items?.length || 0) < 4);
|
||||
|
||||
Object.assign(props.columns, { getInstance: () => getCurrentInstance() });
|
||||
|
||||
const getPaging = (pagination: Partial<any>) => {
|
||||
const { current: page, pageSize: size } = { ...props.pagination, ...pagination } as any;
|
||||
return { page, size };
|
||||
};
|
||||
const inlined = computed(() => (props.search?.items?.length ?? 0) < 4);
|
||||
const reloadData = () => loadData({ current: 1, pageSize: 10 });
|
||||
const openModifyModal = (data: any) => modifyRef.value?.open(data.record);
|
||||
|
||||
const loadData = async (pagination: Partial<any> = {}) => {
|
||||
if (!props.data) {
|
||||
return;
|
||||
}
|
||||
const merged = { ...props.pagination, ...pagination };
|
||||
const paging = { page: merged.current, size: merged.pageSize };
|
||||
const model = searchRef.value?.getModel() ?? {};
|
||||
if (Array.isArray(props.data)) {
|
||||
if (!props.search?.model) {
|
||||
return;
|
||||
}
|
||||
const filters = Object.entries(props.search?.model || {});
|
||||
const data = props.data?.filter((item) => {
|
||||
const filters = Object.entries(model);
|
||||
const data = props.data.filter((item) => {
|
||||
return filters.every(([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
return item[key].includes(value);
|
||||
|
|
@ -99,59 +89,35 @@ export const Table = defineComponent({
|
|||
return item[key] === value;
|
||||
});
|
||||
});
|
||||
renderData.value = data || [];
|
||||
renderData.value = data;
|
||||
props.pagination.total = renderData.value.length;
|
||||
props.pagination.current = 1;
|
||||
return;
|
||||
}
|
||||
if (typeof props.data !== "function") {
|
||||
return;
|
||||
if (typeof props.data === "function") {
|
||||
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(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if (!Array.isArray(data)) {
|
||||
return;
|
||||
if (Array.isArray(data)) {
|
||||
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,
|
||||
|
|
@ -162,19 +128,20 @@ export const Table = defineComponent({
|
|||
loadData();
|
||||
});
|
||||
|
||||
if (props.search) {
|
||||
merge(props.search, { formProps: { layout: "inline" } });
|
||||
}
|
||||
|
||||
const state = {
|
||||
loading,
|
||||
inlined,
|
||||
searchRef,
|
||||
createRef,
|
||||
modifyRef,
|
||||
renderData,
|
||||
inlineSearch,
|
||||
loadData,
|
||||
reloadData,
|
||||
openModifyModal,
|
||||
onPageChange,
|
||||
onCreateOk,
|
||||
onModifyOk,
|
||||
};
|
||||
|
||||
provide("ref:table", { ...state, ...props });
|
||||
|
|
@ -185,52 +152,33 @@ export const Table = defineComponent({
|
|||
(this.columns as any).instance = this;
|
||||
return (
|
||||
<div class="bh-table w-full">
|
||||
{!this.inlineSearch && (
|
||||
<div class="">
|
||||
<Form ref={(el: any) => (this.searchRef = el)} class="grid grid-cols-4 gap-x-4" {...this.search}></Form>
|
||||
{!this.inlined && (
|
||||
<div class="pb-5 border-b border-slate-200 mb-5">
|
||||
<Form ref="searchRef" class="grid grid-cols-4 gap-x-4" {...this.search}></Form>
|
||||
</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">
|
||||
{this.create && (
|
||||
<FormModal
|
||||
ref={(el: any) => (this.createRef = el)}
|
||||
onOk={this.onCreateOk}
|
||||
{...(this.create as any)}
|
||||
></FormModal>
|
||||
)}
|
||||
{this.create && <FormModal ref="createRef" onOk={this.reloadData} {...(this.create as any)}></FormModal>}
|
||||
{this.modify && (
|
||||
<FormModal
|
||||
ref={(el: any) => (this.modifyRef = el)}
|
||||
onOk={this.onModifyOk}
|
||||
trigger={false}
|
||||
{...(this.modify as any)}
|
||||
></FormModal>
|
||||
<FormModal ref="modifyRef" onOk={this.reloadData} trigger={false} {...(this.modify as any)}></FormModal>
|
||||
)}
|
||||
{this.$slots.action?.()}
|
||||
</div>
|
||||
<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>{this.inlined && <Form ref="searchRef" {...this.search}></Form>}</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>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
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 { TableInstance } from "./table";
|
||||
import {
|
||||
TABLE_ACTION_DEFAULTS,
|
||||
TABLE_COLUMN_DEFAULTS,
|
||||
TABLE_DELTE_DEFAULTS,
|
||||
TALBE_INDEX_DEFAULTS,
|
||||
searchItem,
|
||||
} from "./table.config";
|
||||
import { config } from "./table.config";
|
||||
import { UseTableOptions } from "./use-interface";
|
||||
|
||||
const merge = (...args: any[]) => {
|
||||
|
|
@ -34,18 +28,15 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
|||
|
||||
const getTable = (): TableInstance => (columns as any).instance;
|
||||
|
||||
/**
|
||||
* 表格列处理
|
||||
*/
|
||||
options.columns.forEach((column) => {
|
||||
// 序号
|
||||
if (column.type === "index") {
|
||||
defaultsDeep(column, TALBE_INDEX_DEFAULTS);
|
||||
defaultsDeep(column, config.columnIndex);
|
||||
}
|
||||
|
||||
// 操作
|
||||
if (column.type === "button" && isArray(column.buttons)) {
|
||||
if (options.detail) {
|
||||
column.buttons.unshift({ text: "详情", onClick: (data) => {} });
|
||||
}
|
||||
|
||||
if (options.modify) {
|
||||
const modifyAction = column.buttons.find((i) => i.type === "modify");
|
||||
if (modifyAction) {
|
||||
|
|
@ -68,11 +59,10 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
|||
|
||||
column.buttons = column.buttons?.map((action) => {
|
||||
let onClick = action?.onClick;
|
||||
|
||||
if (action.type === "delete") {
|
||||
onClick = (data) => {
|
||||
Modal.warning({
|
||||
...TABLE_DELTE_DEFAULTS,
|
||||
...config.columnButtonDelete,
|
||||
onOk: async () => {
|
||||
const resData: any = await action?.onClick?.(data);
|
||||
resData.msg && Message.success(resData?.msg || "");
|
||||
|
|
@ -81,27 +71,26 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
return { ...TABLE_ACTION_DEFAULTS, ...action, onClick } as any;
|
||||
return { ...config.columnButtonBase, ...action, onClick } as any;
|
||||
});
|
||||
|
||||
column.render = (columnData) =>
|
||||
column.buttons?.map((action) => {
|
||||
const onClick = () => action.onClick?.(columnData);
|
||||
const omitKeys = ["text", "render", "api", "action", "onClick", "disabled"];
|
||||
const disabled = () => action.disabled?.(columnData);
|
||||
if (action.visible && !action.visible(columnData)) {
|
||||
column.render = (columnData) => {
|
||||
return column.buttons?.map((btn) => {
|
||||
const onClick = () => btn.onClick?.(columnData);
|
||||
const disabled = () => btn.disabled?.(columnData);
|
||||
if (btn.visible && !btn.visible(columnData)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Link onClick={onClick} disabled={disabled()} {...omit(action as any, omitKeys)}>
|
||||
{action.text}
|
||||
<Link onClick={onClick} disabled={disabled()} {...btn.buttonProps}>
|
||||
{btn.text}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
columns.push({ ...TABLE_COLUMN_DEFAULTS, ...column });
|
||||
columns.push({ ...config.columnBase, ...column });
|
||||
});
|
||||
|
||||
const itemsMap = options.common?.items?.reduce((map, item) => {
|
||||
|
|
@ -114,28 +103,34 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
|||
*/
|
||||
if (options.search && options.search.items) {
|
||||
const searchItems: any[] = [];
|
||||
options.search.items.forEach((item) => {
|
||||
for (const item of options.search.items) {
|
||||
if (typeof item === "string") {
|
||||
if (!itemsMap[item]) {
|
||||
throw new Error(`search item ${item} not found in common items`);
|
||||
}
|
||||
searchItems.push(itemsMap[item]);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
if ("extend" in item && item.extend && itemsMap[item.extend]) {
|
||||
searchItems.push(merge({}, itemsMap[item.extend], item));
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
searchItems.push(item);
|
||||
});
|
||||
searchItems.push(searchItem);
|
||||
}
|
||||
searchItems.push(config.searchItemSubmit);
|
||||
options.search.items = searchItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增表单处理
|
||||
*/
|
||||
if (options.create && propTruly(options.create, "extend")) {
|
||||
options.create = merge(options.common, options.create);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改表单处理
|
||||
*/
|
||||
if (options.modify && propTruly(options.modify, "extend")) {
|
||||
options.modify = merge(options.common, options.modify);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { ContentType, api } from "@/api";
|
||||
import { Table, useTable } from "@/components";
|
||||
|
||||
const table = useTable({
|
||||
data: async (model, paging) => ({ data: [{}], meta: { total: 0 } }),
|
||||
columns: [
|
||||
{
|
||||
type: "index",
|
||||
},
|
||||
{
|
||||
title: "姓名",
|
||||
dataIndex: "username",
|
||||
|
|
@ -66,7 +68,13 @@ const table = useTable({
|
|||
{
|
||||
label: "头像",
|
||||
field: "avatar?avatarUrl",
|
||||
type: "input",
|
||||
type: "select",
|
||||
},
|
||||
{
|
||||
field: "startTime:endTime",
|
||||
label: "日期范围",
|
||||
type: "dateRange",
|
||||
nodeProps: {},
|
||||
},
|
||||
],
|
||||
modalProps: {
|
||||
|
|
@ -89,16 +97,14 @@ const table = useTable({
|
|||
create: {
|
||||
title: "新建用户",
|
||||
submit: ({ model }) => {
|
||||
return api.user.createUser(model as any, {
|
||||
type: ContentType.FormData,
|
||||
});
|
||||
console.log(model);
|
||||
},
|
||||
},
|
||||
modify: {
|
||||
extend: true,
|
||||
title: "修改用户",
|
||||
submit: ({ model }) => {
|
||||
return api.user.updateUser(model.id, model);
|
||||
console.log(model);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import "uno.css";
|
||||
import { Plugin } from "vue";
|
||||
import "./arco-design.less";
|
||||
import "./style.less";
|
||||
import "./transition.less";
|
||||
import "./uno.less";
|
||||
import "./css-arco.less";
|
||||
import "./css-base.less";
|
||||
import "./css-transition.less";
|
||||
import "./css-unocss.less";
|
||||
|
||||
export const style: Plugin = {
|
||||
install(app) {},
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export default defineConfig(({ mode }) => {
|
|||
less: {
|
||||
javascriptEnabled: true,
|
||||
modifyVars: {
|
||||
hack: `true; @import (reference) "${resolve("src/style/arco-design.less")}";`,
|
||||
hack: `true; @import (reference) "${resolve("src/style/css-arco.less")}";`,
|
||||
arcoblue: "#66f",
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue