xyh 2 лет назад
Родитель
Сommit
63bda91c3d

+ 20 - 0
src/apis/user.ts

@@ -57,6 +57,17 @@ export function getUserList(
   });
 }
 
+/** 查询用户列表 */
+export function getUserInfo(
+  id: string,
+): BaseListResult<UserListData> {
+  return request({
+    method: 'GET',
+    url: BASE_URL + '/getAllUser',
+    data: {id, page: '1', limit: '1'},
+  });
+}
+
 /** 新增用户信息 */
 export function addUser(data: AddUserParams): BaseResult {
   return request({
@@ -92,3 +103,12 @@ export function resetPassword(id: string): BaseResult {
     data: {id},
   });
 }
+
+/** 删除用户 */
+export function delUser(id: string): BaseResult {
+  return request({
+    method: 'DELETE',
+    url: BASE_URL + '/delUser',
+    data: {id},
+  });
+}

+ 11 - 0
src/components/errorboundary/index.css

@@ -0,0 +1,11 @@
+.ld-error-boundary {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+
+  & .n-result {
+    transform: translateY(-50px);
+  }
+}

+ 19 - 11
src/components/errorboundary/index.tsx

@@ -1,5 +1,6 @@
 import {NButton, NResult} from 'naive-ui';
 import {PropType, defineComponent, h} from 'vue';
+import './index.css';
 
 export default defineComponent({
   name: 'LDErrorBoundary',
@@ -16,17 +17,24 @@ export default defineComponent({
   setup(props) {
     return () => (
       h(
-        NResult,
-        {status: '500', description: props.msg},
-        {
-          footer: h(
-            NButton,
-            {
-              onClick: props.onReset,
-            },
-            'retry',
-          ),
-        },
+        'div',
+        {class: 'ld-error-boundary'},
+        h(
+          NResult,
+          {
+            status: '500',
+            description: props.msg,
+          },
+          {
+            footer: h(
+              NButton,
+              {
+                onClick: props.onReset,
+              },
+              'retry',
+            ),
+          },
+        ),
       )
     );
   },

+ 26 - 0
src/locales/user.ts

@@ -1,8 +1,34 @@
 export default {
   zh: {
+    label: '用户',
     filter: ['用户名称', '真实姓名', '角色名称', '邮箱', '手机号'],
+    table: ['用户名称', '真实姓名', '邮箱', '手机号', '角色'],
+    modal: {
+      title: ['新增用户', '修改用户'],
+      errors: [
+        '请输入用户姓名',
+        '请输入真实姓名',
+        '请输入邮箱',
+        '邮箱格式错误',
+        '请输入手机号',
+        '请选择角色',
+      ],
+    },
   },
   ko: {
+    label: '用户',
     filter: ['用户名称kr', '真实姓名kr', '角色名称kr', '邮箱kr', '手机号kr'],
+    table: ['用户名称', '真实姓名', '邮箱', '手机号', '角色'],
+    modal: {
+      title: ['新增用户', '修改用户'],
+      errors: [
+        '请输入用户姓名',
+        '请输入真实姓名',
+        '请输入邮箱',
+        '邮箱格式错误',
+        '请输入手机号',
+        '请选择角色',
+      ],
+    },
   },
 };

+ 1 - 1
src/pages/home/Home.vue

@@ -24,7 +24,7 @@ const {suspense} = useQuery({
 
     if (data.msg === '200') return data.data;
 
-    return [];
+    throw new Error(data.errMsg);
   },
   suspense: true,
 });

+ 16 - 7
src/pages/home/index.vue

@@ -1,7 +1,8 @@
 <script setup lang='ts'>
 import Home from './Home.vue';
-import {LDSuspenseLoading} from '@components';
+import {LDSuspenseLoading, LDErrorBoundary} from '@components';
 import {useI18n} from 'vue-i18n';
+import ErrorBoundary from 'veboundary';
 
 defineOptions({name: 'HomeWrapper'});
 
@@ -9,12 +10,20 @@ const {t} = useI18n();
 </script>
 
 <template>
-  <Suspense>
-    <template #fallback>
-      <main style="width: 100vw; height: 100vh;">
-        <LDSuspenseLoading :msg="t('home.loading')" />
+  <ErrorBoundary>
+    <template #fallback="{reset, error}">
+      <main style="width: 100vw;height: 100vh">
+        <LDErrorBoundary :msg="error.message" @reset="reset" />
       </main>
     </template>
-    <Home />
-  </Suspense>
+
+    <Suspense>
+      <template #fallback>
+        <main style="width: 100vw; height: 100vh;">
+          <LDSuspenseLoading :msg="t('home.loading')" />
+        </main>
+      </template>
+      <Home />
+    </Suspense>
+  </ErrorBoundary>
 </template>

+ 1 - 0
src/pages/role/table/index.vue

@@ -19,6 +19,7 @@ const [
   {columns, editId, visible, menuId, menuVisible},
   {onAdd},
 ] = useColumns(refetch);
+
 const [isExporting, onExport] = useTableExportEvent(
   filterSymbol,
   pageSymbol,

+ 7 - 6
src/pages/user/filter/hooks.ts

@@ -23,7 +23,14 @@ export function useExtendsTools() {
   const tools: LDFilterTool<OriginalListParams<GetUserListParams>>[] = [
     {type: 'field', label: computed(() => t('user.filter[0]')), name: 'userName'},
     {type: 'field', label: computed(() => t('user.filter[1]')), name: 'realName'},
+  ];
+
+  const extendsTools: LDFilterTool<OriginalListParams<GetUserListParams>>[] = [
+
+    {type: 'field', label: computed(() => t('user.filter[3]')), name: 'email', id: '2'},
+    {type: 'field', label: computed(() => t('user.filter[4]')), name: 'phone', id: '3'},
     {
+      id: '1',
       type: 'select',
       label: computed(() => t('user.filter[2]')),
       name: 'role',
@@ -37,11 +44,5 @@ export function useExtendsTools() {
     },
   ];
 
-  const extendsTools: LDFilterTool<OriginalListParams<GetUserListParams>>[] = [
-
-    {type: 'field', label: computed(() => t('user.filter[3]')), name: 'email', id: '2'},
-    {type: 'field', label: computed(() => t('user.filter[4]')), name: 'phone', id: '3'},
-  ];
-
   return {tools, extendsTools};
 }

+ 2 - 0
src/pages/user/index.tsx

@@ -6,6 +6,7 @@ import {
 import {Fragment, defineComponent} from 'vue';
 import {pageSymbol, searchSymbol, filterSymbol, filterState} from './state';
 import Filter from './filter/index.vue';
+import Table from './table/index.vue';
 
 export default defineComponent({
   name: 'UserPage',
@@ -17,6 +18,7 @@ export default defineComponent({
     return () => (
       <Fragment>
         <Filter />
+        <Table />
       </Fragment>
     );
   },

+ 102 - 0
src/pages/user/table/hooks.ts

@@ -0,0 +1,102 @@
+import {delUser, getUserList} from '@apis';
+import {
+  useFetchTableList,
+  useTableDeleteEvent,
+  useTableModalEvent,
+} from '@hooks';
+import {filterSymbol, pageSymbol, searchSymbol} from '../state';
+import {useI18n} from 'vue-i18n';
+import {computed, h} from 'vue';
+import {type DataTableColumn, NSpace} from 'naive-ui';
+import {UserListData} from '@models';
+import {TABLE_CELL_WIDTH} from '@utils';
+import {LDTableButton} from '@components';
+
+export function useTableData() {
+  return useFetchTableList({
+    queryFn: getUserList,
+    searchSymbol,
+    pageSymbol,
+    filterSymbol,
+  });
+}
+
+export function useColumns(refetch: () => void) {
+  const {t} = useI18n();
+  const [
+    {visible, editId},
+    {onEdit, onAdd},
+  ] = useTableModalEvent();
+  const [deleteId, onDelete] = useTableDeleteEvent(
+    delUser,
+    refetch,
+    {label: t('user.label')},
+  );
+
+  const columns = computed<DataTableColumn<UserListData>[]>(function() {
+    return [
+      {
+        title: t('user.table[0]'),
+        key: 'userName',
+        width: TABLE_CELL_WIDTH.normal,
+      },
+      {
+        title: t('user.table[1]'),
+        key: 'realName',
+        width: TABLE_CELL_WIDTH.normal,
+      },
+      {
+        title: t('user.table[4]'),
+        key: 'role',
+        width: TABLE_CELL_WIDTH.normal,
+      },
+      {
+        title: t('user.table[2]'),
+        key: 'email',
+        width: TABLE_CELL_WIDTH.normal,
+      },
+      {
+        title: t('user.table[3]'),
+        key: 'phone',
+        width: TABLE_CELL_WIDTH.normal,
+      },
+      {
+        title: t('common.tableHeader.operation'),
+        width: TABLE_CELL_WIDTH.huge,
+        key: 'id',
+        fixed: 'right',
+        render({id}) {
+          const isDeleteing = String(id) === deleteId.value;
+
+          return h(
+            NSpace,
+            null,
+            [
+              h(
+                h(LDTableButton),
+                {
+                  isDelete: false,
+                  disabled: isDeleteing,
+                  onClick: onEdit(String(id)),
+                  text: t('common.tableTool.buttonGroup.edit'),
+                },
+              ),
+              h(
+                h(LDTableButton),
+                {
+                  isDelete: true,
+                  loading: isDeleteing,
+                  disabled: isDeleteing,
+                  onClick: onDelete(String(id)),
+                  text: t('common.tableTool.buttonGroup.delete'),
+                },
+              ),
+            ],
+          );
+        },
+      },
+    ];
+  });
+
+  return [{columns, visible, editId}, {onAdd}] as const;
+}

+ 47 - 0
src/pages/user/table/index.vue

@@ -0,0 +1,47 @@
+<script setup lang='ts'>
+import {useTableData, useColumns} from './hooks';
+import {useTableExportEvent} from '@hooks';
+import {filterSymbol, pageSymbol, searchSymbol} from '../state';
+import {exportUser} from '@apis';
+import {NCard} from 'naive-ui';
+import {LDTableTool, LDTable} from '@components';
+import Modal from './modal/index.vue';
+
+defineOptions({name: 'UserPageTable'});
+
+const [
+  {isFetching, data, count},
+  {refetch},
+] = useTableData();
+
+const [{columns, visible, editId}, {onAdd}] = useColumns(refetch);
+
+const [isExporting, onExport] = useTableExportEvent(
+  filterSymbol,
+  pageSymbol,
+  {fn: exportUser},
+);
+</script>
+
+<template>
+  <NCard class="table-wrapper">
+    <LDTableTool
+      :isRefreshing="isFetching"
+      @refresh="refetch"
+      @export="onExport"
+      @add="onAdd"
+      :isExporting="isExporting"
+    />
+
+    <LDTable
+      :count="count"
+      :data="data"
+      :columns="columns"
+      :pageSymbol="pageSymbol"
+      :searchSymbol="searchSymbol"
+    />
+  </NCard>
+
+  <Modal v-model:visible="visible" :id="editId" @fetch="refetch" />
+</template>
+

+ 76 - 0
src/pages/user/table/modal/Info.vue

@@ -0,0 +1,76 @@
+<script setup lang='ts'>
+import {LDModalInput, LDModalSelect} from '@components';
+import {useQueryDataInfo} from '@hooks';
+import {getAllRole, getUserInfo} from '@apis';
+import {computed, watchEffect} from 'vue';
+import {useI18n} from 'vue-i18n';
+import type {AddUserParams} from '@models';
+import {useQuery} from '@tanstack/vue-query';
+
+defineOptions({name: 'UserPageModalInfo'});
+
+type Props = {
+  id: string;
+  setValues: (fields: AddUserParams) => void
+};
+
+const props = defineProps<Props>();
+
+const [data, suspense] = useQueryDataInfo({
+  queryFn: getUserInfo,
+  params: computed<[id: string]>(() => [props.id]),
+  enabled: computed(() => props.id.length > 0),
+});
+
+if (props.id.length > 0) await suspense();
+
+watchEffect(function() {
+  const {
+    realName = '',
+    userName = '',
+    roleId = '',
+    email = '',
+    phone = '',
+  } = data.value ?? {};
+
+  props.setValues({
+    realName,
+    userName,
+    role: roleId,
+    email,
+    phone,
+  });
+});
+
+const {t} = useI18n();
+
+const {isLoading, data: options} = useQuery({
+  queryKey: [getAllRole.name],
+  async queryFn({signal}) {
+    const data = await getAllRole(signal);
+
+    if (data.msg === '200') {
+      return data.data.map(function(val) {
+        return {label: val.roleName, value: val.id};
+      });
+    }
+
+    return [];
+  },
+  cacheTime: 1000 * 60,
+});
+</script>
+
+<template>
+  <LDModalInput name="userName" :label="t('user.table[0]')" />
+  <LDModalInput name="realName" :label="t('user.table[1]')" />
+  <LDModalSelect
+    name="role"
+    :label="t('user.table[4]')"
+    :loading="isLoading"
+    :options="options"
+  />
+  <LDModalInput name="email" :label="t('user.table[2]')" />
+  <LDModalInput name="phone" :label="t('user.table[3]')" />
+</template>
+

+ 65 - 0
src/pages/user/table/modal/hooks.ts

@@ -0,0 +1,65 @@
+import {addUser, editUser} from '@apis';
+import {usePutData} from '@hooks';
+import {AddUserParams} from '@models';
+import {formatValidateError} from '@utils';
+import {toTypedSchema} from '@vee-validate/zod';
+import {useForm} from 'vee-validate';
+import {Ref, watchEffect} from 'vue';
+import {object, string} from 'zod';
+
+export function useFormState(
+  id: Ref<string>,
+  visible: Ref<boolean>,
+  options: {onFetch: () => void, onClose: () => void},
+) {
+  const {handleSubmit, setErrors, setValues} = useForm<AddUserParams>({
+    initialValues: {
+      userName: '',
+      realName: '',
+      email: '',
+      phone: '',
+      role: '',
+    },
+    validationSchema: toTypedSchema(object({
+      userName: string(formatValidateError('user.modal.errors[0]'))
+        .min(1, 'user.modal.errors[0]'),
+      realName: string(formatValidateError('user.modal.errors[1]'))
+        .min(1, 'user.modal.errors[1]'),
+      email: string(formatValidateError('user.modal.errors[2]'))
+        .email('user.modal.errors[3]'),
+      phone: string(formatValidateError('user.modal.errors[4]'))
+        .min(1, 'user.modal.errors[4]'),
+      role: string(formatValidateError('user.modal.errors[5]'))
+        .min(1, 'user.modal.errors[5]'),
+    })),
+  });
+
+  watchEffect(function() {
+    if (visible.value) {
+      setErrors({
+        userName: void 0,
+        realName: void 0,
+        email: void 0,
+        phone: void 0,
+        role: void 0,
+      });
+    }
+  });
+
+  const {onFetch, onClose} = options;
+
+  const [isLoading, {addMutate, editMutate}] = usePutData({
+    addFn: addUser,
+    editFn: editUser,
+    onFetch,
+    onClose,
+  });
+
+  const onSubmit = handleSubmit(function(val) {
+    id.value.length > 0
+      ? editMutate({id: id.value, ...val})
+      : addMutate(val);
+  });
+
+  return [isLoading, {setValues, onSubmit}] as const;
+}

+ 59 - 0
src/pages/user/table/modal/index.vue

@@ -0,0 +1,59 @@
+<script setup lang='ts'>
+import {LDNormalModal, LDErrorBoundary, LDSuspenseLoading} from '@components';
+import {toRefs} from 'vue';
+import {useVModel} from '@vueuse/core';
+import {useFormState} from './hooks';
+import {useI18n} from 'vue-i18n';
+import ErrorBoundary from 'veboundary';
+import Info from './Info.vue';
+
+defineOptions({name: 'UserPageModal'});
+
+type Props = {
+  visible: boolean;
+  id: string;
+  onFetch: () => void;
+};
+
+const props = defineProps<Props>();
+const {visible, id} = toRefs(props);
+const emits = defineEmits<{(event: 'update:visible'): void}>();
+const visibleValue = useVModel(props, 'visible', emits);
+const [isLoading, {onSubmit, setValues}] = useFormState(
+  id,
+  visible,
+  {
+    onFetch: props.onFetch,
+    onClose() {
+      visibleValue.value = false;
+    },
+  },
+);
+
+const {t} = useI18n();
+</script>
+
+<template>
+  <LDNormalModal
+    :isAdd="id.length > 0"
+    v-model="visibleValue"
+    :title="t(`user.modal.title[${id.length > 0 ? 1 : 0}]`)"
+    @submit="onSubmit"
+    :isLoading="isLoading"
+  >
+    <ErrorBoundary>
+      <template #fallback="{error, reset}">
+        <LDErrorBoundary :msg="error.message" @reset="reset" />
+      </template>
+
+      <Suspense>
+        <template #fallback>
+          <LDSuspenseLoading />
+        </template>
+
+        <Info :id="id" :setValues="setValues" />
+      </Suspense>
+    </ErrorBoundary>
+  </LDNormalModal>
+</template>
+