Sfoglia il codice sorgente

feat: 添加字典

DESKTOP-USV654P\pc 1 anno fa
parent
commit
07d2f82b69

+ 63 - 0
apps/web-baicai/src/api/system/dictionary.ts

@@ -0,0 +1,63 @@
+import type { BasicFetchResult, BasicPageParams } from '#/api/model';
+
+import { requestClient } from '#/api/request';
+
+export namespace DictionaryApi {
+  export interface ListParams {
+    name?: string;
+    code?: string;
+  }
+  export interface PageParams extends ListParams, BasicPageParams {}
+
+  export interface BasicRecordItem {
+    name: string;
+    code: string;
+    sort: number;
+    remark: string;
+  }
+
+  export interface RecordItem extends BasicRecordItem {
+    id: number;
+  }
+
+  export type AppPageResult = BasicFetchResult<RecordItem>;
+
+  export const getTypePage = (params: PageParams) =>
+    requestClient.get<AppPageResult>('/dictionary/type/page', { params });
+
+  export const getTypeDetail = (id: number) =>
+    requestClient.get<RecordItem>('/dictionary/type/entity', {
+      params: { id },
+    });
+
+  export const addTypeDetail = (data: BasicRecordItem) =>
+    requestClient.post('/dictionary/type', data);
+
+  export const editTypeDetail = (data: RecordItem) =>
+    requestClient.put('/dictionary/type', data);
+
+  export const deleteTypeDetail = (id: number) =>
+    requestClient.delete('/dictionary/type', { data: { id } });
+
+  export const getItemTree = (params: ListParams) =>
+    requestClient.get<RecordItem>('/dictionary/item/page', { params });
+
+  export const getItemDetail = (id: number) =>
+    requestClient.get<RecordItem>('/dictionary/item/entity', {
+      params: { id },
+    });
+
+  export const addItemDetail = (data: BasicRecordItem) =>
+    requestClient.post('/dictionary/item', data);
+
+  export const editItemDetail = (data: RecordItem) =>
+    requestClient.put('/dictionary/item', data);
+
+  export const deleteItemDetail = (id: number) =>
+    requestClient.delete('/dictionary/item', { data: { id } });
+
+  export const getItemList = (code: string) =>
+    requestClient.get<RecordItem>('/dictionary/item/list-code', {
+      params: code,
+    });
+}

+ 1 - 0
apps/web-baicai/src/api/system/index.ts

@@ -1,6 +1,7 @@
 export * from './config';
 export * from './database';
 export * from './department';
+export * from './dictionary';
 export * from './enum';
 export * from './log';
 export * from './menu';

+ 29 - 0
apps/web-baicai/src/views/system/dict/index.vue

@@ -0,0 +1,29 @@
+<script lang="ts" setup>
+import { reactive } from 'vue';
+
+import { Page } from '@vben/common-ui';
+
+import DictionaryItem from './item/index.vue';
+import DictionaryType from './type/index.vue';
+
+const state = reactive<{ type: string }>({
+  type: '',
+});
+
+const handelTypeChange = (code: string) => {
+  state.type = code;
+};
+</script>
+
+<template>
+  <Page auto-content-height>
+    <div class="flex h-full">
+      <div class="w-1/2">
+        <DictionaryType @change="handelTypeChange" />
+      </div>
+      <div class="ml-4 w-1/2">
+        <DictionaryItem :type="state.type" />
+      </div>
+    </div>
+  </Page>
+</template>

+ 105 - 0
apps/web-baicai/src/views/system/dict/item/data.config.ts

@@ -0,0 +1,105 @@
+import type { VbenFormProps, VxeGridProps } from '#/adapter';
+
+import { DictionaryApi } from '#/api';
+import { formatterStatus } from '#/api/model';
+
+export const searchFormOptions: VbenFormProps = {
+  showCollapseButton: false,
+  schema: [
+    {
+      component: 'Input',
+      fieldName: 'code',
+      label: '编码',
+    },
+    {
+      component: 'Input',
+      fieldName: 'value',
+      label: '值',
+    },
+  ],
+};
+
+export const gridOptions: VxeGridProps<DictionaryApi.RecordItem> = {
+  toolbarConfig: {
+    refresh: true,
+    print: false,
+    export: false,
+    zoom: true,
+    custom: true,
+  },
+  treeConfig: {
+    rowField: 'id',
+    childrenField: 'children',
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    { align: 'left', field: 'value', title: '值', width: 200, treeNode: true },
+    { align: 'left', field: 'code', title: '编码', width: 120 },
+    { align: 'left', field: 'sort', title: '排序', width: 60 },
+    { align: 'left', field: 'remark', title: '备注' },
+    {
+      field: 'status',
+      title: '状态',
+      width: 60,
+      formatter: formatterStatus,
+    },
+    {
+      field: 'action',
+      fixed: 'right',
+      slots: { default: 'action' },
+      title: '操作',
+      width: 180,
+    },
+  ],
+  height: 'auto',
+  keepSource: true,
+  pagerConfig: {
+    enabled: false,
+  },
+};
+
+export const formOptions: VbenFormProps = {
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  schema: [
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入编码',
+      },
+      fieldName: 'code',
+      label: '编码',
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入值',
+      },
+      fieldName: 'value',
+      label: '值',
+      rules: 'required',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      fieldName: 'sort',
+      label: '排序',
+      rules: 'required',
+    },
+    {
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'remark',
+      label: '备注',
+    },
+  ],
+  wrapperClass: 'grid-cols-1',
+};

+ 80 - 0
apps/web-baicai/src/views/system/dict/item/edit.vue

@@ -0,0 +1,80 @@
+<script lang="ts" setup>
+import { onMounted, ref, unref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+import { DictionaryApi } from '#/api';
+
+import { formOptions } from './data.config';
+
+defineOptions({
+  name: 'UserEdit',
+});
+const emit = defineEmits(['success']);
+const modelRef = ref<Record<string, any>>({});
+const isUpdate = ref(true);
+
+const [Form, { validate, setValues, getValues }] = useVbenForm({
+  showDefaultActions: false,
+  ...formOptions,
+});
+
+const [Modal, { close, setState, getData }] = useVbenModal({
+  fullscreenButton: false,
+  draggable: true,
+  onCancel() {
+    close();
+  },
+  onConfirm: async () => {
+    try {
+      const { valid } = await validate();
+      if (valid) {
+        const values = await getValues();
+        setState({ confirmLoading: true });
+        const postParams = unref(modelRef);
+        Object.assign(postParams, values);
+        await (unref(isUpdate)
+          ? DictionaryApi.editItemDetail(postParams as DictionaryApi.RecordItem)
+          : DictionaryApi.addItemDetail(
+              postParams as DictionaryApi.BasicRecordItem,
+            ));
+        message.success('操作成功');
+
+        close();
+        emit('success');
+      }
+    } catch {
+      message.error('操作失败');
+    } finally {
+      setState({ confirmLoading: false });
+    }
+  },
+  onOpenChange: async (isOpen: boolean) => {
+    if (isOpen) {
+      setState({ loading: true });
+      const data = getData<Record<string, any>>();
+      isUpdate.value = !!data.isUpdate;
+      modelRef.value = { ...data.baseData };
+      setState({ title: unref(isUpdate) ? '编辑字典项' : '新增字典项' });
+
+      if (unref(isUpdate)) {
+        const entity = await DictionaryApi.getItemDetail(data.baseData.id);
+        modelRef.value = { ...entity };
+        setValues(entity);
+      }
+      setState({ loading: false });
+    }
+  },
+  title: '新增字典项',
+});
+
+onMounted(async () => {});
+</script>
+<template>
+  <Modal class="w-[1000px]">
+    <Form />
+  </Modal>
+</template>

+ 134 - 0
apps/web-baicai/src/views/system/dict/item/index.vue

@@ -0,0 +1,134 @@
+<script lang="ts" setup>
+import { type PropType, watch } from 'vue';
+
+import { useAccess } from '@vben/access';
+import { useVbenModal } from '@vben/common-ui';
+
+import { Button, message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter';
+import { DictionaryApi } from '#/api';
+import { TableAction } from '#/components/table-action';
+
+import { gridOptions, searchFormOptions } from './data.config';
+import FormEdit from './edit.vue';
+
+defineOptions({
+  name: 'DictionaryItem',
+});
+
+const props = defineProps({
+  type: {
+    type: String as PropType<string>,
+    default: '',
+  },
+});
+
+const { hasAccessByCodes } = useAccess();
+
+const [Grid, { reload }] = useVbenVxeGrid({
+  formOptions: searchFormOptions,
+  gridOptions: {
+    ...gridOptions,
+
+    proxyConfig: {
+      autoLoad: false,
+      ajax: {
+        query: async (_: any, formValues: any) => {
+          const postData = { ...formValues };
+          postData.type = props.type;
+          return await DictionaryApi.getItemTree({
+            ...postData,
+          });
+        },
+      },
+    },
+  },
+});
+
+const [FormEditModal, formEditApi] = useVbenModal({
+  connectedComponent: FormEdit,
+});
+
+watch(
+  () => props.type,
+  () => {
+    reload();
+  },
+  { deep: true },
+);
+
+const handleDelete = (id: number) => {
+  Modal.confirm({
+    iconType: 'info',
+    title: '删除提示',
+    content: `确定要删除选择的记录吗?`,
+    cancelText: `关闭`,
+    onOk: async () => {
+      await DictionaryApi.deleteItemDetail(id);
+      message.success('数据删除成功');
+      reload();
+    },
+  });
+};
+
+const handleEdit = (record: any, isUpdate: boolean) => {
+  if (!isUpdate && !props.type) {
+    message.warning('请先选择字典类型');
+    return;
+  }
+  formEditApi.setData({
+    isUpdate,
+    baseData: { ...record, dictionaryCode: props.type },
+  });
+
+  formEditApi.open();
+};
+
+const handelSuccess = () => {
+  reload();
+};
+</script>
+<template>
+  <div class="h-full">
+    <FormEditModal :close-on-click-modal="false" @success="handelSuccess" />
+    <Grid>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'dict-detail:add'"
+          @click="() => handleEdit({}, false)"
+        >
+          新增字典项
+        </Button>
+      </template>
+      <template #action="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '添加子项',
+              type: 'text',
+              disabled: !hasAccessByCodes(['dict-detail:add']),
+              onClick: handleEdit.bind(null, { parentId: row.id }, false),
+            },
+            {
+              label: '编辑',
+              type: 'text',
+              disabled: !hasAccessByCodes(['dict-detail:edit']),
+              onClick: handleEdit.bind(null, { id: row.id }, true),
+            },
+          ]"
+          :drop-down-actions="[
+            {
+              label: '删除',
+              type: 'text',
+              disabled: !hasAccessByCodes(['dict-detail:delete']),
+              onClick: handleDelete.bind(null, row.id),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>

+ 113 - 0
apps/web-baicai/src/views/system/dict/type/data.config.ts

@@ -0,0 +1,113 @@
+import type { VbenFormProps, VxeGridProps } from '#/adapter';
+
+import { DictionaryApi } from '#/api';
+import { formatterStatus } from '#/api/model';
+
+export const searchFormOptions: VbenFormProps = {
+  showCollapseButton: false,
+  schema: [
+    {
+      component: 'Input',
+      fieldName: 'code',
+      label: '编码',
+    },
+    {
+      component: 'Input',
+      fieldName: 'name',
+      label: '名称',
+    },
+  ],
+};
+
+export const gridOptions: VxeGridProps<DictionaryApi.RecordItem> = {
+  toolbarConfig: {
+    refresh: true,
+    print: false,
+    export: false,
+    zoom: true,
+    custom: true,
+  },
+  rowConfig: {
+    isCurrent: true,
+    isHover: true,
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    { align: 'left', field: 'code', title: '编码', width: 120 },
+    { align: 'left', field: 'name', title: '名称', width: 200 },
+    { align: 'left', field: 'sort', title: '排序', width: 60 },
+    { align: 'left', field: 'remark', title: '备注' },
+    {
+      field: 'status',
+      title: '状态',
+      width: 60,
+      formatter: formatterStatus,
+    },
+    {
+      field: 'action',
+      fixed: 'right',
+      slots: { default: 'action' },
+      title: '操作',
+      width: 100,
+    },
+  ],
+  height: 'auto',
+  keepSource: true,
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }, formValues) => {
+        return await DictionaryApi.getTypePage({
+          pageIndex: page.currentPage,
+          pageSize: page.pageSize,
+          ...formValues,
+        });
+      },
+    },
+  },
+};
+
+export const formOptions: VbenFormProps = {
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  schema: [
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入编码',
+      },
+      fieldName: 'code',
+      label: '编码',
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入名称',
+      },
+      fieldName: 'name',
+      label: '名称',
+      rules: 'required',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      fieldName: 'sort',
+      label: '排序',
+      rules: 'required',
+    },
+    {
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'remark',
+      label: '备注',
+    },
+  ],
+  wrapperClass: 'grid-cols-1',
+};

+ 75 - 0
apps/web-baicai/src/views/system/dict/type/edit.vue

@@ -0,0 +1,75 @@
+<script lang="ts" setup>
+import { ref, unref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+import { DictionaryApi } from '#/api';
+
+import { formOptions } from './data.config';
+
+defineOptions({
+  name: 'DictionaryTypeEdit',
+});
+const emit = defineEmits(['success']);
+const modelRef = ref<Record<string, any>>({});
+const isUpdate = ref(true);
+
+const [Form, { validate, setValues, getValues }] = useVbenForm({
+  showDefaultActions: false,
+  ...formOptions,
+});
+
+const [Modal, { close, setState, getData }] = useVbenModal({
+  fullscreenButton: false,
+  draggable: true,
+  onConfirm: async () => {
+    try {
+      const { valid } = await validate();
+      if (valid) {
+        const values = await getValues();
+        setState({ confirmLoading: true });
+        const postParams = unref(modelRef);
+        Object.assign(postParams, values);
+        await (unref(isUpdate)
+          ? DictionaryApi.editTypeDetail(postParams as DictionaryApi.RecordItem)
+          : DictionaryApi.addTypeDetail(
+              postParams as DictionaryApi.BasicRecordItem,
+            ));
+        message.success('操作成功');
+
+        close();
+        emit('success');
+      }
+    } catch {
+      message.error('操作失败');
+    } finally {
+      setState({ confirmLoading: false });
+    }
+  },
+  onOpenChange: async (isOpen: boolean) => {
+    if (isOpen) {
+      setState({ loading: true });
+      const data = getData<Record<string, any>>();
+      isUpdate.value = !!data.isUpdate;
+      modelRef.value = { ...data.baseData };
+      setState({ title: unref(isUpdate) ? '编辑字典类型' : '新增字典类型' });
+
+      if (unref(isUpdate)) {
+        const entity = await DictionaryApi.getTypeDetail(data.baseData.id);
+        modelRef.value = { ...entity };
+        setValues(entity);
+      }
+      setState({ loading: false });
+    }
+  },
+  title: '新增字典类型',
+});
+</script>
+<template>
+  <Modal class="w-[1000px]">
+    <Form />
+  </Modal>
+</template>

+ 99 - 0
apps/web-baicai/src/views/system/dict/type/index.vue

@@ -0,0 +1,99 @@
+<script lang="ts" setup>
+import type { VxeGridListeners } from '#/adapter/vxe-table';
+
+import { useAccess } from '@vben/access';
+import { useVbenModal } from '@vben/common-ui';
+
+import { Button, message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter';
+import { DictionaryApi } from '#/api';
+import { TableAction } from '#/components/table-action';
+
+import { gridOptions, searchFormOptions } from './data.config';
+import FormEdit from './edit.vue';
+
+defineOptions({
+  name: 'DictionaryType',
+});
+
+const emit = defineEmits(['change']);
+const { hasAccessByCodes } = useAccess();
+const gridEvents: VxeGridListeners<DictionaryApi.RecordItem> = {
+  currentChange: ({ row }) => {
+    emit('change', row.code);
+  },
+};
+
+const [Grid, { reload }] = useVbenVxeGrid({
+  gridEvents,
+  formOptions: searchFormOptions,
+  gridOptions,
+});
+
+const [FormEditModal, formEditApi] = useVbenModal({
+  connectedComponent: FormEdit,
+});
+
+const handleDelete = (id: number) => {
+  Modal.confirm({
+    iconType: 'info',
+    title: '删除提示',
+    content: `确定要删除选择的记录吗?`,
+    cancelText: `关闭`,
+    onOk: async () => {
+      await DictionaryApi.deleteTypeDetail(id);
+      message.success('数据删除成功');
+      reload();
+    },
+  });
+};
+
+const handleEdit = (record: any, isUpdate: boolean) => {
+  formEditApi.setData({
+    isUpdate,
+    baseData: { id: record.id },
+  });
+
+  formEditApi.open();
+};
+
+const handelSuccess = () => {
+  reload();
+};
+</script>
+<template>
+  <div class="h-full">
+    <FormEditModal :close-on-click-modal="false" @success="handelSuccess" />
+    <Grid>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'dict:add'"
+          @click="() => handleEdit({}, false)"
+        >
+          新增字典类型
+        </Button>
+      </template>
+      <template #action="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '编辑',
+              type: 'text',
+              disabled: !hasAccessByCodes(['dict:edit']),
+              onClick: handleEdit.bind(null, row, true),
+            },
+            {
+              label: '删除',
+              type: 'text',
+              disabled: !hasAccessByCodes(['dict:delete']),
+              onClick: handleDelete.bind(null, row.id),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>

+ 31 - 0
apps/web-baicai/src/views/system/log/components/view.vue

@@ -0,0 +1,31 @@
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+defineOptions({
+  name: 'LogView',
+});
+const modelRef = ref<Record<string, any>>({});
+
+const [Modal, { setState, getData }] = useVbenModal({
+  footer: false,
+  onOpenChange: async (isOpen: boolean) => {
+    if (isOpen) {
+      setState({ loading: true });
+      const data = getData<Record<string, any>>();
+      modelRef.value = { ...data.baseData };
+
+      setState({ loading: false });
+    }
+  },
+  title: '查询日志详情',
+});
+
+onMounted(async () => {});
+</script>
+<template>
+  <Modal class="w-[1000px]">
+    <pre>{{ modelRef?.content }}</pre>
+  </Modal>
+</template>

+ 14 - 0
apps/web-baicai/src/views/system/log/data.config.ts

@@ -192,6 +192,13 @@ export const gridExceptionOptions: VxeGridProps<LogApi.RecordItem> = {
       title: '日志消息',
       showOverflow: true,
     },
+    {
+      field: 'action',
+      fixed: 'right',
+      slots: { default: 'action' },
+      title: '操作',
+      width: 60,
+    },
   ],
   height: 'auto',
   keepSource: true,
@@ -264,6 +271,13 @@ export const gridOperateOptions: VxeGridProps<LogApi.RecordItem> = {
       title: '日志消息',
       showOverflow: true,
     },
+    {
+      field: 'action',
+      fixed: 'right',
+      slots: { default: 'action' },
+      title: '操作',
+      width: 60,
+    },
   ],
   height: 'auto',
   keepSource: true,

+ 27 - 1
apps/web-baicai/src/views/system/log/exception/index.vue

@@ -1,13 +1,19 @@
 <script lang="ts" setup>
-import { Page } from '@vben/common-ui';
+import { Page, useVbenModal } from '@vben/common-ui';
 
 import { Button, message, Modal } from 'ant-design-vue';
 
 import { useVbenVxeGrid } from '#/adapter';
 import { LogApi } from '#/api';
+import { TableAction } from '#/components/table-action';
 
+import FormView from '../components/view.vue';
 import { gridExceptionOptions, searchFormOptions } from '../data.config';
 
+const [FormViewModel, formViewApi] = useVbenModal({
+  connectedComponent: FormView,
+});
+
 const [Grid, { reload }] = useVbenVxeGrid({
   formOptions: searchFormOptions,
   gridOptions: gridExceptionOptions,
@@ -26,10 +32,18 @@ const handelClear = () => {
     },
   });
 };
+
+const handleView = (data: any) => {
+  formViewApi.setData({
+    baseData: { content: data.message },
+  });
+  formViewApi.open();
+};
 </script>
 
 <template>
   <Page auto-content-height>
+    <FormViewModel :close-on-click-modal="false" />
     <Grid>
       <template #toolbar-tools>
         <Button
@@ -41,6 +55,18 @@ const handelClear = () => {
           清空日志
         </Button>
       </template>
+
+      <template #action="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '详情',
+              type: 'text',
+              onClick: handleView.bind(null, row),
+            },
+          ]"
+        />
+      </template>
     </Grid>
   </Page>
 </template>

+ 25 - 1
apps/web-baicai/src/views/system/log/operate/index.vue

@@ -1,13 +1,19 @@
 <script lang="ts" setup>
-import { Page } from '@vben/common-ui';
+import { Page, useVbenModal } from '@vben/common-ui';
 
 import { Button, message, Modal } from 'ant-design-vue';
 
 import { useVbenVxeGrid } from '#/adapter';
 import { LogApi } from '#/api';
+import { TableAction } from '#/components/table-action';
 
+import FormView from '../components/view.vue';
 import { gridOperateOptions, searchFormOptions } from '../data.config';
 
+const [FormViewModel, formViewApi] = useVbenModal({
+  connectedComponent: FormView,
+});
+
 const [Grid, { reload }] = useVbenVxeGrid({
   formOptions: searchFormOptions,
   gridOptions: gridOperateOptions,
@@ -26,10 +32,17 @@ const handelClear = () => {
     },
   });
 };
+const handleView = (data: any) => {
+  formViewApi.setData({
+    baseData: { content: data.message },
+  });
+  formViewApi.open();
+};
 </script>
 
 <template>
   <Page auto-content-height>
+    <FormViewModel :close-on-click-modal="false" />
     <Grid>
       <template #toolbar-tools>
         <Button
@@ -41,6 +54,17 @@ const handelClear = () => {
           清空日志
         </Button>
       </template>
+      <template #action="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '详情',
+              type: 'text',
+              onClick: handleView.bind(null, row),
+            },
+          ]"
+        />
+      </template>
     </Grid>
   </Page>
 </template>