Ver código fonte

Merge branch 'master' of http://192.168.100.32:3000/fanfan/xt-front

lxz 2 anos atrás
pai
commit
f074a615da
52 arquivos alterados com 1375 adições e 721 exclusões
  1. 8 0
      src/api/sys/sysDictApi.ts
  2. 4 0
      src/components/Form/src/componentMap.ts
  3. 202 0
      src/components/Form/src/components/ApiComplex.vue
  4. 174 0
      src/components/Form/src/components/ApiInputDict.vue
  5. 10 2
      src/components/Form/src/components/ApiTreeSelect.vue
  6. 0 1
      src/components/Form/src/components/FormAction.vue
  7. 18 20
      src/components/Form/src/components/InputNumberGroup.vue
  8. 1 0
      src/components/Form/src/props.ts
  9. 2 0
      src/components/Form/src/types/index.ts
  10. 14 1
      src/components/Icon/data/icons.data.ts
  11. 17 7
      src/components/Table/src/BasicTable.vue
  12. 4 2
      src/components/TableCard/src/BasicTable.vue
  13. 42 9
      src/components/XTForm/src/XTForm.vue
  14. 2 1
      src/components/XTList/index.ts
  15. 87 0
      src/components/XTList/src/Sift.vue
  16. 0 1
      src/components/XTTab/src/XTTab.vue
  17. 1 0
      src/components/XTTitle/index.ts
  18. 157 0
      src/components/XTTitle/src/Title.vue
  19. 1 1
      src/settings/projectSetting.ts
  20. 21 0
      src/utils/index.ts
  21. 18 0
      src/views/biz/README.md
  22. 9 4
      src/views/biz/bed/near/index.vue
  23. 7 0
      src/views/biz/engineer/bed/index.vue
  24. 7 0
      src/views/biz/engineer/bio/index.vue
  25. 7 0
      src/views/biz/engineer/dialysis/index.vue
  26. 7 0
      src/views/biz/engineer/other/index.vue
  27. 7 0
      src/views/biz/engineer/upkeep/index.vue
  28. 7 0
      src/views/biz/engineer/water/index.vue
  29. 7 0
      src/views/biz/mission/article/index.vue
  30. 7 0
      src/views/biz/mission/record/index.vue
  31. 7 0
      src/views/biz/stats/dialysis/index.vue
  32. 7 0
      src/views/biz/stats/infect/index.vue
  33. 7 0
      src/views/biz/stats/patient/index.vue
  34. 7 0
      src/views/biz/stats/quality/index.vue
  35. 7 0
      src/views/biz/stats/supplies/index.vue
  36. 7 0
      src/views/biz/stats/treat/index.vue
  37. 38 0
      src/views/biz/visit/ready/data.ts
  38. 87 34
      src/views/biz/visit/ready/index.vue
  39. 5 5
      src/views/monitor/loginLog/data.ts
  40. 1 1
      src/views/monitor/loginLog/index.vue
  41. 1 7
      src/views/monitor/loginLog/viewDrawer.vue
  42. 5 5
      src/views/monitor/operLog/data.ts
  43. 49 2
      src/views/monitor/operLog/index.vue
  44. 0 6
      src/views/monitor/operLog/viewDrawer.vue
  45. 10 4
      src/views/sys/sysDict/category/FormModal.vue
  46. 54 34
      src/views/sys/sysDict/category/data.ts
  47. 204 0
      src/views/sys/sysDict/category/index.vue
  48. 31 15
      src/views/sys/sysDict/index.vue
  49. 0 61
      src/views/sys/sysDict/sysDictItemTable/FormModal.vue
  50. 0 115
      src/views/sys/sysDict/sysDictItemTable/data.ts
  51. 0 163
      src/views/sys/sysDict/sysDictItemTable/index.vue
  52. 0 220
      src/views/sys/sysDict/sysDictTable/index.vue

+ 8 - 0
src/api/sys/sysDictApi.ts

@@ -5,6 +5,7 @@ enum Api {
   sysDictAdd = '/sys/dict/add',
   sysDictEdit = '/sys/dict/edit',
   sysDictQueryPage = '/sys/dict/query/page',
+  sysDictQueryTree = '/sys/dict/query/tree',
   sysDictRemove = '/sys/dict/deleteByIds',
   sysDictQueryList = '/sys/dict/query/list',
 }
@@ -30,6 +31,13 @@ export const sysDictEdit = (params?: object) => {
 export const sysDictQueryPage = (params?: object) => {
   return defHttp.post({ url: Api.sysDictQueryPage, params: setParams(params) });
 };
+/**
+ * @description: 查询字典树,权限 - sys:dict:list
+ * @method: POST
+ */
+export const sysDictQueryTree = (params?: object) => {
+  return defHttp.post({ url: Api.sysDictQueryTree, params: params });
+};
 
 /**
  * @description: 查询字典列表,权限 - sys:dict:list

+ 4 - 0
src/components/Form/src/componentMap.ts

@@ -22,6 +22,8 @@ import {
 } from 'ant-design-vue';
 
 import ApiRadioGroup from './components/ApiRadioGroup.vue';
+import ApiInputDict from './components/ApiInputDict.vue';
+import ApiComplex from './components/ApiComplex.vue';
 import RadioButtonGroup from './components/RadioButtonGroup.vue';
 import ApiSelect from './components/ApiSelect.vue';
 import ApiTree from './components/ApiTree.vue';
@@ -49,6 +51,7 @@ componentMap.set('InputNumber', InputNumber);
 componentMap.set('AutoComplete', AutoComplete);
 
 componentMap.set('Select', Select);
+componentMap.set('ApiComplex', ApiComplex);
 componentMap.set('ApiSelect', ApiSelect);
 componentMap.set('ApiTree', ApiTree);
 componentMap.set('TreeSelect', TreeSelect);
@@ -70,6 +73,7 @@ componentMap.set('Cascader', Cascader);
 componentMap.set('Slider', Slider);
 componentMap.set('Rate', Rate);
 componentMap.set('ApiTransfer', ApiTransfer);
+componentMap.set('ApiInputDict', ApiInputDict);
 
 componentMap.set('DatePicker', DatePicker);
 componentMap.set('MonthPicker', DatePicker.MonthPicker);

+ 202 - 0
src/components/Form/src/components/ApiComplex.vue

@@ -0,0 +1,202 @@
+<template>
+  <div class="flex items-center">
+    <a-form-item>
+      <RadioGroup v-model:value="radioValue" :options="radioData" @change="handleRadioChange" />
+    </a-form-item>
+    <div v-if="checkboxData.length" class="flex items-center">
+      <div class="mb-2 mr-1">(</div>
+      <a-form-item>
+        <CheckboxGroup
+          v-model:value="checkboxValue"
+          :options="checkboxData"
+          @change="handleCheckboxChange"
+          :disabled="checkboxDisabled"
+        />
+      </a-form-item>
+      <a-form-item>
+        <a-input
+          :style="{ width: '150px', border: 0, borderBottom: '1px solid #ccc' }"
+          :placeholder="props.placeholder || '请输入'"
+          v-model:value="inputValue"
+          :disabled="disabled"
+          @change="handleInputChange"
+        />
+      </a-form-item>
+      <div class="mb-2 ml-1">)</div>
+    </div>
+    <a-form-item v-else>
+      <a-input
+        :placeholder="props.placeholder || '请输入'"
+        v-model:value="inputValue"
+        @change="handleInputChange"
+      />
+    </a-form-item>
+  </div>
+</template>
+
+<script lang="ts">
+  import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
+  import { RadioGroup, CheckboxGroup } from 'ant-design-vue';
+  import { isFunction } from '/@/utils/is';
+  import { get } from 'lodash-es';
+  import { propTypes } from '/@/utils/propTypes';
+  import { useDebounceFn } from '@vueuse/core';
+
+  export default defineComponent({
+    name: 'ApiComplex',
+    components: { RadioGroup, CheckboxGroup },
+    props: {
+      api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
+      params: { type: Object },
+      immediate: { type: Boolean, default: true },
+      resultField: propTypes.string.def(''),
+      placeholder: { type: String },
+    },
+    emits: ['change'],
+    setup(props, { attrs, emit }) {
+      const radioData = props.params?.radioData || [
+        { label: '无', value: 0 },
+        { label: '有', value: 1 },
+      ];
+      const checkboxData = ref([]);
+      const getAttrs = computed(() => {
+        return {
+          ...(props.api ? { checkboxData: unref(checkboxData) } : {}),
+          ...attrs,
+        } as any;
+      });
+      // console.log('🚀 ~ file: ApiComplex.vue:82 ~ attrs:', attrs);
+      // console.log('🚀 ~ file: ApiComplex.vue:82 ~ props:', props);
+      const radioValue = ref(0);
+      const inputValue = ref('');
+      const checkboxValue = ref([]);
+      const checkboxDisabled = computed(() => {
+        return radioValue.value ? false : true;
+      });
+      const disabled = computed(() => {
+        if (checkboxData.value.length) {
+          return checkboxValue.value &&
+            checkboxValue.value.filter(ele => {
+              return ele == '-1';
+            }).length
+            ? false
+            : true;
+        } else {
+          return radioValue.value ? false : true;
+        }
+      });
+      const isFirstLoaded = ref<Boolean>(false);
+      const loading = ref(false);
+      watch(
+        () => attrs.formValues,
+        (v: any) => {
+          // console.log('🚀 ~ file: ApiComplex.vue:102 ~ setup ~ v:', v);
+          const field = v.field;
+          const defaultValue = v?.model?.[field];
+          radioValue.value = defaultValue?.bool;
+          inputValue.value = defaultValue?.remark;
+          checkboxValue.value = defaultValue?.dictValues;
+        },
+      );
+      watch(
+        () => props.params?.dictCode,
+        () => {
+          !unref(isFirstLoaded) && fetch();
+        },
+        { deep: true },
+      );
+
+      watch(
+        () => props.immediate,
+        v => {
+          v && !isFirstLoaded.value && fetch();
+        },
+      );
+
+      onMounted(() => {
+        props.immediate && fetch();
+      });
+
+      async function fetch() {
+        const api = props.api;
+        if (!api || !isFunction(api)) return;
+        checkboxData.value = [];
+        try {
+          loading.value = true;
+          const res = await api(props.params);
+          if (Array.isArray(res)) {
+            checkboxData.value = res;
+            checkboxData.value.push({
+              label: '其他',
+              value: '-1',
+            });
+            return;
+          }
+          if (props.resultField) {
+            checkboxData.value = get(res, props.resultField) || [];
+            checkboxData.value.push({
+              label: '其他',
+              value: '-1',
+            });
+          }
+        } catch (error) {
+          console.warn(error);
+        } finally {
+          loading.value = false;
+        }
+      }
+
+      function emitChange() {
+        debouncedFn();
+      }
+
+      // 防抖
+      const debouncedFn = useDebounceFn(() => {
+        const data = {
+          bool: radioValue.value,
+          boolText: radioData,
+          dictValues: checkboxValue.value,
+          remark: inputValue.value,
+          dictCode: props.params?.dictCode,
+        };
+        emit('change', data);
+      }, 1000);
+
+      // 单选改变
+      function handleRadioChange(e) {
+        checkboxValue.value = e.target.value ? checkboxValue.value : [];
+        radioValue.value = e.target.value;
+        emitChange();
+      }
+
+      // 复选改变
+      function handleCheckboxChange(e) {
+        inputValue.value = e.includes('-1') ? inputValue.value : '';
+        checkboxValue.value = e;
+        emitChange();
+      }
+
+      // 输入改变
+      function handleInputChange(e) {
+        inputValue.value = e.target.value || '';
+        emitChange();
+      }
+
+      return {
+        getAttrs,
+        loading,
+        props,
+        radioData,
+        radioValue,
+        checkboxData,
+        checkboxValue,
+        inputValue,
+        disabled,
+        checkboxDisabled,
+        handleRadioChange,
+        handleCheckboxChange,
+        handleInputChange,
+      };
+    },
+  });
+</script>

+ 174 - 0
src/components/Form/src/components/ApiInputDict.vue

@@ -0,0 +1,174 @@
+<template>
+  <div class="flex items-center">
+    <a-input-group compact>
+      <template v-if="props.params?.dictSort">
+        <a-form-item class="select">
+          <a-select
+            v-model:value="dictValue"
+            :options="dictData"
+            @change="handleSelectChange"
+            style="width: 100%"
+          />
+        </a-form-item>
+        <a-form-item class="input">
+          <a-input v-model:value="inputValue" @change="handleInputChange" style="width: 100%" />
+        </a-form-item>
+      </template>
+      <template v-else>
+        <a-form-item class="input">
+          <a-input v-model:value="inputValue" @change="handleInputChange" style="width: 100%" />
+        </a-form-item>
+        <a-form-item class="select">
+          <a-select
+            v-model:value="dictValue"
+            :options="dictData"
+            @change="handleSelectChange"
+            style="width: 100%"
+          />
+        </a-form-item>
+      </template>
+    </a-input-group>
+  </div>
+</template>
+
+<script lang="ts">
+  import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
+  import { propTypes } from '/@/utils/propTypes';
+  import { useDebounceFn } from '@vueuse/core';
+  import { isFunction } from '/@/utils/is';
+  import { get } from 'lodash-es';
+
+  export default defineComponent({
+    name: 'ApiInputDict',
+    props: {
+      api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
+      params: { type: Object },
+      immediate: { type: Boolean, default: true },
+      resultField: propTypes.string.def(''),
+      placeholder: { type: String },
+    },
+    emits: ['change'],
+    setup(props, { attrs, emit }) {
+      const dictData = ref([]);
+      const getAttrs = computed(() => {
+        return {
+          ...(props.api ? { dictData: unref(dictData) } : {}),
+          ...attrs,
+        } as any;
+      });
+      const inputValue = ref('');
+      const dictValue = ref('');
+      const isFirstLoaded = ref<Boolean>(false);
+      const loading = ref(false);
+      watch(
+        () => attrs.formValues,
+        (v: any) => {
+          // console.log('🚀 ~ file: ApiComplex.vue:102 ~ setup ~ v:', v);
+          const field = v.field;
+          const defaultValue = v?.model?.[field];
+          dictValue.value = defaultValue?.dictValue;
+          inputValue.value = defaultValue?.input;
+        },
+      );
+      watch(
+        () => props.params?.dictSort,
+        v => {
+          console.log('dictSort v', v);
+        },
+        { deep: true },
+      );
+      watch(
+        () => props.params?.dictCode,
+        () => {
+          !unref(isFirstLoaded) && fetch();
+        },
+        { deep: true },
+      );
+      watch(
+        () => props.immediate,
+        v => {
+          v && !isFirstLoaded.value && fetch();
+        },
+      );
+
+      onMounted(() => {
+        props.immediate && fetch();
+      });
+
+      async function fetch() {
+        const api = props.api;
+        if (!api || !isFunction(api)) return;
+        dictData.value = [];
+        try {
+          loading.value = true;
+          const res = await api(props.params);
+          if (Array.isArray(res)) {
+            dictData.value = res;
+            dictValue.value = dictData.value[0]['value'];
+            return;
+          }
+          if (props.resultField) {
+            dictData.value = get(res, props.resultField) || [];
+            dictValue.value = dictData.value[0]['value'];
+          }
+        } catch (error) {
+          console.warn(error);
+        } finally {
+          loading.value = false;
+        }
+      }
+
+      function emitChange() {
+        debouncedFn();
+      }
+
+      // 防抖
+      const debouncedFn = useDebounceFn(() => {
+        const data = {
+          input: inputValue.value,
+          dictValue: dictValue.value,
+          dictCode: props.params?.dictCode,
+        };
+        emit('change', data);
+      }, 1000);
+
+      // 复选改变
+      function handleSelectChange(value) {
+        console.log('🚀 ~ file: ApiInputDict.vue:137 ~ handleSelectChange ~ e:', value);
+        dictValue.value = value;
+        emitChange();
+      }
+
+      // 输入改变
+      function handleInputChange(e) {
+        inputValue.value = e.target.value || '';
+        emitChange();
+      }
+
+      return {
+        getAttrs,
+        loading,
+        props,
+        dictValue,
+        inputValue,
+        dictData,
+        handleSelectChange,
+        handleInputChange,
+      };
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .fan-basic-form .ant-form-item {
+    border: 0;
+  }
+
+  .select {
+    width: 20%;
+  }
+
+  .input {
+    width: 80%;
+  }
+</style>

+ 10 - 2
src/components/Form/src/components/ApiTreeSelect.vue

@@ -36,7 +36,6 @@
           ...attrs,
         };
       });
-
       function handleChange(...args) {
         emit('change', ...args);
       }
@@ -76,7 +75,16 @@
         if (!isArray(result)) {
           result = get(result, props.resultField);
         }
-        treeData.value = (result as Recordable[]) || [];
+        treeData.value =
+          (props.params.root
+            ? [
+                {
+                  [attrs['fieldNames']['label']]: '根目录',
+                  [attrs['fieldNames']['key']]: '0',
+                  children: result,
+                },
+              ]
+            : (result as Recordable[])) || [];
         isFirstLoaded.value = true;
         emit('options-change', treeData.value);
       }

+ 0 - 1
src/components/Form/src/components/FormAction.vue

@@ -16,7 +16,6 @@
 
         <Button
           type="primary"
-          class="mr-2"
           v-bind="getSubmitBtnOptions"
           @click="submitAction"
           v-if="showSubmitButton"

+ 18 - 20
src/components/Form/src/components/InputNumberGroup.vue

@@ -26,24 +26,11 @@
         />
       </div>
     </template>
-
-    <!-- <a-input
-      v-model:value="getValueSplit[0]"
-      style="width: 100px; text-align: center"
-      :placeholder="props.placelhoders[0]"
-    />
-
-    <a-input
-      v-model:value="getValueSplit[0]"
-      style="width: 100px; text-align: center; border-left: 0"
-      :placeholder="props.placelhoders[1]"
-    /> -->
   </InputGroup>
 </template>
 <script lang="ts">
-  import { defineComponent, computed, reactive } from 'vue';
+  import { defineComponent, computed, reactive, watch } from 'vue';
   import { Input, InputNumber, FormItem } from 'ant-design-vue';
-  import { useAttrs } from '/@/hooks/core/useAttrs';
 
   type OptionsItem = {
     min: number;
@@ -82,8 +69,7 @@
       },
     },
     emits: ['change'],
-    setup(props, { emit }) {
-      const attrs = useAttrs();
+    setup(props, { attrs, emit }) {
       const collect = reactive({
         min: 0,
         max: 0,
@@ -93,13 +79,10 @@
       const getOptions = computed((): OptionsItem[] => {
         const { options } = props;
         if (!options || options?.length === 0) return [];
-
-        // console.log('111111111 getOptions, props', props.value);
         const ret = options.map(item => {
+          item.key = Math.round(Math.random() * 1000);
           if (props.value == undefined || props.value == null) {
             item.value = undefined;
-            // 生成随机key,使页面刷新
-            item.key = Math.round(Math.random() * 1000);
           }
           return item;
         }) as OptionsItem[];
@@ -117,6 +100,21 @@
         emit('change', arr);
       }
 
+      // 监听赋值
+      watch(
+        () => attrs.formValues,
+        (v: any) => {
+          const field = v.field;
+          const defaultValue = v?.model?.[field];
+          if (defaultValue?.length) {
+            props.options.map((ele, idx) => {
+              ele['value'] = defaultValue[idx];
+              return ele;
+            });
+          }
+        },
+      );
+
       return { getOptions, attrs, handleChange, props };
     },
   });

+ 1 - 0
src/components/Form/src/props.ts

@@ -35,6 +35,7 @@ export const basicProps = {
   baseRowStyle: {
     type: Object as PropType<CSSProperties>,
   },
+  // Row
   baseRowGutter: {
     type: Array,
     default: () => [16, 0],

+ 2 - 0
src/components/Form/src/types/index.ts

@@ -121,4 +121,6 @@ export type ComponentType =
   | 'FormColorPicker'
   | 'InputNumberGroup'
   | 'RadioDescGroup'
+  | 'ApiComplex'
+  | 'ApiInputDict'
   | 'Cron';

+ 14 - 1
src/components/Icon/data/icons.data.ts

@@ -1,6 +1,20 @@
 export default {
   prefix: 'ant-design',
   icons: [
+    // 左侧菜单icon
+    'icon-xt-set_default|iconfont',
+    'icon-xt-home_default|iconfont',
+    'icon-xt-medical_default|iconfont',
+    'icon-xt-record_default|iconfont',
+    'icon-xt-reservation_default|iconfont',
+    'icon-xt-engineer_default|iconfont',
+    'icon-xt-drug_default|iconfont',
+    'icon-xt-statistics_default|iconfont',
+    'icon-xt-health_default|iconfont',
+    'icon-xt-cost_default|iconfont',
+    'icon-xt-quality_default|iconfont',
+    'icon-xt-machine_default|iconfont',
+    // 左侧菜单icon End
     'icon-cluster|iconfont',
     'icon-gateway|iconfont',
     'icon-unorderedlist|iconfont',
@@ -25,7 +39,6 @@ export default {
     'icon-robot|iconfont',
     'icon-thunderbolt|iconfont',
     'icon-fire|iconfont',
-    'icon-robot|iconfont',
     'icon-gift|iconfont',
     'icon-desktop|iconfont',
     'icon-crown|iconfont',

+ 17 - 7
src/components/Table/src/BasicTable.vue

@@ -379,21 +379,23 @@
 
       .ant-form {
         width: 100%;
-        padding: 12px 10px 6px;
         margin-bottom: 16px;
-        background-color: @component-background;
+        padding: 12px 10px 6px;
         border-radius: 2px;
+        background-color: @component-background;
       }
     }
 
-    .ant-tag {
-      margin-right: 0;
+    .ant-table-cell {
+      .ant-tag {
+        margin-right: 0;
+      }
     }
 
     .ant-table-wrapper {
       padding: 6px;
-      background-color: @component-background;
       border-radius: 2px;
+      background-color: @component-background;
 
       .ant-table-title {
         min-height: 40px;
@@ -403,6 +405,14 @@
       .ant-table.ant-table-bordered .ant-table-title {
         border: none !important;
       }
+
+      .ant-table.ant-table-bordered > .ant-table-container {
+        border: none !important;
+      }
+
+      table {
+        border-left: 1px solid #f0f0f0;
+      }
     }
 
     .ant-table {
@@ -411,10 +421,10 @@
 
       &-title {
         display: flex;
+        align-items: center;
+        justify-content: space-between;
         padding: 8px 6px;
         border-bottom: none;
-        justify-content: space-between;
-        align-items: center;
       }
 
       //.ant-table-tbody > tr.ant-table-row-selected td {

+ 4 - 2
src/components/TableCard/src/BasicTable.vue

@@ -75,7 +75,9 @@
   export default defineComponent({
     components: {
       Checkbox,
+      // eslint-disable-next-line vue/no-reserved-component-names
       Table,
+      // eslint-disable-next-line vue/no-reserved-component-names
       Button,
       HeaderCell,
     },
@@ -111,7 +113,7 @@
       const formRef = ref(null);
       const innerPropsRef = ref<Partial<BasicTableProps>>();
 
-      const { prefixCls } = useDesign('basic-table');
+      const { prefixCls } = useDesign('basic-table-card');
       const [registerForm, formActions] = useForm();
 
       const getProps = computed(() => {
@@ -399,7 +401,7 @@
 <style lang="less">
   @border-color: #cecece4d;
 
-  @prefix-cls: ~'@{namespace}-basic-table';
+  @prefix-cls: ~'@{namespace}-basic-table-card';
 
   [data-theme='dark'] {
     .ant-table-tbody > tr:hover.ant-table-row-selected > td,

+ 42 - 9
src/components/XTForm/src/XTForm.vue

@@ -14,8 +14,12 @@
               :style="{ width: item.width + 'px' }"
               :defaultValue="item.defaultValue"
               @change="handleChange"
-              :size="item.size || 'default'"
-            />
+              :size="item.size || 'large'"
+            >
+              <template #prefix v-if="item.prefix">
+                <i :class="['iconfont', item.prefix]" />
+              </template>
+            </Input>
           </div>
           <div v-if="item.componentType == ComponentEnum.Select">
             <Select
@@ -25,7 +29,7 @@
               :style="{ width: item.width + 'px' }"
               :defaultValue="item.defaultValue"
               @change="handleChange"
-              :size="item.size || 'default'"
+              :size="item.size || 'large'"
             >
               <SelectOption
                 v-for="r in item.dicts"
@@ -56,8 +60,7 @@
               :defaultValue="item.defaultValue"
               :disabled-date="item.disabledDate"
               @change="handleChange"
-              :size="item.size || 'default'"
-              :picker="item.picker"
+              :size="item.size || 'large'"
             />
           </div>
           <div v-if="item.componentType == ComponentEnum.RangePicker">
@@ -69,12 +72,14 @@
               :disabled="item.disabled ? true : false"
               :style="{ width: item.width + 'px' }"
               @change="handleChange"
-              :size="item.size || 'default'"
+              :size="item.size || 'large'"
+              :ranges="ranges"
             />
           </div>
           <div v-if="item.componentType == ComponentEnum.IconBtn">
-            <div class="icon-btn">
-              <i class="iconfont icon-lock" @click="handleClick(item)" />
+            <div :class="['icon-btn', item.selected ? 'icon-btn--selected' : '']">
+              <div class="icon-btn_count" v-if="item.count">{{ item.count }}</div>
+              <i class="iconfont icon-xt-screen_default" @click="handleClick(item)" />
             </div>
           </div>
         </FormItem>
@@ -109,6 +114,8 @@
     // Tooltip,
     // Upload,
   } from 'ant-design-vue';
+  import { dateRanges } from '/@/utils';
+
   interface Props {
     formShow?: boolean;
     formData: Array<{
@@ -131,6 +138,11 @@
       disabledDate?: any;
       picker?: string;
       size?: string;
+      // filter btn 是否选中
+      selected?: string;
+      // iconBtn 数
+      count?: number;
+      prefix?: string;
     }>;
   }
   withDefaults(defineProps<Props>(), {
@@ -138,6 +150,7 @@
     formShow: true,
   });
   const emit = defineEmits(['change', 'click']);
+  const ranges = ref(dateRanges());
   // 表单
   const formRef = ref<any>({});
   function handleChange() {
@@ -165,6 +178,7 @@
   }
 
   .icon-btn {
+    position: relative;
     width: 40px;
     height: 40px;
     background: #fff;
@@ -174,9 +188,28 @@
     line-height: 40px;
     cursor: pointer;
 
+    &--selected,
+    &:hover {
+      color: #0075ff;
+    }
+
+    &_count {
+      position: absolute;
+      top: 2px;
+      right: 4px;
+      display: block;
+      font-size: 10px;
+      width: 16px;
+      height: 16px;
+      line-height: 14px;
+      color: #fff;
+      background: #ff5d39;
+      border-radius: 8px;
+      border: 1px solid #fff;
+    }
+
     & .iconfont {
       font-size: 24px;
-      color: #0075ff;
       margin-right: 0;
     }
   }

+ 2 - 1
src/components/XTList/index.ts

@@ -1,4 +1,5 @@
 import List from './src/List.vue';
 import Menu from './src/Menu.vue';
+import Sift from './src/Sift.vue';
 
-export { List, Menu };
+export { List, Menu, Sift };

+ 87 - 0
src/components/XTList/src/Sift.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="flex sift">
+    <div class="sift-left">
+      <div class="">筛选条件: </div>
+      <div class="sift-item" v-for="item in data" :key="item.label">
+        <span class="sift-item_label">{{ item.label }}: </span>
+        <span class="sift-item_value">{{ item.value }} </span>
+        <i class="iconfont icon-xt-close_default sift-item_icon" @click="handleClose(item)" />
+      </div>
+    </div>
+    <div v-if="data.length" class="sift-right" @click="handleClear"> 清除 </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  interface Props {
+    tit?: string;
+    data: Array<{
+      field: string;
+      label: string;
+      value: string;
+    }>;
+  }
+
+  withDefaults(defineProps<Props>(), {
+    tit: '筛选条件',
+    data: () => [],
+  });
+  const emit = defineEmits(['close', 'clear']);
+  function handleClose(item) {
+    console.log('🚀 ~ file: Sift.vue:28 ~ handleClose ~ item:', item);
+    emit('close', item);
+  }
+
+  function handleClear() {
+    emit('clear');
+  }
+</script>
+
+<style lang="less" scoped>
+  .sift {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+
+    &-left {
+      display: flex;
+      align-items: center;
+    }
+
+    &-right {
+      font-size: 14px;
+      font-weight: 400;
+      color: #0075ff;
+      border-bottom: 1px solid #0075ff;
+      cursor: pointer;
+    }
+
+    &-item {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-left: 8px;
+      padding: 0 8px;
+      min-width: 80px;
+      height: 32px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #000a18;
+      line-height: 32px;
+      background: #e9f5ff;
+      border-radius: 4px;
+
+      &_label {
+        margin-right: 4px;
+      }
+
+      &_icon {
+        margin-left: 8px;
+        font-size: 12px;
+        color: #9198a5;
+        cursor: pointer;
+      }
+    }
+  }
+</style>

+ 0 - 1
src/components/XTTab/src/XTTab.vue

@@ -83,7 +83,6 @@
   // }
   .tab-list {
     display: flex;
-    justify-content: center;
     align-items: center;
 
     &_item {

+ 1 - 0
src/components/XTTitle/index.ts

@@ -0,0 +1 @@
+export { default as XTTitle } from './src/Title.vue';

+ 157 - 0
src/components/XTTitle/src/Title.vue

@@ -0,0 +1,157 @@
+<template>
+  <div class="xt-title">
+    <div class="xt-title_left">
+      <i
+        v-if="goBack"
+        :class="['iconfont xt-title_left-back', 'icon-xt-drawer-return_selected']"
+        @click="$router.go(-1)"
+      />
+      <span>{{ title }}</span>
+      <slot name="title" />
+    </div>
+    <div class="xt-title_right">
+      <slot name="prefix" />
+      <div v-if="rightData" class="flex">
+        <div
+          v-for="item in rightData"
+          :key="item.type"
+          :class="['xt-title_item', item.icon ? 'xt-title_item--icon' : 'xt-title_item--btn']"
+        >
+          <a-upload
+            v-if="item.type == 'upload'"
+            :max-count="1"
+            action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
+          >
+            <i class="iconfont icon-xt-details_upload_default" />
+          </a-upload>
+          <!-- icon-xt-print_default -->
+          <i v-else-if="item.icon" :class="['iconfont', item.icon]" @click="handleClick(item)" />
+          <a-button
+            v-else
+            :type="item.btnType || 'primary'"
+            size="large"
+            @click="handleClick(item)"
+          >
+            <template #icon>
+              <i :class="['iconfont mr-1', item.btnIcon]" />
+            </template>
+            {{ item.btnText }}
+          </a-button>
+        </div>
+      </div>
+      <slot name="suffix" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  interface Props {
+    // 标题
+    title: string | number | undefined;
+    goBack?: boolean;
+    rightData?: Array<{
+      type: string;
+      icon?: string;
+      iconType?: string | 'primary';
+      isBtn: boolean;
+      btnText?: string;
+      btnType?: 'primary' | 'ghost' | 'dashed' | 'link' | 'text' | 'default';
+      btnIcon?: string;
+    }>;
+    upload?: {
+      accept: string;
+      action: string;
+      data: object;
+      maxCount: number;
+    };
+  }
+  withDefaults(defineProps<Props>(), {
+    title: '',
+    goBack: false,
+    // upload: {
+    //   accept:
+    // }
+    rightData: () => [],
+  });
+  const emit = defineEmits(['click']);
+
+  // 回调
+  function handleClick(data) {
+    console.log('🚀 ~ file: Title.vue:55 ~ handleClick ~ data:', data);
+    emit('click', data);
+  }
+</script>
+
+<style lang="less" scoped>
+  .xt-title {
+    display: flex;
+    justify-content: space-between;
+
+    &_left {
+      font-size: 24px;
+      font-weight: 600;
+      color: #000a18;
+      display: flex;
+      align-items: center;
+
+      &-back {
+        font-size: 20px;
+        position: relative;
+        color: #262a41;
+        margin-right: 24px;
+        cursor: pointer;
+
+        &::after {
+          position: absolute;
+          content: '';
+          top: 5px;
+          right: -14px;
+          width: 1px;
+          height: 22px;
+          background: rgb(222 226 231 / 100%);
+          // background: rgb(63, 119, 187);
+        }
+      }
+    }
+
+    &_right {
+      display: flex;
+    }
+
+    &_item {
+      margin-right: 10px;
+      cursor: pointer;
+
+      &--icon {
+        width: 40px;
+        height: 40px;
+        background: #fff;
+        border-radius: 4px;
+        color: #3d4155;
+        text-align: center;
+
+        & .iconfont {
+          font-size: 20px;
+          line-height: 40px;
+        }
+
+        &:hover {
+          color: #0075ff;
+        }
+      }
+
+      &--btn {
+        height: 40px;
+        border-radius: 4px;
+
+        & .iconfont {
+          font-size: 14px;
+        }
+      }
+
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+  }
+</style>

+ 1 - 1
src/settings/projectSetting.ts

@@ -121,7 +121,7 @@ const setting: ProjectConfig = {
   multiTabsSetting: {
     cache: false,
     // Turn on
-    show: true,
+    show: false,
     // Is it possible to drag and drop sorting tabs
     canDrag: true,
     // Turn on quick actions

+ 21 - 0
src/utils/index.ts

@@ -5,6 +5,8 @@ import { unref } from 'vue';
 import { isObject } from '/@/utils/is';
 import { cloneDeep } from 'lodash-es';
 
+import dayjs from 'dayjs';
+
 export const noop = () => {};
 
 /**
@@ -200,3 +202,22 @@ export function setParams(params: any) {
   console.log('🚀 ~ file: index.ts:194 ~ setParams ~ params', params);
   return params;
 }
+
+/**
+ * @description: Date Range 预设范围 related enumeration
+ */
+export function dateRanges(format = 'YYYY-MM-DD') {
+  console.log('🚀 ~ file: index.ts:210 ~ dateRanges ~ format:', format);
+  return {
+    今天: [dayjs(), dayjs()],
+    本周: [dayjs(), dayjs().endOf('week')],
+    上周: [
+      dayjs().add(-1, 'week').startOf('week').add(1, 'day'),
+      dayjs().add(-1, 'week').endOf('week').add(1, 'day'),
+    ],
+    本月: [dayjs(), dayjs().endOf('month')],
+    上月: [dayjs().add(-1, 'month').startOf('month'), dayjs().add(-1, 'month').endOf('month')],
+    // Today: [dayjs(), dayjs()],
+    // 'This Month': [dayjs(), dayjs().endOf('month')],
+  };
+}

+ 18 - 0
src/views/biz/README.md

@@ -13,3 +13,21 @@
     - long 长期排床模板
     - memo 排床备忘录
     - person 个人排班
+  - engineer 工程师端
+    - bed 床位管理
+    - water 水处理设备
+    - other 其他设备
+    - upkeep 保养记录
+    - dialysis 透析设备
+    - bio 生化设备
+    - dics 业务字典
+  - mission 宣教管理
+    - article 宣教库
+    - recycling 回收站
+  - stats 统计分析
+    - treat 透析治疗
+    - supplies 药品耗材
+    - patient 患者信息
+    - quality 质量管理
+    - infect 院感信息
+    - dialysis 透析设备

+ 9 - 4
src/views/biz/bed/near/index.vue

@@ -126,8 +126,8 @@
           <div
             v-for="item in bedHas"
             :key="item.id"
-            :class="['edit-item', item.id == notBedId ? 'edit-item--has' : '']"
-            @click="handleNotBed(item)"
+            :class="['edit-item', item.id == hasBedId ? 'edit-item--has' : '']"
+            @click="handleHasBed(item)"
           >
             {{ item.name }}
           </div>
@@ -344,6 +344,11 @@
     hasBedId.value = null;
     setCnt();
   }
+  function handleHasBed(data) {
+    hasBedId.value = data.id;
+    notBedId.value = null;
+    setCnt();
+  }
   function callFilter(data) {
     console.log('🚀 ~ file: index.vue:173 ~ callFilter ~ data:', data);
     if (data.sailings || data.sailings == 0) {
@@ -526,7 +531,7 @@
 
       &--fixed {
         position: fixed;
-        top: 80px;
+        top: 48px;
         z-index: 8;
         padding-bottom: 4px;
       }
@@ -704,7 +709,7 @@
 
   .edit {
     position: fixed;
-    top: 100px;
+    top: 60px;
     right: 20px;
     min-width: 250px;
     height: calc(100vh - 120px);

+ 7 - 0
src/views/biz/engineer/bed/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/engineer/bio/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/engineer/dialysis/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/engineer/other/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/engineer/upkeep/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/engineer/water/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/mission/article/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/mission/record/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/stats/dialysis/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/stats/infect/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/stats/patient/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/stats/quality/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/stats/supplies/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/views/biz/stats/treat/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 38 - 0
src/views/biz/visit/ready/data.ts

@@ -1,6 +1,7 @@
 import { BasicColumn } from '/@/components/Table';
 import { FormSchema } from '/@/components/Form';
 import { radioBoolean } from '/@/utils/filters';
+import { listDictModel } from '/@/api/common';
 
 export const dataFormSchema: FormSchema[] = [
   {
@@ -56,6 +57,24 @@ export const dataFormSchema: FormSchema[] = [
       extra: '123123',
     },
   },
+  {
+    field: 'configName333',
+    label: '有无异常',
+    component: 'ApiComplex',
+    componentProps: ({ formModel }) => {
+      return {
+        placeholder: '请输入异常内容',
+        api: listDictModel,
+        params: {
+          dictCode: 'sys_dict_type',
+        },
+        onChange: e => {
+          console.log('🚀 ~ file: data.ts:81 ~ e:', e);
+          return (formModel['configName333'] = e);
+        },
+      };
+    },
+  },
   {
     field: 'configKey',
     label: '参数键名',
@@ -85,6 +104,25 @@ export const dataFormSchema: FormSchema[] = [
       };
     },
   },
+  {
+    field: 'idCard',
+    label: '证件号码',
+    component: 'ApiInputDict',
+    componentProps: ({ formModel }) => {
+      return {
+        placeholder: '请输入异常内容',
+        api: listDictModel,
+        params: {
+          dictCode: 'sys_dict_type',
+          dictSort: true,
+        },
+        onChange: e => {
+          console.log('🚀 ~ file: data.ts:81 ~ e:', e);
+          return (formModel['idCard'] = e);
+        },
+      };
+    },
+  },
   {
     field: 'sort',
     label: '排序',

+ 87 - 34
src/views/biz/visit/ready/index.vue

@@ -1,22 +1,30 @@
 <template>
-  <div>
-    透前准备
+  <div class="m-4">
+    <!-- 透前准备
     <a-button type="primary" @click="plusFn">
       {{ countRef }}
-    </a-button>
-    <XTTab
-      type="illness"
-      :width="120"
-      :selected="tabSelected"
-      :data="tabData"
-      @item-click="callTab"
-    />
+    </a-button> -->
+    <XTTitle title="透析病历" :right-data="titleData" />
+
     <div class="mt-6" />
     <XTTab type="illness11" :selected="tabSelected" :data="tabData1" @item-click="callTab" />
     <div class="mt-6" />
     <div class="mt-6" />
     <XTTab type="illness11" :selected="tabSelected" :data="tabData2" @item-click="callTab" />
     <div class="mt-6" />
+    <div class="flex justify-between my-4">
+      <XTTab
+        type="illness"
+        :width="120"
+        :selected="tabSelected"
+        :data="tabData"
+        @item-click="callTab"
+      />
+      <XTForm :form-data="formData" />
+    </div>
+    <div class="flex">
+      <Sift :data="siftData" />
+    </div>
     <div class="m-6">
       <BasicForm @register="registerForm" @field-value-change="filedChange" />
     </div>
@@ -30,9 +38,6 @@
         </template>
       </BasicTable>
     </div>
-    <div>
-      <XTForm :form-data="formData" />
-    </div>
     <div class="mx-6 my-2">
       <ChartsCard
         title="透前血压趋势"
@@ -54,15 +59,13 @@
     <div class="mx-6 my-2">
       <TimeLine :data="timeLineData" @hover="callHover">
         <template #head>
-          <div class="timeline-outer" v-if="timeOuter">
-            <div
-              class="timeline-outer_item animate__animated animate__slideInLeft"
-              @click="handleAdd"
-              >AVF</div
-            >
-            <div class="timeline-outer_item animate__animated animate__slideInLeft">AVH</div>
-            <div class="timeline-outer_item animate__animated animate__slideInLeft">TCG</div>
-          </div>
+          <transition class="animate__animated animate__slideInLeft">
+            <div class="timeline-outer" v-if="timeOuter">
+              <div class="timeline-outer_item" @click="handleAdd">AVF</div>
+              <div class="timeline-outer_item">AVH</div>
+              <div class="timeline-outer_item">TCG</div>
+            </div>
+          </transition>
         </template>
       </TimeLine>
     </div>
@@ -86,6 +89,7 @@
   import { XTTab } from '/@/components/XTTab/index';
   import { XTCard } from '/@/components/XTCard/index';
   import { XTForm } from '/@/components/XTForm/index';
+  import { XTTitle } from '/@/components/XTTitle/index';
   import { ColorEnum } from '/@/enums/colorEnum';
   import { BasicForm, useForm } from '/@/components/Form';
   import { dataFormSchema, columns } from './data';
@@ -95,10 +99,38 @@
   import DescCard from '/@/components/XTCard/src/DescCard.vue';
   import TimeLine from '/@/components/XTTimeLine/src/TimeLine.vue';
   import List from '/@/components/XTList/src/List.vue';
-  import { TransitionPresets, useTransition } from '@vueuse/core';
+  import { Sift } from '/@/components/XTList/index';
+  // import { TransitionPresets, useTransition } from '@vueuse/core';
 
   const tabSelected = ref('0');
   const dataSource = ref([]);
+  const titleData = [
+    {
+      type: 'import',
+      icon: 'icon-xt-import_default',
+    },
+    {
+      type: 'export',
+      icon: 'icon-xt-export_default',
+    },
+    {
+      type: 'add',
+      btnIcon: 'icon-xt-add_default',
+      btnText: '患者建档',
+    },
+  ];
+  const siftData = [
+    {
+      field: 'gender',
+      label: '性别',
+      value: '男',
+    },
+    {
+      field: 'age',
+      label: '年龄区间',
+      value: '25岁-50岁',
+    },
+  ];
   const chartData = {
     colors: [
       {
@@ -206,7 +238,7 @@
     },
   ];
   const timeOuter = ref(false);
-  onMounted(() => {
+  onMounted(async () => {
     for (let i = 0; i < 10; i++) {
       const obj = {
         createTime: '2023-05-23 10:09:48',
@@ -224,15 +256,28 @@
       };
       dataSource.value.push(obj);
     }
+
+    await setFieldsValue({
+      configName333: {
+        bool: 1,
+        remark: '321321dsada',
+        dictValues: ['DIC_BIZ', '-1'],
+      },
+      configValue: [100, 150],
+      idCard: {
+        input: '测试',
+        dictValue: 'DIC_BIZ',
+      },
+    });
   });
-  const count = ref(0);
-  const countRef = useTransition(count, {
-    duration: 1000,
-    transition: TransitionPresets.easeInCirc,
-  });
-  function plusFn() {
-    count.value = Math.round(Math.random() * 100);
-  }
+  // const count = ref(0);
+  // const countRef = useTransition(count, {
+  //   duration: 1000,
+  //   transition: TransitionPresets.easeInCirc,
+  // });
+  // function plusFn() {
+  //   count.value = Math.round(Math.random() * 100);
+  // }
   // const [registerTable, { reload, getCacheColumns, setColumns }] = useTable({
   const [registerTable] = useTable({
     id: 'sys_config',
@@ -250,10 +295,10 @@
     showIndexColumn: false,
     pagination: false,
     // maxHeight: 350,
-    canResize: true,
+    canResize: false,
   });
 
-  const [registerForm] = useForm({
+  const [registerForm, { setFieldsValue }] = useForm({
     layout: 'vertical',
     labelWidth: '100%',
     baseColProps: {
@@ -373,9 +418,17 @@
       format: 'YYYY-MM-DD',
       valueFormat: 'YYYY-MM-DD',
     },
+    {
+      name: 'text233',
+      componentType: 'RangePicker',
+      placeholder: '请输入',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
     {
       name: 'filter',
       componentType: 'IconBtn',
+      count: 4,
     },
   ];
   // card 标签组

+ 5 - 5
src/views/monitor/loginLog/data.ts

@@ -23,10 +23,10 @@ export const columns: BasicColumn[] = [
   //   title: '方法名称',
   //   dataIndex: 'javaMethod',
   // },
-  // {
-  //   title: '操作日志类型',
-  //   dataIndex: 'type',
-  // },
+  {
+    title: '日志类型',
+    dataIndex: 'type',
+  },
   {
     title: '操作时间',
     dataIndex: 'opTime',
@@ -228,7 +228,7 @@ export const viewSchema: DescItem[] = [
     field: 'javaMethod',
   },
   {
-    label: '操作日志类型',
+    label: '日志类型',
     field: 'type',
   },
   {

+ 1 - 1
src/views/monitor/loginLog/index.vue

@@ -94,7 +94,7 @@
   const useApp = useAppStore();
   console.log(useApp.getMenuSetting);
   onBeforeMount(async () => {
-    typeOptions.value = await listDictModel({ dictCode: 'sys_log_type' });
+    typeOptions.value = await listDictModel({ dictCode: 'sys_login_log_type' });
     responseTypeOptions.value = await listDictModel({ dictCode: 'sys_response_type' });
     resultJsonOptions.value = await listDictModel({ dictCode: 'sys_response_type' });
   });

+ 1 - 7
src/views/monitor/loginLog/viewDrawer.vue

@@ -24,12 +24,8 @@
   const width = '45%';
 
   const typeOptions = ref();
-  const responseTypeOptions = ref();
-  const resultJsonOptions = ref();
   onBeforeMount(async () => {
-    typeOptions.value = await listDictModel({ dictCode: 'sys_log_type' });
-    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_response_type' });
-    resultJsonOptions.value = await listDictModel({ dictCode: 'sys_response_type' });
+    typeOptions.value = await listDictModel({ dictCode: 'sys_login_log_type' });
   });
   const [registerDrawer] = useDrawerInner(async data => {
     console.log('::::::::::', data.record);
@@ -37,8 +33,6 @@
     descData.value = {
       ...resData,
       type: formatDictValue(typeOptions.value, resData.type),
-      responseType: formatDictValue(responseTypeOptions.value, resData.responseType),
-      resultJson: formatDictValue(resultJsonOptions.value, resData.resultJson),
     };
   });
   const [registerDesc] = useDescription({

+ 5 - 5
src/views/monitor/operLog/data.ts

@@ -23,10 +23,10 @@ export const columns: BasicColumn[] = [
   //   title: '方法名称',
   //   dataIndex: 'javaMethod',
   // },
-  // {
-  //   title: '操作日志类型',
-  //   dataIndex: 'type',
-  // },
+  {
+    title: '操作日志类型',
+    dataIndex: 'type',
+  },
   {
     title: '操作时间',
     dataIndex: 'opTime',
@@ -228,7 +228,7 @@ export const viewSchema: DescItem[] = [
     field: 'javaMethod',
   },
   {
-    label: '操作日志类型',
+    label: '日志类型',
     field: 'type',
   },
   {

+ 49 - 2
src/views/monitor/operLog/index.vue

@@ -1,5 +1,18 @@
 <template>
-  <div>
+  <div class="m-4">
+    <div>
+      <XTTitle title="操作日志" :go-back="true" />
+      <div class="flex items-center justify-between my-4">
+        <XTTab
+          type="opLog"
+          :width="120"
+          :selected="tabSelected"
+          :data="typeOptions"
+          @item-click="callTab"
+        />
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
     <BasicTable @register="registerTable">
       <template #bodyCell="{ column, record }">
         <template v-if="column.key === 'type'">
@@ -78,6 +91,25 @@
   import { formatDictColor, formatDictValue } from '/@/utils'; //
   import { useDrawer } from '/@/components/Drawer';
 
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { XTTab } from '/@/components/XTTab/index';
+  import { XTForm } from '/@/components/XTForm/index';
+
+  // formdata
+  const formData = [
+    {
+      name: 'opName',
+      componentType: 'Input',
+      placeholder: '请输入操作名称',
+      width: 200,
+      prefix: 'icon-xt-search',
+    },
+  ];
+  // tab 切换选中
+  const tabSelected = ref();
+  // 操作名称
+  const opName = ref('');
+
   const typeOptions = ref();
   const responseTypeOptions = ref();
   const resultJsonOptions = ref();
@@ -85,6 +117,11 @@
     typeOptions.value = await listDictModel({ dictCode: 'sys_log_type' });
     responseTypeOptions.value = await listDictModel({ dictCode: 'sys_response_type' });
     resultJsonOptions.value = await listDictModel({ dictCode: 'sys_response_type' });
+    typeOptions.value = typeOptions.value.map(ele => {
+      ele.key = ele.value;
+      return ele;
+    });
+    tabSelected.value = typeOptions.value[0].value;
   });
 
   const { createConfirm, createMessage } = useMessage();
@@ -175,7 +212,7 @@
 
   // 表格请求之前,对参数进行处理, 添加默认 排序
   function handleBeforeFetch(params) {
-    return { ...params, orders: tableSort.value };
+    return { ...params, orders: tableSort.value, type: tabSelected.value, opName: opName.value };
   }
 
   // 弹窗回调事件
@@ -184,4 +221,14 @@
     console.log(values);
     await reload();
   }
+
+  // 组件回调
+  async function callTab(data) {
+    tabSelected.value = data.value;
+    await reload();
+  }
+  async function callForm(data) {
+    opName.value = data.opName;
+    await reload();
+  }
 </script>

+ 0 - 6
src/views/monitor/operLog/viewDrawer.vue

@@ -24,12 +24,8 @@
   const width = '45%';
 
   const typeOptions = ref();
-  const responseTypeOptions = ref();
-  const resultJsonOptions = ref();
   onBeforeMount(async () => {
     typeOptions.value = await listDictModel({ dictCode: 'sys_log_type' });
-    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_response_type' });
-    resultJsonOptions.value = await listDictModel({ dictCode: 'sys_response_type' });
   });
   const [registerDrawer] = useDrawerInner(async data => {
     console.log('::::::::::', data.record);
@@ -37,8 +33,6 @@
     descData.value = {
       ...resData,
       type: formatDictValue(typeOptions.value, resData.type),
-      responseType: formatDictValue(responseTypeOptions.value, resData.responseType),
-      resultJson: formatDictValue(resultJsonOptions.value, resData.resultJson),
     };
   });
   const [registerDesc] = useDescription({

+ 10 - 4
src/views/sys/sysDict/sysDictTable/FormModal.vue → src/views/sys/sysDict/category/FormModal.vue

@@ -1,6 +1,6 @@
 <template>
   <BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
-    <BasicForm @register="registerForm" />
+    <BasicForm @register="registerForm" layout="vertical" />
   </BasicModal>
 </template>
 <script lang="ts" setup>
@@ -14,8 +14,13 @@
 
   const emit = defineEmits(['success', 'register']);
 
-  const getTitle = computed(() => (!unref(isUpdate) ? '新增字典' : '编辑字典'));
+  const getTitle = computed(() =>
+    !unref(isUpdate)
+      ? `新增${dictType.value ? '系统' : '业务'}字典`
+      : `编辑${dictType.value ? '系统' : '业务'}字典`,
+  );
   const isUpdate = ref(false);
+  const dictType = ref(1);
   const rowId = ref();
 
   const { createMessage } = useMessage();
@@ -31,9 +36,10 @@
     await resetFields();
     setModalProps({ confirmLoading: false });
     isUpdate.value = !!data?.isUpdate;
+    dictType.value = data?.dictType;
     rowId.value = data.record.id;
     const resData = data.record;
-    resData.disable = String(resData.disable);
+    resData.dictType = dictType.value;
     await setFieldsValue({
       ...resData,
     });
@@ -45,7 +51,7 @@
       const values = await validate();
       setModalProps({ confirmLoading: true });
       !unref(isUpdate)
-        ? await sysDictAdd({ ...values })
+        ? await sysDictAdd({ ...values, dictType: dictType.value })
         : await sysDictEdit({ ...values, id: rowId.value });
       !unref(isUpdate) ? createMessage.success('新增成功!') : createMessage.success('编辑成功!');
       closeModal();

+ 54 - 34
src/views/sys/sysDict/sysDictTable/data.ts → src/views/sys/sysDict/category/data.ts

@@ -1,6 +1,5 @@
 import { BasicColumn, FormSchema } from '/@/components/Table';
-import { listDictModel } from '/@/api/common';
-import { validateStr } from '/@/utils/validate';
+import { sysDictQueryTree } from '/@/api/sys/sysDictApi';
 
 export const columns: BasicColumn[] = [
   {
@@ -16,8 +15,13 @@ export const columns: BasicColumn[] = [
     dataIndex: 'dictType',
   },
   {
-    title: '状态',
-    dataIndex: 'disable',
+    title: '颜色',
+    dataIndex: 'color',
+    width: 120,
+  },
+  {
+    title: '排序',
+    dataIndex: 'sort',
     width: 80,
   },
   {
@@ -43,21 +47,39 @@ export const searchFormSchema: FormSchema[] = [
       placeholder: '请输入字典编码',
     },
   },
+];
+
+export const dataFormSchema: FormSchema[] = [
   {
     field: 'dictType',
     label: '字典类型',
-    component: 'ApiSelect',
+    component: 'Input',
     componentProps: {
-      api: listDictModel,
-      params: {
-        dictCode: 'sys_dict_type',
-      },
+      placeholder: '请输入字典类型',
+    },
+    show: false,
+  },
+  {
+    field: 'parentId',
+    label: '上级字典',
+    required: true,
+    component: 'ApiTreeSelect',
+    componentProps: ({ formModel }) => {
+      return {
+        placeholder: '请选择上级字典',
+        api: sysDictQueryTree,
+        // params root 是否手动添加根节点
+        params: { dictType: formModel.dictType, root: true },
+        resultField: 'data',
+        fieldNames: {
+          label: 'dictName',
+          key: 'id',
+          value: 'id',
+        },
+        getPopupContainer: () => document.body,
+      };
     },
-    defaultValue: '1',
   },
-];
-
-export const dataFormSchema: FormSchema[] = [
   {
     field: 'dictName',
     label: '字典名称',
@@ -83,9 +105,9 @@ export const dataFormSchema: FormSchema[] = [
             if (!value) {
               return Promise.reject('字典项编码不能为空');
             }
-            if (validateStr(value)) {
-              return Promise.reject('字典项编码为字母或数字组成');
-            }
+            // if (validateStr(value)) {
+            //   return Promise.reject('字典项编码为字母或数字组成');
+            // }
             return Promise.resolve();
           },
         },
@@ -93,29 +115,27 @@ export const dataFormSchema: FormSchema[] = [
     },
   },
   {
-    field: 'dictType',
-    label: '字典类型',
-    component: 'ApiSelect',
-    required: true,
-    componentProps: {
-      api: listDictModel,
-      params: {
-        dictCode: 'sys_dict_type',
-      },
+    field: 'color',
+    label: '颜色',
+    component: 'FormColorPicker',
+    componentProps: ({ formModel }) => {
+      return {
+        onChange: e => {
+          formModel.color = e;
+        },
+      };
     },
   },
   {
-    field: 'disable',
-    label: '状态',
-    component: 'ApiRadioGroup',
-    required: true,
+    field: 'sort',
+    label: '排序',
+    component: 'InputNumber',
+    defaultValue: 999,
     componentProps: {
-      api: listDictModel,
-      params: {
-        dictCode: 'sys_disable_type',
-      },
+      placeholder: '请输入排序',
+      min: 1,
+      style: { width: '100%' },
     },
-    defaultValue: '0',
   },
   {
     label: '备注',

+ 204 - 0
src/views/sys/sysDict/category/index.vue

@@ -0,0 +1,204 @@
+<template>
+  <a-row>
+    <a-col :span="5">
+      <a-tree
+        v-if="treeData.length > 0"
+        v-model:expandedKeys="defaultExpandedKeys"
+        :tree-data="treeData"
+        :field-names="treeFieldNames"
+        @select="treeSelect"
+      />
+      <a-empty v-else />
+    </a-col>
+    <a-col :span="19">
+      <BasicTable @register="registerTable" :showExpandColumn="false">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'dictType'">
+            <Tag :color="formatDictColor(sysDictTypeOptions, record.dictType)">
+              {{ formatDictValue(sysDictTypeOptions, record.dictType) }}
+            </Tag>
+          </template>
+          <template v-if="column.key === 'color'">
+            <Tag :color="record.color || '#000'">
+              {{ record.color }}
+            </Tag>
+          </template>
+          <template v-if="column.key === 'action'">
+            <TableAction
+              :actions="[
+                {
+                  auth: ['sys:dict:edit'],
+                  icon: 'icon-edit|iconfont',
+                  tooltip: '编辑',
+                  onClick: handleEdit.bind(null, record),
+                },
+                {
+                  auth: ['sys:dict:remove'],
+                  icon: 'icon-delete|iconfont',
+                  tooltip: '删除',
+                  color: 'error',
+                  popConfirm: {
+                    title: '是否确认删除',
+                    placement: 'left',
+                    confirm: handleDelete.bind(null, record),
+                  },
+                },
+              ]"
+            />
+          </template>
+        </template>
+        <template #toolbar>
+          <a-button
+            v-auth="['sys:dict:add']"
+            type="primary"
+            @click="handleCreate"
+            preIcon="icon-plus|iconfont"
+            >新增</a-button
+          >
+        </template>
+      </BasicTable>
+      <FormModal @register="registerModal" @success="handleSuccess" />
+    </a-col>
+  </a-row>
+</template>
+
+<script setup lang="ts">
+  import { computed, onBeforeMount, onMounted, ref } from 'vue';
+  import { Tag } from 'ant-design-vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { useModal } from '/@/components/Modal';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from './FormModal.vue';
+  import { columns, searchFormSchema } from './data';
+  import { sysDictQueryPage, sysDictQueryTree, sysDictRemove } from '/@/api/sys/sysDictApi';
+  import { formatDictColor, formatDictValue } from '/@/utils';
+  import { listDictModel } from '/@/api/common';
+
+  const sysDictTypeOptions = ref([]);
+  onBeforeMount(async () => {
+    const dictTypeStatus = await listDictModel({ dictCode: 'sys_dict_type' });
+    sysDictTypeOptions.value = dictTypeStatus || [];
+  });
+
+  onMounted(async () => {
+    await getTreeData();
+  });
+
+  const props = defineProps({
+    type: {
+      type: String,
+      default: 'DICT_SYS',
+    },
+  });
+  const getTableTitle = computed(() => {
+    if (parentId.value == '0') {
+      return props.type == 'DICT_SYS' ? '系统 字典列表' : '业务 字典列表';
+    } else {
+      return treeSelectNodeName.value + ' 字典列表';
+    }
+  });
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const [registerTable, { reload }] = useTable({
+    title: getTableTitle,
+    api: sysDictQueryPage,
+    rowKey: 'id',
+    columns,
+    formConfig: {
+      labelWidth: 120,
+      schemas: searchFormSchema,
+      autoSubmitOnEnter: true,
+      baseColProps: { span: 8 },
+      resetButtonOptions: {
+        preIcon: 'icon-delete|iconfont',
+      },
+      submitButtonOptions: {
+        preIcon: 'icon-search|iconfont',
+      },
+    },
+    showIndexColumn: false,
+    useSearchForm: true,
+    showTableSetting: true,
+    tableSetting: {
+      setting: false,
+    },
+    bordered: true,
+    actionColumn: {
+      auth: ['sys:dict:edit', 'sys:dict:remove'],
+      width: 80,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+  });
+
+  // 默认父节点
+  const parentId = ref('0');
+  // 默认展开的节点
+  const defaultExpandedKeys = ref(['0']);
+  const treeData = ref([]);
+  const treeSelectNodeName = ref('');
+  // 替换treeNode 中 title,key,children
+  const treeFieldNames = { children: 'children', title: 'dictName', key: 'id' };
+  // 获取 tree 所有节点信息
+  async function getTreeData() {
+    const data = await sysDictQueryTree({ dictType: props.type });
+    treeData.value = [
+      {
+        dictName: '根目录',
+        id: '0',
+        children: data,
+      },
+    ];
+  }
+
+  // 新增按钮事件
+  function handleCreate() {
+    openModal(true, {
+      dictType: props.type,
+      isUpdate: false,
+    });
+  }
+
+  // 编辑按钮事件
+  function handleEdit(record: Recordable) {
+    openModal(true, {
+      record,
+      dictType: record.dictType || '1',
+      isUpdate: true,
+    });
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    console.log(record);
+    await sysDictRemove([record.id]);
+    createMessage.success('删除成功!');
+    await reload();
+  }
+
+  // 弹窗回调事件
+  async function handleSuccess() {
+    await reload();
+  }
+
+  function handleBeforeFetch(params) {
+    return {
+      ...params,
+      dictType: props.type,
+      parentId: parentId.value,
+      orders: [{ field: 'sort', direction: 'ASC' }],
+    };
+  }
+
+  // tree选中事件
+  async function treeSelect(selectedKeys, { selectedNodes }) {
+    if (selectedKeys && selectedKeys.length > 0) {
+      parentId.value = selectedKeys[0];
+      treeSelectNodeName.value = selectedNodes[0]['dictName'];
+      await reload();
+    }
+  }
+</script>
+
+<style lang="less" scoped></style>

+ 31 - 15
src/views/sys/sysDict/index.vue

@@ -1,24 +1,40 @@
 <template>
-  <PageWrapper dense contentFullHeight contentClass="flex">
-    <SysDictTable class="w-1/2" @dict-change="handleDictChange" />
-    <SysDictItemTable class="w-1/2" :dictId="dictId" />
+  <PageWrapper>
+    <a-card
+      :bordered="false"
+      :active-tab-key="activeKey"
+      :tab-list="tabList"
+      @tabChange="
+        key => {
+          activeKey = key;
+        }
+      "
+    >
+      <p v-for="item in tabList" :key="item.key">
+        <Category :type="item.type" v-if="activeKey == item.key" />
+      </p>
+    </a-card>
   </PageWrapper>
 </template>
 <script lang="ts" setup>
-  import { ref } from 'vue';
+  import { onMounted, ref } from 'vue';
   import { PageWrapper } from '/@/components/Page';
-  import SysDictTable from './sysDictTable/index.vue';
-  import SysDictItemTable from './sysDictItemTable/index.vue';
-  // import { filterSub, getSub } from '/@/utils/websocket';
-
+  import Category from './category/index.vue';
+  import { listDictModel } from '/@/api/common';
   defineOptions({
     name: 'sysDict',
   });
-
-  const dictId = ref();
-  // 字典选中事件
-  function handleDictChange(value) {
-    if (!value) return;
-    dictId.value = value;
-  }
+  const activeKey = ref(null);
+  const tabList = ref([]);
+  onMounted(async () => {
+    const dictType = await listDictModel({ dictCode: 'sys_dict_type' });
+    tabList.value = dictType.map(ele => {
+      return {
+        key: ele.id,
+        tab: ele.label + '字典',
+        type: ele.value,
+      };
+    });
+    activeKey.value = dictType[1]['id'];
+  });
 </script>

+ 0 - 61
src/views/sys/sysDict/sysDictItemTable/FormModal.vue

@@ -1,61 +0,0 @@
-<template>
-  <BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
-    <BasicForm @register="registerForm" />
-  </BasicModal>
-</template>
-<script lang="ts" setup>
-  import { ref, computed, unref } from 'vue';
-  import { BasicModal, useModalInner } from '/@/components/Modal';
-  import { BasicForm, useForm } from '/@/components/Form';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { dataFormSchema } from './data';
-
-  import { sysDictItemAdd, sysDictItemEdit } from '/@/api/sys/sysDictItemApi';
-
-  const emit = defineEmits(['success', 'register']);
-
-  const getTitle = computed(() => (!unref(isUpdate) ? '新增字典项' : '编辑字典项'));
-  const isUpdate = ref(false);
-  const rowId = ref();
-  const dictId = ref();
-
-  const { createMessage } = useMessage();
-  const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
-    labelWidth: 100,
-    schemas: dataFormSchema,
-    showActionButtonGroup: false,
-    actionColOptions: {
-      span: 23,
-    },
-  });
-  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
-    await resetFields();
-    setModalProps({ confirmLoading: false });
-    isUpdate.value = !!data?.isUpdate;
-    dictId.value = data?.dictId;
-    rowId.value = data.record.id;
-    const resData = data.record;
-    await setFieldsValue({
-      ...resData,
-    });
-  });
-
-  // 提交按钮事件
-  async function handleSubmit() {
-    try {
-      const values = await validate();
-      setModalProps({ confirmLoading: true });
-      !unref(isUpdate)
-        ? await sysDictItemAdd({ ...values, id: rowId.value, dictId: dictId.value })
-        : await sysDictItemEdit({ ...values, id: rowId.value, dictId: dictId.value });
-      !unref(isUpdate) ? createMessage.success('新增成功!') : createMessage.success('编辑成功!');
-      closeModal();
-      emit('success', {
-        isUpdate: unref(isUpdate),
-        values: { ...values, id: rowId.value, dictId: dictId.value },
-      });
-    } finally {
-      setModalProps({ confirmLoading: false });
-    }
-  }
-</script>

+ 0 - 115
src/views/sys/sysDict/sysDictItemTable/data.ts

@@ -1,115 +0,0 @@
-import { BasicColumn, FormSchema } from '/@/components/Table';
-import { validateStr } from '/@/utils/validate';
-
-export const columns: BasicColumn[] = [
-  {
-    title: '字典项名称',
-    dataIndex: 'label',
-  },
-  {
-    title: '字典项编码',
-    dataIndex: 'value',
-    width: 120,
-  },
-  {
-    title: '颜色',
-    dataIndex: 'color',
-    width: 120,
-  },
-  {
-    title: '排序',
-    dataIndex: 'sort',
-    width: 80,
-  },
-  {
-    title: '备注',
-    dataIndex: 'remark',
-  },
-];
-
-export const searchFormSchema: FormSchema[] = [
-  {
-    field: 'dictItemName',
-    label: '字典项名称',
-    component: 'Input',
-    componentProps: {
-      placeholder: '请输入字典项名称',
-    },
-  },
-  {
-    field: 'dictItemCode',
-    label: '字典项编码',
-    component: 'Input',
-    componentProps: {
-      placeholder: '请输入字典项编码',
-    },
-  },
-];
-
-export const dataFormSchema: FormSchema[] = [
-  {
-    field: 'label',
-    label: '字典项名称',
-    component: 'Input',
-    required: true,
-    componentProps: {
-      placeholder: '请输入字典项名称',
-    },
-  },
-  {
-    field: 'value',
-    label: '字典项编码',
-    component: 'Input',
-    required: true,
-    componentProps: {
-      placeholder: '请输入字典项编码',
-    },
-    dynamicRules: () => {
-      return [
-        {
-          required: true,
-          validator: async (_, value) => {
-            if (!value) {
-              return Promise.reject('字典项编码不能为空');
-            }
-            if (validateStr(value)) {
-              return Promise.reject('字典项编码为字母或数字组成');
-            }
-            return Promise.resolve();
-          },
-        },
-      ];
-    },
-  },
-  {
-    field: 'color',
-    label: '颜色',
-    component: 'FormColorPicker',
-    componentProps: ({ formModel }) => {
-      return {
-        onChange: e => {
-          formModel.color = e;
-        },
-      };
-    },
-  },
-  {
-    field: 'sort',
-    label: '排序',
-    component: 'InputNumber',
-    required: true,
-    defaultValue: '1',
-    componentProps: {
-      placeholder: '请输入排序',
-      min: 1,
-    },
-  },
-  {
-    label: '备注',
-    field: 'remark',
-    component: 'InputTextArea',
-    componentProps: {
-      placeholder: '请输入备注',
-    },
-  },
-];

+ 0 - 163
src/views/sys/sysDict/sysDictItemTable/index.vue

@@ -1,163 +0,0 @@
-<template>
-  <div style="margin-top: 16px">
-    <BasicTable @register="registerTable">
-      <template #bodyCell="{ column, record }">
-        <template v-if="column.key === 'color'">
-          <Tag :color="record.color || '#000'">
-            {{ record.color }}
-          </Tag>
-        </template>
-        <template v-if="column.key === 'action'">
-          <TableAction
-            :actions="[
-              {
-                auth: ['sys:dictItem:edit'],
-                icon: 'icon-edit|iconfont',
-                tooltip: '编辑',
-                onClick: handleEdit.bind(null, record),
-              },
-              {
-                auth: ['sys:dictItem:remove'],
-                icon: 'icon-delete|iconfont',
-                tooltip: '删除',
-                color: 'error',
-                popConfirm: {
-                  title: '是否确认删除',
-                  placement: 'left',
-                  confirm: handleDelete.bind(null, record),
-                },
-              },
-            ]"
-          />
-        </template>
-      </template>
-      <template #toolbar>
-        <a-button
-          v-auth="['sys:dictItem:add']"
-          v-show="!!dictId"
-          type="primary"
-          @click="handleCreate"
-          preIcon="icon-plus|iconfont"
-          >新增</a-button
-        >
-      </template>
-    </BasicTable>
-    <FormModal @register="registerModal" @success="handleSuccess" />
-  </div>
-</template>
-<script lang="ts" setup>
-  import { Tag } from 'ant-design-vue';
-  import { nextTick, onBeforeMount, ref, watch } from 'vue';
-  import { BasicTable, useTable, TableAction } from '/@/components/Table';
-  import { useModal } from '/@/components/Modal';
-  import FormModal from './FormModal.vue';
-  import { columns, searchFormSchema } from './data';
-  import { useMessage } from '/@/hooks/web/useMessage';
-
-  import { sysDictItemQueryList, sysDictItemRemove } from '/@/api/sys/sysDictItemApi';
-  import { listDictModel } from '/@/api/common';
-
-  const props = defineProps({
-    dictId: { type: String },
-  });
-
-  const sysStatusOptions = ref([]);
-  onBeforeMount(async () => {
-    sysStatusOptions.value = await listDictModel({ dictCode: 'sys_status' });
-  });
-
-  // const { createMessage, createConfirm } = useMessage();
-  const { createMessage } = useMessage();
-  const [registerModal, { openModal }] = useModal();
-  const [registerTable, { reload }] = useTable({
-    title: '字典项列表',
-    titleHelpMessage: '请先选中字典,再操作字典项',
-    api: sysDictItemQueryList,
-    rowKey: 'id',
-    columns,
-    formConfig: {
-      labelWidth: 120,
-      schemas: searchFormSchema,
-      autoSubmitOnEnter: true,
-      resetButtonOptions: {
-        preIcon: 'ant-design:delete-outlined',
-      },
-      submitButtonOptions: {
-        preIcon: 'ant-design:search-outlined',
-      },
-    },
-    immediate: false,
-    showIndexColumn: false,
-    useSearchForm: false,
-    showTableSetting: true,
-    bordered: true,
-    actionColumn: {
-      auth: ['sys:dictItem:edit', 'sys:dictItem:remove'],
-      width: 80,
-      title: '操作',
-      dataIndex: 'action',
-    },
-    beforeFetch: handleBeforeFetch,
-  });
-
-  // 新增按钮事件
-  function handleCreate() {
-    openModal(true, {
-      isUpdate: false,
-      dictId: props.dictId,
-    });
-  }
-
-  // 编辑按钮事件
-  function handleEdit(record: Recordable) {
-    console.log(record);
-    openModal(true, {
-      record,
-      isUpdate: true,
-      dictId: props.dictId,
-    });
-  }
-
-  // 删除按钮事件
-  async function handleDelete(record: Recordable) {
-    console.log('🚀 ~ file: index.vue:141 ~ handleDelete ~ record', record);
-    await sysDictItemRemove([record.id]);
-    createMessage.success('删除成功!');
-    await reload();
-  }
-
-  // 导出按钮事件
-  // async function handleExport() {
-  //   createConfirm({
-  //     iconType: 'warning',
-  //     title: '提示',
-  //     content: '确认导出?',
-  //     onOk: async () => {
-  //       const params = Object.assign({}, getForm().getFieldsValue(), { dictId: props.dictId });
-  //       const filepath = await exportList(params);
-  //       downloadFile(filepath);
-  //     },
-  //   });
-  // }
-
-  // 弹窗回调事件
-  function handleSuccess({ isUpdate, values }) {
-    console.log(isUpdate);
-    console.log(values);
-    reload();
-  }
-
-  // 表格请求之前,对参数进行处理
-  function handleBeforeFetch(params) {
-    return { ...params, dictId: props.dictId, orders: [{ field: 'sort', direction: 'ASC' }] };
-  }
-
-  watch(
-    () => props.dictId,
-    () => {
-      nextTick(() => {
-        reload();
-      });
-    },
-  );
-</script>

+ 0 - 220
src/views/sys/sysDict/sysDictTable/index.vue

@@ -1,220 +0,0 @@
-<template>
-  <div>
-    <BasicTable
-      @register="registerTable"
-      @selection-change="handleSelectionChange"
-      @row-click="handleRowClick"
-      @row-dbClick="handleRowDbClick"
-    >
-      <template #bodyCell="{ column, record }">
-        <template v-if="column.key === 'disable'">
-          <Tag :color="formatDictColor(sysDisableType, record.disable)">
-            {{ formatDictValue(sysDisableType, record.disable) }}
-          </Tag>
-        </template>
-        <template v-if="column.key === 'dictType'">
-          <Tag :color="formatDictColor(sysDictTypeOptions, record.dictType)">
-            {{ formatDictValue(sysDictTypeOptions, record.dictType) }}
-          </Tag>
-        </template>
-        <template v-if="column.key === 'action'">
-          <TableAction
-            :actions="[
-              {
-                auth: ['sys:dict:edit'],
-                icon: 'icon-edit|iconfont',
-                tooltip: '编辑',
-                onClick: handleEdit.bind(null, record),
-              },
-              {
-                auth: ['sys:dict:remove'],
-                icon: 'icon-delete|iconfont',
-                tooltip: '删除',
-                color: 'error',
-                popConfirm: {
-                  title: '是否确认删除',
-                  confirm: handleDelete.bind(null, record),
-                },
-              },
-            ]"
-          />
-        </template>
-      </template>
-      <template #toolbar>
-        <a-button
-          v-auth="['sys:dict:add']"
-          type="primary"
-          @click="handleCreate"
-          preIcon="icon-plus|iconfont"
-          >新增</a-button
-        >
-      </template>
-    </BasicTable>
-    <FormModal @register="registerModal" @success="handleSuccess" />
-  </div>
-</template>
-<script lang="ts" setup>
-  import { onBeforeMount, ref } from 'vue';
-  import { Tag } from 'ant-design-vue';
-  import { BasicTable, useTable, TableAction } from '/@/components/Table';
-  import { useModal } from '/@/components/Modal';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import FormModal from './FormModal.vue';
-  import { columns, searchFormSchema } from './data';
-  import { sysDictQueryPage, sysDictRemove } from '/@/api/sys/sysDictApi';
-  import { formatDictColor, formatDictValue } from '/@/utils';
-  import { listDictModel } from '/@/api/common';
-
-  const emit = defineEmits(['dict-change']);
-
-  const sysDictTypeOptions = ref([]);
-  const sysDisableType = ref();
-  onBeforeMount(async () => {
-    const dictTypeStatus = await listDictModel({ dictCode: 'sys_dict_type' });
-    sysDisableType.value = await listDictModel({ dictCode: 'sys_disable_type' });
-    sysDictTypeOptions.value = dictTypeStatus || [];
-  });
-
-  const { createMessage } = useMessage();
-  const [registerModal, { openModal }] = useModal();
-  const [registerTable, { reload, getDataSource, getSelectRowKeys, setSelectedRowKeys }] = useTable(
-    {
-      title: '字典列表',
-      api: sysDictQueryPage,
-      rowKey: 'id',
-      columns,
-      rowSelection: { type: 'radio' },
-      clickToRowSelect: true,
-      formConfig: {
-        labelWidth: 120,
-        schemas: searchFormSchema,
-        autoSubmitOnEnter: true,
-        baseColProps: { span: 12 },
-        resetButtonOptions: {
-          preIcon: 'icon-delete|iconfont',
-        },
-        submitButtonOptions: {
-          preIcon: 'icon-search|iconfont',
-        },
-      },
-      showIndexColumn: false,
-      useSearchForm: true,
-      showTableSetting: true,
-      bordered: true,
-      actionColumn: {
-        auth: ['sys:dict:edit', 'sys:dict:remove'],
-        width: 80,
-        title: '操作',
-        dataIndex: 'action',
-      },
-      afterFetch: handleAfterFetch,
-    },
-  );
-
-  // 新增按钮事件
-  function handleCreate() {
-    openModal(true, {
-      isUpdate: false,
-    });
-  }
-
-  // 编辑按钮事件
-  function handleEdit(record: Recordable) {
-    console.log(record);
-    openModal(true, {
-      record,
-      isUpdate: true,
-    });
-  }
-
-  // 删除按钮事件
-  async function handleDelete(record: Recordable) {
-    console.log(record);
-    await sysDictRemove([record.id]);
-    createMessage.success('删除成功!');
-    await reload();
-    // after delete, select first row
-    const list = getDataSource();
-    if (list.length > 0) {
-      setSelectedRowKeys([list[0].id]);
-    } else {
-      setSelectedRowKeys([]);
-    }
-    console.log('handleDelete');
-    emitDictChange();
-  }
-
-  // 导出按钮事件
-  // async function handleExport() {
-  //   createConfirm({
-  //     iconType: 'warning',
-  //     title: '提示',
-  //     content: '确认导出?',
-  //     onOk: async () => {
-  //       const params = getForm().getFieldsValue();
-  //       const filepath = await exportList(params);
-  //       downloadFile(filepath);
-  //     },
-  //   });
-  // }
-
-  // 弹窗回调事件
-  async function handleSuccess({ isUpdate, values }) {
-    console.log(isUpdate);
-    console.log(values);
-    await reload();
-    if (isUpdate) {
-      // after update, select updated row
-      setSelectedRowKeys([values.id]);
-    } else {
-      // after create, select first row
-      const list = getDataSource();
-      if (list.length > 0) {
-        setSelectedRowKeys([list[0].id]);
-      } else {
-        setSelectedRowKeys([]);
-      }
-    }
-    console.log('handleSuccess', isUpdate ? 'update' : 'create');
-    emitDictChange();
-  }
-
-  // 表格请求之后,对返回值进行处理
-  function handleAfterFetch(data) {
-    // after fetch, select first row
-    if (data.length > 0) {
-      setSelectedRowKeys([data[0].id]);
-    } else {
-      setSelectedRowKeys([]);
-    }
-    console.log('handleAfterFetch', data);
-    emitDictChange();
-  }
-
-  // 表格行点击事件
-  function handleRowClick(record: Recordable) {
-    console.log('handleRowClick', record);
-    setSelectedRowKeys([record.id]);
-    emitDictChange();
-  }
-
-  // 表格行双击事件
-  function handleRowDbClick(record: Recordable) {
-    console.log('handleRowDbClick', record);
-    setSelectedRowKeys([record.id]);
-    emitDictChange();
-  }
-
-  // 表格行选中事件
-  function handleSelectionChange({ keys, rows }) {
-    console.log('handleSelectionChange', keys, rows);
-    emitDictChange();
-  }
-
-  // 字典变化事件
-  function emitDictChange() {
-    const selectedKeys = getSelectRowKeys();
-    console.log(selectedKeys);
-    emit('dict-change', selectedKeys.length > 0 ? selectedKeys[0] : '');
-  }
-</script>