mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-30 23:26:41 +08:00
Merge branch 'soybeanjs:main' into Jin
This commit is contained in:
commit
9254d22665
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -13,5 +13,6 @@
|
|||||||
"i18n-ally.keystyle": "nested",
|
"i18n-ally.keystyle": "nested",
|
||||||
"i18n-ally.localesPaths": ["src/locales/langs"],
|
"i18n-ally.localesPaths": ["src/locales/langs"],
|
||||||
"prettier.enable": false,
|
"prettier.enable": false,
|
||||||
"unocss.root": ["./"]
|
"unocss.root": ["./"],
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
}
|
}
|
||||||
|
12
package.json
12
package.json
@ -65,12 +65,12 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@elegant-router/vue": "0.3.6",
|
"@elegant-router/vue": "0.3.6",
|
||||||
"@iconify/json": "2.2.192",
|
"@iconify/json": "2.2.194",
|
||||||
"@sa/scripts": "workspace:*",
|
"@sa/scripts": "workspace:*",
|
||||||
"@sa/uno-preset": "workspace:*",
|
"@sa/uno-preset": "workspace:*",
|
||||||
"@soybeanjs/eslint-config": "1.2.5",
|
"@soybeanjs/eslint-config": "1.2.5",
|
||||||
"@types/lodash-es": "4.17.12",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@types/node": "20.11.28",
|
"@types/node": "20.11.30",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@unocss/eslint-config": "0.58.6",
|
"@unocss/eslint-config": "0.58.6",
|
||||||
"@unocss/preset-icons": "0.58.6",
|
"@unocss/preset-icons": "0.58.6",
|
||||||
@ -86,15 +86,15 @@
|
|||||||
"sass": "1.72.0",
|
"sass": "1.72.0",
|
||||||
"simple-git-hooks": "2.11.0",
|
"simple-git-hooks": "2.11.0",
|
||||||
"tsx": "4.7.1",
|
"tsx": "4.7.1",
|
||||||
"typescript": "5.4.2",
|
"typescript": "5.4.3",
|
||||||
"unplugin-icons": "0.18.5",
|
"unplugin-icons": "0.18.5",
|
||||||
"unplugin-vue-components": "0.26.0",
|
"unplugin-vue-components": "0.26.0",
|
||||||
"vite": "5.1.6",
|
"vite": "5.2.2",
|
||||||
"vite-plugin-progress": "0.0.7",
|
"vite-plugin-progress": "0.0.7",
|
||||||
"vite-plugin-svg-icons": "2.0.1",
|
"vite-plugin-svg-icons": "2.0.1",
|
||||||
"vite-plugin-vue-devtools": "7.0.18",
|
"vite-plugin-vue-devtools": "7.0.20",
|
||||||
"vue-eslint-parser": "9.4.2",
|
"vue-eslint-parser": "9.4.2",
|
||||||
"vue-tsc": "2.0.6"
|
"vue-tsc": "2.0.7"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
"commit-msg": "pnpm sa git-commit-verify",
|
"commit-msg": "pnpm sa git-commit-verify",
|
||||||
|
@ -12,10 +12,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sa/utils": "workspace:*",
|
"@sa/utils": "workspace:*",
|
||||||
"axios": "1.6.8",
|
"axios": "1.6.8",
|
||||||
"axios-retry": "4.0.0",
|
"axios-retry": "4.1.0",
|
||||||
"qs": "6.12.0"
|
"qs": "6.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/qs": "6.9.12"
|
"@types/qs": "6.9.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,8 @@ import useBoolean from './use-boolean';
|
|||||||
import useLoading from './use-loading';
|
import useLoading from './use-loading';
|
||||||
import useContext from './use-context';
|
import useContext from './use-context';
|
||||||
import useSvgIconRender from './use-svg-icon-render';
|
import useSvgIconRender from './use-svg-icon-render';
|
||||||
|
import useHookTable from './use-table';
|
||||||
|
|
||||||
export { useBoolean, useLoading, useContext, useSvgIconRender };
|
export { useBoolean, useLoading, useContext, useSvgIconRender, useHookTable };
|
||||||
|
|
||||||
|
export * from './use-table';
|
||||||
|
151
packages/hooks/src/use-table.ts
Normal file
151
packages/hooks/src/use-table.ts
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { computed, reactive, ref } from 'vue';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
import useBoolean from './use-boolean';
|
||||||
|
import useLoading from './use-loading';
|
||||||
|
|
||||||
|
export type MaybePromise<T> = T | Promise<T>;
|
||||||
|
|
||||||
|
export type ApiFn = (args: any) => Promise<unknown>;
|
||||||
|
|
||||||
|
export type TableColumnCheck = {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
checked: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TableDataWithIndex<T> = T & { index: number };
|
||||||
|
|
||||||
|
export type TransformedData<T> = {
|
||||||
|
data: TableDataWithIndex<T>[];
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Transformer<T, Response> = (response: Response) => TransformedData<T>;
|
||||||
|
|
||||||
|
export type TableConfig<A extends ApiFn, T, C> = {
|
||||||
|
/** api function to get table data */
|
||||||
|
apiFn: A;
|
||||||
|
/** api params */
|
||||||
|
apiParams?: Parameters<A>[0];
|
||||||
|
/** transform api response to table data */
|
||||||
|
transformer: Transformer<T, Awaited<ReturnType<A>>>;
|
||||||
|
/** columns factory */
|
||||||
|
columns: () => C[];
|
||||||
|
/**
|
||||||
|
* get column checks
|
||||||
|
*
|
||||||
|
* @param columns
|
||||||
|
*/
|
||||||
|
getColumnChecks: (columns: C[]) => TableColumnCheck[];
|
||||||
|
/**
|
||||||
|
* get columns
|
||||||
|
*
|
||||||
|
* @param columns
|
||||||
|
*/
|
||||||
|
getColumns: (columns: C[], checks: TableColumnCheck[]) => C[];
|
||||||
|
/**
|
||||||
|
* callback when response fetched
|
||||||
|
*
|
||||||
|
* @param transformed transformed data
|
||||||
|
*/
|
||||||
|
onFetched?: (transformed: TransformedData<T>) => MaybePromise<void>;
|
||||||
|
/**
|
||||||
|
* whether to get data immediately
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
immediate?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config;
|
||||||
|
|
||||||
|
const searchParams: NonNullable<Parameters<A>[0]> = reactive({ ...apiParams });
|
||||||
|
|
||||||
|
const allColumns = ref(config.columns()) as Ref<C[]>;
|
||||||
|
|
||||||
|
const data: Ref<T[]> = ref([]);
|
||||||
|
|
||||||
|
const columnChecks: Ref<TableColumnCheck[]> = ref(getColumnChecks(config.columns()));
|
||||||
|
|
||||||
|
const columns = computed(() => getColumns(allColumns.value, columnChecks.value));
|
||||||
|
|
||||||
|
function reloadColumns() {
|
||||||
|
allColumns.value = config.columns();
|
||||||
|
|
||||||
|
const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked]));
|
||||||
|
|
||||||
|
const defaultChecks = getColumnChecks(allColumns.value);
|
||||||
|
|
||||||
|
columnChecks.value = defaultChecks.map(col => ({
|
||||||
|
...col,
|
||||||
|
checked: checkMap.get(col.key) ?? col.checked
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getData() {
|
||||||
|
startLoading();
|
||||||
|
|
||||||
|
const formattedParams = formatSearchParams(searchParams);
|
||||||
|
|
||||||
|
const response = await apiFn(formattedParams);
|
||||||
|
|
||||||
|
const transformed = transformer(response as Awaited<ReturnType<A>>);
|
||||||
|
|
||||||
|
data.value = transformed.data;
|
||||||
|
|
||||||
|
setEmpty(transformed.data.length === 0);
|
||||||
|
|
||||||
|
await config.onFetched?.(transformed);
|
||||||
|
|
||||||
|
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<A>[0]>) {
|
||||||
|
Object.assign(searchParams, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** reset search params */
|
||||||
|
function resetSearchParams() {
|
||||||
|
Object.assign(searchParams, apiParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (immediate) {
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
empty,
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
columnChecks,
|
||||||
|
reloadColumns,
|
||||||
|
getData,
|
||||||
|
searchParams,
|
||||||
|
updateSearchParams,
|
||||||
|
resetSearchParams
|
||||||
|
};
|
||||||
|
}
|
878
pnpm-lock.yaml
878
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,12 @@
|
|||||||
<script setup lang="ts" generic="T extends Record<string, unknown>, K = never">
|
<script setup lang="ts" generic="T extends Record<string, unknown>, K = never">
|
||||||
import { VueDraggable } from 'vue-draggable-plus';
|
import { VueDraggable } from 'vue-draggable-plus';
|
||||||
import type { FilteredColumn } from '@/hooks/common/table';
|
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'TableColumnSetting'
|
name: 'TableColumnSetting'
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns = defineModel<FilteredColumn[]>('columns', {
|
const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
|
||||||
required: true
|
required: true
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FilteredColumn } from '@/hooks/common/table';
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'TableHeaderOperation'
|
name: 'TableHeaderOperation'
|
||||||
});
|
});
|
||||||
@ -21,7 +19,7 @@ interface Emits {
|
|||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
const columns = defineModel<FilteredColumn[]>('columns', {
|
const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
|
||||||
default: () => []
|
default: () => []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,151 +1,144 @@
|
|||||||
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';
|
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import type { DataTableBaseColumn, DataTableExpandColumn, DataTableSelectionColumn, PaginationProps } from 'naive-ui';
|
import type { PaginationProps } from 'naive-ui';
|
||||||
import type { TableColumnGroup } from 'naive-ui/es/data-table/src/interface';
|
import { useBoolean, useHookTable } from '@sa/hooks';
|
||||||
import { useBoolean, useLoading } from '@sa/hooks';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { $t } from '@/locales';
|
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 function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
|
||||||
|
|
||||||
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>
|
|
||||||
) {
|
|
||||||
const scope = effectScope();
|
const scope = effectScope();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
const { loading, startLoading, endLoading } = useLoading();
|
const { apiFn, apiParams, immediate } = config;
|
||||||
const { bool: empty, setBool: setEmpty } = useBoolean();
|
|
||||||
|
|
||||||
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,
|
page: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showSizePicker: true,
|
showSizePicker: true,
|
||||||
pageSizes: [10, 15, 20, 25, 30],
|
pageSizes: [10, 15, 20, 25, 30],
|
||||||
// Fix Naive Pagination's outdated API
|
|
||||||
onUpdatePage: async (page: number) => {
|
onUpdatePage: async (page: number) => {
|
||||||
pagination.page = page;
|
pagination.page = page;
|
||||||
|
|
||||||
await onPaginationChanged?.(pagination);
|
updateSearchParams({
|
||||||
|
current: page,
|
||||||
|
size: pagination.pageSize!
|
||||||
|
});
|
||||||
|
|
||||||
|
getData();
|
||||||
},
|
},
|
||||||
onUpdatePageSize: async (pageSize: number) => {
|
onUpdatePageSize: async (pageSize: number) => {
|
||||||
pagination.pageSize = pageSize;
|
pagination.pageSize = pageSize;
|
||||||
pagination.page = 1;
|
pagination.page = 1;
|
||||||
|
|
||||||
await onPaginationChanged?.(pagination);
|
updateSearchParams({
|
||||||
},
|
current: pagination.page,
|
||||||
...config.pagination
|
size: pageSize
|
||||||
}) as PaginationProps;
|
});
|
||||||
|
|
||||||
function updatePagination(update: Partial<PaginationProps>) {
|
getData();
|
||||||
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;
|
// 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;
|
||||||
* update search params
|
});
|
||||||
*
|
|
||||||
* @param params
|
|
||||||
*/
|
|
||||||
function updateSearchParams(params: Partial<Parameters<Fn>[0]>) {
|
|
||||||
Object.assign(searchParams, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** reset search params */
|
function updatePagination(update: Partial<PaginationProps>) {
|
||||||
function resetSearchParams() {
|
Object.assign(pagination, update);
|
||||||
Object.assign(searchParams, apiParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (immediate) {
|
|
||||||
getData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.run(() => {
|
scope.run(() => {
|
||||||
@ -166,9 +159,10 @@ export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomCol
|
|||||||
empty,
|
empty,
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
filteredColumns,
|
columnChecks,
|
||||||
reloadColumns,
|
reloadColumns,
|
||||||
pagination,
|
pagination,
|
||||||
|
mobilePagination,
|
||||||
updatePagination,
|
updatePagination,
|
||||||
getData,
|
getData,
|
||||||
searchParams,
|
searchParams,
|
||||||
@ -177,62 +171,59 @@ export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomCol
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function useTableColumn<TableData extends BaseData, CustomColumnKey = never>(
|
export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void>) {
|
||||||
factory: () => TableColumn<TableData, CustomColumnKey>[]
|
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
|
||||||
) {
|
|
||||||
const SELECTION_KEY = '__selection__';
|
|
||||||
|
|
||||||
const allColumns = ref(factory()) as Ref<TableColumn<TableData, CustomColumnKey>[]>;
|
const operateType = ref<NaiveUI.TableOperateType>('add');
|
||||||
|
|
||||||
const filteredColumns: Ref<FilteredColumn[]> = ref(getFilteredColumns(factory()));
|
function handleAdd() {
|
||||||
|
operateType.value = 'add';
|
||||||
const columns = computed(() => getColumns());
|
openDrawer();
|
||||||
|
|
||||||
function reloadColumns() {
|
|
||||||
allColumns.value = factory();
|
|
||||||
filteredColumns.value = getFilteredColumns(factory());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilteredColumns(aColumns: TableColumn<TableData, CustomColumnKey>[]) {
|
/** the editing row data */
|
||||||
const cols: FilteredColumn[] = [];
|
const editingData: Ref<T | null> = ref(null);
|
||||||
|
|
||||||
aColumns.forEach(column => {
|
function handleEdit(id: T['id']) {
|
||||||
if (column.type === undefined) {
|
operateType.value = 'edit';
|
||||||
cols.push({
|
editingData.value = data.value.find(item => item.id === id) || null;
|
||||||
key: column.key as string,
|
|
||||||
title: column.title as string,
|
openDrawer();
|
||||||
checked: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (column.type === 'selection') {
|
/** the checked row keys of table */
|
||||||
cols.push({
|
const checkedRowKeys = ref<string[]>([]);
|
||||||
key: SELECTION_KEY,
|
|
||||||
title: $t('common.check'),
|
|
||||||
checked: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return cols;
|
/** the hook after the batch delete operation is completed */
|
||||||
|
async function onBatchDeleted() {
|
||||||
|
window.$message?.success($t('common.deleteSuccess'));
|
||||||
|
|
||||||
|
checkedRowKeys.value = [];
|
||||||
|
|
||||||
|
await getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColumns() {
|
/** the hook after the delete operation is completed */
|
||||||
const cols = filteredColumns.value
|
async function onDeleted() {
|
||||||
.filter(column => column.checked)
|
window.$message?.success($t('common.deleteSuccess'));
|
||||||
.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);
|
|
||||||
});
|
|
||||||
|
|
||||||
return cols as TableColumn<TableData, CustomColumnKey>[];
|
await getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns,
|
drawerVisible,
|
||||||
reloadColumns,
|
openDrawer,
|
||||||
filteredColumns
|
closeDrawer,
|
||||||
|
operateType,
|
||||||
|
handleAdd,
|
||||||
|
editingData,
|
||||||
|
handleEdit,
|
||||||
|
checkedRowKeys,
|
||||||
|
onBatchDeleted,
|
||||||
|
onDeleted
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTableColumnHasKey<T>(column: TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
|
||||||
|
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
|
||||||
|
}
|
||||||
|
@ -319,8 +319,9 @@ const local: App.I18n.Schema = {
|
|||||||
menuName: 'Menu Name',
|
menuName: 'Menu Name',
|
||||||
routeName: 'Route Name',
|
routeName: 'Route Name',
|
||||||
routePath: 'Route Path',
|
routePath: 'Route Path',
|
||||||
page: 'Page Component',
|
routeParams: 'Route Params',
|
||||||
layout: 'Layout Component',
|
layout: 'Layout Component',
|
||||||
|
page: 'Page Component',
|
||||||
i18nKey: 'I18n Key',
|
i18nKey: 'I18n Key',
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
localIcon: 'Local Icon',
|
localIcon: 'Local Icon',
|
||||||
|
@ -319,8 +319,9 @@ const local: App.I18n.Schema = {
|
|||||||
menuName: '菜单名称',
|
menuName: '菜单名称',
|
||||||
routeName: '路由名称',
|
routeName: '路由名称',
|
||||||
routePath: '路由路径',
|
routePath: '路由路径',
|
||||||
page: '页面组件',
|
routeParams: '路由参数',
|
||||||
layout: '布局',
|
layout: '布局',
|
||||||
|
page: '页面组件',
|
||||||
i18nKey: '国际化key',
|
i18nKey: '国际化key',
|
||||||
icon: '图标',
|
icon: '图标',
|
||||||
localIcon: '本地图标',
|
localIcon: '本地图标',
|
||||||
|
@ -30,10 +30,30 @@ 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[]>({
|
return request<Api.SystemManage.Menu[]>({
|
||||||
url: '/systemManage/getMenuList',
|
url: '/systemManage/getMenuList',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** get menu list */
|
||||||
|
export function fetchGetMenuList() {
|
||||||
|
return request<Api.SystemManage.MenuList>({
|
||||||
|
url: '/systemManage/getMenuList/v2',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** get all pages */
|
||||||
|
export function fetchGetAllPages() {
|
||||||
|
return request<string[]>({
|
||||||
|
url: '/systemManage/getAllPages',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -225,7 +225,8 @@ export function getNaiveTheme(colors: App.Theme.ThemeColor) {
|
|||||||
|
|
||||||
const theme: GlobalThemeOverrides = {
|
const theme: GlobalThemeOverrides = {
|
||||||
common: {
|
common: {
|
||||||
...getNaiveThemeColors(colors)
|
...getNaiveThemeColors(colors),
|
||||||
|
borderRadius: '6px'
|
||||||
},
|
},
|
||||||
LoadingBar: {
|
LoadingBar: {
|
||||||
colorLoading
|
colorLoading
|
||||||
|
7
src/typings/api.d.ts
vendored
7
src/typings/api.d.ts
vendored
@ -16,7 +16,7 @@ declare namespace Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** common params of paginating query list data */
|
/** common params of paginating query list data */
|
||||||
interface PaginatingQueryRecord<T extends NonNullable<unknown>> extends PaginatingCommonParams {
|
interface PaginatingQueryRecord<T = any> extends PaginatingCommonParams {
|
||||||
records: T[];
|
records: T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ declare namespace Api {
|
|||||||
type EnableStatus = '1' | '2';
|
type EnableStatus = '1' | '2';
|
||||||
|
|
||||||
/** common record */
|
/** common record */
|
||||||
type CommonRecord<T extends NonNullable<unknown>> = {
|
type CommonRecord<T = any> = {
|
||||||
/** record id */
|
/** record id */
|
||||||
id: number;
|
id: number;
|
||||||
/** record creator */
|
/** record creator */
|
||||||
@ -219,5 +219,8 @@ declare namespace Api {
|
|||||||
/** children menu */
|
/** children menu */
|
||||||
children?: Menu[];
|
children?: Menu[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
/** menu list */
|
||||||
|
type MenuList = Common.PaginatingQueryRecord<Menu>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
src/typings/app.d.ts
vendored
5
src/typings/app.d.ts
vendored
@ -502,8 +502,9 @@ declare namespace App {
|
|||||||
menuName: string;
|
menuName: string;
|
||||||
routeName: string;
|
routeName: string;
|
||||||
routePath: string;
|
routePath: string;
|
||||||
page: string;
|
routeParams: string;
|
||||||
layout: string;
|
layout: string;
|
||||||
|
page: string;
|
||||||
i18nKey: string;
|
i18nKey: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
localIcon: string;
|
localIcon: string;
|
||||||
@ -524,8 +525,8 @@ declare namespace App {
|
|||||||
menuName: string;
|
menuName: string;
|
||||||
routeName: string;
|
routeName: string;
|
||||||
routePath: string;
|
routePath: string;
|
||||||
page: string;
|
|
||||||
layout: string;
|
layout: string;
|
||||||
|
page: string;
|
||||||
i18nKey: string;
|
i18nKey: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
localIcon: string;
|
localIcon: string;
|
||||||
|
2
src/typings/common.d.ts
vendored
2
src/typings/common.d.ts
vendored
@ -14,7 +14,7 @@ declare namespace CommonType {
|
|||||||
* @property value: The option value
|
* @property value: The option value
|
||||||
* @property label: The option label
|
* @property label: The option label
|
||||||
*/
|
*/
|
||||||
type Option<K> = { value: K; label: string };
|
type Option<K = string> = { value: K; label: string };
|
||||||
|
|
||||||
type YesOrNo = 'Y' | 'N';
|
type YesOrNo = 'Y' | 'N';
|
||||||
|
|
||||||
|
43
src/typings/naive-ui.d.ts
vendored
43
src/typings/naive-ui.d.ts
vendored
@ -1,4 +1,47 @@
|
|||||||
declare namespace NaiveUI {
|
declare namespace NaiveUI {
|
||||||
type ThemeColor = 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning';
|
type ThemeColor = 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning';
|
||||||
type Align = 'stretch' | 'baseline' | 'start' | 'end' | 'center' | 'flex-end' | 'flex-start';
|
type Align = 'stretch' | 'baseline' | 'start' | 'end' | 'center' | 'flex-end' | 'flex-start';
|
||||||
|
|
||||||
|
type DataTableBaseColumn<T> = import('naive-ui').DataTableBaseColumn<T>;
|
||||||
|
type DataTableExpandColumn<T> = import('naive-ui').DataTableExpandColumn<T>;
|
||||||
|
type DataTableSelectionColumn<T> = import('naive-ui').DataTableSelectionColumn<T>;
|
||||||
|
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>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 };
|
||||||
|
|
||||||
|
type TableData = Api.Common.CommonRecord<object>;
|
||||||
|
|
||||||
|
type TableColumnWithKey<T> = SetTableColumnKey<DataTableBaseColumn<T>, T> | SetTableColumnKey<TableColumnGroup<T>, T>;
|
||||||
|
|
||||||
|
type TableColumn<T> = TableColumnWithKey<T> | DataTableSelectionColumn<T> | DataTableExpandColumn<T>;
|
||||||
|
|
||||||
|
type TableApiFn<T = any, R = Api.SystemManage.CommonSearchParams> = (
|
||||||
|
params: R
|
||||||
|
) => Promise<FlatResponseData<Api.Common.PaginatingQueryRecord<T>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the type of table operation
|
||||||
|
*
|
||||||
|
* - add: add table item
|
||||||
|
* - edit: edit table item
|
||||||
|
*/
|
||||||
|
type TableOperateType = 'add' | 'edit';
|
||||||
|
|
||||||
|
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<NaiveUI.TableDataWithIndex<GetTableData<A>>>>,
|
||||||
|
'apiFn' | 'apiParams' | 'columns' | 'immediate'
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
||||||
import { useBoolean } from '@sa/hooks';
|
import { useBoolean } from '@sa/hooks';
|
||||||
import { fetchGetMenuList } from '@/service/api';
|
import { fetchGetAllPages, fetchGetMenuList } from '@/service/api';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useTable } from '@/hooks/common/table';
|
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { yesOrNoRecord } from '@/constants/common';
|
import { yesOrNoRecord } from '@/constants/common';
|
||||||
import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
|
import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
|
||||||
@ -12,26 +13,13 @@ import SvgIcon from '@/components/custom/svg-icon.vue';
|
|||||||
import MenuOperateDrawer, { type OperateType } from './modules/menu-operate-drawer.vue';
|
import MenuOperateDrawer, { type OperateType } from './modules/menu-operate-drawer.vue';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
|
|
||||||
|
const { bool: drawerVisible, setTrue: openDrawer, setFalse: _closeDrawer } = useBoolean();
|
||||||
|
|
||||||
const wrapperRef = ref<HTMLElement | null>(null);
|
const wrapperRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const { columns, filteredColumns, data, loading, pagination, getData } = useTable<
|
const { columns, columnChecks, data, loading, pagination, getData } = useTable({
|
||||||
Api.SystemManage.Menu,
|
|
||||||
typeof fetchGetMenuList,
|
|
||||||
'index' | 'operate'
|
|
||||||
>({
|
|
||||||
apiFn: fetchGetMenuList,
|
apiFn: fetchGetMenuList,
|
||||||
transformer: res => {
|
|
||||||
const menus = res.data || [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: menus,
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: menus.length
|
|
||||||
};
|
|
||||||
},
|
|
||||||
columns: () => [
|
columns: () => [
|
||||||
{
|
{
|
||||||
type: 'selection',
|
type: 'selection',
|
||||||
@ -159,11 +147,11 @@ const { columns, filteredColumns, data, loading, pagination, getData } = useTabl
|
|||||||
render: row => (
|
render: row => (
|
||||||
<div class="flex-center justify-end gap-8px">
|
<div class="flex-center justify-end gap-8px">
|
||||||
{row.menuType === '1' && (
|
{row.menuType === '1' && (
|
||||||
<NButton type="primary" ghost size="small" onClick={() => handleAddChildMenu(row.id)}>
|
<NButton type="primary" ghost size="small" onClick={() => handleAddChildMenu(row)}>
|
||||||
{$t('page.manage.menu.addChildMenu')}
|
{$t('page.manage.menu.addChildMenu')}
|
||||||
</NButton>
|
</NButton>
|
||||||
)}
|
)}
|
||||||
<NButton type="primary" ghost size="small" onClick={() => handleEdit(row.id)}>
|
<NButton type="primary" ghost size="small" onClick={() => handleEdit(row)}>
|
||||||
{$t('common.edit')}
|
{$t('common.edit')}
|
||||||
</NButton>
|
</NButton>
|
||||||
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
|
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
|
||||||
@ -182,6 +170,8 @@ const { columns, filteredColumns, data, loading, pagination, getData } = useTabl
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, getData);
|
||||||
|
|
||||||
const operateType = ref<OperateType>('add');
|
const operateType = ref<OperateType>('add');
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
@ -189,44 +179,51 @@ function handleAdd() {
|
|||||||
openDrawer();
|
openDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkedRowKeys = ref<string[]>([]);
|
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
// request
|
// request
|
||||||
console.log(checkedRowKeys.value);
|
console.log(checkedRowKeys.value);
|
||||||
window.$message?.success($t('common.deleteSuccess'));
|
|
||||||
|
|
||||||
checkedRowKeys.value = [];
|
onBatchDeleted();
|
||||||
|
|
||||||
getData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddChildMenu(id: number) {
|
function handleDelete(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
|
// request
|
||||||
console.log(id);
|
console.log(id);
|
||||||
window.$message?.success($t('common.deleteSuccess'));
|
|
||||||
|
|
||||||
getData();
|
onDeleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRowKey(row: Api.SystemManage.Menu) {
|
/** the edit menu data or the parent menu data when adding a child menu */
|
||||||
return row.id;
|
const editingData: Ref<Api.SystemManage.Menu | null> = ref(null);
|
||||||
|
|
||||||
|
function handleEdit(item: Api.SystemManage.Menu) {
|
||||||
|
operateType.value = 'edit';
|
||||||
|
editingData.value = { ...item };
|
||||||
|
|
||||||
|
openDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleAddChildMenu(item: Api.SystemManage.Menu) {
|
||||||
|
operateType.value = 'addChild';
|
||||||
|
|
||||||
|
editingData.value = { ...item };
|
||||||
|
|
||||||
|
openDrawer();
|
||||||
|
}
|
||||||
|
|
||||||
|
const allPages = ref<string[]>([]);
|
||||||
|
|
||||||
|
async function getAllPages() {
|
||||||
|
const { data: pages } = await fetchGetAllPages();
|
||||||
|
allPages.value = pages || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
getAllPages();
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
init();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -234,7 +231,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">
|
<NCard :title="$t('page.manage.menu.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<TableHeaderOperation
|
<TableHeaderOperation
|
||||||
v-model:columns="filteredColumns"
|
v-model:columns="columnChecks"
|
||||||
:disabled-delete="checkedRowKeys.length === 0"
|
:disabled-delete="checkedRowKeys.length === 0"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@add="handleAdd"
|
@add="handleAdd"
|
||||||
@ -250,7 +247,7 @@ function getRowKey(row: Api.SystemManage.Menu) {
|
|||||||
:flex-height="!appStore.isMobile"
|
:flex-height="!appStore.isMobile"
|
||||||
:scroll-x="1088"
|
:scroll-x="1088"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:row-key="getRowKey"
|
:row-key="row => row.id"
|
||||||
remote
|
remote
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
class="sm:h-full"
|
class="sm:h-full"
|
||||||
@ -259,6 +256,7 @@ function getRowKey(row: Api.SystemManage.Menu) {
|
|||||||
v-model:visible="drawerVisible"
|
v-model:visible="drawerVisible"
|
||||||
:operate-type="operateType"
|
:operate-type="operateType"
|
||||||
:row-data="editingData"
|
:row-data="editingData"
|
||||||
|
:all-pages="allPages"
|
||||||
@submitted="getData"
|
@submitted="getData"
|
||||||
/>
|
/>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
@ -6,24 +6,21 @@ import { $t } from '@/locales';
|
|||||||
import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business';
|
import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business';
|
||||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||||
import { getLocalIcons } from '@/utils/icon';
|
import { getLocalIcons } from '@/utils/icon';
|
||||||
|
import { getLayoutAndPage, transformLayoutAndPageToComponent } from './shared';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MenuOperateDrawer'
|
name: 'MenuOperateDrawer'
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
export type OperateType = NaiveUI.TableOperateType | 'addChild';
|
||||||
* the type of operation
|
|
||||||
*
|
|
||||||
* - add: add user
|
|
||||||
* - edit: edit user
|
|
||||||
*/
|
|
||||||
export type OperateType = 'add' | 'edit';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** the type of operation */
|
/** the type of operation */
|
||||||
operateType: OperateType;
|
operateType: OperateType;
|
||||||
/** the edit row data */
|
/** the edit menu data or the parent menu data when adding a child menu */
|
||||||
rowData?: Api.SystemManage.Menu | null;
|
rowData?: Api.SystemManage.Menu | null;
|
||||||
|
/** all pages */
|
||||||
|
allPages: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
@ -44,6 +41,7 @@ const { defaultRequiredRule } = useFormRules();
|
|||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
const titles: Record<OperateType, string> = {
|
const titles: Record<OperateType, string> = {
|
||||||
add: $t('page.manage.menu.addMenu'),
|
add: $t('page.manage.menu.addMenu'),
|
||||||
|
addChild: $t('page.manage.menu.addChildMenu'),
|
||||||
edit: $t('page.manage.menu.editMenu')
|
edit: $t('page.manage.menu.editMenu')
|
||||||
};
|
};
|
||||||
return titles[props.operateType];
|
return titles[props.operateType];
|
||||||
@ -51,8 +49,21 @@ const title = computed(() => {
|
|||||||
|
|
||||||
type Model = Pick<
|
type Model = Pick<
|
||||||
Api.SystemManage.Menu,
|
Api.SystemManage.Menu,
|
||||||
'menuType' | 'menuName' | 'icon' | 'iconType' | 'routeName' | 'routePath' | 'status' | 'hideInMenu' | 'order'
|
| 'menuType'
|
||||||
>;
|
| 'menuName'
|
||||||
|
| 'icon'
|
||||||
|
| 'iconType'
|
||||||
|
| 'routeName'
|
||||||
|
| 'routePath'
|
||||||
|
| 'component'
|
||||||
|
| 'status'
|
||||||
|
| 'hideInMenu'
|
||||||
|
| 'order'
|
||||||
|
| 'parentId'
|
||||||
|
> & {
|
||||||
|
layout: string;
|
||||||
|
page: string;
|
||||||
|
};
|
||||||
|
|
||||||
const model: Model = reactive(createDefaultModel());
|
const model: Model = reactive(createDefaultModel());
|
||||||
|
|
||||||
@ -64,9 +75,12 @@ function createDefaultModel(): Model {
|
|||||||
iconType: '1',
|
iconType: '1',
|
||||||
routeName: '',
|
routeName: '',
|
||||||
routePath: '',
|
routePath: '',
|
||||||
|
layout: '',
|
||||||
|
page: '',
|
||||||
status: null,
|
status: null,
|
||||||
hideInMenu: false,
|
hideInMenu: false,
|
||||||
order: 0
|
order: 0,
|
||||||
|
parentId: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +91,8 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
|
|||||||
userStatus: defaultRequiredRule
|
userStatus: defaultRequiredRule
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const disabledMenuType = computed(() => props.operateType === 'edit');
|
||||||
|
|
||||||
const localIcons = getLocalIcons();
|
const localIcons = getLocalIcons();
|
||||||
const localIconOptions = localIcons.map<SelectOption>(item => ({
|
const localIconOptions = localIcons.map<SelectOption>(item => ({
|
||||||
label: () => (
|
label: () => (
|
||||||
@ -88,14 +104,55 @@ const localIconOptions = localIcons.map<SelectOption>(item => ({
|
|||||||
value: item
|
value: item
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function handleUpdateModelWhenEdit() {
|
const showLayout = computed(() => model.parentId === 0);
|
||||||
|
|
||||||
|
const showPage = computed(() => model.menuType === '2');
|
||||||
|
|
||||||
|
const pageOptions = computed(() => {
|
||||||
|
const allPages = [...props.allPages];
|
||||||
|
|
||||||
|
if (model.routeName && !allPages.includes(model.routeName)) {
|
||||||
|
allPages.unshift(model.routeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const opts: CommonType.Option[] = allPages.map(page => ({
|
||||||
|
label: page,
|
||||||
|
value: page
|
||||||
|
}));
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
});
|
||||||
|
|
||||||
|
const layoutOptions: CommonType.Option[] = [
|
||||||
|
{
|
||||||
|
label: 'base',
|
||||||
|
value: 'base'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'blank',
|
||||||
|
value: 'blank'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function handleUpdateModel() {
|
||||||
if (props.operateType === 'add') {
|
if (props.operateType === 'add') {
|
||||||
Object.assign(model, createDefaultModel());
|
Object.assign(model, createDefaultModel());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.operateType === 'addChild' && props.rowData) {
|
||||||
|
const { id } = props.rowData;
|
||||||
|
|
||||||
|
Object.assign(model, createDefaultModel(), { parentId: id });
|
||||||
|
}
|
||||||
|
|
||||||
if (props.operateType === 'edit' && props.rowData) {
|
if (props.operateType === 'edit' && props.rowData) {
|
||||||
Object.assign(model, props.rowData);
|
const { component, ...rest } = props.rowData;
|
||||||
|
|
||||||
|
const { layout, page } = getLayoutAndPage(component);
|
||||||
|
|
||||||
|
Object.assign(model, rest, { layout, page });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +162,9 @@ function closeDrawer() {
|
|||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
await validate();
|
await validate();
|
||||||
|
|
||||||
|
model.component = transformLayoutAndPageToComponent(model.layout, model.page);
|
||||||
|
|
||||||
// request
|
// request
|
||||||
window.$message?.success($t('common.updateSuccess'));
|
window.$message?.success($t('common.updateSuccess'));
|
||||||
closeDrawer();
|
closeDrawer();
|
||||||
@ -113,7 +173,7 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
watch(visible, () => {
|
watch(visible, () => {
|
||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
handleUpdateModelWhenEdit();
|
handleUpdateModel();
|
||||||
restoreValidation();
|
restoreValidation();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -124,7 +184,7 @@ watch(visible, () => {
|
|||||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
|
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
|
||||||
<NFormItem :label="$t('page.manage.menu.menuType')" path="menuType">
|
<NFormItem :label="$t('page.manage.menu.menuType')" path="menuType">
|
||||||
<NRadioGroup v-model:value="model.menuType">
|
<NRadioGroup v-model:value="model.menuType" :disabled="disabledMenuType">
|
||||||
<NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
|
<NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
@ -158,6 +218,16 @@ watch(visible, () => {
|
|||||||
<NFormItem :label="$t('page.manage.menu.routePath')" path="routePath">
|
<NFormItem :label="$t('page.manage.menu.routePath')" path="routePath">
|
||||||
<NInput v-model:value="model.routePath" :placeholder="$t('page.manage.menu.form.routePath')" />
|
<NInput v-model:value="model.routePath" :placeholder="$t('page.manage.menu.form.routePath')" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
<NFormItem v-if="showLayout" :label="$t('page.manage.menu.layout')" path="layout">
|
||||||
|
<NSelect
|
||||||
|
v-model:value="model.layout"
|
||||||
|
:options="layoutOptions"
|
||||||
|
:placeholder="$t('page.manage.menu.form.layout')"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem v-if="showPage" :label="$t('page.manage.menu.page')" path="page">
|
||||||
|
<NSelect v-model:value="model.page" :options="pageOptions" :placeholder="$t('page.manage.menu.form.page')" />
|
||||||
|
</NFormItem>
|
||||||
<NFormItem :label="$t('page.manage.menu.menuStatus')" path="status">
|
<NFormItem :label="$t('page.manage.menu.menuStatus')" path="status">
|
||||||
<NRadioGroup v-model:value="model.status">
|
<NRadioGroup v-model:value="model.status">
|
||||||
<NRadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
|
<NRadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
|
||||||
|
42
src/views/manage/menu/modules/shared.ts
Normal file
42
src/views/manage/menu/modules/shared.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const LAYOUT_PREFIX = 'layout.';
|
||||||
|
const VIEW_PREFIX = 'view.';
|
||||||
|
const FIRST_LEVEL_ROUTE_COMPONENT_SPLIT = '$';
|
||||||
|
|
||||||
|
export function getLayoutAndPage(component?: string | null) {
|
||||||
|
let layout = '';
|
||||||
|
let page = '';
|
||||||
|
|
||||||
|
const [layoutOrPage, pageItem] = component?.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT) || [];
|
||||||
|
|
||||||
|
layout = getLayout(layoutOrPage);
|
||||||
|
page = getPage(pageItem || layoutOrPage);
|
||||||
|
|
||||||
|
return { layout, page };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLayout(layout: string) {
|
||||||
|
return layout.startsWith(LAYOUT_PREFIX) ? layout.replace(LAYOUT_PREFIX, '') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPage(page: string) {
|
||||||
|
return page.startsWith(VIEW_PREFIX) ? page.replace(VIEW_PREFIX, '') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformLayoutAndPageToComponent(layout: string, page: string) {
|
||||||
|
const hasLayout = Boolean(layout);
|
||||||
|
const hasPage = Boolean(page);
|
||||||
|
|
||||||
|
if (hasLayout && hasPage) {
|
||||||
|
return `${LAYOUT_PREFIX}${layout}${FIRST_LEVEL_ROUTE_COMPONENT_SPLIT}${VIEW_PREFIX}${page}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasLayout) {
|
||||||
|
return `${LAYOUT_PREFIX}${layout}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasPage) {
|
||||||
|
return `${VIEW_PREFIX}${page}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
@ -1,30 +1,16 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
||||||
import type { PaginationProps } from 'naive-ui';
|
|
||||||
import { useBoolean } from '@sa/hooks';
|
|
||||||
import { fetchGetRoleList } from '@/service/api';
|
import { fetchGetRoleList } from '@/service/api';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useTable } from '@/hooks/common/table';
|
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { enableStatusRecord } from '@/constants/business';
|
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';
|
import RoleSearch from './modules/role-search.vue';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
|
|
||||||
|
|
||||||
const {
|
const { columns, columnChecks, data, loading, getData, mobilePagination, searchParams, resetSearchParams } = useTable({
|
||||||
columns,
|
|
||||||
filteredColumns,
|
|
||||||
data,
|
|
||||||
loading,
|
|
||||||
pagination,
|
|
||||||
getData,
|
|
||||||
searchParams,
|
|
||||||
updateSearchParams,
|
|
||||||
resetSearchParams
|
|
||||||
} = useTable<Api.SystemManage.Role, typeof fetchGetRoleList, 'index' | 'operate'>({
|
|
||||||
apiFn: fetchGetRoleList,
|
apiFn: fetchGetRoleList,
|
||||||
apiParams: {
|
apiParams: {
|
||||||
current: 1,
|
current: 1,
|
||||||
@ -35,26 +21,6 @@ const {
|
|||||||
roleName: null,
|
roleName: null,
|
||||||
roleCode: 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: () => [
|
columns: () => [
|
||||||
{
|
{
|
||||||
type: 'selection',
|
type: 'selection',
|
||||||
@ -64,7 +30,6 @@ const {
|
|||||||
{
|
{
|
||||||
key: 'index',
|
key: 'index',
|
||||||
title: $t('common.index'),
|
title: $t('common.index'),
|
||||||
render: (_, index): string => getIndex(index),
|
|
||||||
width: 64,
|
width: 64,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
@ -112,7 +77,7 @@ const {
|
|||||||
width: 130,
|
width: 130,
|
||||||
render: row => (
|
render: row => (
|
||||||
<div class="flex-center gap-8px">
|
<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')}
|
{$t('common.edit')}
|
||||||
</NButton>
|
</NButton>
|
||||||
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
|
<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 {
|
||||||
const mobilePagination = computed(() => {
|
drawerVisible,
|
||||||
const p: PaginationProps = {
|
operateType,
|
||||||
...pagination,
|
editingData,
|
||||||
pageSlot: appStore.isMobile ? 3 : 9
|
handleAdd,
|
||||||
};
|
handleEdit,
|
||||||
|
checkedRowKeys,
|
||||||
return p;
|
onBatchDeleted,
|
||||||
});
|
onDeleted
|
||||||
|
// closeDrawer
|
||||||
const operateType = ref<OperateType>('add');
|
} = useTableOperate(data, getData);
|
||||||
|
|
||||||
function handleAdd() {
|
|
||||||
operateType.value = 'add';
|
|
||||||
openDrawer();
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkedRowKeys = ref<string[]>([]);
|
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
// request
|
// request
|
||||||
console.log(checkedRowKeys.value);
|
console.log(checkedRowKeys.value);
|
||||||
window.$message?.success($t('common.deleteSuccess'));
|
|
||||||
|
|
||||||
checkedRowKeys.value = [];
|
onBatchDeleted();
|
||||||
|
|
||||||
getData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** the editing row data */
|
function handleDelete(id: number) {
|
||||||
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) {
|
|
||||||
// request
|
// request
|
||||||
console.log(id);
|
console.log(id);
|
||||||
window.$message?.success($t('common.deleteSuccess'));
|
|
||||||
|
|
||||||
getData();
|
onDeleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIndex(index: number) {
|
function edit(id: number) {
|
||||||
const { page = 0, pageSize = 10 } = pagination;
|
handleEdit(id);
|
||||||
|
|
||||||
return String((page - 1) * pageSize + index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRowKey(row: Api.SystemManage.User) {
|
|
||||||
return row.id;
|
|
||||||
}
|
}
|
||||||
</script>
|
</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">
|
<NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<TableHeaderOperation
|
<TableHeaderOperation
|
||||||
v-model:columns="filteredColumns"
|
v-model:columns="columnChecks"
|
||||||
:disabled-delete="checkedRowKeys.length === 0"
|
:disabled-delete="checkedRowKeys.length === 0"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@add="handleAdd"
|
@add="handleAdd"
|
||||||
@ -211,7 +150,7 @@ function getRowKey(row: Api.SystemManage.User) {
|
|||||||
:scroll-x="702"
|
:scroll-x="702"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
remote
|
remote
|
||||||
:row-key="getRowKey"
|
:row-key="row => row.id"
|
||||||
:pagination="mobilePagination"
|
:pagination="mobilePagination"
|
||||||
class="sm:h-full"
|
class="sm:h-full"
|
||||||
/>
|
/>
|
||||||
|
@ -8,17 +8,9 @@ defineOptions({
|
|||||||
name: 'RoleOperateDrawer'
|
name: 'RoleOperateDrawer'
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* the type of operation
|
|
||||||
*
|
|
||||||
* - add: add role
|
|
||||||
* - edit: edit role
|
|
||||||
*/
|
|
||||||
export type OperateType = 'add' | 'edit';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** the type of operation */
|
/** the type of operation */
|
||||||
operateType: OperateType;
|
operateType: NaiveUI.TableOperateType;
|
||||||
/** the edit row data */
|
/** the edit row data */
|
||||||
rowData?: Api.SystemManage.Role | null;
|
rowData?: Api.SystemManage.Role | null;
|
||||||
}
|
}
|
||||||
@ -39,7 +31,7 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
|
|||||||
const { defaultRequiredRule } = useFormRules();
|
const { defaultRequiredRule } = useFormRules();
|
||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
const titles: Record<OperateType, string> = {
|
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||||
add: $t('page.manage.role.addRole'),
|
add: $t('page.manage.role.addRole'),
|
||||||
edit: $t('page.manage.role.editRole')
|
edit: $t('page.manage.role.editRole')
|
||||||
};
|
};
|
||||||
|
@ -1,30 +1,16 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
||||||
import type { PaginationProps } from 'naive-ui';
|
|
||||||
import { useBoolean } from '@sa/hooks';
|
|
||||||
import { fetchGetUserList } from '@/service/api';
|
import { fetchGetUserList } from '@/service/api';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import { useTable } from '@/hooks/common/table';
|
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
|
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';
|
import UserSearch from './modules/user-search.vue';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
|
|
||||||
|
|
||||||
const {
|
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
|
||||||
columns,
|
|
||||||
filteredColumns,
|
|
||||||
data,
|
|
||||||
loading,
|
|
||||||
pagination,
|
|
||||||
getData,
|
|
||||||
searchParams,
|
|
||||||
updateSearchParams,
|
|
||||||
resetSearchParams
|
|
||||||
} = useTable<Api.SystemManage.User, typeof fetchGetUserList, 'index' | 'operate'>({
|
|
||||||
apiFn: fetchGetUserList,
|
apiFn: fetchGetUserList,
|
||||||
apiParams: {
|
apiParams: {
|
||||||
current: 1,
|
current: 1,
|
||||||
@ -38,26 +24,6 @@ const {
|
|||||||
userPhone: null,
|
userPhone: null,
|
||||||
userEmail: 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: () => [
|
columns: () => [
|
||||||
{
|
{
|
||||||
type: 'selection',
|
type: 'selection',
|
||||||
@ -67,7 +33,6 @@ const {
|
|||||||
{
|
{
|
||||||
key: 'index',
|
key: 'index',
|
||||||
title: $t('common.index'),
|
title: $t('common.index'),
|
||||||
render: (_, index): string => getIndex(index),
|
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 64
|
width: 64
|
||||||
},
|
},
|
||||||
@ -142,7 +107,7 @@ const {
|
|||||||
width: 130,
|
width: 130,
|
||||||
render: row => (
|
render: row => (
|
||||||
<div class="flex-center gap-8px">
|
<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')}
|
{$t('common.edit')}
|
||||||
</NButton>
|
</NButton>
|
||||||
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
|
<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 {
|
||||||
const mobilePagination = computed(() => {
|
drawerVisible,
|
||||||
const p: PaginationProps = {
|
operateType,
|
||||||
...pagination,
|
editingData,
|
||||||
pageSlot: appStore.isMobile ? 3 : 9
|
handleAdd,
|
||||||
};
|
handleEdit,
|
||||||
|
checkedRowKeys,
|
||||||
return p;
|
onBatchDeleted,
|
||||||
});
|
onDeleted
|
||||||
|
// closeDrawer
|
||||||
const operateType = ref<OperateType>('add');
|
} = useTableOperate(data, getData);
|
||||||
|
|
||||||
function handleAdd() {
|
|
||||||
operateType.value = 'add';
|
|
||||||
openDrawer();
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkedRowKeys = ref<string[]>([]);
|
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
// request
|
// request
|
||||||
console.log(checkedRowKeys.value);
|
console.log(checkedRowKeys.value);
|
||||||
window.$message?.success($t('common.deleteSuccess'));
|
|
||||||
|
|
||||||
checkedRowKeys.value = [];
|
onBatchDeleted();
|
||||||
|
|
||||||
getData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** the editing row data */
|
function handleDelete(id: number) {
|
||||||
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) {
|
|
||||||
// request
|
// request
|
||||||
console.log(id);
|
console.log(id);
|
||||||
window.$message?.success($t('common.deleteSuccess'));
|
|
||||||
|
|
||||||
getData();
|
onDeleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIndex(index: number) {
|
function edit(id: number) {
|
||||||
const { page = 0, pageSize = 10 } = pagination;
|
handleEdit(id);
|
||||||
|
|
||||||
return String((page - 1) * pageSize + index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRowKey(row: Api.SystemManage.User) {
|
|
||||||
return row.id;
|
|
||||||
}
|
}
|
||||||
</script>
|
</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">
|
<NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<TableHeaderOperation
|
<TableHeaderOperation
|
||||||
v-model:columns="filteredColumns"
|
v-model:columns="columnChecks"
|
||||||
:disabled-delete="checkedRowKeys.length === 0"
|
:disabled-delete="checkedRowKeys.length === 0"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@add="handleAdd"
|
@add="handleAdd"
|
||||||
@ -241,7 +180,7 @@ function getRowKey(row: Api.SystemManage.User) {
|
|||||||
:scroll-x="962"
|
:scroll-x="962"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
remote
|
remote
|
||||||
:row-key="getRowKey"
|
:row-key="row => row.id"
|
||||||
:pagination="mobilePagination"
|
:pagination="mobilePagination"
|
||||||
class="sm:h-full"
|
class="sm:h-full"
|
||||||
/>
|
/>
|
||||||
|
@ -9,17 +9,9 @@ defineOptions({
|
|||||||
name: 'UserOperateDrawer'
|
name: 'UserOperateDrawer'
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* the type of operation
|
|
||||||
*
|
|
||||||
* - add: add user
|
|
||||||
* - edit: edit user
|
|
||||||
*/
|
|
||||||
export type OperateType = 'add' | 'edit';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** the type of operation */
|
/** the type of operation */
|
||||||
operateType: OperateType;
|
operateType: NaiveUI.TableOperateType;
|
||||||
/** the edit row data */
|
/** the edit row data */
|
||||||
rowData?: Api.SystemManage.User | null;
|
rowData?: Api.SystemManage.User | null;
|
||||||
}
|
}
|
||||||
@ -40,7 +32,7 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
|
|||||||
const { defaultRequiredRule } = useFormRules();
|
const { defaultRequiredRule } = useFormRules();
|
||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
const titles: Record<OperateType, string> = {
|
const titles: Record<NaiveUI.TableOperateType, string> = {
|
||||||
add: $t('page.manage.user.addUser'),
|
add: $t('page.manage.user.addUser'),
|
||||||
edit: $t('page.manage.user.editUser')
|
edit: $t('page.manage.user.editUser')
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user