Przeglądaj źródła

refactor(components): 更新表单、模态框和表格组件

- 修改了.env.test文件中的VITE_SERVICE_BASE_URL配置。
- 新增了ApiSelect组件及其相关类型定义和props。
- 更新了BasicForm组件,包括props名称调整、新增updateSchema方法等。
- 更新了Form相关的helper、hooks以及types文件,以支持新的功能和属性。
- 重构了BasicModal组件,增加了对modalProps的支持,并更新了样式。
- 新增了useModal hook,用于处理模态框逻辑。
- 新增了BasicModalForm组件及其相关hooks和types,整合了表单和模态框的功能。
- 新增了useTable hook,用于处理表格逻辑。
- 新增了Table组件的props和types定义。

这些更改增强了组件的功能性和可维护性。
```
zhangtao 3 miesięcy temu
rodzic
commit
1b0502d14c
56 zmienionych plików z 1706 dodań i 526 usunięć
  1. 1 1
      .env.test
  2. 61 0
      src/components/zt/ApiSelect/api-select.vue
  3. 3 0
      src/components/zt/ApiSelect/index.ts
  4. 8 0
      src/components/zt/ApiSelect/props.ts
  5. 25 0
      src/components/zt/ApiSelect/type/index.ts
  6. 24 20
      src/components/zt/Form/basic-form.vue
  7. 12 4
      src/components/zt/Form/helper.ts
  8. 10 4
      src/components/zt/Form/hooks/useForm.ts
  9. 40 3
      src/components/zt/Form/hooks/useFormEvents.ts
  10. 0 1
      src/components/zt/Form/hooks/useFormValues.ts
  11. 1 1
      src/components/zt/Form/props.ts
  12. 8 3
      src/components/zt/Form/types/form.ts
  13. 110 3
      src/components/zt/Modal/basic-modal.vue
  14. 53 0
      src/components/zt/Modal/hooks/useModal.ts
  15. 42 0
      src/components/zt/Modal/props.ts
  16. 50 0
      src/components/zt/Modal/types/index.ts
  17. 94 0
      src/components/zt/ModalForm/basic-model-form.vue
  18. 134 0
      src/components/zt/ModalForm/hooks/useModalForm.ts
  19. 15 0
      src/components/zt/ModalForm/props.ts
  20. 13 0
      src/components/zt/ModalForm/type/index.ts
  21. 50 0
      src/components/zt/Table/hooks/useTable.ts
  22. 28 0
      src/components/zt/Table/props.ts
  23. 11 15
      src/components/zt/Table/types/index.ts
  24. 147 67
      src/components/zt/Table/z-table.vue
  25. 9 0
      src/components/zt/layoutTable/layout-table.vue
  26. 1 1
      src/constants/business.ts
  27. 1 0
      src/hooks/common/router.ts
  28. 1 1
      src/hooks/common/table.ts
  29. 2 1
      src/locales/langs/en-us.ts
  30. 3 2
      src/locales/langs/zh-cn.ts
  31. 1 0
      src/router/elegant/imports.ts
  32. 9 0
      src/router/elegant/routes.ts
  33. 1 0
      src/router/elegant/transform.ts
  34. 13 0
      src/service/api/auth.ts
  35. 111 1
      src/service/api/system-manage.ts
  36. 4 2
      src/service/request/index.ts
  37. 5 3
      src/store/modules/auth/index.ts
  38. 6 2
      src/typings/api.d.ts
  39. 16 1
      src/typings/api/system-manage.d.ts
  40. 4 0
      src/typings/components.d.ts
  41. 2 0
      src/typings/elegant-router.d.ts
  42. 63 4
      src/utils/zt/index.ts
  43. 2 1
      src/views/home/modules/header-banner.vue
  44. 1 1
      src/views/manage/config/index.vue
  45. 9 0
      src/views/manage/department/index.vue
  46. 1 1
      src/views/manage/log/index.vue
  47. 8 69
      src/views/manage/menu/index.vue
  48. 39 7
      src/views/manage/menu/modules/shared.ts
  49. 20 42
      src/views/manage/role/index.vue
  50. 17 40
      src/views/manage/role/modules/menu-auth-modal.vue
  51. 48 30
      src/views/manage/role/modules/role-operate-drawer.vue
  52. 1 13
      src/views/manage/role/modules/role-search.vue
  53. 1 1
      src/views/manage/schedule/index.vue
  54. 240 178
      src/views/manage/user/index.vue
  55. 1 1
      src/views/plugin/excel/index.vue
  56. 126 2
      src/views/user-center/index.vue

+ 1 - 1
.env.test

@@ -1,5 +1,5 @@
 # backend service base url, test environment
 # backend service base url, test environment
-VITE_SERVICE_BASE_URL=http://192.168.1.206:8114
+VITE_SERVICE_BASE_URL=http://74949mkfh190.vicp.fun
 # VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
 # VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
 
 
 
 

+ 61 - 0
src/components/zt/ApiSelect/api-select.vue

@@ -0,0 +1,61 @@
+<script setup lang="ts">
+import { computed, ref, unref, watch, watchEffect } from 'vue';
+import { basicApiSelectProps } from './props';
+import type { ApiSelectProps } from './type';
+const props = withDefaults(defineProps<ApiSelectProps>(), {
+  immediate: true,
+  resultFeild: 'data'
+});
+const basicProps = ref(basicApiSelectProps);
+const options = ref([]);
+const modelVlaue = defineModel<Array<string | number> | string | number | null>();
+const isFirstReq = ref(false);
+const bindValue = computed(() => {
+  return {
+    ...props,
+    ...basicProps
+  };
+});
+
+async function fetchApi() {
+  const res = await props.api();
+  options.value = res[props.resultFeild];
+}
+watchEffect(() => {
+  if (unref(bindValue).immediate) {
+    fetchApi();
+  }
+});
+watch(
+  () => modelVlaue.value,
+  () => {
+    console.log(modelVlaue.value);
+  }
+);
+function handleFocus() {
+  if (!unref(bindValue).immediate && !unref(isFirstReq)) {
+    fetchApi();
+    isFirstReq.value = true;
+  }
+}
+function handleScroll(e: Event) {
+  const currentTarget = e.currentTarget as HTMLElement;
+  if (currentTarget.scrollTop + currentTarget.offsetHeight >= currentTarget.scrollHeight) {
+    // console.log('到底了');
+  }
+}
+</script>
+
+<template>
+  <NSelect
+    v-bind="bindValue"
+    v-model:value="modelVlaue"
+    :options="options"
+    :label-field="labelFeild"
+    :value-field="valueFeild"
+    @focus="handleFocus"
+    @scroll="handleScroll"
+  ></NSelect>
+</template>
+
+<style scoped></style>

+ 3 - 0
src/components/zt/ApiSelect/index.ts

@@ -0,0 +1,3 @@
+import ApiSelect from './api-select.vue';
+
+export { ApiSelect };

+ 8 - 0
src/components/zt/ApiSelect/props.ts

@@ -0,0 +1,8 @@
+import type { ApiSelectProps } from './type';
+export const basicApiSelectProps: ApiSelectProps = {
+  api: () => Promise.resolve({}),
+  labelFeild: '',
+  valueFeild: '',
+  immediate: true,
+  resultFeild: 'data'
+};

+ 25 - 0
src/components/zt/ApiSelect/type/index.ts

@@ -0,0 +1,25 @@
+import { type SelectProps } from 'naive-ui';
+export interface ApiSelectProps extends /* @vue-ignore */ SelectProps {
+  /**
+   * 请求方法
+   * @param params
+   * @returns
+   */
+  api: (params?: any) => Promise<any>;
+  /**
+   * 绑定的展示字段
+   */
+  labelFeild: string;
+  /**
+   * 绑定的值字段
+   */
+  valueFeild: string;
+  /**
+   * 是否立即请求
+   */
+  immediate?: boolean;
+  /**
+   * 请求结果字段
+   */
+  resultFeild?: string;
+}

+ 24 - 20
src/components/zt/Form/basic-form.vue

@@ -9,7 +9,7 @@ import { isBoolean, isFunction } from '@/utils/zt/is';
 import { createPlaceholderMessage } from './helper';
 import { createPlaceholderMessage } from './helper';
 import { useFormEvents } from './hooks/useFormEvents';
 import { useFormEvents } from './hooks/useFormEvents';
 import { useFormValues } from './hooks/useFormValues';
 import { useFormValues } from './hooks/useFormValues';
-import { basicProps } from './props';
+import { basicFormProps } from './props';
 import type { FormActionType, FormProps, FormSchema } from './types/form';
 import type { FormActionType, FormProps, FormSchema } from './types/form';
 import { componentMap } from './types/form';
 import { componentMap } from './types/form';
 
 
@@ -17,9 +17,9 @@ export default defineComponent({
   name: 'BasicForm',
   name: 'BasicForm',
   components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
   components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
   props: {
   props: {
-    ...basicProps
+    ...basicFormProps
   },
   },
-  emits: ['reset', 'submit', 'register'],
+  emits: ['reset', 'submit', 'registerForm'],
   setup(props, { emit, attrs }) {
   setup(props, { emit, attrs }) {
     const defaultFormModel = ref<Recordable>({});
     const defaultFormModel = ref<Recordable>({});
     const formModel = reactive<Recordable>({});
     const formModel = reactive<Recordable>({});
@@ -114,6 +114,7 @@ export default defineComponent({
           schema.defaultValue = defaultValue;
           schema.defaultValue = defaultValue;
         }
         }
       }
       }
+
       return schemas as FormSchema[];
       return schemas as FormSchema[];
     });
     });
     const getRule = (schema: FormSchema): FormItemRule | undefined => {
     const getRule = (schema: FormSchema): FormItemRule | undefined => {
@@ -158,22 +159,24 @@ export default defineComponent({
       getSchema,
       getSchema,
       formModel
       formModel
     });
     });
-    const { handleSubmit, validate, resetFields, getFieldsValue, clearValidate, setFieldsValue } = useFormEvents({
-      emit,
-      getProps,
-      formModel,
-      getSchema,
-      formElRef: formElRef as Ref<FormActionType>,
-      defaultFormModel,
-      loadingSub,
-      handleFormValues
-    });
+    const { handleSubmit, validate, resetFields, getFieldsValue, clearValidate, setFieldsValue, updateSchema } =
+      useFormEvents({
+        emit,
+        getProps,
+        formModel,
+        getSchema,
+        formElRef: formElRef as Ref<FormActionType>,
+        defaultFormModel,
+        loadingSub,
+        schemaRef: schemaRef as Ref<FormSchema[]>,
+        handleFormValues
+      });
 
 
     function unfoldToggle() {
     function unfoldToggle() {
       gridCollapsed.value = !gridCollapsed.value;
       gridCollapsed.value = !gridCollapsed.value;
     }
     }
 
 
-    async function setProps(formProps: Partial<FormProps>): Promise<void> {
+    async function setFormProps(formProps: Partial<FormProps>): Promise<void> {
       propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
       propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
     }
     }
 
 
@@ -197,8 +200,9 @@ export default defineComponent({
       resetFields,
       resetFields,
       validate,
       validate,
       clearValidate,
       clearValidate,
-      setProps,
-      submit: handleSubmit
+      setFormProps,
+      submit: handleSubmit,
+      updateSchema
     };
     };
 
 
     watch(
     watch(
@@ -207,7 +211,7 @@ export default defineComponent({
         if (unref(isUpdateDefaultRef)) {
         if (unref(isUpdateDefaultRef)) {
           return;
           return;
         }
         }
-        if (schema?.length) {
+        if (schema.length) {
           initDefault();
           initDefault();
           isUpdateDefaultRef.value = true;
           isUpdateDefaultRef.value = true;
         }
         }
@@ -216,7 +220,7 @@ export default defineComponent({
 
 
     onMounted(() => {
     onMounted(() => {
       initDefault();
       initDefault();
-      emit('register', formActionType);
+      emit('registerForm', formActionType);
     });
     });
 
 
     return {
     return {
@@ -284,7 +288,7 @@ export default defineComponent({
 
 
             <!--NCheckbox-->
             <!--NCheckbox-->
             <template v-else-if="schema.component === 'NCheckboxGroup'">
             <template v-else-if="schema.component === 'NCheckboxGroup'">
-              <NCheckboxGroup v-model:value="formModel[schema.field]">
+              <NCheckboxGroup v-model:value="formModel[schema.field]" v-bind="schema.componentProps">
                 <NSpace>
                 <NSpace>
                   <NCheckbox
                   <NCheckbox
                     v-for="item in schema.componentProps!.options"
                     v-for="item in schema.componentProps!.options"
@@ -298,7 +302,7 @@ export default defineComponent({
 
 
             <!--NRadioGroup-->
             <!--NRadioGroup-->
             <template v-else-if="schema.component === 'NRadioGroup'">
             <template v-else-if="schema.component === 'NRadioGroup'">
-              <NRadioGroup v-model:value="formModel[schema.field]">
+              <NRadioGroup v-model:value="formModel[schema.field]" v-bind="schema.componentProps">
                 <NSpace>
                 <NSpace>
                   <NRadio v-for="item in schema.componentProps!.options" :key="item.value" :value="item.value">
                   <NRadio v-for="item in schema.componentProps!.options" :key="item.value" :value="item.value">
                     {{ item.label }}
                     {{ item.label }}

+ 12 - 4
src/components/zt/Form/helper.ts

@@ -6,11 +6,19 @@ import type { ComponentMap } from './types/form';
 export function createPlaceholderMessage(component: keyof ComponentMap, label: string) {
 export function createPlaceholderMessage(component: keyof ComponentMap, label: string) {
   if (component === 'NInput') return `请输入${label}`;
   if (component === 'NInput') return `请输入${label}`;
   if (
   if (
-    ['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker', 'NCheckboxGroup'].includes(
-      component
-    )
+    [
+      'NPicker',
+      'NSelect',
+      'NCheckbox',
+      'NRadio',
+      'NSwitch',
+      'NDatePicker',
+      'NTimePicker',
+      'NCheckboxGroup',
+      'ApiSelect'
+    ].includes(component)
   )
   )
-    return `请输入${label}`;
+    return `请选择${label}`;
   return '';
   return '';
 }
 }
 
 

+ 10 - 4
src/components/zt/Form/hooks/useForm.ts

@@ -1,14 +1,16 @@
 import { nextTick, onUnmounted, ref, unref, watch } from 'vue';
 import { nextTick, onUnmounted, ref, unref, watch } from 'vue';
 import { getDynamicProps } from '@/utils/zt';
 import { getDynamicProps } from '@/utils/zt';
-import type { FormActionType, FormProps, UseFormReturnType } from '../types/form';
+import type { FormActionType, FormProps, FormSchema, UseFormReturnType } from '../types/form';
 
 
 type Props = Partial<FormProps>;
 type Props = Partial<FormProps>;
 
 
 export function useForm(props?: Props): UseFormReturnType {
 export function useForm(props?: Props): UseFormReturnType {
   const formRef = ref<Nullable<FormActionType>>(null);
   const formRef = ref<Nullable<FormActionType>>(null);
   const loadedRef = ref<Nullable<boolean>>(false);
   const loadedRef = ref<Nullable<boolean>>(false);
+
   async function getForm() {
   async function getForm() {
     const form = unref(formRef);
     const form = unref(formRef);
+
     if (!form) {
     if (!form) {
       console.error(
       console.error(
         'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!'
         'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!'
@@ -33,7 +35,7 @@ export function useForm(props?: Props): UseFormReturnType {
       () => props,
       () => props,
       () => {
       () => {
         if (props) {
         if (props) {
-          instance.setProps(getDynamicProps(props));
+          instance.setFormProps(getDynamicProps(props));
         }
         }
       },
       },
       {
       {
@@ -44,9 +46,13 @@ export function useForm(props?: Props): UseFormReturnType {
   }
   }
 
 
   const methods: FormActionType = {
   const methods: FormActionType = {
-    setProps: async (formProps: Partial<FormProps>) => {
+    updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
+      const form = await getForm();
+      form.updateSchema(data);
+    },
+    setFormProps: async (formProps: Partial<FormProps>) => {
       const form = await getForm();
       const form = await getForm();
-      await form.setProps(formProps);
+      await form.setFormProps(formProps);
     },
     },
 
 
     resetFields: async () => {
     resetFields: async () => {

+ 40 - 3
src/components/zt/Form/hooks/useFormEvents.ts

@@ -1,10 +1,13 @@
 import type { ComputedRef, Ref } from 'vue';
 import type { ComputedRef, Ref } from 'vue';
 import { toRaw, unref } from 'vue';
 import { toRaw, unref } from 'vue';
-import { isFunction } from '@/utils/zt/is';
+import { isObject } from '@vueuse/core';
+import { isArray, isFunction } from '@/utils/zt/is';
+import { deepMerge } from '@/utils/zt';
+import { cloneDeep } from '@/utils/zt/lodashChunk';
 import type { FormActionType, FormProps, FormSchema } from '../types/form';
 import type { FormActionType, FormProps, FormSchema } from '../types/form';
 
 
 // 定义事件类型枚举
 // 定义事件类型枚举
-type FormEvents = 'register' | 'reset' | 'submit';
+type FormEvents = 'registerForm' | 'reset' | 'submit';
 
 
 // 修改 EmitType 类型
 // 修改 EmitType 类型
 declare type EmitType = (event: FormEvents, ...args: any[]) => void;
 declare type EmitType = (event: FormEvents, ...args: any[]) => void;
@@ -17,6 +20,7 @@ interface UseFormActionContext {
   formElRef: Ref<FormActionType>;
   formElRef: Ref<FormActionType>;
   defaultFormModel: Recordable;
   defaultFormModel: Recordable;
   loadingSub: Ref<boolean>;
   loadingSub: Ref<boolean>;
+  schemaRef: Ref<FormSchema[]>;
   handleFormValues: (values: Recordable) => Recordable;
   handleFormValues: (values: Recordable) => Recordable;
 }
 }
 
 
@@ -28,6 +32,7 @@ export function useFormEvents({
   formElRef,
   formElRef,
   defaultFormModel,
   defaultFormModel,
   loadingSub,
   loadingSub,
+  schemaRef,
   handleFormValues
   handleFormValues
 }: UseFormActionContext) {
 }: UseFormActionContext) {
   // 验证
   // 验证
@@ -106,6 +111,37 @@ export function useFormEvents({
       }
       }
     });
     });
   }
   }
+  function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
+    let updateData: Partial<FormSchema>[] = [];
+    if (isObject(data)) {
+      updateData.push(data as FormSchema);
+    }
+    if (isArray(data)) {
+      updateData = [...data];
+    }
+
+    const hasField = updateData.every(item => Reflect.has(item, 'field') && item.field);
+
+    if (!hasField) {
+      console.error('All children of the form Schema array that need to be updated must contain the  field');
+      return;
+    }
+
+    // 只更新传入的字段,其余保持不变
+    const schema: FormSchema[] = cloneDeep(unref(getSchema));
+    const updatedFields: string[] = [];
+
+    updateData.forEach(item => {
+      if (item.field) {
+        const index = schema.findIndex(val => val.field === item.field);
+        if (index !== -1) {
+          schema[index] = deepMerge(schema[index], item) as FormSchema;
+          updatedFields.push(item.field);
+        }
+      }
+    });
+    schemaRef.value = schema;
+  }
 
 
   function setLoading(value: boolean): void {
   function setLoading(value: boolean): void {
     loadingSub.value = value;
     loadingSub.value = value;
@@ -118,6 +154,7 @@ export function useFormEvents({
     getFieldsValue,
     getFieldsValue,
     clearValidate,
     clearValidate,
     setFieldsValue,
     setFieldsValue,
-    setLoading
+    setLoading,
+    updateSchema
   };
   };
 }
 }

+ 0 - 1
src/components/zt/Form/hooks/useFormValues.ts

@@ -38,7 +38,6 @@ export function useFormValues({ defaultFormModel, getSchema, formModel }: UseFor
       if (!isNullOrUnDef(defaultValue)) {
       if (!isNullOrUnDef(defaultValue)) {
         obj[item.field] = defaultValue;
         obj[item.field] = defaultValue;
         formModel[item.field] = defaultValue;
         formModel[item.field] = defaultValue;
-        // console.log(formModel[item.field], 'formModel[item.field]');
       }
       }
     });
     });
     defaultFormModel.value = obj;
     defaultFormModel.value = obj;

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

@@ -3,7 +3,7 @@ import type { GridItemProps, GridProps } from 'naive-ui/lib/grid';
 import type { ButtonProps } from 'naive-ui/lib/button';
 import type { ButtonProps } from 'naive-ui/lib/button';
 import { propTypes } from '@/utils/propTypes';
 import { propTypes } from '@/utils/propTypes';
 import type { FormSchema } from './types/form';
 import type { FormSchema } from './types/form';
-export const basicProps = {
+export const basicFormProps = {
   // 标签宽度  固定宽度
   // 标签宽度  固定宽度
   labelWidth: {
   labelWidth: {
     type: [Number, String] as PropType<number | string>,
     type: [Number, String] as PropType<number | string>,

+ 8 - 3
src/components/zt/Form/types/form.ts

@@ -46,6 +46,8 @@ import {
 } from 'naive-ui';
 } from 'naive-ui';
 import type { GridItemProps, GridProps } from 'naive-ui/lib/grid';
 import type { GridItemProps, GridProps } from 'naive-ui/lib/grid';
 import type { ButtonProps } from 'naive-ui/lib/button';
 import type { ButtonProps } from 'naive-ui/lib/button';
+import { ApiSelect } from '../../ApiSelect';
+import type { ApiSelectProps } from '../../ApiSelect/type';
 // componentMap.ts
 // componentMap.ts
 export interface FormProps {
 export interface FormProps {
   model?: Recordable;
   model?: Recordable;
@@ -75,7 +77,7 @@ export interface FormProps {
 
 
 export interface FormActionType {
 export interface FormActionType {
   submit: () => Promise<any>;
   submit: () => Promise<any>;
-  setProps: (formProps: Partial<FormProps>) => Promise<void>;
+  setFormProps: (formProps: Partial<FormProps>) => Promise<void>;
   setSchema: (schemaProps: Partial<FormSchema[]>) => Promise<void>;
   setSchema: (schemaProps: Partial<FormSchema[]>) => Promise<void>;
   setFieldsValue: (values: Recordable) => void;
   setFieldsValue: (values: Recordable) => void;
   clearValidate: (name?: string | string[]) => Promise<void>;
   clearValidate: (name?: string | string[]) => Promise<void>;
@@ -84,6 +86,7 @@ export interface FormActionType {
   validate: (nameList?: any[]) => Promise<any>;
   validate: (nameList?: any[]) => Promise<any>;
   setLoading: (status: boolean) => void;
   setLoading: (status: boolean) => void;
   restoreValidation: () => void;
   restoreValidation: () => void;
+  updateSchema: (FormSchema: Partial<FormSchema> | Partial<FormSchema>[]) => void;
 }
 }
 
 
 export type RegisterFn = (formInstance: FormActionType) => void;
 export type RegisterFn = (formInstance: FormActionType) => void;
@@ -117,7 +120,8 @@ export type FormSchema =
   | FormSchemaWithType<'NMention', MentionProps>
   | FormSchemaWithType<'NMention', MentionProps>
   | FormSchemaWithType<'NQrCode', QrCodeProps>
   | FormSchemaWithType<'NQrCode', QrCodeProps>
   | FormSchemaWithType<'NCalendar', CalendarProps>
   | FormSchemaWithType<'NCalendar', CalendarProps>
-  | FormSchemaWithType<'NDynamicTags', DynamicTagsProps>;
+  | FormSchemaWithType<'NDynamicTags', DynamicTagsProps>
+  | FormSchemaWithType<'ApiSelect', ApiSelectProps>;
 
 
 export interface RenderCallbackParams {
 export interface RenderCallbackParams {
   schema: FormSchema;
   schema: FormSchema;
@@ -196,7 +200,8 @@ export const componentMap = {
   NQrCode,
   NQrCode,
   NCalendar,
   NCalendar,
   NDynamicTags,
   NDynamicTags,
-  NMention
+  NMention,
+  ApiSelect
 } as const;
 } as const;
 
 
 export type ComponentMap = typeof componentMap;
 export type ComponentMap = typeof componentMap;

+ 110 - 3
src/components/zt/Modal/basic-modal.vue

@@ -1,7 +1,114 @@
-<script setup lang="ts"></script>
+// 修改 script 标签内相关部分如下:
+<script lang="ts">
+import { computed, defineComponent, getCurrentInstance, onMounted, ref, unref, useAttrs } from 'vue';
+import { deepMerge } from '@/utils/zt';
+import { basicModalProps } from './props';
+import type { ModalMethods, modalProps } from './types';
+
+export default defineComponent({
+  name: 'BasicModal',
+  props: { ...basicModalProps },
+  emits: ['registerModal', 'close', 'ok'],
+  setup(props, { emit }) {
+    const attrs = useAttrs();
+    const propsRef = ref<Partial<typeof basicModalProps> | null>(null);
+    const isModal = ref(false);
+    const subLoading = ref(false);
+    const getProps = computed((): modalProps => {
+      return { ...props, ...unref(propsRef) } as unknown as modalProps;
+    });
+
+    async function setModalProps(modalProps: Partial<modalProps>): Promise<void> {
+      propsRef.value = deepMerge(unref(propsRef) || {}, modalProps);
+    }
+
+    const getBindValue = computed(() => {
+      return {
+        ...attrs,
+        ...unref(getProps)
+      };
+    });
+
+    function setSubLoading(status: boolean) {
+      subLoading.value = status;
+    }
+
+    function openModal() {
+      isModal.value = true;
+    }
+
+    function closeModal() {
+      isModal.value = false;
+      subLoading.value = false;
+      emit('close');
+    }
+
+    function onCloseModal() {
+      isModal.value = false;
+      subLoading.value = false;
+      emit('close');
+    }
+
+    function handleSubmit() {
+      subLoading.value = true;
+      emit('ok');
+    }
+
+    const modalMethods: ModalMethods = {
+      setModalProps,
+      openModal,
+      closeModal,
+      setSubLoading
+    };
+
+    const instance = getCurrentInstance();
+
+    onMounted(() => {
+      if (instance) {
+        emit('registerModal', modalMethods);
+      }
+    });
+    return {
+      onCloseModal,
+      handleSubmit,
+      getBindValue,
+      isModal,
+      closeModal,
+      subLoading
+    };
+  }
+});
+</script>
 
 
 <template>
 <template>
-  <div></div>
+  <NModal
+    v-bind="getBindValue"
+    v-model:show="isModal"
+    :style="{ width: getBindValue.width + 'px' }"
+    @close="onCloseModal"
+  >
+    <template #header>
+      <div id="basic-modal-bar" class="w-full cursor-move">{{ getBindValue.title }}</div>
+    </template>
+    <template #default>
+      <NScrollbar class="pr-20px" :style="{ height: getBindValue.height + 'px' }">
+        <slot name="default"></slot>
+      </NScrollbar>
+    </template>
+    <template v-if="!$slots.action" #action>
+      <NSpace justify="end">
+        <NButton @click="closeModal">取消</NButton>
+        <NButton type="primary" :loading="subLoading" @click="handleSubmit">{{ getBindValue.subBtuText }}</NButton>
+      </NSpace>
+    </template>
+    <template v-else #action>
+      <slot name="action"></slot>
+    </template>
+  </NModal>
 </template>
 </template>
 
 
-<style scoped></style>
+<style lang="scss">
+.cursor-move {
+  cursor: move;
+}
+</style>

+ 53 - 0
src/components/zt/Modal/hooks/useModal.ts

@@ -0,0 +1,53 @@
+import { getCurrentInstance, ref, unref, watch } from 'vue';
+import { tryOnUnmounted } from '@vueuse/core';
+import type { ModalMethods, UseModalReturnType, modalProps } from '../types';
+export function useModal(props: modalProps): UseModalReturnType {
+  const modalRef = ref<Nullable<ModalMethods>>(null);
+  const currentInstance = getCurrentInstance();
+
+  const getInstance = () => {
+    const instance = unref(modalRef.value);
+    if (!instance) {
+      console.error('useModal instance is undefined!');
+    }
+    return instance;
+  };
+
+  const register = (modalInstance: ModalMethods) => {
+    tryOnUnmounted(() => {
+      modalRef.value = null;
+    });
+    modalRef.value = modalInstance;
+    currentInstance?.emit('registerModal', modalInstance);
+
+    watch(
+      () => props,
+      () => {
+        if (props) {
+          modalInstance.setModalProps(props);
+        }
+      },
+      {
+        immediate: true,
+        deep: true
+      }
+    );
+  };
+
+  const methods: ModalMethods = {
+    setModalProps: (newProps: modalProps): void => {
+      getInstance()?.setModalProps(newProps);
+    },
+    openModal: (record?: Recordable) => {
+      getInstance()?.openModal(record);
+    },
+    closeModal: () => {
+      getInstance()?.closeModal();
+    },
+    setSubLoading: status => {
+      getInstance()?.setSubLoading(status);
+    }
+  };
+
+  return [register, methods];
+}

+ 42 - 0
src/components/zt/Modal/props.ts

@@ -0,0 +1,42 @@
+import { modalProps } from 'naive-ui/lib';
+
+export const basicModalProps = {
+  ...modalProps,
+  // 确认按钮文字
+  subBtuText: {
+    type: String,
+    default: '确认'
+  },
+  showIcon: {
+    type: Boolean,
+    default: false
+  },
+  width: {
+    type: Number,
+    default: 800
+  },
+  title: {
+    type: String,
+    default: ''
+  },
+  maskClosable: {
+    type: Boolean,
+    default: false
+  },
+  preset: {
+    type: String,
+    default: 'card'
+  },
+  draggable: {
+    default: true,
+    type: Boolean
+  },
+  isShowHeaderText: {
+    type: Boolean,
+    default: true
+  },
+  height: {
+    default: 500,
+    type: Number
+  }
+};

+ 50 - 0
src/components/zt/Modal/types/index.ts

@@ -0,0 +1,50 @@
+import type { ModalProps } from 'naive-ui/lib/';
+/**
+ * @description: 弹窗对外暴露的方法
+ */
+export interface ModalMethods {
+  setModalProps: (props: modalProps) => void;
+  openModal: (record?: Recordable, isView?: boolean) => void;
+  closeModal: () => void;
+  setSubLoading: (status: boolean) => void;
+}
+
+/**
+ * 支持修改,DialogOptions 參數
+ */
+export interface modalProps extends ModalProps {
+  /**
+   * 弹窗标题
+   */
+  title?: string;
+  /**
+   * 弹窗宽度
+   */
+  width?: number;
+  /**
+   * 弹窗是否显示
+   */
+  visible?: boolean;
+  /**
+   * 弹窗是否显示遮罩层
+   */
+  maskClosable?: boolean;
+  /**
+   * 弹窗是否显示头部
+   */
+  showHeader?: boolean;
+  subBtuText?: string;
+  showIcon?: boolean;
+  /** \
+   * 弹窗标题是否自动拼接新增和编辑
+   */
+  isShowHeaderText?: boolean;
+  /**
+   * 弹窗内容高度
+   */
+  height?: number;
+}
+
+export type RegisterFn = (ModalInstance: ModalMethods) => void;
+
+export type UseModalReturnType = [RegisterFn, ModalMethods];

+ 94 - 0
src/components/zt/ModalForm/basic-model-form.vue

@@ -0,0 +1,94 @@
+<script lang="ts">
+import { computed, defineComponent, onMounted, ref, unref } from 'vue';
+import { useModal } from '../Modal/hooks/useModal';
+import { useForm } from '../Form/hooks/useForm';
+import type { modalProps } from '../Modal/types';
+import type { FormProps } from '../Form/types/form';
+import type { ModalFromMethods } from './type';
+
+export default defineComponent({
+  name: 'ModalForm',
+  emits: ['registerModalForm', 'submitForm'],
+  setup(props, { emit }) {
+    const modelPropsRef = ref<Partial<modalProps>>();
+    const formPropsRef = ref<Partial<FormProps>>();
+    console.log(props, 'registerModalForm');
+
+    const formComputRef = computed(() => {
+      return {
+        labelWidth: 180,
+        layout: 'horizontal',
+        gridProps: {
+          cols: '1 xl:4 s:1 l:3',
+          itemResponsive: true
+        },
+        collapsedRows: 1,
+        showActionButtonGroup: false,
+        ...unref(formPropsRef)
+      };
+    });
+    const modalComputRef = computed(() => {
+      return {
+        ...unref(modelPropsRef)
+      };
+    });
+    const [registerModal, { openModal, closeModal, setSubLoading, setModalProps }] = useModal(unref(modalComputRef));
+    const [
+      registerForm,
+      {
+        validate,
+        restoreValidation,
+        setFieldsValue,
+        resetFields,
+        setLoading,
+        setSchema,
+        submit,
+        clearValidate,
+        getFieldsValue,
+        setFormProps,
+        updateSchema
+      }
+    ] = useForm(formComputRef.value);
+    const methods: ModalFromMethods = {
+      setModalProps,
+      openModal,
+      closeModal,
+      setFieldsValue,
+      resetFields,
+      setFormProps,
+      setLoading,
+      setSchema,
+      submit,
+      validate,
+      clearValidate,
+      getFieldsValue,
+      restoreValidation,
+      setSubLoading,
+      updateSchema
+    };
+
+    onMounted(() => {
+      emit('registerModalForm', methods);
+    });
+    async function handleSubmitForm() {
+      setSubLoading(false);
+      await validate();
+      setSubLoading(true);
+      emit('submitForm');
+    }
+    return {
+      registerModal,
+      registerForm,
+      handleSubmitForm
+    };
+  }
+});
+</script>
+
+<template>
+  <BasicModal @register-modal="registerModal" @ok="handleSubmitForm">
+    <BasicForm @register-form="registerForm"></BasicForm>
+  </BasicModal>
+</template>
+
+<style scoped></style>

+ 134 - 0
src/components/zt/ModalForm/hooks/useModalForm.ts

@@ -0,0 +1,134 @@
+import { getCurrentInstance, nextTick, ref, unref, watch } from 'vue';
+import { tryOnUnmounted } from '@vueuse/core';
+import type { ModalFromMethods, UseModalFormReturnType, modalFormProps } from '../type';
+import type { modalProps } from '../../Modal/types';
+import type { FormActionType, FormProps, FormSchema } from '../../Form/types/form';
+export function useModalFrom(props: modalFormProps): UseModalFormReturnType {
+  const modalRef = ref<Nullable<ModalFromMethods>>(null);
+  const formRef = ref<Nullable<FormActionType>>(null);
+  const currentInstance = getCurrentInstance();
+
+  const getModalInstance = () => {
+    const instance = unref(modalRef.value);
+    if (!instance) {
+      console.error('useModal instance is undefined!');
+    }
+    return instance;
+  };
+
+  async function getForm() {
+    const form = unref(formRef);
+    if (!form) {
+      console.error(
+        'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!'
+      );
+    }
+    await nextTick();
+    return form as FormActionType;
+  }
+
+  const register = (modalInstance: ModalFromMethods) => {
+    tryOnUnmounted(() => {
+      modalRef.value = null;
+      formRef.value = null;
+    });
+    modalRef.value = modalInstance;
+    currentInstance?.emit('registerModalForm', modalInstance);
+    formRef.value = modalInstance;
+
+    watch(
+      () => props,
+      () => {
+        if (props) {
+          modalInstance.setModalProps(props.modalConfig);
+          // modalInstance.setFormProps(props.formConfig);
+        }
+      },
+      {
+        immediate: true,
+        deep: true
+      }
+    );
+  };
+
+  const methods: ModalFromMethods = {
+    // 表单相关方法
+    updateSchema: async (formSchema: Partial<FormSchema> | Partial<FormSchema>[]) => {
+      const form = await getForm();
+      console.log(form, 'form');
+
+      nextTick(async () => {
+        await form.updateSchema(formSchema);
+      });
+    },
+    setFormProps: async (newProps: Partial<FormProps>): Promise<void> => {
+      const form = await getForm();
+      await form?.setFormProps(newProps);
+    },
+    submit: async (): Promise<any> => {
+      const form = await getForm();
+      return await form?.submit();
+    },
+    setSchema: async schema => {
+      const form = await getForm();
+      await form?.setSchema(schema);
+    },
+    setFieldsValue: async values => {
+      const form = await getForm();
+      form?.setFieldsValue(values);
+    },
+    clearValidate: async () => {
+      const form = await getForm();
+      await form?.clearValidate();
+    },
+    validate: async () => {
+      const form = await getForm();
+      return await form?.validate();
+    },
+    resetFields: async () => {
+      const form = await getForm();
+      form?.resetFields();
+    },
+    getFieldsValue: async () => {
+      const form = await getForm();
+      return form?.getFieldsValue();
+    },
+    setLoading: async status => {
+      const form = await getForm();
+      form?.setLoading(status);
+    },
+    restoreValidation: async () => {
+      const form = await getForm();
+      form?.restoreValidation();
+    },
+
+    // 模态框相关方法
+    setModalProps: (newProps: modalProps): void => {
+      getModalInstance()?.setModalProps(newProps);
+    },
+    openModal: (record?: Recordable, isView?: boolean) => {
+      getModalInstance()?.openModal(record);
+      if (record && !isView && props.modalConfig.isShowHeaderText) {
+        getModalInstance()?.setModalProps({ title: `编辑${props.modalConfig.title}` });
+      }
+      if (!record && !isView && props.modalConfig.isShowHeaderText) {
+        getModalInstance()?.setModalProps({ title: `新增${props.modalConfig.title}` });
+      }
+      if (record && isView && props.modalConfig.isShowHeaderText) {
+        getModalInstance()?.setModalProps({ title: `查看${props.modalConfig.title}` });
+      }
+      nextTick(() => {
+        getModalInstance()?.setFormProps(props.formConfig);
+        getModalInstance()?.resetFields();
+      });
+    },
+    closeModal: () => {
+      getModalInstance()?.closeModal();
+    },
+    setSubLoading: (status: boolean) => {
+      getModalInstance()?.setSubLoading(status);
+    }
+  };
+
+  return [register, methods];
+}

+ 15 - 0
src/components/zt/ModalForm/props.ts

@@ -0,0 +1,15 @@
+import { basicFormProps } from '../Form/props';
+import type { FormProps } from '../Form/types/form';
+import { basicModalProps } from '../Modal/props';
+import type { modalProps } from '../Modal/types';
+
+export const basicProps = {
+  modalConfig: {
+    type: Object as PropType<Partial<modalProps>>,
+    default: () => basicModalProps
+  },
+  formConfig: {
+    type: Object as PropType<Partial<FormProps>>,
+    default: () => basicFormProps
+  }
+};

+ 13 - 0
src/components/zt/ModalForm/type/index.ts

@@ -0,0 +1,13 @@
+import type { FormActionType, FormProps } from '../../Form/types/form';
+import type { ModalMethods, modalProps } from '../../Modal/types';
+
+export type modalFormProps = {
+  formConfig: Partial<FormProps>;
+  modalConfig: Partial<modalProps>;
+};
+
+export type ModalFromMethods = FormActionType & ModalMethods;
+
+export type RegisterFn = (ModalInstance: ModalFromMethods) => void;
+
+export type UseModalFormReturnType = [RegisterFn, ModalFromMethods];

+ 50 - 0
src/components/zt/Table/hooks/useTable.ts

@@ -0,0 +1,50 @@
+import { onUnmounted, ref, unref, watch } from 'vue';
+import { getDynamicProps } from '@/utils/zt';
+import type { FormProps } from '../../Form/types/form';
+export function useTable(tableProps: FormProps): UseTableReturnType {
+  const tableRef = ref<Nullable<TableMethods>>(null);
+  function register(instance: TableMethods) {
+    onUnmounted(() => {
+      tableRef.value = null;
+    });
+
+    if (instance === unref(tableRef)) return;
+
+    tableRef.value = instance;
+    watch(
+      () => tableProps,
+      () => {
+        if (tableProps) {
+          instance.setSearchProps(getDynamicProps(tableProps) as FormProps);
+        }
+      },
+      {
+        immediate: true,
+        deep: true
+      }
+    );
+  }
+  const methods: TableMethods = {
+    refresh: async () => {
+      await tableRef.value?.refresh();
+    },
+
+    setSearchProps(props) {
+      tableRef.value?.setSearchProps(props);
+    },
+    setTableLoading(loading) {
+      tableRef.value?.setTableLoading(loading);
+    }
+  };
+  return [register, methods];
+}
+
+export interface TableMethods {
+  refresh: () => Promise<any>;
+  setSearchProps: (props: FormProps) => void;
+  setTableLoading: (loading: boolean) => void;
+}
+
+export type RegisterFn = (TableInstance: TableMethods) => void;
+
+export type UseTableReturnType = [RegisterFn, TableMethods];

+ 28 - 0
src/components/zt/Table/props.ts

@@ -0,0 +1,28 @@
+import type { FormProps } from '../Form/types/form';
+import type { tableProp } from './types';
+export const basicProps = {
+  searchFormConfig: {
+    type: Object as PropType<FormProps>,
+    default: {
+      labelWidth: 120,
+      layout: 'horizontal',
+      gridProps: {
+        cols: '1 xl:4 s:1 l:3',
+        itemResponsive: true
+      },
+      collapsedRows: 1
+    }
+  },
+  tableConfig: {
+    type: Object as PropType<tableProp>,
+    default: {
+      opWdith: 130,
+      title: '',
+      showDeleteButton: true,
+      showAddButton: true,
+      keyField: 'id',
+      api: () => Promise.resolve(),
+      columns: () => []
+    }
+  }
+};

+ 11 - 15
src/components/zt/Table/types/index.ts

@@ -1,24 +1,20 @@
 import type { InternalRowData } from 'naive-ui/es/data-table/src/interface';
 import type { InternalRowData } from 'naive-ui/es/data-table/src/interface';
-import type { FormSchema } from '../../Form/types/form';
+import type { FormProps } from '../../Form/types/form';
 
 
-export interface tableProps {
+export interface SearchProps {
   /**
   /**
    * 表单配置
    * 表单配置
    */
    */
-  formConfig: FormSchema[];
-  /**
-   * 请求接口
-   * @returns
-   */
-  api: () => Promise<any>;
-  /**
-   * 	需要展示的列
-   */
+  searchFormConfig?: FormProps;
+}
+
+export interface tableProp {
+  opWdith?: number;
+  keyField: string;
+
+  api: (params?: any) => Promise<any>;
+
   columns: NaiveUI.TableColumn<InternalRowData>[];
   columns: NaiveUI.TableColumn<InternalRowData>[];
-  /**
-   * 	需要展示的数据
-   */
-  data?: any[];
   /**
   /**
    * 表格标题
    * 表格标题
    */
    */

+ 147 - 67
src/components/zt/Table/z-table.vue

@@ -1,79 +1,159 @@
-<script setup lang="ts">
+<script lang="tsx">
+import { computed, defineComponent, onMounted, reactive, ref, toRaw, unref } from 'vue';
 import { useAppStore } from '@/store/modules/app';
 import { useAppStore } from '@/store/modules/app';
 import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
 import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
 import { useForm } from '../Form/hooks/useForm';
 import { useForm } from '../Form/hooks/useForm';
-import type { tableProps } from './types';
-const appStore = useAppStore();
-const props = defineProps<tableProps>();
-const emit = defineEmits<{
-  (e: 'add'): void;
-  (e: 'delete', data: string[]): void;
-}>();
-const [registerForm, { getFieldsValue }] = useForm({
-  schemas: props.formConfig,
-  labelWidth: 120,
-  layout: 'horizontal',
-  gridProps: {
-    cols: '1 xl:4 s:1 l:3',
-    itemResponsive: true
+import type { FormProps } from '../Form/types/form';
+import type { TableMethods } from './hooks/useTable';
+import type { tableProp } from './types';
+import { basicProps } from './props';
+export default defineComponent({
+  name: 'ZTable',
+  props: {
+    ...basicProps
   },
   },
-  collapsedRows: 1
-});
-const { columns, columnChecks, data, getData, loading, mobilePagination } = useNaivePaginatedTable({
-  api: () => props.api(),
-  transform: response => defaultTransform(response),
-  // onPaginationParamsChange: params => {
-  //   searchParams.current = params.page;
-  //   searchParams.size = params.pageSize;
-  // },
-  columns: () => props.columns
-});
-const { checkedRowKeys } = useTableOperate(data, 'id', getData);
-function handleSearch() {
-  const params = getFieldsValue();
-  console.log(params, 'params请求参数');
-}
-defineExpose({
-  getData
+  emits: ['register', 'add', 'delete'],
+  setup(props, { emit, slots }) {
+    const appStore = useAppStore();
+    const propsData = toRaw(props);
+    const searchProps = ref<FormProps>();
+    const tableProps = ref<tableProp>();
+    const getFormSearch = computed(() => {
+      return {
+        labelWidth: 120,
+        layout: 'horizontal',
+        gridProps: {
+          cols: '1 xl:4 s:1 l:3',
+          itemResponsive: true
+        },
+        collapsedRows: 1,
+        ...unref(searchProps)
+      };
+    });
+    const getTableProps = computed(() => {
+      return {
+        ...propsData,
+        ...propsData.tableConfig,
+        ...unref(tableProps)
+      };
+    });
+    const [registerSearchForm, { getFieldsValue: getSeachForm, setFormProps: setSearchProps }] = useForm(
+      getFormSearch.value
+    );
+    const searchPage = reactive({
+      current: 1,
+      size: 10
+    });
+    const getForm = ref();
+    const { columns, columnChecks, data, getData, loading, mobilePagination } = useNaivePaginatedTable({
+      api: () => getTableProps.value.api({ ...searchPage, ...getForm.value }),
+      transform: response => defaultTransform(response),
+      onPaginationParamsChange: params => {
+        searchPage.current = Number(params.page);
+        searchPage.size = Number(params.pageSize);
+      },
+      paginationProps: {
+        pageSizes: [10, 20, 50, 100, 150, 200]
+      },
+      columns: () => [
+        ...propsData.tableConfig.columns,
+        {
+          key: 'operate',
+          title: '操作',
+          align: 'center',
+          width: propsData.tableConfig.opWdith,
+          fixed: 'right',
+          render: row => <div class="flex-center gap-8px">{slots.op ? slots.op({ row }) : ''}</div>
+        }
+      ]
+    });
+    const { checkedRowKeys } = useTableOperate(data, propsData.tableConfig.keyField, getData);
+
+    const TableMethod: TableMethods = {
+      refresh: getData,
+      setSearchProps,
+      setTableLoading
+    };
+    function setTableLoading(flage: boolean) {
+      loading.value = flage;
+    }
+    onMounted(() => {
+      emit('register', TableMethod);
+    });
+    function handleAdd() {
+      emit('add');
+    }
+    function handleDelete() {
+      emit('delete', checkedRowKeys.value);
+    }
+    function handleReset() {
+      getForm.value = getSeachForm();
+      getData();
+    }
+    function handleSearch() {
+      getForm.value = getSeachForm();
+      getData();
+    }
+    return {
+      registerSearchForm,
+      handleDelete,
+      checkedRowKeys,
+      appStore,
+      loading,
+      mobilePagination,
+      data,
+      columns,
+      tableProps,
+      columnChecks,
+      getTableProps,
+      handleAdd,
+      getData,
+      handleReset,
+      handleSearch
+    };
+  }
 });
 });
 </script>
 </script>
 
 
 <template>
 <template>
-  <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
-    <NCard :bordered="false" size="small">
-      <NCollapse>
-        <NCollapseItem title="搜索">
-          <BasicForm @register="registerForm" @submit="handleSearch" />
-        </NCollapseItem>
-      </NCollapse>
-    </NCard>
-    <NCard :title="title" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
-      <template #header-extra>
-        <TableHeaderOperation
-          v-model:columns="columnChecks"
-          :disabled-delete="checkedRowKeys.length === 0"
-          :loading="loading"
-          :is-delete="showDeleteButton"
-          :is-add="showAddButton"
-          @add="emit('add')"
-          @delete="emit('delete', checkedRowKeys)"
-        />
-      </template>
-      <NDataTable
-        v-model:checked-row-keys="checkedRowKeys"
-        :columns="columns"
-        :data="data"
-        size="small"
-        :flex-height="!appStore.isMobile"
-        :scroll-x="962"
+  <NCard :bordered="false" size="small">
+    <NCollapse display-directive="show" :default-expanded-names="['role-search']">
+      <NCollapseItem title="搜索" name="role-search">
+        <BasicForm @register-form="registerSearchForm" @submit="handleSearch" @reset="handleReset" />
+      </NCollapseItem>
+    </NCollapse>
+  </NCard>
+  <NCard :bordered="false" :title="getTableProps.title" size="small" class="card-wrapper sm:flex-1-hidden">
+    <template #header-extra>
+      <TableHeaderOperation
+        v-model:columns="columnChecks"
+        :disabled-delete="checkedRowKeys.length === 0"
         :loading="loading"
         :loading="loading"
-        remote
-        :row-key="row => row.id"
-        :pagination="mobilePagination"
-        class="sm:h-full"
-      />
-    </NCard>
-  </div>
+        :is-add="getTableProps.showAddButton"
+        :is-delete="getTableProps.showDeleteButton"
+        @add="handleAdd"
+        @delete="handleDelete"
+        @refresh="getData"
+      >
+        <template #prefix>
+          <slot name="prefix"></slot>
+        </template>
+      </TableHeaderOperation>
+    </template>
+    <NDataTable
+      v-model:checked-row-keys="checkedRowKeys"
+      :columns="columns"
+      :data="data"
+      size="small"
+      :flex-height="!appStore.isMobile"
+      :scroll-x="962"
+      :loading="loading"
+      remote
+      :row-key="row => row[getTableProps.keyField]"
+      :pagination="mobilePagination"
+      class="sm:h-full"
+    />
+  </NCard>
 </template>
 </template>
 
 
 <style scoped></style>
 <style scoped></style>

+ 9 - 0
src/components/zt/layoutTable/layout-table.vue

@@ -0,0 +1,9 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+    <slot></slot>
+  </div>
+</template>
+
+<style scoped></style>

+ 1 - 1
src/constants/business.ts

@@ -2,7 +2,7 @@ import { transformRecordToOption } from '@/utils/common';
 
 
 export const enableStatusRecord: Record<Api.Common.EnableStatus, App.I18n.I18nKey> = {
 export const enableStatusRecord: Record<Api.Common.EnableStatus, App.I18n.I18nKey> = {
   '1': 'page.manage.common.status.enable',
   '1': 'page.manage.common.status.enable',
-  '2': 'page.manage.common.status.disable'
+  '0': 'page.manage.common.status.disable'
 };
 };
 
 
 export const enableStatusOptions = transformRecordToOption(enableStatusRecord);
 export const enableStatusOptions = transformRecordToOption(enableStatusRecord);

+ 1 - 0
src/hooks/common/router.ts

@@ -95,6 +95,7 @@ export function useRouterPush(inSetup = true) {
    */
    */
   async function redirectFromLogin(needRedirect = true) {
   async function redirectFromLogin(needRedirect = true) {
     const redirect = route.value.query?.redirect as string;
     const redirect = route.value.query?.redirect as string;
+    console.log(needRedirect, redirect, '跳转至退出登录之前的页面');
 
 
     if (needRedirect && redirect) {
     if (needRedirect && redirect) {
       await routerPush(redirect);
       await routerPush(redirect);

+ 1 - 1
src/hooks/common/table.ts

@@ -66,7 +66,7 @@ export function useNaiveTable<ResponseData, ApiData>(options: UseNaiveTableOptio
 
 
 type PaginationParams = Pick<PaginationProps, 'page' | 'pageSize'>;
 type PaginationParams = Pick<PaginationProps, 'page' | 'pageSize'>;
 
 
-type UseNaivePaginatedTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, true> & {
+export type UseNaivePaginatedTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, true> & {
   paginationProps?: Omit<PaginationProps, 'page' | 'pageSize' | 'itemCount'>;
   paginationProps?: Omit<PaginationProps, 'page' | 'pageSize' | 'itemCount'>;
   /**
   /**
    * whether to show the total count of the table
    * whether to show the total count of the table

+ 2 - 1
src/locales/langs/en-us.ts

@@ -280,7 +280,8 @@ const local: App.I18n.Schema = {
     plugin_tables_vtable: 'VTable',
     plugin_tables_vtable: 'VTable',
     manage_config: '',
     manage_config: '',
     manage_log: '',
     manage_log: '',
-    manage_schedule: ''
+    manage_schedule: '',
+    manage_department: ''
   },
   },
   page: {
   page: {
     login: {
     login: {

+ 3 - 2
src/locales/langs/zh-cn.ts

@@ -277,7 +277,8 @@ const local: App.I18n.Schema = {
     plugin_tables_vtable: 'VTable',
     plugin_tables_vtable: 'VTable',
     manage_config: '',
     manage_config: '',
     manage_log: '',
     manage_log: '',
-    manage_schedule: ''
+    manage_schedule: '',
+    manage_department: ''
   },
   },
   page: {
   page: {
     login: {
     login: {
@@ -342,7 +343,7 @@ const local: App.I18n.Schema = {
     home: {
     home: {
       branchDesc:
       branchDesc:
         '为了方便大家开发和更新合并,我们对main分支的代码进行了精简,只保留了首页菜单,其余内容已移至example分支进行维护。预览地址显示的内容即为example分支的内容。',
         '为了方便大家开发和更新合并,我们对main分支的代码进行了精简,只保留了首页菜单,其余内容已移至example分支进行维护。预览地址显示的内容即为example分支的内容。',
-      greeting: '早安,{userName}, 今天又是充满活力的一天!',
+      greeting: '早安,{username}, 今天又是充满活力的一天!',
       weatherDesc: '今日多云转晴,20℃ - 25℃!',
       weatherDesc: '今日多云转晴,20℃ - 25℃!',
       projectCount: '项目数',
       projectCount: '项目数',
       todo: '待办',
       todo: '待办',

+ 1 - 0
src/router/elegant/imports.ts

@@ -23,6 +23,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
   about: () => import("@/views/about/index.vue"),
   about: () => import("@/views/about/index.vue"),
   home: () => import("@/views/home/index.vue"),
   home: () => import("@/views/home/index.vue"),
   manage_config: () => import("@/views/manage/config/index.vue"),
   manage_config: () => import("@/views/manage/config/index.vue"),
+  manage_department: () => import("@/views/manage/department/index.vue"),
   manage_log: () => import("@/views/manage/log/index.vue"),
   manage_log: () => import("@/views/manage/log/index.vue"),
   manage_menu: () => import("@/views/manage/menu/index.vue"),
   manage_menu: () => import("@/views/manage/menu/index.vue"),
   manage_role: () => import("@/views/manage/role/index.vue"),
   manage_role: () => import("@/views/manage/role/index.vue"),

+ 9 - 0
src/router/elegant/routes.ts

@@ -107,6 +107,15 @@ export const generatedRoutes: GeneratedRoute[] = [
           i18nKey: 'route.manage_config'
           i18nKey: 'route.manage_config'
         }
         }
       },
       },
+      {
+        name: 'manage_department',
+        path: '/manage/department',
+        component: 'view.manage_department',
+        meta: {
+          title: 'manage_department',
+          i18nKey: 'route.manage_department'
+        }
+      },
       {
       {
         name: 'manage_log',
         name: 'manage_log',
         path: '/manage/log',
         path: '/manage/log',

+ 1 - 0
src/router/elegant/transform.ts

@@ -187,6 +187,7 @@ const routeMap: RouteMap = {
   "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
   "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
   "manage": "/manage",
   "manage": "/manage",
   "manage_config": "/manage/config",
   "manage_config": "/manage/config",
+  "manage_department": "/manage/department",
   "manage_log": "/manage/log",
   "manage_log": "/manage/log",
   "manage_menu": "/manage/menu",
   "manage_menu": "/manage/menu",
   "manage_role": "/manage/role",
   "manage_role": "/manage/role",

+ 13 - 0
src/service/api/auth.ts

@@ -64,3 +64,16 @@ export function fetchRefreshToken(refreshToken: string) {
 export function fetchCustomBackendError(code: string, msg: string) {
 export function fetchCustomBackendError(code: string, msg: string) {
   return request({ url: '/auth/error', params: { code, msg } });
   return request({ url: '/auth/error', params: { code, msg } });
 }
 }
+
+/**
+ * 修改用户密码
+ * @param data
+ * @returns
+ */
+export function fetchUserPassWord(data: Api.Auth.UserPassWord) {
+  return request({
+    url: '/sys/user/password',
+    method: 'post',
+    data
+  });
+}

+ 111 - 1
src/service/api/system-manage.ts

@@ -24,7 +24,7 @@ export function fetchGetAllRoles() {
 /** get user list */
 /** get user list */
 export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
 export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
   return request<Api.SystemManage.UserList>({
   return request<Api.SystemManage.UserList>({
-    url: '/systemManage/getUserList',
+    url: '/sys/user/page',
     method: 'get',
     method: 'get',
     params
     params
   });
   });
@@ -91,3 +91,113 @@ export function fetchDelMenu(id: number) {
     method: 'delete'
     method: 'delete'
   });
   });
 }
 }
+
+/**
+ * 获取角色菜单列表
+ * @param roleId
+ * @returns
+ */
+export function fetchGetRoleMenuList(roleId: number) {
+  return request<Api.SystemManage.RoleMenuList>({
+    url: `/sys/role/info/${roleId}`,
+    method: 'get'
+  });
+}
+/**
+ * 新增角色菜单列表
+ * @param data
+ * @returns
+ */
+export function fetchAddRoleMenu(data: Api.SystemManage.RoleMenuList) {
+  return request({
+    url: '/sys/role',
+    method: 'post',
+    data
+  });
+}
+/**
+ * 修改角色菜单列表
+ * @param data
+ * @returns
+ */
+export function fetchEditRoleMenu(data: Api.SystemManage.RoleMenuList) {
+  return request({
+    url: '/sys/role',
+    method: 'put',
+    data
+  });
+}
+/**
+ * 删除角色
+ * @param roleId
+ * @returns
+ */
+
+export function fetchDelRoleMenu(roleId: number) {
+  return request({
+    url: `/sys/role`,
+    method: 'delete',
+    data: [roleId]
+  });
+}
+
+/**
+ *  获取所有角色
+ * @returns
+ */
+export function fetchGetRoleAllList() {
+  return request<Api.SystemManage.RoleList>({
+    url: '/sys/role/list',
+    method: 'get'
+  });
+}
+
+/**
+ * 新增用户
+ * @param data
+ * @returns
+ */
+export function fetchAddUser(data: Api.SystemManage.UserModel) {
+  return request({
+    url: '/sys/user',
+    method: 'post',
+    data
+  });
+}
+
+/**
+ * 修改用户
+ * @param data
+ * @returns
+ */ export function fetchEditUser(data: Api.SystemManage.UserModel) {
+  return request({
+    url: '/sys/user',
+    method: 'put',
+    data
+  });
+}
+
+/**
+ * 用户详情
+ * @param id
+ * @returns
+ */
+export function fetchDetaileUser(id: string) {
+  return request<Api.SystemManage.UserModel>({
+    url: `/sys/user/info/${id}`,
+    method: 'get'
+  });
+}
+
+/**
+ * 删除用户
+ * @param id
+ * @returns
+ */
+export function fetchDeleteUser(id: number) {
+  return request({
+    url: `/sys/user`,
+    method: 'delete',
+    data: [id]
+  });
+}

+ 4 - 2
src/service/request/index.ts

@@ -37,7 +37,10 @@ export const request = createFlatRequest(
       // when the backend response code is "0000"(default), it means the request is success
       // when the backend response code is "0000"(default), it means the request is success
       // to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
       // to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
       // return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
       // return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
-      return String(response.status) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
+      const isLogin = response.config?.url?.includes('platformLogin');
+      const isSuccess =
+        (isLogin ? String(response.status) : String(response.data.code)) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
+      return isSuccess;
     },
     },
     async onBackendFail(response, instance) {
     async onBackendFail(response, instance) {
       const authStore = useAuthStore();
       const authStore = useAuthStore();
@@ -104,7 +107,6 @@ export const request = createFlatRequest(
       // when the request is fail, you can show error message
       // when the request is fail, you can show error message
       let message = error.message;
       let message = error.message;
       let backendErrorCode = '';
       let backendErrorCode = '';
-
       // get backend error message and code
       // get backend error message and code
       if (error.code === BACKEND_ERROR_CODE) {
       if (error.code === BACKEND_ERROR_CODE) {
         message = error.response?.data?.msg || message;
         message = error.response?.data?.msg || message;

+ 5 - 3
src/store/modules/auth/index.ts

@@ -21,7 +21,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
 
 
   const token = ref(getToken());
   const token = ref(getToken());
 
 
-  let userInfo: Api.Auth.UserInfo = reactive({
+  const userInfo: Api.Auth.UserInfo = reactive({
     userId: '',
     userId: '',
     username: '',
     username: '',
     roles: [],
     roles: [],
@@ -110,6 +110,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
       imageCode,
       imageCode,
       sessionUUID
       sessionUUID
     });
     });
+    console.log('login', error, response);
+
     const loginToken: Api.Auth.LoginToken = {
     const loginToken: Api.Auth.LoginToken = {
       token: response.data.access_token,
       token: response.data.access_token,
       refreshToken: response.data.refreshToken
       refreshToken: response.data.refreshToken
@@ -163,8 +165,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
     if (!error) {
     if (!error) {
       // update store
       // update store
       // Object.assign(userInfo, info);
       // Object.assign(userInfo, info);
-      userInfo = info;
-
+      // userInfo = info;
+      Object.assign(userInfo, info);
       return true;
       return true;
     }
     }
 
 

+ 6 - 2
src/typings/api.d.ts

@@ -27,9 +27,9 @@ declare namespace Api {
      * enable status
      * enable status
      *
      *
      * - "1": enabled
      * - "1": enabled
-     * - "2": disabled
+     * - "0": disabled
      */
      */
-    type EnableStatus = '1' | '2';
+    type EnableStatus = '0' | '1';
 
 
     /** common record */
     /** common record */
     type CommonRecord<T = any> = {
     type CommonRecord<T = any> = {
@@ -67,6 +67,10 @@ declare namespace Api {
       email: string;
       email: string;
       mobile: string;
       mobile: string;
     }
     }
+    interface UserPassWord {
+      password: string;
+      newPassword: string;
+    }
   }
   }
 
 
   /**
   /**

+ 16 - 1
src/typings/api/system-manage.d.ts

@@ -5,8 +5,22 @@ declare namespace Api {
    * backend api module: "systemManage"
    * backend api module: "systemManage"
    */
    */
   namespace SystemManage {
   namespace SystemManage {
+    type UserModel = {
+      userId?: number;
+      username: string;
+      password: string;
+      email: string;
+      mobile: string;
+      status: number;
+      roleIdList: string;
+    };
     type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'current' | 'size'>;
     type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'current' | 'size'>;
-
+    type RoleMenuList = {
+      roleId?: number;
+      remark: string;
+      roleName: string;
+      menuIdList: number[];
+    };
     /** role */
     /** role */
     type Role = Common.CommonRecord<{
     type Role = Common.CommonRecord<{
       /** role name */
       /** role name */
@@ -15,6 +29,7 @@ declare namespace Api {
       roleCode: string;
       roleCode: string;
       /** role description */
       /** role description */
       roleDesc: string;
       roleDesc: string;
+      roleId: number;
     }>;
     }>;
 
 
     /** role search params */
     /** role search params */

+ 4 - 0
src/typings/components.d.ts

@@ -8,9 +8,12 @@ export {}
 /* prettier-ignore */
 /* prettier-ignore */
 declare module 'vue' {
 declare module 'vue' {
   export interface GlobalComponents {
   export interface GlobalComponents {
+    ApiSelect: typeof import('./../components/zt/ApiSelect/api-select.vue')['default']
     AppProvider: typeof import('./../components/common/app-provider.vue')['default']
     AppProvider: typeof import('./../components/common/app-provider.vue')['default']
+    'Basic-ModelForm': typeof import('../components/zt/ModalForm/basic-model-form.vue')['default']
     BasicForm: typeof import('./../components/zt/Form/basic-form.vue')['default']
     BasicForm: typeof import('./../components/zt/Form/basic-form.vue')['default']
     BasicModal: typeof import('./../components/zt/Modal/basic-modal.vue')['default']
     BasicModal: typeof import('./../components/zt/Modal/basic-modal.vue')['default']
+    BasicModelForm: typeof import('./../components/zt/ModalForm/basic-model-form.vue')['default']
     BasicUpload: typeof import('./../components/zt/Upload/src/BasicUpload.vue')['default']
     BasicUpload: typeof import('./../components/zt/Upload/src/BasicUpload.vue')['default']
     BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
     BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
     ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
     ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
@@ -59,6 +62,7 @@ declare module 'vue' {
     IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
     IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
     IconUilSearch: typeof import('~icons/uil/search')['default']
     IconUilSearch: typeof import('~icons/uil/search')['default']
     LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
     LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
+    LayoutTable: typeof import('./../components/zt/layoutTable/layout-table.vue')['default']
     LookForward: typeof import('./../components/custom/look-forward.vue')['default']
     LookForward: typeof import('./../components/custom/look-forward.vue')['default']
     MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
     MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
     NAlert: typeof import('naive-ui')['NAlert']
     NAlert: typeof import('naive-ui')['NAlert']

+ 2 - 0
src/typings/elegant-router.d.ts

@@ -41,6 +41,7 @@ declare module "@elegant-router/types" {
     "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
     "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
     "manage": "/manage";
     "manage": "/manage";
     "manage_config": "/manage/config";
     "manage_config": "/manage/config";
+    "manage_department": "/manage/department";
     "manage_log": "/manage/log";
     "manage_log": "/manage/log";
     "manage_menu": "/manage/menu";
     "manage_menu": "/manage/menu";
     "manage_role": "/manage/role";
     "manage_role": "/manage/role";
@@ -162,6 +163,7 @@ declare module "@elegant-router/types" {
     | "about"
     | "about"
     | "home"
     | "home"
     | "manage_config"
     | "manage_config"
+    | "manage_department"
     | "manage_log"
     | "manage_log"
     | "manage_menu"
     | "manage_menu"
     | "manage_role"
     | "manage_role"

+ 63 - 4
src/utils/zt/index.ts

@@ -219,7 +219,66 @@ function convertMenuItem(menu: OriginalMenuItem, allMenus: OriginalMenuItem[]):
   return converted;
   return converted;
 }
 }
 
 
-// 使用示例
-// const originalData = {...}; // 你的原始数据
-// const convertedData = convertMenuData(originalData);
-// console.log(convertedData);
+/**
+ * 菜单转为树型结构
+ * @param data
+ * @returns
+ */
+export function buildMenuTree(data: any) {
+  // 创建根节点数组和映射表
+  const rootNodes: any = [];
+  const nodeMap: any = {};
+
+  // 首先将所有节点存入映射表
+  data.forEach((item: any) => {
+    nodeMap[item.menuId] = { ...item };
+  });
+
+  // 构建树形结构
+  data.forEach((item: any) => {
+    const node = nodeMap[item.menuId];
+
+    if (item.parentId === 0) {
+      // 根节点
+      rootNodes.push(node);
+    } else {
+      // 子节点,找到父节点并添加到父节点的children中
+      const parent = nodeMap[item.parentId];
+      if (parent) {
+        if (!parent.children) {
+          parent.children = [];
+        }
+        parent.children.push(node);
+      } else {
+        // 如果父节点不存在,也作为根节点
+        rootNodes.push(node);
+      }
+    }
+  });
+
+  // 删除空children字段
+  const removeEmptyChildren = (nodes: any) => {
+    nodes.forEach((node: any) => {
+      if (node.children && node.children.length === 0) {
+        delete node.children;
+      } else if (node.children) {
+        removeEmptyChildren(node.children);
+      }
+    });
+    return nodes;
+  };
+
+  // 对根节点和子节点按orderNum排序
+  const sortByOrderNum = (nodes: any) => {
+    return nodes
+      .sort((a: any, b: any) => a.orderNum - b.orderNum)
+      .map((node: any) => {
+        if (node.children) {
+          node.children = sortByOrderNum(node.children);
+        }
+        return node;
+      });
+  };
+
+  return removeEmptyChildren(sortByOrderNum(rootNodes));
+}

+ 2 - 1
src/views/home/modules/header-banner.vue

@@ -36,6 +36,7 @@ const statisticData = computed<StatisticData[]>(() => [
     value: '12'
     value: '12'
   }
   }
 ]);
 ]);
+console.log(authStore.userInfo);
 </script>
 </script>
 
 
 <template>
 <template>
@@ -48,7 +49,7 @@ const statisticData = computed<StatisticData[]>(() => [
           </div>
           </div>
           <div class="pl-12px">
           <div class="pl-12px">
             <h3 class="text-18px font-semibold">
             <h3 class="text-18px font-semibold">
-              {{ $t('page.home.greeting', { userName: authStore.userInfo.username }) }}
+              {{ $t('page.home.greeting', { username: authStore.userInfo.username }) }}
             </h3>
             </h3>
             <p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>
             <p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>
           </div>
           </div>

+ 1 - 1
src/views/manage/config/index.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts"></script>
 <script setup lang="ts"></script>
 
 
 <template>
 <template>
-  <div>123</div>
+  <LookForward></LookForward>
 </template>
 </template>
 
 
 <style scoped></style>
 <style scoped></style>

+ 9 - 0
src/views/manage/department/index.vue

@@ -0,0 +1,9 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div>
+    <LookForward></LookForward>
+  </div>
+</template>
+
+<style scoped></style>

+ 1 - 1
src/views/manage/log/index.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts"></script>
 <script setup lang="ts"></script>
 
 
 <template>
 <template>
-  <div>123</div>
+  <LookForward></LookForward>
 </template>
 </template>
 
 
 <style scoped></style>
 <style scoped></style>

+ 8 - 69
src/views/manage/menu/index.vue

@@ -6,6 +6,7 @@ import { yesOrNoRecord } from '@/constants/common';
 import { menuTypeRecord } from '@/constants/business';
 import { menuTypeRecord } from '@/constants/business';
 import { fetchAddMenu, fetchDelMenu, fetchEditMenu, fetchGetMenuList } from '@/service/api';
 import { fetchAddMenu, fetchDelMenu, fetchEditMenu, fetchGetMenuList } from '@/service/api';
 import { useAppStore } from '@/store/modules/app';
 import { useAppStore } from '@/store/modules/app';
+import { buildMenuTree } from '@/utils/zt';
 import { $t } from '@/locales';
 import { $t } from '@/locales';
 import SvgIcon from '@/components/custom/svg-icon.vue';
 import SvgIcon from '@/components/custom/svg-icon.vue';
 import { useForm } from '@/components/zt/Form/hooks/useForm';
 import { useForm } from '@/components/zt/Form/hooks/useForm';
@@ -20,12 +21,13 @@ const loading = ref(false);
 const showModal = ref(false);
 const showModal = ref(false);
 const formLoading = ref(false);
 const formLoading = ref(false);
 
 
-const [registerForm, { setFieldsValue, validate, getFieldsValue }] = useForm({
+const [registerForm, { setFieldsValue, validate, getFieldsValue, updateSchema }] = useForm({
   schemas: formSchems,
   schemas: formSchems,
   showSubmitButton: false,
   showSubmitButton: false,
   showAdvancedButton: false,
   showAdvancedButton: false,
   showResetButton: false,
   showResetButton: false,
   labelWidth: 120,
   labelWidth: 120,
+  showActionButtonGroup: false,
   layout: 'horizontal',
   layout: 'horizontal',
   gridProps: {
   gridProps: {
     cols: '1 xl:4 s:1 l:3',
     cols: '1 xl:4 s:1 l:3',
@@ -183,27 +185,23 @@ async function handleDelete(id: number) {
 function handleEdit(item: Api.SystemManage.Menu) {
 function handleEdit(item: Api.SystemManage.Menu) {
   showModal.value = true;
   showModal.value = true;
   operateType.value = 'edit';
   operateType.value = 'edit';
-  nextTick(() => {
-    setFieldsValue(item);
-    console.log(getFieldsValue());
+  nextTick(async () => {
+    await setFieldsValue(item);
+    updateSchema([{ field: 'type', componentProps: { disabled: true } }]);
   });
   });
 }
 }
 
 
 function handleAddChildMenu(item: any) {
 function handleAddChildMenu(item: any) {
   operateType.value = 'addChild';
   operateType.value = 'addChild';
   showModal.value = true;
   showModal.value = true;
-  console.log(item, 'asdasd');
-
   nextTick(() => {
   nextTick(() => {
-    setFieldsValue({ parentId: item.menuId });
-    console.log(getFieldsValue());
+    setFieldsValue({ parentId: item.menuId, url: item.url });
   });
   });
 }
 }
 
 
 async function getAllPages() {
 async function getAllPages() {
   const { data } = await fetchGetMenuList();
   const { data } = await fetchGetMenuList();
   const menuData = buildMenuTree(data);
   const menuData = buildMenuTree(data);
-  console.log(menuData, '数据');
   menusData.value = menuData;
   menusData.value = menuData;
 }
 }
 
 
@@ -213,67 +211,8 @@ function init() {
 
 
 // init
 // init
 init();
 init();
-function buildMenuTree(data: any) {
-  // 创建根节点数组和映射表
-  const rootNodes: any = [];
-  const nodeMap: any = {};
-
-  // 首先将所有节点存入映射表
-  data.forEach((item: any) => {
-    nodeMap[item.menuId] = { ...item };
-  });
-
-  // 构建树形结构
-  data.forEach((item: any) => {
-    const node = nodeMap[item.menuId];
-
-    if (item.parentId === 0) {
-      // 根节点
-      rootNodes.push(node);
-    } else {
-      // 子节点,找到父节点并添加到父节点的children中
-      const parent = nodeMap[item.parentId];
-      if (parent) {
-        if (!parent.children) {
-          parent.children = [];
-        }
-        parent.children.push(node);
-      } else {
-        // 如果父节点不存在,也作为根节点
-        rootNodes.push(node);
-      }
-    }
-  });
-
-  // 删除空children字段
-  const removeEmptyChildren = (nodes: any) => {
-    nodes.forEach((node: any) => {
-      if (node.children && node.children.length === 0) {
-        delete node.children;
-      } else if (node.children) {
-        removeEmptyChildren(node.children);
-      }
-    });
-    return nodes;
-  };
 
 
-  // 对根节点和子节点按orderNum排序
-  const sortByOrderNum = (nodes: any) => {
-    return nodes
-      .sort((a: any, b: any) => a.orderNum - b.orderNum)
-      .map((node: any) => {
-        if (node.children) {
-          node.children = sortByOrderNum(node.children);
-        }
-        return node;
-      });
-  };
-
-  return removeEmptyChildren(sortByOrderNum(rootNodes));
-}
 async function handleSubmit() {
 async function handleSubmit() {
-  console.log(getFieldsValue());
-
   await validate();
   await validate();
   formLoading.value = true;
   formLoading.value = true;
   const form = getFieldsValue();
   const form = getFieldsValue();
@@ -333,7 +272,7 @@ async function handleSubmit() {
               {{ operateType == 'add' ? '新增菜单' : operateType == 'addChild' ? '新增子菜单' : ' 编辑菜单' }}
               {{ operateType == 'add' ? '新增菜单' : operateType == 'addChild' ? '新增子菜单' : ' 编辑菜单' }}
             </div>
             </div>
           </template>
           </template>
-          <BasicForm @register="registerForm">
+          <BasicForm @register-form="registerForm">
             <template #parentId="{ model, field }">
             <template #parentId="{ model, field }">
               <NTreeSelect
               <NTreeSelect
                 v-model:value="model[field]"
                 v-model:value="model[field]"

+ 39 - 7
src/views/manage/menu/modules/shared.ts

@@ -1,4 +1,5 @@
 import { h } from 'vue';
 import { h } from 'vue';
+import { NInput } from 'naive-ui';
 import type { FormSchema } from '@/components/zt/Form/types/form';
 import type { FormSchema } from '@/components/zt/Form/types/form';
 import CustomIconSelect from '@/components/custom/custom-icon-select.vue';
 import CustomIconSelect from '@/components/custom/custom-icon-select.vue';
 import { icons } from './icons';
 import { icons } from './icons';
@@ -89,6 +90,7 @@ export const formSchems: FormSchema[] = [
     show: false,
     show: false,
     component: 'NInput'
     component: 'NInput'
   },
   },
+
   {
   {
     label: '菜单类型',
     label: '菜单类型',
     field: 'type',
     field: 'type',
@@ -112,12 +114,29 @@ export const formSchems: FormSchema[] = [
       ]
       ]
     }
     }
   },
   },
+  {
+    label: '上级菜单',
+    field: 'parentId',
+    slot: 'parentId',
+    component: 'NTreeSelect',
+    defaultValue: 0
+  },
   {
   {
     label: '菜单名称',
     label: '菜单名称',
     field: 'name',
     field: 'name',
     component: 'NInput',
     component: 'NInput',
     required: true
     required: true
   },
   },
+  {
+    label: '前端组件',
+    field: 'component',
+    component: 'NInput',
+    required: true,
+    componentProps: {
+      disabled: true
+    }
+  },
+
   {
   {
     label: '路由名称',
     label: '路由名称',
     field: 'url',
     field: 'url',
@@ -125,15 +144,28 @@ export const formSchems: FormSchema[] = [
     required: true,
     required: true,
     ifShow({ model }) {
     ifShow({ model }) {
       return model.type != 2;
       return model.type != 2;
+    },
+    render({ model, field }) {
+      return h(NInput, {
+        icons,
+        value: model[field],
+        'onUpdate:value': value => {
+          model[field] = value;
+        },
+        onInput: value => {
+          if (model.type == 1) {
+            const cop = model.parentId
+              ? `view.${value.replace(/^\//, '').replace(/\//g, '_')}`
+              : `layout.base$view.${value.replace(/^\//, '')}`;
+            model.component = cop;
+          } else {
+            model.component = 'layout.base';
+          }
+        }
+      });
     }
     }
   },
   },
-  {
-    label: '上级菜单',
-    field: 'parentId',
-    slot: 'parentId',
-    component: 'NTreeSelect',
-    defaultValue: 0
-  },
+
   {
   {
     label: '排序',
     label: '排序',
     field: 'orderNum',
     field: 'orderNum',

+ 20 - 42
src/views/manage/role/index.vue

@@ -1,13 +1,11 @@
 <script setup lang="tsx">
 <script setup lang="tsx">
-import { reactive } from 'vue';
-import { NButton, NPopconfirm, NTag } from 'naive-ui';
-import { enableStatusRecord } from '@/constants/business';
-import { fetchGetRoleList } from '@/service/api';
+import { reactive, ref } from 'vue';
+import { NButton, NPopconfirm } from 'naive-ui';
+import { fetchDelRoleMenu, fetchGetRoleList } from '@/service/api';
 import { useAppStore } from '@/store/modules/app';
 import { useAppStore } from '@/store/modules/app';
 import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
 import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
 import { $t } from '@/locales';
 import { $t } from '@/locales';
 import RoleOperateDrawer from './modules/role-operate-drawer.vue';
 import RoleOperateDrawer from './modules/role-operate-drawer.vue';
-import RoleSearch from './modules/role-search.vue';
 
 
 const appStore = useAppStore();
 const appStore = useAppStore();
 
 
@@ -18,6 +16,7 @@ const searchParams: Api.SystemManage.RoleSearchParams = reactive({
   roleCode: null,
   roleCode: null,
   status: null
   status: null
 });
 });
+const delLoading = ref(false);
 
 
 const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagination } = useNaivePaginatedTable({
 const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagination } = useNaivePaginatedTable({
   api: () => fetchGetRoleList(searchParams),
   api: () => fetchGetRoleList(searchParams),
@@ -46,51 +45,26 @@ const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagi
       minWidth: 120
       minWidth: 120
     },
     },
     {
     {
-      key: 'roleCode',
-      title: $t('page.manage.role.roleCode'),
-      align: 'center',
-      minWidth: 120
-    },
-    {
-      key: 'roleDesc',
+      key: 'remark',
       title: $t('page.manage.role.roleDesc'),
       title: $t('page.manage.role.roleDesc'),
       minWidth: 120
       minWidth: 120
     },
     },
-    {
-      key: 'status',
-      title: $t('page.manage.role.roleStatus'),
-      align: 'center',
-      width: 100,
-      render: row => {
-        if (row.status === null) {
-          return null;
-        }
-
-        const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
-          1: 'success',
-          2: 'warning'
-        };
-
-        const label = $t(enableStatusRecord[row.status]);
-
-        return <NTag type={tagMap[row.status]}>{label}</NTag>;
-      }
-    },
     {
     {
       key: 'operate',
       key: 'operate',
       title: $t('common.operate'),
       title: $t('common.operate'),
       align: 'center',
       align: 'center',
       width: 130,
       width: 130,
+      fixed: 'right',
       render: row => (
       render: row => (
         <div class="flex-center gap-8px">
         <div class="flex-center gap-8px">
-          <NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
+          <NButton type="primary" ghost size="small" onClick={() => edit(row)}>
             {$t('common.edit')}
             {$t('common.edit')}
           </NButton>
           </NButton>
-          <NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
+          <NPopconfirm onPositiveClick={() => handleDelete(row.roleId)}>
             {{
             {{
               default: () => $t('common.confirmDelete'),
               default: () => $t('common.confirmDelete'),
               trigger: () => (
               trigger: () => (
-                <NButton type="error" ghost size="small">
+                <NButton type="error" ghost size="small" loading={delLoading.value}>
                   {$t('common.delete')}
                   {$t('common.delete')}
                 </NButton>
                 </NButton>
               )
               )
@@ -112,7 +86,7 @@ const {
   onBatchDeleted,
   onBatchDeleted,
   onDeleted
   onDeleted
   // closeDrawer
   // closeDrawer
-} = useTableOperate(data, 'id', getData);
+} = useTableOperate(data, 'roleId', getData);
 
 
 async function handleBatchDelete() {
 async function handleBatchDelete() {
   // request
   // request
@@ -121,27 +95,31 @@ async function handleBatchDelete() {
   onBatchDeleted();
   onBatchDeleted();
 }
 }
 
 
-function handleDelete(id: number) {
+async function handleDelete(id: number) {
   // request
   // request
+  if (delLoading.value) return;
+  delLoading.value = true;
   console.log(id);
   console.log(id);
-
+  await fetchDelRoleMenu(id);
   onDeleted();
   onDeleted();
+  delLoading.value = false;
 }
 }
 
 
-function edit(id: number) {
-  handleEdit(id);
+function edit(item: any) {
+  handleEdit(item.roleId);
 }
 }
 </script>
 </script>
 
 
 <template>
 <template>
   <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
   <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
-    <RoleSearch v-model:model="searchParams" @search="getDataByPage" />
+    <!-- <RoleSearch v-model:model="searchParams" @search="getDataByPage" /> -->
     <NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
     <NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
       <template #header-extra>
       <template #header-extra>
         <TableHeaderOperation
         <TableHeaderOperation
           v-model:columns="columnChecks"
           v-model:columns="columnChecks"
           :disabled-delete="checkedRowKeys.length === 0"
           :disabled-delete="checkedRowKeys.length === 0"
           :loading="loading"
           :loading="loading"
+          is-add
           @add="handleAdd"
           @add="handleAdd"
           @delete="handleBatchDelete"
           @delete="handleBatchDelete"
           @refresh="getData"
           @refresh="getData"
@@ -156,7 +134,7 @@ function edit(id: number) {
         :scroll-x="702"
         :scroll-x="702"
         :loading="loading"
         :loading="loading"
         remote
         remote
-        :row-key="row => row.id"
+        :row-key="row => row.roleId"
         :pagination="mobilePagination"
         :pagination="mobilePagination"
         class="sm:h-full"
         class="sm:h-full"
       />
       />

+ 17 - 40
src/views/manage/role/modules/menu-auth-modal.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { computed, shallowRef, watch } from 'vue';
 import { computed, shallowRef, watch } from 'vue';
-import { fetchGetAllPages, fetchGetMenuTree } from '@/service/api';
+import { fetchGetMenuList } from '@/service/api';
+import { buildMenuTree } from '@/utils/zt';
 import { $t } from '@/locales';
 import { $t } from '@/locales';
 
 
 defineOptions({
 defineOptions({
@@ -28,52 +29,26 @@ const home = shallowRef('');
 
 
 async function getHome() {
 async function getHome() {
   console.log(props.roleId);
   console.log(props.roleId);
-
   home.value = 'home';
   home.value = 'home';
 }
 }
 
 
-async function updateHome(val: string) {
-  // request
-
-  home.value = val;
-}
-
-const pages = shallowRef<string[]>([]);
-
-async function getPages() {
-  const { error, data } = await fetchGetAllPages();
-
-  if (!error) {
-    pages.value = data;
-  }
-}
-
-const pageSelectOptions = computed(() => {
-  const opts: CommonType.Option[] = pages.value.map(page => ({
-    label: page,
-    value: page
-  }));
-
-  return opts;
-});
-
 const tree = shallowRef<Api.SystemManage.MenuTree[]>([]);
 const tree = shallowRef<Api.SystemManage.MenuTree[]>([]);
 
 
 async function getTree() {
 async function getTree() {
-  const { error, data } = await fetchGetMenuTree();
-
-  if (!error) {
-    tree.value = data;
+  const { data } = await fetchGetMenuList();
+  const menuData = buildMenuTree(data);
+  if (menuData) {
+    tree.value = menuData;
   }
   }
 }
 }
 
 
 const checks = shallowRef<number[]>([]);
 const checks = shallowRef<number[]>([]);
 
 
-async function getChecks() {
-  console.log(props.roleId);
-  // request
-  checks.value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
-}
+// async function getChecks() {
+//   console.log(props.roleId);
+//   // request
+//   checks.value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
+// }
 
 
 function handleSubmit() {
 function handleSubmit() {
   console.log(checks.value, props.roleId);
   console.log(checks.value, props.roleId);
@@ -86,9 +61,7 @@ function handleSubmit() {
 
 
 function init() {
 function init() {
   getHome();
   getHome();
-  getPages();
   getTree();
   getTree();
-  getChecks();
 }
 }
 
 
 watch(visible, val => {
 watch(visible, val => {
@@ -100,19 +73,23 @@ watch(visible, val => {
 
 
 <template>
 <template>
   <NModal v-model:show="visible" :title="title" preset="card" class="w-480px">
   <NModal v-model:show="visible" :title="title" preset="card" class="w-480px">
-    <div class="flex-y-center gap-16px pb-12px">
+    <!--
+ <div class="flex-y-center gap-16px pb-12px">
       <div>{{ $t('page.manage.menu.home') }}</div>
       <div>{{ $t('page.manage.menu.home') }}</div>
       <NSelect :value="home" :options="pageSelectOptions" size="small" class="w-160px" @update:value="updateHome" />
       <NSelect :value="home" :options="pageSelectOptions" size="small" class="w-160px" @update:value="updateHome" />
     </div>
     </div>
+-->
     <NTree
     <NTree
       v-model:checked-keys="checks"
       v-model:checked-keys="checks"
       :data="tree"
       :data="tree"
-      key-field="id"
       checkable
       checkable
       expand-on-click
       expand-on-click
       virtual-scroll
       virtual-scroll
       block-line
       block-line
       class="h-280px"
       class="h-280px"
+      label-field="name"
+      key-field="menuId"
+      cascade
     />
     />
     <template #footer>
     <template #footer>
       <NSpace justify="end">
       <NSpace justify="end">

+ 48 - 30
src/views/manage/role/modules/role-operate-drawer.vue

@@ -1,11 +1,9 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { computed, ref, watch } from 'vue';
 import { computed, ref, watch } from 'vue';
-import { useBoolean } from '@sa/hooks';
-import { enableStatusOptions } from '@/constants/business';
+import { fetchAddRoleMenu, fetchEditRoleMenu, fetchGetMenuList, fetchGetRoleMenuList } from '@/service/api';
 import { useFormRules, useNaiveForm } from '@/hooks/common/form';
 import { useFormRules, useNaiveForm } from '@/hooks/common/form';
+import { buildMenuTree } from '@/utils/zt';
 import { $t } from '@/locales';
 import { $t } from '@/locales';
-import MenuAuthModal from './menu-auth-modal.vue';
-import ButtonAuthModal from './button-auth-modal.vue';
 
 
 defineOptions({
 defineOptions({
   name: 'RoleOperateDrawer'
   name: 'RoleOperateDrawer'
@@ -32,8 +30,6 @@ const visible = defineModel<boolean>('visible', {
 
 
 const { formRef, validate, restoreValidation } = useNaiveForm();
 const { formRef, validate, restoreValidation } = useNaiveForm();
 const { defaultRequiredRule } = useFormRules();
 const { defaultRequiredRule } = useFormRules();
-const { bool: menuAuthVisible, setTrue: openMenuAuthModal } = useBoolean();
-const { bool: buttonAuthVisible, setTrue: openButtonAuthModal } = useBoolean();
 
 
 const title = computed(() => {
 const title = computed(() => {
   const titles: Record<NaiveUI.TableOperateType, string> = {
   const titles: Record<NaiveUI.TableOperateType, string> = {
@@ -42,37 +38,38 @@ const title = computed(() => {
   };
   };
   return titles[props.operateType];
   return titles[props.operateType];
 });
 });
-
-type Model = Pick<Api.SystemManage.Role, 'roleName' | 'roleCode' | 'roleDesc' | 'status'>;
+const tree = ref([]);
+const checks = ref<number[]>([]);
+type Model = Pick<Api.SystemManage.RoleMenuList, 'roleName' | 'remark'>;
 
 
 const model = ref(createDefaultModel());
 const model = ref(createDefaultModel());
 
 
 function createDefaultModel(): Model {
 function createDefaultModel(): Model {
   return {
   return {
     roleName: '',
     roleName: '',
-    roleCode: '',
-    roleDesc: '',
-    status: null
+    remark: ''
   };
   };
 }
 }
 
 
-type RuleKey = Exclude<keyof Model, 'roleDesc'>;
+type RuleKey = Exclude<keyof Model, 'remark'>;
 
 
 const rules: Record<RuleKey, App.Global.FormRule> = {
 const rules: Record<RuleKey, App.Global.FormRule> = {
-  roleName: defaultRequiredRule,
-  roleCode: defaultRequiredRule,
-  status: defaultRequiredRule
+  roleName: defaultRequiredRule
 };
 };
 
 
-const roleId = computed(() => props.rowData?.id || -1);
+const roleId = computed(() => props.rowData?.roleId || -1);
 
 
-const isEdit = computed(() => props.operateType === 'edit');
+async function handleEdit() {
+  const { data } = await fetchGetRoleMenuList(roleId.value);
+  checks.value = data?.menuIdList as number[];
+}
 
 
 function handleInitModel() {
 function handleInitModel() {
   model.value = createDefaultModel();
   model.value = createDefaultModel();
 
 
   if (props.operateType === 'edit' && props.rowData) {
   if (props.operateType === 'edit' && props.rowData) {
     Object.assign(model.value, props.rowData);
     Object.assign(model.value, props.rowData);
+    handleEdit();
   }
   }
 }
 }
 
 
@@ -83,11 +80,24 @@ function closeDrawer() {
 async function handleSubmit() {
 async function handleSubmit() {
   await validate();
   await validate();
   // request
   // request
+  console.log(checks.value);
+  if (props.operateType == 'add') {
+    await fetchAddRoleMenu({ ...model.value, menuIdList: checks.value });
+  } else {
+    await fetchEditRoleMenu({ ...model.value, menuIdList: checks.value, roleId: roleId.value });
+  }
   window.$message?.success($t('common.updateSuccess'));
   window.$message?.success($t('common.updateSuccess'));
   closeDrawer();
   closeDrawer();
   emit('submitted');
   emit('submitted');
 }
 }
-
+async function getTree() {
+  const { data } = await fetchGetMenuList();
+  const menuData = buildMenuTree(data);
+  if (menuData) {
+    tree.value = menuData;
+  }
+}
+getTree();
 watch(visible, () => {
 watch(visible, () => {
   if (visible.value) {
   if (visible.value) {
     handleInitModel();
     handleInitModel();
@@ -103,24 +113,32 @@ watch(visible, () => {
         <NFormItem :label="$t('page.manage.role.roleName')" path="roleName">
         <NFormItem :label="$t('page.manage.role.roleName')" path="roleName">
           <NInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
           <NInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
         </NFormItem>
         </NFormItem>
-        <NFormItem :label="$t('page.manage.role.roleCode')" path="roleCode">
-          <NInput v-model:value="model.roleCode" :placeholder="$t('page.manage.role.form.roleCode')" />
-        </NFormItem>
-        <NFormItem :label="$t('page.manage.role.roleStatus')" path="status">
-          <NRadioGroup v-model:value="model.status">
-            <NRadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
-          </NRadioGroup>
-        </NFormItem>
-        <NFormItem :label="$t('page.manage.role.roleDesc')" path="roleDesc">
-          <NInput v-model:value="model.roleDesc" :placeholder="$t('page.manage.role.form.roleDesc')" />
+        <NFormItem :label="$t('page.manage.role.roleDesc')" path="remark">
+          <NInput v-model:value="model.remark" :placeholder="$t('page.manage.role.form.roleDesc')" />
         </NFormItem>
         </NFormItem>
       </NForm>
       </NForm>
-      <NSpace v-if="isEdit">
+      <!--
+ <NSpace>
         <NButton @click="openMenuAuthModal">{{ $t('page.manage.role.menuAuth') }}</NButton>
         <NButton @click="openMenuAuthModal">{{ $t('page.manage.role.menuAuth') }}</NButton>
         <MenuAuthModal v-model:visible="menuAuthVisible" :role-id="roleId" />
         <MenuAuthModal v-model:visible="menuAuthVisible" :role-id="roleId" />
-        <NButton @click="openButtonAuthModal">{{ $t('page.manage.role.buttonAuth') }}</NButton>
+
+ <NButton @click="openButtonAuthModal">{{ $t('page.manage.role.buttonAuth') }}</NButton>
         <ButtonAuthModal v-model:visible="buttonAuthVisible" :role-id="roleId" />
         <ButtonAuthModal v-model:visible="buttonAuthVisible" :role-id="roleId" />
+
       </NSpace>
       </NSpace>
+-->
+      <NTree
+        v-model:checked-keys="checks"
+        :data="tree"
+        checkable
+        expand-on-click
+        virtual-scroll
+        block-line
+        class="h-280px"
+        label-field="name"
+        key-field="menuId"
+        cascade
+      />
       <template #footer>
       <template #footer>
         <NSpace :size="16">
         <NSpace :size="16">
           <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
           <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>

+ 1 - 13
src/views/manage/role/modules/role-search.vue

@@ -1,6 +1,4 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import { enableStatusOptions } from '@/constants/business';
-import { translateOptions } from '@/utils/common';
 import { $t } from '@/locales';
 import { $t } from '@/locales';
 
 
 defineOptions({
 defineOptions({
@@ -23,6 +21,7 @@ function resetModel() {
     roleCode: null,
     roleCode: null,
     status: null
     status: null
   };
   };
+  console.log(model.value, 'asdasda');
 }
 }
 
 
 function search() {
 function search() {
@@ -39,17 +38,6 @@ function search() {
             <NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.role.roleName')" path="roleName" class="pr-24px">
             <NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.role.roleName')" path="roleName" class="pr-24px">
               <NInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
               <NInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
             </NFormItemGi>
             </NFormItemGi>
-            <NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.role.roleCode')" path="roleCode" class="pr-24px">
-              <NInput v-model:value="model.roleCode" :placeholder="$t('page.manage.role.form.roleCode')" />
-            </NFormItemGi>
-            <NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.role.roleStatus')" path="status" class="pr-24px">
-              <NSelect
-                v-model:value="model.status"
-                :placeholder="$t('page.manage.role.form.roleStatus')"
-                :options="translateOptions(enableStatusOptions)"
-                clearable
-              />
-            </NFormItemGi>
             <NFormItemGi span="24 s:12 m:6">
             <NFormItemGi span="24 s:12 m:6">
               <NSpace class="w-full" justify="end">
               <NSpace class="w-full" justify="end">
                 <NButton @click="resetModel">
                 <NButton @click="resetModel">

+ 1 - 1
src/views/manage/schedule/index.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts"></script>
 <script setup lang="ts"></script>
 
 
 <template>
 <template>
-  <div>123</div>
+  <LookForward></LookForward>
 </template>
 </template>
 
 
 <style scoped></style>
 <style scoped></style>

+ 240 - 178
src/views/manage/user/index.vue

@@ -1,203 +1,265 @@
 <script setup lang="tsx">
 <script setup lang="tsx">
-import { reactive } from 'vue';
 import { NButton, NPopconfirm, NTag } from 'naive-ui';
 import { NButton, NPopconfirm, NTag } from 'naive-ui';
-import { enableStatusRecord, userGenderRecord } from '@/constants/business';
-import { fetchGetUserList } from '@/service/api';
-import { useAppStore } from '@/store/modules/app';
-import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
+import type { InternalRowData } from 'naive-ui/es/data-table/src/interface';
+import { enableStatusRecord } from '@/constants/business';
+import {
+  fetchAddUser,
+  fetchDeleteUser,
+  fetchDetaileUser,
+  fetchEditUser,
+  fetchGetRoleAllList,
+  fetchGetUserList
+} from '@/service/api';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
 import { $t } from '@/locales';
 import { $t } from '@/locales';
-import UserOperateDrawer from './modules/user-operate-drawer.vue';
-import UserSearch from './modules/user-search.vue';
+import { useModalFrom } from '@/components/zt/ModalForm/hooks/useModalForm';
 
 
-const appStore = useAppStore();
-
-const searchParams: Api.SystemManage.UserSearchParams = reactive({
-  current: 1,
-  size: 10,
-  status: null,
-  userName: null,
-  userGender: null,
-  nickName: null,
-  userPhone: null,
-  userEmail: null
-});
-
-const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination } = useNaivePaginatedTable({
-  api: () => fetchGetUserList(searchParams),
-  transform: response => defaultTransform(response),
-  onPaginationParamsChange: params => {
-    searchParams.current = params.page;
-    searchParams.size = params.pageSize;
+const columns: NaiveUI.TableColumn<InternalRowData>[] = [
+  {
+    type: 'selection',
+    align: 'center',
+    width: 48
+  },
+  {
+    key: 'index',
+    title: $t('common.index'),
+    align: 'center',
+    width: 64,
+    render: (_, index) => index + 1
+  },
+  {
+    key: 'username',
+    title: $t('page.manage.user.userName'),
+    align: 'center',
+    minWidth: 100
   },
   },
-  columns: () => [
-    {
-      type: 'selection',
-      align: 'center',
-      width: 48
-    },
-    {
-      key: 'index',
-      title: $t('common.index'),
-      align: 'center',
-      width: 64,
-      render: (_, index) => index + 1
-    },
-    {
-      key: 'userName',
-      title: $t('page.manage.user.userName'),
-      align: 'center',
-      minWidth: 100
-    },
-    {
-      key: 'userGender',
-      title: $t('page.manage.user.userGender'),
-      align: 'center',
-      width: 100,
-      render: row => {
-        if (row.userGender === null) {
-          return null;
-        }
-
-        const tagMap: Record<Api.SystemManage.UserGender, NaiveUI.ThemeColor> = {
-          1: 'primary',
-          2: 'error'
-        };
-
-        const label = $t(userGenderRecord[row.userGender]);
 
 
-        return <NTag type={tagMap[row.userGender]}>{label}</NTag>;
+  {
+    key: 'mobile',
+    title: $t('page.manage.user.userPhone'),
+    align: 'center',
+    width: 120
+  },
+  {
+    key: 'email',
+    title: $t('page.manage.user.userEmail'),
+    align: 'center',
+    minWidth: 200
+  },
+  {
+    key: 'status',
+    title: $t('page.manage.user.userStatus'),
+    align: 'center',
+    width: 100,
+    render: row => {
+      if (row.status === null) {
+        return null;
       }
       }
-    },
-    {
-      key: 'nickName',
-      title: $t('page.manage.user.nickName'),
-      align: 'center',
-      minWidth: 100
-    },
-    {
-      key: 'userPhone',
-      title: $t('page.manage.user.userPhone'),
-      align: 'center',
-      width: 120
-    },
-    {
-      key: 'userEmail',
-      title: $t('page.manage.user.userEmail'),
-      align: 'center',
-      minWidth: 200
-    },
-    {
-      key: 'status',
-      title: $t('page.manage.user.userStatus'),
-      align: 'center',
-      width: 100,
-      render: row => {
-        if (row.status === null) {
-          return null;
-        }
-
-        const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
-          1: 'success',
-          2: 'warning'
-        };
 
 
-        const label = $t(enableStatusRecord[row.status]);
+      const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
+        1: 'success',
+        0: 'warning'
+      };
 
 
-        return <NTag type={tagMap[row.status]}>{label}</NTag>;
-      }
-    },
+      const status = row.status as Api.Common.EnableStatus;
+      const label = $t(enableStatusRecord[status]);
+      return <NTag type={tagMap[status]}>{label}</NTag>;
+    }
+  }
+];
+const [registerTable, { refresh, setTableLoading }] = useTable({
+  schemas: [
     {
     {
-      key: 'operate',
-      title: $t('common.operate'),
-      align: 'center',
-      width: 130,
-      render: row => (
-        <div class="flex-center gap-8px">
-          <NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
-            {$t('common.edit')}
-          </NButton>
-          <NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
-            {{
-              default: () => $t('common.confirmDelete'),
-              trigger: () => (
-                <NButton type="error" ghost size="small">
-                  {$t('common.delete')}
-                </NButton>
-              )
-            }}
-          </NPopconfirm>
-        </div>
-      )
+      field: 'username',
+      label: '用户名',
+      component: 'NInput'
     }
     }
-  ]
+  ],
+  inline: false,
+  size: 'small',
+  labelPlacement: 'left',
+  isFull: false
 });
 });
 
 
-const {
-  drawerVisible,
-  operateType,
-  editingData,
-  handleAdd,
-  handleEdit,
-  checkedRowKeys,
-  onBatchDeleted,
-  onDeleted
-  // closeDrawer
-} = useTableOperate(data, 'id', getData);
-
-async function handleBatchDelete() {
-  // request
-  console.log(checkedRowKeys.value);
-
-  onBatchDeleted();
+async function handleDelete(row: Recordable) {
+  setTableLoading(true);
+  await fetchDeleteUser(row.userId);
+  refresh();
 }
 }
+const [registerModalForm, { openModal, closeModal, getFieldsValue, setFieldsValue, updateSchema }] = useModalFrom({
+  modalConfig: {
+    title: '用户 ',
+    width: 800
+  },
+  formConfig: {
+    schemas: [
+      {
+        field: 'userId',
+        label: '',
+        component: 'NInput',
+        show: false
+      },
+      {
+        field: 'username',
+        label: '用户名',
+        component: 'NInput',
+        required: true,
+        rules: [
+          {
+            required: true,
+            message: '请输入用户名',
+            trigger: ['blur', 'input'],
+            validator: (rule, value) => {
+              return new Promise<void>((resolve, reject) => {
+                if (value.length < 2) {
+                  console.log(rule);
 
 
-function handleDelete(id: number) {
-  // request
-  console.log(id);
-
-  onDeleted();
+                  reject(new Error('用户名不能低于2位'));
+                }
+                if (value.length > 20) {
+                  reject(new Error('用户名不能高于20位'));
+                }
+                resolve();
+              });
+            }
+          }
+        ]
+      },
+      {
+        field: 'password',
+        label: '密码',
+        component: 'NInput',
+        required: true,
+        componentProps: {
+          type: 'password',
+          showPasswordOn: 'click'
+        }
+      },
+      {
+        field: 'comfirmPassword',
+        label: '确认密码',
+        component: 'NInput',
+        required: true,
+        componentProps: {
+          type: 'password',
+          showPasswordOn: 'click'
+        }
+      },
+      {
+        field: 'email',
+        label: '邮箱',
+        component: 'NInput',
+        required: true,
+        rules: [
+          {
+            pattern: /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/,
+            type: 'email',
+            message: '请输入正确的邮箱地址',
+            trigger: ['blur', 'input']
+          }
+        ]
+      },
+      {
+        field: 'mobile',
+        label: '手机号',
+        component: 'NInput',
+        required: true,
+        componentProps: {
+          maxlength: 11
+        },
+        rules: [
+          {
+            pattern: /^1[3456789]\d{9}$/,
+            message: '请输入正确的手机号',
+            trigger: ['blur', 'input']
+          }
+        ]
+      },
+      {
+        field: 'roleIdList',
+        label: '角色',
+        component: 'ApiSelect',
+        required: true,
+        componentProps: {
+          api: () => fetchGetRoleAllList(),
+          labelFeild: 'roleName',
+          valueFeild: 'roleId',
+          multiple: true
+        }
+      },
+      {
+        field: 'status',
+        label: '状态',
+        component: 'NRadioGroup',
+        required: true,
+        componentProps: {
+          options: [
+            {
+              label: '启用',
+              value: 1
+            },
+            {
+              label: '禁用',
+              value: 0
+            }
+          ]
+        },
+        defaultValue: 1
+      }
+    ],
+    labelWidth: 120,
+    gridProps: {
+      cols: '1'
+    }
+  }
+});
+async function handleSubmit() {
+  const form = await getFieldsValue();
+  if (form.userId) {
+    await fetchEditUser(form as Api.SystemManage.UserModel);
+  } else {
+    await fetchAddUser(form as Api.SystemManage.UserModel);
+  }
+  closeModal();
+  refresh();
 }
 }
 
 
-function edit(id: number) {
-  handleEdit(id);
+async function edit(row: Recordable) {
+  const res = await fetchDetaileUser(row.userId);
+  openModal(row);
+  setFieldsValue({ ...res.data, userId: row.userId });
+  updateSchema([
+    { field: 'password', required: false },
+    { field: 'comfirmPassword', required: false }
+  ]);
 }
 }
 </script>
 </script>
 
 
 <template>
 <template>
-  <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
-    <UserSearch v-model:model="searchParams" @search="getDataByPage" />
-    <NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
-      <template #header-extra>
-        <TableHeaderOperation
-          v-model:columns="columnChecks"
-          :disabled-delete="checkedRowKeys.length === 0"
-          :loading="loading"
-          @add="handleAdd"
-          @delete="handleBatchDelete"
-          @refresh="getData"
-        />
+  <LayoutTable>
+    <ZTable
+      :table-config="{
+        api: fetchGetUserList,
+        columns: columns,
+        keyField: 'userId',
+        title: '用户列表',
+        showAddButton: true
+      }"
+      @register="registerTable"
+      @add="openModal"
+    >
+      <template #op="{ row }">
+        <NButton v-if="row.userId != 1" size="small" ghost type="primary" @click="edit(row)">编辑</NButton>
+        <NPopconfirm v-if="row.userId != 1" @positive-click="handleDelete(row)">
+          <template #trigger>
+            <NButton size="small" type="error" ghost>删除</NButton>
+          </template>
+          确定删除吗?
+        </NPopconfirm>
       </template>
       </template>
-      <NDataTable
-        v-model:checked-row-keys="checkedRowKeys"
-        :columns="columns"
-        :data="data"
-        size="small"
-        :flex-height="!appStore.isMobile"
-        :scroll-x="962"
-        :loading="loading"
-        remote
-        :row-key="row => row.id"
-        :pagination="mobilePagination"
-        class="sm:h-full"
-      />
-      <UserOperateDrawer
-        v-model:visible="drawerVisible"
-        :operate-type="operateType"
-        :row-data="editingData"
-        @submitted="getDataByPage"
-      />
-    </NCard>
-  </div>
+    </ZTable>
+    <BasicModelForm @register-modal-form="registerModalForm" @submit-form="handleSubmit"></BasicModelForm>
+  </LayoutTable>
 </template>
 </template>
 
 
 <style scoped></style>
 <style scoped></style>

+ 1 - 1
src/views/plugin/excel/index.vue

@@ -101,7 +101,7 @@ const { columns, data, loading } = useNaiveTable({
 
 
         const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
         const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
           1: 'success',
           1: 'success',
-          2: 'warning'
+          0: 'warning'
         };
         };
 
 
         const label = $t(enableStatusRecord[row.status]);
         const label = $t(enableStatusRecord[row.status]);

+ 126 - 2
src/views/user-center/index.vue

@@ -1,7 +1,131 @@
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { onMounted } from 'vue';
+import { fetchUserPassWord } from '@/service/api';
+import { useAuthStore } from '@/store/modules/auth';
+import { useForm } from '@/components/zt/Form/hooks/useForm';
+import { useModalFrom } from '@/components/zt/ModalForm/hooks/useModalForm';
+const appAuthStore = useAuthStore();
+
+const [registerForm, { setFieldsValue }] = useForm({
+  schemas: [
+    {
+      field: 'username',
+      component: 'NInput',
+      label: '用户名',
+      giProps: {
+        span: '6 xl:4'
+      },
+      componentProps: {
+        readonly: true
+      }
+    },
+    {
+      field: 'mobile',
+      component: 'NInput',
+      label: '手机号',
+      giProps: {
+        span: '6 xl:4'
+      },
+      componentProps: {
+        readonly: true
+      }
+    },
+    {
+      field: 'email',
+      component: 'NInput',
+      label: '邮箱',
+      giProps: {
+        span: '6 xl:4'
+      },
+      componentProps: {
+        readonly: true
+      }
+    }
+  ],
+  showActionButtonGroup: false,
+  layout: 'horizontal',
+  gridProps: {
+    cols: 6,
+    xGap: 24
+  }
+});
+onMounted(() => {
+  setFieldsValue(appAuthStore.userInfo);
+});
+
+const [registerModalForm, { openModal, getFieldsValue, closeModal, setSubLoading }] = useModalFrom({
+  modalConfig: {
+    title: '修改密码',
+    width: 800,
+    isShowHeaderText: false,
+    height: 300
+  },
+  formConfig: {
+    labelWidth: 120,
+    schemas: [
+      {
+        field: 'password',
+        label: '旧密码',
+        component: 'NInput',
+        required: true,
+        componentProps: {
+          type: 'password',
+          showPasswordOn: 'click'
+        }
+      },
+      {
+        field: 'newPassword',
+        label: '新密码',
+        component: 'NInput',
+        required: true,
+        componentProps: {
+          type: 'password',
+          showPasswordOn: 'click'
+        }
+      },
+      {
+        field: 'confirmPassword',
+        label: '确认密码',
+        component: 'NInput',
+        required: true,
+        componentProps: {
+          type: 'password',
+          showPasswordOn: 'click'
+        }
+      }
+    ]
+  }
+});
+async function handleSubmit() {
+  const form = await getFieldsValue();
+  const obj = {
+    newPassword: form.newPassword,
+    password: form.password
+  };
+  const { error } = await fetchUserPassWord(obj);
+
+  if (!error) {
+    closeModal();
+
+    appAuthStore.resetStore();
+  } else {
+    setSubLoading(false);
+  }
+}
+</script>
 
 
 <template>
 <template>
-  <LookForward />
+  <LayoutTable>
+    <NCard title="个人中心" :bordered="false" size="small" class="card-wrapper">
+      <BasicForm @register-form="registerForm"></BasicForm>
+      <template #footer>
+        <NSpace justify="end">
+          <NButton type="primary" @click="openModal">修改密码</NButton>
+        </NSpace>
+      </template>
+    </NCard>
+    <BasicModelForm @register-modal-form="registerModalForm" @submit-form="handleSubmit"></BasicModelForm>
+  </LayoutTable>
 </template>
 </template>
 
 
 <style scoped></style>
 <style scoped></style>