mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-29 22:56:41 +08:00
feat: add subpackage @sa/alova
, refactor all example pages with @sa/alova
This commit is contained in:
parent
3fbf2e2a5f
commit
77e17b9bd0
@ -51,7 +51,7 @@
|
|||||||
"@antv/g2": "5.2.5",
|
"@antv/g2": "5.2.5",
|
||||||
"@better-scroll/core": "2.5.1",
|
"@better-scroll/core": "2.5.1",
|
||||||
"@iconify/vue": "4.1.2",
|
"@iconify/vue": "4.1.2",
|
||||||
"@sa/axios": "workspace:*",
|
"@sa/alova": "workspace:*",
|
||||||
"@sa/color": "workspace:*",
|
"@sa/color": "workspace:*",
|
||||||
"@sa/hooks": "workspace:*",
|
"@sa/hooks": "workspace:*",
|
||||||
"@sa/materials": "workspace:*",
|
"@sa/materials": "workspace:*",
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { computed, reactive, ref } from 'vue';
|
import { computed, reactive, ref } from 'vue';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { jsonClone } from '@sa/utils';
|
import { jsonClone } from '@sa/utils';
|
||||||
|
import { usePagination } from '@sa/alova/client';
|
||||||
|
import type { AlovaGenerics, Method } from '@sa/alova';
|
||||||
import useBoolean from './use-boolean';
|
import useBoolean from './use-boolean';
|
||||||
import useLoading from './use-loading';
|
|
||||||
|
|
||||||
export type MaybePromise<T> = T | Promise<T>;
|
export type MaybePromise<T> = T | Promise<T>;
|
||||||
|
|
||||||
export type ApiFn = (args: any) => Promise<unknown>;
|
export type ApiFn = (args: any) => Method<AlovaGenerics>;
|
||||||
|
|
||||||
export type TableColumnCheck = {
|
export type TableColumnCheck = {
|
||||||
key: string;
|
key: string;
|
||||||
@ -16,14 +17,7 @@ export type TableColumnCheck = {
|
|||||||
|
|
||||||
export type TableDataWithIndex<T> = T & { index: number };
|
export type TableDataWithIndex<T> = T & { index: number };
|
||||||
|
|
||||||
export type TransformedData<T> = {
|
export type Transformer<T, Response> = (response: Response) => TableDataWithIndex<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> = {
|
export type TableConfig<A extends ApiFn, T, C> = {
|
||||||
/** api function to get table data */
|
/** api function to get table data */
|
||||||
@ -31,7 +25,7 @@ export type TableConfig<A extends ApiFn, T, C> = {
|
|||||||
/** api params */
|
/** api params */
|
||||||
apiParams?: Parameters<A>[0];
|
apiParams?: Parameters<A>[0];
|
||||||
/** transform api response to table data */
|
/** transform api response to table data */
|
||||||
transformer: Transformer<T, Awaited<ReturnType<A>>>;
|
transformer: Transformer<T, Awaited<ReturnType<ReturnType<A>['send']>>>;
|
||||||
/** columns factory */
|
/** columns factory */
|
||||||
columns: () => C[];
|
columns: () => C[];
|
||||||
/**
|
/**
|
||||||
@ -46,12 +40,6 @@ export type TableConfig<A extends ApiFn, T, C> = {
|
|||||||
* @param columns
|
* @param columns
|
||||||
*/
|
*/
|
||||||
getColumns: (columns: C[], checks: TableColumnCheck[]) => C[];
|
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
|
* whether to get data immediately
|
||||||
*
|
*
|
||||||
@ -61,7 +49,6 @@ export type TableConfig<A extends ApiFn, T, C> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function useHookTable<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();
|
const { bool: empty, setBool: setEmpty } = useBoolean();
|
||||||
|
|
||||||
const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config;
|
const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config;
|
||||||
@ -70,12 +57,22 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
|
|||||||
|
|
||||||
const allColumns = ref(config.columns()) as Ref<C[]>;
|
const allColumns = ref(config.columns()) as Ref<C[]>;
|
||||||
|
|
||||||
const data: Ref<TableDataWithIndex<T>[]> = ref([]);
|
|
||||||
|
|
||||||
const columnChecks: Ref<TableColumnCheck[]> = ref(getColumnChecks(config.columns()));
|
const columnChecks: Ref<TableColumnCheck[]> = ref(getColumnChecks(config.columns()));
|
||||||
|
|
||||||
const columns = computed(() => getColumns(allColumns.value, columnChecks.value));
|
const columns = computed(() => getColumns(allColumns.value, columnChecks.value));
|
||||||
|
|
||||||
|
const states = usePagination<ReturnType<A> extends Method<infer AG> ? AG : never, ReturnType<typeof transformer>>(
|
||||||
|
(page, size) => apiFn({ ...formatSearchParams(searchParams), page, size }) as any,
|
||||||
|
{
|
||||||
|
immediate,
|
||||||
|
data: transformer,
|
||||||
|
total: res => res.total
|
||||||
|
}
|
||||||
|
).onSuccess(({ data }) => {
|
||||||
|
setEmpty(data.length === 0);
|
||||||
|
});
|
||||||
|
Reflect.deleteProperty(states, 'uploading');
|
||||||
|
|
||||||
function reloadColumns() {
|
function reloadColumns() {
|
||||||
allColumns.value = config.columns();
|
allColumns.value = config.columns();
|
||||||
|
|
||||||
@ -89,33 +86,13 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
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>) {
|
function formatSearchParams(params: Record<string, unknown>) {
|
||||||
const formattedParams: Record<string, unknown> = {};
|
const formattedParams: Record<string, unknown> = {};
|
||||||
|
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
if (value !== null && value !== undefined) {
|
if (value !== null && value !== undefined) {
|
||||||
formattedParams[key] = value;
|
formattedParams[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return formattedParams;
|
return formattedParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,18 +110,13 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
|
|||||||
Object.assign(searchParams, jsonClone(apiParams));
|
Object.assign(searchParams, jsonClone(apiParams));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (immediate) {
|
|
||||||
getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
...states,
|
||||||
empty,
|
empty,
|
||||||
data,
|
|
||||||
columns,
|
columns,
|
||||||
columnChecks,
|
columnChecks,
|
||||||
reloadColumns,
|
reloadColumns,
|
||||||
getData,
|
getData: states.send,
|
||||||
searchParams,
|
searchParams,
|
||||||
updateSearchParams,
|
updateSearchParams,
|
||||||
resetSearchParams
|
resetSearchParams
|
||||||
|
@ -20,9 +20,9 @@ importers:
|
|||||||
'@iconify/vue':
|
'@iconify/vue':
|
||||||
specifier: 4.1.2
|
specifier: 4.1.2
|
||||||
version: 4.1.2(vue@3.5.11(typescript@5.6.3))
|
version: 4.1.2(vue@3.5.11(typescript@5.6.3))
|
||||||
'@sa/axios':
|
'@sa/alova':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:packages/axios
|
version: link:packages/alova
|
||||||
'@sa/color':
|
'@sa/color':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:packages/color
|
version: link:packages/color
|
||||||
|
@ -7,10 +7,10 @@ import { useAppStore } from '@/store/modules/app';
|
|||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
|
|
||||||
type TableData = NaiveUI.TableData;
|
type TableData = NaiveUI.TableData;
|
||||||
type GetTableData<A extends NaiveUI.TableApiFn> = NaiveUI.GetTableData<A>;
|
type GetTableData<A extends NaiveUI.TableAlovaApiFn> = NaiveUI.GetTableData<A>;
|
||||||
type TableColumn<T> = NaiveUI.TableColumn<T>;
|
type TableColumn<T> = NaiveUI.TableColumn<T>;
|
||||||
|
|
||||||
export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
|
export function useTable<A extends NaiveUI.TableAlovaApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
|
||||||
const scope = effectScope();
|
const scope = effectScope();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
@ -21,41 +21,24 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
|||||||
const SELECTION_KEY = '__selection__';
|
const SELECTION_KEY = '__selection__';
|
||||||
|
|
||||||
const EXPAND_KEY = '__expand__';
|
const EXPAND_KEY = '__expand__';
|
||||||
|
const { reloadColumns, page, pageSize, total, getData, update, ...rest } = useHookTable<
|
||||||
const {
|
A,
|
||||||
loading,
|
GetTableData<A>,
|
||||||
empty,
|
TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>
|
||||||
data,
|
>({
|
||||||
columns,
|
|
||||||
columnChecks,
|
|
||||||
reloadColumns,
|
|
||||||
getData,
|
|
||||||
searchParams,
|
|
||||||
updateSearchParams,
|
|
||||||
resetSearchParams
|
|
||||||
} = useHookTable<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>({
|
|
||||||
apiFn,
|
apiFn,
|
||||||
apiParams,
|
apiParams,
|
||||||
columns: config.columns,
|
columns: config.columns,
|
||||||
transformer: res => {
|
transformer: res => {
|
||||||
const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
|
const { records = [], current = 1, size = 10 } = res || {};
|
||||||
|
|
||||||
// Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors.
|
// Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors.
|
||||||
const pageSize = size <= 0 ? 10 : size;
|
const pageSizeValue = size <= 0 ? 10 : size;
|
||||||
|
|
||||||
const recordsWithIndex = records.map((item, index) => {
|
return records.map((item, index) => ({
|
||||||
return {
|
|
||||||
...item,
|
...item,
|
||||||
index: (current - 1) * pageSize + index + 1
|
index: (current - 1) * pageSizeValue + index + 1
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: recordsWithIndex,
|
|
||||||
pageNum: current,
|
|
||||||
pageSize,
|
|
||||||
total
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
getColumnChecks: cols => {
|
getColumnChecks: cols => {
|
||||||
const checks: NaiveUI.TableColumnCheck[] = [];
|
const checks: NaiveUI.TableColumnCheck[] = [];
|
||||||
@ -103,64 +86,56 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
|||||||
|
|
||||||
return filteredColumns;
|
return filteredColumns;
|
||||||
},
|
},
|
||||||
onFetched: async transformed => {
|
|
||||||
const { pageNum, pageSize, total } = transformed;
|
|
||||||
|
|
||||||
updatePagination({
|
|
||||||
page: pageNum,
|
|
||||||
pageSize,
|
|
||||||
itemCount: total
|
|
||||||
});
|
|
||||||
},
|
|
||||||
immediate
|
immediate
|
||||||
});
|
});
|
||||||
|
|
||||||
const pagination: PaginationProps = reactive({
|
const paginationBase: PaginationProps = reactive({
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
showSizePicker: true,
|
showSizePicker: true,
|
||||||
pageSizes: [10, 15, 20, 25, 30],
|
pageSizes: [10, 15, 20, 25, 30],
|
||||||
onUpdatePage: async (page: number) => {
|
onUpdatePage: async pageValue => {
|
||||||
pagination.page = page;
|
page.value = pageValue;
|
||||||
|
|
||||||
updateSearchParams({
|
|
||||||
current: page,
|
|
||||||
size: pagination.pageSize!
|
|
||||||
});
|
|
||||||
|
|
||||||
getData();
|
|
||||||
},
|
},
|
||||||
onUpdatePageSize: async (pageSize: number) => {
|
onUpdatePageSize: async pageSizeValue => {
|
||||||
pagination.pageSize = pageSize;
|
pageSize.value = pageSizeValue;
|
||||||
pagination.page = 1;
|
|
||||||
|
|
||||||
updateSearchParams({
|
|
||||||
current: pagination.page,
|
|
||||||
size: pageSize
|
|
||||||
});
|
|
||||||
|
|
||||||
getData();
|
|
||||||
},
|
},
|
||||||
...(showTotal
|
...(showTotal
|
||||||
? {
|
? {
|
||||||
prefix: page => $t('datatable.itemCount', { total: page.itemCount })
|
prefix: pageProps => $t('datatable.itemCount', { total: pageProps.itemCount })
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pagination = computed(
|
||||||
|
() =>
|
||||||
|
<PaginationProps>{
|
||||||
|
...paginationBase,
|
||||||
|
page: page.value,
|
||||||
|
pageSize: pageSize.value,
|
||||||
|
itemCount: total.value
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
|
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
|
||||||
const mobilePagination = computed(() => {
|
const mobilePagination = computed(() => {
|
||||||
const p: PaginationProps = {
|
const p: PaginationProps = {
|
||||||
...pagination,
|
...pagination.value,
|
||||||
pageSlot: isMobile.value ? 3 : 9,
|
pageSlot: isMobile.value ? 3 : 9,
|
||||||
prefix: !isMobile.value && showTotal ? pagination.prefix : undefined
|
prefix: !isMobile.value && showTotal ? pagination.value.prefix : undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
});
|
});
|
||||||
|
|
||||||
function updatePagination(update: Partial<PaginationProps>) {
|
function updatePagination(updateProps: Partial<PaginationProps>) {
|
||||||
Object.assign(pagination, update);
|
const innerPageStates = ['page', 'pageSize', 'itemCount'] as const;
|
||||||
|
innerPageStates.forEach(key => {
|
||||||
|
if (updateProps[key]) {
|
||||||
|
update({
|
||||||
|
[key]: updateProps[key]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.assign(paginationBase, updateProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -169,16 +144,8 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
|||||||
* @param pageNum the page number. default is 1
|
* @param pageNum the page number. default is 1
|
||||||
*/
|
*/
|
||||||
async function getDataByPage(pageNum: number = 1) {
|
async function getDataByPage(pageNum: number = 1) {
|
||||||
updatePagination({
|
page.value = pageNum;
|
||||||
page: pageNum
|
return getData();
|
||||||
});
|
|
||||||
|
|
||||||
updateSearchParams({
|
|
||||||
current: pageNum,
|
|
||||||
size: pagination.pageSize!
|
|
||||||
});
|
|
||||||
|
|
||||||
await getData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.run(() => {
|
scope.run(() => {
|
||||||
@ -195,24 +162,17 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
...rest,
|
||||||
empty,
|
getData,
|
||||||
data,
|
|
||||||
columns,
|
|
||||||
columnChecks,
|
|
||||||
reloadColumns,
|
reloadColumns,
|
||||||
pagination,
|
pagination,
|
||||||
mobilePagination,
|
mobilePagination,
|
||||||
updatePagination,
|
updatePagination,
|
||||||
getData,
|
getDataByPage
|
||||||
getDataByPage,
|
|
||||||
searchParams,
|
|
||||||
updateSearchParams,
|
|
||||||
resetSearchParams
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void>) {
|
export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void> | void) {
|
||||||
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
|
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
|
||||||
|
|
||||||
const operateType = ref<NaiveUI.TableOperateType>('add');
|
const operateType = ref<NaiveUI.TableOperateType>('add');
|
||||||
|
@ -14,7 +14,7 @@ import FirstLevelMenu from '../components/first-level-menu.vue';
|
|||||||
import GlobalLogo from '../../global-logo/index.vue';
|
import GlobalLogo from '../../global-logo/index.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'VerticalMixMenu'
|
name: 'VerticalMenuMix'
|
||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { request } from '../request';
|
import { alova } from '../request';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login
|
* Login
|
||||||
@ -7,19 +7,12 @@ import { request } from '../request';
|
|||||||
* @param password Password
|
* @param password Password
|
||||||
*/
|
*/
|
||||||
export function fetchLogin(userName: string, password: string) {
|
export function fetchLogin(userName: string, password: string) {
|
||||||
return request<Api.Auth.LoginToken>({
|
return alova.Post<Api.Auth.LoginToken>('/auth/login', { userName, password });
|
||||||
url: '/auth/login',
|
|
||||||
method: 'post',
|
|
||||||
data: {
|
|
||||||
userName,
|
|
||||||
password
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get user info */
|
/** Get user info */
|
||||||
export function fetchGetUserInfo() {
|
export function fetchGetUserInfo() {
|
||||||
return request<Api.Auth.UserInfo>({ url: '/auth/getUserInfo' });
|
return alova.Get<Api.Auth.UserInfo>('/auth/getUserInfo');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,13 +21,15 @@ export function fetchGetUserInfo() {
|
|||||||
* @param refreshToken Refresh token
|
* @param refreshToken Refresh token
|
||||||
*/
|
*/
|
||||||
export function fetchRefreshToken(refreshToken: string) {
|
export function fetchRefreshToken(refreshToken: string) {
|
||||||
return request<Api.Auth.LoginToken>({
|
return alova.Post<Api.Auth.LoginToken>(
|
||||||
url: '/auth/refreshToken',
|
'/auth/refreshToken',
|
||||||
method: 'post',
|
{ refreshToken },
|
||||||
data: {
|
{
|
||||||
refreshToken
|
meta: {
|
||||||
|
authRole: 'refreshToken'
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,5 +39,8 @@ export function fetchRefreshToken(refreshToken: string) {
|
|||||||
* @param msg error message
|
* @param msg error message
|
||||||
*/
|
*/
|
||||||
export function fetchCustomBackendError(code: string, msg: string) {
|
export function fetchCustomBackendError(code: string, msg: string) {
|
||||||
return request({ url: '/auth/error', params: { code, msg } });
|
return alova.Get('/auth/error', {
|
||||||
|
params: { code, msg },
|
||||||
|
shareRequest: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { request } from '../request';
|
import { alova } from '../request';
|
||||||
|
|
||||||
/** get constant routes */
|
/** get constant routes */
|
||||||
export function fetchGetConstantRoutes() {
|
export function fetchGetConstantRoutes() {
|
||||||
return request<Api.Route.MenuRoute[]>({ url: '/route/getConstantRoutes' });
|
return alova.Get<Api.Route.MenuRoute[]>('/route/getConstantRoutes');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** get user routes */
|
/** get user routes */
|
||||||
export function fetchGetUserRoutes() {
|
export function fetchGetUserRoutes() {
|
||||||
return request<Api.Route.UserRoute>({ url: '/route/getUserRoutes' });
|
return alova.Get<Api.Route.UserRoute>('/route/getUserRoutes');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,5 +16,5 @@ export function fetchGetUserRoutes() {
|
|||||||
* @param routeName route name
|
* @param routeName route name
|
||||||
*/
|
*/
|
||||||
export function fetchIsRouteExist(routeName: string) {
|
export function fetchIsRouteExist(routeName: string) {
|
||||||
return request<boolean>({ url: '/route/isRouteExist', params: { routeName } });
|
return alova.Get<boolean>('/route/isRouteExist', { params: { routeName } });
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
import { request } from '../request';
|
import { alova } from '../request';
|
||||||
|
|
||||||
/** get role list */
|
/** get role list */
|
||||||
export function fetchGetRoleList(params?: Api.SystemManage.RoleSearchParams) {
|
export function fetchGetRoleList(params?: Api.SystemManage.RoleSearchParams) {
|
||||||
return request<Api.SystemManage.RoleList>({
|
return alova.Get<Api.SystemManage.RoleList>('/systemManage/getRoleList', { params });
|
||||||
url: '/systemManage/getRoleList',
|
|
||||||
method: 'get',
|
|
||||||
params
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,41 +11,25 @@ export function fetchGetRoleList(params?: Api.SystemManage.RoleSearchParams) {
|
|||||||
* these roles are all enabled
|
* these roles are all enabled
|
||||||
*/
|
*/
|
||||||
export function fetchGetAllRoles() {
|
export function fetchGetAllRoles() {
|
||||||
return request<Api.SystemManage.AllRole[]>({
|
return alova.Get<Api.SystemManage.AllRole[]>('/systemManage/getAllRoles');
|
||||||
url: '/systemManage/getAllRoles',
|
|
||||||
method: 'get'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** get user list */
|
/** get user list */
|
||||||
export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
|
export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
|
||||||
return request<Api.SystemManage.UserList>({
|
return alova.Get<Api.SystemManage.UserList>('/systemManage/getUserList', { params });
|
||||||
url: '/systemManage/getUserList',
|
|
||||||
method: 'get',
|
|
||||||
params
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** get menu list */
|
/** get menu list */
|
||||||
export function fetchGetMenuList() {
|
export function fetchGetMenuList() {
|
||||||
return request<Api.SystemManage.MenuList>({
|
return alova.Get<Api.SystemManage.MenuList>('/systemManage/getMenuList/v2');
|
||||||
url: '/systemManage/getMenuList/v2',
|
|
||||||
method: 'get'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** get all pages */
|
/** get all pages */
|
||||||
export function fetchGetAllPages() {
|
export function fetchGetAllPages() {
|
||||||
return request<string[]>({
|
return alova.Get<string[]>('/systemManage/getAllPages');
|
||||||
url: '/systemManage/getAllPages',
|
|
||||||
method: 'get'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** get menu tree */
|
/** get menu tree */
|
||||||
export function fetchGetMenuTree() {
|
export function fetchGetMenuTree() {
|
||||||
return request<Api.SystemManage.MenuTree[]>({
|
return alova.Get<Api.SystemManage.MenuTree[]>('/systemManage/getMenuTree');
|
||||||
url: '/systemManage/getMenuTree',
|
|
||||||
method: 'get'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -1,67 +1,86 @@
|
|||||||
import type { AxiosResponse } from 'axios';
|
import { createAlovaRequest } from '@sa/alova';
|
||||||
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';
|
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { localStg } from '@/utils/storage';
|
|
||||||
import { getServiceBaseURL } from '@/utils/service';
|
import { getServiceBaseURL } from '@/utils/service';
|
||||||
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
|
import { getAuthorization, handleRefreshToken, showErrorMsg } from './shared';
|
||||||
import type { RequestInstanceState } from './type';
|
import type { RequestInstanceState } from './type';
|
||||||
|
|
||||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||||
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||||
|
|
||||||
export const request = createFlatRequest<App.Service.Response, RequestInstanceState>(
|
const state: RequestInstanceState = {
|
||||||
|
errMsgStack: []
|
||||||
|
};
|
||||||
|
export const alova = createAlovaRequest(
|
||||||
{
|
{
|
||||||
baseURL,
|
baseURL
|
||||||
headers: {
|
},
|
||||||
apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2'
|
{
|
||||||
|
onRequest({ config }) {
|
||||||
|
const Authorization = getAuthorization();
|
||||||
|
config.headers.Authorization = Authorization;
|
||||||
|
config.headers.apifoxToken = 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2';
|
||||||
|
},
|
||||||
|
tokenRefresher: {
|
||||||
|
async isExpired(response) {
|
||||||
|
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
|
||||||
|
const { code } = await response.clone().json();
|
||||||
|
return expiredTokenCodes.includes(String(code));
|
||||||
|
},
|
||||||
|
async handler() {
|
||||||
|
await handleRefreshToken();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
async isBackendSuccess(response) {
|
||||||
async onRequest(config) {
|
|
||||||
const Authorization = getAuthorization();
|
|
||||||
Object.assign(config.headers, { Authorization });
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
isBackendSuccess(response) {
|
|
||||||
// when the backend response code is "0000"(default), it means the request is success
|
// when the backend response code is "0000"(default), it means the request is success
|
||||||
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
|
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
|
||||||
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
|
const resp = response.clone();
|
||||||
|
const data = await resp.json();
|
||||||
|
return String(data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
|
||||||
},
|
},
|
||||||
async onBackendFail(response, instance) {
|
async transformBackendResponse(response) {
|
||||||
|
return (await response.clone().json()).data;
|
||||||
|
},
|
||||||
|
async onError(error, response) {
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const responseCode = String(response.data.code);
|
|
||||||
|
let message = error.message;
|
||||||
|
let responseCode = '';
|
||||||
|
if (response) {
|
||||||
|
const data = await response?.clone().json();
|
||||||
|
message = data.msg;
|
||||||
|
responseCode = String(data.code);
|
||||||
|
}
|
||||||
|
|
||||||
function handleLogout() {
|
function handleLogout() {
|
||||||
|
showErrorMsg(state, message);
|
||||||
authStore.resetStore();
|
authStore.resetStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
function logoutAndCleanup() {
|
function logoutAndCleanup() {
|
||||||
handleLogout();
|
handleLogout();
|
||||||
window.removeEventListener('beforeunload', handleLogout);
|
window.removeEventListener('beforeunload', handleLogout);
|
||||||
|
state.errMsgStack = state.errMsgStack.filter(msg => msg !== message);
|
||||||
request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
|
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
|
||||||
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
|
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
|
||||||
if (logoutCodes.includes(responseCode)) {
|
if (logoutCodes.includes(responseCode)) {
|
||||||
handleLogout();
|
handleLogout();
|
||||||
return null;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
|
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
|
||||||
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
||||||
if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
|
if (modalLogoutCodes.includes(responseCode) && !state.errMsgStack?.includes(message)) {
|
||||||
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
|
state.errMsgStack = [...(state.errMsgStack || []), message];
|
||||||
|
|
||||||
// prevent the user from refreshing the page
|
// prevent the user from refreshing the page
|
||||||
window.addEventListener('beforeunload', handleLogout);
|
window.addEventListener('beforeunload', handleLogout);
|
||||||
|
|
||||||
window.$dialog?.error({
|
window.$dialog?.error({
|
||||||
title: $t('common.error'),
|
title: $t('common.error'),
|
||||||
content: response.data.msg,
|
content: message,
|
||||||
positiveText: $t('common.confirm'),
|
positiveText: $t('common.confirm'),
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
closeOnEsc: false,
|
closeOnEsc: false,
|
||||||
@ -72,95 +91,10 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
|||||||
logoutAndCleanup();
|
logoutAndCleanup();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
throw error;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
showErrorMsg(state, message);
|
||||||
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
|
throw error;
|
||||||
// the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
|
|
||||||
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
|
|
||||||
if (expiredTokenCodes.includes(responseCode)) {
|
|
||||||
const success = await handleExpiredRequest(request.state);
|
|
||||||
if (success) {
|
|
||||||
const Authorization = getAuthorization();
|
|
||||||
Object.assign(response.config.headers, { Authorization });
|
|
||||||
|
|
||||||
return instance.request(response.config) as Promise<AxiosResponse>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
transformBackendResponse(response) {
|
|
||||||
return response.data.data;
|
|
||||||
},
|
|
||||||
onError(error) {
|
|
||||||
// when the request is fail, you can show error message
|
|
||||||
|
|
||||||
let message = error.message;
|
|
||||||
let backendErrorCode = '';
|
|
||||||
|
|
||||||
// get backend error message and code
|
|
||||||
if (error.code === BACKEND_ERROR_CODE) {
|
|
||||||
message = error.response?.data?.msg || message;
|
|
||||||
backendErrorCode = String(error.response?.data?.code || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// the error message is displayed in the modal
|
|
||||||
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
|
||||||
if (modalLogoutCodes.includes(backendErrorCode)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// when the token is expired, refresh token and retry request, so no need to show error message
|
|
||||||
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
|
|
||||||
if (expiredTokenCodes.includes(backendErrorCode)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showErrorMsg(request.state, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const demoRequest = createRequest<App.Service.DemoResponse>(
|
|
||||||
{
|
|
||||||
baseURL: otherBaseURL.demo
|
|
||||||
},
|
|
||||||
{
|
|
||||||
async onRequest(config) {
|
|
||||||
const { headers } = config;
|
|
||||||
|
|
||||||
// set token
|
|
||||||
const token = localStg.get('token');
|
|
||||||
const Authorization = token ? `Bearer ${token}` : null;
|
|
||||||
Object.assign(headers, { Authorization });
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
isBackendSuccess(response) {
|
|
||||||
// when the backend response code is "200", it means the request is success
|
|
||||||
// you can change this logic by yourself
|
|
||||||
return response.data.status === '200';
|
|
||||||
},
|
|
||||||
async onBackendFail(_response) {
|
|
||||||
// when the backend response code is not "200", it means the request is fail
|
|
||||||
// for example: the token is expired, refresh token and retry request
|
|
||||||
},
|
|
||||||
transformBackendResponse(response) {
|
|
||||||
return response.data.result;
|
|
||||||
},
|
|
||||||
onError(error) {
|
|
||||||
// when the request is fail, you can show error message
|
|
||||||
|
|
||||||
let message = error.message;
|
|
||||||
|
|
||||||
// show backend error message
|
|
||||||
if (error.code === BACKEND_ERROR_CODE) {
|
|
||||||
message = error.response?.data?.message || message;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.$message?.error(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -11,34 +11,23 @@ export function getAuthorization() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** refresh token */
|
/** refresh token */
|
||||||
async function handleRefreshToken() {
|
export async function handleRefreshToken() {
|
||||||
const { resetStore } = useAuthStore();
|
const { resetStore } = useAuthStore();
|
||||||
|
|
||||||
const rToken = localStg.get('refreshToken') || '';
|
const rToken = localStg.get('refreshToken') || '';
|
||||||
const { error, data } = await fetchRefreshToken(rToken);
|
const refreshTokenMethod = fetchRefreshToken(rToken);
|
||||||
if (!error) {
|
|
||||||
|
// set the refreshToken role, so that the request will not be intercepted
|
||||||
|
refreshTokenMethod.meta.authRole = 'refreshToken';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await refreshTokenMethod;
|
||||||
localStg.set('token', data.token);
|
localStg.set('token', data.token);
|
||||||
localStg.set('refreshToken', data.refreshToken);
|
localStg.set('refreshToken', data.refreshToken);
|
||||||
return true;
|
} catch (error) {
|
||||||
}
|
|
||||||
|
|
||||||
resetStore();
|
resetStore();
|
||||||
|
throw error;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleExpiredRequest(state: RequestInstanceState) {
|
|
||||||
if (!state.refreshTokenFn) {
|
|
||||||
state.refreshTokenFn = handleRefreshToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
const success = await state.refreshTokenFn;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
state.refreshTokenFn = null;
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showErrorMsg(state: RequestInstanceState, message: string) {
|
export function showErrorMsg(state: RequestInstanceState, message: string) {
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
export interface RequestInstanceState {
|
export interface RequestInstanceState {
|
||||||
/** whether the request is refreshing token */
|
|
||||||
refreshTokenFn: Promise<boolean> | null;
|
|
||||||
/** the request error message stack */
|
/** the request error message stack */
|
||||||
errMsgStack: string[];
|
errMsgStack: string[];
|
||||||
}
|
}
|
||||||
|
@ -63,9 +63,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
async function login(userName: string, password: string, redirect = true) {
|
async function login(userName: string, password: string, redirect = true) {
|
||||||
startLoading();
|
startLoading();
|
||||||
|
|
||||||
const { data: loginToken, error } = await fetchLogin(userName, password);
|
try {
|
||||||
|
const loginToken = await fetchLogin(userName, password);
|
||||||
if (!error) {
|
|
||||||
const pass = await loginByToken(loginToken);
|
const pass = await loginByToken(loginToken);
|
||||||
|
|
||||||
if (pass) {
|
if (pass) {
|
||||||
@ -81,7 +80,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} catch {
|
||||||
resetStore();
|
resetStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,17 +105,16 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getUserInfo() {
|
async function getUserInfo() {
|
||||||
const { data: info, error } = await fetchGetUserInfo();
|
try {
|
||||||
|
const info = await fetchGetUserInfo();
|
||||||
if (!error) {
|
|
||||||
// update store
|
// update store
|
||||||
Object.assign(userInfo, info);
|
Object.assign(userInfo, info);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
} catch {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function initUserInfo() {
|
async function initUserInfo() {
|
||||||
const hasToken = getToken();
|
const hasToken = getToken();
|
||||||
|
@ -201,11 +201,10 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
if (authRouteMode.value === 'static') {
|
if (authRouteMode.value === 'static') {
|
||||||
addConstantRoutes(staticRoute.constantRoutes);
|
addConstantRoutes(staticRoute.constantRoutes);
|
||||||
} else {
|
} else {
|
||||||
const { data, error } = await fetchGetConstantRoutes();
|
try {
|
||||||
|
const data = await fetchGetConstantRoutes();
|
||||||
if (!error) {
|
|
||||||
addConstantRoutes(data);
|
addConstantRoutes(data);
|
||||||
} else {
|
} catch {
|
||||||
// if fetch constant routes failed, use static constant routes
|
// if fetch constant routes failed, use static constant routes
|
||||||
addConstantRoutes(staticRoute.constantRoutes);
|
addConstantRoutes(staticRoute.constantRoutes);
|
||||||
}
|
}
|
||||||
@ -246,9 +245,9 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
|
|
||||||
/** Init dynamic auth route */
|
/** Init dynamic auth route */
|
||||||
async function initDynamicAuthRoute() {
|
async function initDynamicAuthRoute() {
|
||||||
const { data, error } = await fetchGetUserRoutes();
|
try {
|
||||||
|
const data = await fetchGetUserRoutes();
|
||||||
|
|
||||||
if (!error) {
|
|
||||||
const { routes, home } = data;
|
const { routes, home } = data;
|
||||||
|
|
||||||
addAuthRoutes(routes);
|
addAuthRoutes(routes);
|
||||||
@ -260,7 +259,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
handleUpdateRootRouteRedirect(home);
|
handleUpdateRootRouteRedirect(home);
|
||||||
|
|
||||||
setIsInitAuthRoute(true);
|
setIsInitAuthRoute(true);
|
||||||
} else {
|
} catch {
|
||||||
// if fetch user routes failed, reset store
|
// if fetch user routes failed, reset store
|
||||||
authStore.resetStore();
|
authStore.resetStore();
|
||||||
}
|
}
|
||||||
@ -340,9 +339,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
return isRouteExistByRouteName(routeName, staticAuthRoutes);
|
return isRouteExistByRouteName(routeName, staticAuthRoutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await fetchIsRouteExist(routeName);
|
return fetchIsRouteExist(routeName);
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,7 +19,7 @@ export function filterAuthRoutesByRoles(routes: ElegantConstRoute[], roles: stri
|
|||||||
* @param route Auth route
|
* @param route Auth route
|
||||||
* @param roles Roles
|
* @param roles Roles
|
||||||
*/
|
*/
|
||||||
function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]): ElegantConstRoute[] {
|
function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]) {
|
||||||
const routeRoles = (route.meta && route.meta.roles) || [];
|
const routeRoles = (route.meta && route.meta.roles) || [];
|
||||||
|
|
||||||
// if the route's "roles" is empty, then it is allowed to access
|
// if the route's "roles" is empty, then it is allowed to access
|
||||||
@ -34,11 +34,6 @@ function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]): Eleg
|
|||||||
filterRoute.children = filterRoute.children.flatMap(item => filterAuthRouteByRoles(item, roles));
|
filterRoute.children = filterRoute.children.flatMap(item => filterAuthRouteByRoles(item, roles));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude the route if it has no children after filtering
|
|
||||||
if (filterRoute.children?.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasPermission || isEmptyRoles ? [filterRoute] : [];
|
return hasPermission || isEmptyRoles ? [filterRoute] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +283,8 @@ export function getBreadcrumbsByRoute(
|
|||||||
|
|
||||||
for (const menu of menus) {
|
for (const menu of menus) {
|
||||||
if (menu.key === key) {
|
if (menu.key === key) {
|
||||||
return [transformMenuToBreadcrumb(menu)];
|
const breadcrumbMenu = menu;
|
||||||
|
return [transformMenuToBreadcrumb(breadcrumbMenu)];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (menu.key === activeKey) {
|
if (menu.key === activeKey) {
|
||||||
|
1
src/typings/components.d.ts
vendored
1
src/typings/components.d.ts
vendored
@ -87,6 +87,7 @@ declare module 'vue' {
|
|||||||
NSelect: typeof import('naive-ui')['NSelect']
|
NSelect: typeof import('naive-ui')['NSelect']
|
||||||
NSkeleton: typeof import('naive-ui')['NSkeleton']
|
NSkeleton: typeof import('naive-ui')['NSkeleton']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
|
NSpin: typeof import('naive-ui')['NSpin']
|
||||||
NStatistic: typeof import('naive-ui')['NStatistic']
|
NStatistic: typeof import('naive-ui')['NStatistic']
|
||||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||||
NTab: typeof import('naive-ui')['NTab']
|
NTab: typeof import('naive-ui')['NTab']
|
||||||
|
8
src/typings/global.d.ts
vendored
8
src/typings/global.d.ts
vendored
@ -14,6 +14,14 @@ declare global {
|
|||||||
$notification?: import('naive-ui').NotificationProviderInst;
|
$notification?: import('naive-ui').NotificationProviderInst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ViewTransition {
|
||||||
|
ready: Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Document {
|
||||||
|
startViewTransition?: (callback: () => Promise<void> | void) => ViewTransition;
|
||||||
|
}
|
||||||
|
|
||||||
/** Build time of the project */
|
/** Build time of the project */
|
||||||
export const BUILD_TIME: string;
|
export const BUILD_TIME: string;
|
||||||
}
|
}
|
||||||
|
9
src/typings/naive-ui.d.ts
vendored
9
src/typings/naive-ui.d.ts
vendored
@ -9,7 +9,6 @@ declare namespace NaiveUI {
|
|||||||
type PaginationProps = import('naive-ui').PaginationProps;
|
type PaginationProps = import('naive-ui').PaginationProps;
|
||||||
type TableColumnCheck = import('@sa/hooks').TableColumnCheck;
|
type TableColumnCheck = import('@sa/hooks').TableColumnCheck;
|
||||||
type TableDataWithIndex<T> = import('@sa/hooks').TableDataWithIndex<T>;
|
type TableDataWithIndex<T> = import('@sa/hooks').TableDataWithIndex<T>;
|
||||||
type FlatResponseData<T> = import('@sa/axios').FlatResponseData<T>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the custom column key
|
* the custom column key
|
||||||
@ -26,9 +25,9 @@ declare namespace NaiveUI {
|
|||||||
|
|
||||||
type TableColumn<T> = TableColumnWithKey<T> | DataTableSelectionColumn<T> | DataTableExpandColumn<T>;
|
type TableColumn<T> = TableColumnWithKey<T> | DataTableSelectionColumn<T> | DataTableExpandColumn<T>;
|
||||||
|
|
||||||
type TableApiFn<T = any, R = Api.Common.CommonSearchParams> = (
|
type TableAlovaApiFn<T = any, R = Api.Common.CommonSearchParams> = (
|
||||||
params: R
|
params: R
|
||||||
) => Promise<FlatResponseData<Api.Common.PaginatingQueryRecord<T>>>;
|
) => import('@sa/alova').Method<import('@sa/alova').AlovaGenerics<Api.Common.PaginatingQueryRecord<T>>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the type of table operation
|
* the type of table operation
|
||||||
@ -38,9 +37,9 @@ declare namespace NaiveUI {
|
|||||||
*/
|
*/
|
||||||
type TableOperateType = 'add' | 'edit';
|
type TableOperateType = 'add' | 'edit';
|
||||||
|
|
||||||
type GetTableData<A extends TableApiFn> = A extends TableApiFn<infer T> ? T : never;
|
type GetTableData<A extends TableAlovaApiFn> = A extends TableAlovaApiFn<infer T> ? T : never;
|
||||||
|
|
||||||
type NaiveTableConfig<A extends TableApiFn> = Pick<
|
type NaiveTableConfig<A extends TableAlovaApiFn> = Pick<
|
||||||
import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<TableDataWithIndex<GetTableData<A>>>>,
|
import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<TableDataWithIndex<GetTableData<A>>>>,
|
||||||
'apiFn' | 'apiParams' | 'columns' | 'immediate'
|
'apiFn' | 'apiParams' | 'columns' | 'immediate'
|
||||||
> & {
|
> & {
|
||||||
|
@ -40,7 +40,7 @@ async function handleSubmit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
||||||
<NFormItem path="phone">
|
<NFormItem path="phone">
|
||||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
@ -75,7 +75,7 @@ async function handleAccountLogin(account: Account) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
||||||
<NFormItem path="userName">
|
<NFormItem path="userName">
|
||||||
<NInput v-model:value="model.userName" :placeholder="$t('page.login.common.userNamePlaceholder')" />
|
<NInput v-model:value="model.userName" :placeholder="$t('page.login.common.userNamePlaceholder')" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
@ -46,7 +46,7 @@ async function handleSubmit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
||||||
<NFormItem path="phone">
|
<NFormItem path="phone">
|
||||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
@ -45,7 +45,7 @@ async function handleSubmit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
||||||
<NFormItem path="phone">
|
<NFormItem path="phone">
|
||||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
@ -3,7 +3,7 @@ import { ref } from 'vue';
|
|||||||
import type { 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 { fetchGetAllPages, fetchGetMenuList } from '@/service/api';
|
import { fetchGetMenuList } from '@/service/api';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useTable, useTableOperate } from '@/hooks/common/table';
|
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
@ -18,7 +18,7 @@ const { bool: visible, setTrue: openModal } = useBoolean();
|
|||||||
|
|
||||||
const wrapperRef = ref<HTMLElement | null>(null);
|
const wrapperRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const { columns, columnChecks, data, loading, pagination, getData, getDataByPage } = useTable({
|
const { columns, columnChecks, data, loading, pagination, refresh, reload, getDataByPage } = useTable({
|
||||||
apiFn: fetchGetMenuList,
|
apiFn: fetchGetMenuList,
|
||||||
columns: () => [
|
columns: () => [
|
||||||
{
|
{
|
||||||
@ -170,7 +170,7 @@ const { columns, columnChecks, data, loading, pagination, getData, getDataByPage
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, getData);
|
const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, reload);
|
||||||
|
|
||||||
const operateType = ref<OperateType>('add');
|
const operateType = ref<OperateType>('add');
|
||||||
|
|
||||||
@ -210,20 +210,6 @@ function handleAddChildMenu(item: Api.SystemManage.Menu) {
|
|||||||
|
|
||||||
openModal();
|
openModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
const allPages = ref<string[]>([]);
|
|
||||||
|
|
||||||
async function getAllPages() {
|
|
||||||
const { data: pages } = await fetchGetAllPages();
|
|
||||||
allPages.value = pages || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
getAllPages();
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
|
||||||
init();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -236,7 +222,7 @@ init();
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
@add="handleAdd"
|
@add="handleAdd"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@refresh="getData"
|
@refresh="refresh"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<NDataTable
|
<NDataTable
|
||||||
@ -256,7 +242,6 @@ init();
|
|||||||
v-model:visible="visible"
|
v-model:visible="visible"
|
||||||
:operate-type="operateType"
|
:operate-type="operateType"
|
||||||
:row-data="editingData"
|
:row-data="editingData"
|
||||||
:all-pages="allPages"
|
|
||||||
@submitted="getDataByPage"
|
@submitted="getDataByPage"
|
||||||
/>
|
/>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
import { computed, reactive, watch } from 'vue';
|
||||||
import type { SelectOption } from 'naive-ui';
|
import type { SelectOption } from 'naive-ui';
|
||||||
|
import { useWatcher } from '@sa/alova/client';
|
||||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||||
import { $t } from '@/locales';
|
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 { fetchGetAllRoles } from '@/service/api';
|
import { fetchGetAllPages } from '@/service/api';
|
||||||
import {
|
import {
|
||||||
getLayoutAndPage,
|
getLayoutAndPage,
|
||||||
getPathParamFromRoutePath,
|
getPathParamFromRoutePath,
|
||||||
@ -26,8 +27,6 @@ interface Props {
|
|||||||
operateType: OperateType;
|
operateType: OperateType;
|
||||||
/** the edit menu data or the parent menu data when adding a child menu */
|
/** 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>();
|
||||||
@ -138,8 +137,14 @@ const showLayout = computed(() => model.parentId === 0);
|
|||||||
|
|
||||||
const showPage = computed(() => model.menuType === '2');
|
const showPage = computed(() => model.menuType === '2');
|
||||||
|
|
||||||
|
const { data: allPagesRaw, loading: loadingPages } = useWatcher(fetchGetAllPages, [visible], {
|
||||||
|
initialData: [],
|
||||||
|
middleware(_, next) {
|
||||||
|
return visible.value ? next() : undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
const pageOptions = computed(() => {
|
const pageOptions = computed(() => {
|
||||||
const allPages = [...props.allPages];
|
const allPages = [...allPagesRaw.value];
|
||||||
|
|
||||||
if (model.routeName && !allPages.includes(model.routeName)) {
|
if (model.routeName && !allPages.includes(model.routeName)) {
|
||||||
allPages.unshift(model.routeName);
|
allPages.unshift(model.routeName);
|
||||||
@ -165,20 +170,18 @@ const layoutOptions: CommonType.Option[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/** the enabled role options */
|
/** the enabled role options */
|
||||||
const roleOptions = ref<CommonType.Option<string>[]>([]);
|
// const { data: roleOptionsRaw, loading } = useWatcher(fetchGetAllRoles, [visible], {
|
||||||
|
// initialData: [],
|
||||||
async function getRoleOptions() {
|
// middleware(_, next) {
|
||||||
const { error, data } = await fetchGetAllRoles();
|
// return visible.value ? next() : undefined;
|
||||||
|
// }
|
||||||
if (!error) {
|
// });
|
||||||
const options = data.map(item => ({
|
// const roleOptions = computed<CommonType.Option<string>[]>(() => {
|
||||||
label: item.roleName,
|
// return roleOptionsRaw.value.map(item => ({
|
||||||
value: item.roleCode
|
// label: item.roleName,
|
||||||
}));
|
// value: item.roleCode
|
||||||
|
// }));
|
||||||
roleOptions.value = [...options];
|
// });
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInitModel() {
|
function handleInitModel() {
|
||||||
Object.assign(model, createDefaultModel());
|
Object.assign(model, createDefaultModel());
|
||||||
@ -266,7 +269,6 @@ watch(visible, () => {
|
|||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
handleInitModel();
|
handleInitModel();
|
||||||
restoreValidation();
|
restoreValidation();
|
||||||
getRoleOptions();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -311,6 +313,7 @@ watch(
|
|||||||
<NFormItemGi v-if="showPage" span="24 m:12" :label="$t('page.manage.menu.page')" path="page">
|
<NFormItemGi v-if="showPage" span="24 m:12" :label="$t('page.manage.menu.page')" path="page">
|
||||||
<NSelect
|
<NSelect
|
||||||
v-model:value="model.page"
|
v-model:value="model.page"
|
||||||
|
:loading="loadingPages"
|
||||||
:options="pageOptions"
|
:options="pageOptions"
|
||||||
:placeholder="$t('page.manage.menu.form.page')"
|
:placeholder="$t('page.manage.menu.form.page')"
|
||||||
/>
|
/>
|
||||||
|
@ -15,7 +15,8 @@ const {
|
|||||||
columnChecks,
|
columnChecks,
|
||||||
data,
|
data,
|
||||||
loading,
|
loading,
|
||||||
getData,
|
reload,
|
||||||
|
refresh,
|
||||||
getDataByPage,
|
getDataByPage,
|
||||||
mobilePagination,
|
mobilePagination,
|
||||||
searchParams,
|
searchParams,
|
||||||
@ -116,7 +117,7 @@ const {
|
|||||||
onBatchDeleted,
|
onBatchDeleted,
|
||||||
onDeleted
|
onDeleted
|
||||||
// closeDrawer
|
// closeDrawer
|
||||||
} = useTableOperate(data, getData);
|
} = useTableOperate(data, reload);
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
// request
|
// request
|
||||||
@ -148,7 +149,7 @@ function edit(id: number) {
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
@add="handleAdd"
|
@add="handleAdd"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@refresh="getData"
|
@refresh="refresh"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<NDataTable
|
<NDataTable
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, shallowRef, watch } from 'vue';
|
import { computed, shallowRef, watch } from 'vue';
|
||||||
|
import { useWatcher } from '@sa/alova/client';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { fetchGetAllPages, fetchGetMenuTree } from '@/service/api';
|
import { fetchGetAllPages, fetchGetMenuTree } from '@/service/api';
|
||||||
|
|
||||||
@ -38,16 +39,12 @@ async function updateHome(val: string) {
|
|||||||
home.value = val;
|
home.value = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pages = shallowRef<string[]>([]);
|
const { data: pages, loading: loadingPages } = useWatcher(fetchGetAllPages, [visible], {
|
||||||
|
initialData: [],
|
||||||
async function getPages() {
|
middleware(_, next) {
|
||||||
const { error, data } = await fetchGetAllPages();
|
return visible.value ? next() : undefined;
|
||||||
|
|
||||||
if (!error) {
|
|
||||||
pages.value = data;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
const pageSelectOptions = computed(() => {
|
const pageSelectOptions = computed(() => {
|
||||||
const opts: CommonType.Option[] = pages.value.map(page => ({
|
const opts: CommonType.Option[] = pages.value.map(page => ({
|
||||||
label: page,
|
label: page,
|
||||||
@ -57,15 +54,12 @@ const pageSelectOptions = computed(() => {
|
|||||||
return opts;
|
return opts;
|
||||||
});
|
});
|
||||||
|
|
||||||
const tree = shallowRef<Api.SystemManage.MenuTree[]>([]);
|
const { data: tree, loading: loadingTree } = useWatcher(fetchGetMenuTree, [visible], {
|
||||||
|
initialData: [],
|
||||||
async function getTree() {
|
middleware(_, next) {
|
||||||
const { error, data } = await fetchGetMenuTree();
|
return visible.value ? next() : undefined;
|
||||||
|
|
||||||
if (!error) {
|
|
||||||
tree.value = data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const checks = shallowRef<number[]>([]);
|
const checks = shallowRef<number[]>([]);
|
||||||
|
|
||||||
@ -86,8 +80,6 @@ function handleSubmit() {
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
getHome();
|
getHome();
|
||||||
getPages();
|
|
||||||
getTree();
|
|
||||||
getChecks();
|
getChecks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +94,14 @@ watch(visible, val => {
|
|||||||
<NModal v-model:show="visible" :title="title" preset="card" class="w-480px">
|
<NModal v-model:show="visible" :title="title" preset="card" class="w-480px">
|
||||||
<div class="flex-y-center gap-16px pb-12px">
|
<div class="flex-y-center gap-16px pb-12px">
|
||||||
<div>{{ $t('page.manage.menu.home') }}</div>
|
<div>{{ $t('page.manage.menu.home') }}</div>
|
||||||
<NSelect :value="home" :options="pageSelectOptions" size="small" class="w-160px" @update:value="updateHome" />
|
<NSelect
|
||||||
|
:loading="loadingPages"
|
||||||
|
:value="home"
|
||||||
|
:options="pageSelectOptions"
|
||||||
|
size="small"
|
||||||
|
class="w-160px"
|
||||||
|
@update:value="updateHome"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<NTree
|
<NTree
|
||||||
v-model:checked-keys="checks"
|
v-model:checked-keys="checks"
|
||||||
@ -113,7 +112,11 @@ watch(visible, val => {
|
|||||||
virtual-scroll
|
virtual-scroll
|
||||||
block-line
|
block-line
|
||||||
class="h-280px"
|
class="h-280px"
|
||||||
/>
|
>
|
||||||
|
<template v-if="loadingTree" #empty>
|
||||||
|
<NSpin size="small"></NSpin>
|
||||||
|
</template>
|
||||||
|
</NTree>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NSpace justify="end">
|
<NSpace justify="end">
|
||||||
<NButton size="small" class="mt-16px" @click="closeModal">
|
<NButton size="small" class="mt-16px" @click="closeModal">
|
||||||
|
@ -10,22 +10,11 @@ import UserSearch from './modules/user-search.vue';
|
|||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
const {
|
const { columns, columnChecks, data, refresh, reload, loading, mobilePagination, searchParams, resetSearchParams } =
|
||||||
columns,
|
useTable({
|
||||||
columnChecks,
|
|
||||||
data,
|
|
||||||
getData,
|
|
||||||
getDataByPage,
|
|
||||||
loading,
|
|
||||||
mobilePagination,
|
|
||||||
searchParams,
|
|
||||||
resetSearchParams
|
|
||||||
} = useTable({
|
|
||||||
apiFn: fetchGetUserList,
|
apiFn: fetchGetUserList,
|
||||||
showTotal: true,
|
showTotal: true,
|
||||||
apiParams: {
|
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
|
// 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
|
// the value can not be undefined, otherwise the property in Form will not be reactive
|
||||||
status: null,
|
status: null,
|
||||||
@ -147,7 +136,7 @@ const {
|
|||||||
onBatchDeleted,
|
onBatchDeleted,
|
||||||
onDeleted
|
onDeleted
|
||||||
// closeDrawer
|
// closeDrawer
|
||||||
} = useTableOperate(data, getData);
|
} = useTableOperate(data, reload);
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
// request
|
// request
|
||||||
@ -170,7 +159,7 @@ function edit(id: number) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||||
<UserSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" />
|
<UserSearch v-model:model="searchParams" @reset="resetSearchParams" @search="reload" />
|
||||||
<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
|
||||||
@ -179,7 +168,7 @@ function edit(id: number) {
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
@add="handleAdd"
|
@add="handleAdd"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@refresh="getData"
|
@refresh="refresh"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<NDataTable
|
<NDataTable
|
||||||
@ -199,7 +188,7 @@ function edit(id: number) {
|
|||||||
v-model:visible="drawerVisible"
|
v-model:visible="drawerVisible"
|
||||||
:operate-type="operateType"
|
:operate-type="operateType"
|
||||||
:row-data="editingData"
|
:row-data="editingData"
|
||||||
@submitted="getDataByPage"
|
@submitted="reload"
|
||||||
/>
|
/>
|
||||||
</NCard>
|
</NCard>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
import { computed, reactive, watch } from 'vue';
|
||||||
|
import { useWatcher } from '@sa/alova/client';
|
||||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||||
import { fetchGetAllRoles } from '@/service/api';
|
import { fetchGetAllRoles } from '@/service/api';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
@ -66,13 +67,14 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** the enabled role options */
|
/** the enabled role options */
|
||||||
const roleOptions = ref<CommonType.Option<string>[]>([]);
|
const { data: roleOptionsRaw, loading } = useWatcher(fetchGetAllRoles, [visible], {
|
||||||
|
initialData: [],
|
||||||
async function getRoleOptions() {
|
middleware(_, next) {
|
||||||
const { error, data } = await fetchGetAllRoles();
|
return visible.value ? next() : undefined;
|
||||||
|
}
|
||||||
if (!error) {
|
});
|
||||||
const options = data.map(item => ({
|
const roleOptions = computed<CommonType.Option<string>[]>(() => {
|
||||||
|
const options = roleOptionsRaw.value.map(item => ({
|
||||||
label: item.roleName,
|
label: item.roleName,
|
||||||
value: item.roleCode
|
value: item.roleCode
|
||||||
}));
|
}));
|
||||||
@ -85,9 +87,8 @@ async function getRoleOptions() {
|
|||||||
}));
|
}));
|
||||||
// end
|
// end
|
||||||
|
|
||||||
roleOptions.value = [...userRoleOptions, ...options];
|
return [...userRoleOptions, ...options];
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function handleInitModel() {
|
function handleInitModel() {
|
||||||
Object.assign(model, createDefaultModel());
|
Object.assign(model, createDefaultModel());
|
||||||
@ -113,7 +114,6 @@ watch(visible, () => {
|
|||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
handleInitModel();
|
handleInitModel();
|
||||||
restoreValidation();
|
restoreValidation();
|
||||||
getRoleOptions();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -148,6 +148,7 @@ watch(visible, () => {
|
|||||||
<NSelect
|
<NSelect
|
||||||
v-model:value="model.userRoles"
|
v-model:value="model.userRoles"
|
||||||
multiple
|
multiple
|
||||||
|
:loading="loading"
|
||||||
:options="roleOptions"
|
:options="roleOptions"
|
||||||
:placeholder="$t('page.manage.user.form.userRole')"
|
:placeholder="$t('page.manage.user.form.userRole')"
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user