Kaynağa Gözat

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 4 gün önce
ebeveyn
işleme
1b0502d14c
56 değiştirilmiş dosya ile 1706 ekleme ve 526 silme
  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
-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
 
 

+ 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 { useFormEvents } from './hooks/useFormEvents';
 import { useFormValues } from './hooks/useFormValues';
-import { basicProps } from './props';
+import { basicFormProps } from './props';
 import type { FormActionType, FormProps, FormSchema } from './types/form';
 import { componentMap } from './types/form';
 
@@ -17,9 +17,9 @@ export default defineComponent({
   name: 'BasicForm',
   components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
   props: {
-    ...basicProps
+    ...basicFormProps
   },
-  emits: ['reset', 'submit', 'register'],
+  emits: ['reset', 'submit', 'registerForm'],
   setup(props, { emit, attrs }) {
     const defaultFormModel = ref<Recordable>({});
     const formModel = reactive<Recordable>({});
@@ -114,6 +114,7 @@ export default defineComponent({
           schema.defaultValue = defaultValue;
         }
       }
+
       return schemas as FormSchema[];
     });
     const getRule = (schema: FormSchema): FormItemRule | undefined => {
@@ -158,22 +159,24 @@ export default defineComponent({
       getSchema,
       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() {
       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);
     }
 
@@ -197,8 +200,9 @@ export default defineComponent({
       resetFields,
       validate,
       clearValidate,
-      setProps,
-      submit: handleSubmit
+      setFormProps,
+      submit: handleSubmit,
+      updateSchema
     };
 
     watch(
@@ -207,7 +211,7 @@ export default defineComponent({
         if (unref(isUpdateDefaultRef)) {
           return;
         }
-        if (schema?.length) {
+        if (schema.length) {
           initDefault();
           isUpdateDefaultRef.value = true;
         }
@@ -216,7 +220,7 @@ export default defineComponent({
 
     onMounted(() => {
       initDefault();
-      emit('register', formActionType);
+      emit('registerForm', formActionType);
     });
 
     return {
@@ -284,7 +288,7 @@ export default defineComponent({
 
             <!--NCheckbox-->
             <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>
                   <NCheckbox
                     v-for="item in schema.componentProps!.options"
@@ -298,7 +302,7 @@ export default defineComponent({
 
             <!--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>
                   <NRadio v-for="item in schema.componentProps!.options" :key="item.value" :value="item.value">
                     {{ 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) {
   if (component === 'NInput') return `请输入${label}`;
   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 '';
 }
 

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

@@ -1,14 +1,16 @@
 import { nextTick, onUnmounted, ref, unref, watch } from 'vue';
 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>;
 
 export function useForm(props?: Props): UseFormReturnType {
   const formRef = ref<Nullable<FormActionType>>(null);
   const loadedRef = ref<Nullable<boolean>>(false);
+
   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!'
@@ -33,7 +35,7 @@ export function useForm(props?: Props): UseFormReturnType {
       () => props,
       () => {
         if (props) {
-          instance.setProps(getDynamicProps(props));
+          instance.setFormProps(getDynamicProps(props));
         }
       },
       {
@@ -44,9 +46,13 @@ export function useForm(props?: Props): UseFormReturnType {
   }
 
   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();
-      await form.setProps(formProps);
+      await form.setFormProps(formProps);
     },
 
     resetFields: async () => {

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

@@ -1,10 +1,13 @@
 import type { ComputedRef, Ref } 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';
 
 // 定义事件类型枚举
-type FormEvents = 'register' | 'reset' | 'submit';
+type FormEvents = 'registerForm' | 'reset' | 'submit';
 
 // 修改 EmitType 类型
 declare type EmitType = (event: FormEvents, ...args: any[]) => void;
@@ -17,6 +20,7 @@ interface UseFormActionContext {
   formElRef: Ref<FormActionType>;
   defaultFormModel: Recordable;
   loadingSub: Ref<boolean>;
+  schemaRef: Ref<FormSchema[]>;
   handleFormValues: (values: Recordable) => Recordable;
 }
 
@@ -28,6 +32,7 @@ export function useFormEvents({
   formElRef,
   defaultFormModel,
   loadingSub,
+  schemaRef,
   handleFormValues
 }: 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 {
     loadingSub.value = value;
@@ -118,6 +154,7 @@ export function useFormEvents({
     getFieldsValue,
     clearValidate,
     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)) {
         obj[item.field] = defaultValue;
         formModel[item.field] = defaultValue;
-        // console.log(formModel[item.field], 'formModel[item.field]');
       }
     });
     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 { propTypes } from '@/utils/propTypes';
 import type { FormSchema } from './types/form';
-export const basicProps = {
+export const basicFormProps = {
   // 标签宽度  固定宽度
   labelWidth: {
     type: [Number, String] as PropType<number | string>,

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

@@ -46,6 +46,8 @@ import {
 } from 'naive-ui';
 import type { GridItemProps, GridProps } from 'naive-ui/lib/grid';
 import type { ButtonProps } from 'naive-ui/lib/button';
+import { ApiSelect } from '../../ApiSelect';
+import type { ApiSelectProps } from '../../ApiSelect/type';
 // componentMap.ts
 export interface FormProps {
   model?: Recordable;
@@ -75,7 +77,7 @@ export interface FormProps {
 
 export interface FormActionType {
   submit: () => Promise<any>;
-  setProps: (formProps: Partial<FormProps>) => Promise<void>;
+  setFormProps: (formProps: Partial<FormProps>) => Promise<void>;
   setSchema: (schemaProps: Partial<FormSchema[]>) => Promise<void>;
   setFieldsValue: (values: Recordable) => void;
   clearValidate: (name?: string | string[]) => Promise<void>;
@@ -84,6 +86,7 @@ export interface FormActionType {
   validate: (nameList?: any[]) => Promise<any>;
   setLoading: (status: boolean) => void;
   restoreValidation: () => void;
+  updateSchema: (FormSchema: Partial<FormSchema> | Partial<FormSchema>[]) => void;
 }
 
 export type RegisterFn = (formInstance: FormActionType) => void;
@@ -117,7 +120,8 @@ export type FormSchema =
   | FormSchemaWithType<'NMention', MentionProps>
   | FormSchemaWithType<'NQrCode', QrCodeProps>
   | FormSchemaWithType<'NCalendar', CalendarProps>
-  | FormSchemaWithType<'NDynamicTags', DynamicTagsProps>;
+  | FormSchemaWithType<'NDynamicTags', DynamicTagsProps>
+  | FormSchemaWithType<'ApiSelect', ApiSelectProps>;
 
 export interface RenderCallbackParams {
   schema: FormSchema;
@@ -196,7 +200,8 @@ export const componentMap = {
   NQrCode,
   NCalendar,
   NDynamicTags,
-  NMention
+  NMention,
+  ApiSelect
 } as const;
 
 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>
-  <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>
 
-<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 { 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>[];
-  /**
-   * 	需要展示的数据
-   */
-  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 { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
 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>
 
 <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"
-        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>
 
 <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> = {
   '1': 'page.manage.common.status.enable',
-  '2': 'page.manage.common.status.disable'
+  '0': 'page.manage.common.status.disable'
 };
 
 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) {
     const redirect = route.value.query?.redirect as string;
+    console.log(needRedirect, redirect, '跳转至退出登录之前的页面');
 
     if (needRedirect && 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 UseNaivePaginatedTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, true> & {
+export type UseNaivePaginatedTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, true> & {
   paginationProps?: Omit<PaginationProps, 'page' | 'pageSize' | 'itemCount'>;
   /**
    * 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',
     manage_config: '',
     manage_log: '',
-    manage_schedule: ''
+    manage_schedule: '',
+    manage_department: ''
   },
   page: {
     login: {

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

@@ -277,7 +277,8 @@ const local: App.I18n.Schema = {
     plugin_tables_vtable: 'VTable',
     manage_config: '',
     manage_log: '',
-    manage_schedule: ''
+    manage_schedule: '',
+    manage_department: ''
   },
   page: {
     login: {
@@ -342,7 +343,7 @@ const local: App.I18n.Schema = {
     home: {
       branchDesc:
         '为了方便大家开发和更新合并,我们对main分支的代码进行了精简,只保留了首页菜单,其余内容已移至example分支进行维护。预览地址显示的内容即为example分支的内容。',
-      greeting: '早安,{userName}, 今天又是充满活力的一天!',
+      greeting: '早安,{username}, 今天又是充满活力的一天!',
       weatherDesc: '今日多云转晴,20℃ - 25℃!',
       projectCount: '项目数',
       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"),
   home: () => import("@/views/home/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_menu: () => import("@/views/manage/menu/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'
         }
       },
+      {
+        name: 'manage_department',
+        path: '/manage/department',
+        component: 'view.manage_department',
+        meta: {
+          title: 'manage_department',
+          i18nKey: 'route.manage_department'
+        }
+      },
       {
         name: '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)?",
   "manage": "/manage",
   "manage_config": "/manage/config",
+  "manage_department": "/manage/department",
   "manage_log": "/manage/log",
   "manage_menu": "/manage/menu",
   "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) {
   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 */
 export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
   return request<Api.SystemManage.UserList>({
-    url: '/systemManage/getUserList',
+    url: '/sys/user/page',
     method: 'get',
     params
   });
@@ -91,3 +91,113 @@ export function fetchDelMenu(id: number) {
     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
       // 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.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) {
       const authStore = useAuthStore();
@@ -104,7 +107,6 @@ export const request = createFlatRequest(
       // when the request is fail, you can show error message
       let message = error.message;
       let backendErrorCode = '';
-
       // get backend error message and code
       if (error.code === BACKEND_ERROR_CODE) {
         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());
 
-  let userInfo: Api.Auth.UserInfo = reactive({
+  const userInfo: Api.Auth.UserInfo = reactive({
     userId: '',
     username: '',
     roles: [],
@@ -110,6 +110,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
       imageCode,
       sessionUUID
     });
+    console.log('login', error, response);
+
     const loginToken: Api.Auth.LoginToken = {
       token: response.data.access_token,
       refreshToken: response.data.refreshToken
@@ -163,8 +165,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
     if (!error) {
       // update store
       // Object.assign(userInfo, info);
-      userInfo = info;
-
+      // userInfo = info;
+      Object.assign(userInfo, info);
       return true;
     }
 

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

@@ -27,9 +27,9 @@ declare namespace Api {
      * enable status
      *
      * - "1": enabled
-     * - "2": disabled
+     * - "0": disabled
      */
-    type EnableStatus = '1' | '2';
+    type EnableStatus = '0' | '1';
 
     /** common record */
     type CommonRecord<T = any> = {
@@ -67,6 +67,10 @@ declare namespace Api {
       email: 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"
    */
   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 RoleMenuList = {
+      roleId?: number;
+      remark: string;
+      roleName: string;
+      menuIdList: number[];
+    };
     /** role */
     type Role = Common.CommonRecord<{
       /** role name */
@@ -15,6 +29,7 @@ declare namespace Api {
       roleCode: string;
       /** role description */
       roleDesc: string;
+      roleId: number;
     }>;
 
     /** role search params */

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

@@ -8,9 +8,12 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    ApiSelect: typeof import('./../components/zt/ApiSelect/api-select.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']
     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']
     BetterScroll: typeof import('./../components/custom/better-scroll.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']
     IconUilSearch: typeof import('~icons/uil/search')['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']
     MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
     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)?";
     "manage": "/manage";
     "manage_config": "/manage/config";
+    "manage_department": "/manage/department";
     "manage_log": "/manage/log";
     "manage_menu": "/manage/menu";
     "manage_role": "/manage/role";
@@ -162,6 +163,7 @@ declare module "@elegant-router/types" {
     | "about"
     | "home"
     | "manage_config"
+    | "manage_department"
     | "manage_log"
     | "manage_menu"
     | "manage_role"

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

@@ -219,7 +219,66 @@ function convertMenuItem(menu: OriginalMenuItem, allMenus: OriginalMenuItem[]):
   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'
   }
 ]);
+console.log(authStore.userInfo);
 </script>
 
 <template>
@@ -48,7 +49,7 @@ const statisticData = computed<StatisticData[]>(() => [
           </div>
           <div class="pl-12px">
             <h3 class="text-18px font-semibold">
-              {{ $t('page.home.greeting', { userName: authStore.userInfo.username }) }}
+              {{ $t('page.home.greeting', { username: authStore.userInfo.username }) }}
             </h3>
             <p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>
           </div>

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

@@ -1,7 +1,7 @@
 <script setup lang="ts"></script>
 
 <template>
-  <div>123</div>
+  <LookForward></LookForward>
 </template>
 
 <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>
 
 <template>
-  <div>123</div>
+  <LookForward></LookForward>
 </template>
 
 <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 { fetchAddMenu, fetchDelMenu, fetchEditMenu, fetchGetMenuList } from '@/service/api';
 import { useAppStore } from '@/store/modules/app';
+import { buildMenuTree } from '@/utils/zt';
 import { $t } from '@/locales';
 import SvgIcon from '@/components/custom/svg-icon.vue';
 import { useForm } from '@/components/zt/Form/hooks/useForm';
@@ -20,12 +21,13 @@ const loading = ref(false);
 const showModal = ref(false);
 const formLoading = ref(false);
 
-const [registerForm, { setFieldsValue, validate, getFieldsValue }] = useForm({
+const [registerForm, { setFieldsValue, validate, getFieldsValue, updateSchema }] = useForm({
   schemas: formSchems,
   showSubmitButton: false,
   showAdvancedButton: false,
   showResetButton: false,
   labelWidth: 120,
+  showActionButtonGroup: false,
   layout: 'horizontal',
   gridProps: {
     cols: '1 xl:4 s:1 l:3',
@@ -183,27 +185,23 @@ async function handleDelete(id: number) {
 function handleEdit(item: Api.SystemManage.Menu) {
   showModal.value = true;
   operateType.value = 'edit';
-  nextTick(() => {
-    setFieldsValue(item);
-    console.log(getFieldsValue());
+  nextTick(async () => {
+    await setFieldsValue(item);
+    updateSchema([{ field: 'type', componentProps: { disabled: true } }]);
   });
 }
 
 function handleAddChildMenu(item: any) {
   operateType.value = 'addChild';
   showModal.value = true;
-  console.log(item, 'asdasd');
-
   nextTick(() => {
-    setFieldsValue({ parentId: item.menuId });
-    console.log(getFieldsValue());
+    setFieldsValue({ parentId: item.menuId, url: item.url });
   });
 }
 
 async function getAllPages() {
   const { data } = await fetchGetMenuList();
   const menuData = buildMenuTree(data);
-  console.log(menuData, '数据');
   menusData.value = menuData;
 }
 
@@ -213,67 +211,8 @@ function 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() {
-  console.log(getFieldsValue());
-
   await validate();
   formLoading.value = true;
   const form = getFieldsValue();
@@ -333,7 +272,7 @@ async function handleSubmit() {
               {{ operateType == 'add' ? '新增菜单' : operateType == 'addChild' ? '新增子菜单' : ' 编辑菜单' }}
             </div>
           </template>
-          <BasicForm @register="registerForm">
+          <BasicForm @register-form="registerForm">
             <template #parentId="{ model, field }">
               <NTreeSelect
                 v-model:value="model[field]"

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

@@ -1,4 +1,5 @@
 import { h } from 'vue';
+import { NInput } from 'naive-ui';
 import type { FormSchema } from '@/components/zt/Form/types/form';
 import CustomIconSelect from '@/components/custom/custom-icon-select.vue';
 import { icons } from './icons';
@@ -89,6 +90,7 @@ export const formSchems: FormSchema[] = [
     show: false,
     component: 'NInput'
   },
+
   {
     label: '菜单类型',
     field: 'type',
@@ -112,12 +114,29 @@ export const formSchems: FormSchema[] = [
       ]
     }
   },
+  {
+    label: '上级菜单',
+    field: 'parentId',
+    slot: 'parentId',
+    component: 'NTreeSelect',
+    defaultValue: 0
+  },
   {
     label: '菜单名称',
     field: 'name',
     component: 'NInput',
     required: true
   },
+  {
+    label: '前端组件',
+    field: 'component',
+    component: 'NInput',
+    required: true,
+    componentProps: {
+      disabled: true
+    }
+  },
+
   {
     label: '路由名称',
     field: 'url',
@@ -125,15 +144,28 @@ export const formSchems: FormSchema[] = [
     required: true,
     ifShow({ model }) {
       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: '排序',
     field: 'orderNum',

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

@@ -1,13 +1,11 @@
 <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 { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
 import { $t } from '@/locales';
 import RoleOperateDrawer from './modules/role-operate-drawer.vue';
-import RoleSearch from './modules/role-search.vue';
 
 const appStore = useAppStore();
 
@@ -18,6 +16,7 @@ const searchParams: Api.SystemManage.RoleSearchParams = reactive({
   roleCode: null,
   status: null
 });
+const delLoading = ref(false);
 
 const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagination } = useNaivePaginatedTable({
   api: () => fetchGetRoleList(searchParams),
@@ -46,51 +45,26 @@ const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagi
       minWidth: 120
     },
     {
-      key: 'roleCode',
-      title: $t('page.manage.role.roleCode'),
-      align: 'center',
-      minWidth: 120
-    },
-    {
-      key: 'roleDesc',
+      key: 'remark',
       title: $t('page.manage.role.roleDesc'),
       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',
       title: $t('common.operate'),
       align: 'center',
       width: 130,
+      fixed: 'right',
       render: row => (
         <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')}
           </NButton>
-          <NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
+          <NPopconfirm onPositiveClick={() => handleDelete(row.roleId)}>
             {{
               default: () => $t('common.confirmDelete'),
               trigger: () => (
-                <NButton type="error" ghost size="small">
+                <NButton type="error" ghost size="small" loading={delLoading.value}>
                   {$t('common.delete')}
                 </NButton>
               )
@@ -112,7 +86,7 @@ const {
   onBatchDeleted,
   onDeleted
   // closeDrawer
-} = useTableOperate(data, 'id', getData);
+} = useTableOperate(data, 'roleId', getData);
 
 async function handleBatchDelete() {
   // request
@@ -121,27 +95,31 @@ async function handleBatchDelete() {
   onBatchDeleted();
 }
 
-function handleDelete(id: number) {
+async function handleDelete(id: number) {
   // request
+  if (delLoading.value) return;
+  delLoading.value = true;
   console.log(id);
-
+  await fetchDelRoleMenu(id);
   onDeleted();
+  delLoading.value = false;
 }
 
-function edit(id: number) {
-  handleEdit(id);
+function edit(item: any) {
+  handleEdit(item.roleId);
 }
 </script>
 
 <template>
   <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">
       <template #header-extra>
         <TableHeaderOperation
           v-model:columns="columnChecks"
           :disabled-delete="checkedRowKeys.length === 0"
           :loading="loading"
+          is-add
           @add="handleAdd"
           @delete="handleBatchDelete"
           @refresh="getData"
@@ -156,7 +134,7 @@ function edit(id: number) {
         :scroll-x="702"
         :loading="loading"
         remote
-        :row-key="row => row.id"
+        :row-key="row => row.roleId"
         :pagination="mobilePagination"
         class="sm:h-full"
       />

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

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 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';
 
 defineOptions({
@@ -28,52 +29,26 @@ const home = shallowRef('');
 
 async function getHome() {
   console.log(props.roleId);
-
   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[]>([]);
 
 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[]>([]);
 
-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() {
   console.log(checks.value, props.roleId);
@@ -86,9 +61,7 @@ function handleSubmit() {
 
 function init() {
   getHome();
-  getPages();
   getTree();
-  getChecks();
 }
 
 watch(visible, val => {
@@ -100,19 +73,23 @@ watch(visible, val => {
 
 <template>
   <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>
       <NSelect :value="home" :options="pageSelectOptions" size="small" class="w-160px" @update:value="updateHome" />
     </div>
+-->
     <NTree
       v-model:checked-keys="checks"
       :data="tree"
-      key-field="id"
       checkable
       expand-on-click
       virtual-scroll
       block-line
       class="h-280px"
+      label-field="name"
+      key-field="menuId"
+      cascade
     />
     <template #footer>
       <NSpace justify="end">

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

@@ -1,11 +1,9 @@
 <script setup lang="ts">
 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 { buildMenuTree } from '@/utils/zt';
 import { $t } from '@/locales';
-import MenuAuthModal from './menu-auth-modal.vue';
-import ButtonAuthModal from './button-auth-modal.vue';
 
 defineOptions({
   name: 'RoleOperateDrawer'
@@ -32,8 +30,6 @@ const visible = defineModel<boolean>('visible', {
 
 const { formRef, validate, restoreValidation } = useNaiveForm();
 const { defaultRequiredRule } = useFormRules();
-const { bool: menuAuthVisible, setTrue: openMenuAuthModal } = useBoolean();
-const { bool: buttonAuthVisible, setTrue: openButtonAuthModal } = useBoolean();
 
 const title = computed(() => {
   const titles: Record<NaiveUI.TableOperateType, string> = {
@@ -42,37 +38,38 @@ const title = computed(() => {
   };
   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());
 
 function createDefaultModel(): Model {
   return {
     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> = {
-  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() {
   model.value = createDefaultModel();
 
   if (props.operateType === 'edit' && props.rowData) {
     Object.assign(model.value, props.rowData);
+    handleEdit();
   }
 }
 
@@ -83,11 +80,24 @@ function closeDrawer() {
 async function handleSubmit() {
   await validate();
   // 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'));
   closeDrawer();
   emit('submitted');
 }
-
+async function getTree() {
+  const { data } = await fetchGetMenuList();
+  const menuData = buildMenuTree(data);
+  if (menuData) {
+    tree.value = menuData;
+  }
+}
+getTree();
 watch(visible, () => {
   if (visible.value) {
     handleInitModel();
@@ -103,24 +113,32 @@ watch(visible, () => {
         <NFormItem :label="$t('page.manage.role.roleName')" path="roleName">
           <NInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
         </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>
       </NForm>
-      <NSpace v-if="isEdit">
+      <!--
+ <NSpace>
         <NButton @click="openMenuAuthModal">{{ $t('page.manage.role.menuAuth') }}</NButton>
         <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" />
+
       </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>
         <NSpace :size="16">
           <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">
-import { enableStatusOptions } from '@/constants/business';
-import { translateOptions } from '@/utils/common';
 import { $t } from '@/locales';
 
 defineOptions({
@@ -23,6 +21,7 @@ function resetModel() {
     roleCode: null,
     status: null
   };
+  console.log(model.value, 'asdasda');
 }
 
 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">
               <NInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
             </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">
               <NSpace class="w-full" justify="end">
                 <NButton @click="resetModel">

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

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

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

@@ -1,203 +1,265 @@
 <script setup lang="tsx">
-import { reactive } from 'vue';
 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 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>
 
 <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>
-      <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>
 
 <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> = {
           1: 'success',
-          2: 'warning'
+          0: 'warning'
         };
 
         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>
-  <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>
 
 <style scoped></style>