feat: 优化表格组件
parent
2a27f67b85
commit
fd7b437102
|
|
@ -0,0 +1,367 @@
|
||||||
|
import { AnForm, AnFormInstance, AnFormModal, AnFormModalInstance, AnFormModalProps, AnFormProps, getModel } from '@/components/AnForm';
|
||||||
|
import AnEmpty from '@/components/AnEmpty/AnEmpty.vue';
|
||||||
|
import { Button, PaginationProps, Table, TableColumnData, TableData, TableInstance } from '@arco-design/web-vue';
|
||||||
|
import { isArray, merge } from 'lodash-es';
|
||||||
|
import { InjectionKey, PropType, Ref, VNodeChild, defineComponent, ref } from 'vue';
|
||||||
|
|
||||||
|
type DataFn = (params: { page: number; size: number; [key: string]: any }) => any | Promise<TableData[] | { data: TableData[]; total: number }>;
|
||||||
|
export type ArcoTableProps = Omit<TableInstance['$props'], 'ref' | 'pagination' | 'loading' | 'data'>;
|
||||||
|
export const AnTableContextKey = Symbol('AnTableContextKey') as InjectionKey<AnTableContext>;
|
||||||
|
export type TableColumnRender = (data: { record: TableData; column: TableColumnData; rowIndex: number }) => VNodeChild;
|
||||||
|
|
||||||
|
export type ArcoTableSlots = {
|
||||||
|
/**
|
||||||
|
* 自定义 th 元素
|
||||||
|
*/
|
||||||
|
th?: (column: TableColumnData) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 thead 元素
|
||||||
|
*/
|
||||||
|
thead?: () => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空白展示
|
||||||
|
*/
|
||||||
|
empty?: () => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总结行
|
||||||
|
*/
|
||||||
|
'summary-cell'?: (column: TableColumnData, record: TableData, rowIndex: number) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页器右侧内容
|
||||||
|
*/
|
||||||
|
'pagination-right'?: () => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页器左侧内容
|
||||||
|
*/
|
||||||
|
'pagination-left'?: () => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 td 元素
|
||||||
|
*/
|
||||||
|
td?: (column: TableColumnData, record: TableData, rowIndex: number) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 tr 元素
|
||||||
|
*/
|
||||||
|
tr?: (column: TableColumnData, record: TableData, rowIndex: number) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 tbody 元素
|
||||||
|
*/
|
||||||
|
tbody?: () => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拖拽锚点图标
|
||||||
|
*/
|
||||||
|
'drag-handle-icon'?: () => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格底部
|
||||||
|
*/
|
||||||
|
footer?: () => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展开行内容
|
||||||
|
*/
|
||||||
|
'expand-row'?: () => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展开行图标
|
||||||
|
*/
|
||||||
|
'expand-icon'?: () => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格列定义。启用时会屏蔽 columns 属性
|
||||||
|
*/
|
||||||
|
columns?: () => any;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格组件
|
||||||
|
*/
|
||||||
|
export const AnTable = defineComponent({
|
||||||
|
name: 'AnTable',
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* 表格数据
|
||||||
|
* @description 数组或函数,函数请返回数组或 `{ data, total }` 对象
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* async data(params) {
|
||||||
|
* const res = await api.xxx(params);
|
||||||
|
* const { data, total } = res;
|
||||||
|
* return { data, total }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
data: {
|
||||||
|
type: [Array, Function] as PropType<TableData[] | DataFn>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 表格列
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* title: "名字",
|
||||||
|
* dataIndex: "name"
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
columns: {
|
||||||
|
type: Array as PropType<TableColumnData[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 分页配置
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* {
|
||||||
|
* showTotal: true
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
paging: {
|
||||||
|
type: Object as PropType<PaginationProps & { hide?: boolean }>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 搜索表单
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* label: "姓名关键字",
|
||||||
|
* setter: "input",
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
search: {
|
||||||
|
type: Object as PropType<AnFormProps>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 新建弹窗
|
||||||
|
*/
|
||||||
|
create: {
|
||||||
|
type: Object as PropType<AnFormModalProps>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 修改弹窗
|
||||||
|
*/
|
||||||
|
modify: {
|
||||||
|
type: Object as PropType<AnFormModalProps>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 操作按钮
|
||||||
|
*/
|
||||||
|
actions: {
|
||||||
|
type: Array as PropType<any[]>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 部件
|
||||||
|
*/
|
||||||
|
widgets: {
|
||||||
|
type: Array as PropType<any[]>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传递给 Table 组件的属性
|
||||||
|
*/
|
||||||
|
tableProps: {
|
||||||
|
type: Object as PropType<ArcoTableProps>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传递给 Table 组件的插槽
|
||||||
|
*/
|
||||||
|
tableSlots: {
|
||||||
|
type: Object as PropType<ArcoTableSlots>,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const loading = ref(false);
|
||||||
|
const renderData = ref<TableData[]>([]);
|
||||||
|
const tableRef = ref<TableInstance | null>(null);
|
||||||
|
const searchRef = ref<AnFormInstance | null>(null);
|
||||||
|
const createRef = ref<AnFormModalInstance | null>(null);
|
||||||
|
const modifyRef = ref<AnFormModalInstance | null>(null);
|
||||||
|
const selected = ref<TableData[]>([]);
|
||||||
|
const selectedKeys = computed(() => selected.value.map(i => i[props.tableProps?.rowKey ?? 'id']));
|
||||||
|
|
||||||
|
const setPaging = (paging: PaginationProps) => {
|
||||||
|
if (props.paging) {
|
||||||
|
merge(props.paging, paging);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetPaging = () => {
|
||||||
|
setPaging({ current: 1, pageSize: 10 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
if (!props.data || Array.isArray(props.data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await searchRef.value?.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = props.paging?.current ?? 1;
|
||||||
|
const size = props.paging?.pageSize ?? 10;
|
||||||
|
const search = getModel(props.search?.model ?? {});
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = { ...search, page, size };
|
||||||
|
const resData = await props.data(params);
|
||||||
|
if (resData) {
|
||||||
|
let data: TableData[] = [];
|
||||||
|
let total = 0;
|
||||||
|
if (isArray(resData)) {
|
||||||
|
data = resData;
|
||||||
|
total = resData.length;
|
||||||
|
} else {
|
||||||
|
data = resData.data ?? [];
|
||||||
|
total = resData.total ?? 0;
|
||||||
|
}
|
||||||
|
renderData.value = data;
|
||||||
|
props.paging?.showTotal && (props.paging.total = total);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('AnTable load fail: ', e);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const load = (page?: number, size?: number) => {
|
||||||
|
if (props.paging) {
|
||||||
|
page && (props.paging.current = page);
|
||||||
|
size && (props.paging.pageSize = size);
|
||||||
|
}
|
||||||
|
loadData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const reload = () => load(1, 10);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (Array.isArray(props.data)) {
|
||||||
|
renderData.value = props.data;
|
||||||
|
resetPaging();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
renderData,
|
||||||
|
selected,
|
||||||
|
selectedKeys,
|
||||||
|
tableRef,
|
||||||
|
searchRef,
|
||||||
|
createRef,
|
||||||
|
modifyRef,
|
||||||
|
load,
|
||||||
|
reload,
|
||||||
|
refresh: loadData,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="an-table table w-full">
|
||||||
|
{(this.create || this.actions || this.search || this.widgets) && (
|
||||||
|
<div class={`mb-3 flex gap-2 toolbar justify-between`}>
|
||||||
|
{this.create && <AnFormModal {...this.create} ref="createRef" onSubmited={this.reload}></AnFormModal>}
|
||||||
|
{this.actions && <div class={`flex-1 flex gap-2 items-center`}>{this.actions.map(action => action())}</div>}
|
||||||
|
{this.search && (
|
||||||
|
<div>
|
||||||
|
<AnForm ref="searchRef" v-model:model={this.search.model} items={this.search.items} formProps={this.search.formProps}></AnForm>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{this.widgets && <div class="flex gap-2">{this.widgets.map(widget => widget())}</div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Table
|
||||||
|
row-key="id"
|
||||||
|
bordered={false}
|
||||||
|
{...this.tableProps}
|
||||||
|
ref="tableRef"
|
||||||
|
loading={this.loading}
|
||||||
|
pagination={this.paging?.hide ? false : this.paging}
|
||||||
|
data={this.renderData}
|
||||||
|
columns={this.columns}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
empty: () => <AnEmpty />,
|
||||||
|
...this.tableSlots,
|
||||||
|
}}
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
{this.modify && <AnFormModal {...this.modify} trigger={false} ref="modifyRef" onSubmited={this.refresh}></AnFormModal>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格组件实例
|
||||||
|
*/
|
||||||
|
export type AnTableInstance = InstanceType<typeof AnTable>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格组件参数
|
||||||
|
*/
|
||||||
|
export type AnTableProps = Pick<AnTableInstance['$props'], 'data' | 'columns' | 'search' | 'paging' | 'create' | 'modify' | 'tableProps' | 'tableSlots' | 'actions' | 'widgets'>;
|
||||||
|
|
||||||
|
export interface AnTableContext {
|
||||||
|
/**
|
||||||
|
* 是否加载中
|
||||||
|
*/
|
||||||
|
loading: Ref<boolean>;
|
||||||
|
/**
|
||||||
|
* 表格实例
|
||||||
|
*/
|
||||||
|
tableRef: Ref<TableInstance | null>;
|
||||||
|
/**
|
||||||
|
* 搜索表单实例
|
||||||
|
*/
|
||||||
|
searchRef: Ref<AnFormInstance | null>;
|
||||||
|
/**
|
||||||
|
* 新增弹窗实例
|
||||||
|
*/
|
||||||
|
createRef: Ref<AnFormModalInstance | null>;
|
||||||
|
/**
|
||||||
|
* 修改弹窗实例
|
||||||
|
*/
|
||||||
|
modifyRef: Ref<AnFormModalInstance | null>;
|
||||||
|
/**
|
||||||
|
* 当前表格数据
|
||||||
|
*/
|
||||||
|
renderData: Ref<TableData[]>;
|
||||||
|
/**
|
||||||
|
* 加载数据
|
||||||
|
*/
|
||||||
|
loadData: () => Promise<void>;
|
||||||
|
/**
|
||||||
|
* 重置加载
|
||||||
|
*/
|
||||||
|
reload: () => Promise<void>;
|
||||||
|
/**
|
||||||
|
* 重新加载
|
||||||
|
*/
|
||||||
|
refresh: () => Promise<void>;
|
||||||
|
/**
|
||||||
|
* 原表格参数
|
||||||
|
*/
|
||||||
|
props: AnTableProps;
|
||||||
|
onPageChange: any;
|
||||||
|
onPageSizeChange: any;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './Table';
|
||||||
|
export * from './useTable';
|
||||||
|
export * from './useTableColumns';
|
||||||
|
export * from './useSearchForm';
|
||||||
|
export * from './useModiyForm';
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { FormModalUseOptions, useFormModalProps } from '@/components/AnForm';
|
||||||
|
|
||||||
|
export type UseCreateFormOptions = FormModalUseOptions & {};
|
||||||
|
|
||||||
|
export function useCreateForm(options: UseCreateFormOptions) {
|
||||||
|
if (options.width) {
|
||||||
|
if (!options.modalProps) {
|
||||||
|
(options as any).modalProps = {};
|
||||||
|
}
|
||||||
|
(options.modalProps as any).width = options.width;
|
||||||
|
delete options.width;
|
||||||
|
}
|
||||||
|
if (options.formClass) {
|
||||||
|
if (!options.formProps) {
|
||||||
|
(options as any).formProps = {};
|
||||||
|
}
|
||||||
|
options.formProps!.class = options.formClass;
|
||||||
|
delete options.formClass;
|
||||||
|
}
|
||||||
|
return useFormModalProps(options);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { FormItem, FormModalUseOptions, useFormModalProps, AnFormModalProps } from '@/components/AnForm';
|
||||||
|
import { cloneDeep, merge } from 'lodash-es';
|
||||||
|
import { ExtendFormItem } from './useSearchForm';
|
||||||
|
import { TableUseOptions } from './useTable';
|
||||||
|
import { AnTableInstance } from './Table';
|
||||||
|
|
||||||
|
export type ModifyForm = Omit<FormModalUseOptions, 'items' | 'trigger'> & {
|
||||||
|
/**
|
||||||
|
* 是否继承新建弹窗
|
||||||
|
* @default
|
||||||
|
* ```ts
|
||||||
|
* false
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
extend?: boolean;
|
||||||
|
/**
|
||||||
|
* 表单项
|
||||||
|
* ```tsx
|
||||||
|
* [{
|
||||||
|
* extend: 'name', // 从 create.items 中继承
|
||||||
|
* }]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
items?: ExtendFormItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useModifyForm(options: TableUseOptions, createModel: Recordable, tableRef: Ref<AnTableInstance | null>): AnFormModalProps | undefined {
|
||||||
|
const { create, modify, columns } = options;
|
||||||
|
|
||||||
|
if (!modify) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const column of columns ?? []) {
|
||||||
|
if(column.type === 'button') {
|
||||||
|
const btn = column.buttons.find(i => i.type === 'modify')
|
||||||
|
if(!btn) {
|
||||||
|
column.buttons.unshift({
|
||||||
|
text: '修改',
|
||||||
|
type: 'modify',
|
||||||
|
onClick({ record }) {
|
||||||
|
tableRef.value?.modifyRef?.open(record);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: FormModalUseOptions = { items: [], model: cloneDeep(createModel) };
|
||||||
|
if (modify.extend && create) {
|
||||||
|
result = merge({}, create);
|
||||||
|
}
|
||||||
|
result = merge(result, modify);
|
||||||
|
|
||||||
|
if (modify.items) {
|
||||||
|
const items: FormItem[] = [];
|
||||||
|
const createItemMap: Record<string, FormItem> = {};
|
||||||
|
for (const item of create?.items ?? []) {
|
||||||
|
createItemMap[item.field] = item;
|
||||||
|
}
|
||||||
|
for (let item of modify.items ?? []) {
|
||||||
|
if (item.extend) {
|
||||||
|
item = merge({}, createItemMap[item.field!] ?? {}, item);
|
||||||
|
}
|
||||||
|
items.push(item as any);
|
||||||
|
}
|
||||||
|
if (items.length) {
|
||||||
|
result.items = items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modify.width || create?.width) {
|
||||||
|
if (!result.modalProps) {
|
||||||
|
(result as any).modalProps = {};
|
||||||
|
}
|
||||||
|
(result.modalProps as any).width = modify.width || create?.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modify.formClass || create?.formClass) {
|
||||||
|
if (!result.formProps) {
|
||||||
|
(result as any).formProps = {};
|
||||||
|
}
|
||||||
|
result.formProps!.class = modify.formClass || create?.formClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
return useFormModalProps(result);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { defaultsDeep, isArray, merge } from 'lodash-es';
|
||||||
|
import { AnFormProps, FormUseOptions, AnFormItemProps, FormItem, useFormItems } from '@/components/AnForm';
|
||||||
|
import { AnTableInstance, AnTableProps } from './Table';
|
||||||
|
|
||||||
|
export type ExtendFormItem = Partial<
|
||||||
|
FormItem & {
|
||||||
|
/**
|
||||||
|
* 从新建弹窗继承表单项
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* 'name'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
extend: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SearchFormItem = ExtendFormItem & {
|
||||||
|
/**
|
||||||
|
* 是否点击图标后进行搜索
|
||||||
|
* @description 仅 setter: 'search' 类型可用
|
||||||
|
* @default
|
||||||
|
* ```ts
|
||||||
|
* false
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
searchable?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否回车后进行查询
|
||||||
|
* @default
|
||||||
|
* ```ts
|
||||||
|
* false
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
enterable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SearchForm = Omit<FormUseOptions, 'items' | 'submit'> & {
|
||||||
|
/**
|
||||||
|
* 搜索表单项
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* [{
|
||||||
|
* extend: 'name' // 从 create.items 继承
|
||||||
|
* }]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
items?: SearchFormItem[];
|
||||||
|
/**
|
||||||
|
* 是否隐藏查询按钮
|
||||||
|
* @default
|
||||||
|
* ```tsx
|
||||||
|
* false
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
hideSearch?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useSearchForm(search: SearchForm | SearchFormItem[] | null, extendItems: AnFormItemProps[] = [], tableRef: Ref<AnTableInstance | null>): AnFormProps | undefined {
|
||||||
|
if (!search) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArray(search)) {
|
||||||
|
search = { items: search };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { items: _items = [], hideSearch, model: _model, formProps: _formProps } = search;
|
||||||
|
const extendMap = extendItems.reduce((m, v) => ((m[v.field] = v), m), {} as Record<string, AnFormItemProps>);
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
items: [] as AnFormItemProps[],
|
||||||
|
model: _model ?? {},
|
||||||
|
formProps: defaultsDeep({}, _formProps, { layout: 'inline' }),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defualts: Partial<AnFormItemProps> = {
|
||||||
|
setter: 'input',
|
||||||
|
itemProps: {
|
||||||
|
hideLabel: true,
|
||||||
|
},
|
||||||
|
setterProps: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const items: AnFormItemProps[] = [];
|
||||||
|
|
||||||
|
for (const _item of _items) {
|
||||||
|
const { searchable, enterable, field, extend, ...itemRest } = _item;
|
||||||
|
if ((field || extend) === 'submit' && hideSearch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let item: AnFormItemProps = defaultsDeep({ field }, itemRest, defualts);
|
||||||
|
if (extend) {
|
||||||
|
const extendItem = extendMap[extend];
|
||||||
|
if (extendItem) {
|
||||||
|
item = merge({}, extendItem, itemRest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.setter === 'search') {
|
||||||
|
Object.assign(item.setterProps!, {
|
||||||
|
onSearch: () => tableRef.value?.reload(),
|
||||||
|
onPressEnter: () => tableRef.value?.reload(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (item.setterProps) {
|
||||||
|
(item.setterProps as any).placeholder = (item.setterProps as any).placeholder ?? item.label;
|
||||||
|
}
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
props.items = useFormItems(items, props.model);
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
import { useFormModalProps } from '@/components/AnForm';
|
||||||
|
import { AnTable, AnTableInstance, AnTableProps } from './Table';
|
||||||
|
import { ModifyForm, useModifyForm } from './useModiyForm';
|
||||||
|
import { SearchForm, SearchFormItem, useSearchForm } from './useSearchForm';
|
||||||
|
import { TableColumn, useTableColumns } from './useTableColumns';
|
||||||
|
import { UseCreateFormOptions } from './useCreateForm';
|
||||||
|
import { Component, FunctionalComponent } from 'vue';
|
||||||
|
import { Button } from '@arco-design/web-vue';
|
||||||
|
import { isFunction } from 'lodash-es';
|
||||||
|
|
||||||
|
export interface TableUseOptions extends Pick<AnTableProps, 'data' | 'tableProps' | 'tableSlots' | 'paging' | 'actions' | 'widgets'> {
|
||||||
|
/**
|
||||||
|
* 唯一ID
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* 'UserTable'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
id?: string;
|
||||||
|
/**
|
||||||
|
* 表格列
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* [{
|
||||||
|
* dataIndex: 'title',
|
||||||
|
* title: '标题'
|
||||||
|
* }]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
columns?: TableColumn[];
|
||||||
|
/**
|
||||||
|
* 搜索表单
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* [{
|
||||||
|
* field: 'name',
|
||||||
|
* label: '用户名称',
|
||||||
|
* setter: 'input'
|
||||||
|
* }]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
search?: SearchFormItem[] | SearchForm;
|
||||||
|
/**
|
||||||
|
* 新建弹窗
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* {
|
||||||
|
* title: '添加用户',
|
||||||
|
* items: [],
|
||||||
|
* submit: (model) => {}
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
create?: UseCreateFormOptions;
|
||||||
|
/**
|
||||||
|
* 修改弹窗
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* {
|
||||||
|
* extend: true, // 基于新建弹窗扩展
|
||||||
|
* title: '修改用户',
|
||||||
|
* submit: (model) => {}
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
modify?: ModifyForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useButtons(buttons: any[], tableRef: Ref<AnTableInstance | null>) {
|
||||||
|
const result: Component[] = [];
|
||||||
|
for (const button of buttons) {
|
||||||
|
if (button.render) {
|
||||||
|
result.push(button.render);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.push(() => {
|
||||||
|
if (button.visible && !button.visible()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Button onClick={() => button.onClick?.()} disabled={button.disable?.()} {...button.buttonProps}>
|
||||||
|
{{
|
||||||
|
icon: button.icon ? () => <i class={button.icon}></i> : null,
|
||||||
|
default: button.text ? () => button.text : null,
|
||||||
|
}}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTableProps(options: TableUseOptions, tableRef: Ref<AnTableInstance | null>): AnTableProps {
|
||||||
|
const { data, tableProps = {}, tableSlots } = options;
|
||||||
|
|
||||||
|
const paging = { hide: false, showTotal: true, showPageSize: true, ...(options.paging ?? {}) };
|
||||||
|
const search = options.search && useSearchForm(options.search, [], tableRef);
|
||||||
|
const create = options.create && useFormModalProps(options.create);
|
||||||
|
const modify = options.modify && useModifyForm(options, create?.model ?? {}, tableRef);
|
||||||
|
const columns = useTableColumns(options.columns ?? [], tableRef);
|
||||||
|
const actions = options.actions && useButtons(options.actions, tableRef);
|
||||||
|
const widgets = options.widgets && useButtons(options.widgets, tableRef);
|
||||||
|
|
||||||
|
const onPageChange = tableProps.onPageChange;
|
||||||
|
const onPageSizeChange = tableProps.onPageSizeChange;
|
||||||
|
|
||||||
|
tableProps.onPageChange = (page: number) => {
|
||||||
|
onPageChange?.(page);
|
||||||
|
tableRef.value?.load(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
tableProps.onPageSizeChange = (size: number) => {
|
||||||
|
onPageSizeChange?.(size);
|
||||||
|
tableRef.value?.load(1, size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
tableProps,
|
||||||
|
tableSlots,
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
search,
|
||||||
|
paging,
|
||||||
|
create,
|
||||||
|
modify,
|
||||||
|
actions,
|
||||||
|
widgets,
|
||||||
|
};
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* @example
|
||||||
|
* ```html
|
||||||
|
* <template>
|
||||||
|
* <UserTable></UserTable>
|
||||||
|
* </template>
|
||||||
|
* <script lang="ts" setup>
|
||||||
|
* const UserTable = useTable({
|
||||||
|
* data() {
|
||||||
|
* },
|
||||||
|
* columns: []
|
||||||
|
* })
|
||||||
|
* <script>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useTable(options: TableUseOptions | ((tableRef: Ref<AnTableInstance | null>) => TableUseOptions)) {
|
||||||
|
const tableRef = ref<AnTableInstance | null>(null);
|
||||||
|
const refresh = () => tableRef.value?.refresh();
|
||||||
|
const reload = () => tableRef.value?.reload();
|
||||||
|
|
||||||
|
const option: TableUseOptions = isFunction(options) ? options(tableRef) : options;
|
||||||
|
const rawProps = useTableProps(option, tableRef);
|
||||||
|
const props = reactive(rawProps);
|
||||||
|
|
||||||
|
const component = {
|
||||||
|
name: 'AnTableWrapper',
|
||||||
|
tableRef,
|
||||||
|
refresh,
|
||||||
|
reload,
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<AnTable
|
||||||
|
ref={el => (tableRef.value = el)}
|
||||||
|
data={props.data}
|
||||||
|
columns={props.columns}
|
||||||
|
paging={props.paging}
|
||||||
|
search={props.search}
|
||||||
|
create={props.create}
|
||||||
|
modify={props.modify}
|
||||||
|
actions={props.actions}
|
||||||
|
widgets={props.widgets}
|
||||||
|
tableProps={props.tableProps}
|
||||||
|
tableSlots={props.tableSlots}
|
||||||
|
></AnTable>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
import { delConfirm, delOptions } from '@/utils';
|
||||||
|
import { Divider, Link, Message, TableColumnData } from '@arco-design/web-vue';
|
||||||
|
import { defaultsDeep } from 'lodash-es';
|
||||||
|
import { AnTableInstance } from './Table';
|
||||||
|
|
||||||
|
interface TableBaseColumn {
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* 'delete'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
type?: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableIndexColumn {
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
type: 'index';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableColumnButton {
|
||||||
|
/**
|
||||||
|
* 特殊类型
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* 'delete'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
type?: 'modify' | 'delete';
|
||||||
|
/**
|
||||||
|
* 确认弹窗配置
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* '确定删除吗?'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
confirm?: string;
|
||||||
|
/**
|
||||||
|
* 按钮文本
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* '修改'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
text?: string;
|
||||||
|
/**
|
||||||
|
* 按钮参数
|
||||||
|
* @see ALink
|
||||||
|
*/
|
||||||
|
buttonProps?: Recordable;
|
||||||
|
icon?: string;
|
||||||
|
/**
|
||||||
|
* 是否可见
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* (props) => props.record.status === 1
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
visible?: (args: Recordable) => boolean;
|
||||||
|
/**
|
||||||
|
* 是否禁用
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* (props) => props.record.status === 1
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
disable?: (args: Recordable) => boolean;
|
||||||
|
/**
|
||||||
|
* 处理函数
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* (props) => api.user.rmUser(props.record.id)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
onClick?: (props: any) => any | Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableButtonColumn {
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
type: 'button';
|
||||||
|
/**
|
||||||
|
* 按钮列表
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* [{
|
||||||
|
* type: 'delete',
|
||||||
|
* text: '删除',
|
||||||
|
* onClick: (args) => api.user.rmUser(args.record.id)
|
||||||
|
* }]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
buttons: TableColumnButton[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableDropdownColumn {
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
type: 'dropdown';
|
||||||
|
/**
|
||||||
|
* 下拉列表
|
||||||
|
*/
|
||||||
|
dropdowns: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TableColumn = TableColumnData &
|
||||||
|
(TableIndexColumn | TableBaseColumn | TableButtonColumn | TableDropdownColumn) & {
|
||||||
|
/**
|
||||||
|
* 是否可配置
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* true
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
configable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function useRowDelete(btn: TableColumnButton, tableRef: Ref<AnTableInstance | null>) {
|
||||||
|
const onClick = btn.onClick;
|
||||||
|
let confirm = btn.confirm ?? {};
|
||||||
|
if (typeof confirm === 'string') {
|
||||||
|
confirm = { content: confirm };
|
||||||
|
}
|
||||||
|
defaultsDeep(btn, {
|
||||||
|
buttonProps: {
|
||||||
|
status: 'danger',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
btn.onClick = async props => {
|
||||||
|
delConfirm({
|
||||||
|
...delOptions,
|
||||||
|
...confirm,
|
||||||
|
async onBeforeOk() {
|
||||||
|
const res: any = await onClick?.(props);
|
||||||
|
const msg = res?.data?.message;
|
||||||
|
msg && Message.success(`提示: ${msg}`);
|
||||||
|
tableRef.value?.refresh();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useRowModify(btn: TableColumnButton, tableRef: Ref<AnTableInstance | null>) {
|
||||||
|
const onClick = btn.onClick;
|
||||||
|
btn.onClick = async props => {
|
||||||
|
const data = (await onClick?.(props)) ?? props.record;
|
||||||
|
tableRef.value?.modifyRef?.open(data);
|
||||||
|
};
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTableColumns(data: TableColumn[], tableRef: Ref<AnTableInstance | null>) {
|
||||||
|
const columns: TableColumnData[] = [];
|
||||||
|
|
||||||
|
for (let column of data) {
|
||||||
|
// if (column.type === "index") {
|
||||||
|
// column = useTableIndexColumn(column);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (column.type === 'button') {
|
||||||
|
column = useTableButtonColumn(column, tableRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (column.type === "dropdown") {
|
||||||
|
// column = useTableDropdownColumn(column);
|
||||||
|
// }
|
||||||
|
|
||||||
|
columns.push(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useTableIndexColumn() {}
|
||||||
|
|
||||||
|
function useTableButtonColumn(column: TableButtonColumn & TableColumnData, tableRef: Ref<AnTableInstance | null>) {
|
||||||
|
const items: TableColumnButton[] = [];
|
||||||
|
|
||||||
|
defaultsDeep(column, { align: 'right' });
|
||||||
|
|
||||||
|
for (let button of column.buttons) {
|
||||||
|
if (button.type === 'delete') {
|
||||||
|
button = useRowDelete(button, tableRef);
|
||||||
|
}
|
||||||
|
if (button.type === 'modify') {
|
||||||
|
button = useRowModify(button, tableRef);
|
||||||
|
}
|
||||||
|
items.push(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
column.render = props => {
|
||||||
|
return items.map((item, index) => {
|
||||||
|
if (item.visible && !item.visible(props)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{index !== 0 && <Divider direction="vertical" margin={4} />}
|
||||||
|
<Link {...item.buttonProps} disabled={item.disable?.(props)} onClick={() => item.onClick?.(props)}>
|
||||||
|
{item.text}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useTableDropdownColumn() {}
|
||||||
|
|
@ -3,15 +3,21 @@ import 'nprogress/nprogress.css';
|
||||||
import './nprogress.css';
|
import './nprogress.css';
|
||||||
import { App } from 'vue';
|
import { App } from 'vue';
|
||||||
|
|
||||||
|
declare module 'nprogress' {
|
||||||
|
interface NProgress {
|
||||||
|
install: (app: App) => void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作为VUE插件进行初始化
|
||||||
|
*/
|
||||||
|
NProgress.install = function (app: App) {
|
||||||
NProgress.configure({
|
NProgress.configure({
|
||||||
showSpinner: false,
|
showSpinner: false,
|
||||||
trickleSpeed: 200,
|
trickleSpeed: 200,
|
||||||
minimum: 0.3,
|
minimum: 0.3,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
/**
|
|
||||||
* 作为VUE插件进行初始化
|
|
||||||
*/
|
|
||||||
NProgress.install = function (app: App) {};
|
|
||||||
|
|
||||||
export { NProgress };
|
export { NProgress };
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import 'nprogress';
|
|
||||||
import { App } from 'vue';
|
|
||||||
|
|
||||||
declare module 'nprogress' {
|
|
||||||
interface NProgress {
|
|
||||||
install: (app: App) => void;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,18 +2,12 @@
|
||||||
<a-layout class="layout">
|
<a-layout class="layout">
|
||||||
<a-layout-header class="h-13 overflow-hidden flex justify-between items-center gap-4 px-2 pr-4 border-b border-slate-200 bg-white dark:bg-slate-800 dark:border-slate-700">
|
<a-layout-header class="h-13 overflow-hidden flex justify-between items-center gap-4 px-2 pr-4 border-b border-slate-200 bg-white dark:bg-slate-800 dark:border-slate-700">
|
||||||
<div class="h-13 flex items-center">
|
<div class="h-13 flex items-center">
|
||||||
<!-- <a-button size="small" @click="isCollapsed = !isCollapsed">
|
|
||||||
<template #icon>
|
|
||||||
<i class="icon-park-outline-hamburger-button text-base"></i>
|
|
||||||
</template>
|
|
||||||
</a-button> -->
|
|
||||||
<router-link to="/" class="px-2 flex items-center gap-2 text-slate-700">
|
<router-link to="/" class="px-2 flex items-center gap-2 text-slate-700">
|
||||||
<img src="/favicon.ico" alt="" width="24" height="24" class="" />
|
<img src="/favicon.ico" alt="" width="24" height="24" class="" />
|
||||||
<h1 class="relative text-[18px] leading-[22px] dark:text-white m-0 p-0 font-normal">
|
<h1 class="relative text-[18px] leading-[22px] dark:text-white m-0 p-0 font-normal">
|
||||||
{{ appStore.title }}
|
{{ appStore.title }}
|
||||||
<span class="absolute -right-10 -top-1 font-normal text-xs text-gray-400"> v0.0.1 </span>
|
<span class="absolute -right-10 -top-1 font-normal text-xs text-gray-400"> v0.0.1 </span>
|
||||||
</h1>
|
</h1>
|
||||||
<!-- <span class="text-gray-400">{{ appStore.subtitle }}</span> -->
|
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center w-full overflow-hidden">
|
<div class="flex items-center justify-center w-full overflow-hidden">
|
||||||
<div
|
<div class="login-box w-[960px] h-[560px] relative mx-4 grid md:grid-cols-2 rounded-lg overflow-hidden border border-blue-100">
|
||||||
class="login-box w-[960px] h-[560px] relative mx-4 grid md:grid-cols-2 rounded-lg overflow-hidden border border-blue-100"
|
<div class="login-left relative hidden md:block w-full h-full overflow-hidden bg-[rgb(var(--primary-6))] px-4"></div>
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="login-left relative hidden md:block w-full h-full overflow-hidden bg-[rgb(var(--primary-6))] px-4"
|
|
||||||
></div>
|
|
||||||
<div class="relative p-20 px-8 md:px-14 bg-white shadow-sm">
|
<div class="relative p-20 px-8 md:px-14 bg-white shadow-sm">
|
||||||
<div class="text-xl text-brand-500 font-semibold">用户登陆</div>
|
<div class="text-xl text-brand-500 font-semibold">用户登陆</div>
|
||||||
<div class="text-gray-500 mt-2.5">{{ meridiem }}好,欢迎访问 {{ appStore.title }} 系统!</div>
|
<div class="text-gray-500 mt-2.5">{{ meridiem }}好,欢迎访问 {{ appStore.title }} 系统!</div>
|
||||||
<a-form ref="formRef" :model="model" :rules="formRules" layout="vertical" class="mt-6">
|
<a-form ref="formRef" :model="model" :rules="formRules" layout="vertical" class="mt-6">
|
||||||
<a-form-item field="username" label="账号" :disabled="loading" hide-asterisk>
|
<a-form-item field="username" label="账号" :disabled="loading" hide-asterisk>
|
||||||
<a-input v-model="model.username" placeholder="请输入账号/手机号/邮箱" allow-clear>
|
<a-input v-model="model.username" placeholder="请输入账号" allow-clear>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<i class="icon-park-outline-user" />
|
<i class="icon-park-outline-user" />
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -77,7 +73,7 @@ const formRules: Record<string, FieldRule[]> = {
|
||||||
username: [
|
username: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请输入账号/手机号/邮箱',
|
message: '请输入账号',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
|
|
@ -101,8 +97,8 @@ const onSubmitForm = async () => {
|
||||||
if (await formRef.value?.validate()) {
|
if (await formRef.value?.validate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
try {
|
||||||
const res = await api.auth.login(model);
|
const res = await api.auth.login(model);
|
||||||
userStore.setAccessToken(res.data.data);
|
userStore.setAccessToken(res.data.data);
|
||||||
Notification.success({
|
Notification.success({
|
||||||
|
|
@ -113,9 +109,8 @@ const onSubmitForm = async () => {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const message = error?.response?.data?.message;
|
const message = error?.response?.data?.message;
|
||||||
message && Message.warning(`提示:${message}`);
|
message && Message.warning(`提示:${message}`);
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
}
|
||||||
|
loading.value = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full p-5 flex gap-4">
|
<div class="w-full p-5 flex gap-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
<div class="bg-white p-4">
|
||||||
|
<UserTable></UserTable>
|
||||||
|
</div>
|
||||||
<div class="bg-white px-5 py-4 rounded-sm">
|
<div class="bg-white px-5 py-4 rounded-sm">
|
||||||
<div>统计概览</div>
|
<div>统计概览</div>
|
||||||
<div class="flex justify-between gap-4 mt-4">
|
<div class="flex justify-between gap-4 mt-4">
|
||||||
|
|
@ -31,9 +34,7 @@
|
||||||
<i class="icon-park-outline-delete text-xs"></i>
|
<i class="icon-park-outline-delete text-xs"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="py-3 px-3 border border-dashed rounded-sm border-gray-400 text-gray-500 hover:bg-gray-100 cursor-pointer">
|
||||||
class="py-3 px-3 border border-dashed rounded-sm border-gray-400 text-gray-500 hover:bg-gray-100 cursor-pointer"
|
|
||||||
>
|
|
||||||
<i class="icon-park-outline-add ml-2"></i>
|
<i class="icon-park-outline-add ml-2"></i>
|
||||||
添加服务1
|
添加服务1
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -59,9 +60,7 @@
|
||||||
<ul class="list-none w-full m-0 p-0">
|
<ul class="list-none w-full m-0 p-0">
|
||||||
<li v-for="i in 8" class="w-full h-6 items-center overflow-hidden justify-between flex gap-2 mb-2">
|
<li v-for="i in 8" class="w-full h-6 items-center overflow-hidden justify-between flex gap-2 mb-2">
|
||||||
<a-tag>{{ i }}</a-tag>
|
<a-tag>{{ i }}</a-tag>
|
||||||
<span class="flex-1 truncate hover:underline underline-offset-2 hover:text-brand-500 cursor-pointer">
|
<span class="flex-1 truncate hover:underline underline-offset-2 hover:text-brand-500 cursor-pointer"> 但是预测已加载的数据不足以 </span>
|
||||||
但是预测已加载的数据不足以
|
|
||||||
</span>
|
|
||||||
<span class="text-gray-400">3天前</span>
|
<span class="text-gray-400">3天前</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -71,8 +70,102 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="tsx">
|
||||||
import { useUserStore } from '@/store/user';
|
import { useUserStore } from '@/store/user';
|
||||||
|
import { useTable } from '@/components/AnTable.1';
|
||||||
|
|
||||||
|
const UserTable = useTable(instance => {
|
||||||
|
return {
|
||||||
|
data: async params => {
|
||||||
|
await new Promise(res => setTimeout(res, 2000));
|
||||||
|
return {
|
||||||
|
data: Array.from({ length: params.size }, (_, i) => ({
|
||||||
|
id: i + 1,
|
||||||
|
name: Math.random(),
|
||||||
|
})),
|
||||||
|
total: 200,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '名字',
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
type: 'button',
|
||||||
|
width: 200,
|
||||||
|
align: 'right',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: '测试',
|
||||||
|
onClick() {
|
||||||
|
console.log(instance);
|
||||||
|
instance.value?.refresh();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
text: '删除',
|
||||||
|
onClick(props) {
|
||||||
|
instance.value?.renderData.splice(props.rowIndex, 1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
search: [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
label: '请输入名字',
|
||||||
|
setter: 'search',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
text: '测试',
|
||||||
|
icon: 'icon-park-outline-refresh',
|
||||||
|
disable: () => Boolean(instance.value?.search?.model?.name),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
widgets: [
|
||||||
|
{
|
||||||
|
// text: '测试',
|
||||||
|
icon: 'icon-park-outline-refresh',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tableProps: {
|
||||||
|
rowSelection: {
|
||||||
|
showCheckedAll: true,
|
||||||
|
},
|
||||||
|
onSelect(rowKeys, rowKey, record) {
|
||||||
|
console.log(rowKeys, rowKey, record);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tableSlots: {
|
||||||
|
// 'pagination-left': () => {
|
||||||
|
// return <a-button>测试1</a-button>;
|
||||||
|
// },
|
||||||
|
// 'pagination-right': () => {
|
||||||
|
// return <a-button>测试1</a-button>;
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
label: '名字',
|
||||||
|
setter: 'input',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
submit: () => {},
|
||||||
|
},
|
||||||
|
modify: {
|
||||||
|
extend: true,
|
||||||
|
title: '修改',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ const { component: UserTable } = useTable({
|
||||||
title: '操作',
|
title: '操作',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
width: 200,
|
width: 200,
|
||||||
|
align: 'right',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: '重置密码',
|
text: '重置密码',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue