更新表格组件,新增过滤功能,调整数据获取逻辑,优化界面布局

This commit is contained in:
2025-12-17 23:24:18 +07:00
parent 8a9d617129
commit 63ca414f2b
7 changed files with 225 additions and 85 deletions

View File

@@ -1,5 +1,5 @@
# backend service base url, test environment
VITE_SERVICE_BASE_URL=http://192.168.1.36:9527
VITE_SERVICE_BASE_URL=http://192.168.1.36:9528
# other backend service base url, test environment
VITE_OTHER_SERVICE_BASE_URL= `{}`

View File

@@ -39,8 +39,9 @@ function refresh() {
</script>
<template>
<NSpace :align="itemAlign" wrap justify="end" class="lt-sm:w-200px">
<NSpace :align="itemAlign" justify="space-between" wrap class="lt-sm:w-200px">
<slot name="prefix"></slot>
<div class="space-x-5">
<slot name="default">
<NButton size="small" ghost type="primary" @click="add">
<template #icon>
@@ -48,17 +49,13 @@ function refresh() {
</template>
{{ $t('common.add') }}
</NButton>
<NPopconfirm @positive-click="batchDelete">
<template #trigger>
<NButton size="small" ghost type="error" :disabled="disabledDelete">
<NButton size="small" ghost type="error" :disabled="disabledDelete" @click="batchDelete">
<template #icon>
<icon-ic-round-delete class="text-icon" />
</template>
{{ $t('common.batchDelete') }}
</NButton>
</template>
{{ $t('common.confirmDelete') }}
</NPopconfirm>
</slot>
<NButton size="small" @click="refresh">
<template #icon>
@@ -67,6 +64,7 @@ function refresh() {
{{ $t('common.refresh') }}
</NButton>
<TableColumnSetting v-model:columns="columns" />
</div>
<slot name="suffix"></slot>
</NSpace>
</template>

View File

@@ -1,3 +1,4 @@
import type { Component, VNode } from 'vue';
import { h } from 'vue';
import type { ButtonProps, DataTableColumn, DataTableColumns } from 'naive-ui';
import { NButton, NSpace } from 'naive-ui';
@@ -24,12 +25,27 @@ export interface Pagination {
[key: string]: any;
}
export type TableFetchData = (page: Pagination) => ReturnType<typeof safeClient>;
export type TableFetchData = (query: {
pagination: Pagination;
filter?: Record<string, any>;
}) => ReturnType<typeof safeClient>;
export type TableFilterColumn = {
key: string;
title: string;
component?: Component | VNode;
componentProps?: Record<string, any>;
};
export type TableFilterColumns = Array<TableFilterColumn>;
export function transformColumns<T = InternalRowData>(columns: TableBaseColumns<T>): DataTableColumns<T> {
return columns.map(col => {
return {
...col,
ellipsis: {
tooltip: true
},
render: col.operations
? (row: T) =>
h(NSpace, null, {

View File

@@ -1,12 +1,25 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import type { PaginationProps } from 'naive-ui';
import { type TableBaseColumns, type TableFetchData, transformColumns, transformHeaderColumns } from '.';
import { useRoute } from 'vue-router';
import type { DataTableColumns, PaginationProps } from 'naive-ui';
import { useI18n } from 'vue-i18n';
import {
type TableBaseColumns,
type TableFetchData,
type TableFilterColumns,
transformColumns,
transformHeaderColumns
} from '.';
import type { TableColumnCheck } from '~/packages/hooks/src';
const route = useRoute();
const { t } = useI18n();
const title = t(route.meta.i18nKey as string);
const props = defineProps<{
fetchData: TableFetchData;
columns: TableBaseColumns;
showHeaderOperation?: boolean;
filterColumns?: TableFilterColumns;
}>();
const emit = defineEmits<{
(e: 'add'): void;
@@ -15,8 +28,8 @@ const emit = defineEmits<{
}>();
const tableData = ref<any[]>([]);
const dataTableColumns = transformColumns(props.columns);
const headerTableColumns = transformHeaderColumns(props.columns);
const dataTableColumns = ref<DataTableColumns>(transformColumns(props.columns));
const headerTableColumns = ref<NaiveUI.TableColumnCheck[]>(transformHeaderColumns(props.columns));
const pagination = ref<PaginationProps>({
page: 1,
pageSize: 10,
@@ -25,12 +38,15 @@ const pagination = ref<PaginationProps>({
pageSizes: [10, 20, 50, 100]
});
async function loadData() {
async function loadData(query?: Record<string, any>) {
const page = pagination.value.page || 1;
const pageSize = pagination.value.pageSize || 10;
const { data } = await props.fetchData({
pagination: {
pageIndex: page,
pageSize
},
filter: query
});
tableData.value = (data.value as any).data;
@@ -45,6 +61,19 @@ function handlePageSizeChange(curPageSize: number) {
pagination.value.pageSize = curPageSize;
loadData();
}
function handleSearch(form: Record<string, any>) {
pagination.value.page = 1;
loadData(form);
}
function handleUpdateColumns(columns: TableColumnCheck[]) {
headerTableColumns.value = columns;
const sortKeys = columns.map(col => col.key);
const currentColumns: DataTableColumns = [...dataTableColumns.value];
const sortedColumns = sortKeys.map(key => currentColumns.find(col => (col as any).key === key)).filter(Boolean);
dataTableColumns.value = sortedColumns as DataTableColumns;
}
onMounted(() => {
loadData();
@@ -57,14 +86,24 @@ defineExpose({
<template>
<div class="space-y-5">
<TableFilter :columns="filterColumns" @confirm="handleSearch" />
<div class="rounded-lg bg-white p-5 space-y-5">
<TableHeaderOperation
v-if="showHeaderOperation"
:columns="headerTableColumns"
@update:columns="handleUpdateColumns"
@add="emit('add')"
@refresh="emit('refresh')"
@refresh="loadData()"
@delete="emit('delete')"
/>
>
<template #prefix>
<div class="text-lg font-bold">{{ title }}</div>
</template>
</TableHeaderOperation>
<NDataTable
:scroll-x="2000"
:columns="dataTableColumns"
:data="tableData"
:pagination="pagination"
@@ -73,6 +112,7 @@ defineExpose({
:on-update:page-size="handlePageSizeChange"
/>
</div>
</div>
</template>
<style lang="css" scoped></style>

View File

@@ -0,0 +1,47 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { NInput } from 'naive-ui';
import type { TableFilterColumns } from '.';
defineProps<{
columns?: TableFilterColumns;
}>();
const emit = defineEmits<{
(e: 'confirm', form: Record<string, any>): void;
}>();
const form = ref<Record<string, any>>({});
function handleReset() {
form.value = {};
emit('confirm', form.value);
}
function handleConfirm() {
emit('confirm', form.value);
}
</script>
<template>
<div class="rounded-lg bg-white p-5">
<div class="mb-5">搜索</div>
<NForm :label-width="80" label-align="left" label-placement="left">
<NGrid x-gap="20" :cols="4">
<NGi v-for="col in columns" :key="col.key">
<NFormItem :label="col.title" :path="col.key">
<component :is="col.component || NInput" v-model:value="form[col.key]" v-bind="col.componentProps" />
</NFormItem>
</NGi>
<NGi>
<NSpace>
<NButton type="primary" ghost @click="handleReset">重置</NButton>
<NButton type="primary" @click="handleConfirm">搜索</NButton>
</NSpace>
</NGi>
</NGrid>
</NForm>
</div>
</template>
<style lang="css" scoped></style>

View File

@@ -91,6 +91,7 @@ declare module 'vue' {
SystemLogo: typeof import('./../components/common/system-logo.vue')['default']
TableBase: typeof import('./../components/table/table-base.vue')['default']
TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default']
TableFilter: typeof import('./../components/table/table-filter.vue')['default']
TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
@@ -178,6 +179,7 @@ declare global {
const SystemLogo: typeof import('./../components/common/system-logo.vue')['default']
const TableBase: typeof import('./../components/table/table-base.vue')['default']
const TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default']
const TableFilter: typeof import('./../components/table/table-filter.vue')['default']
const TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
const ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
const WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']

View File

@@ -1,18 +1,20 @@
<script lang="ts" setup>
import { h, ref, useTemplateRef } from 'vue';
import { NInputNumber, useDialog, useMessage } from 'naive-ui';
import { useTemplateRef } from 'vue';
import { NDatePicker, useDialog, useMessage } from 'naive-ui';
import { client, safeClient } from '@/service/api';
import { DepositTypeEnum } from '@/enum';
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
const dialog = useDialog();
const message = useMessage();
const tableInst = useTemplateRef<TableInst>('tableInst');
const fetchData: TableFetchData = () => {
const fetchData: TableFetchData = ({ pagination, filter }) => {
return safeClient(() =>
client.api.admin.rwa.issuance.products.get({
query: {}
query: {
...pagination,
...filter
}
})
);
};
@@ -23,7 +25,7 @@ const columns: TableBaseColumns = [
key: 'id'
},
{
title: 'code',
title: 'Code',
key: 'code'
},
{
@@ -31,64 +33,99 @@ const columns: TableBaseColumns = [
key: 'name'
},
{
title: 'description',
key: 'description'
},
{
title: 'estimatedValue',
title: '估值',
key: 'estimatedValue'
},
{
title: 'categoryId',
title: '产品分类',
key: 'categoryId'
},
{
title: 'createdBy',
title: '创建人',
key: 'createdBy'
},
{
title: '创建时间',
key: 'createdAt',
render: (row: any) => {
return new Date(row.createdAt).toLocaleDateString();
}
},
{
title: '状态',
key: 'status'
},
{
title: '描述',
key: 'description'
},
{
title: '操作',
fixed: 'right',
key: 'operation',
width: 160,
width: 230,
operations: (row: any) => [
{
contentText: '编辑',
type: 'primary',
onClick: () => {
contentText: '批准',
size: 'small',
onClick: async () => {
safeClient(() =>
client.api.admin.rwa.issuance.approve.post({
productId: row.id as string
})
);
tableInst.value?.reload();
}
},
{
contentText: '删除',
type: 'error',
ghost: true,
contentText: '拒绝',
size: 'small',
onClick: async () => {
dialog.create({
title: '提示',
positiveText: '是',
negativeText: '否',
content: '确认删除该银行信息?',
onPositiveClick: async () => {
safeClient(() =>
client.api.admin.deposit.reject({ orderId: row.id as string }).post({
reviewNote: '管理员拒绝充值'
client.api.admin.rwa.issuance.reject.post({
productId: row.id as string,
rejectionReason: '不符合要求'
})
);
// tableInst.value?.reload();
message.success('删除成功');
tableInst.value?.reload();
}
});
},
{
contentText: '编辑',
size: 'small',
onClick: () => {
tableInst.value?.reload();
}
}
]
}
];
const filterColumns: TableFilterColumns = [
{
title: '产品名称',
key: 'name'
},
{
title: '产品Code',
key: 'Code'
},
{
title: '创建时间',
key: 'createdAt',
component: NDatePicker
}
];
</script>
<template>
<TableBase ref="tableInst" :columns="columns" :fetch-data="fetchData" />
<TableBase
ref="tableInst"
show-header-operation
:columns="columns"
:filter-columns="filterColumns"
:fetch-data="fetchData"
/>
</template>
<style lang="css" scoped></style>