mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
Compare commits
57 Commits
flow-v1.1.
...
2504498eb5
Author | SHA1 | Date | |
---|---|---|---|
2504498eb5 | |||
5581a4a59f | |||
7c83ce7937 | |||
89719abe34 | |||
d73111116a | |||
29a2a5c66a | |||
4005763c00 | |||
a55b4dc073 | |||
be8f915a0c | |||
8d7f91dccf | |||
33ade53904 | |||
358e129765 | |||
ab9c84d831 | |||
7651816495 | |||
2af57caf44 | |||
4539fe01fb | |||
4e9839bd48 | |||
a15b683b1d | |||
4699654fc1 | |||
01116c9ffa | |||
9df8d2f55f | |||
710374398a | |||
52318c106d | |||
9ea56c9b82 | |||
9027632bef | |||
b96c46baa9 | |||
2d31d7dc62 | |||
e538355f2b | |||
f89835578c | |||
4eb77eac78 | |||
adca2e26be | |||
ff576f3f42 | |||
8fcc70d73d | |||
923eb98a5c | |||
87adc35f2e | |||
ee4341457a | |||
8a7cd5934b | |||
c962f7b2c5 | |||
8cc5177cda | |||
3a343eea33 | |||
f83eefbc3e | |||
936b834e62 | |||
c965140b87 | |||
32b8f99071 | |||
abaaa4a068 | |||
b4e125300e | |||
50a5cba088 | |||
d6c8142bb4 | |||
8146858b96 | |||
8b8a2083bb | |||
6207292d81 | |||
b4e5c6d990 | |||
b6ac3106ce | |||
d37ce04606 | |||
8439a60070 | |||
f238fcbd47 | |||
8ba71a0857 |
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
@ -4,7 +4,18 @@
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"editor.formatOnSave": false,
|
||||
"eslint.validate": ["html", "css", "scss", "json", "jsonc"],
|
||||
"eslint.validate": [
|
||||
"html",
|
||||
"css",
|
||||
"scss",
|
||||
"json",
|
||||
"jsonc",
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue"
|
||||
],
|
||||
"i18n-ally.displayLanguage": "zh-cn",
|
||||
"i18n-ally.enabledParsers": ["ts"],
|
||||
"i18n-ally.enabledFrameworks": ["vue"],
|
||||
|
@ -35,23 +35,4 @@ UPDATE `sys_menu` SET `component` = 'FrameView', `query_param` = 'https://previe
|
||||
UPDATE `sys_menu` SET `path` = 'https://gitee.com/xlsea/ruoyi-plus-soybean', `component` = 'FrameView', `icon` = 'local-icon-gitee', `menu_name` = 'RuoYi-Plus-Soybean' WHERE `menu_id` = 4;
|
||||
|
||||
-- plus-ui 需要禁用的页面
|
||||
UPDATE `sys_menu` SET `status` = '1' WHERE `menu_id` IN ( '116', '130', '131', '132' );
|
||||
-- 工作流要启用的页面
|
||||
UPDATE `sys_menu` SET `status` = '0' WHERE `menu_id` IN ( '11616', '11618', '11700', '11701' );
|
||||
|
||||
-- 工作流菜单
|
||||
UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'hugeicons:flow-square' WHERE `menu_id` = 11616;
|
||||
UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'fluent:notepad-person-16-regular' WHERE `menu_id` = 11618;
|
||||
UPDATE `sys_menu` SET `component` = 'workflow/task/taskWaiting/index', `icon` = 'ri:todo-line' WHERE `menu_id` = 11619;
|
||||
UPDATE `sys_menu` SET `icon` = 'weui:setting-outlined' WHERE `menu_id` = 11620;
|
||||
UPDATE `sys_menu` SET `icon` = 'ri:instance-line' WHERE `menu_id` = 11621;
|
||||
UPDATE `sys_menu` SET `icon` = 'carbon:category' WHERE `menu_id` = 11622;
|
||||
UPDATE `sys_menu` SET `component` = 'workflow/task/myDocument/index', `icon` = 'hugeicons:start-up-02' WHERE `menu_id` = 11629;
|
||||
UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'lucide:monitor-cog' WHERE `menu_id` = 11630;
|
||||
UPDATE `sys_menu` SET `component` = 'workflow/task/allTaskWaiting/index', `icon` = 'ri:todo-line' WHERE `menu_id` = 11631;
|
||||
UPDATE `sys_menu` SET `component` = 'workflow/task/taskFinish/index', `icon` = 'hugeicons:task-done-01' WHERE `menu_id` = 11632;
|
||||
UPDATE `sys_menu` SET `path` = 'taskCopy', `component` = 'workflow/task/taskCopy/index', `icon` = 'mynaui:copy' WHERE `menu_id` = 11633;
|
||||
UPDATE `sys_menu` SET `icon` = 'ic:twotone-time-to-leave' WHERE `menu_id` = 11638;
|
||||
UPDATE `sys_menu` SET `icon` = 'material-symbols:design-services-outline', `path` = 'design', `component` = 'workflow/design/index' WHERE `menu_id` = 11700;
|
||||
UPDATE `sys_menu` SET `icon` = 'ic:twotone-time-to-leave' WHERE `menu_id` = 11701;
|
||||
UPDATE `sys_menu` SET `icon` = 'material-symbols:regular-expression-rounded' WHERE `menu_id` = 11801;
|
||||
UPDATE `sys_menu` SET `status` = '1' WHERE `menu_id` IN ( '116', '130', '131', '132', '11700', '11701' );
|
||||
|
10
package.json
10
package.json
@ -83,16 +83,16 @@
|
||||
"vue": "3.5.17",
|
||||
"vue-advanced-cropper": "^2.8.9",
|
||||
"vue-draggable-plus": "0.6.0",
|
||||
"vue-i18n": "11.1.9",
|
||||
"vue-i18n": "11.1.10",
|
||||
"vue-router": "4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@elegant-router/vue": "0.3.8",
|
||||
"@iconify/json": "2.2.357",
|
||||
"@iconify/json": "2.2.359",
|
||||
"@sa/scripts": "workspace:*",
|
||||
"@sa/uno-preset": "workspace:*",
|
||||
"@soybeanjs/eslint-config": "1.7.1",
|
||||
"@types/node": "24.0.13",
|
||||
"@types/node": "24.0.15",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@unocss/eslint-config": "66.3.3",
|
||||
"@unocss/preset-icons": "66.3.3",
|
||||
@ -112,14 +112,14 @@
|
||||
"typescript": "5.8.3",
|
||||
"unplugin-icons": "22.1.0",
|
||||
"unplugin-vue-components": "28.8.0",
|
||||
"vite": "7.0.4",
|
||||
"vite": "7.0.5",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-progress": "0.0.7",
|
||||
"vite-plugin-static-copy": "^3.1.0",
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vite-plugin-vue-devtools": "7.7.7",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "3.0.1"
|
||||
"vue-tsc": "3.0.3"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"commit-msg": "pnpm sa git-commit-verify",
|
||||
|
@ -13,11 +13,12 @@ import type {
|
||||
ResponseType
|
||||
} from './type';
|
||||
|
||||
function createCommonRequest<ResponseData = any>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const opts = createDefaultOptions<ResponseData>(options);
|
||||
function createCommonRequest<
|
||||
ResponseData,
|
||||
ApiData = ResponseData,
|
||||
State extends Record<string, unknown> = Record<string, unknown>
|
||||
>(axiosConfig?: CreateAxiosDefaults, options?: Partial<RequestOption<ResponseData, ApiData, State>>) {
|
||||
const opts = createDefaultOptions<ResponseData, ApiData, State>(options);
|
||||
|
||||
const axiosConf = createAxiosConfig(axiosConfig);
|
||||
const instance = axios.create(axiosConf);
|
||||
@ -80,14 +81,6 @@ function createCommonRequest<ResponseData = any>(
|
||||
}
|
||||
);
|
||||
|
||||
function cancelRequest(requestId: string) {
|
||||
const abortController = abortControllerMap.get(requestId);
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
abortControllerMap.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAllRequest() {
|
||||
abortControllerMap.forEach(abortController => {
|
||||
abortController.abort();
|
||||
@ -98,7 +91,6 @@ function createCommonRequest<ResponseData = any>(
|
||||
return {
|
||||
instance,
|
||||
opts,
|
||||
cancelRequest,
|
||||
cancelAllRequest
|
||||
};
|
||||
}
|
||||
@ -109,27 +101,27 @@ function createCommonRequest<ResponseData = any>(
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||
export function createRequest<ResponseData, ApiData, State extends Record<string, unknown>>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
options?: Partial<RequestOption<ResponseData, ApiData, State>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
const { instance, opts, cancelAllRequest } = createCommonRequest<ResponseData, ApiData, State>(axiosConfig, options);
|
||||
|
||||
const request: RequestInstance<State> = async function request<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
) {
|
||||
const request: RequestInstance<ApiData, State> = async function request<
|
||||
T extends ApiData = ApiData,
|
||||
R extends ResponseType = 'json'
|
||||
>(config: CustomAxiosRequestConfig) {
|
||||
const response: AxiosResponse<ResponseData> = await instance(config);
|
||||
|
||||
const responseType = response.config?.responseType || 'json';
|
||||
|
||||
if (responseType === 'json') {
|
||||
return opts.transformBackendResponse(response);
|
||||
return opts.transform(response);
|
||||
}
|
||||
|
||||
return response.data as MappedType<R, T>;
|
||||
} as RequestInstance<State>;
|
||||
} as RequestInstance<ApiData, State>;
|
||||
|
||||
request.cancelRequest = cancelRequest;
|
||||
request.cancelAllRequest = cancelAllRequest;
|
||||
request.state = {} as State;
|
||||
|
||||
@ -144,14 +136,14 @@ export function createRequest<ResponseData = any, State = Record<string, unknown
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createFlatRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||
export function createFlatRequest<ResponseData, ApiData, State extends Record<string, unknown>>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
options?: Partial<RequestOption<ResponseData, ApiData, State>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
const { instance, opts, cancelAllRequest } = createCommonRequest<ResponseData, ApiData, State>(axiosConfig, options);
|
||||
|
||||
const flatRequest: FlatRequestInstance<State, ResponseData> = async function flatRequest<
|
||||
T = any,
|
||||
const flatRequest: FlatRequestInstance<ResponseData, ApiData, State> = async function flatRequest<
|
||||
T extends ApiData = ApiData,
|
||||
R extends ResponseType = 'json'
|
||||
>(config: CustomAxiosRequestConfig) {
|
||||
try {
|
||||
@ -160,20 +152,21 @@ export function createFlatRequest<ResponseData = any, State = Record<string, unk
|
||||
const responseType = response.config?.responseType || 'json';
|
||||
|
||||
if (responseType === 'json') {
|
||||
const data = opts.transformBackendResponse(response);
|
||||
const data = await opts.transform(response);
|
||||
|
||||
return { data, error: null, response };
|
||||
}
|
||||
|
||||
return { data: response.data as MappedType<R, T>, error: null };
|
||||
return { data: response.data as MappedType<R, T>, error: null, response };
|
||||
} catch (error) {
|
||||
return { data: null, error, response: (error as AxiosError<ResponseData>).response };
|
||||
}
|
||||
} as FlatRequestInstance<State, ResponseData>;
|
||||
} as FlatRequestInstance<ResponseData, ApiData, State>;
|
||||
|
||||
flatRequest.cancelRequest = cancelRequest;
|
||||
flatRequest.cancelAllRequest = cancelAllRequest;
|
||||
flatRequest.state = {} as State;
|
||||
flatRequest.state = {
|
||||
...opts.defaultState
|
||||
} as State;
|
||||
|
||||
return flatRequest;
|
||||
}
|
||||
|
@ -4,15 +4,27 @@ import { stringify } from 'qs';
|
||||
import { isHttpSuccess } from './shared';
|
||||
import type { RequestOption } from './type';
|
||||
|
||||
export function createDefaultOptions<ResponseData = any>(options?: Partial<RequestOption<ResponseData>>) {
|
||||
const opts: RequestOption<ResponseData> = {
|
||||
export function createDefaultOptions<
|
||||
ResponseData,
|
||||
ApiData = ResponseData,
|
||||
State extends Record<string, unknown> = Record<string, unknown>
|
||||
>(options?: Partial<RequestOption<ResponseData, ApiData, State>>) {
|
||||
const opts: RequestOption<ResponseData, ApiData, State> = {
|
||||
defaultState: {} as State,
|
||||
transform: async response => response.data as unknown as ApiData,
|
||||
transformBackendResponse: async response => response.data as unknown as ApiData,
|
||||
onRequest: async config => config,
|
||||
isBackendSuccess: _response => true,
|
||||
onBackendFail: async () => {},
|
||||
transformBackendResponse: async response => response.data,
|
||||
onError: async () => {}
|
||||
};
|
||||
|
||||
if (options?.transform) {
|
||||
opts.transform = options.transform;
|
||||
} else {
|
||||
opts.transform = options?.transformBackendResponse || opts.transform;
|
||||
}
|
||||
|
||||
Object.assign(opts, options);
|
||||
|
||||
return opts;
|
||||
|
@ -8,7 +8,30 @@ export type ContentType =
|
||||
| 'application/x-www-form-urlencoded'
|
||||
| 'application/octet-stream';
|
||||
|
||||
export interface RequestOption<ResponseData = any> {
|
||||
export type ResponseTransform<Input = any, Output = any> = (input: Input) => Output | Promise<Output>;
|
||||
|
||||
export interface RequestOption<
|
||||
ResponseData,
|
||||
ApiData = ResponseData,
|
||||
State extends Record<string, unknown> = Record<string, unknown>
|
||||
> {
|
||||
/**
|
||||
* The default state
|
||||
*/
|
||||
defaultState?: State;
|
||||
/**
|
||||
* transform the response data to the api data
|
||||
*
|
||||
* @param response Axios response
|
||||
*/
|
||||
transform: ResponseTransform<AxiosResponse<ResponseData>, ApiData>;
|
||||
/**
|
||||
* transform the response data to the api data
|
||||
*
|
||||
* @deprecated use `transform` instead, will be removed in the next major version v3
|
||||
* @param response Axios response
|
||||
*/
|
||||
transformBackendResponse: ResponseTransform<AxiosResponse<ResponseData>, ApiData>;
|
||||
/**
|
||||
* The hook before request
|
||||
*
|
||||
@ -35,12 +58,6 @@ export interface RequestOption<ResponseData = any> {
|
||||
response: AxiosResponse<ResponseData>,
|
||||
instance: AxiosInstance
|
||||
) => Promise<AxiosResponse | null> | Promise<void>;
|
||||
/**
|
||||
* transform backend response when the responseType is json
|
||||
*
|
||||
* @param response Axios response
|
||||
*/
|
||||
transformBackendResponse(response: AxiosResponse<ResponseData>): any | Promise<any>;
|
||||
/**
|
||||
* The hook to handle error
|
||||
*
|
||||
@ -68,15 +85,7 @@ export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<Axi
|
||||
responseType?: R;
|
||||
};
|
||||
|
||||
export interface RequestInstanceCommon<T> {
|
||||
/**
|
||||
* cancel the request by request id
|
||||
*
|
||||
* if the request provide abort controller sign from config, it will not collect in the abort controller map
|
||||
*
|
||||
* @param requestId
|
||||
*/
|
||||
cancelRequest: (requestId: string) => void;
|
||||
export interface RequestInstanceCommon<State extends Record<string, unknown>> {
|
||||
/**
|
||||
* cancel all request
|
||||
*
|
||||
@ -84,32 +93,35 @@ export interface RequestInstanceCommon<T> {
|
||||
*/
|
||||
cancelAllRequest: () => void;
|
||||
/** you can set custom state in the request instance */
|
||||
state: T;
|
||||
state: State;
|
||||
}
|
||||
|
||||
/** The request instance */
|
||||
export interface RequestInstance<S = Record<string, unknown>> extends RequestInstanceCommon<S> {
|
||||
<T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>;
|
||||
export interface RequestInstance<ApiData, State extends Record<string, unknown>> extends RequestInstanceCommon<State> {
|
||||
<T extends ApiData = ApiData, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig<R>
|
||||
): Promise<MappedType<R, T>>;
|
||||
}
|
||||
|
||||
export type FlatResponseSuccessData<T = any, ResponseData = any> = {
|
||||
data: T;
|
||||
export type FlatResponseSuccessData<ResponseData, ApiData> = {
|
||||
data: ApiData;
|
||||
error: null;
|
||||
response: AxiosResponse<ResponseData>;
|
||||
};
|
||||
|
||||
export type FlatResponseFailData<ResponseData = any> = {
|
||||
export type FlatResponseFailData<ResponseData> = {
|
||||
data: null;
|
||||
error: AxiosError<ResponseData>;
|
||||
response: AxiosResponse<ResponseData>;
|
||||
};
|
||||
|
||||
export type FlatResponseData<T = any, ResponseData = any> =
|
||||
| FlatResponseSuccessData<T, ResponseData>
|
||||
export type FlatResponseData<ResponseData, ApiData> =
|
||||
| FlatResponseSuccessData<ResponseData, ApiData>
|
||||
| FlatResponseFailData<ResponseData>;
|
||||
|
||||
export interface FlatRequestInstance<S = Record<string, unknown>, ResponseData = any> extends RequestInstanceCommon<S> {
|
||||
<T = any, R extends ResponseType = 'json'>(
|
||||
export interface FlatRequestInstance<ResponseData, ApiData, State extends Record<string, unknown>>
|
||||
extends RequestInstanceCommon<State> {
|
||||
<T extends ApiData = ApiData, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig<R>
|
||||
): Promise<FlatResponseData<MappedType<R, T>, ResponseData>>;
|
||||
): Promise<FlatResponseData<ResponseData, MappedType<R, T>>>;
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ import useLoading from './use-loading';
|
||||
import useCountDown from './use-count-down';
|
||||
import useContext from './use-context';
|
||||
import useSvgIconRender from './use-svg-icon-render';
|
||||
import useHookTable from './use-table';
|
||||
import useTable from './use-table';
|
||||
|
||||
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable };
|
||||
|
||||
export * from './use-signal';
|
||||
export * from './use-table';
|
||||
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useTable };
|
||||
export type * from './use-table';
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { inject, provide } from 'vue';
|
||||
import type { InjectionKey } from 'vue';
|
||||
|
||||
/**
|
||||
* Use context
|
||||
@ -12,7 +11,7 @@ import type { InjectionKey } from 'vue';
|
||||
* import { ref } from 'vue';
|
||||
* import { useContext } from '@sa/hooks';
|
||||
*
|
||||
* export const { setupStore, useStore } = useContext('demo', () => {
|
||||
* export const [provideDemoContext, useDemoContext] = useContext('demo', () => {
|
||||
* const count = ref(0);
|
||||
*
|
||||
* function increment() {
|
||||
@ -35,10 +34,10 @@ import type { InjectionKey } from 'vue';
|
||||
* <div>A</div>
|
||||
* </template>
|
||||
* <script setup lang="ts">
|
||||
* import { setupStore } from './context';
|
||||
* import { provideDemoContext } from './context';
|
||||
*
|
||||
* setupStore();
|
||||
* // const { increment } = setupStore(); // also can control the store in the parent component
|
||||
* provideDemoContext();
|
||||
* // const { increment } = provideDemoContext(); // also can control the store in the parent component
|
||||
* </script>
|
||||
* ``` // B.vue
|
||||
* ```vue
|
||||
@ -46,9 +45,9 @@ import type { InjectionKey } from 'vue';
|
||||
* <div>B</div>
|
||||
* </template>
|
||||
* <script setup lang="ts">
|
||||
* import { useStore } from './context';
|
||||
* import { useDemoContext } from './context';
|
||||
*
|
||||
* const { count, increment } = useStore();
|
||||
* const { count, increment } = useDemoContext();
|
||||
* </script>
|
||||
* ```;
|
||||
*
|
||||
@ -57,40 +56,41 @@ import type { InjectionKey } from 'vue';
|
||||
* @param contextName Context name
|
||||
* @param fn Context function
|
||||
*/
|
||||
export default function useContext<T extends (...args: any[]) => any>(contextName: string, fn: T) {
|
||||
type Context = ReturnType<T>;
|
||||
export default function useContext<Arguments extends Array<any>, T>(
|
||||
contextName: string,
|
||||
composable: (...args: Arguments) => T
|
||||
) {
|
||||
const key = Symbol(contextName);
|
||||
|
||||
const { useProvide, useInject: useStore } = createContext<Context>(contextName);
|
||||
/**
|
||||
* Injects the context value.
|
||||
*
|
||||
* @param consumerName - The name of the component that is consuming the context. If provided, the component must be
|
||||
* used within the context provider.
|
||||
* @param defaultValue - The default value to return if the context is not provided.
|
||||
* @returns The context value.
|
||||
*/
|
||||
const useInject = <N extends string | null | undefined = undefined>(
|
||||
consumerName?: N,
|
||||
defaultValue?: T
|
||||
): N extends null | undefined ? T | null : T => {
|
||||
const value = inject(key, defaultValue);
|
||||
|
||||
function setupStore(...args: Parameters<T>) {
|
||||
const context: Context = fn(...args);
|
||||
return useProvide(context);
|
||||
}
|
||||
if (consumerName && !value) {
|
||||
throw new Error(`\`${consumerName}\` must be used within \`${contextName}\``);
|
||||
}
|
||||
|
||||
return {
|
||||
/** Setup store in the parent component */
|
||||
setupStore,
|
||||
/** Use store in the child component */
|
||||
useStore
|
||||
// @ts-expect-error - we want to return null if the value is undefined or null
|
||||
return value || null;
|
||||
};
|
||||
}
|
||||
|
||||
/** Create context */
|
||||
function createContext<T>(contextName: string) {
|
||||
const injectKey: InjectionKey<T> = Symbol(contextName);
|
||||
const useProvide = (...args: Arguments) => {
|
||||
const value = composable(...args);
|
||||
|
||||
function useProvide(context: T) {
|
||||
provide(injectKey, context);
|
||||
provide(key, value);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function useInject() {
|
||||
return inject(injectKey) as T;
|
||||
}
|
||||
|
||||
return {
|
||||
useProvide,
|
||||
useInject
|
||||
return value;
|
||||
};
|
||||
|
||||
return [useProvide, useInject] as const;
|
||||
}
|
||||
|
@ -6,31 +6,31 @@ import type {
|
||||
CreateAxiosDefaults,
|
||||
CustomAxiosRequestConfig,
|
||||
MappedType,
|
||||
RequestInstanceCommon,
|
||||
RequestOption,
|
||||
ResponseType
|
||||
} from '@sa/axios';
|
||||
import useLoading from './use-loading';
|
||||
|
||||
export type HookRequestInstanceResponseSuccessData<T = any> = {
|
||||
data: Ref<T>;
|
||||
export type HookRequestInstanceResponseSuccessData<ApiData> = {
|
||||
data: Ref<ApiData>;
|
||||
error: Ref<null>;
|
||||
};
|
||||
|
||||
export type HookRequestInstanceResponseFailData<ResponseData = any> = {
|
||||
export type HookRequestInstanceResponseFailData<ResponseData> = {
|
||||
data: Ref<null>;
|
||||
error: Ref<AxiosError<ResponseData>>;
|
||||
};
|
||||
|
||||
export type HookRequestInstanceResponseData<T = any, ResponseData = any> = {
|
||||
export type HookRequestInstanceResponseData<ResponseData, ApiData> = {
|
||||
loading: Ref<boolean>;
|
||||
} & (HookRequestInstanceResponseSuccessData<T> | HookRequestInstanceResponseFailData<ResponseData>);
|
||||
} & (HookRequestInstanceResponseSuccessData<ApiData> | HookRequestInstanceResponseFailData<ResponseData>);
|
||||
|
||||
export interface HookRequestInstance<ResponseData = any> {
|
||||
<T = any, R extends ResponseType = 'json'>(
|
||||
export interface HookRequestInstance<ResponseData, ApiData, State extends Record<string, unknown>>
|
||||
extends RequestInstanceCommon<State> {
|
||||
<T extends ApiData = ApiData, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
): HookRequestInstanceResponseData<MappedType<R, T>, ResponseData>;
|
||||
cancelRequest: (requestId: string) => void;
|
||||
cancelAllRequest: () => void;
|
||||
): HookRequestInstanceResponseData<ResponseData, MappedType<R, T>>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,25 +39,26 @@ export interface HookRequestInstance<ResponseData = any> {
|
||||
* @param axiosConfig
|
||||
* @param options
|
||||
*/
|
||||
export default function createHookRequest<ResponseData = any>(
|
||||
export default function createHookRequest<ResponseData, ApiData, State extends Record<string, unknown>>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
options?: Partial<RequestOption<ResponseData, ApiData, State>>
|
||||
) {
|
||||
const request = createFlatRequest<ResponseData>(axiosConfig, options);
|
||||
const request = createFlatRequest<ResponseData, ApiData, State>(axiosConfig, options);
|
||||
|
||||
const hookRequest: HookRequestInstance<ResponseData> = function hookRequest<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
) {
|
||||
const hookRequest: HookRequestInstance<ResponseData, ApiData, State> = function hookRequest<
|
||||
T extends ApiData = ApiData,
|
||||
R extends ResponseType = 'json'
|
||||
>(config: CustomAxiosRequestConfig) {
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
|
||||
const data = ref<MappedType<R, T> | null>(null) as Ref<MappedType<R, T>>;
|
||||
const error = ref<AxiosError<ResponseData> | null>(null) as Ref<AxiosError<ResponseData> | null>;
|
||||
const data = ref(null) as Ref<MappedType<R, T>>;
|
||||
const error = ref(null) as Ref<AxiosError<ResponseData> | null>;
|
||||
|
||||
startLoading();
|
||||
|
||||
request(config).then(res => {
|
||||
if (res.data) {
|
||||
data.value = res.data;
|
||||
data.value = res.data as MappedType<R, T>;
|
||||
} else {
|
||||
error.value = res.error;
|
||||
}
|
||||
@ -70,9 +71,8 @@ export default function createHookRequest<ResponseData = any>(
|
||||
data,
|
||||
error
|
||||
};
|
||||
} as HookRequestInstance<ResponseData>;
|
||||
} as HookRequestInstance<ResponseData, ApiData, State>;
|
||||
|
||||
hookRequest.cancelRequest = request.cancelRequest;
|
||||
hookRequest.cancelAllRequest = request.cancelAllRequest;
|
||||
|
||||
return hookRequest;
|
||||
|
@ -1,144 +0,0 @@
|
||||
import { computed, ref, shallowRef, triggerRef } from 'vue';
|
||||
import type {
|
||||
ComputedGetter,
|
||||
DebuggerOptions,
|
||||
Ref,
|
||||
ShallowRef,
|
||||
WritableComputedOptions,
|
||||
WritableComputedRef
|
||||
} from 'vue';
|
||||
|
||||
type Updater<T> = (value: T) => T;
|
||||
type Mutator<T> = (value: T) => void;
|
||||
|
||||
/**
|
||||
* Signal is a reactive value that can be set, updated or mutated
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const count = useSignal(0);
|
||||
*
|
||||
* // `watchEffect`
|
||||
* watchEffect(() => {
|
||||
* console.log(count());
|
||||
* });
|
||||
*
|
||||
* // watch
|
||||
* watch(count, value => {
|
||||
* console.log(value);
|
||||
* });
|
||||
*
|
||||
* // useComputed
|
||||
* const double = useComputed(() => count() * 2);
|
||||
* const writeableDouble = useComputed({
|
||||
* get: () => count() * 2,
|
||||
* set: value => count.set(value / 2)
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export interface Signal<T> {
|
||||
(): Readonly<T>;
|
||||
/**
|
||||
* Set the value of the signal
|
||||
*
|
||||
* It recommend use `set` for primitive values
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
set(value: T): void;
|
||||
/**
|
||||
* Update the value of the signal using an updater function
|
||||
*
|
||||
* It recommend use `update` for non-primitive values, only the first level of the object will be reactive.
|
||||
*
|
||||
* @param updater
|
||||
*/
|
||||
update(updater: Updater<T>): void;
|
||||
/**
|
||||
* Mutate the value of the signal using a mutator function
|
||||
*
|
||||
* this action will call `triggerRef`, so the value will be tracked on `watchEffect`.
|
||||
*
|
||||
* It recommend use `mutate` for non-primitive values, all levels of the object will be reactive.
|
||||
*
|
||||
* @param mutator
|
||||
*/
|
||||
mutate(mutator: Mutator<T>): void;
|
||||
/**
|
||||
* Get the reference of the signal
|
||||
*
|
||||
* Sometimes it can be useful to make `v-model` work with the signal
|
||||
*
|
||||
* ```vue
|
||||
* <template>
|
||||
* <input v-model="model.count" />
|
||||
* </template>;
|
||||
*
|
||||
* <script setup lang="ts">
|
||||
* const state = useSignal({ count: 0 }, { useRef: true });
|
||||
*
|
||||
* const model = state.getRef();
|
||||
* </script>
|
||||
* ```
|
||||
*/
|
||||
getRef(): Readonly<ShallowRef<Readonly<T>>>;
|
||||
}
|
||||
|
||||
export interface ReadonlySignal<T> {
|
||||
(): Readonly<T>;
|
||||
}
|
||||
|
||||
export interface SignalOptions {
|
||||
/**
|
||||
* Whether to use `ref` to store the value
|
||||
*
|
||||
* @default false use `sharedRef` to store the value
|
||||
*/
|
||||
useRef?: boolean;
|
||||
}
|
||||
|
||||
export function useSignal<T>(initialValue: T, options?: SignalOptions): Signal<T> {
|
||||
const { useRef } = options || {};
|
||||
|
||||
const state = useRef ? (ref(initialValue) as Ref<T>) : shallowRef(initialValue);
|
||||
|
||||
return createSignal(state);
|
||||
}
|
||||
|
||||
export function useComputed<T>(getter: ComputedGetter<T>, debugOptions?: DebuggerOptions): ReadonlySignal<T>;
|
||||
export function useComputed<T>(options: WritableComputedOptions<T>, debugOptions?: DebuggerOptions): Signal<T>;
|
||||
export function useComputed<T>(
|
||||
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
|
||||
debugOptions?: DebuggerOptions
|
||||
) {
|
||||
const isGetter = typeof getterOrOptions === 'function';
|
||||
|
||||
const computedValue = computed(getterOrOptions as any, debugOptions);
|
||||
|
||||
if (isGetter) {
|
||||
return () => computedValue.value as ReadonlySignal<T>;
|
||||
}
|
||||
|
||||
return createSignal(computedValue);
|
||||
}
|
||||
|
||||
function createSignal<T>(state: ShallowRef<T> | WritableComputedRef<T>): Signal<T> {
|
||||
const signal = () => state.value;
|
||||
|
||||
signal.set = (value: T) => {
|
||||
state.value = value;
|
||||
};
|
||||
|
||||
signal.update = (updater: Updater<T>) => {
|
||||
state.value = updater(state.value);
|
||||
};
|
||||
|
||||
signal.mutate = (mutator: Mutator<T>) => {
|
||||
mutator(state.value);
|
||||
triggerRef(state);
|
||||
};
|
||||
|
||||
signal.getRef = () => state as Readonly<ShallowRef<Readonly<T>>>;
|
||||
|
||||
return signal;
|
||||
}
|
@ -1,12 +1,20 @@
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import type { Ref, VNodeChild } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import useBoolean from './use-boolean';
|
||||
import useLoading from './use-loading';
|
||||
|
||||
export type MaybePromise<T> = T | Promise<T>;
|
||||
export interface PaginationData<T> {
|
||||
data: T[];
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export type ApiFn = (args: any) => Promise<unknown>;
|
||||
type GetApiData<ApiData, Pagination extends boolean> = Pagination extends true ? PaginationData<ApiData> : ApiData[];
|
||||
|
||||
type Transform<ResponseData, ApiData, Pagination extends boolean> = (
|
||||
response: ResponseData
|
||||
) => GetApiData<ApiData, Pagination>;
|
||||
|
||||
export type TableColumnCheckTitle = string | ((...args: any) => VNodeChild);
|
||||
|
||||
@ -14,77 +22,64 @@ export type TableColumnCheck = {
|
||||
key: string;
|
||||
title: TableColumnCheckTitle;
|
||||
checked: boolean;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
export type TableDataWithIndex<T> = T & { index: number };
|
||||
|
||||
export type TransformedData<T> = {
|
||||
data: TableDataWithIndex<T>[];
|
||||
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[];
|
||||
export interface UseTableOptions<ResponseData, ApiData, Column, Pagination extends boolean> {
|
||||
/**
|
||||
* api function to get table data
|
||||
*/
|
||||
api: () => Promise<ResponseData>;
|
||||
/**
|
||||
* whether to enable pagination
|
||||
*/
|
||||
pagination?: Pagination;
|
||||
/**
|
||||
* transform api response to table data
|
||||
*/
|
||||
transform: Transform<ResponseData, ApiData, Pagination>;
|
||||
/**
|
||||
* columns factory
|
||||
*/
|
||||
columns: () => Column[];
|
||||
/**
|
||||
* get column checks
|
||||
*
|
||||
* @param columns
|
||||
*/
|
||||
getColumnChecks: (columns: C[]) => TableColumnCheck[];
|
||||
getColumnChecks: (columns: Column[]) => TableColumnCheck[];
|
||||
/**
|
||||
* get columns
|
||||
*
|
||||
* @param columns
|
||||
*/
|
||||
getColumns: (columns: C[], checks: TableColumnCheck[]) => C[];
|
||||
getColumns: (columns: Column[], checks: TableColumnCheck[]) => Column[];
|
||||
/**
|
||||
* callback when response fetched
|
||||
*
|
||||
* @param transformed transformed data
|
||||
*/
|
||||
onFetched?: (transformed: TransformedData<T>) => MaybePromise<void>;
|
||||
onFetched?: (data: GetApiData<ApiData, Pagination>) => void | Promise<void>;
|
||||
/**
|
||||
* whether to get data immediately
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
immediate?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<A, T, C>) {
|
||||
export default function useTable<ResponseData, ApiData, Column, Pagination extends boolean>(
|
||||
options: UseTableOptions<ResponseData, ApiData, Column, Pagination>
|
||||
) {
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
const { bool: empty, setBool: setEmpty } = useBoolean();
|
||||
|
||||
const { transformer, immediate = true, getColumnChecks, getColumns } = config;
|
||||
const { api, pagination, transform, columns, getColumnChecks, getColumns, onFetched, immediate = true } = options;
|
||||
|
||||
let currentApiFn = config.apiFn;
|
||||
const apiParams = config.apiParams;
|
||||
const data = ref([]) as Ref<ApiData[]>;
|
||||
|
||||
const searchParams: NonNullable<Parameters<A>[0]> = reactive(jsonClone({ ...apiParams }));
|
||||
const columnChecks = ref(getColumnChecks(columns())) as Ref<TableColumnCheck[]>;
|
||||
|
||||
const allColumns = ref(config.columns()) as Ref<C[]>;
|
||||
|
||||
const data: Ref<TableDataWithIndex<T>[]> = ref([]);
|
||||
|
||||
const columnChecks: Ref<TableColumnCheck[]> = ref(getColumnChecks(config.columns()));
|
||||
|
||||
const columns = computed(() => getColumns(allColumns.value, columnChecks.value));
|
||||
const $columns = computed(() => getColumns(columns(), 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);
|
||||
const defaultChecks = getColumnChecks(columns());
|
||||
|
||||
columnChecks.value = defaultChecks.map(col => ({
|
||||
...col,
|
||||
@ -93,52 +88,21 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
startLoading();
|
||||
try {
|
||||
startLoading();
|
||||
|
||||
const formattedParams = formatSearchParams(searchParams);
|
||||
const response = await api();
|
||||
|
||||
const response = await currentApiFn(formattedParams);
|
||||
const transformed = transform(response);
|
||||
|
||||
const transformed = transformer(response as Awaited<ReturnType<A>>);
|
||||
data.value = getTableData(transformed, pagination);
|
||||
|
||||
data.value = transformed.data;
|
||||
setEmpty(data.value.length === 0);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function updateApiFn(newApiFn: A) {
|
||||
currentApiFn = newApiFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* update search params
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
function updateSearchParams(params: Partial<Parameters<A>[0]>) {
|
||||
Object.assign(searchParams, params);
|
||||
}
|
||||
|
||||
/** reset search params */
|
||||
function resetSearchParams() {
|
||||
Object.assign(searchParams, jsonClone(apiParams));
|
||||
getData();
|
||||
await onFetched?.(transformed);
|
||||
} finally {
|
||||
endLoading();
|
||||
}
|
||||
}
|
||||
|
||||
if (immediate) {
|
||||
@ -149,13 +113,20 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
|
||||
loading,
|
||||
empty,
|
||||
data,
|
||||
columns,
|
||||
columns: $columns,
|
||||
columnChecks,
|
||||
reloadColumns,
|
||||
getData,
|
||||
searchParams,
|
||||
updateSearchParams,
|
||||
resetSearchParams,
|
||||
updateApiFn
|
||||
getData
|
||||
};
|
||||
}
|
||||
|
||||
function getTableData<ApiData, Pagination extends boolean>(
|
||||
data: GetApiData<ApiData, Pagination>,
|
||||
pagination?: Pagination
|
||||
) {
|
||||
if (pagination) {
|
||||
return (data as PaginationData<ApiData>).data;
|
||||
}
|
||||
|
||||
return data as ApiData[];
|
||||
}
|
||||
|
@ -127,7 +127,6 @@ function handleClickMask() {
|
||||
:class="[
|
||||
style['layout-header'],
|
||||
commonClass,
|
||||
headerClass,
|
||||
headerLeftGapClass,
|
||||
{ 'absolute top-0 left-0 w-full': fixedHeaderAndTab }
|
||||
]"
|
||||
|
@ -6,12 +6,6 @@ interface AdminLayoutHeaderConfig {
|
||||
* @default true
|
||||
*/
|
||||
headerVisible?: boolean;
|
||||
/**
|
||||
* Header class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
headerClass?: string;
|
||||
/**
|
||||
* Header height
|
||||
*
|
||||
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "@sa/fetch",
|
||||
"version": "1.3.15",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"ofetch": "1.4.1"
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { ofetch } from 'ofetch';
|
||||
import type { FetchOptions } from 'ofetch';
|
||||
|
||||
export function createRequest(options: FetchOptions) {
|
||||
const request = ofetch.create(options);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
export default createRequest;
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
"devDependencies": {
|
||||
"@soybeanjs/changelog": "0.3.24",
|
||||
"bumpp": "10.2.0",
|
||||
"c12": "3.0.4",
|
||||
"c12": "3.1.0",
|
||||
"cac": "6.7.14",
|
||||
"consola": "3.4.2",
|
||||
"enquirer": "2.4.1",
|
||||
|
@ -32,7 +32,8 @@ export function createStorage<T extends object>(type: StorageType, storagePrefix
|
||||
storageData = JSON.parse(json);
|
||||
} catch {}
|
||||
|
||||
if (storageData) {
|
||||
// storageData may be `false` if it is boolean type
|
||||
if (storageData !== null) {
|
||||
return storageData as T[K];
|
||||
}
|
||||
}
|
||||
|
200
pnpm-lock.yaml
generated
200
pnpm-lock.yaml
generated
@ -87,8 +87,8 @@ importers:
|
||||
specifier: 0.6.0
|
||||
version: 0.6.0(@types/sortablejs@1.15.8)
|
||||
vue-i18n:
|
||||
specifier: 11.1.9
|
||||
version: 11.1.9(vue@3.5.17(typescript@5.8.3))
|
||||
specifier: 11.1.10
|
||||
version: 11.1.10(vue@3.5.17(typescript@5.8.3))
|
||||
vue-router:
|
||||
specifier: 4.5.1
|
||||
version: 4.5.1(vue@3.5.17(typescript@5.8.3))
|
||||
@ -97,8 +97,8 @@ importers:
|
||||
specifier: 0.3.8
|
||||
version: 0.3.8
|
||||
'@iconify/json':
|
||||
specifier: 2.2.357
|
||||
version: 2.2.357
|
||||
specifier: 2.2.359
|
||||
version: 2.2.359
|
||||
'@sa/scripts':
|
||||
specifier: workspace:*
|
||||
version: link:packages/scripts
|
||||
@ -109,8 +109,8 @@ importers:
|
||||
specifier: 1.7.1
|
||||
version: 1.7.1(@typescript-eslint/utils@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(@unocss/eslint-config@66.3.3(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.35.1(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@2.4.2))))(eslint@9.31.0(jiti@2.4.2))(svelte-eslint-parser@1.3.0)(typescript@5.8.3)(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@2.4.2)))
|
||||
'@types/node':
|
||||
specifier: 24.0.13
|
||||
version: 24.0.13
|
||||
specifier: 24.0.15
|
||||
version: 24.0.15
|
||||
'@types/nprogress':
|
||||
specifier: 0.2.3
|
||||
version: 0.2.3
|
||||
@ -131,13 +131,13 @@ importers:
|
||||
version: 66.3.3
|
||||
'@unocss/vite':
|
||||
specifier: 66.3.3
|
||||
version: 66.3.3(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
version: 66.3.3(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: 6.0.0
|
||||
version: 6.0.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
version: 6.0.0(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
'@vitejs/plugin-vue-jsx':
|
||||
specifier: 5.0.1
|
||||
version: 5.0.1(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
version: 5.0.1(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
consola:
|
||||
specifier: 3.4.2
|
||||
version: 3.4.2
|
||||
@ -169,29 +169,29 @@ importers:
|
||||
specifier: 28.8.0
|
||||
version: 28.8.0(@babel/parser@7.28.0)(vue@3.5.17(typescript@5.8.3))
|
||||
vite:
|
||||
specifier: 7.0.4
|
||||
version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
specifier: 7.0.5
|
||||
version: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite-plugin-monaco-editor:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(monaco-editor@0.52.2)
|
||||
vite-plugin-progress:
|
||||
specifier: 0.0.7
|
||||
version: 0.0.7(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
version: 0.0.7(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
vite-plugin-static-copy:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.1(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
version: 3.1.1(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
vite-plugin-svg-icons:
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
version: 2.0.1(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
vite-plugin-vue-devtools:
|
||||
specifier: 7.7.7
|
||||
version: 7.7.7(rollup@4.45.1)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
version: 7.7.7(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
vue-eslint-parser:
|
||||
specifier: 10.2.0
|
||||
version: 10.2.0(eslint@9.31.0(jiti@2.4.2))
|
||||
vue-tsc:
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1(typescript@5.8.3)
|
||||
specifier: 3.0.3
|
||||
version: 3.0.3(typescript@5.8.3)
|
||||
|
||||
packages/alova:
|
||||
dependencies:
|
||||
@ -255,12 +255,6 @@ importers:
|
||||
specifier: 0.9.1
|
||||
version: 0.9.1
|
||||
|
||||
packages/ofetch:
|
||||
dependencies:
|
||||
ofetch:
|
||||
specifier: 1.4.1
|
||||
version: 1.4.1
|
||||
|
||||
packages/scripts:
|
||||
devDependencies:
|
||||
'@soybeanjs/changelog':
|
||||
@ -270,8 +264,8 @@ importers:
|
||||
specifier: 10.2.0
|
||||
version: 10.2.0
|
||||
c12:
|
||||
specifier: 3.0.4
|
||||
version: 3.0.4
|
||||
specifier: 3.1.0
|
||||
version: 3.1.0
|
||||
cac:
|
||||
specifier: 6.7.14
|
||||
version: 6.7.14
|
||||
@ -764,8 +758,8 @@ packages:
|
||||
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@iconify/json@2.2.357':
|
||||
resolution: {integrity: sha512-v8fr/KwcJ0qsoEJ69k1+M928bfzNmmApyJBTIAwwIzHZrVEUneHTEOJRy7OVYKisauBMVVH067I2uFNoPA92iA==}
|
||||
'@iconify/json@2.2.359':
|
||||
resolution: {integrity: sha512-nOIaROD3xeLiFGvJu0YIgeu4Hqbmz6T71b0lsFv1TY6Uu6Lk/5Z8GhDByIE2/zfgxvxfv3f+5A/DkLHmMXYu8Q==}
|
||||
|
||||
'@iconify/types@2.0.0':
|
||||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||
@ -778,16 +772,16 @@ packages:
|
||||
peerDependencies:
|
||||
vue: '>=3'
|
||||
|
||||
'@intlify/core-base@11.1.9':
|
||||
resolution: {integrity: sha512-Lrdi4wp3XnGhWmB/mMD/XtfGUw1Jt+PGpZI/M63X1ZqhTDjNHRVCs/i8vv8U1cwaj1A9fb0bkCQHLSL0SK+pIQ==}
|
||||
'@intlify/core-base@11.1.10':
|
||||
resolution: {integrity: sha512-JhRb40hD93Vk0BgMgDc/xMIFtdXPHoytzeK6VafBNOj6bb6oUZrGamXkBKecMsmGvDQQaPRGG2zpa25VCw8pyw==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/message-compiler@11.1.9':
|
||||
resolution: {integrity: sha512-84SNs3Ikjg0rD1bOuchzb3iK1vR2/8nxrkyccIl5DjFTeMzE/Fxv6X+A7RN5ZXjEWelc1p5D4kHA6HEOhlKL5Q==}
|
||||
'@intlify/message-compiler@11.1.10':
|
||||
resolution: {integrity: sha512-TABl3c8tSLWbcD+jkQTyBhrnW251dzqW39MPgEUCsd69Ua3ceoimsbIzvkcPzzZvt1QDxNkenMht+5//V3JvLQ==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@11.1.9':
|
||||
resolution: {integrity: sha512-H/83xgU1l8ox+qG305p6ucmoy93qyjIPnvxGWRA7YdOoHe1tIiW9IlEu4lTdsOR7cfP1ecrwyflQSqXdXBacXA==}
|
||||
'@intlify/shared@11.1.10':
|
||||
resolution: {integrity: sha512-6ZW/f3Zzjxfa1Wh0tYQI5pLKUtU+SY7l70pEG+0yd0zjcsYcK0EBt6Fz30Dy0tZhEqemziQQy2aNU3GJzyrMUA==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@isaacs/balanced-match@4.0.1':
|
||||
@ -1160,8 +1154,8 @@ packages:
|
||||
'@types/node@10.17.60':
|
||||
resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==}
|
||||
|
||||
'@types/node@24.0.13':
|
||||
resolution: {integrity: sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==}
|
||||
'@types/node@24.0.15':
|
||||
resolution: {integrity: sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==}
|
||||
|
||||
'@types/nprogress@0.2.3':
|
||||
resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==}
|
||||
@ -1442,14 +1436,14 @@ packages:
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
vue: ^3.2.25
|
||||
|
||||
'@volar/language-core@2.4.17':
|
||||
resolution: {integrity: sha512-chmRZMbKmcGpKMoO7Reb70uiLrzo0KWC2CkFttKUuKvrE+VYgi+fL9vWMJ07Fv5ulX0V1TAyyacN9q3nc5/ecA==}
|
||||
'@volar/language-core@2.4.20':
|
||||
resolution: {integrity: sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA==}
|
||||
|
||||
'@volar/source-map@2.4.17':
|
||||
resolution: {integrity: sha512-QDybtQyO3Ms/NjFqNHTC5tbDN2oK5VH7ZaKrcubtfHBDj63n2pizHC3wlMQ+iT55kQXZUUAbmBX5L1C8CHFeBw==}
|
||||
'@volar/source-map@2.4.20':
|
||||
resolution: {integrity: sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg==}
|
||||
|
||||
'@volar/typescript@2.4.17':
|
||||
resolution: {integrity: sha512-3paEFNh4P5DkgNUB2YkTRrfUekN4brAXxd3Ow1syMqdIPtCZHbUy4AW99S5RO/7mzyTWPMdDSo3mqTpB/LPObQ==}
|
||||
'@volar/typescript@2.4.20':
|
||||
resolution: {integrity: sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ==}
|
||||
|
||||
'@vue/babel-helper-vue-transform-on@1.4.0':
|
||||
resolution: {integrity: sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==}
|
||||
@ -1499,8 +1493,8 @@ packages:
|
||||
'@vue/devtools-shared@7.7.7':
|
||||
resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==}
|
||||
|
||||
'@vue/language-core@3.0.1':
|
||||
resolution: {integrity: sha512-sq+/Mc1IqIexWEQ+Q2XPiDb5SxSvY5JPqHnMOl/PlF5BekslzduX8dglSkpC17VeiAQB6dpS+4aiwNLJRduCNw==}
|
||||
'@vue/language-core@3.0.3':
|
||||
resolution: {integrity: sha512-I9wY0ULMN9tMSua+2C7g+ez1cIziVMUzIHlDYGSl2rtru3Eh4sXj95vZ+4GBuXwwPnEmYfzSApVbXiVbI8V5Gg==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
@ -1713,8 +1707,8 @@ packages:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
c12@3.0.4:
|
||||
resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==}
|
||||
c12@3.1.0:
|
||||
resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==}
|
||||
peerDependencies:
|
||||
magicast: ^0.3.5
|
||||
peerDependenciesMeta:
|
||||
@ -4025,8 +4019,8 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
|
||||
|
||||
vite@7.0.4:
|
||||
resolution: {integrity: sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==}
|
||||
vite@7.0.5:
|
||||
resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@ -4110,8 +4104,8 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.4.37
|
||||
|
||||
vue-i18n@11.1.9:
|
||||
resolution: {integrity: sha512-N9ZTsXdRmX38AwS9F6Rh93RtPkvZTkSy/zNv63FTIwZCUbLwwrpqlKz9YQuzFLdlvRdZTnWAUE5jMxr8exdl7g==}
|
||||
vue-i18n@11.1.10:
|
||||
resolution: {integrity: sha512-C+IwnSg8QDSOAox0gdFYP5tsKLx5jNWxiawNoiNB/Tw4CReXmM1VJMXbduhbrEzAFLhreqzfDocuSVjGbxQrag==}
|
||||
engines: {node: '>= 16'}
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
@ -4121,8 +4115,8 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
|
||||
vue-tsc@3.0.1:
|
||||
resolution: {integrity: sha512-UvMLQD0hAGL1g/NfEQelnSVB4H5gtf/gz2lJKjMMwWNOUmSNyWkejwJagAxEbSjtV5CPPJYslOtoSuqJ63mhdg==}
|
||||
vue-tsc@3.0.3:
|
||||
resolution: {integrity: sha512-uU1OMSzWE8/y0+kDTc0iEIu9v82bmFkGyJpAO/x3wQqBkkHkButKgtygREyOkxL4E/xtcf/ExvgNhhjdzonldw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '>=5.0.0'
|
||||
@ -4636,7 +4630,7 @@ snapshots:
|
||||
|
||||
'@humanwhocodes/retry@0.4.3': {}
|
||||
|
||||
'@iconify/json@2.2.357':
|
||||
'@iconify/json@2.2.359':
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
pathe: 1.1.2
|
||||
@ -4661,17 +4655,17 @@ snapshots:
|
||||
'@iconify/types': 2.0.0
|
||||
vue: 3.5.17(typescript@5.8.3)
|
||||
|
||||
'@intlify/core-base@11.1.9':
|
||||
'@intlify/core-base@11.1.10':
|
||||
dependencies:
|
||||
'@intlify/message-compiler': 11.1.9
|
||||
'@intlify/shared': 11.1.9
|
||||
'@intlify/message-compiler': 11.1.10
|
||||
'@intlify/shared': 11.1.10
|
||||
|
||||
'@intlify/message-compiler@11.1.9':
|
||||
'@intlify/message-compiler@11.1.10':
|
||||
dependencies:
|
||||
'@intlify/shared': 11.1.9
|
||||
'@intlify/shared': 11.1.10
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@intlify/shared@11.1.9': {}
|
||||
'@intlify/shared@11.1.10': {}
|
||||
|
||||
'@isaacs/balanced-match@4.0.1': {}
|
||||
|
||||
@ -4965,7 +4959,7 @@ snapshots:
|
||||
|
||||
'@types/node@10.17.60': {}
|
||||
|
||||
'@types/node@24.0.13':
|
||||
'@types/node@24.0.15':
|
||||
dependencies:
|
||||
undici-types: 7.8.0
|
||||
|
||||
@ -4979,7 +4973,7 @@ snapshots:
|
||||
|
||||
'@types/svgo@2.6.4':
|
||||
dependencies:
|
||||
'@types/node': 24.0.13
|
||||
'@types/node': 24.0.15
|
||||
|
||||
'@types/web-bluetooth@0.0.21': {}
|
||||
|
||||
@ -5210,7 +5204,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@unocss/core': 66.3.3
|
||||
|
||||
'@unocss/vite@66.3.3(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
|
||||
'@unocss/vite@66.3.3(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@unocss/config': 66.3.3
|
||||
@ -5221,7 +5215,7 @@ snapshots:
|
||||
pathe: 2.0.3
|
||||
tinyglobby: 0.2.14
|
||||
unplugin-utils: 0.2.4
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
@ -5284,32 +5278,32 @@ snapshots:
|
||||
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
||||
optional: true
|
||||
|
||||
'@vitejs/plugin-vue-jsx@5.0.1(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
|
||||
'@vitejs/plugin-vue-jsx@5.0.1(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
'@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0)
|
||||
'@rolldown/pluginutils': 1.0.0-beta.27
|
||||
'@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.28.0)
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vue: 3.5.17(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitejs/plugin-vue@6.0.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
|
||||
'@vitejs/plugin-vue@6.0.0(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.19
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vue: 3.5.17(typescript@5.8.3)
|
||||
|
||||
'@volar/language-core@2.4.17':
|
||||
'@volar/language-core@2.4.20':
|
||||
dependencies:
|
||||
'@volar/source-map': 2.4.17
|
||||
'@volar/source-map': 2.4.20
|
||||
|
||||
'@volar/source-map@2.4.17': {}
|
||||
'@volar/source-map@2.4.20': {}
|
||||
|
||||
'@volar/typescript@2.4.17':
|
||||
'@volar/typescript@2.4.20':
|
||||
dependencies:
|
||||
'@volar/language-core': 2.4.17
|
||||
'@volar/language-core': 2.4.20
|
||||
path-browserify: 1.0.1
|
||||
vscode-uri: 3.1.0
|
||||
|
||||
@ -5383,14 +5377,14 @@ snapshots:
|
||||
dependencies:
|
||||
'@vue/devtools-kit': 7.7.7
|
||||
|
||||
'@vue/devtools-core@7.7.7(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
|
||||
'@vue/devtools-core@7.7.7(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vue/devtools-kit': 7.7.7
|
||||
'@vue/devtools-shared': 7.7.7
|
||||
mitt: 3.0.1
|
||||
nanoid: 5.1.5
|
||||
pathe: 2.0.3
|
||||
vite-hot-client: 2.1.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
vite-hot-client: 2.1.0(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
vue: 3.5.17(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- vite
|
||||
@ -5409,16 +5403,16 @@ snapshots:
|
||||
dependencies:
|
||||
rfdc: 1.4.1
|
||||
|
||||
'@vue/language-core@3.0.1(typescript@5.8.3)':
|
||||
'@vue/language-core@3.0.3(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@volar/language-core': 2.4.17
|
||||
'@volar/language-core': 2.4.20
|
||||
'@vue/compiler-dom': 3.5.17
|
||||
'@vue/compiler-vue2': 2.7.16
|
||||
'@vue/shared': 3.5.17
|
||||
alien-signals: 2.0.5
|
||||
minimatch: 10.0.3
|
||||
muggle-string: 0.4.1
|
||||
path-browserify: 1.0.1
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
@ -5623,7 +5617,7 @@ snapshots:
|
||||
dependencies:
|
||||
ansis: 4.1.0
|
||||
args-tokenizer: 0.3.0
|
||||
c12: 3.0.4
|
||||
c12: 3.1.0
|
||||
cac: 6.7.14
|
||||
escalade: 3.2.0
|
||||
jsonc-parser: 3.3.1
|
||||
@ -5639,7 +5633,7 @@ snapshots:
|
||||
dependencies:
|
||||
run-applescript: 7.0.0
|
||||
|
||||
c12@3.0.4:
|
||||
c12@3.1.0:
|
||||
dependencies:
|
||||
chokidar: 4.0.3
|
||||
confbox: 0.2.2
|
||||
@ -8136,11 +8130,11 @@ snapshots:
|
||||
evtd: 0.2.4
|
||||
vue: 3.5.17(typescript@5.8.3)
|
||||
|
||||
vite-hot-client@2.1.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
vite-hot-client@2.1.0(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
|
||||
vite-plugin-inspect@0.8.9(rollup@4.45.1)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
vite-plugin-inspect@0.8.9(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@antfu/utils': 0.7.10
|
||||
'@rollup/pluginutils': 5.2.0(rollup@4.45.1)
|
||||
@ -8151,7 +8145,7 @@ snapshots:
|
||||
perfect-debounce: 1.0.0
|
||||
picocolors: 1.1.1
|
||||
sirv: 3.0.1
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
@ -8160,23 +8154,23 @@ snapshots:
|
||||
dependencies:
|
||||
monaco-editor: 0.52.2
|
||||
|
||||
vite-plugin-progress@0.0.7(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
vite-plugin-progress@0.0.7(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
picocolors: 1.1.1
|
||||
progress: 2.0.3
|
||||
rd: 2.0.1
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
|
||||
vite-plugin-static-copy@3.1.1(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
vite-plugin-static-copy@3.1.1(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
chokidar: 3.6.0
|
||||
fs-extra: 11.3.0
|
||||
p-map: 7.0.3
|
||||
picocolors: 1.1.1
|
||||
tinyglobby: 0.2.14
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
|
||||
vite-plugin-svg-icons@2.0.1(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
vite-plugin-svg-icons@2.0.1(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@types/svgo': 2.6.4
|
||||
cors: 2.8.5
|
||||
@ -8186,27 +8180,27 @@ snapshots:
|
||||
pathe: 0.2.0
|
||||
svg-baker: 1.7.0
|
||||
svgo: 2.8.0
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-vue-devtools@7.7.7(rollup@4.45.1)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)):
|
||||
vite-plugin-vue-devtools@7.7.7(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-core': 7.7.7(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
'@vue/devtools-core': 7.7.7(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
|
||||
'@vue/devtools-kit': 7.7.7
|
||||
'@vue/devtools-shared': 7.7.7
|
||||
execa: 9.6.0
|
||||
sirv: 3.0.1
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite-plugin-inspect: 0.8.9(rollup@4.45.1)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
vite-plugin-vue-inspector: 5.3.2(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite-plugin-inspect: 0.8.9(rollup@4.45.1)(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
vite-plugin-vue-inspector: 5.3.2(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
- rollup
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
vite-plugin-vue-inspector@5.3.2(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
vite-plugin-vue-inspector@5.3.2(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
'@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.0)
|
||||
@ -8217,11 +8211,11 @@ snapshots:
|
||||
'@vue/compiler-dom': 3.5.17
|
||||
kolorist: 1.8.0
|
||||
magic-string: 0.30.17
|
||||
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0):
|
||||
vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.6
|
||||
fdir: 6.4.6(picomatch@4.0.3)
|
||||
@ -8230,7 +8224,7 @@ snapshots:
|
||||
rollup: 4.45.1
|
||||
tinyglobby: 0.2.14
|
||||
optionalDependencies:
|
||||
'@types/node': 24.0.13
|
||||
'@types/node': 24.0.15
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.4.2
|
||||
sass: 1.89.2
|
||||
@ -8275,10 +8269,10 @@ snapshots:
|
||||
dependencies:
|
||||
vue: 3.5.17(typescript@5.8.3)
|
||||
|
||||
vue-i18n@11.1.9(vue@3.5.17(typescript@5.8.3)):
|
||||
vue-i18n@11.1.10(vue@3.5.17(typescript@5.8.3)):
|
||||
dependencies:
|
||||
'@intlify/core-base': 11.1.9
|
||||
'@intlify/shared': 11.1.9
|
||||
'@intlify/core-base': 11.1.10
|
||||
'@intlify/shared': 11.1.10
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.17(typescript@5.8.3)
|
||||
|
||||
@ -8287,10 +8281,10 @@ snapshots:
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.17(typescript@5.8.3)
|
||||
|
||||
vue-tsc@3.0.1(typescript@5.8.3):
|
||||
vue-tsc@3.0.3(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@volar/typescript': 2.4.17
|
||||
'@vue/language-core': 3.0.1(typescript@5.8.3)
|
||||
'@volar/typescript': 2.4.20
|
||||
'@vue/language-core': 3.0.3(typescript@5.8.3)
|
||||
typescript: 5.8.3
|
||||
|
||||
vue@3.5.17(typescript@5.8.3):
|
||||
|
19
src/App.vue
19
src/App.vue
@ -4,7 +4,6 @@ import { NConfigProvider, darkTheme } from 'naive-ui';
|
||||
import type { WatermarkProps } from 'naive-ui';
|
||||
import { useAppStore } from './store/modules/app';
|
||||
import { useThemeStore } from './store/modules/theme';
|
||||
import { useAuthStore } from './store/modules/auth';
|
||||
import { naiveDateLocales, naiveLocales } from './locales/naive';
|
||||
|
||||
defineOptions({
|
||||
@ -13,7 +12,6 @@ defineOptions({
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { userInfo } = useAuthStore();
|
||||
|
||||
const naiveDarkTheme = computed(() => (themeStore.darkMode ? darkTheme : undefined));
|
||||
|
||||
@ -26,24 +24,19 @@ const naiveDateLocale = computed(() => {
|
||||
});
|
||||
|
||||
const watermarkProps = computed<WatermarkProps>(() => {
|
||||
const appTitle = import.meta.env.VITE_APP_TITLE || 'RuoYi-Vue-Plus';
|
||||
const content =
|
||||
themeStore.watermark.enableUserName && userInfo.user?.userName
|
||||
? `${userInfo.user?.nickName}@${appTitle} ${userInfo.user?.userName}`
|
||||
: appTitle;
|
||||
return {
|
||||
content,
|
||||
content: themeStore.watermarkContent,
|
||||
cross: true,
|
||||
fullscreen: true,
|
||||
fontSize: 14,
|
||||
fontColor: themeStore.darkMode ? 'rgba(200, 200, 200, 0.03)' : 'rgba(200, 200, 200, 0.2)',
|
||||
lineHeight: 14,
|
||||
width: 200,
|
||||
height: 300,
|
||||
width: 384,
|
||||
height: 384,
|
||||
xOffset: 12,
|
||||
yOffset: 60,
|
||||
rotate: -18,
|
||||
zIndex: 9999
|
||||
rotate: -13,
|
||||
zIndex: 9999,
|
||||
fontColor: themeStore.darkMode ? 'rgba(200, 200, 200, 0.03)' : 'rgba(200, 200, 200, 0.2)'
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -22,7 +22,12 @@ const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
|
||||
</NButton>
|
||||
</template>
|
||||
<VueDraggable v-model="columns" :animation="150" filter=".none_draggable">
|
||||
<div v-for="item in columns" :key="item.key" class="h-36px flex-y-center rd-4px hover:(bg-primary bg-opacity-20)">
|
||||
<div
|
||||
v-for="item in columns"
|
||||
:key="item.key"
|
||||
class="h-36px flex-y-center rd-4px hover:(bg-primary bg-opacity-20)"
|
||||
:class="{ hidden: !item.visible }"
|
||||
>
|
||||
<icon-mdi-drag class="mr-8px h-full cursor-move text-icon" />
|
||||
<NCheckbox v-model:checked="item.checked" class="none_draggable flex-1">
|
||||
<template v-if="typeof item.title === 'function'">
|
||||
|
42
src/components/common/icon-tooltip.vue
Normal file
42
src/components/common/icon-tooltip.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, useSlots } from 'vue';
|
||||
import type { PopoverPlacement } from 'naive-ui';
|
||||
|
||||
defineOptions({ name: 'IconTooltip' });
|
||||
|
||||
interface Props {
|
||||
icon?: string;
|
||||
localIcon?: string;
|
||||
desc?: string;
|
||||
placement?: PopoverPlacement;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
icon: 'mdi-help-circle',
|
||||
localIcon: '',
|
||||
desc: '',
|
||||
placement: 'top'
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
const hasCustomTrigger = computed(() => Boolean(slots.trigger));
|
||||
|
||||
if (!hasCustomTrigger.value && !props.icon && !props.localIcon) {
|
||||
throw new Error('icon or localIcon is required when no custom trigger slot is provided');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NTooltip :placement="placement">
|
||||
<template #trigger>
|
||||
<slot name="trigger">
|
||||
<div class="cursor-pointer">
|
||||
<SvgIcon :icon="icon" :local-icon="localIcon" />
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<slot>
|
||||
<span>{{ desc }}</span>
|
||||
</slot>
|
||||
</NTooltip>
|
||||
</template>
|
@ -21,27 +21,27 @@ const attrs: SelectProps = useAttrs();
|
||||
|
||||
const { loading: postLoading, startLoading: startPostLoading, endLoading: endPostLoading } = useLoading();
|
||||
|
||||
/** the enabled role options */
|
||||
const roleOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
/** the enabled post options */
|
||||
const postOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
|
||||
|
||||
watch(
|
||||
() => props.deptId,
|
||||
() => {
|
||||
if (!props.deptId) {
|
||||
roleOptions.value = [];
|
||||
postOptions.value = [];
|
||||
return;
|
||||
}
|
||||
getRoleOptions();
|
||||
getPostOptions();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
async function getRoleOptions() {
|
||||
async function getPostOptions() {
|
||||
startPostLoading();
|
||||
const { error, data } = await fetchGetPostSelect(props.deptId!);
|
||||
|
||||
if (!error) {
|
||||
roleOptions.value = data.map(item => ({
|
||||
postOptions.value = data.map(item => ({
|
||||
label: item.postName,
|
||||
value: item.postId
|
||||
}));
|
||||
@ -54,7 +54,7 @@ async function getRoleOptions() {
|
||||
<NSelect
|
||||
v-model:value="value"
|
||||
:loading="postLoading"
|
||||
:options="roleOptions"
|
||||
:options="postOptions"
|
||||
v-bind="attrs"
|
||||
placeholder="请选择岗位"
|
||||
/>
|
||||
|
@ -1,79 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { NPopover, NSpace, NTag } from 'naive-ui';
|
||||
|
||||
interface Props {
|
||||
value: string | any[];
|
||||
type?: NaiveUI.ThemeColor;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
placeholder?: string;
|
||||
closable?: boolean;
|
||||
threadshold?: number; // 超过该数量显示popover
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'info',
|
||||
size: 'small',
|
||||
placeholder: '无',
|
||||
closable: false,
|
||||
threadshold: 1 // 默认超过1个就显示popover
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'close', index?: number): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// 统一解析 value 成数组
|
||||
const tags = computed(() => {
|
||||
if (!props.value) return [];
|
||||
return Array.isArray(props.value) ? props.value : props.value.split(',');
|
||||
});
|
||||
|
||||
function handleClose(index?: number) {
|
||||
emit('close', index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="tags.length === 0">
|
||||
<NTag :size="size">
|
||||
{{ placeholder }}
|
||||
</NTag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="tags.length <= threadshold">
|
||||
<NTag
|
||||
v-for="(tag, index) in tags"
|
||||
:key="index"
|
||||
:type="type"
|
||||
class="m-1"
|
||||
:size="size"
|
||||
:closable="closable"
|
||||
@close="handleClose(index)"
|
||||
>
|
||||
{{ tag }}
|
||||
</NTag>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<NPopover trigger="hover" placement="bottom">
|
||||
<template #trigger>
|
||||
<NTag :type="type" :size="size" class="cursor-pointer">{{ tags[0] }}...({{ tags.length }})</NTag>
|
||||
</template>
|
||||
<NSpace vertical size="small">
|
||||
<NTag
|
||||
v-for="(tag, index) in tags"
|
||||
:key="index"
|
||||
:type="type"
|
||||
:size="size"
|
||||
:closable="closable"
|
||||
@close="handleClose(index)"
|
||||
>
|
||||
{{ tag }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
</NPopover>
|
||||
</template>
|
||||
</template>
|
@ -1,370 +0,0 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { NButton } from 'naive-ui';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
import { fetchGetDeptTree, fetchGetUserList } from '@/service/api/system';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { $t } from '@/locales';
|
||||
import UserSearch from '@/views/system/user/modules/user-search.vue';
|
||||
import DictTag from './dict-tag.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'UserSelectModal'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
multiple?: boolean;
|
||||
/** 禁选用户ID */
|
||||
disabledIds?: CommonType.IdType[];
|
||||
rowKeys?: CommonType.IdType[];
|
||||
searchUserIds?: string | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '用户选择',
|
||||
multiple: false,
|
||||
disabledIds: () => [],
|
||||
rowKeys: () => [],
|
||||
searchUserIds: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'confirm', value: CommonType.IdType[], rows?: Api.System.User[]): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
useDict('sys_normal_disable');
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const {
|
||||
columns,
|
||||
columnChecks,
|
||||
data,
|
||||
getData,
|
||||
getDataByPage,
|
||||
loading,
|
||||
mobilePagination,
|
||||
searchParams,
|
||||
resetSearchParams
|
||||
} = useTable({
|
||||
apiFn: fetchGetUserList,
|
||||
apiParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
|
||||
// the value can not be undefined, otherwise the property in Form will not be reactive
|
||||
deptId: null,
|
||||
userName: null,
|
||||
nickName: null,
|
||||
phonenumber: null,
|
||||
status: null,
|
||||
params: {}
|
||||
},
|
||||
immediate: false,
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
multiple: props.multiple,
|
||||
align: 'center',
|
||||
width: 48,
|
||||
disabled: row => props.disabledIds.includes(row.userId.toString())
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64
|
||||
},
|
||||
{
|
||||
key: 'userName',
|
||||
title: $t('page.system.user.userName'),
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
key: 'nickName',
|
||||
title: $t('page.system.user.nickName'),
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
key: 'deptName',
|
||||
title: $t('page.system.user.deptName'),
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
key: 'phonenumber',
|
||||
title: $t('page.system.user.phonenumber'),
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: $t('page.system.user.status'),
|
||||
align: 'center',
|
||||
minWidth: 80,
|
||||
render(row) {
|
||||
return <DictTag dict-code="sys_normal_disable" value={row.status} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'createTime',
|
||||
title: $t('page.system.user.createTime'),
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { checkedRowKeys } = useTableOperate(data, getData);
|
||||
|
||||
// 存储所有页面的用户数据,用于跨页选择
|
||||
const allPagesData = ref<Api.System.User[]>([]);
|
||||
|
||||
// 更新allPagesData,保存当前页数据
|
||||
function updateAllPagesData() {
|
||||
// 将当前页数据添加到allPagesData中,避免重复
|
||||
data.value.forEach(user => {
|
||||
const existIndex = allPagesData.value.findIndex(item => item.userId === user.userId);
|
||||
if (existIndex === -1) {
|
||||
allPagesData.value.push(user);
|
||||
} else {
|
||||
// 更新已存在的数据
|
||||
allPagesData.value[existIndex] = user;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading();
|
||||
const deptPattern = ref<string>();
|
||||
const deptData = ref<Api.Common.CommonTreeRecord>([]);
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
|
||||
async function getTreeData() {
|
||||
startTreeLoading();
|
||||
const { data: tree, error } = await fetchGetDeptTree();
|
||||
if (!error) {
|
||||
deptData.value = tree;
|
||||
}
|
||||
endTreeLoading();
|
||||
}
|
||||
|
||||
function handleClickTree(keys: string[]) {
|
||||
searchParams.deptId = keys.length ? keys[0] : null;
|
||||
checkedRowKeys.value = [];
|
||||
getDataByPage();
|
||||
}
|
||||
|
||||
function handleResetTreeData() {
|
||||
deptPattern.value = undefined;
|
||||
getTreeData();
|
||||
}
|
||||
|
||||
const expandedKeys = ref<CommonType.IdType[]>([100]);
|
||||
|
||||
const selectable = computed(() => {
|
||||
return !loading.value;
|
||||
});
|
||||
|
||||
function handleResetSearch() {
|
||||
resetSearchParams();
|
||||
selectedKeys.value = [];
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
checkedRowKeys.value = [];
|
||||
allPagesData.value = [];
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
if (checkedRowKeys.value.length === 0) {
|
||||
window.$message?.error('请选择用户');
|
||||
return;
|
||||
}
|
||||
// 获取选中行对应的用户对象(从所有页面数据中筛选)
|
||||
const selectedUsers = allPagesData.value.filter(item => checkedRowKeys.value.includes(item.userId.toString()));
|
||||
emit('confirm', checkedRowKeys.value, selectedUsers);
|
||||
closeModal();
|
||||
}
|
||||
|
||||
function getRowProps(row: Api.System.User) {
|
||||
return {
|
||||
onClick: (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.closest('.n-data-table-td--selection')) {
|
||||
return;
|
||||
}
|
||||
if (props.disabledIds.includes(row.userId.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 将userId转为字符串
|
||||
const userId = row.userId.toString();
|
||||
|
||||
if (props.multiple) {
|
||||
const index = checkedRowKeys.value.findIndex(key => key === userId);
|
||||
if (index > -1) {
|
||||
checkedRowKeys.value.splice(index, 1);
|
||||
} else {
|
||||
checkedRowKeys.value.push(userId);
|
||||
}
|
||||
} else {
|
||||
checkedRowKeys.value = [userId];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 监听数据变化(页面切换时)
|
||||
watch(
|
||||
data,
|
||||
() => {
|
||||
updateAllPagesData();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
getTreeData();
|
||||
if (props.searchUserIds) {
|
||||
searchParams.userIds = props.searchUserIds;
|
||||
}
|
||||
allPagesData.value = [];
|
||||
getDataByPage();
|
||||
checkedRowKeys.value = [...props.rowKeys];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="visible"
|
||||
class="user-select-modal max-h-800px max-w-90% w-1400px"
|
||||
preset="card"
|
||||
size="medium"
|
||||
:title="props.title"
|
||||
>
|
||||
<TableSiderLayout :sider-title="$t('page.system.dept.title')">
|
||||
<template #header-extra>
|
||||
<NButton size="small" text class="h-18px" @click.stop="() => handleResetTreeData()">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ic:round-refresh" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
<template #sider>
|
||||
<NInput v-model:value="deptPattern" clearable :placeholder="$t('common.keywordSearch')" />
|
||||
<NSpin class="dept-tree" :show="treeLoading">
|
||||
<NTree
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
block-node
|
||||
show-line
|
||||
:data="deptData as []"
|
||||
:show-irrelevant-nodes="false"
|
||||
:pattern="deptPattern"
|
||||
class="infinite-scroll h-full min-h-200px py-3"
|
||||
key-field="id"
|
||||
label-field="label"
|
||||
virtual-scroll
|
||||
:selectable="selectable"
|
||||
@update:selected-keys="handleClickTree"
|
||||
>
|
||||
<template #empty>
|
||||
<NEmpty :description="$t('page.system.dept.empty')" class="h-full min-h-200px justify-center" />
|
||||
</template>
|
||||
</NTree>
|
||||
</NSpin>
|
||||
</template>
|
||||
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:max-h-500px lt-sm:overflow-auto">
|
||||
<UserSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
|
||||
<TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
|
||||
<NAlert v-if="props.disabledIds.length > 0" type="warning">
|
||||
<span>已存在的用户无法被选择</span>
|
||||
</NAlert>
|
||||
<NCard
|
||||
:title="$t('page.system.user.title')"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
class="card-wrapper sm:flex-1-hidden lt-sm:overflow-auto"
|
||||
>
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:loading="loading"
|
||||
:show-add="false"
|
||||
:show-delete="false"
|
||||
:show-export="false"
|
||||
@refresh="getData"
|
||||
></TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="962"
|
||||
:loading="loading"
|
||||
:row-props="getRowProps"
|
||||
remote
|
||||
:row-key="row => row.userId.toString()"
|
||||
:pagination="mobilePagination"
|
||||
class="h-full lt-sm:max-h-300px"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</TableSiderLayout>
|
||||
<template #footer>
|
||||
<NSpace justify="end" :size="16">
|
||||
<NButton @click="closeModal">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleConfirm">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.n-layout) {
|
||||
height: 600px;
|
||||
|
||||
@media (max-width: 639px) {
|
||||
height: auto;
|
||||
max-height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-select-modal {
|
||||
@media (max-width: 639px) {
|
||||
:deep(.n-card-content) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.n-data-table) {
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.n-alert {
|
||||
--n-padding: 5px 13px !important;
|
||||
--n-icon-margin: 6px 8px 0 12px !important;
|
||||
--n-icon-size: 20px !important;
|
||||
}
|
||||
</style>
|
@ -1,178 +0,0 @@
|
||||
<script setup lang="tsx">
|
||||
import { ref } from 'vue';
|
||||
import { NPopover, NSpace, NTag } from 'naive-ui';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
import { fetchGetFlowHisTaskList } from '@/service/api/workflow/instance';
|
||||
import { fetchGetOssListByIds } from '@/service/api/system/oss';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
import DictTag from '@/components/custom/dict-tag.vue';
|
||||
import TagGroup from '@/components/custom/tag-group.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'ApprovalInfoPanel'
|
||||
});
|
||||
interface Props {
|
||||
/** 业务id */
|
||||
businessId: CommonType.IdType;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
useDict('wf_task_status');
|
||||
|
||||
const activeTab = ref('image');
|
||||
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
|
||||
const { oss } = useDownload();
|
||||
|
||||
const columns = ref<NaiveUI.TableColumn<Api.Workflow.HisTask>[]>([
|
||||
{
|
||||
title: '任务名称',
|
||||
key: 'nodeName',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '办理人',
|
||||
key: 'approveName',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
render: row => {
|
||||
return <TagGroup value={row.approveName} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '任务状态',
|
||||
key: 'flowStatus',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
render: row => {
|
||||
return <DictTag size="small" value={row.flowStatus} dict-code="wf_task_status" />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '审批意见',
|
||||
key: 'message',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '运行时间',
|
||||
key: 'runDuration',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '附件',
|
||||
key: 'attachmentList',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
render: row => {
|
||||
if (!row.attachmentList || row.attachmentList.length === 0) return null;
|
||||
|
||||
if (row.attachmentList.length === 1) {
|
||||
return (
|
||||
<NTag size="small" type="info" class="cursor-pointer">
|
||||
<div class="flex items-center gap-2" onClick={() => oss(row.attachmentList[0].ossId)}>
|
||||
{row.attachmentList[0].originalName}
|
||||
</div>
|
||||
</NTag>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NPopover trigger="hover" placement="bottom">
|
||||
{{
|
||||
trigger: () => (
|
||||
<NTag size="small" type="info" class="cursor-pointer">
|
||||
{row.attachmentList[0].originalName}...({row.attachmentList.length})
|
||||
</NTag>
|
||||
),
|
||||
default: () => (
|
||||
<NSpace vertical size="small">
|
||||
{row.attachmentList.map(item => (
|
||||
<NTag key={item.ossId} size="small" type="info" class="cursor-pointer">
|
||||
<div class="flex items-center gap-2" onClick={() => oss(item.ossId)}>
|
||||
{item.originalName}
|
||||
</div>
|
||||
</NTag>
|
||||
))}
|
||||
</NSpace>
|
||||
)
|
||||
}}
|
||||
</NPopover>
|
||||
);
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
const instanceId = ref<CommonType.IdType>();
|
||||
|
||||
const hisTask = ref<Api.Workflow.HisTask[]>([]);
|
||||
|
||||
/** 初始化数据 */
|
||||
async function initData() {
|
||||
activeTab.value = 'image';
|
||||
instanceId.value = undefined;
|
||||
hisTask.value = [];
|
||||
await getData();
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
startLoading();
|
||||
const { error, data } = await fetchGetFlowHisTaskList(props.businessId);
|
||||
if (error) {
|
||||
window.$message?.error(error.message);
|
||||
return;
|
||||
}
|
||||
instanceId.value = data?.instanceId || '';
|
||||
|
||||
const rawList = data?.list || [];
|
||||
if (rawList.length === 0) {
|
||||
hisTask.value = [];
|
||||
return;
|
||||
}
|
||||
const promises = rawList.map(async (item: Api.Workflow.HisTask) => {
|
||||
if (item.ext) {
|
||||
const { error: err, data: ossList } = await fetchGetOssListByIds(item.ext.split(','));
|
||||
if (!err) {
|
||||
item.attachmentList = ossList;
|
||||
}
|
||||
}
|
||||
});
|
||||
await Promise.all(promises);
|
||||
hisTask.value = rawList;
|
||||
endLoading();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
initData
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider />
|
||||
<div>
|
||||
<NTabs v-model:value="activeTab" type="segment" animated>
|
||||
<NTabPane display-directive="show" bar-width="100px" name="image" tab="流程图">
|
||||
<FlowPreview v-if="instanceId" :instance-id="instanceId" />
|
||||
</NTabPane>
|
||||
<NTabPane bar-width="100px" name="info" tab="审批信息">
|
||||
<NDataTable size="small" :scroll-x="760" :columns="columns" :data="hisTask" :loading="loading" />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</div>
|
||||
</template>
|
@ -1,142 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import type { UploadFileInfo } from 'naive-ui';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
import { messageTypeOptions } from '@/constants/workflow';
|
||||
import { fetchBackTask, fetchGetBackNode } from '@/service/api/workflow/task';
|
||||
defineOptions({
|
||||
name: 'BackTaskModal'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
task: Api.Workflow.Task;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submit'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
const title = defineModel<string>('title', {
|
||||
default: '驳回'
|
||||
});
|
||||
const { loading: backFormLoading, startLoading: startBackFormLoading, endLoading: endBackFormLoading } = useLoading();
|
||||
const { loading: backBtnLoading, startLoading: startBackBtnLoading, endLoading: endBackBtnLoading } = useLoading();
|
||||
|
||||
const accept = ref<string>('.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.pdf,.jpg,.jpeg,.png,.gif,.bmp,.webp');
|
||||
|
||||
type Model = Api.Workflow.BackOperateParams;
|
||||
|
||||
const backModel = reactive(createBackModel());
|
||||
|
||||
function createBackModel(): Model {
|
||||
return {
|
||||
taskId: null,
|
||||
fileId: null,
|
||||
messageType: ['1'],
|
||||
nodeCode: null,
|
||||
message: null,
|
||||
notice: null,
|
||||
variables: null
|
||||
};
|
||||
}
|
||||
const fileList = ref<UploadFileInfo[]>([]);
|
||||
|
||||
const backTaskNodeOptions = ref<CommonType.Option<string, string>[]>([]);
|
||||
|
||||
async function initDefault() {
|
||||
startBackFormLoading();
|
||||
startBackBtnLoading();
|
||||
Object.assign(backModel, createBackModel());
|
||||
const { error, data } = await fetchGetBackNode(props.task.definitionId, props.task.nodeCode);
|
||||
endBackFormLoading();
|
||||
endBackBtnLoading();
|
||||
if (error) return;
|
||||
backTaskNodeOptions.value = data.map(item => ({
|
||||
label: item.nodeName,
|
||||
value: item.nodeCode
|
||||
}));
|
||||
backModel.nodeCode = data[0].nodeCode;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
backModel.taskId = props.task.id;
|
||||
if (fileList.value?.length) {
|
||||
const fileIds = fileList.value.map(item => item.id);
|
||||
backModel.fileId = fileIds.join(',');
|
||||
}
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: `是否确认驳回?`,
|
||||
positiveText: `确认驳回`,
|
||||
positiveButtonProps: {
|
||||
type: 'primary'
|
||||
},
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
startBackBtnLoading();
|
||||
startBackFormLoading();
|
||||
const { error } = await fetchBackTask(backModel);
|
||||
endBackBtnLoading();
|
||||
endBackFormLoading();
|
||||
if (error) return;
|
||||
window.$message?.success('驳回成功');
|
||||
closeDrawer();
|
||||
emit('submit');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
initDefault();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="visible" preset="card" class="w-800px" :title="title" :native-scrollbar="false" closable>
|
||||
<NSpin :show="backFormLoading">
|
||||
<NForm v-if="task.flowStatus === 'waiting'" :model="backModel">
|
||||
<NFormItem label="驳回节点" path="nodeCode">
|
||||
<NSelect v-model:value="backModel.nodeCode" :options="backTaskNodeOptions" />
|
||||
</NFormItem>
|
||||
<NFormItem label="通知方式" path="messageType">
|
||||
<NCheckboxGroup v-model:value="backModel.messageType">
|
||||
<NSpace item-style="display: flex;">
|
||||
<NCheckbox
|
||||
v-for="item in messageTypeOptions"
|
||||
:key="item.value"
|
||||
:disabled="item.value === '1'"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</NSpace>
|
||||
</NCheckboxGroup>
|
||||
</NFormItem>
|
||||
<NFormItem label="附件" path="fileId">
|
||||
<FileUpload v-model:file-list="fileList" :file-size="20" :max="20" upload-type="file" :accept="accept" />
|
||||
</NFormItem>
|
||||
<NFormItem label="审批意见" path="message">
|
||||
<NInput v-model:value="backModel.message" type="textarea" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</NSpin>
|
||||
<template #footer>
|
||||
<NSpace justify="end" :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton :loading="backBtnLoading" type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
@ -1,57 +0,0 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, useAttrs } from 'vue';
|
||||
import type { TreeSelectProps } from 'naive-ui';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
import { fetchGetCategoryTree } from '@/service/api/workflow';
|
||||
import { isNull } from '@/utils/common';
|
||||
|
||||
defineOptions({ name: 'FlowCategorySelect' });
|
||||
|
||||
interface Props {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const rawValue = defineModel<CommonType.IdType | null>('value', { required: false });
|
||||
const options = defineModel<Api.Common.CommonTreeRecord>('options', { required: false, default: [] });
|
||||
|
||||
const attrs: TreeSelectProps = useAttrs();
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
|
||||
/** 转换为str,id可能是number类型或者String类型,导致回显失败 */
|
||||
const strValue = computed({
|
||||
get() {
|
||||
return isNull(rawValue.value) ? null : rawValue.value?.toString();
|
||||
},
|
||||
set(val) {
|
||||
rawValue.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
async function getCategoryList() {
|
||||
startLoading();
|
||||
const { error, data } = await fetchGetCategoryTree();
|
||||
if (error) return;
|
||||
options.value = data;
|
||||
endLoading();
|
||||
}
|
||||
|
||||
getCategoryList();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NTreeSelect
|
||||
v-model:value="strValue"
|
||||
filterable
|
||||
class="h-full"
|
||||
:loading="loading"
|
||||
key-field="id"
|
||||
label-field="label"
|
||||
:options="options as []"
|
||||
:default-expanded-keys="[0]"
|
||||
v-bind="attrs"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,96 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
interface Props {
|
||||
/** 抽屉是否可见 */
|
||||
visible?: boolean;
|
||||
/** 抽屉标题 */
|
||||
title: string;
|
||||
/** 是否显示加载状态 */
|
||||
loading?: boolean;
|
||||
/** 抽屉宽度 */
|
||||
width?: number;
|
||||
operateType: CommonType.WorkflowTableOperateType;
|
||||
status?: string | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
loading: false,
|
||||
width: 1200,
|
||||
status: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
(e: 'close'): void;
|
||||
(e: 'saveDraft'): void;
|
||||
(e: 'submit'): void;
|
||||
(e: 'approval'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visibleValue = computed({
|
||||
get: () => props.visible,
|
||||
set: value => {
|
||||
emit('update:visible', value);
|
||||
}
|
||||
});
|
||||
|
||||
const showSubmit = computed(
|
||||
() =>
|
||||
props.operateType === 'add' ||
|
||||
(props.operateType === 'edit' &&
|
||||
props.status &&
|
||||
(props.status === 'draft' || props.status === 'cancel' || props.status === 'back'))
|
||||
);
|
||||
const showApproval = computed(() => props.operateType === 'approval' && props.status && props.status === 'waiting');
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
}
|
||||
|
||||
function handleSaveDraft() {
|
||||
emit('saveDraft');
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
emit('submit');
|
||||
}
|
||||
function handleApproval() {
|
||||
emit('approval');
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleClose,
|
||||
handleSaveDraft,
|
||||
handleSubmit,
|
||||
handleApproval
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visibleValue" :title="title" display-directive="show" :width="width" class="max-w-90%">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable @close="handleClose">
|
||||
<NSpin :show="loading">
|
||||
<slot></slot>
|
||||
</NSpin>
|
||||
<template #footer>
|
||||
<slot name="footer">
|
||||
<div>
|
||||
<NSpace :size="16">
|
||||
<NButton v-if="showSubmit || showApproval" @click="handleClose">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton v-if="showSubmit" type="warning" @click="handleSaveDraft">暂存</NButton>
|
||||
<NButton v-if="showSubmit" type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
<NButton v-if="showApproval" type="primary" @click="handleApproval">办理</NButton>
|
||||
</NSpace>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,204 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useBoolean, useLoading } from '@sa/hooks';
|
||||
import { fetchGetTask, fetchTaskOperate, fetchTerminateTask } from '@/service/api/workflow/task';
|
||||
import ReduceSignatureModal from './reduce-signature-modal.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'FlowInterveneModal'
|
||||
});
|
||||
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
const { bool: addSignatureVisible, setTrue: openAddSignatureModal } = useBoolean();
|
||||
const { bool: transferVisible, setTrue: openTransferModal } = useBoolean();
|
||||
const { bool: reduceSignatureVisible, setTrue: openReduceSignatureModal } = useBoolean();
|
||||
interface Props {
|
||||
taskId: CommonType.IdType;
|
||||
assigneeIds: CommonType.IdType[];
|
||||
assigneeNames: string[];
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'refresh'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const taskInfo = ref<Api.Workflow.Task>();
|
||||
|
||||
const isWaiting = computed(() => taskInfo.value?.flowStatus === 'waiting');
|
||||
|
||||
// 流程签署比例值 大于0为票签,会签
|
||||
const isTicketOrSignInstance = computed(() => Number(taskInfo.value?.nodeRatio) > 0);
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
type Model = Api.Workflow.TaskOperateParams;
|
||||
const model: Model = reactive(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
taskId: null,
|
||||
userId: undefined,
|
||||
userIds: undefined,
|
||||
message: ''
|
||||
};
|
||||
}
|
||||
|
||||
type TerminateModel = Api.Workflow.TerminateTaskOperateParams;
|
||||
const terminateModel: TerminateModel = reactive(createDefaultTerminateModel());
|
||||
|
||||
function createDefaultTerminateModel(): TerminateModel {
|
||||
return {
|
||||
taskId: null,
|
||||
comment: ''
|
||||
};
|
||||
}
|
||||
|
||||
function handleTransferConfirm(ids: CommonType.IdType[]) {
|
||||
model.userId = ids[0];
|
||||
model.taskId = props.taskId;
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: '是否确认转办?',
|
||||
positiveText: '确认转办',
|
||||
positiveButtonProps: {
|
||||
type: 'primary'
|
||||
},
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
const { error } = await fetchTaskOperate(model, 'transferTask');
|
||||
if (error) return;
|
||||
window.$message?.success('转办成功');
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleAddSignatureConfirm(ids: CommonType.IdType[]) {
|
||||
model.userIds = ids;
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: '是否确认加签?',
|
||||
positiveText: '确认加签',
|
||||
positiveButtonProps: {
|
||||
type: 'primary'
|
||||
},
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
const { error } = await fetchTaskOperate(model, 'addSignature');
|
||||
if (error) return;
|
||||
window.$message?.success('加签成功');
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleTerminate() {
|
||||
terminateModel.taskId = props.taskId;
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: '是否确认终止?',
|
||||
positiveText: '确认',
|
||||
positiveButtonProps: {
|
||||
type: 'primary'
|
||||
},
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
const { error } = await fetchTerminateTask(terminateModel);
|
||||
if (error) return;
|
||||
window.$message?.success('终止成功');
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleReduceSubmit() {
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
}
|
||||
|
||||
async function getTaskInfo() {
|
||||
startLoading();
|
||||
const { error, data } = await fetchGetTask(props.taskId);
|
||||
if (error) return;
|
||||
taskInfo.value = data;
|
||||
endLoading();
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
getTaskInfo();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="visible"
|
||||
class="max-h-520px max-w-90% w-700px"
|
||||
title="流程干预"
|
||||
preset="card"
|
||||
size="medium"
|
||||
:native-scrollbar="false"
|
||||
>
|
||||
<NSpin :show="loading">
|
||||
<NDescriptions :title="taskInfo?.flowName" label-placement="left" :column="2" size="small" bordered>
|
||||
<NDescriptionsItem label="任务名称">
|
||||
{{ taskInfo?.nodeName }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="节点编码">
|
||||
{{ taskInfo?.nodeCode }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="开始时间">
|
||||
{{ taskInfo?.createTime }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="流程实例ID">
|
||||
{{ taskInfo?.instanceId }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="办理人">
|
||||
<TagGroup :value="assigneeNames" />
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="版本号">
|
||||
<NTag type="info" size="small">v{{ taskInfo?.version }}.0</NTag>
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="业务ID">
|
||||
{{ taskInfo?.businessId }}
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
</NSpin>
|
||||
|
||||
<template #footer>
|
||||
<NSpace justify="end" :size="16">
|
||||
<NButton v-if="isWaiting" type="primary" @click="openTransferModal">转办</NButton>
|
||||
<NButton v-if="isWaiting && isTicketOrSignInstance" type="primary" @click="openAddSignatureModal">加签</NButton>
|
||||
<NButton v-if="isWaiting && isTicketOrSignInstance" type="primary" @click="openReduceSignatureModal">
|
||||
减签
|
||||
</NButton>
|
||||
<NButton v-if="isWaiting" type="error" @click="handleTerminate">终止</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
<!-- 转办用户选择器 -->
|
||||
<UserSelectModal v-model:visible="transferVisible" :disabled-ids="assigneeIds" @confirm="handleTransferConfirm" />
|
||||
<!-- 加签用户选择器 -->
|
||||
<UserSelectModal
|
||||
v-model:visible="addSignatureVisible"
|
||||
multiple
|
||||
:disabled-ids="assigneeIds"
|
||||
@confirm="handleAddSignatureConfirm"
|
||||
/>
|
||||
<!-- 减签用户 -->
|
||||
<ReduceSignatureModal
|
||||
v-model:visible="reduceSignatureVisible"
|
||||
:task="taskInfo!"
|
||||
@reduce-submit="handleReduceSubmit"
|
||||
/>
|
||||
</NModal>
|
||||
</template>
|
@ -1,30 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { stringify } from 'qs';
|
||||
import { getToken } from '@/store/modules/auth/shared';
|
||||
import { getServiceBaseURL } from '@/utils/service';
|
||||
|
||||
defineOptions({
|
||||
name: 'FlowPreview'
|
||||
});
|
||||
interface Props {
|
||||
instanceId: CommonType.IdType;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||
const urlParams = {
|
||||
type: 'FlowChart',
|
||||
id: props.instanceId,
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
clientid: import.meta.env.VITE_APP_CLIENT_ID || ''
|
||||
};
|
||||
|
||||
const iframeUrl = `${baseURL}/warm-flow-ui/index.html?${stringify(urlParams)}`;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<iframe :src="iframeUrl" class="h-[450px] w-full" />
|
||||
</div>
|
||||
</template>
|
@ -1,496 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import type { UploadFileInfo } from 'naive-ui';
|
||||
import { useBoolean, useLoading } from '@sa/hooks';
|
||||
import { messageTypeOptions } from '@/constants/workflow';
|
||||
import {
|
||||
fetchCompleteTask,
|
||||
fetchGetTask,
|
||||
fetchGetkNextNode,
|
||||
fetchTaskOperate,
|
||||
fetchTerminateTask
|
||||
} from '@/service/api/workflow';
|
||||
import FileUpload from '@/components/custom/file-upload.vue';
|
||||
import ReduceSignatureModal from './reduce-signature-modal.vue';
|
||||
import BackTaskModal from './back-task-modal.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'FlowTaskApprovalModal'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** 任务id */
|
||||
taskId: CommonType.IdType;
|
||||
/** 任务变量 */
|
||||
taskVariables: { [key: string]: any };
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'finished'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
const { loading: baseFormLoading, startLoading: startBaseFormLoading, endLoading: endBaseFormLoading } = useLoading();
|
||||
const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading();
|
||||
const { bool: copyVisible, setTrue: openCopyModal } = useBoolean();
|
||||
const { bool: assigneeVisible, setTrue: openAssigneeModal } = useBoolean();
|
||||
const { bool: delegateVisible, setTrue: openDelegateModal, setFalse: closeDelegateModal } = useBoolean();
|
||||
const { bool: transferVisible, setTrue: openTransferModal, setFalse: closeTransferModal } = useBoolean();
|
||||
const { bool: addSignatureVisible, setTrue: openAddSignatureModal, setFalse: closeAddSignatureModal } = useBoolean();
|
||||
const { bool: reduceSignatureVisible, setTrue: openReduceSignatureModal } = useBoolean();
|
||||
const { bool: backVisible, setTrue: openBackModal } = useBoolean();
|
||||
const title = defineModel<string>('title', {
|
||||
default: '流程发起'
|
||||
});
|
||||
|
||||
const accept = ref<string>('.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.pdf,.jpg,.jpeg,.png,.gif,.bmp,.webp');
|
||||
|
||||
type Model = Api.Workflow.CompleteTaskOperateParams;
|
||||
|
||||
const task = ref<Api.Workflow.Task>();
|
||||
|
||||
const isWaiting = computed(() => task.value?.flowStatus === 'waiting');
|
||||
|
||||
const model: Model = reactive(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
taskId: null,
|
||||
fileId: null,
|
||||
flowCopyList: [],
|
||||
messageType: ['1'],
|
||||
taskVariables: null,
|
||||
variables: null,
|
||||
assigneeMap: null
|
||||
};
|
||||
}
|
||||
const fileList = ref<UploadFileInfo[]>([]);
|
||||
// 抄送人
|
||||
const selectCopyUserList = ref<Api.System.User[]>([]);
|
||||
// 抄送人id
|
||||
const selectCopyUserIds = ref<CommonType.IdType[]>([]);
|
||||
// 下一节点列表
|
||||
const nestNodeList = ref<Api.Workflow.FlowNode[]>([]);
|
||||
|
||||
const nickNameMap = ref<{ [key: string]: string }>({});
|
||||
|
||||
const assigneeSearchUserIds = ref<string | null>(null);
|
||||
|
||||
const selectAssigneeIds = ref<string[]>([]);
|
||||
|
||||
// 节点编码
|
||||
const nodeCode = ref<string>('');
|
||||
// 按钮权限
|
||||
interface ButtonPerm {
|
||||
pop: boolean;
|
||||
trust: boolean;
|
||||
transfer: boolean;
|
||||
addSign: boolean;
|
||||
subSign: boolean;
|
||||
termination: boolean;
|
||||
back: boolean;
|
||||
copy: boolean;
|
||||
}
|
||||
const buttonPerm = reactive<ButtonPerm>(createDefaultButtonPerm());
|
||||
|
||||
function createDefaultButtonPerm(): ButtonPerm {
|
||||
return {
|
||||
pop: false,
|
||||
trust: false,
|
||||
transfer: false,
|
||||
addSign: false,
|
||||
subSign: false,
|
||||
termination: false,
|
||||
back: false,
|
||||
copy: false
|
||||
};
|
||||
}
|
||||
|
||||
function initDefault() {
|
||||
selectCopyUserList.value = [];
|
||||
selectCopyUserIds.value = [];
|
||||
nickNameMap.value = {};
|
||||
assigneeSearchUserIds.value = null;
|
||||
selectAssigneeIds.value = [];
|
||||
nodeCode.value = '';
|
||||
Object.assign(model, createDefaultModel());
|
||||
Object.assign(buttonPerm, createDefaultButtonPerm());
|
||||
}
|
||||
|
||||
async function getTask() {
|
||||
startBtnLoading();
|
||||
startBaseFormLoading();
|
||||
const { error, data } = await fetchGetTask(props.taskId);
|
||||
if (error) {
|
||||
endBtnLoading();
|
||||
endBaseFormLoading();
|
||||
return;
|
||||
}
|
||||
task.value = data;
|
||||
task.value.buttonList.forEach(item => {
|
||||
buttonPerm[item.code as keyof ButtonPerm] = item.show!;
|
||||
});
|
||||
endBtnLoading();
|
||||
const { error: nextNodeError, data: nextNodeData } = await fetchGetkNextNode({
|
||||
taskId: props.taskId,
|
||||
taskVariables: props.taskVariables
|
||||
});
|
||||
endBaseFormLoading();
|
||||
if (nextNodeError) {
|
||||
return;
|
||||
}
|
||||
nestNodeList.value = nextNodeData;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (buttonPerm.pop && nestNodeList.value?.length) {
|
||||
const hasEmptyAssignee = nestNodeList.value.some(e => !model.assigneeMap || !model.assigneeMap[e.nodeCode]);
|
||||
if (hasEmptyAssignee) {
|
||||
window.$message?.error('请选择审批人!');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
model.assigneeMap = {};
|
||||
}
|
||||
if (selectCopyUserList.value?.length) {
|
||||
model.flowCopyList = selectCopyUserList.value.map(e => ({
|
||||
userId: e.userId,
|
||||
userName: e.nickName
|
||||
}));
|
||||
}
|
||||
if (fileList.value?.length) {
|
||||
const fileIds = fileList.value.map(item => item.id);
|
||||
model.fileId = fileIds.join(',');
|
||||
}
|
||||
model.taskId = props.taskId;
|
||||
model.variables = props.taskVariables;
|
||||
startBtnLoading();
|
||||
startBaseFormLoading();
|
||||
try {
|
||||
const { error } = await fetchCompleteTask(model);
|
||||
if (error) return;
|
||||
window.$message?.success('提交成功');
|
||||
visible.value = false;
|
||||
emit('finished');
|
||||
} catch (error) {
|
||||
window.$message?.error(`提交失败,请稍后重试,${error}`);
|
||||
} finally {
|
||||
endBtnLoading();
|
||||
endBaseFormLoading();
|
||||
}
|
||||
}
|
||||
|
||||
function handleCopyConfirm(userIds: CommonType.IdType[], users?: Api.System.User[]) {
|
||||
selectCopyUserList.value = users || [];
|
||||
selectCopyUserIds.value = userIds;
|
||||
}
|
||||
|
||||
function handleAssigneeOpen(item: Api.Workflow.FlowNode) {
|
||||
if (!item.permissionFlag) {
|
||||
window.$message?.error('没有可选人员,请联系管理员!');
|
||||
return;
|
||||
}
|
||||
assigneeSearchUserIds.value = item.permissionFlag;
|
||||
nodeCode.value = item.nodeCode;
|
||||
selectAssigneeIds.value = model.assigneeMap?.[item.nodeCode]?.split(',') || [];
|
||||
openAssigneeModal();
|
||||
}
|
||||
|
||||
function handleAssigneeConfirm(userIds: CommonType.IdType[], users?: Api.System.User[]) {
|
||||
// 更新当前节点的审批人
|
||||
if (!model.assigneeMap) model.assigneeMap = {};
|
||||
model.assigneeMap[nodeCode.value] = userIds.join(',');
|
||||
nickNameMap.value[nodeCode.value] = users?.map(item => item.nickName).join(',') || '';
|
||||
}
|
||||
|
||||
function handleCopyTagClose(index?: number) {
|
||||
if (index !== undefined) {
|
||||
// 删除指定索引的用户
|
||||
selectCopyUserIds.value = selectCopyUserIds.value.filter((_, i) => i !== index);
|
||||
selectCopyUserList.value = selectCopyUserList.value.filter((_, i) => i !== index);
|
||||
} else {
|
||||
// 清空所有用户
|
||||
selectCopyUserList.value = [];
|
||||
selectCopyUserIds.value = [];
|
||||
model.flowCopyList = [];
|
||||
}
|
||||
}
|
||||
|
||||
function handleAssigneeTagClose(code: string, index?: number) {
|
||||
if (!model.assigneeMap?.[code]) return;
|
||||
|
||||
// 获取当前节点的用户ID列表和名称列表
|
||||
const userIds = model.assigneeMap[code].split(',');
|
||||
const nickNames = nickNameMap.value[code]?.split(',') || [];
|
||||
|
||||
if (index !== undefined) {
|
||||
// 删除指定索引的用户
|
||||
// 使用filter方式移除指定索引的元素
|
||||
const newUserIds = userIds.filter((_, i) => i !== index);
|
||||
const newNickNames = nickNames.filter((_, i) => i !== index);
|
||||
|
||||
// 更新数据
|
||||
model.assigneeMap[code] = newUserIds.join(',');
|
||||
nickNameMap.value[code] = newNickNames.join(',');
|
||||
} else {
|
||||
// 清空所有用户
|
||||
model.assigneeMap[code] = '';
|
||||
nickNameMap.value[code] = '';
|
||||
}
|
||||
}
|
||||
|
||||
interface TaskOperationOptions {
|
||||
userIds: CommonType.IdType[];
|
||||
operation: 'delegateTask' | 'transferTask' | 'addSignature';
|
||||
confirmText: string;
|
||||
successMessage: string;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
function handleTaskOperationConfirm(options: TaskOperationOptions) {
|
||||
const { userIds, operation, confirmText, successMessage, closeModal } = options;
|
||||
|
||||
const taskModel = {
|
||||
taskId: props.taskId,
|
||||
userId: userIds[0],
|
||||
message: model.message
|
||||
};
|
||||
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: `是否确认${confirmText}?`,
|
||||
positiveText: `确认${confirmText}`,
|
||||
positiveButtonProps: {
|
||||
type: 'primary'
|
||||
},
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
startBtnLoading();
|
||||
startBaseFormLoading();
|
||||
const { error } = await fetchTaskOperate(taskModel, operation);
|
||||
endBtnLoading();
|
||||
endBaseFormLoading();
|
||||
if (error) return;
|
||||
window.$message?.success(successMessage);
|
||||
closeModal();
|
||||
visible.value = false;
|
||||
emit('finished');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 委托
|
||||
function handleDelegateConfirm(userIds: CommonType.IdType[]) {
|
||||
handleTaskOperationConfirm({
|
||||
userIds,
|
||||
operation: 'delegateTask',
|
||||
confirmText: '委托',
|
||||
successMessage: '委托成功',
|
||||
closeModal: closeDelegateModal
|
||||
});
|
||||
}
|
||||
|
||||
// 转办
|
||||
function handleTransferConfirm(userIds: CommonType.IdType[]) {
|
||||
handleTaskOperationConfirm({
|
||||
userIds,
|
||||
operation: 'transferTask',
|
||||
confirmText: '转办',
|
||||
successMessage: '转办成功',
|
||||
closeModal: closeTransferModal
|
||||
});
|
||||
}
|
||||
|
||||
// 加签
|
||||
function handleAddSignatureConfirm(userIds: CommonType.IdType[]) {
|
||||
handleTaskOperationConfirm({
|
||||
userIds,
|
||||
operation: 'addSignature',
|
||||
confirmText: '加签',
|
||||
successMessage: '加签成功',
|
||||
closeModal: closeAddSignatureModal
|
||||
});
|
||||
}
|
||||
|
||||
// 减签
|
||||
function handleReduceSubmit() {
|
||||
visible.value = false;
|
||||
emit('finished');
|
||||
}
|
||||
|
||||
// 终止
|
||||
function handleTerminate() {
|
||||
const terminateModel = {
|
||||
taskId: props.taskId,
|
||||
comment: model.message
|
||||
};
|
||||
window.$dialog?.warning({
|
||||
title: '提示',
|
||||
content: '是否确认终止?',
|
||||
positiveText: '确认',
|
||||
positiveButtonProps: {
|
||||
type: 'primary'
|
||||
},
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
startBtnLoading();
|
||||
startBaseFormLoading();
|
||||
const { error } = await fetchTerminateTask(terminateModel);
|
||||
endBtnLoading();
|
||||
endBaseFormLoading();
|
||||
if (error) return;
|
||||
window.$message?.success('终止成功');
|
||||
visible.value = false;
|
||||
emit('finished');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleBackSubmit() {
|
||||
visible.value = false;
|
||||
emit('finished');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
initDefault();
|
||||
getTask();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="visible" preset="card" class="w-800px" :title="title" :native-scrollbar="false" closable>
|
||||
<NSpin :show="baseFormLoading">
|
||||
<NForm :model="model" label-placement="left" :label-width="100">
|
||||
<NFormItem label="通知方式" path="messageType">
|
||||
<NCheckboxGroup v-model:value="model.messageType">
|
||||
<NSpace item-style="display: flex;">
|
||||
<NCheckbox
|
||||
v-for="item in messageTypeOptions"
|
||||
:key="item.value"
|
||||
:disabled="item.value === '1'"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</NSpace>
|
||||
</NCheckboxGroup>
|
||||
</NFormItem>
|
||||
<NFormItem label="附件" path="fileId">
|
||||
<FileUpload v-model:file-list="fileList" :file-size="20" :max="20" upload-type="file" :accept="accept" />
|
||||
</NFormItem>
|
||||
<NFormItem v-if="buttonPerm.copy" label="抄送人员">
|
||||
<NSpace>
|
||||
<NButton ghost type="primary" @click="openCopyModal">选择抄送人员</NButton>
|
||||
<TagGroup
|
||||
size="large"
|
||||
:value="selectCopyUserList.map(item => item.nickName)"
|
||||
:closable="true"
|
||||
@close="handleCopyTagClose"
|
||||
/>
|
||||
</NSpace>
|
||||
</NFormItem>
|
||||
<NFormItem
|
||||
v-if="buttonPerm.pop && nestNodeList && nestNodeList.length > 0"
|
||||
label="下一步审批人"
|
||||
path="assigneeMap"
|
||||
>
|
||||
<NSpace>
|
||||
<div v-for="(item, index) in nestNodeList" :key="index">
|
||||
<span>【{{ item.nodeName }}】:</span>
|
||||
<NSpace>
|
||||
<NButton ghost type="primary" @click="handleAssigneeOpen(item)">选择审批人员</NButton>
|
||||
<NInput v-if="false" v-model:value="model.assigneeMap![item.nodeCode]" />
|
||||
<TagGroup
|
||||
size="large"
|
||||
:value="nickNameMap[item.nodeCode]"
|
||||
:closable="true"
|
||||
@close="index => handleAssigneeTagClose(item.nodeCode, index)"
|
||||
/>
|
||||
</NSpace>
|
||||
</div>
|
||||
</NSpace>
|
||||
</NFormItem>
|
||||
<NFormItem v-if="isWaiting" label="审批意见" path="message">
|
||||
<NInput v-model:value="model.message" type="textarea" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</NSpin>
|
||||
<template #footer>
|
||||
<NSpace justify="end" :size="16">
|
||||
<NButton @click="visible = false">{{ $t('common.cancel') }}</NButton>
|
||||
<!-- 委托 -->
|
||||
<NButton v-if="isWaiting && buttonPerm.trust" :loading="btnLoading" type="warning" @click="openDelegateModal">
|
||||
委托
|
||||
</NButton>
|
||||
<!-- 转办 -->
|
||||
<NButton
|
||||
v-if="isWaiting && buttonPerm.transfer"
|
||||
:loading="btnLoading"
|
||||
type="warning"
|
||||
@click="openTransferModal"
|
||||
>
|
||||
转办
|
||||
</NButton>
|
||||
<!-- 加签 -->
|
||||
<NButton
|
||||
v-if="isWaiting && buttonPerm.addSign && Number(task?.nodeRatio) > 0"
|
||||
:loading="btnLoading"
|
||||
type="warning"
|
||||
@click="openAddSignatureModal"
|
||||
>
|
||||
加签
|
||||
</NButton>
|
||||
<!-- 减签 -->
|
||||
<NButton
|
||||
v-if="isWaiting && buttonPerm.subSign && Number(task?.nodeRatio) > 0"
|
||||
:loading="btnLoading"
|
||||
type="warning"
|
||||
@click="openReduceSignatureModal"
|
||||
>
|
||||
减签
|
||||
</NButton>
|
||||
<!-- 终止 -->
|
||||
<NButton v-if="isWaiting && buttonPerm.termination" :loading="btnLoading" type="error" @click="handleTerminate">
|
||||
终止
|
||||
</NButton>
|
||||
<!-- 驳回 -->
|
||||
<NButton v-if="isWaiting && buttonPerm.back" :loading="btnLoading" type="error" @click="openBackModal">
|
||||
驳回
|
||||
</NButton>
|
||||
<NButton :loading="btnLoading" type="primary" @click="handleSubmit">提交</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
<!-- 抄送人员选择 -->
|
||||
<UserSelectModal
|
||||
v-model:visible="copyVisible"
|
||||
:row-keys="selectCopyUserIds"
|
||||
multiple
|
||||
@confirm="handleCopyConfirm"
|
||||
/>
|
||||
<!-- 下一步审批人员选择 -->
|
||||
<UserSelectModal
|
||||
v-model:visible="assigneeVisible"
|
||||
:row-keys="selectAssigneeIds"
|
||||
:search-user-ids="assigneeSearchUserIds"
|
||||
multiple
|
||||
@confirm="handleAssigneeConfirm"
|
||||
/>
|
||||
<!-- 转办 -->
|
||||
<UserSelectModal v-model:visible="transferVisible" @confirm="handleTransferConfirm" />
|
||||
<!-- 委托 -->
|
||||
<UserSelectModal v-model:visible="delegateVisible" @confirm="handleDelegateConfirm" />
|
||||
<!-- 加签 -->
|
||||
<UserSelectModal v-model:visible="addSignatureVisible" @confirm="handleAddSignatureConfirm" />
|
||||
<!-- 减签 -->
|
||||
<ReduceSignatureModal v-model:visible="reduceSignatureVisible" :task="task!" @reduce-submit="handleReduceSubmit" />
|
||||
<!-- 驳回 -->
|
||||
<BackTaskModal v-model:visible="backVisible" :task="task!" @submit="handleBackSubmit" />
|
||||
</NModal>
|
||||
</template>
|
@ -1,312 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { useBoolean, useLoading } from '@sa/hooks';
|
||||
import { flowCodeTypeOptions, flowCodeTypeRecord, leaveTypeOptions, leaveTypeRecord } from '@/constants/workflow';
|
||||
import { fetchCreateLeave, fetchGetLeaveDetail, fetchStartWorkflow, fetchUpdateLeave } from '@/service/api/workflow';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { $t } from '@/locales';
|
||||
import ApprovalInfoPanel from '@/components/workflow/approval-info-panel.vue';
|
||||
import FlowTaskApprovalModal from '@/components/workflow/flow-task-approval-modal.vue';
|
||||
import FlowDrawer from '@/components/workflow/flow-drawer.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'LeaveEdit'
|
||||
});
|
||||
|
||||
useDict('wf_task_status');
|
||||
|
||||
interface Props {
|
||||
operateType: CommonType.WorkflowTableOperateType;
|
||||
/** 业务ID */
|
||||
businessId?: CommonType.IdType;
|
||||
taskId?: CommonType.IdType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.Workflow.Leave | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
businessId: undefined,
|
||||
taskId: undefined
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
const approvalInfoPanelRef = ref<InstanceType<typeof ApprovalInfoPanel>>();
|
||||
|
||||
const { bool: taskApplyVisible, setTrue: setTaskApplyVisible } = useBoolean();
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
const title = computed(() => {
|
||||
const titles: Record<CommonType.WorkflowTableOperateType, string> = {
|
||||
add: '新增请假申请',
|
||||
edit: '编辑请假申请',
|
||||
detail: '查看请假申请',
|
||||
approval: '审批请假申请'
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
const readonly = computed(() => {
|
||||
return props.operateType === 'detail' || props.operateType === 'approval';
|
||||
});
|
||||
|
||||
const taskId = ref<CommonType.IdType>(props.taskId!);
|
||||
|
||||
const respLeave = ref<Api.Workflow.Leave>();
|
||||
const startWorkflowResult = ref<Api.Workflow.StartWorkflowResult>();
|
||||
|
||||
type Model = Api.Workflow.LeaveOperateParams & {
|
||||
flowCode: Api.Workflow.FlowCodeType;
|
||||
};
|
||||
|
||||
const model: Model = reactive(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
flowCode: 'leave1',
|
||||
leaveType: null,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
leaveDays: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type ModelDetail = Api.Workflow.LeaveDetail & {
|
||||
flowCode: Api.Workflow.FlowCodeType;
|
||||
};
|
||||
|
||||
const modelDetail: ModelDetail = reactive(createDefaultModelDetail());
|
||||
|
||||
function createDefaultModelDetail(): ModelDetail {
|
||||
return {
|
||||
flowCode: 'leave1',
|
||||
status: null,
|
||||
id: null,
|
||||
leaveType: null,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
leaveDays: null,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
const showApprovalInfoPanel = computed(() => {
|
||||
return modelDetail.status !== 'draft';
|
||||
});
|
||||
type StartWorkflowModel = Api.Workflow.StartWorkflowOperateParams;
|
||||
|
||||
const startWorkflowModel: StartWorkflowModel = reactive(createDefaultStartWorkflowModel());
|
||||
|
||||
function createDefaultStartWorkflowModel(): StartWorkflowModel {
|
||||
return {
|
||||
flowCode: null,
|
||||
businessId: null,
|
||||
variables: {}
|
||||
};
|
||||
}
|
||||
|
||||
const dateRange = computed<[string, string] | null>({
|
||||
get: () => {
|
||||
if (!model.startDate || !model.endDate) return null;
|
||||
return [model.startDate, model.endDate] as [string, string];
|
||||
},
|
||||
set: (value: [string, string] | null) => {
|
||||
if (value) {
|
||||
model.startDate = value[0];
|
||||
model.endDate = value[1];
|
||||
// 计算请假天数
|
||||
const start = dayjs(value[0]);
|
||||
const end = dayjs(value[1]);
|
||||
model.leaveDays = end.diff(start, 'day') + 1;
|
||||
} else {
|
||||
model.startDate = null;
|
||||
model.endDate = null;
|
||||
model.leaveDays = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'id' | 'leaveType' | 'leaveDays' | 'startDate' | 'endDate'> | 'flowCode';
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
id: createRequiredRule('id不能为空'),
|
||||
flowCode: createRequiredRule('流程类型不能为空'),
|
||||
leaveType: createRequiredRule('请假类型不能为空'),
|
||||
startDate: createRequiredRule('请假时间不能为空'),
|
||||
endDate: createRequiredRule('结束时间不能为空'),
|
||||
leaveDays: createRequiredRule('请假天数不能为空')
|
||||
};
|
||||
|
||||
async function handleUpdateModelWhenEdit() {
|
||||
if (props.operateType === 'add') {
|
||||
Object.assign(model, createDefaultModel());
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.rowData) {
|
||||
Object.assign(model, props.rowData);
|
||||
Object.assign(modelDetail, props.rowData);
|
||||
} else {
|
||||
const { error, data } = await fetchGetLeaveDetail(props.businessId!);
|
||||
if (error) {
|
||||
window.$message?.error(error.message);
|
||||
return;
|
||||
}
|
||||
Object.assign(model, data);
|
||||
Object.assign(modelDetail, data);
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleOperate() {
|
||||
await validate();
|
||||
// request
|
||||
if (props.operateType === 'add') {
|
||||
const { leaveType, startDate, endDate, leaveDays, remark } = model;
|
||||
const { error, data } = await fetchCreateLeave({ leaveType, startDate, endDate, leaveDays, remark });
|
||||
if (error) return;
|
||||
respLeave.value = data;
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { id, leaveType, startDate, endDate, leaveDays, remark } = model;
|
||||
const { error, data } = await fetchUpdateLeave({ id, leaveType, startDate, endDate, leaveDays, remark });
|
||||
if (error) return;
|
||||
respLeave.value = data;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveDraft() {
|
||||
await handleOperate();
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
const taskVariables = ref<{ [key: string]: any }>({});
|
||||
|
||||
async function handleSubmit() {
|
||||
await handleOperate();
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
// 提交流程
|
||||
startWorkflowModel.businessId = respLeave.value?.id;
|
||||
startWorkflowModel.flowCode = model.flowCode;
|
||||
taskVariables.value = {
|
||||
leaveDays: respLeave.value?.leaveDays,
|
||||
userList: ['1', '3', '4']
|
||||
};
|
||||
startWorkflowModel.variables = taskVariables.value;
|
||||
const { error, data } = await fetchStartWorkflow(startWorkflowModel);
|
||||
if (error) return;
|
||||
startWorkflowResult.value = data;
|
||||
taskId.value = data.taskId!;
|
||||
setTaskApplyVisible();
|
||||
}
|
||||
|
||||
function handleTaskFinished() {
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
function handleApproval() {
|
||||
setTaskApplyVisible();
|
||||
}
|
||||
|
||||
async function initializeData() {
|
||||
if (visible.value) {
|
||||
startLoading();
|
||||
await handleUpdateModelWhenEdit();
|
||||
restoreValidation();
|
||||
|
||||
if (showApprovalInfoPanel.value) {
|
||||
approvalInfoPanelRef.value?.initData();
|
||||
}
|
||||
endLoading();
|
||||
}
|
||||
}
|
||||
|
||||
watch(visible, initializeData, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FlowDrawer
|
||||
v-model:visible="visible"
|
||||
:title="title"
|
||||
:loading="loading"
|
||||
:operate-type="operateType"
|
||||
:status="modelDetail.status"
|
||||
@close="closeDrawer"
|
||||
@save-draft="handleSaveDraft"
|
||||
@submit="handleSubmit"
|
||||
@approval="handleApproval"
|
||||
>
|
||||
<div :class="loading ? 'hidden' : ''">
|
||||
<div v-if="!readonly" class="h-full">
|
||||
<NForm ref="formRef" :model="model" :rules="rules">
|
||||
<NFormItem label="流程类型" path="flowCode">
|
||||
<NSelect v-model:value="model.flowCode" placeholder="请输入流程类型" :options="flowCodeTypeOptions" />
|
||||
</NFormItem>
|
||||
<NFormItem label="请假类型" path="leaveType">
|
||||
<NSelect v-model:value="model.leaveType" placeholder="请输入请假类型" :options="leaveTypeOptions" />
|
||||
</NFormItem>
|
||||
<NFormItem label="请假时间" path="startDate">
|
||||
<NDatePicker
|
||||
v-model:formatted-value="dateRange"
|
||||
class="w-full"
|
||||
type="datetimerange"
|
||||
format="yyyy-MM-dd HH:mm:ss"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="请假天数" path="leaveDays">
|
||||
<NInputNumber v-model:value="model.leaveDays" class="w-full" disabled placeholder="请输入请假天数" />
|
||||
</NFormItem>
|
||||
<NFormItem label="请假原因" path="remark">
|
||||
<NInput v-model:value="model.remark" placeholder="请输入请假原因" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</div>
|
||||
<div v-else>
|
||||
<NDescriptions size="small" bordered :column="2" label-placement="left">
|
||||
<NDescriptionsItem label="流程类型">
|
||||
{{ flowCodeTypeRecord[modelDetail.flowCode] }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="请假类型">
|
||||
<NTag type="info">{{ leaveTypeRecord[modelDetail.leaveType!] }}</NTag>
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="请假时间">
|
||||
{{ `${modelDetail.startDate} 至 ${modelDetail.endDate}` }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="请假天数">{{ modelDetail.leaveDays }} 天</NDescriptionsItem>
|
||||
<NDescriptionsItem label="请假原因">
|
||||
{{ modelDetail.remark || '-' }}
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
<!-- 审批信息 -->
|
||||
<ApprovalInfoPanel v-if="showApprovalInfoPanel" ref="approvalInfoPanelRef" :business-id="modelDetail.id!" />
|
||||
</div>
|
||||
</div>
|
||||
</FlowDrawer>
|
||||
<FlowTaskApprovalModal
|
||||
v-model:visible="taskApplyVisible"
|
||||
:task-id="taskId"
|
||||
:task-variables="taskVariables"
|
||||
@finished="handleTaskFinished"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,154 +0,0 @@
|
||||
<script lang="tsx" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
import { fetchGetCurrentTaskAllUser, fetchTaskOperate } from '@/service/api/workflow/task';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'ReduceSignatureModal'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
task: Api.Workflow.Task;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'reduceSubmit'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
type UserTaskModel = Api.System.User & { nodeName: string };
|
||||
|
||||
const userData = ref<UserTaskModel[]>([]);
|
||||
|
||||
type Model = Api.Workflow.TaskOperateParams;
|
||||
const model: Model = reactive(createDefaultModel());
|
||||
|
||||
const checkedRowKeys = ref<CommonType.IdType[]>([]);
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
taskId: null,
|
||||
userId: undefined,
|
||||
userIds: undefined,
|
||||
message: ''
|
||||
};
|
||||
}
|
||||
const columns = ref<NaiveUI.TableColumn<UserTaskModel>[]>([
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center',
|
||||
width: 50
|
||||
},
|
||||
{
|
||||
title: '节点名称',
|
||||
key: 'nodeName',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
title: '办理人员',
|
||||
key: 'nickName',
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 130,
|
||||
render(row) {
|
||||
return (
|
||||
<ButtonIcon
|
||||
text
|
||||
type="error"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltipContent={'减签'}
|
||||
popconfirmContent={'是否确认减签?'}
|
||||
onPositiveClick={() => handleReduceSignature([row.userId])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
async function handleReduceSignature(userIds: CommonType.IdType[]) {
|
||||
model.taskId = props.task.id;
|
||||
model.userIds = userIds;
|
||||
const { error } = await fetchTaskOperate(model, 'reductionSignature');
|
||||
if (error) return;
|
||||
window.$message?.success('减签成功');
|
||||
handleCloseDrawer();
|
||||
}
|
||||
|
||||
async function getTaskAllUser() {
|
||||
startLoading();
|
||||
const { error, data } = await fetchGetCurrentTaskAllUser(props.task.id);
|
||||
if (error) return;
|
||||
userData.value = data.map(item => ({
|
||||
...item,
|
||||
nodeName: props.task.nodeName
|
||||
}));
|
||||
endLoading();
|
||||
}
|
||||
|
||||
function handleCloseDrawer() {
|
||||
visible.value = false;
|
||||
emit('reduceSubmit');
|
||||
}
|
||||
|
||||
watch(visible, async () => {
|
||||
if (visible.value) {
|
||||
await getTaskAllUser();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="visible" class="w-700px" preset="card" title="待减签人员">
|
||||
<NCard class="h-full card-wrapper">
|
||||
<NSpace wrap justify="space-between" class="mb-16px lt-sm:w-200px">
|
||||
<TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
|
||||
<NButton
|
||||
size="small"
|
||||
ghost
|
||||
type="error"
|
||||
:disabled="checkedRowKeys.length === 0"
|
||||
@click="handleReduceSignature(checkedRowKeys)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-material-symbols:delete-outline class="text-icon" />
|
||||
</template>
|
||||
批量减签
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
class="h-400px"
|
||||
flex-height
|
||||
:row-key="row => row.userId"
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data="userData"
|
||||
:loading="loading"
|
||||
/>
|
||||
</NCard>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.n-alert {
|
||||
--n-padding: 5px 13px !important;
|
||||
--n-icon-margin: 6px 8px 0 12px !important;
|
||||
--n-icon-size: 20px !important;
|
||||
}
|
||||
</style>
|
@ -5,9 +5,9 @@ export const GLOBAL_HEADER_MENU_ID = '__GLOBAL_HEADER_MENU__';
|
||||
export const GLOBAL_SIDER_MENU_ID = '__GLOBAL_SIDER_MENU__';
|
||||
|
||||
export const themeSchemaRecord: Record<UnionKey.ThemeScheme, App.I18n.I18nKey> = {
|
||||
light: 'theme.themeSchema.light',
|
||||
dark: 'theme.themeSchema.dark',
|
||||
auto: 'theme.themeSchema.auto'
|
||||
light: 'theme.appearance.themeSchema.light',
|
||||
dark: 'theme.appearance.themeSchema.dark',
|
||||
auto: 'theme.appearance.themeSchema.auto'
|
||||
};
|
||||
|
||||
export const themeSchemaOptions = transformRecordToOption(themeSchemaRecord);
|
||||
@ -21,49 +21,61 @@ export const loginModuleRecord: Record<UnionKey.LoginModule, App.I18n.I18nKey> =
|
||||
};
|
||||
|
||||
export const themeLayoutModeRecord: Record<UnionKey.ThemeLayoutMode, App.I18n.I18nKey> = {
|
||||
vertical: 'theme.layoutMode.vertical',
|
||||
'vertical-mix': 'theme.layoutMode.vertical-mix',
|
||||
horizontal: 'theme.layoutMode.horizontal',
|
||||
'horizontal-mix': 'theme.layoutMode.horizontal-mix'
|
||||
vertical: 'theme.layout.layoutMode.vertical',
|
||||
'vertical-mix': 'theme.layout.layoutMode.vertical-mix',
|
||||
'vertical-hybrid-header-first': 'theme.layout.layoutMode.vertical-hybrid-header-first',
|
||||
horizontal: 'theme.layout.layoutMode.horizontal',
|
||||
'top-hybrid-sidebar-first': 'theme.layout.layoutMode.top-hybrid-sidebar-first',
|
||||
'top-hybrid-header-first': 'theme.layout.layoutMode.top-hybrid-header-first'
|
||||
};
|
||||
|
||||
export const themeLayoutModeOptions = transformRecordToOption(themeLayoutModeRecord);
|
||||
|
||||
export const themeScrollModeRecord: Record<UnionKey.ThemeScrollMode, App.I18n.I18nKey> = {
|
||||
wrapper: 'theme.scrollMode.wrapper',
|
||||
content: 'theme.scrollMode.content'
|
||||
wrapper: 'theme.layout.content.scrollMode.wrapper',
|
||||
content: 'theme.layout.content.scrollMode.content'
|
||||
};
|
||||
|
||||
export const themeScrollModeOptions = transformRecordToOption(themeScrollModeRecord);
|
||||
|
||||
export const themeTabModeRecord: Record<UnionKey.ThemeTabMode, App.I18n.I18nKey> = {
|
||||
chrome: 'theme.tab.mode.chrome',
|
||||
button: 'theme.tab.mode.button'
|
||||
chrome: 'theme.layout.tab.mode.chrome',
|
||||
button: 'theme.layout.tab.mode.button'
|
||||
};
|
||||
|
||||
export const themeTabModeOptions = transformRecordToOption(themeTabModeRecord);
|
||||
|
||||
export const themePageAnimationModeRecord: Record<UnionKey.ThemePageAnimateMode, App.I18n.I18nKey> = {
|
||||
'fade-slide': 'theme.page.mode.fade-slide',
|
||||
fade: 'theme.page.mode.fade',
|
||||
'fade-bottom': 'theme.page.mode.fade-bottom',
|
||||
'fade-scale': 'theme.page.mode.fade-scale',
|
||||
'zoom-fade': 'theme.page.mode.zoom-fade',
|
||||
'zoom-out': 'theme.page.mode.zoom-out',
|
||||
none: 'theme.page.mode.none'
|
||||
'fade-slide': 'theme.layout.content.page.mode.fade-slide',
|
||||
fade: 'theme.layout.content.page.mode.fade',
|
||||
'fade-bottom': 'theme.layout.content.page.mode.fade-bottom',
|
||||
'fade-scale': 'theme.layout.content.page.mode.fade-scale',
|
||||
'zoom-fade': 'theme.layout.content.page.mode.zoom-fade',
|
||||
'zoom-out': 'theme.layout.content.page.mode.zoom-out',
|
||||
none: 'theme.layout.content.page.mode.none'
|
||||
};
|
||||
|
||||
export const themePageAnimationModeOptions = transformRecordToOption(themePageAnimationModeRecord);
|
||||
|
||||
export const resetCacheStrategyRecord: Record<UnionKey.ResetCacheStrategy, App.I18n.I18nKey> = {
|
||||
close: 'theme.resetCacheStrategy.close',
|
||||
refresh: 'theme.resetCacheStrategy.refresh'
|
||||
close: 'theme.layout.resetCacheStrategy.close',
|
||||
refresh: 'theme.layout.resetCacheStrategy.refresh'
|
||||
};
|
||||
|
||||
export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord);
|
||||
|
||||
export const DARK_CLASS = 'dark';
|
||||
|
||||
export const watermarkTimeFormatOptions = [
|
||||
{ label: 'YYYY-MM-DD HH:mm', value: 'YYYY-MM-DD HH:mm' },
|
||||
{ label: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss' },
|
||||
{ label: 'YYYY/MM/DD HH:mm', value: 'YYYY/MM/DD HH:mm' },
|
||||
{ label: 'YYYY/MM/DD HH:mm:ss', value: 'YYYY/MM/DD HH:mm:ss' },
|
||||
{ label: 'HH:mm', value: 'HH:mm' },
|
||||
{ label: 'HH:mm:ss', value: 'HH:mm:ss' },
|
||||
{ label: 'MM-DD HH:mm', value: 'MM-DD HH:mm' }
|
||||
];
|
||||
|
||||
export const themeTableSizeRecord: Record<UnionKey.ThemeTableSize, App.I18n.I18nKey> = {
|
||||
small: 'theme.table.size.small',
|
||||
medium: 'theme.table.size.medium',
|
||||
|
@ -1,82 +0,0 @@
|
||||
import { transformRecordToOption } from '@/utils/common';
|
||||
|
||||
export const cooperateTypeRecord: Record<Api.Workflow.CooperateType, string> = {
|
||||
1: '审批',
|
||||
2: '转办',
|
||||
3: '委派',
|
||||
4: '会签',
|
||||
5: '票签',
|
||||
6: '加签',
|
||||
7: '减签'
|
||||
};
|
||||
|
||||
export const cooperateTypeOptions = transformRecordToOption(cooperateTypeRecord);
|
||||
|
||||
export const businessStatusRecord: Record<Api.Workflow.BusinessStatus, string> = {
|
||||
cancel: '已撤销',
|
||||
draft: '草稿',
|
||||
waiting: '待审批',
|
||||
finish: '已完成',
|
||||
invalid: '已作废',
|
||||
back: '已退回',
|
||||
termination: '已终止'
|
||||
};
|
||||
|
||||
export const businessStatusOptions = transformRecordToOption(businessStatusRecord);
|
||||
|
||||
export const messageTypeRecord: Record<Api.Workflow.MessageType, string> = {
|
||||
'1': '站内信',
|
||||
'2': '邮件',
|
||||
'3': '短信'
|
||||
};
|
||||
|
||||
export const messageTypeOptions = transformRecordToOption(messageTypeRecord);
|
||||
|
||||
export const flowCodeTypeRecord: Record<Api.Workflow.FlowCodeType, string> = {
|
||||
leave1: '请假申请-普通',
|
||||
leave2: '请假申请-排他网关',
|
||||
leave3: '请假申请-并行网关',
|
||||
leave4: '请假申请-会签',
|
||||
leave5: '请假申请-并行会签网关',
|
||||
leave6: '请假申请-排他并行会签'
|
||||
};
|
||||
|
||||
export const flowCodeTypeOptions = transformRecordToOption(flowCodeTypeRecord);
|
||||
|
||||
/** leave type */
|
||||
export const leaveTypeRecord: Record<Api.Workflow.LeaveType, string> = {
|
||||
'1': '事假',
|
||||
'2': '调休',
|
||||
'3': '病假',
|
||||
'4': '婚假'
|
||||
};
|
||||
|
||||
export const leaveTypeOptions = transformRecordToOption(leaveTypeRecord);
|
||||
|
||||
/** workflow publish status */
|
||||
export const workflowPublishStatusRecord: Record<Api.Workflow.WorkflowPublishStatus, string> = {
|
||||
'0': '未发布',
|
||||
'1': '已发布',
|
||||
'9': '失效'
|
||||
};
|
||||
|
||||
export const workflowPublishStatusOptions = transformRecordToOption(workflowPublishStatusRecord);
|
||||
|
||||
/** node type */
|
||||
export const workflowNodeTypeRecord: Record<Api.Workflow.WorkflowNodeType, string> = {
|
||||
0: '开始节点',
|
||||
1: '中间节点',
|
||||
2: '结束节点',
|
||||
3: '互斥网关',
|
||||
4: '并行网关'
|
||||
};
|
||||
|
||||
export const workflowNodeTypeOptions = transformRecordToOption(workflowNodeTypeRecord);
|
||||
|
||||
/** activity status */
|
||||
export const workflowActivityStatusRecord: Record<Api.Workflow.WorkflowActivityStatus, string> = {
|
||||
0: '挂起',
|
||||
1: '激活'
|
||||
};
|
||||
|
||||
export const workflowActivityStatusOptions = transformRecordToOption(workflowActivityStatusRecord);
|
@ -16,6 +16,12 @@ export function useDownload() {
|
||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||
|
||||
const isHttps = () => {
|
||||
const protocol = document.location.protocol;
|
||||
const hostname = document.location.hostname;
|
||||
return protocol === 'https' || hostname === 'localhost' || hostname === '127.0.0.1';
|
||||
};
|
||||
|
||||
/** 获取通用请求头 */
|
||||
const getCommonHeaders = (contentType = 'application/octet-stream') => ({
|
||||
Authorization: `Bearer ${localStg.get('token')}`,
|
||||
@ -109,9 +115,10 @@ export function useDownload() {
|
||||
|
||||
await handleResponse(response);
|
||||
|
||||
const finalFilename = filename || response.headers.get('Download-Filename') || `download-${timestamp}`;
|
||||
const rawHeader = response.headers.get('Download-Filename');
|
||||
const finalFilename = filename || (rawHeader ? decodeURIComponent(rawHeader) : null) || `download-${timestamp}`;
|
||||
|
||||
if (response.body) {
|
||||
if (response.body && isHttps()) {
|
||||
const contentLength = Number(response.headers.get('Content-Length'));
|
||||
await downloadByStream(response.body, finalFilename, contentLength);
|
||||
return;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { computed, effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
|
||||
import { computed, effectScope, nextTick, onScopeDispose, shallowRef, watch } from 'vue';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts';
|
||||
@ -86,11 +86,11 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
|
||||
const themeStore = useThemeStore();
|
||||
const darkMode = computed(() => themeStore.darkMode);
|
||||
|
||||
const domRef = ref<HTMLElement | null>(null);
|
||||
const domRef = shallowRef<HTMLElement | null>(null);
|
||||
const initialSize = { width: 0, height: 0 };
|
||||
const { width, height } = useElementSize(domRef, initialSize);
|
||||
|
||||
let chart: echarts.ECharts | null = null;
|
||||
const chart = shallowRef<echarts.ECharts | null>(null);
|
||||
const chartOptions: T = optionsFactory();
|
||||
|
||||
const {
|
||||
@ -111,18 +111,9 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
|
||||
onDestroy
|
||||
} = hooks;
|
||||
|
||||
/**
|
||||
* whether can render chart
|
||||
*
|
||||
* when domRef is ready and initialSize is valid
|
||||
*/
|
||||
function canRender() {
|
||||
return domRef.value && initialSize.width > 0 && initialSize.height > 0;
|
||||
}
|
||||
|
||||
/** is chart rendered */
|
||||
function isRendered() {
|
||||
return Boolean(domRef.value && chart);
|
||||
return Boolean(domRef.value && chart.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,59 +122,59 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
|
||||
* @param callback callback function
|
||||
*/
|
||||
async function updateOptions(callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions) {
|
||||
if (!isRendered()) return;
|
||||
|
||||
const updatedOpts = callback(chartOptions, optionsFactory);
|
||||
|
||||
Object.assign(chartOptions, updatedOpts);
|
||||
|
||||
await nextTick();
|
||||
|
||||
if (!isRendered()) return;
|
||||
|
||||
if (isRendered()) {
|
||||
chart?.clear();
|
||||
chart.value?.clear();
|
||||
}
|
||||
|
||||
chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' });
|
||||
chart.value?.setOption({ ...updatedOpts, backgroundColor: 'transparent' });
|
||||
|
||||
await onUpdated?.(chart!);
|
||||
await onUpdated?.(chart.value!);
|
||||
}
|
||||
|
||||
function setOptions(options: T) {
|
||||
chart?.setOption(options);
|
||||
chart.value?.setOption(options);
|
||||
}
|
||||
|
||||
/** render chart */
|
||||
async function render() {
|
||||
if (!isRendered()) {
|
||||
const chartTheme = darkMode.value ? 'dark' : 'light';
|
||||
if (isRendered()) return;
|
||||
|
||||
await nextTick();
|
||||
const chartTheme = darkMode.value ? 'dark' : 'light';
|
||||
|
||||
chart = echarts.init(domRef.value, chartTheme);
|
||||
chart.value = echarts.init(domRef.value, chartTheme);
|
||||
|
||||
chart.setOption({ ...chartOptions, backgroundColor: 'transparent' });
|
||||
chart.value?.setOption({ ...chartOptions, backgroundColor: 'transparent' });
|
||||
|
||||
await onRender?.(chart);
|
||||
}
|
||||
await onRender?.(chart.value!);
|
||||
}
|
||||
|
||||
/** resize chart */
|
||||
function resize() {
|
||||
chart?.resize();
|
||||
chart.value?.resize();
|
||||
}
|
||||
|
||||
/** destroy chart */
|
||||
async function destroy() {
|
||||
if (!chart) return;
|
||||
if (!chart.value) return;
|
||||
|
||||
await onDestroy?.(chart);
|
||||
chart?.dispose();
|
||||
chart = null;
|
||||
await onDestroy?.(chart.value);
|
||||
chart.value?.dispose();
|
||||
chart.value = null;
|
||||
}
|
||||
|
||||
/** change chart theme */
|
||||
async function changeTheme() {
|
||||
await destroy();
|
||||
await render();
|
||||
await onUpdated?.(chart!);
|
||||
await onUpdated?.(chart.value!);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,30 +187,29 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
|
||||
initialSize.width = w;
|
||||
initialSize.height = h;
|
||||
|
||||
// size is abnormal, destroy chart
|
||||
if (!canRender()) {
|
||||
await destroy();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// resize chart
|
||||
if (isRendered()) {
|
||||
resize();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// render chart
|
||||
await render();
|
||||
|
||||
if (chart) {
|
||||
await onUpdated?.(chart);
|
||||
if (chart.value) {
|
||||
await onUpdated?.(chart.value);
|
||||
}
|
||||
}
|
||||
|
||||
scope.run(() => {
|
||||
watch([width, height], ([newWidth, newHeight]) => {
|
||||
renderChartBySize(newWidth, newHeight);
|
||||
});
|
||||
watch(
|
||||
[width, height],
|
||||
([newWidth, newHeight]) => {
|
||||
renderChartBySize(newWidth, newHeight);
|
||||
},
|
||||
{ flush: 'post' }
|
||||
);
|
||||
|
||||
watch(darkMode, () => {
|
||||
changeTheme();
|
||||
@ -233,6 +223,7 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
|
||||
|
||||
return {
|
||||
domRef,
|
||||
chart,
|
||||
updateOptions,
|
||||
setOptions
|
||||
};
|
||||
|
@ -1,196 +1,55 @@
|
||||
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';
|
||||
import { computed, effectScope, onScopeDispose, reactive, ref, shallowRef, watch } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import type { PaginationProps } from 'naive-ui';
|
||||
import { useBoolean, useTable } from '@sa/hooks';
|
||||
import type { PaginationData, TableColumnCheck, UseTableOptions } from '@sa/hooks';
|
||||
import type { FlatResponseData } from '@sa/axios';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useBoolean, useHookTable } from '@sa/hooks';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
type TableData = NaiveUI.TableData;
|
||||
type GetTableData<A extends NaiveUI.TableApiFn> = NaiveUI.GetTableData<A>;
|
||||
type TableColumn<T> = NaiveUI.TableColumn<T>;
|
||||
export type UseNaiveTableOptions<ResponseData, ApiData, Pagination extends boolean> = Omit<
|
||||
UseTableOptions<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, Pagination>,
|
||||
'pagination' | 'getColumnChecks' | 'getColumns'
|
||||
> & {
|
||||
/**
|
||||
* get column visible
|
||||
*
|
||||
* @param column
|
||||
*
|
||||
* @default true
|
||||
*
|
||||
* @returns true if the column is visible, false otherwise
|
||||
*/
|
||||
getColumnVisible?: (column: NaiveUI.TableColumn<ApiData>) => boolean;
|
||||
};
|
||||
|
||||
export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
|
||||
const SELECTION_KEY = '__selection__';
|
||||
|
||||
const EXPAND_KEY = '__expand__';
|
||||
|
||||
export function useNaiveTable<ResponseData, ApiData>(options: UseNaiveTableOptions<ResponseData, ApiData, false>) {
|
||||
const scope = effectScope();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const isMobile = computed(() => appStore.isMobile);
|
||||
|
||||
const { apiFn, apiParams, immediate, showTotal = true } = config;
|
||||
|
||||
const SELECTION_KEY = '__selection__';
|
||||
|
||||
const EXPAND_KEY = '__expand__';
|
||||
|
||||
const {
|
||||
loading,
|
||||
empty,
|
||||
data,
|
||||
columns,
|
||||
columnChecks,
|
||||
reloadColumns,
|
||||
getData,
|
||||
searchParams,
|
||||
updateSearchParams,
|
||||
resetSearchParams,
|
||||
updateApiFn
|
||||
} = useHookTable<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>({
|
||||
apiFn,
|
||||
apiParams,
|
||||
columns: config.columns,
|
||||
transformer: res => {
|
||||
const { rows: records = [], total = 0 } = res.data || {};
|
||||
|
||||
const current = searchParams.pageNum as number;
|
||||
const size = (searchParams.pageSize || 0) as number;
|
||||
|
||||
// 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 recordsWithIndex = records.map((item, index) => {
|
||||
return {
|
||||
...item,
|
||||
index: (current - 1) * pageSize + index + 1
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
data: recordsWithIndex,
|
||||
pageNum: current,
|
||||
pageSize,
|
||||
total
|
||||
};
|
||||
},
|
||||
getColumnChecks: cols => {
|
||||
const checks: NaiveUI.TableColumnCheck[] = [];
|
||||
|
||||
cols.forEach(column => {
|
||||
if (isTableColumnHasKey(column)) {
|
||||
checks.push({
|
||||
key: column.key as string,
|
||||
title: column.title!,
|
||||
checked: true
|
||||
});
|
||||
} else if (column.type === 'selection') {
|
||||
checks.push({
|
||||
key: SELECTION_KEY,
|
||||
title: $t('common.check'),
|
||||
checked: true
|
||||
});
|
||||
} else if (column.type === 'expand') {
|
||||
checks.push({
|
||||
key: EXPAND_KEY,
|
||||
title: $t('common.expandColumn'),
|
||||
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);
|
||||
} else if (column.type === 'expand') {
|
||||
columnMap.set(EXPAND_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 { total } = transformed;
|
||||
|
||||
updatePagination({
|
||||
page: searchParams.pageNum,
|
||||
pageSize: searchParams.pageSize,
|
||||
itemCount: total
|
||||
});
|
||||
},
|
||||
immediate
|
||||
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, false>({
|
||||
...options,
|
||||
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
|
||||
getColumns
|
||||
});
|
||||
|
||||
const pagination: PaginationProps = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
showSizePicker: true,
|
||||
itemCount: 0,
|
||||
pageSizes: [10, 15, 20, 25, 30],
|
||||
onUpdatePage: async (page: number) => {
|
||||
pagination.page = page;
|
||||
|
||||
updateSearchParams({
|
||||
pageNum: page,
|
||||
pageSize: pagination.pageSize!
|
||||
});
|
||||
|
||||
getData();
|
||||
},
|
||||
onUpdatePageSize: async (pageSize: number) => {
|
||||
pagination.pageSize = pageSize;
|
||||
pagination.page = 1;
|
||||
|
||||
updateSearchParams({
|
||||
pageNum: pagination.page,
|
||||
pageSize
|
||||
});
|
||||
|
||||
getData();
|
||||
},
|
||||
...(showTotal
|
||||
? {
|
||||
prefix: page => $t('datatable.itemCount', { total: page.itemCount })
|
||||
}
|
||||
: {})
|
||||
// calculate the total width of the table this is used for horizontal scrolling
|
||||
const scrollX = computed(() => {
|
||||
return result.columns.value.reduce((acc, column) => {
|
||||
return acc + Number(column.width ?? column.minWidth ?? 120);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
|
||||
const mobilePagination = computed(() => {
|
||||
const p: PaginationProps = {
|
||||
...pagination,
|
||||
pageSlot: isMobile.value ? 3 : 9,
|
||||
prefix: !isMobile.value && showTotal ? pagination.prefix : undefined
|
||||
};
|
||||
|
||||
return p;
|
||||
});
|
||||
|
||||
function updatePagination(update: Partial<PaginationProps>) {
|
||||
Object.assign(pagination, update);
|
||||
}
|
||||
|
||||
/**
|
||||
* get data by page number
|
||||
*
|
||||
* @param pageNum the page number. default is 1
|
||||
*/
|
||||
async function getDataByPage(pageNum: number = 1) {
|
||||
updatePagination({
|
||||
page: pageNum
|
||||
});
|
||||
|
||||
updateSearchParams({
|
||||
pageNum,
|
||||
pageSize: pagination.pageSize!
|
||||
});
|
||||
|
||||
await getData();
|
||||
}
|
||||
|
||||
scope.run(() => {
|
||||
watch(
|
||||
() => appStore.locale,
|
||||
() => {
|
||||
reloadColumns();
|
||||
result.reloadColumns();
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -200,28 +59,126 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
||||
});
|
||||
|
||||
return {
|
||||
loading,
|
||||
empty,
|
||||
data,
|
||||
columns,
|
||||
columnChecks,
|
||||
reloadColumns,
|
||||
pagination,
|
||||
mobilePagination,
|
||||
updatePagination,
|
||||
getData,
|
||||
getDataByPage,
|
||||
searchParams,
|
||||
updateSearchParams,
|
||||
resetSearchParams,
|
||||
updateApiFn
|
||||
...result,
|
||||
scrollX
|
||||
};
|
||||
}
|
||||
|
||||
export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void>) {
|
||||
type PaginationParams = Pick<PaginationProps, 'page' | 'pageSize'>;
|
||||
|
||||
type UseNaivePaginatedTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, true> & {
|
||||
paginationProps?: Omit<PaginationProps, 'page' | 'pageSize' | 'itemCount'>;
|
||||
/**
|
||||
* whether to show the total count of the table
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
showTotal?: boolean;
|
||||
onPaginationParamsChange?: (params: PaginationParams) => void | Promise<void>;
|
||||
};
|
||||
|
||||
export function useNaivePaginatedTable<ResponseData, ApiData>(
|
||||
options: UseNaivePaginatedTableOptions<ResponseData, ApiData>
|
||||
) {
|
||||
const scope = effectScope();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const isMobile = computed(() => appStore.isMobile);
|
||||
|
||||
const showTotal = computed(() => options.showTotal ?? true);
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
itemCount: 0,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 15, 20, 25, 30],
|
||||
prefix: showTotal.value ? page => $t('datatable.itemCount', { total: page.itemCount }) : undefined,
|
||||
onUpdatePage(page) {
|
||||
pagination.page = page;
|
||||
},
|
||||
onUpdatePageSize(pageSize) {
|
||||
pagination.pageSize = pageSize;
|
||||
pagination.page = 1;
|
||||
},
|
||||
...options.paginationProps
|
||||
}) as PaginationProps;
|
||||
|
||||
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
|
||||
const mobilePagination = computed(() => {
|
||||
const p: PaginationProps = {
|
||||
...pagination,
|
||||
pageSlot: isMobile.value ? 3 : 9,
|
||||
prefix: !isMobile.value && showTotal.value ? pagination.prefix : undefined
|
||||
};
|
||||
|
||||
return p;
|
||||
});
|
||||
|
||||
const paginationParams = computed(() => {
|
||||
const { page, pageSize } = pagination;
|
||||
|
||||
return {
|
||||
page,
|
||||
pageSize
|
||||
};
|
||||
});
|
||||
|
||||
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, true>({
|
||||
...options,
|
||||
pagination: true,
|
||||
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
|
||||
getColumns,
|
||||
onFetched: data => {
|
||||
pagination.itemCount = data.total;
|
||||
}
|
||||
});
|
||||
|
||||
async function getDataByPage(page: number = 1) {
|
||||
if (page !== pagination.page) {
|
||||
pagination.page = page;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await result.getData();
|
||||
}
|
||||
|
||||
scope.run(() => {
|
||||
watch(
|
||||
() => appStore.locale,
|
||||
() => {
|
||||
result.reloadColumns();
|
||||
}
|
||||
);
|
||||
|
||||
watch(paginationParams, async newVal => {
|
||||
await options.onPaginationParamsChange?.(newVal);
|
||||
|
||||
await result.getData();
|
||||
});
|
||||
});
|
||||
|
||||
onScopeDispose(() => {
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
getDataByPage,
|
||||
pagination,
|
||||
mobilePagination
|
||||
};
|
||||
}
|
||||
|
||||
export function useTableOperate<TableData>(
|
||||
data: Ref<TableData[]>,
|
||||
idKey: keyof TableData,
|
||||
getData: () => Promise<void>
|
||||
) {
|
||||
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
|
||||
|
||||
const operateType = ref<NaiveUI.TableOperateType>('add');
|
||||
const operateType = shallowRef<NaiveUI.TableOperateType>('add');
|
||||
|
||||
function handleAdd() {
|
||||
operateType.value = 'add';
|
||||
@ -229,18 +186,18 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
|
||||
}
|
||||
|
||||
/** the editing row data */
|
||||
const editingData: Ref<T | null> = ref(null);
|
||||
const editingData = shallowRef<TableData | null>(null);
|
||||
|
||||
function handleEdit(field: keyof T, id: CommonType.IdType) {
|
||||
function handleEdit(id: TableData[keyof TableData]) {
|
||||
operateType.value = 'edit';
|
||||
const findItem = data.value.find(item => item[field] === id) || null;
|
||||
const findItem = data.value.find(item => item[idKey] === id) || null;
|
||||
editingData.value = jsonClone(findItem);
|
||||
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
/** the checked row keys of table */
|
||||
const checkedRowKeys = ref<CommonType.IdType[]>([]);
|
||||
const checkedRowKeys = shallowRef<string[]>([]);
|
||||
|
||||
/** the hook after the batch delete operation is completed */
|
||||
async function onBatchDeleted() {
|
||||
@ -272,6 +229,285 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
|
||||
};
|
||||
}
|
||||
|
||||
function isTableColumnHasKey<T>(column: TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
|
||||
export function defaultTransform<ApiData>(
|
||||
response: FlatResponseData<any, Api.Common.PaginatingQueryRecord<ApiData>>
|
||||
): PaginationData<ApiData> {
|
||||
const { data, error } = response;
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
data: [],
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
};
|
||||
}
|
||||
|
||||
const { rows: records, pageSize: current, pageNum: size, total } = data;
|
||||
|
||||
return {
|
||||
data: records,
|
||||
pageNum: current,
|
||||
pageSize: size,
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
type UseNaiveTreeTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, false> & {
|
||||
keyField: keyof ApiData;
|
||||
defaultExpandAll?: boolean;
|
||||
};
|
||||
|
||||
export function useNaiveTreeTable<ResponseData, ApiData>(options: UseNaiveTreeTableOptions<ResponseData, ApiData>) {
|
||||
const scope = effectScope();
|
||||
const appStore = useAppStore();
|
||||
const rows: Ref<ApiData[]> = ref([]);
|
||||
|
||||
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, false>({
|
||||
...options,
|
||||
pagination: false,
|
||||
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
|
||||
getColumns,
|
||||
onFetched: transformData => {
|
||||
const data: ApiData[] = [];
|
||||
|
||||
const collect = (nodes: any[]) => {
|
||||
nodes.forEach(node => {
|
||||
data.push(node);
|
||||
if (node?.children?.length) {
|
||||
collect(node.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
collect(transformData);
|
||||
rows.value = data;
|
||||
}
|
||||
});
|
||||
|
||||
const { keyField = 'id', defaultExpandAll = false } = options;
|
||||
|
||||
const expandedRowKeys = ref<ApiData[keyof ApiData][]>([]);
|
||||
const { bool: isCollapse, toggle: toggleCollapse } = useBoolean(defaultExpandAll);
|
||||
|
||||
/** expand all nodes */
|
||||
function expandAll() {
|
||||
toggleCollapse();
|
||||
expandedRowKeys.value = rows.value.map(item => item[keyField as keyof ApiData]);
|
||||
}
|
||||
|
||||
/** collapse all nodes */
|
||||
function collapseAll() {
|
||||
toggleCollapse();
|
||||
expandedRowKeys.value = [];
|
||||
}
|
||||
|
||||
scope.run(() => {
|
||||
watch(
|
||||
() => appStore.locale,
|
||||
() => {
|
||||
result.reloadColumns();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
onScopeDispose(() => {
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
rows,
|
||||
isCollapse,
|
||||
expandedRowKeys,
|
||||
expandAll,
|
||||
collapseAll
|
||||
};
|
||||
}
|
||||
|
||||
export function useTreeTableOperate<ApiData>(data: Ref<ApiData[]>, idKey: keyof ApiData, getData: () => Promise<void>) {
|
||||
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
|
||||
|
||||
const operateType = shallowRef<NaiveUI.TableOperateType>('add');
|
||||
|
||||
function handleAdd() {
|
||||
operateType.value = 'add';
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
/** the editing row data */
|
||||
const editingData = shallowRef<ApiData | null>(null);
|
||||
|
||||
function handleEdit(id: ApiData[keyof ApiData]) {
|
||||
operateType.value = 'edit';
|
||||
const findItem = data.value.find(item => item[idKey] === id) || null;
|
||||
editingData.value = jsonClone(findItem);
|
||||
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
/** the checked row keys of table */
|
||||
const checkedRowKeys = shallowRef<string[]>([]);
|
||||
|
||||
/** the hook after the batch delete operation is completed */
|
||||
async function onBatchDeleted() {
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
|
||||
checkedRowKeys.value = [];
|
||||
|
||||
await getData();
|
||||
}
|
||||
|
||||
/** the hook after the delete operation is completed */
|
||||
async function onDeleted() {
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
|
||||
await getData();
|
||||
}
|
||||
|
||||
return {
|
||||
drawerVisible,
|
||||
openDrawer,
|
||||
closeDrawer,
|
||||
operateType,
|
||||
handleAdd,
|
||||
editingData,
|
||||
handleEdit,
|
||||
checkedRowKeys,
|
||||
onBatchDeleted,
|
||||
onDeleted
|
||||
};
|
||||
}
|
||||
|
||||
type TreeTableOptions<ApiData> = {
|
||||
/** id field name */
|
||||
idField?: keyof ApiData;
|
||||
/** parent id field name */
|
||||
parentIdField?: keyof ApiData;
|
||||
/** children field name */
|
||||
childrenField?: keyof ApiData;
|
||||
/** filter function */
|
||||
filterFn?: (node: ApiData) => boolean;
|
||||
};
|
||||
|
||||
export function treeTransform<ApiData>(
|
||||
response: FlatResponseData<any, ApiData[]>,
|
||||
options: TreeTableOptions<ApiData>
|
||||
): ApiData[] {
|
||||
const { data, error } = response;
|
||||
|
||||
if (error || !data.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { idField = 'id', parentIdField = 'parentId', childrenField = 'children', filterFn = () => true } = options;
|
||||
|
||||
// 使用 Map 替代普通对象,提高性能
|
||||
const childrenMap = new Map<ApiData[keyof ApiData], ApiData[]>();
|
||||
const nodeMap = new Map<ApiData[keyof ApiData], ApiData>();
|
||||
const tree: ApiData[] = [];
|
||||
|
||||
// 第一遍遍历:构建节点映射
|
||||
for (const item of data) {
|
||||
const id = item[idField as keyof ApiData];
|
||||
const parentId = item[parentIdField as keyof ApiData];
|
||||
|
||||
nodeMap.set(id, item);
|
||||
|
||||
if (!childrenMap.has(parentId)) {
|
||||
childrenMap.set(parentId, []);
|
||||
}
|
||||
// 应用过滤函数
|
||||
if (filterFn(item)) {
|
||||
childrenMap.get(parentId)!.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// 第二遍遍历:找出根节点
|
||||
for (const item of data) {
|
||||
const parentId = item[parentIdField as keyof ApiData];
|
||||
if (!nodeMap.has(parentId) && filterFn(item)) {
|
||||
tree.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// 递归构建树形结构
|
||||
const buildTree = (node: ApiData) => {
|
||||
const id = node[idField as keyof ApiData];
|
||||
const children = childrenMap.get(id);
|
||||
|
||||
if (children?.length) {
|
||||
// 使用类型断言确保类型安全
|
||||
(node as any)[childrenField] = children;
|
||||
for (const child of children) {
|
||||
buildTree(child);
|
||||
}
|
||||
} else {
|
||||
// 如果没有子节点,设置为 undefined
|
||||
(node as any)[childrenField] = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// 从根节点开始构建树
|
||||
for (const root of tree) {
|
||||
buildTree(root);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
|
||||
cols: Column[],
|
||||
getColumnVisible?: (column: Column) => boolean
|
||||
) {
|
||||
const checks: TableColumnCheck[] = [];
|
||||
|
||||
cols.forEach(column => {
|
||||
if (isTableColumnHasKey(column)) {
|
||||
checks.push({
|
||||
key: column.key as string,
|
||||
title: column.title!,
|
||||
checked: true,
|
||||
visible: getColumnVisible?.(column) ?? true
|
||||
});
|
||||
} else if (column.type === 'selection') {
|
||||
checks.push({
|
||||
key: SELECTION_KEY,
|
||||
title: $t('common.check'),
|
||||
checked: true,
|
||||
visible: getColumnVisible?.(column) ?? false
|
||||
});
|
||||
} else if (column.type === 'expand') {
|
||||
checks.push({
|
||||
key: EXPAND_KEY,
|
||||
title: $t('common.expandColumn'),
|
||||
checked: true,
|
||||
visible: getColumnVisible?.(column) ?? false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
}
|
||||
|
||||
function getColumns<Column extends NaiveUI.TableColumn<any>>(cols: Column[], checks: TableColumnCheck[]) {
|
||||
const columnMap = new Map<string, Column>();
|
||||
|
||||
cols.forEach(column => {
|
||||
if (isTableColumnHasKey(column)) {
|
||||
columnMap.set(column.key as string, column);
|
||||
} else if (column.type === 'selection') {
|
||||
columnMap.set(SELECTION_KEY, column);
|
||||
} else if (column.type === 'expand') {
|
||||
columnMap.set(EXPAND_KEY, column);
|
||||
}
|
||||
});
|
||||
|
||||
const filteredColumns = checks.filter(item => item.checked).map(check => columnMap.get(check.key) as Column);
|
||||
|
||||
return filteredColumns;
|
||||
}
|
||||
|
||||
export function isTableColumnHasKey<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
|
||||
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
|
||||
}
|
||||
|
@ -1,237 +0,0 @@
|
||||
import { effectScope, onScopeDispose, ref, watch } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { jsonClone } from '@sa/utils';
|
||||
import { useBoolean, useHookTable } from '@sa/hooks';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { handleTree } from '@/utils/common';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
type TableData = NaiveUI.TableData;
|
||||
type GetTableData<A extends NaiveUI.TreeTableApiFn> = NaiveUI.GetTreeTableData<A>;
|
||||
type TableColumn<T> = NaiveUI.TableColumn<T>;
|
||||
|
||||
export function useTreeTable<A extends NaiveUI.TreeTableApiFn>(
|
||||
config: NaiveUI.NaiveTreeTableConfig<A> & CommonType.TreeConfig & { defaultExpandAll?: boolean }
|
||||
) {
|
||||
const scope = effectScope();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const {
|
||||
apiFn,
|
||||
apiParams,
|
||||
immediate,
|
||||
idField,
|
||||
parentIdField = 'parentId',
|
||||
childrenField = 'children',
|
||||
defaultExpandAll = false
|
||||
} = config;
|
||||
|
||||
const SELECTION_KEY = '__selection__';
|
||||
const EXPAND_KEY = '__expand__';
|
||||
|
||||
const expandedRowKeys = ref<CommonType.IdType[]>([]);
|
||||
|
||||
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 = res.data || [];
|
||||
if (!records.length) return { data: [] };
|
||||
|
||||
const treeData = handleTree(records, {
|
||||
idField,
|
||||
parentIdField,
|
||||
childrenField
|
||||
});
|
||||
|
||||
// if defaultExpandAll is true, expand all nodes
|
||||
expandedRowKeys.value = defaultExpandAll
|
||||
? records.map(item => item[idField])
|
||||
: records.filter(item => item[parentIdField] === 0).map(item => item[idField]) || [];
|
||||
|
||||
return { data: treeData };
|
||||
},
|
||||
getColumnChecks: cols => {
|
||||
const checks: NaiveUI.TableColumnCheck[] = [];
|
||||
|
||||
cols.forEach(column => {
|
||||
if (isTableColumnHasKey(column)) {
|
||||
checks.push({
|
||||
key: column.key as string,
|
||||
title: column.title as string,
|
||||
checked: true
|
||||
});
|
||||
} else if (column.type === 'selection') {
|
||||
checks.push({
|
||||
key: SELECTION_KEY,
|
||||
title: $t('common.check'),
|
||||
checked: true
|
||||
});
|
||||
} else if (column.type === 'expand') {
|
||||
checks.push({
|
||||
key: EXPAND_KEY,
|
||||
title: $t('common.expandColumn'),
|
||||
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);
|
||||
} else if (column.type === 'expand') {
|
||||
columnMap.set(EXPAND_KEY, column);
|
||||
}
|
||||
});
|
||||
|
||||
const filteredColumns = checks
|
||||
.filter(item => item.checked)
|
||||
.map(check => columnMap.get(check.key) as TableColumn<GetTableData<A>>);
|
||||
|
||||
return filteredColumns;
|
||||
},
|
||||
immediate
|
||||
});
|
||||
|
||||
/** 收集所有节点的key */
|
||||
function collectAllNodeKeys(treeNodes: any[]): CommonType.IdType[] {
|
||||
const keys: CommonType.IdType[] = [];
|
||||
|
||||
const collect = (nodes: any[]) => {
|
||||
nodes.forEach(node => {
|
||||
keys.push(node[idField]);
|
||||
if (node[childrenField]?.length) {
|
||||
collect(node[childrenField]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
collect(treeNodes);
|
||||
return keys;
|
||||
}
|
||||
|
||||
const { bool: isCollapse, toggle: toggleCollapse } = useBoolean(defaultExpandAll);
|
||||
|
||||
/** expand all nodes */
|
||||
function expandAll() {
|
||||
toggleCollapse();
|
||||
expandedRowKeys.value = collectAllNodeKeys(data.value);
|
||||
}
|
||||
|
||||
/** collapse all nodes */
|
||||
function collapseAll() {
|
||||
toggleCollapse();
|
||||
expandedRowKeys.value = [];
|
||||
}
|
||||
|
||||
scope.run(() => {
|
||||
watch(
|
||||
() => appStore.locale,
|
||||
() => {
|
||||
reloadColumns();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
onScopeDispose(() => {
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
return {
|
||||
loading,
|
||||
empty,
|
||||
data,
|
||||
columns,
|
||||
columnChecks,
|
||||
reloadColumns,
|
||||
getData,
|
||||
searchParams,
|
||||
updateSearchParams,
|
||||
resetSearchParams,
|
||||
expandedRowKeys,
|
||||
isCollapse,
|
||||
expandAll,
|
||||
collapseAll
|
||||
};
|
||||
}
|
||||
|
||||
export function useTreeTableOperate<T extends TableData = TableData>(_: Ref<T[]>, getData: () => Promise<void>) {
|
||||
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
|
||||
|
||||
const operateType = ref<NaiveUI.TableOperateType>('add');
|
||||
|
||||
function handleAdd() {
|
||||
operateType.value = 'add';
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
/** the editing row data */
|
||||
const editingData: Ref<T | null> = ref(null);
|
||||
|
||||
function handleEdit(row: T) {
|
||||
operateType.value = 'edit';
|
||||
editingData.value = jsonClone(row);
|
||||
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
/** the checked row keys of table */
|
||||
const checkedRowKeys = ref<CommonType.IdType[]>([]);
|
||||
|
||||
function clearCheckedRowKeys() {
|
||||
checkedRowKeys.value = [];
|
||||
}
|
||||
|
||||
/** the hook after the batch delete operation is completed */
|
||||
async function onBatchDeleted() {
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
|
||||
checkedRowKeys.value = [];
|
||||
|
||||
await getData();
|
||||
}
|
||||
|
||||
/** the hook after the delete operation is completed */
|
||||
async function onDeleted() {
|
||||
window.$message?.success($t('common.deleteSuccess'));
|
||||
|
||||
await getData();
|
||||
}
|
||||
|
||||
return {
|
||||
drawerVisible,
|
||||
openDrawer,
|
||||
closeDrawer,
|
||||
operateType,
|
||||
handleAdd,
|
||||
editingData,
|
||||
handleEdit,
|
||||
checkedRowKeys,
|
||||
onBatchDeleted,
|
||||
onDeleted,
|
||||
clearCheckedRowKeys
|
||||
};
|
||||
}
|
||||
|
||||
function isTableColumnHasKey<T>(column: TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
|
||||
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
|
||||
}
|
@ -12,7 +12,7 @@ import GlobalTab from '../modules/global-tab/index.vue';
|
||||
import GlobalContent from '../modules/global-content/index.vue';
|
||||
import GlobalFooter from '../modules/global-footer/index.vue';
|
||||
import ThemeDrawer from '../modules/theme-drawer/index.vue';
|
||||
import { setupMixMenuContext } from '../context';
|
||||
import { provideMixMenuContext } from '../modules/global-menu/context';
|
||||
|
||||
defineOptions({
|
||||
name: 'BaseLayout'
|
||||
@ -20,7 +20,7 @@ defineOptions({
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = setupMixMenuContext();
|
||||
const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = provideMixMenuContext();
|
||||
|
||||
const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue'));
|
||||
|
||||
@ -31,7 +31,7 @@ const layoutMode = computed(() => {
|
||||
});
|
||||
|
||||
const headerProps = computed(() => {
|
||||
const { mode, reverseHorizontalMix } = themeStore.layout;
|
||||
const { mode } = themeStore.layout;
|
||||
|
||||
const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
|
||||
vertical: {
|
||||
@ -44,15 +44,25 @@ const headerProps = computed(() => {
|
||||
showMenu: false,
|
||||
showMenuToggler: false
|
||||
},
|
||||
'vertical-hybrid-header-first': {
|
||||
showLogo: !isActiveFirstLevelMenuHasChildren.value,
|
||||
showMenu: true,
|
||||
showMenuToggler: false
|
||||
},
|
||||
horizontal: {
|
||||
showLogo: true,
|
||||
showMenu: true,
|
||||
showMenuToggler: false
|
||||
},
|
||||
'horizontal-mix': {
|
||||
'top-hybrid-sidebar-first': {
|
||||
showLogo: true,
|
||||
showMenu: true,
|
||||
showMenuToggler: reverseHorizontalMix && isActiveFirstLevelMenuHasChildren.value
|
||||
showMenuToggler: false
|
||||
},
|
||||
'top-hybrid-header-first': {
|
||||
showLogo: true,
|
||||
showMenu: true,
|
||||
showMenuToggler: isActiveFirstLevelMenuHasChildren.value
|
||||
}
|
||||
};
|
||||
|
||||
@ -63,44 +73,56 @@ const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
|
||||
|
||||
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
|
||||
|
||||
const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix');
|
||||
const isVerticalHybridHeaderFirst = computed(() => themeStore.layout.mode === 'vertical-hybrid-header-first');
|
||||
|
||||
const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first');
|
||||
|
||||
const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
|
||||
|
||||
const siderWidth = computed(() => getSiderWidth());
|
||||
|
||||
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
|
||||
|
||||
function getSiderWidth() {
|
||||
const { reverseHorizontalMix } = themeStore.layout;
|
||||
const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
|
||||
function getSiderAndCollapsedWidth(isCollapsed: boolean) {
|
||||
const {
|
||||
mixChildMenuWidth,
|
||||
collapsedWidth,
|
||||
width: themeWidth,
|
||||
mixCollapsedWidth,
|
||||
mixWidth: themeMixWidth
|
||||
} = themeStore.sider;
|
||||
|
||||
if (isHorizontalMix.value && reverseHorizontalMix) {
|
||||
const width = isCollapsed ? collapsedWidth : themeWidth;
|
||||
const mixWidth = isCollapsed ? mixCollapsedWidth : themeMixWidth;
|
||||
|
||||
if (isTopHybridHeaderFirst.value) {
|
||||
return isActiveFirstLevelMenuHasChildren.value ? width : 0;
|
||||
}
|
||||
|
||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width;
|
||||
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||
w += mixChildMenuWidth;
|
||||
if (isVerticalHybridHeaderFirst.value && !isActiveFirstLevelMenuHasChildren.value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return w;
|
||||
const isMixMode = isVerticalMix.value || isTopHybridSidebarFirst.value || isVerticalHybridHeaderFirst.value;
|
||||
let finalWidth = isMixMode ? mixWidth : width;
|
||||
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||
finalWidth += mixChildMenuWidth;
|
||||
}
|
||||
|
||||
if (isVerticalHybridHeaderFirst.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||
finalWidth += mixChildMenuWidth;
|
||||
}
|
||||
|
||||
return finalWidth;
|
||||
}
|
||||
|
||||
function getSiderWidth() {
|
||||
return getSiderAndCollapsedWidth(false);
|
||||
}
|
||||
|
||||
function getSiderCollapsedWidth() {
|
||||
const { reverseHorizontalMix } = themeStore.layout;
|
||||
const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider;
|
||||
|
||||
if (isHorizontalMix.value && reverseHorizontalMix) {
|
||||
return isActiveFirstLevelMenuHasChildren.value ? collapsedWidth : 0;
|
||||
}
|
||||
|
||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth;
|
||||
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||
w += mixChildMenuWidth;
|
||||
}
|
||||
|
||||
return w;
|
||||
return getSiderAndCollapsedWidth(true);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -1,83 +0,0 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useContext } from '@sa/hooks';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
|
||||
export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu);
|
||||
|
||||
function useMixMenu() {
|
||||
const route = useRoute();
|
||||
const routeStore = useRouteStore();
|
||||
const { selectedKey } = useMenu();
|
||||
|
||||
const activeFirstLevelMenuKey = ref('');
|
||||
|
||||
function setActiveFirstLevelMenuKey(key: string) {
|
||||
activeFirstLevelMenuKey.value = key;
|
||||
}
|
||||
|
||||
function getActiveFirstLevelMenuKey() {
|
||||
const [firstLevelRouteName] = selectedKey.value.split('_');
|
||||
|
||||
setActiveFirstLevelMenuKey(firstLevelRouteName);
|
||||
}
|
||||
|
||||
const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
|
||||
|
||||
const firstLevelMenus = computed<App.Global.Menu[]>(() =>
|
||||
routeStore.menus.map(menu => {
|
||||
const { children: _, ...rest } = menu;
|
||||
|
||||
return rest;
|
||||
})
|
||||
);
|
||||
|
||||
const childLevelMenus = computed<App.Global.Menu[]>(
|
||||
() => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
|
||||
);
|
||||
|
||||
const isActiveFirstLevelMenuHasChildren = computed(() => {
|
||||
if (!activeFirstLevelMenuKey.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const findItem = allMenus.value.find(item => item.key === activeFirstLevelMenuKey.value);
|
||||
|
||||
return Boolean(findItem?.children?.length);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
() => {
|
||||
getActiveFirstLevelMenuKey();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return {
|
||||
allMenus,
|
||||
firstLevelMenus,
|
||||
childLevelMenus,
|
||||
isActiveFirstLevelMenuHasChildren,
|
||||
activeFirstLevelMenuKey,
|
||||
setActiveFirstLevelMenuKey,
|
||||
getActiveFirstLevelMenuKey
|
||||
};
|
||||
}
|
||||
|
||||
export function useMenu() {
|
||||
const route = useRoute();
|
||||
|
||||
const selectedKey = computed(() => {
|
||||
const { hideInMenu, activeMenu } = route.meta;
|
||||
const name = route.name as string;
|
||||
|
||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||
|
||||
return routeName;
|
||||
});
|
||||
|
||||
return {
|
||||
selectedKey
|
||||
};
|
||||
}
|
@ -3,6 +3,7 @@ import { computed } from 'vue';
|
||||
import { createReusableTemplate } from '@vueuse/core';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { transformColorWithOpacity } from '@sa/color';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
|
||||
defineOptions({
|
||||
name: 'FirstLevelMenu'
|
||||
@ -20,7 +21,7 @@ interface Props {
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'select', menu: App.Global.Menu): boolean;
|
||||
(e: 'select', menuKey: RouteKey): boolean;
|
||||
(e: 'toggleSiderCollapse'): void;
|
||||
}
|
||||
|
||||
@ -47,8 +48,8 @@ const selectedBgColor = computed(() => {
|
||||
return darkMode ? dark : light;
|
||||
});
|
||||
|
||||
function handleClickMixMenu(menu: App.Global.Menu) {
|
||||
emit('select', menu);
|
||||
function handleClickMixMenu(menuKey: RouteKey) {
|
||||
emit('select', menuKey);
|
||||
}
|
||||
|
||||
function toggleSiderCollapse() {
|
||||
@ -88,7 +89,7 @@ function toggleSiderCollapse() {
|
||||
:icon="menu.icon"
|
||||
:active="menu.key === activeMenuKey"
|
||||
:is-mini="siderCollapse"
|
||||
@click="handleClickMixMenu(menu)"
|
||||
@click="handleClickMixMenu(menu.routeKey)"
|
||||
/>
|
||||
</SimpleScrollbar>
|
||||
<MenuToggler
|
||||
|
143
src/layouts/modules/global-menu/context/index.ts
Normal file
143
src/layouts/modules/global-menu/context/index.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useContext } from '@sa/hooks';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
|
||||
export const [provideMixMenuContext, useMixMenuContext] = useContext('MixMenu', useMixMenu);
|
||||
|
||||
function useMixMenu() {
|
||||
const route = useRoute();
|
||||
const routeStore = useRouteStore();
|
||||
const { selectedKey } = useMenu();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
|
||||
const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
|
||||
|
||||
const firstLevelMenus = computed<App.Global.Menu[]>(() =>
|
||||
routeStore.menus.map(menu => {
|
||||
const { children: _, ...rest } = menu;
|
||||
|
||||
return rest;
|
||||
})
|
||||
);
|
||||
|
||||
const activeFirstLevelMenuKey = ref('');
|
||||
|
||||
function setActiveFirstLevelMenuKey(key: string) {
|
||||
activeFirstLevelMenuKey.value = key;
|
||||
}
|
||||
|
||||
function getActiveFirstLevelMenuKey() {
|
||||
const [firstLevelRouteName] = selectedKey.value.split('_');
|
||||
|
||||
setActiveFirstLevelMenuKey(firstLevelRouteName);
|
||||
}
|
||||
|
||||
const isActiveFirstLevelMenuHasChildren = computed(() => {
|
||||
if (!activeFirstLevelMenuKey.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const findItem = allMenus.value.find(item => item.key === activeFirstLevelMenuKey.value);
|
||||
|
||||
return Boolean(findItem?.children?.length);
|
||||
});
|
||||
|
||||
function handleSelectFirstLevelMenu(key: RouteKey) {
|
||||
setActiveFirstLevelMenuKey(key);
|
||||
|
||||
if (!isActiveFirstLevelMenuHasChildren.value) {
|
||||
routerPushByKeyWithMetaQuery(key);
|
||||
}
|
||||
}
|
||||
|
||||
const secondLevelMenus = computed<App.Global.Menu[]>(
|
||||
() => allMenus.value.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
|
||||
);
|
||||
|
||||
const activeSecondLevelMenuKey = ref('');
|
||||
|
||||
function setActiveSecondLevelMenuKey(key: string) {
|
||||
activeSecondLevelMenuKey.value = key;
|
||||
}
|
||||
|
||||
function getActiveSecondLevelMenuKey() {
|
||||
const keys = selectedKey.value.split('_');
|
||||
|
||||
if (keys.length < 2) {
|
||||
setActiveSecondLevelMenuKey('');
|
||||
return;
|
||||
}
|
||||
|
||||
const [firstLevelRouteName, level2SuffixName] = keys;
|
||||
|
||||
const secondLevelRouteName = `${firstLevelRouteName}_${level2SuffixName}`;
|
||||
|
||||
setActiveSecondLevelMenuKey(secondLevelRouteName);
|
||||
}
|
||||
|
||||
const isActiveSecondLevelMenuHasChildren = computed(() => {
|
||||
if (!activeSecondLevelMenuKey.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const findItem = secondLevelMenus.value.find(item => item.key === activeSecondLevelMenuKey.value);
|
||||
|
||||
return Boolean(findItem?.children?.length);
|
||||
});
|
||||
|
||||
function handleSelectSecondLevelMenu(key: RouteKey) {
|
||||
setActiveSecondLevelMenuKey(key);
|
||||
|
||||
if (!isActiveSecondLevelMenuHasChildren.value) {
|
||||
routerPushByKeyWithMetaQuery(key);
|
||||
}
|
||||
}
|
||||
|
||||
const childLevelMenus = computed<App.Global.Menu[]>(
|
||||
() => secondLevelMenus.value.find(menu => menu.key === activeSecondLevelMenuKey.value)?.children || []
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
() => {
|
||||
getActiveFirstLevelMenuKey();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return {
|
||||
firstLevelMenus,
|
||||
activeFirstLevelMenuKey,
|
||||
setActiveFirstLevelMenuKey,
|
||||
isActiveFirstLevelMenuHasChildren,
|
||||
handleSelectFirstLevelMenu,
|
||||
getActiveFirstLevelMenuKey,
|
||||
secondLevelMenus,
|
||||
activeSecondLevelMenuKey,
|
||||
setActiveSecondLevelMenuKey,
|
||||
isActiveSecondLevelMenuHasChildren,
|
||||
handleSelectSecondLevelMenu,
|
||||
getActiveSecondLevelMenuKey,
|
||||
childLevelMenus
|
||||
};
|
||||
}
|
||||
|
||||
export function useMenu() {
|
||||
const route = useRoute();
|
||||
|
||||
const selectedKey = computed(() => {
|
||||
const { hideInMenu, activeMenu } = route.meta;
|
||||
const name = route.name as string;
|
||||
|
||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||
|
||||
return routeName;
|
||||
});
|
||||
|
||||
return {
|
||||
selectedKey
|
||||
};
|
||||
}
|
@ -5,9 +5,10 @@ import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import VerticalMenu from './modules/vertical-menu.vue';
|
||||
import VerticalMixMenu from './modules/vertical-mix-menu.vue';
|
||||
import VerticalHybridHeaderFirst from './modules/vertical-hybrid-header-first.vue';
|
||||
import HorizontalMenu from './modules/horizontal-menu.vue';
|
||||
import HorizontalMixMenu from './modules/horizontal-mix-menu.vue';
|
||||
import ReversedHorizontalMixMenu from './modules/reversed-horizontal-mix-menu.vue';
|
||||
import TopHybridSidebarFirst from './modules/top-hybrid-sidebar-first.vue';
|
||||
import TopHybridHeaderFirst from './modules/top-hybrid-header-first.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'GlobalMenu'
|
||||
@ -20,8 +21,10 @@ const activeMenu = computed(() => {
|
||||
const menuMap: Record<UnionKey.ThemeLayoutMode, Component> = {
|
||||
vertical: VerticalMenu,
|
||||
'vertical-mix': VerticalMixMenu,
|
||||
'vertical-hybrid-header-first': VerticalHybridHeaderFirst,
|
||||
horizontal: HorizontalMenu,
|
||||
'horizontal-mix': themeStore.layout.reverseHorizontalMix ? ReversedHorizontalMixMenu : HorizontalMixMenu
|
||||
'top-hybrid-sidebar-first': TopHybridSidebarFirst,
|
||||
'top-hybrid-header-first': TopHybridHeaderFirst
|
||||
};
|
||||
|
||||
return menuMap[themeStore.layout.mode];
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { useMenu } from '../../../context';
|
||||
import { useMenu } from '../context';
|
||||
|
||||
defineOptions({
|
||||
name: 'HorizontalMenu'
|
||||
|
@ -1,17 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { useMenu, useMixMenuContext } from '../../../context';
|
||||
import { useMenu, useMixMenuContext } from '../context';
|
||||
|
||||
defineOptions({
|
||||
name: 'ReversedHorizontalMixMenu'
|
||||
name: 'TopHybridHeaderFirst'
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
@ -19,23 +18,10 @@ const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
const {
|
||||
firstLevelMenus,
|
||||
childLevelMenus,
|
||||
activeFirstLevelMenuKey,
|
||||
setActiveFirstLevelMenuKey,
|
||||
isActiveFirstLevelMenuHasChildren
|
||||
} = useMixMenuContext();
|
||||
const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } =
|
||||
useMixMenuContext('TopHybridHeaderFirst');
|
||||
const { selectedKey } = useMenu();
|
||||
|
||||
function handleSelectMixMenu(key: RouteKey) {
|
||||
setActiveFirstLevelMenuKey(key);
|
||||
|
||||
if (!isActiveFirstLevelMenuHasChildren.value) {
|
||||
routerPushByKeyWithMetaQuery(key);
|
||||
}
|
||||
}
|
||||
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
|
||||
function updateExpandedKeys() {
|
||||
@ -63,7 +49,7 @@ watch(
|
||||
:options="firstLevelMenus"
|
||||
:indent="18"
|
||||
responsive
|
||||
@update:value="handleSelectMixMenu"
|
||||
@update:value="handleSelectFirstLevelMenu"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||
@ -75,7 +61,7 @@ watch(
|
||||
:collapsed="appStore.siderCollapse"
|
||||
:collapsed-width="themeStore.sider.collapsedWidth"
|
||||
:collapsed-icon-size="22"
|
||||
:options="childLevelMenus"
|
||||
:options="secondLevelMenus"
|
||||
:indent="18"
|
||||
@update:value="routerPushByKeyWithMetaQuery"
|
||||
/>
|
@ -4,25 +4,18 @@ import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import FirstLevelMenu from '../components/first-level-menu.vue';
|
||||
import { useMenu, useMixMenuContext } from '../../../context';
|
||||
import { useMenu, useMixMenuContext } from '../context';
|
||||
|
||||
defineOptions({
|
||||
name: 'HorizontalMixMenu'
|
||||
name: 'TopHybridSidebarFirst'
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
|
||||
const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } =
|
||||
useMixMenuContext('TopHybridSidebarFirst');
|
||||
const { selectedKey } = useMenu();
|
||||
|
||||
function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||
setActiveFirstLevelMenuKey(menu.key);
|
||||
|
||||
if (!menu.children?.length) {
|
||||
routerPushByKeyWithMetaQuery(menu.routeKey);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -30,22 +23,24 @@ function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||
<NMenu
|
||||
mode="horizontal"
|
||||
:value="selectedKey"
|
||||
:options="childLevelMenus"
|
||||
:options="secondLevelMenus"
|
||||
:indent="18"
|
||||
responsive
|
||||
@update:value="routerPushByKeyWithMetaQuery"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||
<FirstLevelMenu
|
||||
:menus="allMenus"
|
||||
:active-menu-key="activeFirstLevelMenuKey"
|
||||
:sider-collapse="appStore.siderCollapse"
|
||||
:dark-mode="themeStore.darkMode"
|
||||
:theme-color="themeStore.themeColor"
|
||||
@select="handleSelectMixMenu"
|
||||
@toggle-sider-collapse="appStore.toggleSiderCollapse"
|
||||
/>
|
||||
<div class="h-full pt-2">
|
||||
<FirstLevelMenu
|
||||
:menus="firstLevelMenus"
|
||||
:active-menu-key="activeFirstLevelMenuKey"
|
||||
:sider-collapse="appStore.siderCollapse"
|
||||
:dark-mode="themeStore.darkMode"
|
||||
:theme-color="themeStore.themeColor"
|
||||
@select="handleSelectFirstLevelMenu"
|
||||
@toggle-sider-collapse="appStore.toggleSiderCollapse"
|
||||
/>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
@ -0,0 +1,149 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { useMenu, useMixMenuContext } from '../context';
|
||||
import FirstLevelMenu from '../components/first-level-menu.vue';
|
||||
import GlobalLogo from '../../global-logo/index.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'VerticalHybridHeaderFirst'
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
|
||||
const {
|
||||
firstLevelMenus,
|
||||
activeFirstLevelMenuKey,
|
||||
handleSelectFirstLevelMenu,
|
||||
getActiveFirstLevelMenuKey,
|
||||
secondLevelMenus,
|
||||
activeSecondLevelMenuKey,
|
||||
isActiveSecondLevelMenuHasChildren,
|
||||
handleSelectSecondLevelMenu,
|
||||
getActiveSecondLevelMenuKey,
|
||||
childLevelMenus
|
||||
} = useMixMenuContext('VerticalHybridHeaderFirst');
|
||||
const { selectedKey } = useMenu();
|
||||
|
||||
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||
|
||||
const hasChildMenus = computed(() => childLevelMenus.value.length > 0);
|
||||
|
||||
const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
|
||||
|
||||
function handleSelectMixMenu(key: RouteKey) {
|
||||
handleSelectSecondLevelMenu(key);
|
||||
|
||||
if (isActiveSecondLevelMenuHasChildren.value) {
|
||||
setDrawerVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectMenu(key: RouteKey) {
|
||||
handleSelectFirstLevelMenu(key);
|
||||
|
||||
if (secondLevelMenus.value.length > 0) {
|
||||
handleSelectMixMenu(secondLevelMenus.value[0].routeKey);
|
||||
}
|
||||
}
|
||||
|
||||
function handleResetActiveMenu() {
|
||||
setDrawerVisible(false);
|
||||
|
||||
if (!appStore.mixSiderFixed) {
|
||||
getActiveFirstLevelMenuKey();
|
||||
getActiveSecondLevelMenuKey();
|
||||
}
|
||||
}
|
||||
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
|
||||
function updateExpandedKeys() {
|
||||
if (appStore.siderCollapse || !selectedKey.value) {
|
||||
expandedKeys.value = [];
|
||||
return;
|
||||
}
|
||||
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
() => {
|
||||
updateExpandedKeys();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
|
||||
<NMenu
|
||||
mode="horizontal"
|
||||
:value="activeFirstLevelMenuKey"
|
||||
:options="firstLevelMenus"
|
||||
:indent="18"
|
||||
responsive
|
||||
@update:value="handleSelectMenu"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||
<div class="h-full flex" @mouseleave="handleResetActiveMenu">
|
||||
<FirstLevelMenu
|
||||
:menus="secondLevelMenus"
|
||||
:active-menu-key="activeSecondLevelMenuKey"
|
||||
:inverted="inverted"
|
||||
:sider-collapse="appStore.siderCollapse"
|
||||
:dark-mode="themeStore.darkMode"
|
||||
:theme-color="themeStore.themeColor"
|
||||
@select="handleSelectMixMenu"
|
||||
@toggle-sider-collapse="appStore.toggleSiderCollapse"
|
||||
>
|
||||
<GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
|
||||
</FirstLevelMenu>
|
||||
<div
|
||||
class="relative h-full transition-width-300"
|
||||
:style="{ width: appStore.mixSiderFixed && hasChildMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||
>
|
||||
<DarkModeContainer
|
||||
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
|
||||
:inverted="inverted"
|
||||
:style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||
>
|
||||
<header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
|
||||
<h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
|
||||
<PinToggler
|
||||
:pin="appStore.mixSiderFixed"
|
||||
:class="{ 'text-white:88 !hover:text-white': inverted }"
|
||||
@click="appStore.toggleMixSiderFixed"
|
||||
/>
|
||||
</header>
|
||||
<SimpleScrollbar>
|
||||
<NMenu
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
mode="vertical"
|
||||
:value="selectedKey"
|
||||
:options="childLevelMenus"
|
||||
:inverted="inverted"
|
||||
:indent="18"
|
||||
@update:value="routerPushByKeyWithMetaQuery"
|
||||
/>
|
||||
</SimpleScrollbar>
|
||||
</DarkModeContainer>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -7,7 +7,7 @@ import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { useMenu } from '../../../context';
|
||||
import { useMenu } from '../context';
|
||||
|
||||
defineOptions({
|
||||
name: 'VerticalMenu'
|
||||
|
@ -3,13 +3,14 @@ import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { $t } from '@/locales';
|
||||
import { useMenu, useMixMenuContext } from '../../../context';
|
||||
import { useMenu, useMixMenuContext } from '../context';
|
||||
import FirstLevelMenu from '../components/first-level-menu.vue';
|
||||
import GlobalLogo from '../../global-logo/index.vue';
|
||||
|
||||
@ -24,28 +25,26 @@ const routeStore = useRouteStore();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
|
||||
const {
|
||||
allMenus,
|
||||
childLevelMenus,
|
||||
firstLevelMenus,
|
||||
secondLevelMenus,
|
||||
activeFirstLevelMenuKey,
|
||||
setActiveFirstLevelMenuKey,
|
||||
getActiveFirstLevelMenuKey
|
||||
//
|
||||
} = useMixMenuContext();
|
||||
isActiveFirstLevelMenuHasChildren,
|
||||
getActiveFirstLevelMenuKey,
|
||||
handleSelectFirstLevelMenu
|
||||
} = useMixMenuContext('VerticalMixMenu');
|
||||
const { selectedKey } = useMenu();
|
||||
|
||||
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||
|
||||
const hasChildMenus = computed(() => childLevelMenus.value.length > 0);
|
||||
const hasChildMenus = computed(() => secondLevelMenus.value.length > 0);
|
||||
|
||||
const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
|
||||
|
||||
function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||
setActiveFirstLevelMenuKey(menu.key);
|
||||
function handleSelectMenu(key: RouteKey) {
|
||||
handleSelectFirstLevelMenu(key);
|
||||
|
||||
if (menu.children?.length) {
|
||||
if (isActiveFirstLevelMenuHasChildren.value) {
|
||||
setDrawerVisible(true);
|
||||
} else {
|
||||
routerPushByKeyWithMetaQuery(menu.routeKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,13 +79,13 @@ watch(
|
||||
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||
<div class="h-full flex" @mouseleave="handleResetActiveMenu">
|
||||
<FirstLevelMenu
|
||||
:menus="allMenus"
|
||||
:menus="firstLevelMenus"
|
||||
:active-menu-key="activeFirstLevelMenuKey"
|
||||
:inverted="inverted"
|
||||
:sider-collapse="appStore.siderCollapse"
|
||||
:dark-mode="themeStore.darkMode"
|
||||
:theme-color="themeStore.themeColor"
|
||||
@select="handleSelectMixMenu"
|
||||
@select="handleSelectMenu"
|
||||
@toggle-sider-collapse="appStore.toggleSiderCollapse"
|
||||
>
|
||||
<GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
|
||||
@ -113,7 +112,7 @@ watch(
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
mode="vertical"
|
||||
:value="selectedKey"
|
||||
:options="childLevelMenus"
|
||||
:options="secondLevelMenus"
|
||||
:inverted="inverted"
|
||||
:indent="18"
|
||||
@update:value="routerPushByKeyWithMetaQuery"
|
||||
|
@ -12,10 +12,13 @@ defineOptions({
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
|
||||
const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix');
|
||||
const darkMenu = computed(() => !themeStore.darkMode && !isHorizontalMix.value && themeStore.sider.inverted);
|
||||
const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
|
||||
const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first');
|
||||
const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
|
||||
const darkMenu = computed(
|
||||
() =>
|
||||
!themeStore.darkMode && !isTopHybridSidebarFirst.value && !isTopHybridHeaderFirst.value && themeStore.sider.inverted
|
||||
);
|
||||
const showLogo = computed(() => themeStore.layout.mode === 'vertical');
|
||||
const menuWrapperClass = computed(() => (showLogo.value ? 'flex-1-hidden' : 'h-full'));
|
||||
</script>
|
||||
|
||||
|
@ -27,7 +27,6 @@ type LayoutConfig = Record<
|
||||
UnionKey.ThemeLayoutMode,
|
||||
{
|
||||
placement: PopoverPlacement;
|
||||
headerClass: string;
|
||||
menuClass: string;
|
||||
mainClass: string;
|
||||
}
|
||||
@ -36,25 +35,31 @@ type LayoutConfig = Record<
|
||||
const layoutConfig: LayoutConfig = {
|
||||
vertical: {
|
||||
placement: 'bottom',
|
||||
headerClass: '',
|
||||
menuClass: 'w-1/3 h-full',
|
||||
mainClass: 'w-2/3 h-3/4'
|
||||
},
|
||||
'vertical-mix': {
|
||||
placement: 'bottom',
|
||||
headerClass: '',
|
||||
menuClass: 'w-1/4 h-full',
|
||||
mainClass: 'w-2/3 h-3/4'
|
||||
},
|
||||
'vertical-hybrid-header-first': {
|
||||
placement: 'bottom',
|
||||
menuClass: 'w-1/4 h-full',
|
||||
mainClass: 'w-2/3 h-3/4'
|
||||
},
|
||||
horizontal: {
|
||||
placement: 'bottom',
|
||||
headerClass: '',
|
||||
menuClass: 'w-full h-1/4',
|
||||
mainClass: 'w-full h-3/4'
|
||||
},
|
||||
'horizontal-mix': {
|
||||
'top-hybrid-sidebar-first': {
|
||||
placement: 'bottom',
|
||||
menuClass: 'w-full h-1/4',
|
||||
mainClass: 'w-2/3 h-3/4'
|
||||
},
|
||||
'top-hybrid-header-first': {
|
||||
placement: 'bottom',
|
||||
headerClass: '',
|
||||
menuClass: 'w-full h-1/4',
|
||||
mainClass: 'w-2/3 h-3/4'
|
||||
}
|
||||
@ -68,25 +73,27 @@ function handleChangeMode(mode: UnionKey.ThemeLayoutMode) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-center flex-wrap gap-x-32px gap-y-16px">
|
||||
<div class="grid grid-cols-2 gap-x-16px gap-y-12px md:grid-cols-3">
|
||||
<div
|
||||
v-for="(item, key) in layoutConfig"
|
||||
:key="key"
|
||||
class="flex cursor-pointer border-2px rounded-6px hover:border-primary"
|
||||
:class="[mode === key ? 'border-primary' : 'border-transparent']"
|
||||
class="flex-col-center cursor-pointer"
|
||||
@click="handleChangeMode(key)"
|
||||
>
|
||||
<NTooltip :placement="item.placement">
|
||||
<IconTooltip :placement="item.placement">
|
||||
<template #trigger>
|
||||
<div
|
||||
class="h-64px w-96px gap-6px rd-4px p-6px shadow dark:shadow-coolGray-5"
|
||||
:class="[key.includes('vertical') ? 'flex' : 'flex-col']"
|
||||
class="h-64px w-96px gap-6px rd-4px p-6px shadow ring-2 ring-transparent transition-all hover:ring-primary"
|
||||
:class="{ '!ring-primary': mode === key }"
|
||||
>
|
||||
<slot :name="key"></slot>
|
||||
<div class="h-full w-full gap-1" :class="[key.includes('vertical') ? 'flex' : 'flex-col']">
|
||||
<slot :name="key"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
{{ $t(themeLayoutModeRecord[key]) }}
|
||||
</NTooltip>
|
||||
{{ $t(`theme.layout.layoutMode.${key}_detail`) }}
|
||||
</IconTooltip>
|
||||
<p class="mt-8px text-12px">{{ $t(themeLayoutModeRecord[key]) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -13,7 +13,7 @@ defineProps<Props>();
|
||||
|
||||
<template>
|
||||
<div class="w-full flex-y-center justify-between">
|
||||
<div>
|
||||
<div class="flex-y-center">
|
||||
<span class="pr-8px text-base-text">{{ label }}</span>
|
||||
<slot name="suffix"></slot>
|
||||
</div>
|
||||
|
@ -1,28 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { $t } from '@/locales';
|
||||
import DarkMode from './modules/dark-mode.vue';
|
||||
import LayoutMode from './modules/layout-mode.vue';
|
||||
import ThemeColor from './modules/theme-color.vue';
|
||||
import PageFun from './modules/page-fun.vue';
|
||||
import AppearanceSettings from './modules/appearance/index.vue';
|
||||
import LayoutSettings from './modules/layout/index.vue';
|
||||
import GeneralSettings from './modules/general/index.vue';
|
||||
import ConfigOperation from './modules/config-operation.vue';
|
||||
import TableProps from './modules/table-props.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'ThemeDrawer'
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const activeTab = ref('appearance');
|
||||
|
||||
const drawerWidth = computed(() => {
|
||||
const width = 400;
|
||||
|
||||
// On mobile devices, use 90% of viewport width with a maximum of 400px
|
||||
if (appStore.isMobile) {
|
||||
return `min(90vw, ${width}px)`;
|
||||
}
|
||||
|
||||
return width;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="appStore.themeDrawerVisible" display-directive="show" :width="360">
|
||||
<NDrawer v-model:show="appStore.themeDrawerVisible" display-directive="show" :width="drawerWidth">
|
||||
<NDrawerContent :title="$t('theme.themeDrawerTitle')" :native-scrollbar="false" closable>
|
||||
<DarkMode />
|
||||
<LayoutMode />
|
||||
<ThemeColor />
|
||||
<PageFun />
|
||||
<TableProps />
|
||||
<NTabs v-model:value="activeTab" type="segment" size="medium" class="mb-16px">
|
||||
<NTab name="appearance" :tab="$t('theme.tabs.appearance')"></NTab>
|
||||
<NTab name="layout" :tab="$t('theme.tabs.layout')"></NTab>
|
||||
<NTab name="general" :tab="$t('theme.tabs.general')"></NTab>
|
||||
</NTabs>
|
||||
|
||||
<div class="min-h-400px">
|
||||
<KeepAlive>
|
||||
<AppearanceSettings v-if="activeTab === 'appearance'" />
|
||||
<LayoutSettings v-else-if="activeTab === 'layout'" />
|
||||
<GeneralSettings v-else-if="activeTab === 'general'" />
|
||||
</KeepAlive>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<ConfigOperation />
|
||||
</template>
|
||||
@ -30,4 +50,14 @@ const appStore = useAppStore();
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
:deep(.n-tab) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.n-tab-pane) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import ThemeSchema from './modules/theme-schema.vue';
|
||||
import ThemeColor from './modules/theme-color.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'AppearanceSettings'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-16px">
|
||||
<ThemeSchema />
|
||||
<ThemeColor />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../components/setting-item.vue';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'ThemeColor'
|
||||
@ -34,33 +34,38 @@ const swatches: string[] = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.themeColor.title') }}</NDivider>
|
||||
<NDivider>{{ $t('theme.appearance.themeColor.title') }}</NDivider>
|
||||
<div class="flex-col-stretch gap-12px">
|
||||
<NTooltip placement="top-start">
|
||||
<template #trigger>
|
||||
<SettingItem key="recommend-color" :label="$t('theme.recommendColor')">
|
||||
<NSwitch v-model:value="themeStore.recommendColor" />
|
||||
</SettingItem>
|
||||
<SettingItem key="recommend-color" :label="$t('theme.appearance.recommendColor')">
|
||||
<template #suffix>
|
||||
<IconTooltip>
|
||||
<p>
|
||||
<span class="pr-12px">{{ $t('theme.appearance.recommendColorDesc') }}</span>
|
||||
<br />
|
||||
<NButton
|
||||
text
|
||||
tag="a"
|
||||
href="https://uicolors.app/create"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-gray"
|
||||
>
|
||||
https://uicolors.app/create
|
||||
</NButton>
|
||||
</p>
|
||||
</IconTooltip>
|
||||
</template>
|
||||
<p>
|
||||
<span class="pr-12px">{{ $t('theme.recommendColorDesc') }}</span>
|
||||
<br />
|
||||
<NButton
|
||||
text
|
||||
tag="a"
|
||||
href="https://uicolors.app/create"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-gray"
|
||||
>
|
||||
https://uicolors.app/create
|
||||
</NButton>
|
||||
</p>
|
||||
</NTooltip>
|
||||
<SettingItem v-for="(_, key) in themeStore.themeColors" :key="key" :label="$t(`theme.themeColor.${key}`)">
|
||||
<NSwitch v-model:value="themeStore.recommendColor" />
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
v-for="(_, key) in themeStore.themeColors"
|
||||
:key="key"
|
||||
:label="$t(`theme.appearance.themeColor.${key}`)"
|
||||
>
|
||||
<template v-if="key === 'info'" #suffix>
|
||||
<NCheckbox v-model:checked="themeStore.isInfoFollowPrimary">
|
||||
{{ $t('theme.themeColor.followPrimary') }}
|
||||
{{ $t('theme.appearance.themeColor.followPrimary') }}
|
||||
</NCheckbox>
|
||||
</template>
|
||||
<NColorPicker
|
@ -3,10 +3,10 @@ import { computed } from 'vue';
|
||||
import { themeSchemaRecord } from '@/constants/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../components/setting-item.vue';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'DarkMode'
|
||||
name: 'ThemeSchema'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
@ -33,7 +33,7 @@ const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layo
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.themeSchema.title') }}</NDivider>
|
||||
<NDivider>{{ $t('theme.appearance.themeSchema.title') }}</NDivider>
|
||||
<div class="flex-col-stretch gap-16px">
|
||||
<div class="i-flex-center">
|
||||
<NTabs
|
||||
@ -50,14 +50,14 @@ const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layo
|
||||
</NTabs>
|
||||
</div>
|
||||
<Transition name="sider-inverted">
|
||||
<SettingItem v-if="showSiderInverted" :label="$t('theme.sider.inverted')">
|
||||
<SettingItem v-if="showSiderInverted" :label="$t('theme.layout.sider.inverted')">
|
||||
<NSwitch v-model:value="themeStore.sider.inverted" />
|
||||
</SettingItem>
|
||||
</Transition>
|
||||
<SettingItem :label="$t('theme.grayscale')">
|
||||
<SettingItem :label="$t('theme.appearance.grayscale')">
|
||||
<NSwitch :value="themeStore.grayscale" @update:value="handleGrayscaleChange" />
|
||||
</SettingItem>
|
||||
<SettingItem :label="$t('theme.colourWeakness')">
|
||||
<SettingItem :label="$t('theme.appearance.colourWeakness')">
|
||||
<NSwitch :value="themeStore.colourWeakness" @update:value="handleColourWeaknessChange" />
|
||||
</SettingItem>
|
||||
</div>
|
17
src/layouts/modules/theme-drawer/modules/general/index.vue
Normal file
17
src/layouts/modules/theme-drawer/modules/general/index.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import GlobalSettings from './modules/global-settings.vue';
|
||||
import WatermarkSettings from './modules/watermark-settings.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'GeneralSettings'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-16px">
|
||||
<GlobalSettings />
|
||||
<WatermarkSettings />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'GlobalSettings'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.general.title') }}</NDivider>
|
||||
<SettingItem :label="$t('theme.general.multilingual.visible')">
|
||||
<NSwitch v-model:value="themeStore.header.multilingual.visible" />
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem :label="$t('theme.general.globalSearch.visible')">
|
||||
<NSwitch v-model:value="themeStore.header.globalSearch.visible" />
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-list-move,
|
||||
.setting-list-enter-active,
|
||||
.setting-list-leave-active {
|
||||
--uno: transition-all-300;
|
||||
}
|
||||
|
||||
.setting-list-enter-from,
|
||||
.setting-list-leave-to {
|
||||
--uno: opacity-0 -translate-x-30px;
|
||||
}
|
||||
|
||||
.setting-list-leave-active {
|
||||
--uno: absolute;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { watermarkTimeFormatOptions } from '@/constants/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'WatermarkSettings'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.general.watermark.title') }}</NDivider>
|
||||
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||
<SettingItem key="1" :label="$t('theme.general.watermark.visible')">
|
||||
<NSwitch v-model:value="themeStore.watermark.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.watermark.visible" key="2" :label="$t('theme.general.watermark.enableUserName')">
|
||||
<NSwitch :value="themeStore.watermark.enableUserName" @update:value="themeStore.setWatermarkEnableUserName" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.watermark.visible" key="3" :label="$t('theme.general.watermark.enableTime')">
|
||||
<NSwitch :value="themeStore.watermark.enableTime" @update:value="themeStore.setWatermarkEnableTime" />
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
v-if="themeStore.watermark.visible && themeStore.watermark.enableTime"
|
||||
key="4"
|
||||
:label="$t('theme.general.watermark.timeFormat')"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="themeStore.watermark.timeFormat"
|
||||
:options="watermarkTimeFormatOptions"
|
||||
size="small"
|
||||
class="w-210px"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem key="5" :label="$t('theme.general.watermark.text')">
|
||||
<NInput
|
||||
v-model:value="themeStore.watermark.text"
|
||||
autosize
|
||||
type="text"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
placeholder="SoybeanAdmin"
|
||||
/>
|
||||
</SettingItem>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-list-move,
|
||||
.setting-list-enter-active,
|
||||
.setting-list-leave-active {
|
||||
--uno: transition-all-300;
|
||||
}
|
||||
|
||||
.setting-list-enter-from,
|
||||
.setting-list-leave-to {
|
||||
--uno: opacity-0 -translate-x-30px;
|
||||
}
|
||||
|
||||
.setting-list-leave-active {
|
||||
--uno: absolute;
|
||||
}
|
||||
</style>
|
31
src/layouts/modules/theme-drawer/modules/layout/index.vue
Normal file
31
src/layouts/modules/theme-drawer/modules/layout/index.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import LayoutMode from './modules/layout-mode.vue';
|
||||
import TabSettings from './modules/tab-settings.vue';
|
||||
import HeaderSettings from './modules/header-settings.vue';
|
||||
import SiderSettings from './modules/sider-settings.vue';
|
||||
import FooterSettings from './modules/footer-settings.vue';
|
||||
import ContentSettings from './modules/content-settings.vue';
|
||||
import TableSettings from './modules/table-settings.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'LayoutSettings'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-16px">
|
||||
<LayoutMode />
|
||||
<TabSettings />
|
||||
<HeaderSettings />
|
||||
<!-- The top menu mode does not have a sidebar -->
|
||||
<SiderSettings v-if="themeStore.layout.mode !== 'horizontal'" />
|
||||
<FooterSettings />
|
||||
<ContentSettings />
|
||||
<TableSettings />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { themePageAnimationModeOptions, themeScrollModeOptions } from '@/constants/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { translateOptions } from '@/utils/common';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'ContentSettings'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wrapper');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.layout.content.title') }}</NDivider>
|
||||
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||
<SettingItem key="1" :label="$t('theme.layout.content.scrollMode.title')">
|
||||
<template #suffix>
|
||||
<IconTooltip :desc="$t('theme.layout.content.scrollMode.tip')" />
|
||||
</template>
|
||||
<NSelect
|
||||
v-model:value="themeStore.layout.scrollMode"
|
||||
:options="translateOptions(themeScrollModeOptions)"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem key="2" :label="$t('theme.layout.content.page.animate')">
|
||||
<NSwitch v-model:value="themeStore.page.animate" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.page.animate" key="3" :label="$t('theme.layout.content.page.mode.title')">
|
||||
<NSelect
|
||||
v-model:value="themeStore.page.animateMode"
|
||||
:options="translateOptions(themePageAnimationModeOptions)"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem v-if="isWrapperScrollMode" key="4" :label="$t('theme.layout.content.fixedHeaderAndTab')">
|
||||
<NSwitch v-model:value="themeStore.fixedHeaderAndTab" />
|
||||
</SettingItem>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-list-move,
|
||||
.setting-list-enter-active,
|
||||
.setting-list-leave-active {
|
||||
--uno: transition-all-300;
|
||||
}
|
||||
|
||||
.setting-list-enter-from,
|
||||
.setting-list-leave-to {
|
||||
--uno: opacity-0 -translate-x-30px;
|
||||
}
|
||||
|
||||
.setting-list-leave-active {
|
||||
--uno: absolute;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'FooterSettings'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const layoutMode = computed(() => themeStore.layout.mode);
|
||||
const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wrapper');
|
||||
const isMixHorizontalMode = computed(() =>
|
||||
['top-hybrid-sidebar-first', 'top-hybrid-header-first'].includes(layoutMode.value)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.layout.footer.title') }}</NDivider>
|
||||
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||
<SettingItem key="1" :label="$t('theme.layout.footer.visible')">
|
||||
<NSwitch v-model:value="themeStore.footer.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
v-if="themeStore.footer.visible && isWrapperScrollMode"
|
||||
key="2"
|
||||
:label="$t('theme.layout.footer.fixed')"
|
||||
>
|
||||
<NSwitch v-model:value="themeStore.footer.fixed" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.footer.visible" key="3" :label="$t('theme.layout.footer.height')">
|
||||
<NInputNumber v-model:value="themeStore.footer.height" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
v-if="themeStore.footer.visible && isMixHorizontalMode"
|
||||
key="4"
|
||||
:label="$t('theme.layout.footer.right')"
|
||||
>
|
||||
<NSwitch v-model:value="themeStore.footer.right" />
|
||||
</SettingItem>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-list-move,
|
||||
.setting-list-enter-active,
|
||||
.setting-list-leave-active {
|
||||
--uno: transition-all-300;
|
||||
}
|
||||
|
||||
.setting-list-enter-from,
|
||||
.setting-list-leave-to {
|
||||
--uno: opacity-0 -translate-x-30px;
|
||||
}
|
||||
|
||||
.setting-list-leave-active {
|
||||
--uno: absolute;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'HeaderSettings'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.layout.header.title') }}</NDivider>
|
||||
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||
<SettingItem key="1" :label="$t('theme.layout.header.height')">
|
||||
<NInputNumber v-model:value="themeStore.header.height" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem key="2" :label="$t('theme.layout.header.breadcrumb.visible')">
|
||||
<NSwitch v-model:value="themeStore.header.breadcrumb.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
v-if="themeStore.header.breadcrumb.visible"
|
||||
key="3"
|
||||
:label="$t('theme.layout.header.breadcrumb.showIcon')"
|
||||
>
|
||||
<NSwitch v-model:value="themeStore.header.breadcrumb.showIcon" />
|
||||
</SettingItem>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-list-move,
|
||||
.setting-list-enter-active,
|
||||
.setting-list-leave-active {
|
||||
--uno: transition-all-300;
|
||||
}
|
||||
|
||||
.setting-list-enter-from,
|
||||
.setting-list-leave-to {
|
||||
--uno: opacity-0 -translate-x-30px;
|
||||
}
|
||||
|
||||
.setting-list-leave-active {
|
||||
--uno: absolute;
|
||||
}
|
||||
</style>
|
@ -2,8 +2,7 @@
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
import LayoutModeCard from '../components/layout-mode-card.vue';
|
||||
import SettingItem from '../components/setting-item.vue';
|
||||
import LayoutModeCard from '../../../components/layout-mode-card.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'LayoutMode'
|
||||
@ -11,56 +10,60 @@ defineOptions({
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
function handleReverseHorizontalMixChange(value: boolean) {
|
||||
themeStore.setLayoutReverseHorizontalMix(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.layoutMode.title') }}</NDivider>
|
||||
<NDivider>{{ $t('theme.layout.layoutMode.title') }}</NDivider>
|
||||
<LayoutModeCard v-model:mode="themeStore.layout.mode" :disabled="appStore.isMobile">
|
||||
<template #vertical>
|
||||
<div class="layout-sider h-full w-18px"></div>
|
||||
<div class="layout-sider h-full w-18px !bg-primary"></div>
|
||||
<div class="vertical-wrapper">
|
||||
<div class="layout-header"></div>
|
||||
<div class="layout-header bg-primary-200"></div>
|
||||
<div class="layout-main"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #vertical-mix>
|
||||
<div class="layout-sider h-full w-8px"></div>
|
||||
<div class="layout-sider h-full w-16px"></div>
|
||||
<div class="layout-sider h-full w-8px !bg-primary"></div>
|
||||
<div class="layout-sider h-full w-16px !bg-primary-300"></div>
|
||||
<div class="vertical-wrapper">
|
||||
<div class="layout-header"></div>
|
||||
<div class="layout-header bg-primary-200"></div>
|
||||
<div class="layout-main"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #vertical-hybrid-header-first>
|
||||
<div class="layout-sider h-full w-8px !bg-primary"></div>
|
||||
<div class="layout-sider h-full w-16px !bg-primary-300"></div>
|
||||
<div class="vertical-wrapper">
|
||||
<div class="layout-header bg-primary"></div>
|
||||
<div class="layout-main"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #horizontal>
|
||||
<div class="layout-header"></div>
|
||||
<div class="layout-header !bg-primary"></div>
|
||||
<div class="horizontal-wrapper">
|
||||
<div class="layout-main"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #horizontal-mix>
|
||||
<div class="layout-header"></div>
|
||||
<template #top-hybrid-sidebar-first>
|
||||
<div class="layout-header !bg-primary-300"></div>
|
||||
<div class="horizontal-wrapper">
|
||||
<div class="layout-sider w-18px !bg-primary"></div>
|
||||
<div class="layout-main"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #top-hybrid-header-first>
|
||||
<div class="layout-header bg-primary"></div>
|
||||
<div class="horizontal-wrapper">
|
||||
<div class="layout-sider w-18px"></div>
|
||||
<div class="layout-main"></div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutModeCard>
|
||||
<SettingItem
|
||||
v-if="themeStore.layout.mode === 'horizontal-mix'"
|
||||
:label="$t('theme.layoutMode.reverseHorizontalMix')"
|
||||
class="mt-16px"
|
||||
>
|
||||
<NSwitch :value="themeStore.layout.reverseHorizontalMix" @update:value="handleReverseHorizontalMixChange" />
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.layout-header {
|
||||
--uno: h-16px bg-primary rd-4px;
|
||||
--uno: h-16px rd-4px;
|
||||
}
|
||||
|
||||
.layout-sider {
|
@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SiderSettings'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const layoutMode = computed(() => themeStore.layout.mode);
|
||||
const isMixLayoutMode = computed(() => layoutMode.value.includes('mix') || layoutMode.value.includes('hybrid'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.layout.sider.title') }}</NDivider>
|
||||
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||
<SettingItem v-if="layoutMode === 'vertical'" key="1" :label="$t('theme.layout.sider.width')">
|
||||
<NInputNumber v-model:value="themeStore.sider.width" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="layoutMode === 'vertical'" key="2" :label="$t('theme.layout.sider.collapsedWidth')">
|
||||
<NInputNumber v-model:value="themeStore.sider.collapsedWidth" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="isMixLayoutMode" key="3" :label="$t('theme.layout.sider.mixWidth')">
|
||||
<NInputNumber v-model:value="themeStore.sider.mixWidth" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="isMixLayoutMode" key="4" :label="$t('theme.layout.sider.mixCollapsedWidth')">
|
||||
<NInputNumber v-model:value="themeStore.sider.mixCollapsedWidth" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="layoutMode === 'vertical-mix'" key="5" :label="$t('theme.layout.sider.mixChildMenuWidth')">
|
||||
<NInputNumber v-model:value="themeStore.sider.mixChildMenuWidth" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-list-move,
|
||||
.setting-list-enter-active,
|
||||
.setting-list-leave-active {
|
||||
--uno: transition-all-300;
|
||||
}
|
||||
|
||||
.setting-list-enter-from,
|
||||
.setting-list-leave-to {
|
||||
--uno: opacity-0 -translate-x-30px;
|
||||
}
|
||||
|
||||
.setting-list-leave-active {
|
||||
--uno: absolute;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { resetCacheStrategyOptions, themeTabModeOptions } from '@/constants/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { translateOptions } from '@/utils/common';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'TabSettings'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.layout.tab.title') }}</NDivider>
|
||||
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||
<SettingItem key="0" :label="$t('theme.layout.resetCacheStrategy.title')">
|
||||
<NSelect
|
||||
v-model:value="themeStore.resetCacheStrategy"
|
||||
:options="translateOptions(resetCacheStrategyOptions)"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem key="1" :label="$t('theme.layout.tab.visible')">
|
||||
<NSwitch v-model:value="themeStore.tab.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.tab.visible" key="2" :label="$t('theme.layout.tab.cache')">
|
||||
<template #suffix>
|
||||
<IconTooltip :desc="$t('theme.layout.tab.cacheTip')" />
|
||||
</template>
|
||||
<NSwitch v-model:value="themeStore.tab.cache" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.tab.visible" key="3" :label="$t('theme.layout.tab.height')">
|
||||
<NInputNumber v-model:value="themeStore.tab.height" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.tab.visible" key="4" :label="$t('theme.layout.tab.mode.title')">
|
||||
<NSelect
|
||||
v-model:value="themeStore.tab.mode"
|
||||
:options="translateOptions(themeTabModeOptions)"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
/>
|
||||
</SettingItem>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-list-move,
|
||||
.setting-list-enter-active,
|
||||
.setting-list-leave-active {
|
||||
--uno: transition-all-300;
|
||||
}
|
||||
|
||||
.setting-list-enter-from,
|
||||
.setting-list-leave-to {
|
||||
--uno: opacity-0 -translate-x-30px;
|
||||
}
|
||||
|
||||
.setting-list-leave-active {
|
||||
--uno: absolute;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { themeTableSizeOptions } from '@/constants/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { translateOptions } from '@/utils/common';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../../../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'TableProps'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.tablePropsTitle') }}</NDivider>
|
||||
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||
<SettingItem key="0" :label="$t('theme.table.size.title')">
|
||||
<NSelect
|
||||
v-model:value="themeStore.table.size"
|
||||
:options="translateOptions(themeTableSizeOptions)"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem key="1" :label="$t('theme.table.bordered')">
|
||||
<NSwitch v-model:value="themeStore.table.bordered" />
|
||||
</SettingItem>
|
||||
<SettingItem key="2" :label="$t('theme.table.bottomBordered')">
|
||||
<NSwitch v-model:value="themeStore.table.bottomBordered" />
|
||||
</SettingItem>
|
||||
<SettingItem key="3" :label="$t('theme.table.singleColumn')">
|
||||
<NSwitch v-model:value="themeStore.table.singleColumn" :checked-value="false" :unchecked-value="true" />
|
||||
</SettingItem>
|
||||
<SettingItem key="4" :label="$t('theme.table.singleLine')">
|
||||
<NSwitch v-model:value="themeStore.table.singleLine" :checked-value="false" :unchecked-value="true" />
|
||||
</SettingItem>
|
||||
<SettingItem key="5" :label="$t('theme.table.striped')">
|
||||
<NSwitch v-model:value="themeStore.table.striped" />
|
||||
</SettingItem>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,157 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
resetCacheStrategyOptions,
|
||||
themePageAnimationModeOptions,
|
||||
themeScrollModeOptions,
|
||||
themeTabModeOptions
|
||||
} from '@/constants/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { translateOptions } from '@/utils/common';
|
||||
import { $t } from '@/locales';
|
||||
import SettingItem from '../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PageFun'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const layoutMode = computed(() => themeStore.layout.mode);
|
||||
|
||||
const isMixLayoutMode = computed(() => layoutMode.value.includes('mix'));
|
||||
|
||||
const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wrapper');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.pageFunTitle') }}</NDivider>
|
||||
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||
<SettingItem key="0" :label="$t('theme.resetCacheStrategy.title')">
|
||||
<NSelect
|
||||
v-model:value="themeStore.resetCacheStrategy"
|
||||
:options="translateOptions(resetCacheStrategyOptions)"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem key="1" :label="$t('theme.scrollMode.title')">
|
||||
<NSelect
|
||||
v-model:value="themeStore.layout.scrollMode"
|
||||
:options="translateOptions(themeScrollModeOptions)"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem key="1-1" :label="$t('theme.page.animate')">
|
||||
<NSwitch v-model:value="themeStore.page.animate" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.page.animate" key="1-2" :label="$t('theme.page.mode.title')">
|
||||
<NSelect
|
||||
v-model:value="themeStore.page.animateMode"
|
||||
:options="translateOptions(themePageAnimationModeOptions)"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem v-if="isWrapperScrollMode" key="2" :label="$t('theme.fixedHeaderAndTab')">
|
||||
<NSwitch v-model:value="themeStore.fixedHeaderAndTab" />
|
||||
</SettingItem>
|
||||
<SettingItem key="3" :label="$t('theme.header.height')">
|
||||
<NInputNumber v-model:value="themeStore.header.height" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem key="4" :label="$t('theme.header.breadcrumb.visible')">
|
||||
<NSwitch v-model:value="themeStore.header.breadcrumb.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.header.breadcrumb.visible" key="4-1" :label="$t('theme.header.breadcrumb.showIcon')">
|
||||
<NSwitch v-model:value="themeStore.header.breadcrumb.showIcon" />
|
||||
</SettingItem>
|
||||
<SettingItem key="5" :label="$t('theme.tab.visible')">
|
||||
<NSwitch v-model:value="themeStore.tab.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.tab.visible" key="5-1" :label="$t('theme.tab.cache')">
|
||||
<NSwitch v-model:value="themeStore.tab.cache" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.tab.visible" key="5-2" :label="$t('theme.tab.height')">
|
||||
<NInputNumber v-model:value="themeStore.tab.height" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.tab.visible" key="5-3" :label="$t('theme.tab.mode.title')">
|
||||
<NSelect
|
||||
v-model:value="themeStore.tab.mode"
|
||||
:options="translateOptions(themeTabModeOptions)"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem v-if="layoutMode === 'vertical'" key="6-1" :label="$t('theme.sider.width')">
|
||||
<NInputNumber v-model:value="themeStore.sider.width" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="layoutMode === 'vertical'" key="6-2" :label="$t('theme.sider.collapsedWidth')">
|
||||
<NInputNumber v-model:value="themeStore.sider.collapsedWidth" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="isMixLayoutMode" key="6-3" :label="$t('theme.sider.mixWidth')">
|
||||
<NInputNumber v-model:value="themeStore.sider.mixWidth" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="isMixLayoutMode" key="6-4" :label="$t('theme.sider.mixCollapsedWidth')">
|
||||
<NInputNumber v-model:value="themeStore.sider.mixCollapsedWidth" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="layoutMode === 'vertical-mix'" key="6-5" :label="$t('theme.sider.mixChildMenuWidth')">
|
||||
<NInputNumber v-model:value="themeStore.sider.mixChildMenuWidth" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem key="7" :label="$t('theme.footer.visible')">
|
||||
<NSwitch v-model:value="themeStore.footer.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.footer.visible && isWrapperScrollMode" key="7-1" :label="$t('theme.footer.fixed')">
|
||||
<NSwitch v-model:value="themeStore.footer.fixed" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.footer.visible" key="7-2" :label="$t('theme.footer.height')">
|
||||
<NInputNumber v-model:value="themeStore.footer.height" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
v-if="themeStore.footer.visible && layoutMode === 'horizontal-mix'"
|
||||
key="7-3"
|
||||
:label="$t('theme.footer.right')"
|
||||
>
|
||||
<NSwitch v-model:value="themeStore.footer.right" />
|
||||
</SettingItem>
|
||||
<SettingItem key="8" :label="$t('theme.watermark.visible')">
|
||||
<NSwitch v-model:value="themeStore.watermark.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.watermark.visible" key="8-1" :label="$t('theme.watermark.enableUserName')">
|
||||
<NSwitch v-model:value="themeStore.watermark.enableUserName" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.watermark.visible" key="8-2" :label="$t('theme.watermark.text')">
|
||||
<NInput
|
||||
v-model:value="themeStore.watermark.text"
|
||||
autosize
|
||||
type="text"
|
||||
size="small"
|
||||
class="w-120px"
|
||||
placeholder="SoybeanAdmin"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem key="9" :label="$t('theme.header.multilingual.visible')">
|
||||
<NSwitch v-model:value="themeStore.header.multilingual.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem key="10" :label="$t('theme.header.globalSearch.visible')">
|
||||
<NSwitch v-model:value="themeStore.header.globalSearch.visible" />
|
||||
</SettingItem>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-list-move,
|
||||
.setting-list-enter-active,
|
||||
.setting-list-leave-active {
|
||||
--uno: transition-all-300;
|
||||
}
|
||||
|
||||
.setting-list-enter-from,
|
||||
.setting-list-leave-to {
|
||||
--uno: opacity-0 -translate-x-30px;
|
||||
}
|
||||
|
||||
.setting-list-leave-active {
|
||||
--uno: absolute;
|
||||
}
|
||||
</style>
|
@ -82,93 +82,142 @@ const local: App.I18n.Schema = {
|
||||
tokenExpired: 'The requested token has expired'
|
||||
},
|
||||
theme: {
|
||||
themeSchema: {
|
||||
title: 'Theme Schema',
|
||||
light: 'Light',
|
||||
dark: 'Dark',
|
||||
auto: 'Follow System'
|
||||
themeDrawerTitle: 'Theme Configuration',
|
||||
tabs: {
|
||||
appearance: 'Appearance',
|
||||
layout: 'Layout',
|
||||
general: 'General'
|
||||
},
|
||||
grayscale: 'Grayscale',
|
||||
colourWeakness: 'Colour Weakness',
|
||||
layoutMode: {
|
||||
title: 'Layout Mode',
|
||||
vertical: 'Vertical Menu Mode',
|
||||
horizontal: 'Horizontal Menu Mode',
|
||||
'vertical-mix': 'Vertical Mix Menu Mode',
|
||||
'horizontal-mix': 'Horizontal Mix menu Mode',
|
||||
reverseHorizontalMix: 'Reverse first level menus and child level menus position'
|
||||
appearance: {
|
||||
themeSchema: {
|
||||
title: 'Theme Schema',
|
||||
light: 'Light',
|
||||
dark: 'Dark',
|
||||
auto: 'Follow System'
|
||||
},
|
||||
grayscale: 'Grayscale',
|
||||
colourWeakness: 'Colour Weakness',
|
||||
themeColor: {
|
||||
title: 'Theme Color',
|
||||
primary: 'Primary',
|
||||
info: 'Info',
|
||||
success: 'Success',
|
||||
warning: 'Warning',
|
||||
error: 'Error',
|
||||
followPrimary: 'Follow Primary'
|
||||
},
|
||||
recommendColor: 'Apply Recommended Color Algorithm',
|
||||
recommendColorDesc: 'The recommended color algorithm refers to'
|
||||
},
|
||||
recommendColor: 'Apply Recommended Color Algorithm',
|
||||
recommendColorDesc: 'The recommended color algorithm refers to',
|
||||
themeColor: {
|
||||
title: 'Theme Color',
|
||||
primary: 'Primary',
|
||||
info: 'Info',
|
||||
success: 'Success',
|
||||
warning: 'Warning',
|
||||
error: 'Error',
|
||||
followPrimary: 'Follow Primary'
|
||||
},
|
||||
scrollMode: {
|
||||
title: 'Scroll Mode',
|
||||
wrapper: 'Wrapper',
|
||||
content: 'Content'
|
||||
},
|
||||
page: {
|
||||
animate: 'Page Animate',
|
||||
mode: {
|
||||
title: 'Page Animate Mode',
|
||||
fade: 'Fade',
|
||||
'fade-slide': 'Slide',
|
||||
'fade-bottom': 'Fade Zoom',
|
||||
'fade-scale': 'Fade Scale',
|
||||
'zoom-fade': 'Zoom Fade',
|
||||
'zoom-out': 'Zoom Out',
|
||||
none: 'None'
|
||||
layout: {
|
||||
layoutMode: {
|
||||
title: 'Layout Mode',
|
||||
vertical: 'Vertical Mode',
|
||||
horizontal: 'Horizontal Mode',
|
||||
'vertical-mix': 'Vertical Mix Mode',
|
||||
'vertical-hybrid-header-first': 'Left Hybrid Header-First',
|
||||
'top-hybrid-sidebar-first': 'Top-Hybrid Sidebar-First',
|
||||
'top-hybrid-header-first': 'Top-Hybrid Header-First',
|
||||
vertical_detail: 'Vertical menu layout, with the menu on the left and content on the right.',
|
||||
'vertical-mix_detail':
|
||||
'Vertical mix-menu layout, with the primary menu on the dark left side and the secondary menu on the lighter left side.',
|
||||
'vertical-hybrid-header-first_detail':
|
||||
'Left hybrid layout, with the primary menu at the top, the secondary menu on the dark left side, and the tertiary menu on the lighter left side.',
|
||||
horizontal_detail: 'Horizontal menu layout, with the menu at the top and content below.',
|
||||
'top-hybrid-sidebar-first_detail':
|
||||
'Top hybrid layout, with the primary menu on the left and the secondary menu at the top.',
|
||||
'top-hybrid-header-first_detail':
|
||||
'Top hybrid layout, with the primary menu at the top and the secondary menu on the left.'
|
||||
},
|
||||
tab: {
|
||||
title: 'Tab Settings',
|
||||
visible: 'Tab Visible',
|
||||
cache: 'Tag Bar Info Cache',
|
||||
cacheTip: 'One-click to open/close global keepalive',
|
||||
height: 'Tab Height',
|
||||
mode: {
|
||||
title: 'Tab Mode',
|
||||
chrome: 'Chrome',
|
||||
button: 'Button'
|
||||
}
|
||||
},
|
||||
header: {
|
||||
title: 'Header Settings',
|
||||
height: 'Header Height',
|
||||
breadcrumb: {
|
||||
visible: 'Breadcrumb Visible',
|
||||
showIcon: 'Breadcrumb Icon Visible'
|
||||
}
|
||||
},
|
||||
sider: {
|
||||
title: 'Sider Settings',
|
||||
inverted: 'Dark Sider',
|
||||
width: 'Sider Width',
|
||||
collapsedWidth: 'Sider Collapsed Width',
|
||||
mixWidth: 'Mix Sider Width',
|
||||
mixCollapsedWidth: 'Mix Sider Collapse Width',
|
||||
mixChildMenuWidth: 'Mix Child Menu Width'
|
||||
},
|
||||
footer: {
|
||||
title: 'Footer Settings',
|
||||
visible: 'Footer Visible',
|
||||
fixed: 'Fixed Footer',
|
||||
height: 'Footer Height',
|
||||
right: 'Right Footer'
|
||||
},
|
||||
content: {
|
||||
title: 'Content Area Settings',
|
||||
scrollMode: {
|
||||
title: 'Scroll Mode',
|
||||
tip: 'The theme scroll only scrolls the main part, the outer scroll can carry the header and footer together',
|
||||
wrapper: 'Wrapper',
|
||||
content: 'Content'
|
||||
},
|
||||
page: {
|
||||
animate: 'Page Animate',
|
||||
mode: {
|
||||
title: 'Page Animate Mode',
|
||||
fade: 'Fade',
|
||||
'fade-slide': 'Slide',
|
||||
'fade-bottom': 'Fade Zoom',
|
||||
'fade-scale': 'Fade Scale',
|
||||
'zoom-fade': 'Zoom Fade',
|
||||
'zoom-out': 'Zoom Out',
|
||||
none: 'None'
|
||||
}
|
||||
},
|
||||
fixedHeaderAndTab: 'Fixed Header And Tab'
|
||||
},
|
||||
resetCacheStrategy: {
|
||||
title: 'Reset Cache Strategy',
|
||||
close: 'Close Page',
|
||||
refresh: 'Refresh Page'
|
||||
}
|
||||
},
|
||||
fixedHeaderAndTab: 'Fixed Header And Tab',
|
||||
header: {
|
||||
height: 'Header Height',
|
||||
breadcrumb: {
|
||||
visible: 'Breadcrumb Visible',
|
||||
showIcon: 'Breadcrumb Icon Visible'
|
||||
general: {
|
||||
title: 'General Settings',
|
||||
watermark: {
|
||||
title: 'Watermark Settings',
|
||||
visible: 'Watermark Full Screen Visible',
|
||||
text: 'Custom Watermark Text',
|
||||
enableUserName: 'Enable User Name Watermark',
|
||||
enableTime: 'Show Current Time',
|
||||
timeFormat: 'Time Format'
|
||||
},
|
||||
multilingual: {
|
||||
title: 'Multilingual Settings',
|
||||
visible: 'Display multilingual button'
|
||||
},
|
||||
globalSearch: {
|
||||
title: 'Global Search Settings',
|
||||
visible: 'Display GlobalSearch button'
|
||||
}
|
||||
},
|
||||
tab: {
|
||||
visible: 'Tab Visible',
|
||||
cache: 'Tag Bar Info Cache',
|
||||
height: 'Tab Height',
|
||||
mode: {
|
||||
title: 'Tab Mode',
|
||||
chrome: 'Chrome',
|
||||
button: 'Button'
|
||||
}
|
||||
},
|
||||
sider: {
|
||||
inverted: 'Dark Sider',
|
||||
width: 'Sider Width',
|
||||
collapsedWidth: 'Sider Collapsed Width',
|
||||
mixWidth: 'Mix Sider Width',
|
||||
mixCollapsedWidth: 'Mix Sider Collapse Width',
|
||||
mixChildMenuWidth: 'Mix Child Menu Width'
|
||||
},
|
||||
footer: {
|
||||
visible: 'Footer Visible',
|
||||
fixed: 'Fixed Footer',
|
||||
height: 'Footer Height',
|
||||
right: 'Right Footer'
|
||||
},
|
||||
watermark: {
|
||||
visible: 'Watermark Full Screen Visible',
|
||||
text: 'Watermark Text',
|
||||
enableUserName: 'Enable User Name Watermark'
|
||||
configOperation: {
|
||||
copyConfig: 'Copy Config',
|
||||
copySuccessMsg: 'Copy Success, Please replace the variable "themeSettings" in "src/theme/settings.ts"',
|
||||
resetConfig: 'Reset Config',
|
||||
resetSuccessMsg: 'Reset Success'
|
||||
},
|
||||
tablePropsTitle: 'Table Props',
|
||||
table: {
|
||||
@ -183,19 +232,6 @@ const local: App.I18n.Schema = {
|
||||
singleColumn: 'Single Column',
|
||||
singleLine: 'Single Line',
|
||||
striped: 'Striped'
|
||||
},
|
||||
themeDrawerTitle: 'Theme Configuration',
|
||||
pageFunTitle: 'Page Function',
|
||||
resetCacheStrategy: {
|
||||
title: 'Reset Cache Strategy',
|
||||
close: 'Close Page',
|
||||
refresh: 'Refresh Page'
|
||||
},
|
||||
configOperation: {
|
||||
copyConfig: 'Copy Config',
|
||||
copySuccessMsg: 'Copy Success, Please replace the variable "themeSettings" in "src/theme/settings.ts"',
|
||||
resetConfig: 'Reset Config',
|
||||
resetSuccessMsg: 'Reset Success'
|
||||
}
|
||||
},
|
||||
route: {
|
||||
@ -205,49 +241,17 @@ const local: App.I18n.Schema = {
|
||||
500: 'Server Error',
|
||||
'iframe-page': 'Iframe',
|
||||
home: 'Home',
|
||||
system: 'System Management',
|
||||
system_menu: 'Menu Management',
|
||||
tool: 'System Tools',
|
||||
tool_gen: 'Code Generation',
|
||||
system_user: 'User Management',
|
||||
system_dict: 'Dict Management',
|
||||
system_tenant: 'Tenant Management',
|
||||
'system_tenant-package': 'Tenant Package Management',
|
||||
system_config: 'Config Management',
|
||||
system_dept: 'Dept Management',
|
||||
system_post: 'Post Management',
|
||||
monitor: 'Monitor Management',
|
||||
monitor_logininfor: 'Login Log',
|
||||
monitor_operlog: 'Operate Log',
|
||||
system_client: 'Client Management',
|
||||
system_notice: 'Notice Management',
|
||||
monitor: 'Monitor',
|
||||
'social-callback': 'Social Callback',
|
||||
system_oss: 'File Management',
|
||||
'system_oss-config': 'OSS Config',
|
||||
monitor_cache: 'Cache Monitor',
|
||||
monitor_online: 'Online User',
|
||||
'user-center': 'User Center',
|
||||
system_role: 'Role Management',
|
||||
demo: 'Demo',
|
||||
demo_demo: 'Demo Table',
|
||||
demo_tree: 'Demo Tree',
|
||||
workflow: 'Workflow',
|
||||
workflow_category: 'Workflow Category',
|
||||
exception: 'Exception',
|
||||
exception_403: '403',
|
||||
exception_404: '404',
|
||||
exception_500: '500',
|
||||
workflow_design: 'Process Design',
|
||||
workflow_spel: 'Process Spel',
|
||||
'workflow_process-definition': 'Process Definition',
|
||||
'workflow_process-instance': 'Process Instance',
|
||||
workflow_task: 'Task',
|
||||
'workflow_task_all-task-waiting': 'All Task Waiting',
|
||||
workflow_leave: 'Leave Apply',
|
||||
'workflow_task_my-document': 'My Document',
|
||||
'workflow_task_task-waiting': 'My Task Waiting',
|
||||
'workflow_task_task-finish': 'My Task Finish',
|
||||
'workflow_task_task-copy': 'My Task Copy'
|
||||
exception_500: '500'
|
||||
},
|
||||
menu: {
|
||||
system_tenant: 'Tenant Management',
|
||||
|
@ -82,93 +82,139 @@ const local: App.I18n.Schema = {
|
||||
tokenExpired: 'token已过期'
|
||||
},
|
||||
theme: {
|
||||
themeSchema: {
|
||||
title: '主题模式',
|
||||
light: '亮色模式',
|
||||
dark: '暗黑模式',
|
||||
auto: '跟随系统'
|
||||
themeDrawerTitle: '主题配置',
|
||||
tabs: {
|
||||
appearance: '外观',
|
||||
layout: '布局',
|
||||
general: '通用'
|
||||
},
|
||||
grayscale: '灰色模式',
|
||||
colourWeakness: '色弱模式',
|
||||
layoutMode: {
|
||||
title: '布局模式',
|
||||
vertical: '左侧菜单模式',
|
||||
'vertical-mix': '左侧菜单混合模式',
|
||||
horizontal: '顶部菜单模式',
|
||||
'horizontal-mix': '顶部菜单混合模式',
|
||||
reverseHorizontalMix: '一级菜单与子级菜单位置反转'
|
||||
appearance: {
|
||||
themeSchema: {
|
||||
title: '主题模式',
|
||||
light: '亮色模式',
|
||||
dark: '暗黑模式',
|
||||
auto: '跟随系统'
|
||||
},
|
||||
grayscale: '灰色模式',
|
||||
colourWeakness: '色弱模式',
|
||||
themeColor: {
|
||||
title: '主题颜色',
|
||||
primary: '主色',
|
||||
info: '信息色',
|
||||
success: '成功色',
|
||||
warning: '警告色',
|
||||
error: '错误色',
|
||||
followPrimary: '跟随主色'
|
||||
},
|
||||
recommendColor: '应用推荐算法的颜色',
|
||||
recommendColorDesc: '推荐颜色的算法参照'
|
||||
},
|
||||
recommendColor: '应用推荐算法的颜色',
|
||||
recommendColorDesc: '推荐颜色的算法参照',
|
||||
themeColor: {
|
||||
title: '主题颜色',
|
||||
primary: '主色',
|
||||
info: '信息色',
|
||||
success: '成功色',
|
||||
warning: '警告色',
|
||||
error: '错误色',
|
||||
followPrimary: '跟随主色'
|
||||
},
|
||||
scrollMode: {
|
||||
title: '滚动模式',
|
||||
wrapper: '外层滚动',
|
||||
content: '主体滚动'
|
||||
},
|
||||
page: {
|
||||
animate: '页面切换动画',
|
||||
mode: {
|
||||
title: '页面切换动画类型',
|
||||
'fade-slide': '滑动',
|
||||
fade: '淡入淡出',
|
||||
'fade-bottom': '底部消退',
|
||||
'fade-scale': '缩放消退',
|
||||
'zoom-fade': '渐变',
|
||||
'zoom-out': '闪现',
|
||||
none: '无'
|
||||
layout: {
|
||||
layoutMode: {
|
||||
title: '布局模式',
|
||||
vertical: '左侧菜单模式',
|
||||
'vertical-mix': '左侧菜单混合模式',
|
||||
'vertical-hybrid-header-first': '左侧混合-顶部优先',
|
||||
horizontal: '顶部菜单模式',
|
||||
'top-hybrid-sidebar-first': '顶部混合-侧边优先',
|
||||
'top-hybrid-header-first': '顶部混合-顶部优先',
|
||||
vertical_detail: '左侧菜单布局,菜单在左,内容在右。',
|
||||
'vertical-mix_detail': '左侧双菜单布局,一级菜单在左侧深色区域,二级菜单在左侧浅色区域。',
|
||||
'vertical-hybrid-header-first_detail':
|
||||
'左侧混合布局,一级菜单在顶部,二级菜单在左侧深色区域,三级菜单在左侧浅色区域。',
|
||||
horizontal_detail: '顶部菜单布局,菜单在顶部,内容在下方。',
|
||||
'top-hybrid-sidebar-first_detail': '顶部混合布局,一级菜单在左侧,二级菜单在顶部。',
|
||||
'top-hybrid-header-first_detail': '顶部混合布局,一级菜单在顶部,二级菜单在左侧。'
|
||||
},
|
||||
tab: {
|
||||
title: '标签栏设置',
|
||||
visible: '显示标签栏',
|
||||
cache: '标签栏信息缓存',
|
||||
cacheTip: '一键开启/关闭全局 keepalive',
|
||||
height: '标签栏高度',
|
||||
mode: {
|
||||
title: '标签栏风格',
|
||||
chrome: '谷歌风格',
|
||||
button: '按钮风格'
|
||||
}
|
||||
},
|
||||
header: {
|
||||
title: '头部设置',
|
||||
height: '头部高度',
|
||||
breadcrumb: {
|
||||
visible: '显示面包屑',
|
||||
showIcon: '显示面包屑图标'
|
||||
}
|
||||
},
|
||||
sider: {
|
||||
title: '侧边栏设置',
|
||||
inverted: '深色侧边栏',
|
||||
width: '侧边栏宽度',
|
||||
collapsedWidth: '侧边栏折叠宽度',
|
||||
mixWidth: '混合布局侧边栏宽度',
|
||||
mixCollapsedWidth: '混合布局侧边栏折叠宽度',
|
||||
mixChildMenuWidth: '混合布局子菜单宽度'
|
||||
},
|
||||
footer: {
|
||||
title: '底部设置',
|
||||
visible: '显示底部',
|
||||
fixed: '固定底部',
|
||||
height: '底部高度',
|
||||
right: '底部局右'
|
||||
},
|
||||
content: {
|
||||
title: '内容区域设置',
|
||||
scrollMode: {
|
||||
title: '滚动模式',
|
||||
tip: '主题滚动仅 main 部分滚动,外层滚动可携带头部底部一起滚动',
|
||||
wrapper: '外层滚动',
|
||||
content: '主体滚动'
|
||||
},
|
||||
page: {
|
||||
animate: '页面切换动画',
|
||||
mode: {
|
||||
title: '页面切换动画类型',
|
||||
'fade-slide': '滑动',
|
||||
fade: '淡入淡出',
|
||||
'fade-bottom': '底部消退',
|
||||
'fade-scale': '缩放消退',
|
||||
'zoom-fade': '渐变',
|
||||
'zoom-out': '闪现',
|
||||
none: '无'
|
||||
}
|
||||
},
|
||||
fixedHeaderAndTab: '固定头部和标签栏'
|
||||
},
|
||||
resetCacheStrategy: {
|
||||
title: '重置缓存策略',
|
||||
close: '关闭页面',
|
||||
refresh: '刷新页面'
|
||||
}
|
||||
},
|
||||
fixedHeaderAndTab: '固定头部和标签栏',
|
||||
header: {
|
||||
height: '头部高度',
|
||||
breadcrumb: {
|
||||
visible: '显示面包屑',
|
||||
showIcon: '显示面包屑图标'
|
||||
general: {
|
||||
title: '通用设置',
|
||||
watermark: {
|
||||
title: '水印设置',
|
||||
visible: '显示全屏水印',
|
||||
text: '自定义水印文本',
|
||||
enableUserName: '启用用户名水印',
|
||||
enableTime: '显示当前时间',
|
||||
timeFormat: '时间格式'
|
||||
},
|
||||
multilingual: {
|
||||
title: '多语言设置',
|
||||
visible: '显示多语言按钮'
|
||||
},
|
||||
globalSearch: {
|
||||
title: '全局搜索设置',
|
||||
visible: '显示全局搜索按钮'
|
||||
}
|
||||
},
|
||||
tab: {
|
||||
visible: '显示标签栏',
|
||||
cache: '标签栏信息缓存',
|
||||
height: '标签栏高度',
|
||||
mode: {
|
||||
title: '标签栏风格',
|
||||
chrome: '谷歌风格',
|
||||
button: '按钮风格'
|
||||
}
|
||||
},
|
||||
sider: {
|
||||
inverted: '深色侧边栏',
|
||||
width: '侧边栏宽度',
|
||||
collapsedWidth: '侧边栏折叠宽度',
|
||||
mixWidth: '混合布局侧边栏宽度',
|
||||
mixCollapsedWidth: '混合布局侧边栏折叠宽度',
|
||||
mixChildMenuWidth: '混合布局子菜单宽度'
|
||||
},
|
||||
footer: {
|
||||
visible: '显示底部',
|
||||
fixed: '固定底部',
|
||||
height: '底部高度',
|
||||
right: '底部局右'
|
||||
},
|
||||
watermark: {
|
||||
visible: '显示全屏水印',
|
||||
text: '水印文本',
|
||||
enableUserName: '启用用户名水印'
|
||||
configOperation: {
|
||||
copyConfig: '复制配置',
|
||||
copySuccessMsg: '复制成功,请替换 src/theme/settings.ts 中的变量 themeSettings',
|
||||
resetConfig: '重置配置',
|
||||
resetSuccessMsg: '重置成功'
|
||||
},
|
||||
tablePropsTitle: '表格配置',
|
||||
table: {
|
||||
@ -183,19 +229,6 @@ const local: App.I18n.Schema = {
|
||||
singleColumn: '设定行的分割线',
|
||||
singleLine: '设定列的分割线',
|
||||
striped: '斑马线条纹'
|
||||
},
|
||||
themeDrawerTitle: '主题配置',
|
||||
pageFunTitle: '页面功能',
|
||||
resetCacheStrategy: {
|
||||
title: '重置缓存策略',
|
||||
close: '关闭页面',
|
||||
refresh: '刷新页面'
|
||||
},
|
||||
configOperation: {
|
||||
copyConfig: '复制配置',
|
||||
copySuccessMsg: '复制成功,请替换 src/theme/settings.ts 中的变量 themeSettings',
|
||||
resetConfig: '重置配置',
|
||||
resetSuccessMsg: '重置成功'
|
||||
}
|
||||
},
|
||||
route: {
|
||||
@ -205,49 +238,17 @@ const local: App.I18n.Schema = {
|
||||
500: '服务器错误',
|
||||
'iframe-page': '外链页面',
|
||||
home: '首页',
|
||||
system: '系统管理',
|
||||
system_menu: '菜单管理',
|
||||
tool: '系统工具',
|
||||
tool_gen: '代码生成',
|
||||
system_user: '用户管理',
|
||||
system_dict: '字典管理',
|
||||
system_tenant: '租户管理',
|
||||
'system_tenant-package': '租户套餐',
|
||||
system_config: '参数设置',
|
||||
system_dept: '部门管理',
|
||||
system_post: '岗位管理',
|
||||
monitor: '系统监控',
|
||||
monitor_logininfor: '登录日志',
|
||||
monitor_operlog: '操作日志',
|
||||
system_client: '客户端管理',
|
||||
system_notice: '通知公告',
|
||||
'social-callback': '单点登录回调',
|
||||
system_oss: '文件管理',
|
||||
'system_oss-config': 'OSS 配置',
|
||||
monitor_cache: '缓存监控',
|
||||
monitor_online: '在线用户',
|
||||
'user-center': '个人中心',
|
||||
system_role: '角色管理',
|
||||
demo: '测试',
|
||||
demo_demo: '测试单表',
|
||||
demo_tree: '测试树表',
|
||||
workflow: '流程管理',
|
||||
workflow_category: '流程分类',
|
||||
exception: '异常页',
|
||||
exception_403: '403',
|
||||
exception_404: '404',
|
||||
exception_500: '500',
|
||||
workflow_design: '流程设计',
|
||||
workflow_spel: '流程表达式',
|
||||
'workflow_process-definition': '流程定义',
|
||||
'workflow_process-instance': '流程实例',
|
||||
workflow_task: '任务',
|
||||
'workflow_task_all-task-waiting': '待办任务',
|
||||
workflow_leave: '请假申请',
|
||||
'workflow_task_my-document': '我发起的',
|
||||
'workflow_task_task-waiting': '我的待办',
|
||||
'workflow_task_task-finish': '我的已办',
|
||||
'workflow_task_task-copy': '我的抄送'
|
||||
exception_500: '500'
|
||||
},
|
||||
menu: {
|
||||
system_tenant: '租户管理',
|
||||
|
@ -26,32 +26,4 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
||||
demo_tree: () => import("@/views/demo/tree/index.vue"),
|
||||
home: () => import("@/views/home/index.vue"),
|
||||
monitor_cache: () => import("@/views/monitor/cache/index.vue"),
|
||||
monitor_logininfor: () => import("@/views/monitor/logininfor/index.vue"),
|
||||
monitor_online: () => import("@/views/monitor/online/index.vue"),
|
||||
monitor_operlog: () => import("@/views/monitor/operlog/index.vue"),
|
||||
system_client: () => import("@/views/system/client/index.vue"),
|
||||
system_config: () => import("@/views/system/config/index.vue"),
|
||||
system_dept: () => import("@/views/system/dept/index.vue"),
|
||||
system_dict: () => import("@/views/system/dict/index.vue"),
|
||||
system_menu: () => import("@/views/system/menu/index.vue"),
|
||||
system_notice: () => import("@/views/system/notice/index.vue"),
|
||||
"system_oss-config": () => import("@/views/system/oss-config/index.vue"),
|
||||
system_oss: () => import("@/views/system/oss/index.vue"),
|
||||
system_post: () => import("@/views/system/post/index.vue"),
|
||||
system_role: () => import("@/views/system/role/index.vue"),
|
||||
"system_tenant-package": () => import("@/views/system/tenant-package/index.vue"),
|
||||
system_tenant: () => import("@/views/system/tenant/index.vue"),
|
||||
system_user: () => import("@/views/system/user/index.vue"),
|
||||
tool_gen: () => import("@/views/tool/gen/index.vue"),
|
||||
workflow_category: () => import("@/views/workflow/category/index.vue"),
|
||||
workflow_design: () => import("@/views/workflow/design/index.vue"),
|
||||
workflow_leave: () => import("@/views/workflow/leave/index.vue"),
|
||||
"workflow_process-definition": () => import("@/views/workflow/process-definition/index.vue"),
|
||||
"workflow_process-instance": () => import("@/views/workflow/process-instance/index.vue"),
|
||||
workflow_spel: () => import("@/views/workflow/spel/index.vue"),
|
||||
"workflow_task_all-task-waiting": () => import("@/views/workflow/task/all-task-waiting/index.vue"),
|
||||
"workflow_task_my-document": () => import("@/views/workflow/task/my-document/index.vue"),
|
||||
"workflow_task_task-copy": () => import("@/views/workflow/task/task-copy/index.vue"),
|
||||
"workflow_task_task-finish": () => import("@/views/workflow/task/task-finish/index.vue"),
|
||||
"workflow_task_task-waiting": () => import("@/views/workflow/task/task-waiting/index.vue"),
|
||||
};
|
||||
|
@ -121,33 +121,6 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
title: 'monitor_cache',
|
||||
i18nKey: 'route.monitor_cache'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'monitor_logininfor',
|
||||
path: '/monitor/logininfor',
|
||||
component: 'view.monitor_logininfor',
|
||||
meta: {
|
||||
title: 'monitor_logininfor',
|
||||
i18nKey: 'route.monitor_logininfor'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'monitor_online',
|
||||
path: '/monitor/online',
|
||||
component: 'view.monitor_online',
|
||||
meta: {
|
||||
title: 'monitor_online',
|
||||
i18nKey: 'route.monitor_online'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'monitor_operlog',
|
||||
path: '/monitor/operlog',
|
||||
component: 'view.monitor_operlog',
|
||||
meta: {
|
||||
title: 'monitor_operlog',
|
||||
i18nKey: 'route.monitor_operlog'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -162,165 +135,6 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
hideInMenu: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system',
|
||||
path: '/system',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'system',
|
||||
i18nKey: 'route.system',
|
||||
localIcon: 'menu-system',
|
||||
order: 1
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'system_client',
|
||||
path: '/system/client',
|
||||
component: 'view.system_client',
|
||||
meta: {
|
||||
title: 'system_client',
|
||||
i18nKey: 'route.system_client'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_config',
|
||||
path: '/system/config',
|
||||
component: 'view.system_config',
|
||||
meta: {
|
||||
title: 'system_config',
|
||||
i18nKey: 'route.system_config'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_dept',
|
||||
path: '/system/dept',
|
||||
component: 'view.system_dept',
|
||||
meta: {
|
||||
title: 'system_dept',
|
||||
i18nKey: 'route.system_dept'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_dict',
|
||||
path: '/system/dict',
|
||||
component: 'view.system_dict',
|
||||
meta: {
|
||||
title: 'system_dict',
|
||||
i18nKey: 'route.system_dict'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_menu',
|
||||
path: '/system/menu',
|
||||
component: 'view.system_menu',
|
||||
meta: {
|
||||
title: 'system_menu',
|
||||
i18nKey: 'route.system_menu',
|
||||
localIcon: 'menu-tree-table',
|
||||
order: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_notice',
|
||||
path: '/system/notice',
|
||||
component: 'view.system_notice',
|
||||
meta: {
|
||||
title: 'system_notice',
|
||||
i18nKey: 'route.system_notice'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_oss',
|
||||
path: '/system/oss',
|
||||
component: 'view.system_oss',
|
||||
meta: {
|
||||
title: 'system_oss',
|
||||
i18nKey: 'route.system_oss'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_oss-config',
|
||||
path: '/system/oss-config',
|
||||
component: 'view.system_oss-config',
|
||||
meta: {
|
||||
title: 'system_oss-config',
|
||||
i18nKey: 'route.system_oss-config',
|
||||
constant: true,
|
||||
hideInMenu: true,
|
||||
icon: 'hugeicons:configuration-01'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_post',
|
||||
path: '/system/post',
|
||||
component: 'view.system_post',
|
||||
meta: {
|
||||
title: 'system_post',
|
||||
i18nKey: 'route.system_post'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_role',
|
||||
path: '/system/role',
|
||||
component: 'view.system_role',
|
||||
meta: {
|
||||
title: 'system_role',
|
||||
i18nKey: 'route.system_role'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_tenant',
|
||||
path: '/system/tenant',
|
||||
component: 'view.system_tenant',
|
||||
meta: {
|
||||
title: 'system_tenant',
|
||||
i18nKey: 'route.system_tenant'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_tenant-package',
|
||||
path: '/system/tenant-package',
|
||||
component: 'view.system_tenant-package',
|
||||
meta: {
|
||||
title: 'system_tenant-package',
|
||||
i18nKey: 'route.system_tenant-package'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system_user',
|
||||
path: '/system/user',
|
||||
component: 'view.system_user',
|
||||
meta: {
|
||||
title: 'system_user',
|
||||
i18nKey: 'route.system_user'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'tool',
|
||||
path: '/tool',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'tool',
|
||||
i18nKey: 'route.tool',
|
||||
localIcon: 'menu-tool',
|
||||
order: 4
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'tool_gen',
|
||||
path: '/tool/gen',
|
||||
component: 'view.tool_gen',
|
||||
meta: {
|
||||
title: 'tool_gen',
|
||||
i18nKey: 'route.tool_gen',
|
||||
localIcon: 'menu-code',
|
||||
order: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'user-center',
|
||||
path: '/user-center',
|
||||
@ -331,125 +145,5 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
icon: 'material-symbols:account-circle-full',
|
||||
hideInMenu: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow',
|
||||
path: '/workflow',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'workflow',
|
||||
i18nKey: 'route.workflow'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'workflow_category',
|
||||
path: '/workflow/category',
|
||||
component: 'view.workflow_category',
|
||||
meta: {
|
||||
title: 'workflow_category',
|
||||
i18nKey: 'route.workflow_category'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_design',
|
||||
path: '/workflow/design',
|
||||
component: 'view.workflow_design',
|
||||
meta: {
|
||||
title: 'workflow_design',
|
||||
i18nKey: 'route.workflow_design'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_leave',
|
||||
path: '/workflow/leave',
|
||||
component: 'view.workflow_leave',
|
||||
meta: {
|
||||
title: 'workflow_leave',
|
||||
i18nKey: 'route.workflow_leave'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_process-definition',
|
||||
path: '/workflow/process-definition',
|
||||
component: 'view.workflow_process-definition',
|
||||
meta: {
|
||||
title: 'workflow_process-definition',
|
||||
i18nKey: 'route.workflow_process-definition'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_process-instance',
|
||||
path: '/workflow/process-instance',
|
||||
component: 'view.workflow_process-instance',
|
||||
meta: {
|
||||
title: 'workflow_process-instance',
|
||||
i18nKey: 'route.workflow_process-instance'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_spel',
|
||||
path: '/workflow/spel',
|
||||
component: 'view.workflow_spel',
|
||||
meta: {
|
||||
title: 'workflow_spel',
|
||||
i18nKey: 'route.workflow_spel'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_task',
|
||||
path: '/workflow/task',
|
||||
meta: {
|
||||
title: 'workflow_task',
|
||||
i18nKey: 'route.workflow_task'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'workflow_task_all-task-waiting',
|
||||
path: '/workflow/task/all-task-waiting',
|
||||
component: 'view.workflow_task_all-task-waiting',
|
||||
meta: {
|
||||
title: 'workflow_task_all-task-waiting',
|
||||
i18nKey: 'route.workflow_task_all-task-waiting'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_task_my-document',
|
||||
path: '/workflow/task/my-document',
|
||||
component: 'view.workflow_task_my-document',
|
||||
meta: {
|
||||
title: 'workflow_task_my-document',
|
||||
i18nKey: 'route.workflow_task_my-document'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_task_task-copy',
|
||||
path: '/workflow/task/task-copy',
|
||||
component: 'view.workflow_task_task-copy',
|
||||
meta: {
|
||||
title: 'workflow_task_task-copy',
|
||||
i18nKey: 'route.workflow_task_task-copy'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_task_task-finish',
|
||||
path: '/workflow/task/task-finish',
|
||||
component: 'view.workflow_task_task-finish',
|
||||
meta: {
|
||||
title: 'workflow_task_task-finish',
|
||||
i18nKey: 'route.workflow_task_task-finish'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'workflow_task_task-waiting',
|
||||
path: '/workflow/task/task-waiting',
|
||||
component: 'view.workflow_task_task-waiting',
|
||||
meta: {
|
||||
title: 'workflow_task_task-waiting',
|
||||
i18nKey: 'route.workflow_task_task-waiting'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
@ -178,40 +178,8 @@ const routeMap: RouteMap = {
|
||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
|
||||
"monitor": "/monitor",
|
||||
"monitor_cache": "/monitor/cache",
|
||||
"monitor_logininfor": "/monitor/logininfor",
|
||||
"monitor_online": "/monitor/online",
|
||||
"monitor_operlog": "/monitor/operlog",
|
||||
"social-callback": "/social-callback",
|
||||
"system": "/system",
|
||||
"system_client": "/system/client",
|
||||
"system_config": "/system/config",
|
||||
"system_dept": "/system/dept",
|
||||
"system_dict": "/system/dict",
|
||||
"system_menu": "/system/menu",
|
||||
"system_notice": "/system/notice",
|
||||
"system_oss": "/system/oss",
|
||||
"system_oss-config": "/system/oss-config",
|
||||
"system_post": "/system/post",
|
||||
"system_role": "/system/role",
|
||||
"system_tenant": "/system/tenant",
|
||||
"system_tenant-package": "/system/tenant-package",
|
||||
"system_user": "/system/user",
|
||||
"tool": "/tool",
|
||||
"tool_gen": "/tool/gen",
|
||||
"user-center": "/user-center",
|
||||
"workflow": "/workflow",
|
||||
"workflow_category": "/workflow/category",
|
||||
"workflow_design": "/workflow/design",
|
||||
"workflow_leave": "/workflow/leave",
|
||||
"workflow_process-definition": "/workflow/process-definition",
|
||||
"workflow_process-instance": "/workflow/process-instance",
|
||||
"workflow_spel": "/workflow/spel",
|
||||
"workflow_task": "/workflow/task",
|
||||
"workflow_task_all-task-waiting": "/workflow/task/all-task-waiting",
|
||||
"workflow_task_my-document": "/workflow/task/my-document",
|
||||
"workflow_task_task-copy": "/workflow/task/task-copy",
|
||||
"workflow_task_task-finish": "/workflow/task/task-finish",
|
||||
"workflow_task_task-waiting": "/workflow/task/task-waiting"
|
||||
"user-center": "/user-center"
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,53 +0,0 @@
|
||||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取测试树列表 */
|
||||
export function fetchGetCategoryList(params?: Api.Workflow.WorkflowCategorySearchParams) {
|
||||
return request<Api.Workflow.WorkflowCategoryList>({
|
||||
url: '/workflow/category/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** 新增测试树 */
|
||||
export function fetchCreateCategory(data: Api.Workflow.WorkflowCategoryOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/category',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改测试树 */
|
||||
export function fetchUpdateCategory(data: Api.Workflow.WorkflowCategoryOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/category',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除分类 */
|
||||
export function fetchDeleteCategory(id: CommonType.IdType) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/category/${id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/** 导出工作流分类 */
|
||||
export function fetchExportCategory(params?: Api.Workflow.WorkflowCategorySearchParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/category/export',
|
||||
method: 'post',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取分类树 */
|
||||
export function fetchGetCategoryTree() {
|
||||
return request<Api.Common.CommonTreeRecord>({
|
||||
url: '/workflow/category/categoryTree',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取流程定义列表 */
|
||||
export function fetchGetDefinitionList(params?: Api.Workflow.DefinitionSearchParams) {
|
||||
return request<Api.Workflow.DefinitionList>({
|
||||
url: '/workflow/definition/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取未发布流程定义列表 */
|
||||
export function fetchGetUnPublishDefinitionList(params?: Api.Workflow.DefinitionSearchParams) {
|
||||
return request<Api.Workflow.DefinitionList>({
|
||||
url: '/workflow/definition/unPublishList',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** 新增流程定义 */
|
||||
export function fetchCreateDefinition(data: Api.Workflow.DefinitionOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/definition',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改流程定义 */
|
||||
export function fetchUpdateDefinition(data: Api.Workflow.DefinitionOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/definition',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除流程定义 */
|
||||
export function fetchBatchDeleteDefinition(ids: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/definition/${ids.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/** 激活/挂起流程定义 */
|
||||
export function fetchActiveDefinition(id: CommonType.IdType, active: boolean) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/definition/active/${id}`,
|
||||
method: 'put',
|
||||
params: {
|
||||
active
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 发布流程定义 */
|
||||
export function fetchPublishDefinition(id: CommonType.IdType) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/definition/publish/${id}`,
|
||||
method: 'put'
|
||||
});
|
||||
}
|
||||
|
||||
/** 复制流程定义 */
|
||||
export function fetchCopyDefinition(id: CommonType.IdType) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/definition/copy/${id}`,
|
||||
method: 'post'
|
||||
});
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export * from './category';
|
||||
export * from './leave';
|
||||
export * from './instance';
|
||||
export * from './definition';
|
||||
export * from './task';
|
@ -1,61 +0,0 @@
|
||||
import { request } from '@/service/request';
|
||||
|
||||
/** 查询正在运行的流程实例列表 */
|
||||
export function fetchGetRunningInstanceList(params: Api.Workflow.InstanceSearchParams) {
|
||||
return request<Api.Workflow.InstanceList>({
|
||||
url: '/workflow/instance/pageByRunning',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** 查询已结束的流程实例列表 */
|
||||
export function fetchGetFinishedInstanceList(params: Api.Workflow.InstanceSearchParams) {
|
||||
return request<Api.Workflow.InstanceList>({
|
||||
url: '/workflow/instance/pageByFinish',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** 按照实例id删除流程实例 */
|
||||
export function fetchBatchDeleteInstance(instanceIds: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/instance/deleteByInstanceIds/${instanceIds.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/** 流程作废操作 */
|
||||
export function fetchFlowInvalidOperate(data: Api.Workflow.FlowInvalidOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/instance/invalid',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取流程记录 */
|
||||
export function fetchGetFlowHisTaskList(businessId: CommonType.IdType) {
|
||||
return request<Api.Workflow.InstanceIdWithHisTask>({
|
||||
url: `/workflow/instance/flowHisTaskList/${businessId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
/** 流程作废操作 */
|
||||
export function fetchCancelProcessApply(data: Api.Workflow.CancelProcessApplyParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/instance/cancelProcessApply',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取我的待办 */
|
||||
export function fetchGetMyDocument(data: Api.Workflow.InstanceSearchParams) {
|
||||
return request<Api.Workflow.InstanceList>({
|
||||
url: '/workflow/instance/pageByCurrent',
|
||||
method: 'get',
|
||||
params: data
|
||||
});
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取请假申请列表 */
|
||||
export function fetchGetLeaveList(params?: Api.Workflow.LeaveSearchParams) {
|
||||
return request<Api.Workflow.LeaveList>({
|
||||
url: '/workflow/leave/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
/** 获取请假申请详情 */
|
||||
export function fetchGetLeaveDetail(id: CommonType.IdType) {
|
||||
return request<Api.Workflow.LeaveDetail>({
|
||||
url: `/workflow/leave/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/** 新增请假申请 */
|
||||
export function fetchCreateLeave(data: Api.Workflow.LeaveOperateParams) {
|
||||
return request<Api.Workflow.Leave>({
|
||||
url: '/workflow/leave',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改请假申请 */
|
||||
export function fetchUpdateLeave(data: Api.Workflow.LeaveOperateParams) {
|
||||
return request<Api.Workflow.Leave>({
|
||||
url: '/workflow/leave',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除请假申请 */
|
||||
export function fetchBatchDeleteLeave(ids: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/leave/${ids.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/** 撤销请假申请 */
|
||||
export function fetchCancelLeave(id: CommonType.IdType) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/leave/cancel/${id}`,
|
||||
method: 'put'
|
||||
});
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import { request } from '@/service/request';
|
||||
|
||||
/** 获取流程spel达式定义列表 */
|
||||
export function fetchGetSpelList(params?: Api.Workflow.SpelSearchParams) {
|
||||
return request<Api.Workflow.SpelList>({
|
||||
url: '/workflow/spel/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** 新增流程spel达式定义 */
|
||||
export function fetchCreateSpel(data: Api.Workflow.SpelOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/spel',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 修改流程spel达式定义 */
|
||||
export function fetchUpdateSpel(data: Api.Workflow.SpelOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/spel',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除流程spel达式定义 */
|
||||
export function fetchBatchDeleteSpel(ids: CommonType.IdType[]) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/spel/${ids.join(',')}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
import { request } from '@/service/request';
|
||||
|
||||
/** 启动任务 */
|
||||
export function fetchStartWorkflow(data: Api.Workflow.StartWorkflowOperateParams) {
|
||||
return request<Api.Workflow.StartWorkflowResult>({
|
||||
url: '/workflow/task/startWorkFlow',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取任务 */
|
||||
export function fetchGetTask(taskId: CommonType.IdType) {
|
||||
return request<Api.Workflow.Task>({
|
||||
url: `/workflow/task/getTask/${taskId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取任务下一个节点 */
|
||||
export function fetchGetkNextNode(data: Api.Workflow.TaskNextNodeSearchParams) {
|
||||
return request<Api.Workflow.FlowNodeList>({
|
||||
url: '/workflow/task/getNextNodeList',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 完成任务 */
|
||||
export function fetchCompleteTask(data: Api.Workflow.CompleteTaskOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/task/completeTask',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取所有待办任务 */
|
||||
export function fetchGetAllWaitingTask(data: Api.Workflow.TaskSearchParams) {
|
||||
return request<Api.Workflow.TaskList>({
|
||||
url: '/workflow/task/pageByAllTaskWait',
|
||||
method: 'get',
|
||||
params: data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取所有已办任务 */
|
||||
export function fetchGetAllFinishedTask(data: Api.Workflow.TaskSearchParams) {
|
||||
return request<Api.Workflow.HisTaskList>({
|
||||
url: '/workflow/task/pageByAllTaskFinish',
|
||||
method: 'get',
|
||||
params: data
|
||||
});
|
||||
}
|
||||
|
||||
/** 任务操作 */
|
||||
export function fetchTaskOperate(data: Api.Workflow.TaskOperateParams, operateType: Api.Workflow.TaskOperateType) {
|
||||
return request<boolean>({
|
||||
url: `/workflow/task/taskOperation/${operateType}`,
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 终止任务 */
|
||||
export function fetchTerminateTask(data: Api.Workflow.TerminateTaskOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/task/terminationTask',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取当前任务所有人员 */
|
||||
export function fetchGetCurrentTaskAllUser(taskId: CommonType.IdType) {
|
||||
return request<Api.System.User[]>({
|
||||
url: `/workflow/task/currentTaskAllUser/${taskId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取我的待办 */
|
||||
export function fetchGetTaskWaitList(data: Api.Workflow.TaskSearchParams) {
|
||||
return request<Api.Workflow.TaskList>({
|
||||
url: '/workflow/task/pageByTaskWait',
|
||||
method: 'get',
|
||||
params: data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取可驳回节点 */
|
||||
export function fetchGetBackNode(definitionId: CommonType.IdType, nodeCode: string) {
|
||||
return request<Api.Workflow.FlowNodeList>({
|
||||
url: `/workflow/task/getBackTaskNode/${definitionId}/${nodeCode}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/** 驳回任务 */
|
||||
export function fetchBackTask(data: Api.Workflow.BackOperateParams) {
|
||||
return request<boolean>({
|
||||
url: '/workflow/task/backProcess',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取我的已办任务 */
|
||||
export function fetchGetFinishedTask(data: Api.Workflow.TaskSearchParams) {
|
||||
return request<Api.Workflow.HisTaskList>({
|
||||
url: '/workflow/task/pageByTaskFinish',
|
||||
method: 'get',
|
||||
params: data
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取我的抄送任务 */
|
||||
export function fetchGetCopyTask(data: Api.Workflow.TaskSearchParams) {
|
||||
return request<Api.Workflow.TaskList>({
|
||||
url: '/workflow/task/pageByTaskCopy',
|
||||
method: 'get',
|
||||
params: data
|
||||
});
|
||||
}
|
@ -12,7 +12,7 @@ const encryptHeader = 'encrypt-key';
|
||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||
|
||||
export const request = createFlatRequest<App.Service.Response, RequestInstanceState>(
|
||||
export const request = createFlatRequest(
|
||||
{
|
||||
baseURL,
|
||||
'axios-retry': {
|
||||
@ -20,6 +20,39 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
||||
}
|
||||
},
|
||||
{
|
||||
defaultState: {
|
||||
errMsgStack: [],
|
||||
refreshTokenPromise: null
|
||||
} as RequestInstanceState,
|
||||
transform(response: AxiosResponse<App.Service.Response<any>>) {
|
||||
if (import.meta.env.VITE_APP_ENCRYPT === 'Y') {
|
||||
// 加密后的 AES 秘钥
|
||||
const keyStr = response.headers[encryptHeader];
|
||||
// 加密
|
||||
if (keyStr && keyStr !== '') {
|
||||
const data = String(response.data);
|
||||
// 请求体 AES 解密
|
||||
const base64Str = decrypt(keyStr);
|
||||
// base64 解码 得到请求头的 AES 秘钥
|
||||
const aesKey = decryptBase64(base64Str.toString());
|
||||
// aesKey 解码 data
|
||||
const decryptData = decryptWithAes(data, aesKey);
|
||||
// 将结果 (得到的是 JSON 字符串) 转为 JSON
|
||||
response.data = JSON.parse(decryptData);
|
||||
}
|
||||
}
|
||||
|
||||
// 二进制数据则直接返回
|
||||
if (response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer') {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
if (response.data.rows) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
return response.data.data;
|
||||
},
|
||||
async onRequest(config) {
|
||||
const isToken = config.headers?.isToken === false;
|
||||
// set token
|
||||
@ -77,7 +110,7 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
||||
// 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(',') || [];
|
||||
if (modalLogoutCodes.includes(responseCode) && isLogin) {
|
||||
const isExist = request.state.errMsgStack && request.state.errMsgStack.includes(response.data.msg);
|
||||
const isExist = request.state.errMsgStack?.includes(response.data.msg);
|
||||
if (isExist) {
|
||||
return null;
|
||||
}
|
||||
@ -85,23 +118,24 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
||||
logoutAndCleanup();
|
||||
return null;
|
||||
}
|
||||
|
||||
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
|
||||
// prevent the user from refreshing the page
|
||||
window.addEventListener('beforeunload', handleLogout);
|
||||
|
||||
window.$dialog?.warning({
|
||||
title: '系统提示',
|
||||
content: '登录状态已过期,您可以继续留在该页面,或者重新登录',
|
||||
content: '登录状态已过期,请重新登录',
|
||||
positiveText: '重新登录',
|
||||
negativeText: '取消',
|
||||
maskClosable: false,
|
||||
closeOnEsc: false,
|
||||
onAfterEnter() {
|
||||
// prevent the user from refreshing the page
|
||||
window.addEventListener('beforeunload', handleLogout);
|
||||
},
|
||||
onPositiveClick() {
|
||||
logoutAndCleanup();
|
||||
},
|
||||
onClose() {
|
||||
window.removeEventListener('beforeunload', handleLogout);
|
||||
request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg);
|
||||
logoutAndCleanup();
|
||||
}
|
||||
});
|
||||
request.cancelAllRequest();
|
||||
@ -123,35 +157,6 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
||||
|
||||
return null;
|
||||
},
|
||||
transformBackendResponse(response) {
|
||||
if (import.meta.env.VITE_APP_ENCRYPT === 'Y') {
|
||||
// 加密后的 AES 秘钥
|
||||
const keyStr = response.headers[encryptHeader];
|
||||
// 加密
|
||||
if (keyStr && keyStr !== '') {
|
||||
const data = String(response.data);
|
||||
// 请求体 AES 解密
|
||||
const base64Str = decrypt(keyStr);
|
||||
// base64 解码 得到请求头的 AES 秘钥
|
||||
const aesKey = decryptBase64(base64Str.toString());
|
||||
// aesKey 解码 data
|
||||
const decryptData = decryptWithAes(data, aesKey);
|
||||
// 将结果 (得到的是 JSON 字符串) 转为 JSON
|
||||
response.data = JSON.parse(decryptData);
|
||||
}
|
||||
}
|
||||
|
||||
// 二进制数据则直接返回
|
||||
if (response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer') {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
if (response.data.rows) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
return response.data.data;
|
||||
},
|
||||
onError(error) {
|
||||
// when the request is fail, you can show error message
|
||||
|
||||
|
@ -27,14 +27,14 @@ async function handleRefreshToken() {
|
||||
}
|
||||
|
||||
export async function handleExpiredRequest(state: RequestInstanceState) {
|
||||
if (!state.refreshTokenFn) {
|
||||
state.refreshTokenFn = handleRefreshToken();
|
||||
if (!state.refreshTokenPromise) {
|
||||
state.refreshTokenPromise = handleRefreshToken();
|
||||
}
|
||||
|
||||
const success = await state.refreshTokenFn;
|
||||
const success = await state.refreshTokenPromise;
|
||||
|
||||
setTimeout(() => {
|
||||
state.refreshTokenFn = null;
|
||||
state.refreshTokenPromise = null;
|
||||
}, 1000);
|
||||
|
||||
return success;
|
||||
|
@ -1,6 +1,7 @@
|
||||
export interface RequestInstanceState {
|
||||
/** whether the request is refreshing token */
|
||||
refreshTokenFn: Promise<boolean> | null;
|
||||
/** the promise of refreshing token */
|
||||
refreshTokenPromise: Promise<boolean> | null;
|
||||
/** the request error message stack */
|
||||
errMsgStack: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
@ -67,6 +67,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
if (authRouteMode.value === 'dynamic') {
|
||||
if (route.path === '/' && route.children?.length) {
|
||||
const child = route.children[0];
|
||||
// @ts-expect-error no hidden field
|
||||
child.hidden = route.hidden;
|
||||
parseRouter(child);
|
||||
child.name = Math.random().toString(36).slice(2, 12);
|
||||
Object.assign(route, child);
|
||||
@ -123,7 +125,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
} else if (!isNotNull(route.meta.icon)) {
|
||||
route.meta.icon = defaultIcon;
|
||||
}
|
||||
|
||||
// @ts-expect-error no hidden field
|
||||
route.meta.hideInMenu = route.hidden;
|
||||
if (route.meta.hideInMenu && parent) {
|
||||
@ -388,7 +389,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
}
|
||||
|
||||
async function onRouteSwitchWhenLoggedIn() {
|
||||
// await authStore.initUserInfo();
|
||||
// some global init logic when logged in and switch route
|
||||
}
|
||||
|
||||
async function onRouteSwitchWhenNotLoggedIn() {
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { computed, effectScope, onScopeDispose, ref, toRefs, watch } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { useEventListener, usePreferredColorScheme } from '@vueuse/core';
|
||||
import { useDateFormat, useEventListener, useNow, usePreferredColorScheme } from '@vueuse/core';
|
||||
import { defineStore } from 'pinia';
|
||||
import { getPaletteColorByNumber } from '@sa/color';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { SetupStoreId } from '@/enum';
|
||||
import { useAuthStore } from '../auth';
|
||||
import {
|
||||
addThemeVarsToGlobal,
|
||||
createThemeToken,
|
||||
@ -18,10 +19,14 @@ import {
|
||||
export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
const scope = effectScope();
|
||||
const osTheme = usePreferredColorScheme();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
/** Theme settings */
|
||||
const settings: Ref<App.Theme.ThemeSetting> = ref(initThemeSettings());
|
||||
|
||||
/** Watermark time instance with controls */
|
||||
const { now: watermarkTime, pause: pauseWatermarkTime, resume: resumeWatermarkTime } = useNow({ controls: true });
|
||||
|
||||
/** Dark mode */
|
||||
const darkMode = computed(() => {
|
||||
if (settings.value.themeScheme === 'auto') {
|
||||
@ -57,6 +62,29 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
*/
|
||||
const settingsJson = computed(() => JSON.stringify(settings.value));
|
||||
|
||||
/** Watermark time date formatter */
|
||||
const formattedWatermarkTime = computed(() => {
|
||||
const { watermark } = settings.value;
|
||||
const date = useDateFormat(watermarkTime, watermark.timeFormat);
|
||||
return date.value;
|
||||
});
|
||||
|
||||
/** Watermark content */
|
||||
const watermarkContent = computed(() => {
|
||||
const { watermark } = settings.value;
|
||||
let content = watermark.text;
|
||||
|
||||
if (watermark.enableUserName && authStore.userInfo.user?.userName) {
|
||||
content = `${authStore.userInfo.user.userName}@${content}`;
|
||||
}
|
||||
|
||||
if (watermark.enableTime) {
|
||||
content = `${content} ${formattedWatermarkTime.value}`;
|
||||
}
|
||||
|
||||
return content;
|
||||
});
|
||||
|
||||
/** Reset store */
|
||||
function resetStore() {
|
||||
const themeStore = useThemeStore();
|
||||
@ -144,13 +172,43 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
);
|
||||
addThemeVarsToGlobal(themeTokens, darkThemeTokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set layout reverse horizontal mix
|
||||
* Set watermark enable user name
|
||||
*
|
||||
* @param reverse Reverse horizontal mix
|
||||
* @param enable Whether to enable user name watermark
|
||||
*/
|
||||
function setLayoutReverseHorizontalMix(reverse: boolean) {
|
||||
settings.value.layout.reverseHorizontalMix = reverse;
|
||||
function setWatermarkEnableUserName(enable: boolean) {
|
||||
settings.value.watermark.enableUserName = enable;
|
||||
|
||||
if (enable) {
|
||||
// settings.value.watermark.enableTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set watermark enable time
|
||||
*
|
||||
* @param enable Whether to enable time watermark
|
||||
*/
|
||||
function setWatermarkEnableTime(enable: boolean) {
|
||||
settings.value.watermark.enableTime = enable;
|
||||
|
||||
if (enable) {
|
||||
// settings.value.watermark.enableUserName = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Only run timer when watermark is visible and time display is enabled */
|
||||
function updateWatermarkTimer() {
|
||||
const { watermark } = settings.value;
|
||||
const shouldRunTimer = watermark.visible && watermark.enableTime;
|
||||
|
||||
if (shouldRunTimer) {
|
||||
resumeWatermarkTime();
|
||||
} else {
|
||||
pauseWatermarkTime();
|
||||
}
|
||||
}
|
||||
|
||||
/** Cache theme settings */
|
||||
@ -196,6 +254,15 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// watch watermark settings to control timer
|
||||
watch(
|
||||
() => [settings.value.watermark.visible, settings.value.watermark.enableTime],
|
||||
() => {
|
||||
updateWatermarkTimer();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
});
|
||||
|
||||
/** On scope dispose */
|
||||
@ -209,6 +276,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
themeColors,
|
||||
naiveTheme,
|
||||
settingsJson,
|
||||
watermarkContent,
|
||||
setGrayscale,
|
||||
setColourWeakness,
|
||||
resetStore,
|
||||
@ -216,6 +284,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
toggleThemeScheme,
|
||||
updateThemeColors,
|
||||
setThemeLayout,
|
||||
setLayoutReverseHorizontalMix
|
||||
setWatermarkEnableUserName,
|
||||
setWatermarkEnableTime
|
||||
};
|
||||
});
|
||||
|
@ -13,6 +13,13 @@
|
||||
border-color: var(--un-default-border-color, #e5e7eb); /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* [Naive UI] Fix the icon size in the image preview toolbar
|
||||
*/
|
||||
.n-image-preview-toolbar .n-base-icon {
|
||||
box-sizing: unset !important;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
|
@ -15,8 +15,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
||||
resetCacheStrategy: 'close',
|
||||
layout: {
|
||||
mode: 'vertical',
|
||||
scrollMode: 'content',
|
||||
reverseHorizontalMix: false
|
||||
scrollMode: 'content'
|
||||
},
|
||||
page: {
|
||||
animate: true,
|
||||
@ -58,8 +57,10 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
||||
},
|
||||
watermark: {
|
||||
visible: import.meta.env.VITE_WATERMARK === 'Y',
|
||||
text: 'RuoYi-Vue-Plus',
|
||||
enableUserName: false
|
||||
text: 'RuoYi-Plus-Soybean',
|
||||
enableUserName: true,
|
||||
enableTime: false,
|
||||
timeFormat: 'YYYY-MM-DD HH:mm'
|
||||
},
|
||||
table: {
|
||||
bordered: true,
|
||||
|
127
src/typings/api/api.d.ts
vendored
127
src/typings/api/api.d.ts
vendored
@ -85,131 +85,4 @@ declare namespace Api {
|
||||
children: CommonTreeRecord[];
|
||||
}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* namespace Auth
|
||||
*
|
||||
* backend api module: "auth"
|
||||
*/
|
||||
namespace Auth {
|
||||
/** base login form */
|
||||
interface LoginForm {
|
||||
/** 客户端 ID */
|
||||
clientId?: string;
|
||||
/** 授权类型 */
|
||||
grantType?: string;
|
||||
/** 租户ID */
|
||||
tenantId?: string;
|
||||
/** 验证码 */
|
||||
code?: string;
|
||||
/** 唯一标识 */
|
||||
uuid?: string;
|
||||
}
|
||||
|
||||
/** password login form */
|
||||
interface PwdLoginForm extends LoginForm {
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
/** 密码 */
|
||||
password?: string;
|
||||
}
|
||||
|
||||
/** social login form */
|
||||
interface SocialLoginForm extends LoginForm {
|
||||
/** 授权码 */
|
||||
socialCode?: string;
|
||||
/** 授权状态 */
|
||||
socialState?: string;
|
||||
/** 来源 */
|
||||
source?: string;
|
||||
}
|
||||
|
||||
/** register form */
|
||||
interface RegisterForm extends LoginForm {
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
/** 密码 */
|
||||
password?: string;
|
||||
/** 确认密码 */
|
||||
confirmPassword?: string;
|
||||
/** 用户类型 */
|
||||
userType?: string;
|
||||
}
|
||||
|
||||
/** login token data */
|
||||
interface LoginToken {
|
||||
/** 授权令牌 */
|
||||
access_token?: string;
|
||||
/** 应用id */
|
||||
client_id?: string;
|
||||
/** 授权令牌 access_token 的有效期 */
|
||||
expire_in?: number;
|
||||
/** 用户 openid */
|
||||
openid?: string;
|
||||
/** 刷新令牌 refresh_token 的有效期 */
|
||||
refresh_expire_in?: number;
|
||||
/** 刷新令牌 */
|
||||
refresh_token?: string;
|
||||
/** 令牌权限 */
|
||||
scope?: string;
|
||||
}
|
||||
|
||||
/** userinfo */
|
||||
interface UserInfo {
|
||||
/** 用户信息 */
|
||||
user?: Api.System.User & {
|
||||
/** 所属角色 */
|
||||
roles: Api.System.Role[];
|
||||
};
|
||||
/** 角色列表 */
|
||||
roles: string[];
|
||||
/** 菜单权限 */
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
/** tenant */
|
||||
interface Tenant {
|
||||
/** 企业名称 */
|
||||
companyName: string;
|
||||
/** 域名 */
|
||||
domain: string;
|
||||
/** 租户编号 */
|
||||
tenantId: string;
|
||||
}
|
||||
|
||||
/** login tenant */
|
||||
interface LoginTenant {
|
||||
/** 租户开关 */
|
||||
tenantEnabled: boolean;
|
||||
/** 租户列表 */
|
||||
voList: Tenant[];
|
||||
}
|
||||
|
||||
interface CaptchaCode {
|
||||
/** 是否开启验证码 */
|
||||
captchaEnabled: boolean;
|
||||
/** 唯一标识 */
|
||||
uuid?: string;
|
||||
/** 验证码图片 */
|
||||
img?: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* namespace Route
|
||||
*
|
||||
* backend api module: "route"
|
||||
*/
|
||||
namespace Route {
|
||||
type ElegantConstRoute = import('@elegant-router/types').ElegantConstRoute;
|
||||
|
||||
interface MenuRoute extends ElegantConstRoute {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface UserRoute {
|
||||
routes: MenuRoute[];
|
||||
home: import('@elegant-router/types').LastLevelRouteKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
110
src/typings/api/auth.d.ts
vendored
Normal file
110
src/typings/api/auth.d.ts
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Auth
|
||||
*
|
||||
* backend api module: "auth"
|
||||
*/
|
||||
namespace Auth {
|
||||
/** base login form */
|
||||
interface LoginForm {
|
||||
/** 客户端 ID */
|
||||
clientId?: string;
|
||||
/** 授权类型 */
|
||||
grantType?: string;
|
||||
/** 租户ID */
|
||||
tenantId?: string;
|
||||
/** 验证码 */
|
||||
code?: string;
|
||||
/** 唯一标识 */
|
||||
uuid?: string;
|
||||
}
|
||||
|
||||
/** password login form */
|
||||
interface PwdLoginForm extends LoginForm {
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
/** 密码 */
|
||||
password?: string;
|
||||
}
|
||||
|
||||
/** social login form */
|
||||
interface SocialLoginForm extends LoginForm {
|
||||
/** 授权码 */
|
||||
socialCode?: string;
|
||||
/** 授权状态 */
|
||||
socialState?: string;
|
||||
/** 来源 */
|
||||
source?: string;
|
||||
}
|
||||
|
||||
/** register form */
|
||||
interface RegisterForm extends LoginForm {
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
/** 密码 */
|
||||
password?: string;
|
||||
/** 确认密码 */
|
||||
confirmPassword?: string;
|
||||
/** 用户类型 */
|
||||
userType?: string;
|
||||
}
|
||||
|
||||
/** login token data */
|
||||
interface LoginToken {
|
||||
/** 授权令牌 */
|
||||
access_token?: string;
|
||||
/** 应用id */
|
||||
client_id?: string;
|
||||
/** 授权令牌 access_token 的有效期 */
|
||||
expire_in?: number;
|
||||
/** 用户 openid */
|
||||
openid?: string;
|
||||
/** 刷新令牌 refresh_token 的有效期 */
|
||||
refresh_expire_in?: number;
|
||||
/** 刷新令牌 */
|
||||
refresh_token?: string;
|
||||
/** 令牌权限 */
|
||||
scope?: string;
|
||||
}
|
||||
|
||||
/** userinfo */
|
||||
interface UserInfo {
|
||||
/** 用户信息 */
|
||||
user?: Api.System.User & {
|
||||
/** 所属角色 */
|
||||
roles: Api.System.Role[];
|
||||
};
|
||||
/** 角色列表 */
|
||||
roles: string[];
|
||||
/** 菜单权限 */
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
/** tenant */
|
||||
interface Tenant {
|
||||
/** 企业名称 */
|
||||
companyName: string;
|
||||
/** 域名 */
|
||||
domain: string;
|
||||
/** 租户编号 */
|
||||
tenantId: string;
|
||||
}
|
||||
|
||||
/** login tenant */
|
||||
interface LoginTenant {
|
||||
/** 租户开关 */
|
||||
tenantEnabled: boolean;
|
||||
/** 租户列表 */
|
||||
voList: Tenant[];
|
||||
}
|
||||
|
||||
interface CaptchaCode {
|
||||
/** 是否开启验证码 */
|
||||
captchaEnabled: boolean;
|
||||
/** 唯一标识 */
|
||||
uuid?: string;
|
||||
/** 验证码图片 */
|
||||
img?: string;
|
||||
}
|
||||
}
|
||||
}
|
19
src/typings/api/route.d.ts
vendored
Normal file
19
src/typings/api/route.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Route
|
||||
*
|
||||
* backend api module: "route"
|
||||
*/
|
||||
namespace Route {
|
||||
type ElegantConstRoute = import('@elegant-router/types').ElegantConstRoute;
|
||||
|
||||
interface MenuRoute extends ElegantConstRoute {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface UserRoute {
|
||||
routes: MenuRoute[];
|
||||
home: import('@elegant-router/types').LastLevelRouteKey;
|
||||
}
|
||||
}
|
||||
}
|
3
src/typings/api/system.api.d.ts
vendored
3
src/typings/api/system.api.d.ts
vendored
@ -128,7 +128,6 @@ declare namespace Api {
|
||||
type UserSearchParams = CommonType.RecordNullable<
|
||||
Pick<User, 'deptId' | 'userName' | 'nickName' | 'phonenumber' | 'status'> & {
|
||||
roleId: CommonType.IdType;
|
||||
userIds: string;
|
||||
} & Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
@ -164,6 +163,8 @@ declare namespace Api {
|
||||
postIds: string[];
|
||||
/** user role ids */
|
||||
roleIds: string[];
|
||||
/** roles */
|
||||
roles: Role[];
|
||||
};
|
||||
|
||||
/** user list */
|
||||
|
550
src/typings/api/workflow.api.d.ts
vendored
550
src/typings/api/workflow.api.d.ts
vendored
@ -1,550 +0,0 @@
|
||||
/**
|
||||
* Namespace Api
|
||||
*
|
||||
* All backend api type
|
||||
*/
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Workflow
|
||||
*
|
||||
* backend api module: "Workflow"
|
||||
*/
|
||||
namespace Workflow {
|
||||
/** 业务流程状态 */
|
||||
type BusinessStatus = 'cancel' | 'draft' | 'waiting' | 'finish' | 'invalid' | 'back' | 'termination';
|
||||
|
||||
/** 流程类型 */
|
||||
type FlowCodeType = 'leave1' | 'leave2' | 'leave3' | 'leave4' | 'leave5' | 'leave6';
|
||||
/** 请假状态 */
|
||||
type LeaveType = '1' | '2' | '3' | '4';
|
||||
|
||||
/** leave */
|
||||
type Leave = Common.CommonRecord<{
|
||||
/** id */
|
||||
id: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 请假类型 */
|
||||
leaveType: LeaveType;
|
||||
/** 开始时间 */
|
||||
startDate: string;
|
||||
/** 结束时间 */
|
||||
endDate: string;
|
||||
/** 请假天数 */
|
||||
leaveDays: number;
|
||||
/** 请假原因 */
|
||||
remark: string;
|
||||
/** 状态 */
|
||||
status: BusinessStatus;
|
||||
}>;
|
||||
|
||||
/** leave search params */
|
||||
type LeaveSearchParams = CommonType.RecordNullable<
|
||||
{ startLeaveDays: number; endLeaveDays: number } & Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** leave operate params */
|
||||
type LeaveOperateParams = CommonType.RecordNullable<
|
||||
Pick<Api.Workflow.Leave, 'id' | 'leaveType' | 'startDate' | 'endDate' | 'leaveDays' | 'remark'>
|
||||
>;
|
||||
|
||||
/** leave detail */
|
||||
type LeaveDetail = CommonType.RecordNullable<
|
||||
Pick<Api.Workflow.Leave, 'id' | 'leaveType' | 'startDate' | 'endDate' | 'leaveDays' | 'remark' | 'status'>
|
||||
>;
|
||||
|
||||
/** leave list */
|
||||
type LeaveList = Api.Common.PaginatingQueryRecord<Leave>;
|
||||
/** 工作流分类 */
|
||||
type WorkflowCategory = Common.CommonRecord<{
|
||||
/** 主键 */
|
||||
categoryId: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 分类名称 */
|
||||
categoryName: string;
|
||||
/** 父级ID */
|
||||
parentId: CommonType.IdType;
|
||||
/** 祖级列表 */
|
||||
ancestors: string;
|
||||
/** 排序号 */
|
||||
orderNum: number;
|
||||
/** 删除标志 */
|
||||
delFlag: number;
|
||||
}>;
|
||||
|
||||
/** 工作流分类搜索参数 */
|
||||
type WorkflowCategorySearchParams = CommonType.RecordNullable<
|
||||
Pick<WorkflowCategory, 'categoryName'> & Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** 工作流分类操作参数 */
|
||||
type WorkflowCategoryOperateParams = CommonType.RecordNullable<
|
||||
Pick<WorkflowCategory, 'categoryId' | 'categoryName' | 'parentId' | 'orderNum'>
|
||||
>;
|
||||
|
||||
/** 工作流分类列表 */
|
||||
type WorkflowCategoryList = WorkflowCategory[];
|
||||
|
||||
/** spel */
|
||||
type Spel = Common.CommonRecord<{
|
||||
/** 主键id */
|
||||
id: CommonType.IdType;
|
||||
/** 组件名称 */
|
||||
componentName: string;
|
||||
/** 方法名 */
|
||||
methodName: string;
|
||||
/** 参数 */
|
||||
methodParams: string;
|
||||
/** spel表达式 */
|
||||
viewSpel: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
/** 状态 */
|
||||
status: string;
|
||||
/** 删除标志 */
|
||||
delFlag: string;
|
||||
}>;
|
||||
|
||||
/** spel search params */
|
||||
type SpelSearchParams = CommonType.RecordNullable<
|
||||
Pick<Api.Workflow.Spel, 'componentName' | 'methodName' | 'status'> & Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** spel operate params */
|
||||
type SpelOperateParams = CommonType.RecordNullable<
|
||||
Pick<Api.Workflow.Spel, 'id' | 'componentName' | 'methodName' | 'methodParams' | 'viewSpel' | 'remark' | 'status'>
|
||||
>;
|
||||
|
||||
/** spel list */
|
||||
type SpelList = Api.Common.PaginatingQueryRecord<Spel>;
|
||||
|
||||
/** 工作流发布状态 */
|
||||
type WorkflowPublishStatus = 0 | 1 | 9;
|
||||
|
||||
/** definition */
|
||||
type Definition = Common.CommonTenantRecord<{
|
||||
/** 主键id */
|
||||
id: CommonType.IdType;
|
||||
/** 流程编码 */
|
||||
flowCode: string;
|
||||
/** 流程名称 */
|
||||
flowName: string;
|
||||
/** 流程类别 */
|
||||
category: string;
|
||||
/** 流程分类名称 */
|
||||
categoryName: string;
|
||||
/** 流程版本 */
|
||||
version: string;
|
||||
/** 是否发布(0未发布 1已发布 9失效) */
|
||||
isPublish: WorkflowPublishStatus;
|
||||
/** 审批表单是否自定义(Y是 N否) */
|
||||
formCustom: string;
|
||||
/** 审批表单路径 */
|
||||
formPath: string;
|
||||
/** 流程激活状态(0挂起 1激活) */
|
||||
activityStatus: number;
|
||||
/** 监听器类型 */
|
||||
listenerType: string;
|
||||
/** 监听器路径 */
|
||||
listenerPath: string;
|
||||
/** 业务详情 存业务表对象json字符串 */
|
||||
ext: string;
|
||||
/** 删除标志 */
|
||||
delFlag: string;
|
||||
}>;
|
||||
|
||||
/** definition search params */
|
||||
type DefinitionSearchParams = CommonType.RecordNullable<
|
||||
Pick<Api.Workflow.Definition, 'flowCode' | 'flowName' | 'category'> & Api.Common.CommonSearchParams
|
||||
>;
|
||||
|
||||
/** definition operate params */
|
||||
type DefinitionOperateParams = CommonType.RecordNullable<
|
||||
Pick<Api.Workflow.Definition, 'id' | 'flowCode' | 'flowName' | 'category' | 'formPath'>
|
||||
>;
|
||||
|
||||
/** definition list */
|
||||
type DefinitionList = Api.Common.PaginatingQueryRecord<Definition>;
|
||||
|
||||
/** 节点类型 */
|
||||
type WorkflowNodeType = 0 | 1 | 2 | 3 | 4;
|
||||
|
||||
/** 流程激活状态 */
|
||||
type WorkflowActivityStatus = 0 | 1;
|
||||
|
||||
/** 流程实例 */
|
||||
type Instance = Common.CommonRecord<{
|
||||
/** 主键 */
|
||||
id: CommonType.IdType;
|
||||
/** 租户编号 */
|
||||
tenantId: CommonType.IdType;
|
||||
/** 分类ID */
|
||||
category: CommonType.IdType;
|
||||
/** 分类名称 */
|
||||
categoryName: string;
|
||||
/** 流程定义ID */
|
||||
definitionId: CommonType.IdType;
|
||||
/** 流程定义名称 */
|
||||
flowName: string;
|
||||
/** 流程定义编码 */
|
||||
flowCode: string;
|
||||
/** 业务ID */
|
||||
businessId: CommonType.IdType;
|
||||
/** 节点类型 */
|
||||
nodeType: WorkflowNodeType;
|
||||
/** 节点编码 */
|
||||
nodeCode: string;
|
||||
/** 节点名称 */
|
||||
nodeName: string;
|
||||
/** 变量 */
|
||||
variable: string;
|
||||
/** 流程状态 */
|
||||
flowStatus: string;
|
||||
/** 流程状态名称 */
|
||||
flowStatusName: string;
|
||||
/** 流程激活状态 */
|
||||
activityStatus: WorkflowActivityStatus;
|
||||
/** 审批表单是否自定义 */
|
||||
formCustom: Api.Common.YesOrNoStatus;
|
||||
/** 审批表单路径 */
|
||||
formPath: string;
|
||||
/** 扩展字段 */
|
||||
ext: string;
|
||||
/** 流程定义版本 */
|
||||
version: string;
|
||||
/** 创建者 */
|
||||
createBy: string;
|
||||
/** 创建者名称 */
|
||||
createByName: string;
|
||||
/** 创建时间 */
|
||||
createTime: string;
|
||||
|
||||
/** 更新时间 */
|
||||
updateTime: string;
|
||||
/** 删除标志 */
|
||||
delFlag: number;
|
||||
}>;
|
||||
|
||||
/** 流程实例搜索参数 */
|
||||
type InstanceSearchParams = CommonType.RecordNullable<
|
||||
Pick<Instance, 'flowName' | 'flowCode' | 'businessId' | 'category' | 'nodeName'> &
|
||||
Api.Common.CommonSearchParams & {
|
||||
startUserId: CommonType.IdType;
|
||||
createByIds: CommonType.IdType[];
|
||||
}
|
||||
>;
|
||||
/** 流程实例列表 */
|
||||
type InstanceList = Common.PaginatingQueryRecord<Instance>;
|
||||
|
||||
/** 流程作废操作参数 */
|
||||
type FlowInvalidOperateParams = CommonType.RecordNullable<{
|
||||
/** 主键 */
|
||||
id: CommonType.IdType;
|
||||
/** 作废原因 */
|
||||
comment: string;
|
||||
}>;
|
||||
|
||||
/** 流程撤销操作参数 */
|
||||
type CancelProcessApplyParams = CommonType.RecordNullable<{
|
||||
/** 主键 */
|
||||
businessId: CommonType.IdType;
|
||||
/** 撤销原因 */
|
||||
message: string;
|
||||
}>;
|
||||
|
||||
/** 启动流程操作参数 */
|
||||
type StartWorkflowOperateParams = CommonType.RecordNullable<{
|
||||
/** 流程定义ID */
|
||||
flowCode: string;
|
||||
/** 业务ID */
|
||||
businessId: CommonType.IdType;
|
||||
/** 变量 */
|
||||
variables: { [key: string]: any };
|
||||
}>;
|
||||
|
||||
/** 启动流程结果 */
|
||||
type StartWorkflowResult = CommonType.RecordNullable<{
|
||||
/** 流程实例ID */
|
||||
instanceId: CommonType.IdType;
|
||||
/** 任务ID */
|
||||
taskId: CommonType.IdType;
|
||||
}>;
|
||||
|
||||
/** 抄送人 */
|
||||
type FlowCopy = CommonType.RecordNullable<{
|
||||
/** 用户ID */
|
||||
userId: CommonType.IdType;
|
||||
/** 用户名称 */
|
||||
userName: string;
|
||||
}>;
|
||||
/** 按钮权限 */
|
||||
type ButtonPermission = CommonType.RecordNullable<{
|
||||
/** 唯一编码 */
|
||||
code: CommonType.IdType;
|
||||
/** 选项值 */
|
||||
value: string;
|
||||
/** 是否显示 */
|
||||
show: boolean;
|
||||
}>;
|
||||
|
||||
type TaskOrHisTask = Task | HisTask;
|
||||
|
||||
/** 任务详情 */
|
||||
type Task = Common.CommonTenantRecord<{
|
||||
/** 任务ID */
|
||||
id: CommonType.IdType;
|
||||
/** 删除标志 */
|
||||
delFlag: number;
|
||||
/** 流程定义ID */
|
||||
definitionId: CommonType.IdType;
|
||||
/** 流程实例ID */
|
||||
instanceId: CommonType.IdType;
|
||||
/** 业务ID */
|
||||
businessId: CommonType.IdType;
|
||||
/** 节点编码 */
|
||||
nodeCode: string;
|
||||
/** 节点类型 */
|
||||
nodeType: WorkflowNodeType;
|
||||
/** 权限列表 */
|
||||
permissionList: string[];
|
||||
/** 用户列表 */
|
||||
userList: any[];
|
||||
/** 审批表单是否自定义 */
|
||||
formCustom: Api.Common.YesOrNoStatus;
|
||||
/** 审批表单路径 */
|
||||
formPath: string;
|
||||
/** 流程状态 */
|
||||
flowStatus: string;
|
||||
/** 流程状态名称 */
|
||||
flowStatusName: string;
|
||||
/** 分类ID */
|
||||
category: CommonType.IdType;
|
||||
/** 分类名称 */
|
||||
categoryName: string;
|
||||
/** 办理人类型 */
|
||||
type: string;
|
||||
/** 审批人 */
|
||||
assigneeIds: string;
|
||||
/** 审批人名称 */
|
||||
assigneeNames: string;
|
||||
/** 审批人 */
|
||||
processedBy: string;
|
||||
/** 审批人名称 */
|
||||
processedByName: string;
|
||||
/** 流程签署比例值 大于0为票签,会签 */
|
||||
nodeRatio: string;
|
||||
/** 创建人名称 */
|
||||
createByName: string;
|
||||
/** 是否为申请人节点 */
|
||||
applyNode: string;
|
||||
/** 按钮列表 */
|
||||
buttonList: ButtonPermission[];
|
||||
/** 节点名称 */
|
||||
nodeName: string;
|
||||
/** 流程定义名称 */
|
||||
flowName: string;
|
||||
/** 流程定义编码 */
|
||||
flowCode: string;
|
||||
/** 流程版本号 */
|
||||
version: string;
|
||||
}>;
|
||||
|
||||
/** 任务列表 */
|
||||
type TaskList = Common.PaginatingQueryRecord<Task>;
|
||||
|
||||
/** 任务操作类型 */
|
||||
type TaskOperateType = 'delegateTask' | 'transferTask' | 'addSignature' | 'reductionSignature' | 'stopTask';
|
||||
|
||||
/** 任务操作参数 */
|
||||
type TaskOperateParams = CommonType.RecordNullable<{
|
||||
taskId: CommonType.IdType;
|
||||
userId?: CommonType.IdType;
|
||||
userIds?: CommonType.IdType[];
|
||||
message?: string;
|
||||
}>;
|
||||
|
||||
/** 终止任务 */
|
||||
type TerminateTaskOperateParams = CommonType.RecordNullable<{
|
||||
taskId: CommonType.IdType;
|
||||
comment?: string;
|
||||
}>;
|
||||
|
||||
/** 协作方式 */
|
||||
type CooperateType = 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
|
||||
/** 历史任务 */
|
||||
type HisTask = Common.CommonTenantRecord<{
|
||||
/** 任务ID */
|
||||
id: CommonType.IdType;
|
||||
/** 删除标志 */
|
||||
delFlag: number;
|
||||
/** 流程定义ID */
|
||||
definitionId: CommonType.IdType;
|
||||
/** 流程定义名称 */
|
||||
flowName: string;
|
||||
/** 流程实例ID */
|
||||
instanceId: CommonType.IdType;
|
||||
/** 任务表ID */
|
||||
taskId: CommonType.IdType;
|
||||
/** 协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签) */
|
||||
cooperateType: CooperateType;
|
||||
/** 协作方式名称 */
|
||||
cooperateTypeName: string;
|
||||
/** 业务ID */
|
||||
businessId: string;
|
||||
/** 节点编码 */
|
||||
nodeCode: string;
|
||||
/** 节点名称 */
|
||||
nodeName: string;
|
||||
/** 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) */
|
||||
nodeType: WorkflowNodeType;
|
||||
/** 目标节点编码 */
|
||||
targetNodeCode: string;
|
||||
/** 目标节点名称 */
|
||||
targetNodeName: string;
|
||||
/** 审批者 */
|
||||
approver: string;
|
||||
/** 审批者名称 */
|
||||
approveName: string;
|
||||
/** 协作人 */
|
||||
collaborator: string;
|
||||
/** 权限标识 */
|
||||
permissionList: string[];
|
||||
/** 跳转类型(PASS通过 REJECT退回 NONE无动作) */
|
||||
skipType: string;
|
||||
/** 流程状态 */
|
||||
flowStatus: string;
|
||||
/** 任务状态 */
|
||||
flowTaskStatus: string;
|
||||
/** 流程状态名称 */
|
||||
flowStatusName: string;
|
||||
/** 审批意见 */
|
||||
message: string;
|
||||
/** 业务扩展信息(JSON字符串) */
|
||||
ext: string;
|
||||
/** 创建者姓名(申请人名称) */
|
||||
createByName: string;
|
||||
/** 流程分类ID */
|
||||
category: string;
|
||||
/** 流程分类名称 */
|
||||
categoryName: string;
|
||||
/** 审批表单是否自定义(Y是 N否) */
|
||||
formCustom: Api.Common.YesOrNoStatus;
|
||||
/** 表单路径 */
|
||||
formPath: string;
|
||||
/** 流程定义编码 */
|
||||
flowCode: string;
|
||||
/** 流程版本号 */
|
||||
version: string;
|
||||
/** 运行时长 */
|
||||
runDuration: string;
|
||||
/** 附件 */
|
||||
attachmentList: Api.System.Oss[];
|
||||
}>;
|
||||
|
||||
/** 历史任务列表 */
|
||||
type HisTaskList = Common.PaginatingQueryRecord<HisTask>;
|
||||
|
||||
/** 流程实例ID与历史任务 */
|
||||
type InstanceIdWithHisTask = Common.CommonRecord<{
|
||||
/** 流程实例ID */
|
||||
instanceId: CommonType.IdType;
|
||||
/** 历史任务 */
|
||||
list: HisTask[];
|
||||
}>;
|
||||
|
||||
/** 任务搜索参数 */
|
||||
type TaskSearchParams = CommonType.RecordNullable<
|
||||
Pick<Task, 'flowName' | 'flowCode' | 'businessId' | 'category' | 'nodeName' | 'instanceId' | 'permissionList'> &
|
||||
Api.Common.CommonSearchParams & {
|
||||
createByIds: CommonType.IdType[];
|
||||
}
|
||||
>;
|
||||
type TaskNextNodeSearchParams = CommonType.RecordNullable<{
|
||||
taskId: CommonType.IdType;
|
||||
taskVariables: { [key: string]: any };
|
||||
}>;
|
||||
|
||||
/** 消息类型 */
|
||||
type MessageType = '1' | '2' | '3';
|
||||
|
||||
/** 完成任务操作参数 */
|
||||
type CompleteTaskOperateParams = CommonType.RecordNullable<{
|
||||
/** 任务ID */
|
||||
taskId: CommonType.IdType;
|
||||
/** 文件ID */
|
||||
fileId: CommonType.IdType;
|
||||
/** 抄送人 */
|
||||
flowCopyList: FlowCopy[];
|
||||
/** 消息类型 */
|
||||
messageType: string[];
|
||||
/** 消息 */
|
||||
message: string;
|
||||
/** 通知 */
|
||||
notice: string;
|
||||
/** 任务变量 */
|
||||
taskVariables: { [key: string]: any };
|
||||
/** 变量 */
|
||||
variables: { [key: string]: any };
|
||||
/** 审批人 */
|
||||
assigneeMap: { [key: string]: string };
|
||||
/** 扩展字段 */
|
||||
ext: string;
|
||||
}>;
|
||||
|
||||
/** 工作流节点 */
|
||||
type FlowNode = Common.CommonTenantRecord<{
|
||||
/** 节点ID */
|
||||
id: CommonType.IdType;
|
||||
/** 删除标志 */
|
||||
delFlag: string;
|
||||
/** 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) */
|
||||
nodeType: WorkflowNodeType;
|
||||
/** 流程定义ID */
|
||||
definitionId: CommonType.IdType;
|
||||
/** 节点编码 */
|
||||
nodeCode: string;
|
||||
/** 节点名称 */
|
||||
nodeName: string;
|
||||
/** 权限标识 */
|
||||
permissionFlag: string;
|
||||
/** 流程签署比例值 */
|
||||
nodeRatio: string;
|
||||
/** 节点坐标 */
|
||||
coordinate: string;
|
||||
/** 流程版本号 */
|
||||
version: string;
|
||||
/** 是否允许任意节点跳转 */
|
||||
anyNodeSkip: string;
|
||||
/** 监听器类型 */
|
||||
listenerType: string;
|
||||
/** 监听器路径 */
|
||||
listenerPath: string;
|
||||
/** 处理器类型 */
|
||||
handlerType: string;
|
||||
/** 处理器路径 */
|
||||
handlerPath: string;
|
||||
/** 审批表单是否自定义(Y是 N否) */
|
||||
formCustom: Api.Common.YesOrNoStatus;
|
||||
/** 审批表单路径 */
|
||||
formPath: string;
|
||||
/** 扩展字段 */
|
||||
ext: string;
|
||||
}>;
|
||||
|
||||
/** 工作流节点列表 */
|
||||
type FlowNodeList = FlowNode[];
|
||||
|
||||
/** 驳回操作参数 */
|
||||
type BackOperateParams = CommonType.RecordNullable<{
|
||||
taskId: CommonType.IdType;
|
||||
fileId: CommonType.IdType;
|
||||
messageType: string[];
|
||||
nodeCode: string;
|
||||
message: string;
|
||||
notice: string;
|
||||
variables: { [key: string]: any };
|
||||
}>;
|
||||
}
|
||||
}
|
154
src/typings/app.d.ts
vendored
154
src/typings/app.d.ts
vendored
@ -28,12 +28,6 @@ declare namespace App {
|
||||
mode: UnionKey.ThemeLayoutMode;
|
||||
/** Scroll mode */
|
||||
scrollMode: UnionKey.ThemeScrollMode;
|
||||
/**
|
||||
* Whether to reverse the horizontal mix
|
||||
*
|
||||
* if true, the vertical child level menus in left and horizontal first level menus in top
|
||||
*/
|
||||
reverseHorizontalMix: boolean;
|
||||
};
|
||||
/** Page */
|
||||
page: {
|
||||
@ -88,11 +82,14 @@ declare namespace App {
|
||||
width: number;
|
||||
/** Collapsed sider width */
|
||||
collapsedWidth: number;
|
||||
/** Sider width when the layout is 'vertical-mix' or 'horizontal-mix' */
|
||||
/** Sider width when the layout is 'vertical-mix', 'top-hybrid-sidebar-first', or 'top-hybrid-header-first' */
|
||||
mixWidth: number;
|
||||
/** Collapsed sider width when the layout is 'vertical-mix' or 'horizontal-mix' */
|
||||
/**
|
||||
* Collapsed sider width when the layout is 'vertical-mix', 'top-hybrid-sidebar-first', or
|
||||
* 'top-hybrid-header-first'
|
||||
*/
|
||||
mixCollapsedWidth: number;
|
||||
/** Child menu width when the layout is 'vertical-mix' or 'horizontal-mix' */
|
||||
/** Child menu width when the layout is 'vertical-mix', 'top-hybrid-sidebar-first', or 'top-hybrid-header-first' */
|
||||
mixChildMenuWidth: number;
|
||||
};
|
||||
/** Footer */
|
||||
@ -103,7 +100,10 @@ declare namespace App {
|
||||
fixed: boolean;
|
||||
/** Footer height */
|
||||
height: number;
|
||||
/** Whether float the footer to the right when the layout is 'horizontal-mix' */
|
||||
/**
|
||||
* Whether float the footer to the right when the layout is 'top-hybrid-sidebar-first' or
|
||||
* 'top-hybrid-header-first'
|
||||
*/
|
||||
right: boolean;
|
||||
};
|
||||
/** Watermark */
|
||||
@ -114,6 +114,10 @@ declare namespace App {
|
||||
text: string;
|
||||
/** Whether to use user name as watermark text */
|
||||
enableUserName: boolean;
|
||||
/** Whether to use current time as watermark text */
|
||||
enableTime: boolean;
|
||||
/** Time format for watermark text */
|
||||
timeFormat: string;
|
||||
};
|
||||
table: {
|
||||
/** Whether to show the table border */
|
||||
@ -397,59 +401,94 @@ declare namespace App {
|
||||
tokenExpired: string;
|
||||
};
|
||||
theme: {
|
||||
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
|
||||
grayscale: string;
|
||||
colourWeakness: string;
|
||||
layoutMode: { title: string; reverseHorizontalMix: string } & Record<UnionKey.ThemeLayoutMode, string>;
|
||||
recommendColor: string;
|
||||
recommendColorDesc: string;
|
||||
themeColor: {
|
||||
title: string;
|
||||
followPrimary: string;
|
||||
} & Theme.ThemeColor;
|
||||
scrollMode: { title: string } & Record<UnionKey.ThemeScrollMode, string>;
|
||||
page: {
|
||||
animate: string;
|
||||
mode: { title: string } & Record<UnionKey.ThemePageAnimateMode, string>;
|
||||
themeDrawerTitle: string;
|
||||
tabs: {
|
||||
appearance: string;
|
||||
layout: string;
|
||||
general: string;
|
||||
};
|
||||
fixedHeaderAndTab: string;
|
||||
header: {
|
||||
height: string;
|
||||
breadcrumb: {
|
||||
appearance: {
|
||||
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
|
||||
grayscale: string;
|
||||
colourWeakness: string;
|
||||
themeColor: {
|
||||
title: string;
|
||||
followPrimary: string;
|
||||
} & Theme.ThemeColor;
|
||||
recommendColor: string;
|
||||
recommendColorDesc: string;
|
||||
};
|
||||
layout: {
|
||||
layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string> & {
|
||||
[K in `${UnionKey.ThemeLayoutMode}_detail`]: string;
|
||||
};
|
||||
tab: {
|
||||
title: string;
|
||||
visible: string;
|
||||
showIcon: string;
|
||||
cache: string;
|
||||
cacheTip: string;
|
||||
height: string;
|
||||
mode: { title: string } & Record<UnionKey.ThemeTabMode, string>;
|
||||
};
|
||||
header: {
|
||||
title: string;
|
||||
height: string;
|
||||
breadcrumb: {
|
||||
visible: string;
|
||||
showIcon: string;
|
||||
};
|
||||
};
|
||||
sider: {
|
||||
title: string;
|
||||
inverted: string;
|
||||
width: string;
|
||||
collapsedWidth: string;
|
||||
mixWidth: string;
|
||||
mixCollapsedWidth: string;
|
||||
mixChildMenuWidth: string;
|
||||
};
|
||||
footer: {
|
||||
title: string;
|
||||
visible: string;
|
||||
fixed: string;
|
||||
height: string;
|
||||
right: string;
|
||||
};
|
||||
content: {
|
||||
title: string;
|
||||
scrollMode: { title: string; tip: string } & Record<UnionKey.ThemeScrollMode, string>;
|
||||
page: {
|
||||
animate: string;
|
||||
mode: { title: string } & Record<UnionKey.ThemePageAnimateMode, string>;
|
||||
};
|
||||
fixedHeaderAndTab: string;
|
||||
};
|
||||
resetCacheStrategy: { title: string } & Record<UnionKey.ResetCacheStrategy, string>;
|
||||
};
|
||||
general: {
|
||||
title: string;
|
||||
watermark: {
|
||||
title: string;
|
||||
visible: string;
|
||||
text: string;
|
||||
enableUserName: string;
|
||||
enableTime: string;
|
||||
timeFormat: string;
|
||||
};
|
||||
multilingual: {
|
||||
title: string;
|
||||
visible: string;
|
||||
};
|
||||
globalSearch: {
|
||||
title: string;
|
||||
visible: string;
|
||||
};
|
||||
};
|
||||
tab: {
|
||||
visible: string;
|
||||
cache: string;
|
||||
height: string;
|
||||
mode: { title: string } & Record<UnionKey.ThemeTabMode, string>;
|
||||
};
|
||||
sider: {
|
||||
inverted: string;
|
||||
width: string;
|
||||
collapsedWidth: string;
|
||||
mixWidth: string;
|
||||
mixCollapsedWidth: string;
|
||||
mixChildMenuWidth: string;
|
||||
};
|
||||
footer: {
|
||||
visible: string;
|
||||
fixed: string;
|
||||
height: string;
|
||||
right: string;
|
||||
};
|
||||
watermark: {
|
||||
visible: string;
|
||||
text: string;
|
||||
enableUserName: string;
|
||||
configOperation: {
|
||||
copyConfig: string;
|
||||
copySuccessMsg: string;
|
||||
resetConfig: string;
|
||||
resetSuccessMsg: string;
|
||||
};
|
||||
tablePropsTitle: string;
|
||||
table: {
|
||||
@ -460,15 +499,6 @@ declare namespace App {
|
||||
singleLine: string;
|
||||
striped: string;
|
||||
};
|
||||
themeDrawerTitle: string;
|
||||
pageFunTitle: string;
|
||||
resetCacheStrategy: { title: string } & Record<UnionKey.ResetCacheStrategy, string>;
|
||||
configOperation: {
|
||||
copyConfig: string;
|
||||
copySuccessMsg: string;
|
||||
resetConfig: string;
|
||||
resetSuccessMsg: string;
|
||||
};
|
||||
};
|
||||
route: Record<I18nRouteKey, string>;
|
||||
menu: Record<string, string>;
|
||||
|
2
src/typings/common.d.ts
vendored
2
src/typings/common.d.ts
vendored
@ -43,6 +43,4 @@ declare namespace CommonType {
|
||||
/** filter function */
|
||||
filterFn?: (node: any) => boolean;
|
||||
};
|
||||
/** the type of workflow table operate */
|
||||
type WorkflowTableOperateType = 'add' | 'edit' | 'detail' | 'approval';
|
||||
}
|
||||
|
38
src/typings/components.d.ts
vendored
38
src/typings/components.d.ts
vendored
@ -9,8 +9,6 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
|
||||
ApprovalInfoPanel: typeof import('./../components/workflow/approval-info-panel.vue')['default']
|
||||
BackTaskModal: typeof import('./../components/workflow/back-task-modal.vue')['default']
|
||||
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
|
||||
BooleanTag: typeof import('./../components/custom/boolean-tag.vue')['default']
|
||||
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
|
||||
@ -24,20 +22,13 @@ declare module 'vue' {
|
||||
DictTag: typeof import('./../components/custom/dict-tag.vue')['default']
|
||||
ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
|
||||
FileUpload: typeof import('./../components/custom/file-upload.vue')['default']
|
||||
FlowCategorySelect: typeof import('./../components/workflow/flow-category-select.vue')['default']
|
||||
FlowDrawer: typeof import('./../components/workflow/flow-drawer.vue')['default']
|
||||
FlowInterveneModal: typeof import('./../components/workflow/flow-intervene-modal.vue')['default']
|
||||
FlowPreview: typeof import('./../components/workflow/flow-preview.vue')['default']
|
||||
FlowTaskApprovalModal: typeof import('./../components/workflow/flow-task-approval-modal.vue')['default']
|
||||
FormTip: typeof import('./../components/custom/form-tip.vue')['default']
|
||||
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
|
||||
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
|
||||
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
|
||||
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
|
||||
IconEpCopyDocument: typeof import('~icons/ep/copy-document')['default']
|
||||
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
|
||||
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
|
||||
'IconHugeicons:configuration01': typeof import('~icons/hugeicons/configuration01')['default']
|
||||
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
|
||||
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
|
||||
IconLocalBanner: typeof import('~icons/local/banner')['default']
|
||||
@ -45,14 +36,7 @@ declare module 'vue' {
|
||||
'IconMaterialSymbols:add': typeof import('~icons/material-symbols/add')['default']
|
||||
'IconMaterialSymbols:deleteOutline': typeof import('~icons/material-symbols/delete-outline')['default']
|
||||
'IconMaterialSymbols:downloadRounded': typeof import('~icons/material-symbols/download-rounded')['default']
|
||||
'IconMaterialSymbols:imageOutline': typeof import('~icons/material-symbols/image-outline')['default']
|
||||
'IconMaterialSymbols:refreshRounded': typeof import('~icons/material-symbols/refresh-rounded')['default']
|
||||
'IconMaterialSymbols:syncOutline': typeof import('~icons/material-symbols/sync-outline')['default']
|
||||
'IconMaterialSymbols:uploadRounded': typeof import('~icons/material-symbols/upload-rounded')['default']
|
||||
'IconMaterialSymbols:warningOutlineRounded': typeof import('~icons/material-symbols/warning-outline-rounded')['default']
|
||||
IconMaterialSymbolsAddRounded: typeof import('~icons/material-symbols/add-rounded')['default']
|
||||
IconMaterialSymbolsDeleteOutline: typeof import('~icons/material-symbols/delete-outline')['default']
|
||||
IconMaterialSymbolsDriveFileRenameOutlineOutline: typeof import('~icons/material-symbols/drive-file-rename-outline-outline')['default']
|
||||
'IconMdi:github': typeof import('~icons/mdi/github')['default']
|
||||
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
|
||||
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
|
||||
@ -62,10 +46,10 @@ declare module 'vue' {
|
||||
'IconQuill:collapse': typeof import('~icons/quill/collapse')['default']
|
||||
'IconQuill:expand': typeof import('~icons/quill/expand')['default']
|
||||
'IconSimpleIcons:gitee': typeof import('~icons/simple-icons/gitee')['default']
|
||||
IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
|
||||
IconUilSearch: typeof import('~icons/uil/search')['default']
|
||||
JsonPreview: typeof import('./../components/custom/json-preview.vue')['default']
|
||||
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
|
||||
LeaveEdit: typeof import('./../components/workflow/form/leave-edit/index.vue')['default']
|
||||
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
|
||||
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
|
||||
MenuTree: typeof import('./../components/custom/menu-tree.vue')['default']
|
||||
@ -80,22 +64,15 @@ declare module 'vue' {
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
||||
NCode: typeof import('naive-ui')['NCode']
|
||||
NCollapse: typeof import('naive-ui')['NCollapse']
|
||||
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDatePicker: typeof import('naive-ui')['NDatePicker']
|
||||
NDescriptions: typeof import('naive-ui')['NDescriptions']
|
||||
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
||||
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
|
||||
NDynamicTags: typeof import('naive-ui')['NDynamicTags']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
@ -103,14 +80,9 @@ declare module 'vue' {
|
||||
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||
NGi: typeof import('naive-ui')['NGi']
|
||||
NGrid: typeof import('naive-ui')['NGrid']
|
||||
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
NLayoutContent: typeof import('naive-ui')['NLayoutContent']
|
||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||
NList: typeof import('naive-ui')['NList']
|
||||
NListItem: typeof import('naive-ui')['NListItem']
|
||||
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
|
||||
@ -121,9 +93,6 @@ declare module 'vue' {
|
||||
NP: typeof import('naive-ui')['NP']
|
||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NRadioButton: typeof import('naive-ui')['NRadioButton']
|
||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
@ -131,13 +100,11 @@ declare module 'vue' {
|
||||
NStatistic: typeof import('naive-ui')['NStatistic']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTab: typeof import('naive-ui')['NTab']
|
||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||
NTabs: typeof import('naive-ui')['NTabs']
|
||||
NTag: typeof import('naive-ui')['NTag']
|
||||
NText: typeof import('naive-ui')['NText']
|
||||
NThing: typeof import('naive-ui')['NThing']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
NTree: typeof import('naive-ui')['NTree']
|
||||
NTreeSelect: typeof import('naive-ui')['NTreeSelect']
|
||||
NUpload: typeof import('naive-ui')['NUpload']
|
||||
NUploadDragger: typeof import('naive-ui')['NUploadDragger']
|
||||
@ -145,7 +112,6 @@ declare module 'vue' {
|
||||
OssUpload: typeof import('./../components/custom/oss-upload.vue')['default']
|
||||
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
|
||||
PostSelect: typeof import('./../components/custom/post-select.vue')['default']
|
||||
ReduceSignatureModal: typeof import('./../components/workflow/reduce-signature-modal.vue')['default']
|
||||
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
|
||||
RoleSelect: typeof import('./../components/custom/role-select.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
@ -158,12 +124,10 @@ declare module 'vue' {
|
||||
TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
|
||||
TableRowCheckAlert: typeof import('./../components/advanced/table-row-check-alert.vue')['default']
|
||||
TableSiderLayout: typeof import('./../components/advanced/table-sider-layout.vue')['default']
|
||||
TagGroup: typeof import('./../components/custom/tag-group.vue')['default']
|
||||
TenantSelect: typeof import('./../components/custom/tenant-select.vue')['default']
|
||||
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
|
||||
TinymceEditor: typeof import('./../components/custom/tinymce-editor.vue')['default']
|
||||
UserSelect: typeof import('./../components/custom/user-select.vue')['default']
|
||||
UserSelectModal: typeof import('./../components/custom/user-select-modal.vue')['default']
|
||||
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
|
||||
}
|
||||
}
|
||||
|
63
src/typings/elegant-router.d.ts
vendored
63
src/typings/elegant-router.d.ts
vendored
@ -32,40 +32,8 @@ declare module "@elegant-router/types" {
|
||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
|
||||
"monitor": "/monitor";
|
||||
"monitor_cache": "/monitor/cache";
|
||||
"monitor_logininfor": "/monitor/logininfor";
|
||||
"monitor_online": "/monitor/online";
|
||||
"monitor_operlog": "/monitor/operlog";
|
||||
"social-callback": "/social-callback";
|
||||
"system": "/system";
|
||||
"system_client": "/system/client";
|
||||
"system_config": "/system/config";
|
||||
"system_dept": "/system/dept";
|
||||
"system_dict": "/system/dict";
|
||||
"system_menu": "/system/menu";
|
||||
"system_notice": "/system/notice";
|
||||
"system_oss": "/system/oss";
|
||||
"system_oss-config": "/system/oss-config";
|
||||
"system_post": "/system/post";
|
||||
"system_role": "/system/role";
|
||||
"system_tenant": "/system/tenant";
|
||||
"system_tenant-package": "/system/tenant-package";
|
||||
"system_user": "/system/user";
|
||||
"tool": "/tool";
|
||||
"tool_gen": "/tool/gen";
|
||||
"user-center": "/user-center";
|
||||
"workflow": "/workflow";
|
||||
"workflow_category": "/workflow/category";
|
||||
"workflow_design": "/workflow/design";
|
||||
"workflow_leave": "/workflow/leave";
|
||||
"workflow_process-definition": "/workflow/process-definition";
|
||||
"workflow_process-instance": "/workflow/process-instance";
|
||||
"workflow_spel": "/workflow/spel";
|
||||
"workflow_task": "/workflow/task";
|
||||
"workflow_task_all-task-waiting": "/workflow/task/all-task-waiting";
|
||||
"workflow_task_my-document": "/workflow/task/my-document";
|
||||
"workflow_task_task-copy": "/workflow/task/task-copy";
|
||||
"workflow_task_task-finish": "/workflow/task/task-finish";
|
||||
"workflow_task_task-waiting": "/workflow/task/task-waiting";
|
||||
};
|
||||
|
||||
/**
|
||||
@ -110,10 +78,7 @@ declare module "@elegant-router/types" {
|
||||
| "login"
|
||||
| "monitor"
|
||||
| "social-callback"
|
||||
| "system"
|
||||
| "tool"
|
||||
| "user-center"
|
||||
| "workflow"
|
||||
>;
|
||||
|
||||
/**
|
||||
@ -142,34 +107,6 @@ declare module "@elegant-router/types" {
|
||||
| "demo_tree"
|
||||
| "home"
|
||||
| "monitor_cache"
|
||||
| "monitor_logininfor"
|
||||
| "monitor_online"
|
||||
| "monitor_operlog"
|
||||
| "system_client"
|
||||
| "system_config"
|
||||
| "system_dept"
|
||||
| "system_dict"
|
||||
| "system_menu"
|
||||
| "system_notice"
|
||||
| "system_oss-config"
|
||||
| "system_oss"
|
||||
| "system_post"
|
||||
| "system_role"
|
||||
| "system_tenant-package"
|
||||
| "system_tenant"
|
||||
| "system_user"
|
||||
| "tool_gen"
|
||||
| "workflow_category"
|
||||
| "workflow_design"
|
||||
| "workflow_leave"
|
||||
| "workflow_process-definition"
|
||||
| "workflow_process-instance"
|
||||
| "workflow_spel"
|
||||
| "workflow_task_all-task-waiting"
|
||||
| "workflow_task_my-document"
|
||||
| "workflow_task_task-copy"
|
||||
| "workflow_task_task-finish"
|
||||
| "workflow_task_task-waiting"
|
||||
>;
|
||||
|
||||
/**
|
||||
|
43
src/typings/naive-ui.d.ts
vendored
43
src/typings/naive-ui.d.ts
vendored
@ -6,32 +6,14 @@ declare namespace NaiveUI {
|
||||
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 SetTableColumnKey<C, T> = Omit<C, 'key'> & { key: keyof T | (string & {}) };
|
||||
|
||||
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.Common.CommonSearchParams> = (
|
||||
params: R
|
||||
) => Promise<FlatResponseData<Api.Common.PaginatingQueryRecord<T>>>;
|
||||
|
||||
type TreeTableApiFn<T = any, R = Record<string, any>> = (params: R) => Promise<FlatResponseData<T[]>>;
|
||||
|
||||
/**
|
||||
* the type of table operation
|
||||
*
|
||||
@ -39,27 +21,4 @@ declare namespace NaiveUI {
|
||||
* - edit: edit table item
|
||||
*/
|
||||
type TableOperateType = 'add' | 'edit';
|
||||
|
||||
type GetTableData<A extends TableApiFn> = A extends TableApiFn<infer T> ? T : never;
|
||||
|
||||
type GetTreeTableData<A extends TreeTableApiFn> = A extends TreeTableApiFn<infer T> ? T : never;
|
||||
|
||||
type NaiveTableConfig<A extends TableApiFn> = Pick<
|
||||
import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<TableDataWithIndex<GetTableData<A>>>>,
|
||||
'apiFn' | 'apiParams' | 'columns' | 'immediate'
|
||||
> & {
|
||||
/**
|
||||
* whether to display the total items count
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
showTotal?: boolean;
|
||||
};
|
||||
|
||||
type NaiveTreeTableConfig<A extends TreeTableApiFn> = Pick<
|
||||
import('@sa/hooks').TableConfig<A, GetTreeTableData<A>, TableColumn<TableDataWithIndex<GetTreeTableData<A>>>>,
|
||||
'apiFn' | 'apiParams' | 'columns' | 'immediate'
|
||||
>;
|
||||
|
||||
type CodeMirrorLang = 'js' | 'json';
|
||||
}
|
||||
|
11
src/typings/union-key.d.ts
vendored
11
src/typings/union-key.d.ts
vendored
@ -28,9 +28,16 @@ declare namespace UnionKey {
|
||||
* - vertical: the vertical menu in left
|
||||
* - horizontal: the horizontal menu in top
|
||||
* - vertical-mix: two vertical mixed menus in left
|
||||
* - horizontal-mix: the vertical first level menus in left and horizontal child level menus in top
|
||||
* - top-hybrid-sidebar-first: the vertical first level menus in left and horizontal child level menus in top
|
||||
* - top-hybrid-header-first: the horizontal first level menus in top and vertical child level menus in left
|
||||
*/
|
||||
type ThemeLayoutMode = 'vertical' | 'horizontal' | 'vertical-mix' | 'horizontal-mix';
|
||||
type ThemeLayoutMode =
|
||||
| 'vertical'
|
||||
| 'horizontal'
|
||||
| 'vertical-mix'
|
||||
| 'vertical-hybrid-header-first'
|
||||
| 'top-hybrid-sidebar-first'
|
||||
| 'top-hybrid-header-first';
|
||||
|
||||
/**
|
||||
* The scroll mode when content overflow
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user