|
|
@@ -0,0 +1,357 @@
|
|
|
+<script setup lang="tsx">
|
|
|
+import { ref } from 'vue';
|
|
|
+import { useRoute } from 'vue-router';
|
|
|
+import { NEllipsis, NTooltip, type TreeOption } from 'naive-ui';
|
|
|
+import {
|
|
|
+ fetchBatchDeleteDictData,
|
|
|
+ fetchBatchDeleteDictType,
|
|
|
+ fetchCreateDictData,
|
|
|
+ fetchCreateDictType,
|
|
|
+ fetchGetDictDataList,
|
|
|
+ fetchGetDictTypeOption,
|
|
|
+ fetchUpdateDictData,
|
|
|
+ fetchUpdateDictType
|
|
|
+} from '@/service/api/config/dict';
|
|
|
+import { useDict } from '@/hooks/business/dict';
|
|
|
+import { copyTextToClipboard } from '@/utils/zt';
|
|
|
+import { useTable } from '@/components/zt/Table/hooks/useTable';
|
|
|
+import { $t } from '@/locales';
|
|
|
+import ButtonIcon from '@/components/custom/button-icon.vue';
|
|
|
+import { useModalFrom } from '@/components/zt/ModalForm/hooks/useModalForm';
|
|
|
+import { columns, modelDataDict, modelTypeDict } from './dict.data';
|
|
|
+const [registerTable, { refresh, getTableLoding, setTableConfig, setFieldsValue, getSeachForm }] = useTable({
|
|
|
+ searchFormConfig: {
|
|
|
+ schemas: [
|
|
|
+ {
|
|
|
+ label: '字典名称',
|
|
|
+ field: 'dictName',
|
|
|
+ component: 'NInput'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '字典类型',
|
|
|
+ field: 'dictType',
|
|
|
+ component: 'NInput',
|
|
|
+ show: false
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ labelWidth: 120,
|
|
|
+ layout: 'horizontal',
|
|
|
+ size: 'small',
|
|
|
+ gridProps: {
|
|
|
+ cols: '1 xl:4 s:1 l:3',
|
|
|
+ itemResponsive: true
|
|
|
+ },
|
|
|
+ collapsedRows: 1
|
|
|
+ },
|
|
|
+ tableConfig: {
|
|
|
+ keyField: 'dictCode',
|
|
|
+ title: '字典列表'
|
|
|
+ }
|
|
|
+});
|
|
|
+const [
|
|
|
+ registerModalForm,
|
|
|
+ {
|
|
|
+ openModal: openModalType,
|
|
|
+ getFieldsValue: getModalTypeFields,
|
|
|
+ closeModal: closeModalType,
|
|
|
+ setFieldsValue: setModalTypeFields
|
|
|
+ }
|
|
|
+] = useModalFrom({
|
|
|
+ formConfig: {
|
|
|
+ schemas: modelTypeDict,
|
|
|
+ labelWidth: 120,
|
|
|
+ gridProps: {
|
|
|
+ cols: '1'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ modalConfig: {
|
|
|
+ title: '字典类型',
|
|
|
+ width: 800,
|
|
|
+ isShowHeaderText: true
|
|
|
+ }
|
|
|
+});
|
|
|
+const [
|
|
|
+ registerModalDataForm,
|
|
|
+ {
|
|
|
+ openModal: openModalData,
|
|
|
+ getFieldsValue: getModalDataFields,
|
|
|
+ closeModal: closeModalData,
|
|
|
+ setFieldsValue: setModalDataFields
|
|
|
+ }
|
|
|
+] = useModalFrom({
|
|
|
+ formConfig: {
|
|
|
+ schemas: modelDataDict,
|
|
|
+ labelWidth: 120,
|
|
|
+ gridProps: {
|
|
|
+ cols: '1'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ modalConfig: {
|
|
|
+ title: '字典数据',
|
|
|
+ width: 800,
|
|
|
+ isShowHeaderText: true
|
|
|
+ }
|
|
|
+});
|
|
|
+useDict('sys_user_sex');
|
|
|
+
|
|
|
+const route = useRoute();
|
|
|
+const dictData = ref<Api.System.DictType[]>([]);
|
|
|
+const selectedKeys = ref<string[]>([]);
|
|
|
+const dictPattern = ref<string>();
|
|
|
+async function getTreeData() {
|
|
|
+ const { data: tree, error } = await fetchGetDictTypeOption();
|
|
|
+ if (!error) {
|
|
|
+ dictData.value = tree;
|
|
|
+ handleClickTree(route.query.dictType ? [route.query.dictType as string] : []);
|
|
|
+ }
|
|
|
+}
|
|
|
+getTreeData();
|
|
|
+async function handleClickTree(keys: string[]) {
|
|
|
+ console.log(keys, '点事件');
|
|
|
+
|
|
|
+ const dictType = keys.length ? keys[0] : null;
|
|
|
+ selectedKeys.value = keys;
|
|
|
+ await setFieldsValue({ dictType });
|
|
|
+ const dictDataType = dictData.value.find(item => item.dictType === dictType);
|
|
|
+
|
|
|
+ setTableConfig({
|
|
|
+ title: dictDataType
|
|
|
+ ? () => (
|
|
|
+ <NEllipsis lineClamp={2} class="flex">
|
|
|
+ <span>{dictDataType.dictName}</span>
|
|
|
+ <span class="cursor-copy" onClick={async () => await copyTextToClipboard(dictDataType.dictType)}>
|
|
|
+ {` (${dictDataType.dictType} )`}
|
|
|
+ </span>
|
|
|
+ </NEllipsis>
|
|
|
+ )
|
|
|
+ : '字典列表',
|
|
|
+ keyField: 'dictCode',
|
|
|
+ showAddButton: Boolean(dictDataType)
|
|
|
+ });
|
|
|
+
|
|
|
+ window.history.pushState(null, '', `${route.path}${dictType ? `?dictType=${dictType}` : ''}`);
|
|
|
+ refresh();
|
|
|
+}
|
|
|
+
|
|
|
+function dictFilter(pattern: string, node: TreeOption) {
|
|
|
+ const dictName = node.dictName as string;
|
|
|
+ const dictType = node.dictType as string;
|
|
|
+ return dictName.includes(pattern) || dictType.includes(pattern);
|
|
|
+}
|
|
|
+function renderLabel({ option }: { option: TreeOption }) {
|
|
|
+ return (
|
|
|
+ <NTooltip placement="left">
|
|
|
+ {{
|
|
|
+ trigger: () => (
|
|
|
+ <div class="w-200px flex gap-6px overflow-hidden text-ellipsis whitespace-nowrap">
|
|
|
+ <span>{option.dictName}</span>
|
|
|
+ <span class="text-12px text-gray-500">( {option.dictType} )</span>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ default: () => (
|
|
|
+ <div class="flex-col">
|
|
|
+ <span>
|
|
|
+ {option.dictName} {option.dictType}
|
|
|
+ </span>
|
|
|
+ {option.remark ? <span>( {option.remark} )</span> : null}
|
|
|
+ <span>{option.createTime}</span>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ </NTooltip>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function renderSuffix({ option }: { option: TreeOption }) {
|
|
|
+ return (
|
|
|
+ <div class="flex-center gap-12px">
|
|
|
+ <ButtonIcon
|
|
|
+ text
|
|
|
+ type="primary"
|
|
|
+ icon="material-symbols:drive-file-rename-outline-outline"
|
|
|
+ tooltip-content={$t('common.edit')}
|
|
|
+ onClick={(event: Event) => {
|
|
|
+ event.stopPropagation();
|
|
|
+ handleEditType(option as Api.System.DictType);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ <ButtonIcon
|
|
|
+ text
|
|
|
+ type="error"
|
|
|
+ icon="material-symbols:delete-outline"
|
|
|
+ tooltip-content={$t('common.delete')}
|
|
|
+ popconfirm-content={`确定删除 ${option.dictType} ?`}
|
|
|
+ onClick={(event: Event) => event.stopPropagation()}
|
|
|
+ onPositiveClick={() => handleDeleteType(option as Api.System.DictType)}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+function handleResetTreeData() {
|
|
|
+ dictPattern.value = '';
|
|
|
+ getTreeData();
|
|
|
+}
|
|
|
+async function handleDeleteType(dictType: Api.System.DictType) {
|
|
|
+ const { error } = await fetchBatchDeleteDictType([dictType.dictId]);
|
|
|
+ if (error) return;
|
|
|
+ window.$message?.success($t('common.deleteSuccess'));
|
|
|
+ getTreeData();
|
|
|
+}
|
|
|
+function handleEditType(row: Api.System.DictType) {
|
|
|
+ openModalType(row);
|
|
|
+ setModalTypeFields(row);
|
|
|
+}
|
|
|
+async function handleSubmitDictType() {
|
|
|
+ const form = await getModalTypeFields();
|
|
|
+ if (form.dictId) {
|
|
|
+ await fetchUpdateDictType(form);
|
|
|
+ } else {
|
|
|
+ await fetchCreateDictType(form);
|
|
|
+ }
|
|
|
+ closeModalType();
|
|
|
+ getTreeData();
|
|
|
+}
|
|
|
+async function handleSubmitDictData() {
|
|
|
+ const form = await getModalDataFields();
|
|
|
+ if (form.dictCode) {
|
|
|
+ await fetchUpdateDictData(form);
|
|
|
+ } else {
|
|
|
+ await fetchCreateDictData(form);
|
|
|
+ }
|
|
|
+ closeModalData();
|
|
|
+ refresh();
|
|
|
+}
|
|
|
+function handleAddModalData() {
|
|
|
+ openModalData();
|
|
|
+ setModalDataFields(getSeachForm());
|
|
|
+}
|
|
|
+function handleEditData(row: Api.System.DictData) {
|
|
|
+ openModalData();
|
|
|
+ setModalDataFields(row);
|
|
|
+}
|
|
|
+async function handleDelete(row: Api.System.DictData) {
|
|
|
+ await fetchBatchDeleteDictData([row.dictCode]);
|
|
|
+ refresh();
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <TableSiderLayout>
|
|
|
+ <template #header-extra>
|
|
|
+ <ButtonIcon
|
|
|
+ size="small"
|
|
|
+ icon="material-symbols:add-rounded"
|
|
|
+ class="h-18px text-icon"
|
|
|
+ tooltip-content="新增"
|
|
|
+ @click.stop="() => openModalType()"
|
|
|
+ />
|
|
|
+ <ButtonIcon
|
|
|
+ size="small"
|
|
|
+ icon="material-symbols:refresh-rounded"
|
|
|
+ class="h-18px text-icon"
|
|
|
+ tooltip-content="刷新"
|
|
|
+ @click.stop="() => handleResetTreeData()"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template #sider>
|
|
|
+ <NSpin class="dict-tree" :show="getTableLoding">
|
|
|
+ <NInput v-model:value="dictPattern" clearable :placeholder="$t('common.keywordSearch')" />
|
|
|
+ <NTree
|
|
|
+ v-model:selected-keys="selectedKeys"
|
|
|
+ block-node
|
|
|
+ show-line
|
|
|
+ :data="dictData"
|
|
|
+ :show-irrelevant-nodes="false"
|
|
|
+ :pattern="dictPattern"
|
|
|
+ :filter="dictFilter"
|
|
|
+ class="infinite-scroll h-full min-h-200px py-3"
|
|
|
+ key-field="dictType"
|
|
|
+ label-field="dictName"
|
|
|
+ virtual-scroll
|
|
|
+ :selectable="!getTableLoding"
|
|
|
+ :render-label="renderLabel"
|
|
|
+ :render-suffix="renderSuffix"
|
|
|
+ @update:selected-keys="handleClickTree"
|
|
|
+ >
|
|
|
+ <template #empty>
|
|
|
+ <NEmpty description="暂无数据" class="h-full min-h-200px justify-center" />
|
|
|
+ </template>
|
|
|
+ </NTree>
|
|
|
+ </NSpin>
|
|
|
+ </template>
|
|
|
+ <LayoutTable class="h-full">
|
|
|
+ <ZTable
|
|
|
+ :columns="columns"
|
|
|
+ :immediate="false"
|
|
|
+ :api="fetchGetDictDataList"
|
|
|
+ @register="registerTable"
|
|
|
+ @add="handleAddModalData"
|
|
|
+ >
|
|
|
+ <template #op="{ row }">
|
|
|
+ <ButtonIcon
|
|
|
+ size="small"
|
|
|
+ icon="material-symbols:drive-file-rename-outline-outline"
|
|
|
+ type="primary"
|
|
|
+ class="h-18px text-icon"
|
|
|
+ tooltip-content="编辑"
|
|
|
+ @click.stop="() => handleEditData(row)"
|
|
|
+ />
|
|
|
+ <ButtonIcon
|
|
|
+ size="small"
|
|
|
+ icon="material-symbols:delete-outline"
|
|
|
+ type="error"
|
|
|
+ class="h-18px text-icon"
|
|
|
+ tooltip-content="删除"
|
|
|
+ popconfirm-content="确定删除该字典吗"
|
|
|
+ @positive-click="handleDelete(row)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </ZTable>
|
|
|
+ </LayoutTable>
|
|
|
+ <BasicModelForm @register-modal-form="registerModalForm" @submit-form="handleSubmitDictType"></BasicModelForm>
|
|
|
+ <BasicModelForm @register-modal-form="registerModalDataForm" @submit-form="handleSubmitDictData"></BasicModelForm>
|
|
|
+ </TableSiderLayout>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.dict-tree {
|
|
|
+ .n-button {
|
|
|
+ --n-padding: 8px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.n-tree__empty) {
|
|
|
+ height: 100%;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.n-spin-content) {
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.infinite-scroll) {
|
|
|
+ height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
|
|
|
+ max-height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media screen and (max-width: 1024px) {
|
|
|
+ :deep(.infinite-scroll) {
|
|
|
+ height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
|
|
|
+ max-height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.n-tree-node) {
|
|
|
+ height: 30px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.n-tree-node-switcher) {
|
|
|
+ height: 30px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.n-tree-node-switcher__icon) {
|
|
|
+ font-size: 16px !important;
|
|
|
+ height: 16px !important;
|
|
|
+ width: 16px !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|