feat(components): add tablesearch component, reduce page build

This commit is contained in:
PangSanJin 2024-03-01 20:11:21 +08:00
parent a17d68eb11
commit 038dda8967
6 changed files with 158 additions and 5 deletions

View File

@ -0,0 +1,71 @@
<script setup lang="tsx">
import { $t } from '@/locales';
import type { SelectMixedOption } from '@/utils/selectTypes';
import { transformRecordToOption, translateOptions } from '@/utils/common';
interface Emits {
(e: 'reset'): void;
(e: 'search'): void;
}
const emit = defineEmits<Emits>();
const model = defineModel<any>('model', { required: true });
const rule = defineModel<any>('rule', { required: true });
function reset() {
emit('reset');
}
function search() {
emit('search');
}
function recordToOptions(optionsRecord: Record<string, App.I18n.I18nKey>): SelectMixedOption[] {
if (!optionsRecord || Object.keys(optionsRecord).length === 0) {
return [];
}
const anyOptions = transformRecordToOption(optionsRecord);
return translateOptions(anyOptions);
}
</script>
<template>
<NCard :bordered="false" size="small" class="card-wrapper">
<NForm :model="model" label-placement="left">
<NGrid responsive="screen" item-responsive>
<NFormItemGi v-for="item in rule" :key="item.model" span="24 s:5 m:5" :label="$t(item.label)" class="pr-24px">
<NInput v-if="!item.options" v-model:value="model[item.model]" :placeholder="$t(item.placeholder)" />
<NSelect
v-else
v-model:value="model[item.model]"
:placeholder="$t(item.placeholder) === '' ? '' : $t(item.placeholder)"
filterable
autocomplete="off"
clearable
:options="recordToOptions(item.options)"
></NSelect>
</NFormItemGi>
<NFormItemGi span="24 s:6 m:6">
<NSpace class="w-full" justify="end">
<NButton type="primary" ghost @click="search">
<template #icon>
<icon-ic-round-search class="text-icon" />
</template>
{{ $t('common.search') }}
</NButton>
<NButton @click="reset">
<template #icon>
<icon-ic-round-refresh class="text-icon" />
</template>
{{ $t('common.reset') }}
</NButton>
</NSpace>
</NFormItemGi>
</NGrid>
</NForm>
</NCard>
</template>
<style scoped></style>

View File

@ -10,6 +10,13 @@ type BaseData = Record<string, unknown>;
type ApiFn = (args: any) => Promise<unknown>; type ApiFn = (args: any) => Promise<unknown>;
type ParamType = {
label: string;
model: string;
placeholder: string;
options?: Record<string, App.I18n.I18nKey>;
};
export type TableColumn<T extends BaseData = BaseData, CustomColumnKey = never> = export type TableColumn<T extends BaseData = BaseData, CustomColumnKey = never> =
| (Omit<TableColumnGroup<T>, 'key'> & { key: keyof T | CustomColumnKey }) | (Omit<TableColumnGroup<T>, 'key'> & { key: keyof T | CustomColumnKey })
| (Omit<DataTableBaseColumn<T>, 'key'> & { key: keyof T | CustomColumnKey }) | (Omit<DataTableBaseColumn<T>, 'key'> & { key: keyof T | CustomColumnKey })
@ -34,6 +41,8 @@ export type TableConfig<TableData extends BaseData = BaseData, Fn extends ApiFn
apiFn: Fn; apiFn: Fn;
/** api params */ /** api params */
apiParams?: Parameters<Fn>[0]; apiParams?: Parameters<Fn>[0];
/** params rule */
paramsRule?: ParamType[];
/** transform api response to table data */ /** transform api response to table data */
transformer: Transformer<TableData, Awaited<ReturnType<Fn>>>; transformer: Transformer<TableData, Awaited<ReturnType<Fn>>>;
/** pagination */ /** pagination */
@ -70,10 +79,12 @@ export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomCol
const { loading, startLoading, endLoading } = useLoading(); const { loading, startLoading, endLoading } = useLoading();
const { bool: empty, setBool: setEmpty } = useBoolean(); const { bool: empty, setBool: setEmpty } = useBoolean();
const { apiFn, apiParams, transformer, onPaginationChanged, immediate = true } = config; const { apiFn, apiParams, paramsRule, transformer, onPaginationChanged, immediate = true } = config;
const searchParams: NonNullable<Parameters<Fn>[0]> = reactive({ ...apiParams }); const searchParams: NonNullable<Parameters<Fn>[0]> = reactive({ ...apiParams });
const rule = ref({ ...paramsRule });
const { columns, filteredColumns, reloadColumns } = useTableColumn(config.columns); const { columns, filteredColumns, reloadColumns } = useTableColumn(config.columns);
const data: Ref<TableData[]> = ref([]); const data: Ref<TableData[]> = ref([]);
@ -172,6 +183,7 @@ export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomCol
updatePagination, updatePagination,
getData, getData,
searchParams, searchParams,
rule,
updateSearchParams, updateSearchParams,
resetSearchParams resetSearchParams
}; };

View File

@ -79,6 +79,7 @@ declare module 'vue' {
SystemLogo: typeof import('./../components/common/system-logo.vue')['default'] SystemLogo: typeof import('./../components/common/system-logo.vue')['default']
TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default'] TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default']
TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default'] TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
TableSearch: typeof import('./../components/advanced/table-search.vue')['default']
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default'] ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default'] WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
} }

18
src/utils/selectTypes.ts Normal file
View File

@ -0,0 +1,18 @@
import { type CSSProperties, type VNode, type VNodeChild } from 'vue';
import type { SelectGroupOption } from 'naive-ui';
type UpdatedSelectBaseOption<
V = boolean | string | number,
L = boolean | string | ((option: UpdatedSelectBaseOption<V>, selected: boolean) => VNodeChild)
> = {
value?: V;
label?: L;
class?: string;
style?: string | CSSProperties;
disabled?: boolean;
render?: (info: { node: VNode; option: UpdatedSelectBaseOption<V>; selected: boolean }) => VNodeChild;
[k: string]: unknown;
};
type UpdatedSelectMixedOption = UpdatedSelectBaseOption | SelectGroupOption | any;
export type SelectMixedOption = UpdatedSelectMixedOption;

View File

@ -7,8 +7,8 @@ import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table'; import { useTable } from '@/hooks/common/table';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { enableStatusRecord } from '@/constants/business'; import { enableStatusRecord } from '@/constants/business';
import TableSearch from '@/components/advanced/table-search.vue';
import RoleOperateDrawer, { type OperateType } from './modules/role-operate-drawer.vue'; import RoleOperateDrawer, { type OperateType } from './modules/role-operate-drawer.vue';
import RoleSearch from './modules/role-search.vue';
const appStore = useAppStore(); const appStore = useAppStore();
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean(); const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
@ -21,6 +21,7 @@ const {
pagination, pagination,
getData, getData,
searchParams, searchParams,
rule,
updateSearchParams, updateSearchParams,
resetSearchParams resetSearchParams
} = useTable<Api.SystemManage.Role, typeof fetchGetRoleList, 'index' | 'operate'>({ } = useTable<Api.SystemManage.Role, typeof fetchGetRoleList, 'index' | 'operate'>({
@ -34,6 +35,19 @@ const {
roleName: null, roleName: null,
roleCode: null roleCode: null
}, },
paramsRule: [
{ label: 'page.manage.role.roleName', model: 'roleName', placeholder: 'page.manage.role.form.roleName' },
{ label: 'page.manage.role.roleCode', model: 'roleCode', placeholder: 'page.manage.role.form.roleCode' },
{
label: 'page.manage.role.roleStatus',
model: 'status',
placeholder: 'page.manage.role.form.roleStatus',
options: {
'1': 'page.manage.common.status.enable',
'2': 'page.manage.common.status.disable'
}
}
],
transformer: res => { transformer: res => {
const { records = [], current = 1, size = 10, total = 0 } = res.data || {}; const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
@ -175,7 +189,7 @@ function getIndex(index: number) {
<template> <template>
<div class="flex-vertical-stretch gap-16px overflow-hidden <sm:overflow-auto"> <div class="flex-vertical-stretch gap-16px overflow-hidden <sm:overflow-auto">
<RoleSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" /> <TableSearch v-model:model="searchParams" :rule="rule" @reset="resetSearchParams" @search="getData" />
<NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden"> <NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header-extra> <template #header-extra>
<TableHeaderOperation <TableHeaderOperation

View File

@ -7,8 +7,8 @@ import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table'; import { useTable } from '@/hooks/common/table';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { enableStatusRecord, userGenderRecord } from '@/constants/business'; import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import TableSearch from '@/components/advanced/table-search.vue';
import UserOperateDrawer, { type OperateType } from './modules/user-operate-drawer.vue'; import UserOperateDrawer, { type OperateType } from './modules/user-operate-drawer.vue';
import UserSearch from './modules/user-search.vue';
const appStore = useAppStore(); const appStore = useAppStore();
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean(); const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
@ -21,6 +21,7 @@ const {
pagination, pagination,
getData, getData,
searchParams, searchParams,
rule,
updateSearchParams, updateSearchParams,
resetSearchParams resetSearchParams
} = useTable<Api.SystemManage.User, typeof fetchGetUserList, 'index' | 'operate'>({ } = useTable<Api.SystemManage.User, typeof fetchGetUserList, 'index' | 'operate'>({
@ -37,6 +38,42 @@ const {
userPhone: null, userPhone: null,
userEmail: null userEmail: null
}, },
paramsRule: [
{ label: 'page.manage.user.userName', model: 'userName', placeholder: 'page.manage.user.form.userName' },
{
label: 'page.manage.user.userGender',
model: 'userGender',
placeholder: 'page.manage.user.form.userGender',
options: {
'1': 'page.manage.user.gender.male',
'2': 'page.manage.user.gender.female'
}
},
{
label: 'page.manage.user.nickName',
model: 'nickName',
placeholder: 'page.manage.user.form.nickName'
},
{
label: 'page.manage.user.userPhone',
model: 'userPhone',
placeholder: 'page.manage.user.form.userPhone'
},
{
label: 'page.manage.user.userEmail',
model: 'userEmail',
placeholder: 'page.manage.user.form.userEmail'
},
{
label: 'page.manage.user.userStatus',
model: 'status',
placeholder: 'page.manage.user.form.userStatus',
options: {
'1': 'page.manage.common.status.enable',
'2': 'page.manage.common.status.disable'
}
}
],
transformer: res => { transformer: res => {
const { records = [], current = 1, size = 10, total = 0 } = res.data || {}; const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
@ -205,7 +242,7 @@ function getIndex(index: number) {
<template> <template>
<div class="flex-vertical-stretch gap-16px overflow-hidden <sm:overflow-auto"> <div class="flex-vertical-stretch gap-16px overflow-hidden <sm:overflow-auto">
<UserSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" /> <TableSearch v-model:model="searchParams" :rule="rule" @reset="resetSearchParams" @search="getData" />
<NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden"> <NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header-extra> <template #header-extra>
<TableHeaderOperation <TableHeaderOperation