mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 07:43:42 +08:00 
			
		
		
		
	refactor(projects): finish refactor useTable and apply
This commit is contained in:
		@@ -2,8 +2,8 @@ import useBoolean from './use-boolean';
 | 
			
		||||
import useLoading from './use-loading';
 | 
			
		||||
import useContext from './use-context';
 | 
			
		||||
import useSvgIconRender from './use-svg-icon-render';
 | 
			
		||||
import useTable from './use-table';
 | 
			
		||||
import useHookTable from './use-table';
 | 
			
		||||
 | 
			
		||||
export { useBoolean, useLoading, useContext, useSvgIconRender, useTable };
 | 
			
		||||
export { useBoolean, useLoading, useContext, useSvgIconRender, useHookTable };
 | 
			
		||||
 | 
			
		||||
export * from './use-table';
 | 
			
		||||
 
 | 
			
		||||
@@ -7,18 +7,16 @@ export type MaybePromise<T> = T | Promise<T>;
 | 
			
		||||
 | 
			
		||||
export type ApiFn = (args: any) => Promise<unknown>;
 | 
			
		||||
 | 
			
		||||
export type TableData = Record<string, unknown>;
 | 
			
		||||
 | 
			
		||||
export type TableColumn = Record<string, any>;
 | 
			
		||||
 | 
			
		||||
export type TableColumnCheck = {
 | 
			
		||||
  key: string;
 | 
			
		||||
  title: string;
 | 
			
		||||
  checked: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type TableDataWithIndex<T> = T & { index: number };
 | 
			
		||||
 | 
			
		||||
export type TransformedData<T> = {
 | 
			
		||||
  data: T[];
 | 
			
		||||
  data: TableDataWithIndex<T>[];
 | 
			
		||||
  pageNum: number;
 | 
			
		||||
  pageSize: number;
 | 
			
		||||
  total: number;
 | 
			
		||||
@@ -61,7 +59,7 @@ export type TableConfig<A extends ApiFn, T, C> = {
 | 
			
		||||
  immediate?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function useTable<A extends ApiFn, T, C>(config: TableConfig<A, T, C>) {
 | 
			
		||||
export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<A, T, C>) {
 | 
			
		||||
  const { loading, startLoading, endLoading } = useLoading();
 | 
			
		||||
  const { bool: empty, setBool: setEmpty } = useBoolean();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,226 +0,0 @@
 | 
			
		||||
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';
 | 
			
		||||
import type { Ref } from 'vue';
 | 
			
		||||
import type { PaginationProps } from 'naive-ui';
 | 
			
		||||
import { useBoolean, useTable } from '@sa/hooks';
 | 
			
		||||
import { useAppStore } from '@/store/modules/app';
 | 
			
		||||
import { $t } from '@/locales';
 | 
			
		||||
 | 
			
		||||
type TableData = NaiveUI.TableData;
 | 
			
		||||
type GetTableData<A extends NaiveUI.TableApiFn> = NaiveUI.GetTableData<A>;
 | 
			
		||||
type TableColumn<T> = NaiveUI.TableColumn<T>;
 | 
			
		||||
 | 
			
		||||
export function useNaiveTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
 | 
			
		||||
  const scope = effectScope();
 | 
			
		||||
  const appStore = useAppStore();
 | 
			
		||||
 | 
			
		||||
  const { apiFn, apiParams, immediate } = config;
 | 
			
		||||
 | 
			
		||||
  const SELECTION_KEY = '__selection__';
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    loading,
 | 
			
		||||
    empty,
 | 
			
		||||
    data,
 | 
			
		||||
    columns,
 | 
			
		||||
    columnChecks,
 | 
			
		||||
    reloadColumns,
 | 
			
		||||
    getData,
 | 
			
		||||
    searchParams,
 | 
			
		||||
    updateSearchParams,
 | 
			
		||||
    resetSearchParams
 | 
			
		||||
  } = useTable<A, GetTableData<A>, TableColumn<GetTableData<A>>>({
 | 
			
		||||
    apiFn,
 | 
			
		||||
    apiParams,
 | 
			
		||||
    columns: config.columns,
 | 
			
		||||
    transformer: res => {
 | 
			
		||||
      const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        data: records,
 | 
			
		||||
        pageNum: current,
 | 
			
		||||
        pageSize: size,
 | 
			
		||||
        total
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    getColumnChecks: cols => {
 | 
			
		||||
      const checks: NaiveUI.TableColumnCheck[] = [];
 | 
			
		||||
 | 
			
		||||
      cols.forEach(column => {
 | 
			
		||||
        if (isTableColumnHasKey(column)) {
 | 
			
		||||
          checks.push({
 | 
			
		||||
            key: column.key as string,
 | 
			
		||||
            title: column.title as string,
 | 
			
		||||
            checked: true
 | 
			
		||||
          });
 | 
			
		||||
        } else if (column.type === 'selection') {
 | 
			
		||||
          checks.push({
 | 
			
		||||
            key: SELECTION_KEY,
 | 
			
		||||
            title: $t('common.check'),
 | 
			
		||||
            checked: true
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return checks;
 | 
			
		||||
    },
 | 
			
		||||
    getColumns: (cols, checks) => {
 | 
			
		||||
      const columnMap = new Map<string, TableColumn<GetTableData<A>>>();
 | 
			
		||||
 | 
			
		||||
      cols.forEach(column => {
 | 
			
		||||
        if (isTableColumnHasKey(column)) {
 | 
			
		||||
          columnMap.set(column.key as string, column);
 | 
			
		||||
        } else if (column.type === 'selection') {
 | 
			
		||||
          columnMap.set(SELECTION_KEY, column);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const filteredColumns = checks
 | 
			
		||||
        .filter(item => item.checked)
 | 
			
		||||
        .map(check => columnMap.get(check.key) as TableColumn<GetTableData<A>>);
 | 
			
		||||
 | 
			
		||||
      return filteredColumns;
 | 
			
		||||
    },
 | 
			
		||||
    onFetched: async transformed => {
 | 
			
		||||
      const { pageNum, pageSize, total } = transformed;
 | 
			
		||||
 | 
			
		||||
      updatePagination({
 | 
			
		||||
        page: pageNum,
 | 
			
		||||
        pageSize,
 | 
			
		||||
        itemCount: total
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    immediate
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const pagination: PaginationProps = reactive({
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 10,
 | 
			
		||||
    showSizePicker: true,
 | 
			
		||||
    pageSizes: [10, 15, 20, 25, 30],
 | 
			
		||||
    onUpdatePage: async (page: number) => {
 | 
			
		||||
      pagination.page = page;
 | 
			
		||||
 | 
			
		||||
      updateSearchParams({
 | 
			
		||||
        current: page,
 | 
			
		||||
        size: pagination.pageSize!
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      getData();
 | 
			
		||||
    },
 | 
			
		||||
    onUpdatePageSize: async (pageSize: number) => {
 | 
			
		||||
      pagination.pageSize = pageSize;
 | 
			
		||||
      pagination.page = 1;
 | 
			
		||||
 | 
			
		||||
      updateSearchParams({
 | 
			
		||||
        current: pagination.page,
 | 
			
		||||
        size: pageSize
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      getData();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // this is for mobile, if the system does not support mobile, you can use `pagination` directly
 | 
			
		||||
  const mobilePagination = computed(() => {
 | 
			
		||||
    const p: PaginationProps = {
 | 
			
		||||
      ...pagination,
 | 
			
		||||
      pageSlot: appStore.isMobile ? 3 : 9
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return p;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  function updatePagination(update: Partial<PaginationProps>) {
 | 
			
		||||
    Object.assign(pagination, update);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  scope.run(() => {
 | 
			
		||||
    watch(
 | 
			
		||||
      () => appStore.locale,
 | 
			
		||||
      () => {
 | 
			
		||||
        reloadColumns();
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  onScopeDispose(() => {
 | 
			
		||||
    scope.stop();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    loading,
 | 
			
		||||
    empty,
 | 
			
		||||
    data,
 | 
			
		||||
    columns,
 | 
			
		||||
    columnChecks,
 | 
			
		||||
    reloadColumns,
 | 
			
		||||
    pagination,
 | 
			
		||||
    mobilePagination,
 | 
			
		||||
    updatePagination,
 | 
			
		||||
    getData,
 | 
			
		||||
    searchParams,
 | 
			
		||||
    updateSearchParams,
 | 
			
		||||
    resetSearchParams
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useNaiveTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void>) {
 | 
			
		||||
  const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
 | 
			
		||||
 | 
			
		||||
  const operateType = ref<NaiveUI.TableOperateType>('add');
 | 
			
		||||
 | 
			
		||||
  function handleAdd() {
 | 
			
		||||
    operateType.value = 'add';
 | 
			
		||||
    openDrawer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** the editing row data */
 | 
			
		||||
  const editingData: Ref<T | null> = ref(null);
 | 
			
		||||
 | 
			
		||||
  function handleEdit(id: number) {
 | 
			
		||||
    operateType.value = 'edit';
 | 
			
		||||
    editingData.value = data.value.find(item => item.id === id) || null;
 | 
			
		||||
 | 
			
		||||
    openDrawer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const checkedRowKeys = ref<string[]>([]);
 | 
			
		||||
 | 
			
		||||
  /** the hook after the batch delete operation is completed */
 | 
			
		||||
  async function onBatchDeleted() {
 | 
			
		||||
    window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
    checkedRowKeys.value = [];
 | 
			
		||||
 | 
			
		||||
    await getData();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** the hook after the delete operation is completed */
 | 
			
		||||
  async function onDeleted() {
 | 
			
		||||
    window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
    await getData();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    drawerVisible,
 | 
			
		||||
    operateType,
 | 
			
		||||
    handleAdd,
 | 
			
		||||
    editingData,
 | 
			
		||||
    handleEdit,
 | 
			
		||||
    checkedRowKeys,
 | 
			
		||||
    onBatchDeleted,
 | 
			
		||||
    onDeleted,
 | 
			
		||||
    closeDrawer
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getNaiveTableIndex(pagination: PaginationProps, index: number) {
 | 
			
		||||
  const { page = 1, pageSize = 10 } = pagination;
 | 
			
		||||
 | 
			
		||||
  return (page - 1) * pageSize + index + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isTableColumnHasKey<T>(column: TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
 | 
			
		||||
  return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,153 +1,146 @@
 | 
			
		||||
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';
 | 
			
		||||
import type { Ref } from 'vue';
 | 
			
		||||
import type { DataTableBaseColumn, DataTableExpandColumn, DataTableSelectionColumn, PaginationProps } from 'naive-ui';
 | 
			
		||||
import type { TableColumnGroup } from 'naive-ui/es/data-table/src/interface';
 | 
			
		||||
import { useBoolean, useLoading } from '@sa/hooks';
 | 
			
		||||
import type { PaginationProps } from 'naive-ui';
 | 
			
		||||
import { useBoolean, useHookTable } from '@sa/hooks';
 | 
			
		||||
import { useAppStore } from '@/store/modules/app';
 | 
			
		||||
import { $t } from '@/locales';
 | 
			
		||||
 | 
			
		||||
type BaseData = Record<string, unknown>;
 | 
			
		||||
type TableData = NaiveUI.TableData;
 | 
			
		||||
type GetTableData<A extends NaiveUI.TableApiFn> = NaiveUI.GetTableData<A>;
 | 
			
		||||
type TableColumn<T> = NaiveUI.TableColumn<T>;
 | 
			
		||||
 | 
			
		||||
type ApiFn = (args: any) => Promise<unknown>;
 | 
			
		||||
 | 
			
		||||
export type TableColumn<T extends BaseData = BaseData, CustomColumnKey = never> =
 | 
			
		||||
  | (Omit<TableColumnGroup<T>, 'key'> & { key: keyof T | CustomColumnKey })
 | 
			
		||||
  | (Omit<DataTableBaseColumn<T>, 'key'> & { key: keyof T | CustomColumnKey })
 | 
			
		||||
  | DataTableSelectionColumn<T>
 | 
			
		||||
  | DataTableExpandColumn<T>;
 | 
			
		||||
 | 
			
		||||
export type TransformedData<TableData extends BaseData = BaseData> = {
 | 
			
		||||
  data: TableData[];
 | 
			
		||||
  pageNum: number;
 | 
			
		||||
  pageSize: number;
 | 
			
		||||
  total: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** transform api response to table data */
 | 
			
		||||
type Transformer<TableData extends BaseData = BaseData, Response = NonNullable<unknown>> = (
 | 
			
		||||
  response: Response
 | 
			
		||||
) => TransformedData<TableData>;
 | 
			
		||||
 | 
			
		||||
/** table config */
 | 
			
		||||
export type TableConfig<TableData extends BaseData = BaseData, Fn extends ApiFn = ApiFn, CustomColumnKey = never> = {
 | 
			
		||||
  /** api function to get table data */
 | 
			
		||||
  apiFn: Fn;
 | 
			
		||||
  /** api params */
 | 
			
		||||
  apiParams?: Parameters<Fn>[0];
 | 
			
		||||
  /** transform api response to table data */
 | 
			
		||||
  transformer: Transformer<TableData, Awaited<ReturnType<Fn>>>;
 | 
			
		||||
  /** pagination */
 | 
			
		||||
  pagination?: PaginationProps;
 | 
			
		||||
  /**
 | 
			
		||||
   * callback when pagination changed
 | 
			
		||||
   *
 | 
			
		||||
   * @param pagination
 | 
			
		||||
   */
 | 
			
		||||
  onPaginationChanged?: (pagination: PaginationProps) => void | Promise<void>;
 | 
			
		||||
  /**
 | 
			
		||||
   * whether to get data immediately
 | 
			
		||||
   *
 | 
			
		||||
   * @default true
 | 
			
		||||
   */
 | 
			
		||||
  immediate?: boolean;
 | 
			
		||||
  /** columns factory */
 | 
			
		||||
  columns: () => TableColumn<TableData, CustomColumnKey>[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** filter columns */
 | 
			
		||||
export type FilteredColumn = {
 | 
			
		||||
  key: string;
 | 
			
		||||
  title: string;
 | 
			
		||||
  checked: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomColumnKey = never>(
 | 
			
		||||
  config: TableConfig<TableData, Fn, CustomColumnKey>
 | 
			
		||||
) {
 | 
			
		||||
export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
 | 
			
		||||
  const scope = effectScope();
 | 
			
		||||
  const appStore = useAppStore();
 | 
			
		||||
 | 
			
		||||
  const { loading, startLoading, endLoading } = useLoading();
 | 
			
		||||
  const { bool: empty, setBool: setEmpty } = useBoolean();
 | 
			
		||||
  const { apiFn, apiParams, immediate } = config;
 | 
			
		||||
 | 
			
		||||
  const { apiFn, apiParams, transformer, onPaginationChanged, immediate = true } = config;
 | 
			
		||||
  const SELECTION_KEY = '__selection__';
 | 
			
		||||
 | 
			
		||||
  const searchParams: NonNullable<Parameters<Fn>[0]> = reactive({ ...apiParams });
 | 
			
		||||
  const {
 | 
			
		||||
    loading,
 | 
			
		||||
    empty,
 | 
			
		||||
    data,
 | 
			
		||||
    columns,
 | 
			
		||||
    columnChecks,
 | 
			
		||||
    reloadColumns,
 | 
			
		||||
    getData,
 | 
			
		||||
    searchParams,
 | 
			
		||||
    updateSearchParams,
 | 
			
		||||
    resetSearchParams
 | 
			
		||||
  } = useHookTable<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>({
 | 
			
		||||
    apiFn,
 | 
			
		||||
    apiParams,
 | 
			
		||||
    columns: config.columns,
 | 
			
		||||
    transformer: res => {
 | 
			
		||||
      const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
 | 
			
		||||
 | 
			
		||||
  const { columns, filteredColumns, reloadColumns } = useTableColumn(config.columns);
 | 
			
		||||
      const recordsWithIndex = records.map((item, index) => {
 | 
			
		||||
        return {
 | 
			
		||||
          ...item,
 | 
			
		||||
          index: (current - 1) * size + index + 1
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
  const data: Ref<TableData[]> = ref([]);
 | 
			
		||||
      return {
 | 
			
		||||
        data: recordsWithIndex,
 | 
			
		||||
        pageNum: current,
 | 
			
		||||
        pageSize: size,
 | 
			
		||||
        total
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    getColumnChecks: cols => {
 | 
			
		||||
      const checks: NaiveUI.TableColumnCheck[] = [];
 | 
			
		||||
 | 
			
		||||
  const pagination = reactive({
 | 
			
		||||
      cols.forEach(column => {
 | 
			
		||||
        if (isTableColumnHasKey(column)) {
 | 
			
		||||
          checks.push({
 | 
			
		||||
            key: column.key as string,
 | 
			
		||||
            title: column.title as string,
 | 
			
		||||
            checked: true
 | 
			
		||||
          });
 | 
			
		||||
        } else if (column.type === 'selection') {
 | 
			
		||||
          checks.push({
 | 
			
		||||
            key: SELECTION_KEY,
 | 
			
		||||
            title: $t('common.check'),
 | 
			
		||||
            checked: true
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return checks;
 | 
			
		||||
    },
 | 
			
		||||
    getColumns: (cols, checks) => {
 | 
			
		||||
      const columnMap = new Map<string, TableColumn<GetTableData<A>>>();
 | 
			
		||||
 | 
			
		||||
      cols.forEach(column => {
 | 
			
		||||
        if (isTableColumnHasKey(column)) {
 | 
			
		||||
          columnMap.set(column.key as string, column);
 | 
			
		||||
        } else if (column.type === 'selection') {
 | 
			
		||||
          columnMap.set(SELECTION_KEY, column);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const filteredColumns = checks
 | 
			
		||||
        .filter(item => item.checked)
 | 
			
		||||
        .map(check => columnMap.get(check.key) as TableColumn<GetTableData<A>>);
 | 
			
		||||
 | 
			
		||||
      return filteredColumns;
 | 
			
		||||
    },
 | 
			
		||||
    onFetched: async transformed => {
 | 
			
		||||
      const { pageNum, pageSize, total } = transformed;
 | 
			
		||||
 | 
			
		||||
      updatePagination({
 | 
			
		||||
        page: pageNum,
 | 
			
		||||
        pageSize,
 | 
			
		||||
        itemCount: total
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    immediate
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const pagination: PaginationProps = reactive({
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 10,
 | 
			
		||||
    showSizePicker: true,
 | 
			
		||||
    pageSizes: [10, 15, 20, 25, 30],
 | 
			
		||||
    // Fix Naive Pagination's outdated API
 | 
			
		||||
    onUpdatePage: async (page: number) => {
 | 
			
		||||
      pagination.page = page;
 | 
			
		||||
 | 
			
		||||
      await onPaginationChanged?.(pagination);
 | 
			
		||||
      updateSearchParams({
 | 
			
		||||
        current: page,
 | 
			
		||||
        size: pagination.pageSize!
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      getData();
 | 
			
		||||
    },
 | 
			
		||||
    onUpdatePageSize: async (pageSize: number) => {
 | 
			
		||||
      pagination.pageSize = pageSize;
 | 
			
		||||
      pagination.page = 1;
 | 
			
		||||
 | 
			
		||||
      await onPaginationChanged?.(pagination);
 | 
			
		||||
    },
 | 
			
		||||
    ...config.pagination
 | 
			
		||||
  }) as PaginationProps;
 | 
			
		||||
      updateSearchParams({
 | 
			
		||||
        current: pagination.page,
 | 
			
		||||
        size: pageSize
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      getData();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // this is for mobile, if the system does not support mobile, you can use `pagination` directly
 | 
			
		||||
  const mobilePagination = computed(() => {
 | 
			
		||||
    const p: PaginationProps = {
 | 
			
		||||
      ...pagination,
 | 
			
		||||
      pageSlot: appStore.isMobile ? 3 : 9
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return p;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  function updatePagination(update: Partial<PaginationProps>) {
 | 
			
		||||
    Object.assign(pagination, update);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function getData() {
 | 
			
		||||
    startLoading();
 | 
			
		||||
 | 
			
		||||
    const formattedParams = formatSearchParams(searchParams);
 | 
			
		||||
 | 
			
		||||
    const response = await apiFn(formattedParams);
 | 
			
		||||
 | 
			
		||||
    const { data: tableData, pageNum, pageSize, total } = transformer(response as Awaited<ReturnType<Fn>>);
 | 
			
		||||
 | 
			
		||||
    data.value = tableData;
 | 
			
		||||
 | 
			
		||||
    setEmpty(tableData.length === 0);
 | 
			
		||||
    updatePagination({ page: pageNum, pageSize, itemCount: total });
 | 
			
		||||
    endLoading();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function formatSearchParams(params: Record<string, unknown>) {
 | 
			
		||||
    const formattedParams: Record<string, unknown> = {};
 | 
			
		||||
 | 
			
		||||
    Object.entries(params).forEach(([key, value]) => {
 | 
			
		||||
      if (value !== null && value !== undefined) {
 | 
			
		||||
        formattedParams[key] = value;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return formattedParams;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * update search params
 | 
			
		||||
   *
 | 
			
		||||
   * @param params
 | 
			
		||||
   */
 | 
			
		||||
  function updateSearchParams(params: Partial<Parameters<Fn>[0]>) {
 | 
			
		||||
    Object.assign(searchParams, params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** reset search params */
 | 
			
		||||
  function resetSearchParams() {
 | 
			
		||||
    Object.assign(searchParams, apiParams);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (immediate) {
 | 
			
		||||
    getData();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  scope.run(() => {
 | 
			
		||||
    watch(
 | 
			
		||||
      () => appStore.locale,
 | 
			
		||||
@@ -166,9 +159,10 @@ export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomCol
 | 
			
		||||
    empty,
 | 
			
		||||
    data,
 | 
			
		||||
    columns,
 | 
			
		||||
    filteredColumns,
 | 
			
		||||
    columnChecks,
 | 
			
		||||
    reloadColumns,
 | 
			
		||||
    pagination,
 | 
			
		||||
    mobilePagination,
 | 
			
		||||
    updatePagination,
 | 
			
		||||
    getData,
 | 
			
		||||
    searchParams,
 | 
			
		||||
@@ -177,62 +171,58 @@ export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomCol
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function useTableColumn<TableData extends BaseData, CustomColumnKey = never>(
 | 
			
		||||
  factory: () => TableColumn<TableData, CustomColumnKey>[]
 | 
			
		||||
) {
 | 
			
		||||
  const SELECTION_KEY = '__selection__';
 | 
			
		||||
export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void>) {
 | 
			
		||||
  const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
 | 
			
		||||
 | 
			
		||||
  const allColumns = ref(factory()) as Ref<TableColumn<TableData, CustomColumnKey>[]>;
 | 
			
		||||
  const operateType = ref<NaiveUI.TableOperateType>('add');
 | 
			
		||||
 | 
			
		||||
  const filteredColumns: Ref<FilteredColumn[]> = ref(getFilteredColumns(factory()));
 | 
			
		||||
 | 
			
		||||
  const columns = computed(() => getColumns());
 | 
			
		||||
 | 
			
		||||
  function reloadColumns() {
 | 
			
		||||
    allColumns.value = factory();
 | 
			
		||||
    filteredColumns.value = getFilteredColumns(factory());
 | 
			
		||||
  function handleAdd() {
 | 
			
		||||
    operateType.value = 'add';
 | 
			
		||||
    openDrawer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getFilteredColumns(aColumns: TableColumn<TableData, CustomColumnKey>[]) {
 | 
			
		||||
    const cols: FilteredColumn[] = [];
 | 
			
		||||
  /** the editing row data */
 | 
			
		||||
  const editingData: Ref<T | null> = ref(null);
 | 
			
		||||
 | 
			
		||||
    aColumns.forEach(column => {
 | 
			
		||||
      if (column.type === undefined) {
 | 
			
		||||
        cols.push({
 | 
			
		||||
          key: column.key as string,
 | 
			
		||||
          title: column.title as string,
 | 
			
		||||
          checked: true
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
  function handleEdit(id: T['id']) {
 | 
			
		||||
    operateType.value = 'edit';
 | 
			
		||||
    editingData.value = data.value.find(item => item.id === id) || null;
 | 
			
		||||
 | 
			
		||||
      if (column.type === 'selection') {
 | 
			
		||||
        cols.push({
 | 
			
		||||
          key: SELECTION_KEY,
 | 
			
		||||
          title: $t('common.check'),
 | 
			
		||||
          checked: true
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return cols;
 | 
			
		||||
    openDrawer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getColumns() {
 | 
			
		||||
    const cols = filteredColumns.value
 | 
			
		||||
      .filter(column => column.checked)
 | 
			
		||||
      .map(column => {
 | 
			
		||||
        if (column.key === SELECTION_KEY) {
 | 
			
		||||
          return allColumns.value.find(col => col.type === 'selection');
 | 
			
		||||
        }
 | 
			
		||||
        return allColumns.value.find(col => (col as DataTableBaseColumn).key === column.key);
 | 
			
		||||
      });
 | 
			
		||||
  /** the checked row keys of table */
 | 
			
		||||
  const checkedRowKeys = ref<string[]>([]);
 | 
			
		||||
 | 
			
		||||
    return cols as TableColumn<TableData, CustomColumnKey>[];
 | 
			
		||||
  /** the hook after the batch delete operation is completed */
 | 
			
		||||
  async function onBatchDeleted() {
 | 
			
		||||
    window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
    checkedRowKeys.value = [];
 | 
			
		||||
 | 
			
		||||
    await getData();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** the hook after the delete operation is completed */
 | 
			
		||||
  async function onDeleted() {
 | 
			
		||||
    window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
    await getData();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    columns,
 | 
			
		||||
    reloadColumns,
 | 
			
		||||
    filteredColumns
 | 
			
		||||
    drawerVisible,
 | 
			
		||||
    operateType,
 | 
			
		||||
    handleAdd,
 | 
			
		||||
    editingData,
 | 
			
		||||
    handleEdit,
 | 
			
		||||
    checkedRowKeys,
 | 
			
		||||
    onBatchDeleted,
 | 
			
		||||
    onDeleted,
 | 
			
		||||
    closeDrawer
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isTableColumnHasKey<T>(column: TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
 | 
			
		||||
  return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,10 +30,22 @@ export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** get menu list */
 | 
			
		||||
export function fetchGetMenuList() {
 | 
			
		||||
/**
 | 
			
		||||
 * get menu list
 | 
			
		||||
 *
 | 
			
		||||
 * @deprecated this will removed in next version 1.1.0
 | 
			
		||||
 */
 | 
			
		||||
export function fetchGetMenuListV1() {
 | 
			
		||||
  return request<Api.SystemManage.Menu[]>({
 | 
			
		||||
    url: '/systemManage/getMenuList',
 | 
			
		||||
    method: 'get'
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** get menu list */
 | 
			
		||||
export function fetchGetMenuList() {
 | 
			
		||||
  return request<Api.SystemManage.MenuList>({
 | 
			
		||||
    url: '/systemManage/getMenuList/v2',
 | 
			
		||||
    method: 'get'
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								src/typings/api.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/typings/api.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -219,5 +219,8 @@ declare namespace Api {
 | 
			
		||||
      /** children menu */
 | 
			
		||||
      children?: Menu[];
 | 
			
		||||
    }>;
 | 
			
		||||
 | 
			
		||||
    /** menu list */
 | 
			
		||||
    type MenuList = Common.PaginatingQueryRecord<Menu>;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/typings/naive-ui.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								src/typings/naive-ui.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -8,9 +8,15 @@ declare namespace NaiveUI {
 | 
			
		||||
  type TableColumnGroup<T> = import('naive-ui/es/data-table/src/interface').TableColumnGroup<T>;
 | 
			
		||||
  type PaginationProps = import('naive-ui').PaginationProps;
 | 
			
		||||
  type TableColumnCheck = import('@sa/hooks').TableColumnCheck;
 | 
			
		||||
  type TableDataWithIndex<T> = import('@sa/hooks').TableDataWithIndex<T>;
 | 
			
		||||
  type FlatResponseData<T> = import('@sa/axios').FlatResponseData<T>;
 | 
			
		||||
 | 
			
		||||
  type CustomColumnKey = 'index' | 'operate';
 | 
			
		||||
  /**
 | 
			
		||||
   * the custom column key
 | 
			
		||||
   *
 | 
			
		||||
   * if you want to add a custom column, you should add a key to this type
 | 
			
		||||
   */
 | 
			
		||||
  type CustomColumnKey = 'operate';
 | 
			
		||||
 | 
			
		||||
  type SetTableColumnKey<C, T> = Omit<C, 'key'> & { key: keyof T | CustomColumnKey };
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +41,7 @@ declare namespace NaiveUI {
 | 
			
		||||
  type GetTableData<A extends TableApiFn> = A extends TableApiFn<infer T> ? T : never;
 | 
			
		||||
 | 
			
		||||
  type NaiveTableConfig<A extends TableApiFn> = Pick<
 | 
			
		||||
    import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<GetTableData<A>>>,
 | 
			
		||||
    import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>,
 | 
			
		||||
    'apiFn' | 'apiParams' | 'columns' | 'immediate'
 | 
			
		||||
  >;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,37 +1,21 @@
 | 
			
		||||
<script setup lang="tsx">
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
 | 
			
		||||
import { useBoolean } from '@sa/hooks';
 | 
			
		||||
import { fetchGetMenuList } from '@/service/api';
 | 
			
		||||
import { useAppStore } from '@/store/modules/app';
 | 
			
		||||
import { useTable } from '@/hooks/common/table';
 | 
			
		||||
import { useTable, useTableOperate } from '@/hooks/common/table';
 | 
			
		||||
import { $t } from '@/locales';
 | 
			
		||||
import { yesOrNoRecord } from '@/constants/common';
 | 
			
		||||
import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
 | 
			
		||||
import SvgIcon from '@/components/custom/svg-icon.vue';
 | 
			
		||||
import MenuOperateDrawer, { type OperateType } from './modules/menu-operate-drawer.vue';
 | 
			
		||||
import MenuOperateDrawer from './modules/menu-operate-drawer.vue';
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore();
 | 
			
		||||
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
 | 
			
		||||
 | 
			
		||||
const wrapperRef = ref<HTMLElement | null>(null);
 | 
			
		||||
 | 
			
		||||
const { columns, filteredColumns, data, loading, pagination, getData } = useTable<
 | 
			
		||||
  Api.SystemManage.Menu,
 | 
			
		||||
  typeof fetchGetMenuList,
 | 
			
		||||
  'index' | 'operate'
 | 
			
		||||
>({
 | 
			
		||||
const { columns, columnChecks, data, loading, pagination, getData } = useTable({
 | 
			
		||||
  apiFn: fetchGetMenuList,
 | 
			
		||||
  transformer: res => {
 | 
			
		||||
    const menus = res.data || [];
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      data: menus,
 | 
			
		||||
      pageNum: 1,
 | 
			
		||||
      pageSize: 10,
 | 
			
		||||
      total: menus.length
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  columns: () => [
 | 
			
		||||
    {
 | 
			
		||||
      type: 'selection',
 | 
			
		||||
@@ -163,7 +147,7 @@ const { columns, filteredColumns, data, loading, pagination, getData } = useTabl
 | 
			
		||||
              {$t('page.manage.menu.addChildMenu')}
 | 
			
		||||
            </NButton>
 | 
			
		||||
          )}
 | 
			
		||||
          <NButton type="primary" ghost size="small" onClick={() => handleEdit(row.id)}>
 | 
			
		||||
          <NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
 | 
			
		||||
            {$t('common.edit')}
 | 
			
		||||
          </NButton>
 | 
			
		||||
          <NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
 | 
			
		||||
@@ -182,50 +166,40 @@ const { columns, filteredColumns, data, loading, pagination, getData } = useTabl
 | 
			
		||||
  ]
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const operateType = ref<OperateType>('add');
 | 
			
		||||
 | 
			
		||||
function handleAdd() {
 | 
			
		||||
  operateType.value = 'add';
 | 
			
		||||
  openDrawer();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const checkedRowKeys = ref<string[]>([]);
 | 
			
		||||
const {
 | 
			
		||||
  drawerVisible,
 | 
			
		||||
  operateType,
 | 
			
		||||
  editingData,
 | 
			
		||||
  handleAdd,
 | 
			
		||||
  handleEdit,
 | 
			
		||||
  checkedRowKeys,
 | 
			
		||||
  onBatchDeleted,
 | 
			
		||||
  onDeleted
 | 
			
		||||
  // closeDrawer
 | 
			
		||||
} = useTableOperate(data, getData);
 | 
			
		||||
 | 
			
		||||
async function handleBatchDelete() {
 | 
			
		||||
  // request
 | 
			
		||||
  console.log(checkedRowKeys.value);
 | 
			
		||||
  window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
  checkedRowKeys.value = [];
 | 
			
		||||
  onBatchDeleted();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  getData();
 | 
			
		||||
function handleDelete(id: number) {
 | 
			
		||||
  // request
 | 
			
		||||
  console.log(id);
 | 
			
		||||
 | 
			
		||||
  onDeleted();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleAddChildMenu(id: number) {
 | 
			
		||||
  console.log('id: ', id);
 | 
			
		||||
  operateType.value = 'add';
 | 
			
		||||
  openDrawer();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** the editing row data */
 | 
			
		||||
const editingData = ref<Api.SystemManage.Menu | null>(null);
 | 
			
		||||
 | 
			
		||||
function handleEdit(id: number) {
 | 
			
		||||
  operateType.value = 'edit';
 | 
			
		||||
  editingData.value = data.value.find(item => item.id === id) || null;
 | 
			
		||||
  openDrawer();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleDelete(id: number) {
 | 
			
		||||
  // request
 | 
			
		||||
  console.log(id);
 | 
			
		||||
  window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
  getData();
 | 
			
		||||
  handleAdd();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getRowKey(row: Api.SystemManage.Menu) {
 | 
			
		||||
  return row.id;
 | 
			
		||||
function edit(id: number) {
 | 
			
		||||
  handleEdit(id);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@@ -234,7 +208,7 @@ function getRowKey(row: Api.SystemManage.Menu) {
 | 
			
		||||
    <NCard :title="$t('page.manage.menu.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
 | 
			
		||||
      <template #header-extra>
 | 
			
		||||
        <TableHeaderOperation
 | 
			
		||||
          v-model:columns="filteredColumns"
 | 
			
		||||
          v-model:columns="columnChecks"
 | 
			
		||||
          :disabled-delete="checkedRowKeys.length === 0"
 | 
			
		||||
          :loading="loading"
 | 
			
		||||
          @add="handleAdd"
 | 
			
		||||
@@ -250,7 +224,7 @@ function getRowKey(row: Api.SystemManage.Menu) {
 | 
			
		||||
        :flex-height="!appStore.isMobile"
 | 
			
		||||
        :scroll-x="1088"
 | 
			
		||||
        :loading="loading"
 | 
			
		||||
        :row-key="getRowKey"
 | 
			
		||||
        :row-key="row => row.id"
 | 
			
		||||
        remote
 | 
			
		||||
        :pagination="pagination"
 | 
			
		||||
        class="sm:h-full"
 | 
			
		||||
 
 | 
			
		||||
@@ -11,17 +11,9 @@ defineOptions({
 | 
			
		||||
  name: 'MenuOperateDrawer'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the type of operation
 | 
			
		||||
 *
 | 
			
		||||
 * - add: add user
 | 
			
		||||
 * - edit: edit user
 | 
			
		||||
 */
 | 
			
		||||
export type OperateType = 'add' | 'edit';
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  /** the type of operation */
 | 
			
		||||
  operateType: OperateType;
 | 
			
		||||
  operateType: NaiveUI.TableOperateType;
 | 
			
		||||
  /** the edit row data */
 | 
			
		||||
  rowData?: Api.SystemManage.Menu | null;
 | 
			
		||||
}
 | 
			
		||||
@@ -42,7 +34,7 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
 | 
			
		||||
const { defaultRequiredRule } = useFormRules();
 | 
			
		||||
 | 
			
		||||
const title = computed(() => {
 | 
			
		||||
  const titles: Record<OperateType, string> = {
 | 
			
		||||
  const titles: Record<NaiveUI.TableOperateType, string> = {
 | 
			
		||||
    add: $t('page.manage.menu.addMenu'),
 | 
			
		||||
    edit: $t('page.manage.menu.editMenu')
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,16 @@
 | 
			
		||||
<script setup lang="tsx">
 | 
			
		||||
import { computed, ref } from 'vue';
 | 
			
		||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
 | 
			
		||||
import type { PaginationProps } from 'naive-ui';
 | 
			
		||||
import { useBoolean } from '@sa/hooks';
 | 
			
		||||
import { fetchGetRoleList } from '@/service/api';
 | 
			
		||||
import { useAppStore } from '@/store/modules/app';
 | 
			
		||||
import { useTable } from '@/hooks/common/table';
 | 
			
		||||
import { useTable, useTableOperate } from '@/hooks/common/table';
 | 
			
		||||
import { $t } from '@/locales';
 | 
			
		||||
import { enableStatusRecord } from '@/constants/business';
 | 
			
		||||
import RoleOperateDrawer, { type OperateType } from './modules/role-operate-drawer.vue';
 | 
			
		||||
import RoleOperateDrawer from './modules/role-operate-drawer.vue';
 | 
			
		||||
import RoleSearch from './modules/role-search.vue';
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore();
 | 
			
		||||
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  columns,
 | 
			
		||||
  filteredColumns,
 | 
			
		||||
  data,
 | 
			
		||||
  loading,
 | 
			
		||||
  pagination,
 | 
			
		||||
  getData,
 | 
			
		||||
  searchParams,
 | 
			
		||||
  updateSearchParams,
 | 
			
		||||
  resetSearchParams
 | 
			
		||||
} = useTable<Api.SystemManage.Role, typeof fetchGetRoleList, 'index' | 'operate'>({
 | 
			
		||||
const { columns, columnChecks, data, loading, getData, mobilePagination, searchParams, resetSearchParams } = useTable({
 | 
			
		||||
  apiFn: fetchGetRoleList,
 | 
			
		||||
  apiParams: {
 | 
			
		||||
    current: 1,
 | 
			
		||||
@@ -35,26 +21,6 @@ const {
 | 
			
		||||
    roleName: null,
 | 
			
		||||
    roleCode: null
 | 
			
		||||
  },
 | 
			
		||||
  transformer: res => {
 | 
			
		||||
    const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      data: records,
 | 
			
		||||
      pageNum: current,
 | 
			
		||||
      pageSize: size,
 | 
			
		||||
      total
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  onPaginationChanged(pg) {
 | 
			
		||||
    const { page, pageSize } = pg;
 | 
			
		||||
 | 
			
		||||
    updateSearchParams({
 | 
			
		||||
      current: page,
 | 
			
		||||
      size: pageSize
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    getData();
 | 
			
		||||
  },
 | 
			
		||||
  columns: () => [
 | 
			
		||||
    {
 | 
			
		||||
      type: 'selection',
 | 
			
		||||
@@ -64,7 +30,6 @@ const {
 | 
			
		||||
    {
 | 
			
		||||
      key: 'index',
 | 
			
		||||
      title: $t('common.index'),
 | 
			
		||||
      render: (_, index): string => getIndex(index),
 | 
			
		||||
      width: 64,
 | 
			
		||||
      align: 'center'
 | 
			
		||||
    },
 | 
			
		||||
@@ -112,7 +77,7 @@ const {
 | 
			
		||||
      width: 130,
 | 
			
		||||
      render: row => (
 | 
			
		||||
        <div class="flex-center gap-8px">
 | 
			
		||||
          <NButton type="primary" ghost size="small" onClick={() => handleEdit(row.id)}>
 | 
			
		||||
          <NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
 | 
			
		||||
            {$t('common.edit')}
 | 
			
		||||
          </NButton>
 | 
			
		||||
          <NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
 | 
			
		||||
@@ -131,60 +96,34 @@ const {
 | 
			
		||||
  ]
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
 | 
			
		||||
const mobilePagination = computed(() => {
 | 
			
		||||
  const p: PaginationProps = {
 | 
			
		||||
    ...pagination,
 | 
			
		||||
    pageSlot: appStore.isMobile ? 3 : 9
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return p;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const operateType = ref<OperateType>('add');
 | 
			
		||||
 | 
			
		||||
function handleAdd() {
 | 
			
		||||
  operateType.value = 'add';
 | 
			
		||||
  openDrawer();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const checkedRowKeys = ref<string[]>([]);
 | 
			
		||||
const {
 | 
			
		||||
  drawerVisible,
 | 
			
		||||
  operateType,
 | 
			
		||||
  editingData,
 | 
			
		||||
  handleAdd,
 | 
			
		||||
  handleEdit,
 | 
			
		||||
  checkedRowKeys,
 | 
			
		||||
  onBatchDeleted,
 | 
			
		||||
  onDeleted
 | 
			
		||||
  // closeDrawer
 | 
			
		||||
} = useTableOperate(data, getData);
 | 
			
		||||
 | 
			
		||||
async function handleBatchDelete() {
 | 
			
		||||
  // request
 | 
			
		||||
  console.log(checkedRowKeys.value);
 | 
			
		||||
  window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
  checkedRowKeys.value = [];
 | 
			
		||||
 | 
			
		||||
  getData();
 | 
			
		||||
  onBatchDeleted();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** the editing row data */
 | 
			
		||||
const editingData = ref<Api.SystemManage.Role | null>(null);
 | 
			
		||||
 | 
			
		||||
function handleEdit(id: number) {
 | 
			
		||||
  operateType.value = 'edit';
 | 
			
		||||
  editingData.value = data.value.find(item => item.id === id) || null;
 | 
			
		||||
  openDrawer();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleDelete(id: number) {
 | 
			
		||||
function handleDelete(id: number) {
 | 
			
		||||
  // request
 | 
			
		||||
  console.log(id);
 | 
			
		||||
  window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
  getData();
 | 
			
		||||
  onDeleted();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getIndex(index: number) {
 | 
			
		||||
  const { page = 0, pageSize = 10 } = pagination;
 | 
			
		||||
 | 
			
		||||
  return String((page - 1) * pageSize + index + 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getRowKey(row: Api.SystemManage.User) {
 | 
			
		||||
  return row.id;
 | 
			
		||||
function edit(id: number) {
 | 
			
		||||
  handleEdit(id);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@@ -194,7 +133,7 @@ function getRowKey(row: Api.SystemManage.User) {
 | 
			
		||||
    <NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
 | 
			
		||||
      <template #header-extra>
 | 
			
		||||
        <TableHeaderOperation
 | 
			
		||||
          v-model:columns="filteredColumns"
 | 
			
		||||
          v-model:columns="columnChecks"
 | 
			
		||||
          :disabled-delete="checkedRowKeys.length === 0"
 | 
			
		||||
          :loading="loading"
 | 
			
		||||
          @add="handleAdd"
 | 
			
		||||
@@ -211,7 +150,7 @@ function getRowKey(row: Api.SystemManage.User) {
 | 
			
		||||
        :scroll-x="702"
 | 
			
		||||
        :loading="loading"
 | 
			
		||||
        remote
 | 
			
		||||
        :row-key="getRowKey"
 | 
			
		||||
        :row-key="row => row.id"
 | 
			
		||||
        :pagination="mobilePagination"
 | 
			
		||||
        class="sm:h-full"
 | 
			
		||||
      />
 | 
			
		||||
 
 | 
			
		||||
@@ -8,17 +8,9 @@ defineOptions({
 | 
			
		||||
  name: 'RoleOperateDrawer'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the type of operation
 | 
			
		||||
 *
 | 
			
		||||
 * - add: add role
 | 
			
		||||
 * - edit: edit role
 | 
			
		||||
 */
 | 
			
		||||
export type OperateType = 'add' | 'edit';
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  /** the type of operation */
 | 
			
		||||
  operateType: OperateType;
 | 
			
		||||
  operateType: NaiveUI.TableOperateType;
 | 
			
		||||
  /** the edit row data */
 | 
			
		||||
  rowData?: Api.SystemManage.Role | null;
 | 
			
		||||
}
 | 
			
		||||
@@ -39,7 +31,7 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
 | 
			
		||||
const { defaultRequiredRule } = useFormRules();
 | 
			
		||||
 | 
			
		||||
const title = computed(() => {
 | 
			
		||||
  const titles: Record<OperateType, string> = {
 | 
			
		||||
  const titles: Record<NaiveUI.TableOperateType, string> = {
 | 
			
		||||
    add: $t('page.manage.role.addRole'),
 | 
			
		||||
    edit: $t('page.manage.role.editRole')
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,16 @@
 | 
			
		||||
<script setup lang="tsx">
 | 
			
		||||
import { computed, ref } from 'vue';
 | 
			
		||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
 | 
			
		||||
import type { PaginationProps } from 'naive-ui';
 | 
			
		||||
import { useBoolean } from '@sa/hooks';
 | 
			
		||||
import { fetchGetUserList } from '@/service/api';
 | 
			
		||||
import { useAppStore } from '@/store/modules/app';
 | 
			
		||||
import { useTable } from '@/hooks/common/table';
 | 
			
		||||
import { $t } from '@/locales';
 | 
			
		||||
import { useAppStore } from '@/store/modules/app';
 | 
			
		||||
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
 | 
			
		||||
import UserOperateDrawer, { type OperateType } from './modules/user-operate-drawer.vue';
 | 
			
		||||
import { useTable, useTableOperate } from '@/hooks/common/table';
 | 
			
		||||
import UserOperateDrawer from './modules/user-operate-drawer.vue';
 | 
			
		||||
import UserSearch from './modules/user-search.vue';
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore();
 | 
			
		||||
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  columns,
 | 
			
		||||
  filteredColumns,
 | 
			
		||||
  data,
 | 
			
		||||
  loading,
 | 
			
		||||
  pagination,
 | 
			
		||||
  getData,
 | 
			
		||||
  searchParams,
 | 
			
		||||
  updateSearchParams,
 | 
			
		||||
  resetSearchParams
 | 
			
		||||
} = useTable<Api.SystemManage.User, typeof fetchGetUserList, 'index' | 'operate'>({
 | 
			
		||||
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
 | 
			
		||||
  apiFn: fetchGetUserList,
 | 
			
		||||
  apiParams: {
 | 
			
		||||
    current: 1,
 | 
			
		||||
@@ -38,26 +24,6 @@ const {
 | 
			
		||||
    userPhone: null,
 | 
			
		||||
    userEmail: null
 | 
			
		||||
  },
 | 
			
		||||
  transformer: res => {
 | 
			
		||||
    const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      data: records,
 | 
			
		||||
      pageNum: current,
 | 
			
		||||
      pageSize: size,
 | 
			
		||||
      total
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  onPaginationChanged(pg) {
 | 
			
		||||
    const { page, pageSize } = pg;
 | 
			
		||||
 | 
			
		||||
    updateSearchParams({
 | 
			
		||||
      current: page,
 | 
			
		||||
      size: pageSize
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    getData();
 | 
			
		||||
  },
 | 
			
		||||
  columns: () => [
 | 
			
		||||
    {
 | 
			
		||||
      type: 'selection',
 | 
			
		||||
@@ -67,7 +33,6 @@ const {
 | 
			
		||||
    {
 | 
			
		||||
      key: 'index',
 | 
			
		||||
      title: $t('common.index'),
 | 
			
		||||
      render: (_, index): string => getIndex(index),
 | 
			
		||||
      align: 'center',
 | 
			
		||||
      width: 64
 | 
			
		||||
    },
 | 
			
		||||
@@ -142,7 +107,7 @@ const {
 | 
			
		||||
      width: 130,
 | 
			
		||||
      render: row => (
 | 
			
		||||
        <div class="flex-center gap-8px">
 | 
			
		||||
          <NButton type="primary" ghost size="small" onClick={() => handleEdit(row.id)}>
 | 
			
		||||
          <NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
 | 
			
		||||
            {$t('common.edit')}
 | 
			
		||||
          </NButton>
 | 
			
		||||
          <NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
 | 
			
		||||
@@ -161,60 +126,34 @@ const {
 | 
			
		||||
  ]
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
 | 
			
		||||
const mobilePagination = computed(() => {
 | 
			
		||||
  const p: PaginationProps = {
 | 
			
		||||
    ...pagination,
 | 
			
		||||
    pageSlot: appStore.isMobile ? 3 : 9
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return p;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const operateType = ref<OperateType>('add');
 | 
			
		||||
 | 
			
		||||
function handleAdd() {
 | 
			
		||||
  operateType.value = 'add';
 | 
			
		||||
  openDrawer();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const checkedRowKeys = ref<string[]>([]);
 | 
			
		||||
const {
 | 
			
		||||
  drawerVisible,
 | 
			
		||||
  operateType,
 | 
			
		||||
  editingData,
 | 
			
		||||
  handleAdd,
 | 
			
		||||
  handleEdit,
 | 
			
		||||
  checkedRowKeys,
 | 
			
		||||
  onBatchDeleted,
 | 
			
		||||
  onDeleted
 | 
			
		||||
  // closeDrawer
 | 
			
		||||
} = useTableOperate(data, getData);
 | 
			
		||||
 | 
			
		||||
async function handleBatchDelete() {
 | 
			
		||||
  // request
 | 
			
		||||
  console.log(checkedRowKeys.value);
 | 
			
		||||
  window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
  checkedRowKeys.value = [];
 | 
			
		||||
 | 
			
		||||
  getData();
 | 
			
		||||
  onBatchDeleted();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** the editing row data */
 | 
			
		||||
const editingData = ref<Api.SystemManage.User | null>(null);
 | 
			
		||||
 | 
			
		||||
function handleEdit(id: number) {
 | 
			
		||||
  operateType.value = 'edit';
 | 
			
		||||
  editingData.value = data.value.find(item => item.id === id) || null;
 | 
			
		||||
  openDrawer();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleDelete(id: number) {
 | 
			
		||||
function handleDelete(id: number) {
 | 
			
		||||
  // request
 | 
			
		||||
  console.log(id);
 | 
			
		||||
  window.$message?.success($t('common.deleteSuccess'));
 | 
			
		||||
 | 
			
		||||
  getData();
 | 
			
		||||
  onDeleted();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getIndex(index: number) {
 | 
			
		||||
  const { page = 0, pageSize = 10 } = pagination;
 | 
			
		||||
 | 
			
		||||
  return String((page - 1) * pageSize + index + 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getRowKey(row: Api.SystemManage.User) {
 | 
			
		||||
  return row.id;
 | 
			
		||||
function edit(id: number) {
 | 
			
		||||
  handleEdit(id);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@@ -224,7 +163,7 @@ function getRowKey(row: Api.SystemManage.User) {
 | 
			
		||||
    <NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
 | 
			
		||||
      <template #header-extra>
 | 
			
		||||
        <TableHeaderOperation
 | 
			
		||||
          v-model:columns="filteredColumns"
 | 
			
		||||
          v-model:columns="columnChecks"
 | 
			
		||||
          :disabled-delete="checkedRowKeys.length === 0"
 | 
			
		||||
          :loading="loading"
 | 
			
		||||
          @add="handleAdd"
 | 
			
		||||
@@ -241,7 +180,7 @@ function getRowKey(row: Api.SystemManage.User) {
 | 
			
		||||
        :scroll-x="962"
 | 
			
		||||
        :loading="loading"
 | 
			
		||||
        remote
 | 
			
		||||
        :row-key="getRowKey"
 | 
			
		||||
        :row-key="row => row.id"
 | 
			
		||||
        :pagination="mobilePagination"
 | 
			
		||||
        class="sm:h-full"
 | 
			
		||||
      />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,190 +0,0 @@
 | 
			
		||||
<script setup lang="tsx">
 | 
			
		||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
 | 
			
		||||
import { fetchGetUserList } from '@/service/api';
 | 
			
		||||
import { $t } from '@/locales';
 | 
			
		||||
import { useAppStore } from '@/store/modules/app';
 | 
			
		||||
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
 | 
			
		||||
import { getNaiveTableIndex, useNaiveTable, useNaiveTableOperate } from '@/hooks/common/naive-table';
 | 
			
		||||
import UserOperateDrawer from './modules/user-operate-drawer.vue';
 | 
			
		||||
import UserSearch from './modules/user-search.vue';
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore();
 | 
			
		||||
 | 
			
		||||
const { columns, columnChecks, data, getData, loading, pagination, mobilePagination, searchParams, resetSearchParams } =
 | 
			
		||||
  useNaiveTable({
 | 
			
		||||
    apiFn: fetchGetUserList,
 | 
			
		||||
    apiParams: {
 | 
			
		||||
      current: 1,
 | 
			
		||||
      size: 10,
 | 
			
		||||
      // if you want to use the searchParams in Form, you need to define the following properties, and the value is null
 | 
			
		||||
      // the value can not be undefined, otherwise the property in Form will not be reactive
 | 
			
		||||
      status: null,
 | 
			
		||||
      userName: null,
 | 
			
		||||
      userGender: null,
 | 
			
		||||
      nickName: null,
 | 
			
		||||
      userPhone: null,
 | 
			
		||||
      userEmail: null
 | 
			
		||||
    },
 | 
			
		||||
    columns: () => [
 | 
			
		||||
      {
 | 
			
		||||
        type: 'selection',
 | 
			
		||||
        align: 'center',
 | 
			
		||||
        width: 48
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        key: 'index',
 | 
			
		||||
        title: $t('common.index'),
 | 
			
		||||
        render: (_, index): number => getNaiveTableIndex(pagination, index),
 | 
			
		||||
        align: 'center',
 | 
			
		||||
        width: 64
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        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: '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]);
 | 
			
		||||
 | 
			
		||||
          return <NTag type={tagMap[row.status]}>{label}</NTag>;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        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>
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
 | 
			
		||||
  useNaiveTableOperate(data, getData);
 | 
			
		||||
 | 
			
		||||
async function handleBatchDelete() {
 | 
			
		||||
  // request
 | 
			
		||||
  console.log(checkedRowKeys.value);
 | 
			
		||||
 | 
			
		||||
  onBatchDeleted();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleDelete(id: number) {
 | 
			
		||||
  // request
 | 
			
		||||
  console.log(id);
 | 
			
		||||
 | 
			
		||||
  onDeleted();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function edit(id: number) {
 | 
			
		||||
  handleEdit(id);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="min-h-500px flex-vertical-stretch gap-16px overflow-hidden <sm:overflow-auto">
 | 
			
		||||
    <UserSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
 | 
			
		||||
    <NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
 | 
			
		||||
      <template #header-extra>
 | 
			
		||||
        <TableHeaderOperation
 | 
			
		||||
          v-model:columns="columnChecks"
 | 
			
		||||
          :disabled-delete="checkedRowKeys.length === 0"
 | 
			
		||||
          :loading="loading"
 | 
			
		||||
          @add="handleAdd"
 | 
			
		||||
          @delete="handleBatchDelete"
 | 
			
		||||
          @refresh="getData"
 | 
			
		||||
        />
 | 
			
		||||
      </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="getData"
 | 
			
		||||
      />
 | 
			
		||||
    </NCard>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
@@ -9,17 +9,9 @@ defineOptions({
 | 
			
		||||
  name: 'UserOperateDrawer'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * the type of operation
 | 
			
		||||
 *
 | 
			
		||||
 * - add: add user
 | 
			
		||||
 * - edit: edit user
 | 
			
		||||
 */
 | 
			
		||||
export type OperateType = 'add' | 'edit';
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  /** the type of operation */
 | 
			
		||||
  operateType: OperateType;
 | 
			
		||||
  operateType: NaiveUI.TableOperateType;
 | 
			
		||||
  /** the edit row data */
 | 
			
		||||
  rowData?: Api.SystemManage.User | null;
 | 
			
		||||
}
 | 
			
		||||
@@ -40,7 +32,7 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
 | 
			
		||||
const { defaultRequiredRule } = useFormRules();
 | 
			
		||||
 | 
			
		||||
const title = computed(() => {
 | 
			
		||||
  const titles: Record<OperateType, string> = {
 | 
			
		||||
  const titles: Record<NaiveUI.TableOperateType, string> = {
 | 
			
		||||
    add: $t('page.manage.user.addUser'),
 | 
			
		||||
    edit: $t('page.manage.user.editUser')
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user