feat-wip(components): 数据字典相关页面代码提交

This commit is contained in:
2025-12-02 00:25:16 +08:00
parent a10efd3b7e
commit 0e2e512eda
14 changed files with 644 additions and 44 deletions

View File

@ -8,6 +8,7 @@ defineOptions({
interface Props {
itemAlign?: NaiveUI.Align;
disabledDelete?: boolean;
disabledAdd?: boolean;
loading?: boolean;
}
@ -42,7 +43,7 @@ function refresh() {
<NSpace :align="itemAlign" wrap justify="end" class="lt-sm:w-200px">
<slot name="prefix"></slot>
<slot name="default">
<NButton size="small" ghost type="primary" @click="add">
<NButton size="small" ghost type="primary" :disabled="disabledAdd" @click="add">
<template #icon>
<icon-ic-round-plus class="text-icon" />
</template>

View File

@ -0,0 +1,8 @@
import { transformRecordToOption } from '@/utils/common';
export const dictionaryTypeRecord: Record<Api.Sys.Core.DictionaryType, App.I18n.I18nKey> = {
enum: 'page.sys.core.dictionary.options.type.enum',
tree: 'page.sys.core.dictionary.options.type.tree'
};
export const dictionaryTypeOptions = transformRecordToOption(dictionaryTypeRecord);

View File

@ -230,17 +230,17 @@ export function useTableOperate<TableData>(
}
export function defaultTransform<ApiData>(
response: FlatResponseData<any, Api.Common.PaginatingQueryRecord<ApiData>>
response: FlatResponseData<any, Api.Common.PageResponse<ApiData>>
): PaginationData<ApiData> {
const { data, error } = response;
if (!error) {
const { records, current, size, total } = data;
const { records, pageNumber, pageSize, total } = data;
return {
data: records,
pageNum: current,
pageSize: size,
pageNum: pageNumber,
pageSize,
total
};
}

View File

@ -320,6 +320,15 @@ const local: App.I18n.Schema = {
description: 'Description',
createTime: 'Create Time',
updateTime: 'Update Time'
},
options: {
type: {
enum: 'Enum',
tree: 'Tree'
}
},
item: {
title: 'Item'
}
}
},

View File

@ -317,6 +317,15 @@ const local: App.I18n.Schema = {
description: '描述',
createTime: '创建时间',
updateTime: '修改时间'
},
options: {
type: {
enum: '枚举',
tree: '数型'
}
},
item: {
title: '字典项'
}
}
},

View File

@ -1,2 +1,3 @@
export * from './auth';
export * from './route';
export * from './sys/core/dictionary';

View File

@ -0,0 +1,45 @@
import { request } from '../../../request';
export function fetchPageDictionary(pageRequest: Api.Sys.Core.DictionaryQueryPageRequest) {
return request<Api.Common.PageResponse<Api.Sys.Core.Dictionary>>({
url: '/dictionary/page',
method: 'post',
data: pageRequest
});
}
export function fetchDictionaryAdd(dictionaryOp: Api.Sys.Core.DictionaryOp) {
return request({
url: '/dictionary/add',
method: 'post',
data: dictionaryOp
});
}
export function fetchDictionaryEdit(dictionaryOp: Api.Sys.Core.DictionaryOp) {
return request({
url: '/dictionary/edit',
method: 'post',
data: dictionaryOp
});
}
export function fetchDictionaryDelete(id: string) {
return request({
url: '/dictionary/delete',
method: 'post',
data: {
id
}
});
}
export function fetchDictionaryDeleteBatch(ids: string[]) {
return request({
url: '/dictionary/deleteBatch',
method: 'post',
data: {
ids
}
});
}

View File

@ -5,46 +5,23 @@
*/
declare namespace Api {
namespace Common {
/** common params of paginating */
interface PaginatingCommonParams {
/** current page number */
current: number;
/** page size */
size: number;
/** total count */
total: number;
/** 分页请求 */
interface PageRequest {
pageNumber: number;
pageSize: number;
}
/** common params of paginating query list data */
interface PaginatingQueryRecord<T = any> extends PaginatingCommonParams {
/** 带查询参数的分页请求 */
interface QueryPageRequest<T> extends PageRequest {
query: T;
}
/** 分页响应 */
interface PageResponse<T> {
total: number;
pageNumber: number;
pageSize: number;
records: T[];
}
/** common search params of table */
type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'current' | 'size'>;
/**
* enable status
*
* - "1": enabled
* - "2": disabled
*/
type EnableStatus = '1' | '2';
/** common record */
type CommonRecord<T = any> = {
/** record id */
id: number;
/** record creator */
createBy: string;
/** record create time */
createTime: string;
/** record updater */
updateBy: string;
/** record update time */
updateTime: string;
/** record status */
status: EnableStatus | null;
} & T;
}
}

40
src/typings/api/sys/core.d.ts vendored Normal file
View File

@ -0,0 +1,40 @@
declare namespace Api {
namespace Sys {
namespace Core {
// ******************** sys_core_dictionary ********************
type DictionaryType = 'enum' | 'tree';
interface Dictionary {
id: string;
name: string;
code: string;
type: DictionaryType;
description: string | null;
createTime: string;
updateTime: string;
}
interface DictionaryQuery {
name: string | null;
code: string | null;
type: string | null;
}
type DictionaryQueryPageRequest = Api.Common.QueryPageRequest<DictionaryQuery>;
interface DictionaryOp {
id: string | null;
name: string;
code: string;
type: DictionaryType;
description: string | null;
}
interface DictionaryItem {
id: string;
name: string;
code: string;
sort: number;
description: string | null;
createTime: string | null;
updateTime: string | null;
children: DictionaryItem[];
}
}
}
}

View File

@ -559,6 +559,15 @@ declare namespace App {
createTime: string;
updateTime: string;
};
options: {
type: {
enum: string;
tree: string;
};
};
item: {
title: string;
};
};
};
rbac: {

View File

@ -21,14 +21,21 @@ declare module 'vue' {
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
IconLocalBanner: typeof import('~icons/local/banner')['default']
IconLocalLogo: typeof import('~icons/local/logo')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
IconUilSearch: typeof import('~icons/uil/search')['default']
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
@ -41,7 +48,10 @@ declare module 'vue' {
NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCollapse: typeof import('naive-ui')['NCollapse']
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NDataTable: typeof import('naive-ui')['NDataTable']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer']
@ -50,6 +60,7 @@ declare module 'vue' {
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NInput: typeof import('naive-ui')['NInput']
@ -62,7 +73,10 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NPopover: typeof import('naive-ui')['NPopover']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']
@ -98,14 +112,21 @@ declare global {
const FullScreen: typeof import('./../components/common/full-screen.vue')['default']
const IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
const IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
const IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
const IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
const IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
const IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
const IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
const IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
const IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
const IconLocalBanner: typeof import('~icons/local/banner')['default']
const IconLocalLogo: typeof import('~icons/local/logo')['default']
const IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
const IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
const IconMdiDrag: typeof import('~icons/mdi/drag')['default']
const IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
const IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
const IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
const IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
const IconUilSearch: typeof import('~icons/uil/search')['default']
const LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
@ -118,7 +139,10 @@ declare global {
const NButton: typeof import('naive-ui')['NButton']
const NCard: typeof import('naive-ui')['NCard']
const NCheckbox: typeof import('naive-ui')['NCheckbox']
const NCollapse: typeof import('naive-ui')['NCollapse']
const NCollapseItem: typeof import('naive-ui')['NCollapseItem']
const NColorPicker: typeof import('naive-ui')['NColorPicker']
const NDataTable: typeof import('naive-ui')['NDataTable']
const NDialogProvider: typeof import('naive-ui')['NDialogProvider']
const NDivider: typeof import('naive-ui')['NDivider']
const NDrawer: typeof import('naive-ui')['NDrawer']
@ -127,6 +151,7 @@ declare global {
const NEmpty: typeof import('naive-ui')['NEmpty']
const NForm: typeof import('naive-ui')['NForm']
const NFormItem: typeof import('naive-ui')['NFormItem']
const NFormItemGi: typeof import('naive-ui')['NFormItemGi']
const NGi: typeof import('naive-ui')['NGi']
const NGrid: typeof import('naive-ui')['NGrid']
const NInput: typeof import('naive-ui')['NInput']
@ -139,7 +164,10 @@ declare global {
const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
const NModal: typeof import('naive-ui')['NModal']
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
const NPopconfirm: typeof import('naive-ui')['NPopconfirm']
const NPopover: typeof import('naive-ui')['NPopover']
const NRadio: typeof import('naive-ui')['NRadio']
const NRadioGroup: typeof import('naive-ui')['NRadioGroup']
const NScrollbar: typeof import('naive-ui')['NScrollbar']
const NSelect: typeof import('naive-ui')['NSelect']
const NSpace: typeof import('naive-ui')['NSpace']

View File

@ -1,15 +1,166 @@
<script setup lang="ts">
<script setup lang="tsx">
import { reactive } from 'vue';
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { dictionaryTypeRecord } from '@/constants/sys/core/dictionary';
import { fetchDictionaryDelete, fetchDictionaryDeleteBatch, fetchPageDictionary } from '@/service/api';
import { useAppStore } from '@/store/modules/app';
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales';
import DictionarySearch from '@/views/sys/core/dictionary/modules/dictionary-search.vue';
import DictionaryOperateDrawer from '@/views/sys/core/dictionary/modules/dictionary-operate-drawer.vue';
const appStore = useAppStore();
const pageRequest: Api.Sys.Core.DictionaryQueryPageRequest = reactive({
pageNumber: 1,
pageSize: 10,
query: {
name: null,
code: null,
type: null
}
});
const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagination } = useNaivePaginatedTable({
api: () => fetchPageDictionary(pageRequest),
transform: response => defaultTransform(response),
onPaginationParamsChange: params => {
pageRequest.pageNumber = params.page || 1;
pageRequest.pageSize = params.pageSize || 10;
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
},
{
key: 'index',
title: $t('common.index'),
width: 64,
align: 'center',
render: (_, index) => index + 1
},
{
key: 'name',
title: $t('page.sys.core.dictionary.fields.name'),
align: 'center',
minWidth: 120
},
{
key: 'code',
title: $t('page.sys.core.dictionary.fields.code'),
align: 'center',
minWidth: 120
},
{
key: 'type',
title: $t('page.sys.core.dictionary.fields.type'),
width: 100,
render: row => {
if (row.type === null) {
return null;
}
return <NTag type="success">{$t(dictionaryTypeRecord[row.type])}</NTag>;
}
},
{
key: 'createTime',
title: $t('page.sys.core.dictionary.fields.createTime'),
align: 'center',
width: 120
},
{
key: 'updateTime',
title: $t('page.sys.core.dictionary.fields.updateTime'),
align: 'center',
minWidth: 120
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
minWidth: 130,
render: row => (
<div class="flex-center gap-8px">
<NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
<NButton type="error" ghost size="small">
{$t('common.delete')}
</NButton>
)
}}
</NPopconfirm>
</div>
)
}
]
});
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
useTableOperate(data, 'id', getData);
async function handleBatchDelete() {
fetchDictionaryDeleteBatch(checkedRowKeys.value).then(() => {
onBatchDeleted();
});
}
function handleDelete(id: string) {
fetchDictionaryDelete(id).then(() => {
onDeleted();
});
}
function edit(id: string) {
handleEdit(id);
}
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<DictionarySearch v-model:model="pageRequest" @search="getDataByPage" />
<NCard
:title="$t('page.sys.core.dictionary.title')"
:bordered="false"
size="small"
class="card-wrapper sm:flex-1-hidden"
></NCard>
>
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@delete="handleBatchDelete"
@refresh="getData"
/>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="702"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>
<DictionaryOperateDrawer
v-model:visible="drawerVisible"
:operate-type="operateType"
:row-data="editingData"
@submitted="getDataByPage"
/>
</NCard>
</div>
</template>

View File

@ -0,0 +1,233 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import type { DataTableColumns } from 'naive-ui';
import { cloneDeep } from 'lodash-es';
import { jsonClone } from '@sa/utils';
import { dictionaryTypeOptions } from '@/constants/sys/core/dictionary';
import { fetchDictionaryAdd, fetchDictionaryEdit } from '@/service/api';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
defineOptions({
name: 'DictionaryOperateDrawer'
});
interface Props {
/** the type of operation */
operateType: NaiveUI.TableOperateType;
/** the edit row data */
rowData?: Api.Sys.Core.Dictionary | null;
}
const props = defineProps<Props>();
interface Emits {
(e: 'submitted'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = {
add: $t('common.add'),
edit: $t('common.edit')
};
return titles[props.operateType];
});
type Model = Pick<Api.Sys.Core.Dictionary, 'name' | 'code' | 'type' | 'description'>;
const model = ref(createDefaultModel());
function createDefaultModel(): Model {
return {
name: '',
code: '',
type: 'enum',
description: null
};
}
type RuleKey = Exclude<keyof Model, 'description'>;
const rules: Record<RuleKey, App.Global.FormRule> = {
name: defaultRequiredRule,
code: defaultRequiredRule,
type: defaultRequiredRule
};
function handleInitModel() {
model.value = createDefaultModel();
if (props.operateType === 'edit' && props.rowData) {
Object.assign(model.value, jsonClone(props.rowData));
}
}
function closeDrawer() {
visible.value = false;
}
async function handleSubmit() {
await validate();
const isEdit = props.operateType === 'edit';
const opFunc = isEdit ? fetchDictionaryEdit : fetchDictionaryAdd;
const opData: Api.Sys.Core.DictionaryOp = {
id: props.rowData?.id || null,
...model.value
};
opFunc(opData).then(() => {
window.$message?.success($t('common.updateSuccess'));
closeDrawer();
emit('submitted');
});
}
function handleAddDictionaryItem() {}
const dictionaryItemData: Api.Sys.Core.DictionaryItem[] = [
{
id: '07',
name: '07akioni',
code: '07akioni',
sort: 0,
description: null,
createTime: null,
updateTime: null,
children: [
{
id: '08',
name: '08akioni',
code: '08akioni',
sort: 0,
description: null,
createTime: null,
updateTime: null,
children: [
{
id: '09',
name: '09akioni',
code: '09akioni',
children: [],
sort: 0,
description: null,
createTime: null,
updateTime: null
}
]
}
]
},
{
id: '11',
name: '11akioni',
code: '11akioni',
children: [],
sort: 0,
description: null,
createTime: null,
updateTime: null
}
];
const newDictionaryItemData = ref<Api.Sys.Core.DictionaryItem[]>([]);
const dictionaryItemColumns: DataTableColumns<Api.Sys.Core.DictionaryItem> = [
{
type: 'selection'
},
{
title: 'name',
key: 'name'
},
{
title: 'index',
key: 'index'
}
];
function rowKey(row: Api.Sys.Core.DictionaryItem) {
return row.id;
}
function handleUpdateChecked() {
const dictionaryType = model.value.type;
if (!dictionaryType) {
return;
}
newDictionaryItemData.value = cloneDeep(dictionaryItemData);
if (dictionaryType === 'enum') {
for (const newDictionaryItemDatum of newDictionaryItemData.value) {
newDictionaryItemDatum.children = [];
}
}
}
watch(visible, () => {
if (visible.value) {
handleInitModel();
restoreValidation();
handleUpdateChecked();
}
});
</script>
<template>
<NDrawer v-model:show="visible" display-directive="show" :default-width="600" resizable>
<NDrawerContent :title="title" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem :label="$t('page.sys.core.dictionary.fields.name')" path="name">
<NInput v-model:value="model.name" :placeholder="$t('page.sys.core.dictionary.fields.name')" />
</NFormItem>
<NFormItem :label="$t('page.sys.core.dictionary.fields.code')" path="code">
<NInput v-model:value="model.code" :placeholder="$t('page.sys.core.dictionary.fields.code')" />
</NFormItem>
<NFormItem :label="$t('page.sys.core.dictionary.fields.type')" path="type">
<NRadioGroup v-model:value="model.type" @change="handleUpdateChecked">
<NRadio
v-for="item in dictionaryTypeOptions"
:key="item.value"
:value="item.value"
:label="$t(item.label)"
/>
</NRadioGroup>
</NFormItem>
<NFormItem :label="$t('page.sys.core.dictionary.fields.description')" path="description">
<NInput
v-model:value="model.description"
:placeholder="$t('page.sys.core.dictionary.fields.description')"
type="textarea"
/>
</NFormItem>
</NForm>
<NCard :title="$t('page.sys.core.dictionary.item.title')" hoverable embedded>
<template #header-extra>
<NSpace wrap justify="end" class="lt-sm:w-200px">
<NButton size="small" ghost type="primary" @click="handleAddDictionaryItem">
<template #icon>
<icon-ic-round-plus class="text-icon" />
</template>
{{ $t('common.add') }}
</NButton>
</NSpace>
</template>
</NCard>
<template #footer>
<NSpace :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
<NDataTable :columns="dictionaryItemColumns" :data="newDictionaryItemData" :row-key="rowKey" />
</NDrawerContent>
</NDrawer>
</template>
<style scoped></style>

View File

@ -0,0 +1,89 @@
<script setup lang="ts">
import { toRaw } from 'vue';
import { jsonClone } from '@sa/utils';
import { dictionaryTypeOptions } from '@/constants/sys/core/dictionary';
import { translateOptions } from '@/utils/common';
import { $t } from '@/locales';
defineOptions({
name: 'DictionarySearch'
});
interface Emits {
(e: 'search'): void;
}
const emit = defineEmits<Emits>();
const model = defineModel<Api.Sys.Core.DictionaryQueryPageRequest>('model', { required: true });
const defaultModel = jsonClone(toRaw(model.value));
function resetModel() {
Object.assign(model.value, defaultModel);
}
function search() {
emit('search');
}
</script>
<template>
<NCard :bordered="false" size="small" class="card-wrapper">
<NCollapse :default-expanded-names="['role-search']">
<NCollapseItem :title="$t('common.search')" name="role-search">
<NForm :model="model" label-placement="left" :label-width="80">
<NGrid responsive="screen" item-responsive>
<NFormItemGi
span="24 s:12 m:6"
:label="$t('page.sys.core.dictionary.fields.name')"
path="name"
class="pr-24px"
>
<NInput v-model:value="model.query.name" :placeholder="$t('page.sys.core.dictionary.fields.name')" />
</NFormItemGi>
<NFormItemGi
span="24 s:12 m:6"
:label="$t('page.sys.core.dictionary.fields.code')"
path="code"
class="pr-24px"
>
<NInput v-model:value="model.query.code" :placeholder="$t('page.sys.core.dictionary.fields.code')" />
</NFormItemGi>
<NFormItemGi
span="24 s:12 m:6"
:label="$t('page.sys.core.dictionary.fields.type')"
path="type"
class="pr-24px"
>
<NSelect
v-model:value="model.query.type"
:placeholder="$t('page.sys.core.dictionary.fields.type')"
:options="translateOptions(dictionaryTypeOptions)"
clearable
/>
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6">
<NSpace class="w-full" justify="end">
<NButton @click="resetModel">
<template #icon>
<icon-ic-round-refresh class="text-icon" />
</template>
{{ $t('common.reset') }}
</NButton>
<NButton type="primary" ghost @click="search">
<template #icon>
<icon-ic-round-search class="text-icon" />
</template>
{{ $t('common.search') }}
</NButton>
</NSpace>
</NFormItemGi>
</NGrid>
</NForm>
</NCollapseItem>
</NCollapse>
</NCard>
</template>
<style scoped></style>