mirror of
https://gitee.com/honghuangdc/soybean-admin-element-plus.git
synced 2026-08-01 18:17:55 +00:00
feat(projects): 🎉 init project
This commit is contained in:
17
packages/alova/package.json
Normal file
17
packages/alova/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@sa/alova",
|
||||
"version": "0.1.0",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./client": "./src/client.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"alova": "3.0.20"
|
||||
}
|
||||
}
|
||||
1
packages/alova/src/client.ts
Normal file
1
packages/alova/src/client.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from 'alova/client';
|
||||
2
packages/alova/src/constant.ts
Normal file
2
packages/alova/src/constant.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
/** the backend error code key */
|
||||
export const BACKEND_ERROR_CODE = 'BACKEND_ERROR';
|
||||
77
packages/alova/src/index.ts
Normal file
77
packages/alova/src/index.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { createAlova } from 'alova';
|
||||
import type { AlovaDefaultCacheAdapter, AlovaGenerics, AlovaGlobalCacheAdapter, AlovaRequestAdapter } from 'alova';
|
||||
import VueHook from 'alova/vue';
|
||||
import type { VueHookType } from 'alova/vue';
|
||||
import adapterFetch from 'alova/fetch';
|
||||
import { createServerTokenAuthentication } from 'alova/client';
|
||||
import type { FetchRequestInit } from 'alova/fetch';
|
||||
import { BACKEND_ERROR_CODE } from './constant';
|
||||
import type { CustomAlovaConfig, RequestOptions } from './type';
|
||||
|
||||
export const createAlovaRequest = <
|
||||
RequestConfig = FetchRequestInit,
|
||||
ResponseType = Response,
|
||||
ResponseHeader = Headers,
|
||||
L1Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter,
|
||||
L2Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter
|
||||
>(
|
||||
customConfig: CustomAlovaConfig<
|
||||
AlovaGenerics<any, any, RequestConfig, ResponseType, ResponseHeader, L1Cache, L2Cache, any>
|
||||
>,
|
||||
options: RequestOptions<AlovaGenerics<any, any, RequestConfig, ResponseType, ResponseHeader, L1Cache, L2Cache, any>>
|
||||
) => {
|
||||
const { tokenRefresher } = options;
|
||||
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<
|
||||
VueHookType,
|
||||
AlovaRequestAdapter<RequestConfig, ResponseType, ResponseHeader>
|
||||
>({
|
||||
refreshTokenOnSuccess: {
|
||||
isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false,
|
||||
handler: async (response, method) => tokenRefresher?.handler(response, method)
|
||||
},
|
||||
refreshTokenOnError: {
|
||||
isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false,
|
||||
handler: async (response, method) => tokenRefresher?.handler(response, method)
|
||||
}
|
||||
});
|
||||
|
||||
const instance = createAlova({
|
||||
...customConfig,
|
||||
timeout: customConfig.timeout ?? 10 * 1000,
|
||||
requestAdapter: (customConfig.requestAdapter as any) ?? adapterFetch(),
|
||||
statesHook: VueHook,
|
||||
beforeRequest: onAuthRequired(options.onRequest as any),
|
||||
responded: onResponseRefreshToken({
|
||||
onSuccess: async (response, method) => {
|
||||
// check if http status is success
|
||||
let error: any = null;
|
||||
let transformedData: any = null;
|
||||
try {
|
||||
if (await options.isBackendSuccess(response)) {
|
||||
transformedData = await options.transformBackendResponse(response);
|
||||
} else {
|
||||
error = new Error('the backend request error');
|
||||
error.code = BACKEND_ERROR_CODE;
|
||||
}
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
await options.onError?.(error, response, method);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return transformedData;
|
||||
},
|
||||
onComplete: options.onComplete,
|
||||
onError: (error, method) => options.onError?.(error, null, method)
|
||||
})
|
||||
});
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
export { BACKEND_ERROR_CODE };
|
||||
export type * from './type';
|
||||
export type * from 'alova';
|
||||
52
packages/alova/src/type.ts
Normal file
52
packages/alova/src/type.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { AlovaGenerics, AlovaOptions, AlovaRequestAdapter, Method, ResponseCompleteHandler } from 'alova';
|
||||
|
||||
export type CustomAlovaConfig<AG extends AlovaGenerics> = Omit<
|
||||
AlovaOptions<AG>,
|
||||
'statesHook' | 'beforeRequest' | 'responded' | 'requestAdapter'
|
||||
> & {
|
||||
/** request adapter. all request of alova will be sent by it. */
|
||||
requestAdapter?: AlovaRequestAdapter<AG['RequestConfig'], AG['Response'], AG['ResponseHeader']>;
|
||||
};
|
||||
|
||||
export interface RequestOptions<AG extends AlovaGenerics> {
|
||||
/**
|
||||
* The hook before request
|
||||
*
|
||||
* For example: You can add header token in this hook
|
||||
*
|
||||
* @param method alova Method Instance
|
||||
*/
|
||||
onRequest?: AlovaOptions<AG>['beforeRequest'];
|
||||
/**
|
||||
* The hook to check backend response is success or not
|
||||
*
|
||||
* @param response alova response
|
||||
*/
|
||||
isBackendSuccess: (response: AG['Response']) => Promise<boolean>;
|
||||
|
||||
/** The config to refresh token */
|
||||
tokenRefresher?: {
|
||||
/** detect the token is expired */
|
||||
isExpired(response: AG['Response'], Method: Method<AG>): Promise<boolean> | boolean;
|
||||
/** refresh token handler */
|
||||
handler(response: AG['Response'], Method: Method<AG>): Promise<void>;
|
||||
};
|
||||
|
||||
/** The hook after backend request complete */
|
||||
onComplete?: ResponseCompleteHandler<AG>;
|
||||
|
||||
/**
|
||||
* The hook to handle error
|
||||
*
|
||||
* For example: You can show error message in this hook
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
onError?: (error: any, response: AG['Response'] | null, methodInstance: Method<AG>) => any | Promise<any>;
|
||||
/**
|
||||
* transform backend response when the responseType is json
|
||||
*
|
||||
* @param response alova response
|
||||
*/
|
||||
transformBackendResponse: (response: AG['Response']) => any;
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"types": ["node"]
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
21
packages/axios/package.json
Normal file
21
packages/axios/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@sa/axios",
|
||||
"version": "1.3.7",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"axios": "1.7.7",
|
||||
"axios-retry": "4.5.0",
|
||||
"qs": "6.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/qs": "6.9.16"
|
||||
}
|
||||
}
|
||||
5
packages/axios/src/constant.ts
Normal file
5
packages/axios/src/constant.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/** request id key */
|
||||
export const REQUEST_ID_KEY = 'X-Request-Id';
|
||||
|
||||
/** the backend error code key */
|
||||
export const BACKEND_ERROR_CODE = 'BACKEND_ERROR';
|
||||
183
packages/axios/src/index.ts
Normal file
183
packages/axios/src/index.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import type { AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
|
||||
import axiosRetry from 'axios-retry';
|
||||
import { nanoid } from '@sa/utils';
|
||||
import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options';
|
||||
import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant';
|
||||
import type {
|
||||
CustomAxiosRequestConfig,
|
||||
FlatRequestInstance,
|
||||
MappedType,
|
||||
RequestInstance,
|
||||
RequestOption,
|
||||
ResponseType
|
||||
} from './type';
|
||||
|
||||
function createCommonRequest<ResponseData = any>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const opts = createDefaultOptions<ResponseData>(options);
|
||||
|
||||
const axiosConf = createAxiosConfig(axiosConfig);
|
||||
const instance = axios.create(axiosConf);
|
||||
|
||||
const abortControllerMap = new Map<string, AbortController>();
|
||||
|
||||
// config axios retry
|
||||
const retryOptions = createRetryOptions(axiosConf);
|
||||
axiosRetry(instance, retryOptions);
|
||||
|
||||
instance.interceptors.request.use(conf => {
|
||||
const config: InternalAxiosRequestConfig = { ...conf };
|
||||
|
||||
// set request id
|
||||
const requestId = nanoid();
|
||||
config.headers.set(REQUEST_ID_KEY, requestId);
|
||||
|
||||
// config abort controller
|
||||
if (!config.signal) {
|
||||
const abortController = new AbortController();
|
||||
config.signal = abortController.signal;
|
||||
abortControllerMap.set(requestId, abortController);
|
||||
}
|
||||
|
||||
// handle config by hook
|
||||
const handledConfig = opts.onRequest?.(config) || config;
|
||||
|
||||
return handledConfig;
|
||||
});
|
||||
|
||||
instance.interceptors.response.use(
|
||||
async response => {
|
||||
const responseType: ResponseType = (response.config?.responseType as ResponseType) || 'json';
|
||||
|
||||
if (responseType !== 'json' || opts.isBackendSuccess(response)) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
||||
const fail = await opts.onBackendFail(response, instance);
|
||||
if (fail) {
|
||||
return fail;
|
||||
}
|
||||
|
||||
const backendError = new AxiosError<ResponseData>(
|
||||
'the backend request error',
|
||||
BACKEND_ERROR_CODE,
|
||||
response.config,
|
||||
response.request,
|
||||
response
|
||||
);
|
||||
|
||||
await opts.onError(backendError);
|
||||
|
||||
return Promise.reject(backendError);
|
||||
},
|
||||
async (error: AxiosError<ResponseData>) => {
|
||||
await opts.onError(error);
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
function cancelRequest(requestId: string) {
|
||||
const abortController = abortControllerMap.get(requestId);
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
abortControllerMap.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAllRequest() {
|
||||
abortControllerMap.forEach(abortController => {
|
||||
abortController.abort();
|
||||
});
|
||||
abortControllerMap.clear();
|
||||
}
|
||||
|
||||
return {
|
||||
instance,
|
||||
opts,
|
||||
cancelRequest,
|
||||
cancelAllRequest
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* create a request instance
|
||||
*
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const request: RequestInstance<State> = async function request<T = any, 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 response.data as MappedType<R, T>;
|
||||
} as RequestInstance<State>;
|
||||
|
||||
request.cancelRequest = cancelRequest;
|
||||
request.cancelAllRequest = cancelAllRequest;
|
||||
request.state = {} as State;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a flat request instance
|
||||
*
|
||||
* The response data is a flat object: { data: any, error: AxiosError }
|
||||
*
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createFlatRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const flatRequest: FlatRequestInstance<State, ResponseData> = async function flatRequest<
|
||||
T = any,
|
||||
R extends ResponseType = 'json'
|
||||
>(config: CustomAxiosRequestConfig) {
|
||||
try {
|
||||
const response: AxiosResponse<ResponseData> = await instance(config);
|
||||
|
||||
const responseType = response.config?.responseType || 'json';
|
||||
|
||||
if (responseType === 'json') {
|
||||
const data = opts.transformBackendResponse(response);
|
||||
|
||||
return { data, error: null, response };
|
||||
}
|
||||
|
||||
return { data: response.data as MappedType<R, T>, error: null };
|
||||
} catch (error) {
|
||||
return { data: null, error, response: (error as AxiosError<ResponseData>).response };
|
||||
}
|
||||
} as FlatRequestInstance<State, ResponseData>;
|
||||
|
||||
flatRequest.cancelRequest = cancelRequest;
|
||||
flatRequest.cancelAllRequest = cancelAllRequest;
|
||||
flatRequest.state = {} as State;
|
||||
|
||||
return flatRequest;
|
||||
}
|
||||
|
||||
export { BACKEND_ERROR_CODE, REQUEST_ID_KEY };
|
||||
export type * from './type';
|
||||
export type { CreateAxiosDefaults, AxiosError };
|
||||
48
packages/axios/src/options.ts
Normal file
48
packages/axios/src/options.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { CreateAxiosDefaults } from 'axios';
|
||||
import type { IAxiosRetryConfig } from 'axios-retry';
|
||||
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> = {
|
||||
onRequest: async config => config,
|
||||
isBackendSuccess: _response => true,
|
||||
onBackendFail: async () => {},
|
||||
transformBackendResponse: async response => response.data,
|
||||
onError: async () => {}
|
||||
};
|
||||
|
||||
Object.assign(opts, options);
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
|
||||
const retryConfig: IAxiosRetryConfig = {
|
||||
retries: 0
|
||||
};
|
||||
|
||||
Object.assign(retryConfig, config);
|
||||
|
||||
return retryConfig;
|
||||
}
|
||||
|
||||
export function createAxiosConfig(config?: Partial<CreateAxiosDefaults>) {
|
||||
const TEN_SECONDS = 10 * 1000;
|
||||
|
||||
const axiosConfig: CreateAxiosDefaults = {
|
||||
timeout: TEN_SECONDS,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
validateStatus: isHttpSuccess,
|
||||
paramsSerializer: params => {
|
||||
return stringify(params);
|
||||
}
|
||||
};
|
||||
|
||||
Object.assign(axiosConfig, config);
|
||||
|
||||
return axiosConfig;
|
||||
}
|
||||
28
packages/axios/src/shared.ts
Normal file
28
packages/axios/src/shared.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { AxiosHeaderValue, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
export function getContentType(config: InternalAxiosRequestConfig) {
|
||||
const contentType: AxiosHeaderValue = config.headers?.['Content-Type'] || 'application/json';
|
||||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if http status is success
|
||||
*
|
||||
* @param status
|
||||
*/
|
||||
export function isHttpSuccess(status: number) {
|
||||
const isSuccessCode = status >= 200 && status < 300;
|
||||
return isSuccessCode || status === 304;
|
||||
}
|
||||
|
||||
/**
|
||||
* is response json
|
||||
*
|
||||
* @param response axios response
|
||||
*/
|
||||
export function isResponseJson(response: AxiosResponse) {
|
||||
const { responseType } = response.config;
|
||||
|
||||
return responseType === 'json' || responseType === undefined;
|
||||
}
|
||||
115
packages/axios/src/type.ts
Normal file
115
packages/axios/src/type.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
export type ContentType =
|
||||
| 'text/html'
|
||||
| 'text/plain'
|
||||
| 'multipart/form-data'
|
||||
| 'application/json'
|
||||
| 'application/x-www-form-urlencoded'
|
||||
| 'application/octet-stream';
|
||||
|
||||
export interface RequestOption<ResponseData = any> {
|
||||
/**
|
||||
* The hook before request
|
||||
*
|
||||
* For example: You can add header token in this hook
|
||||
*
|
||||
* @param config Axios config
|
||||
*/
|
||||
onRequest: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
|
||||
/**
|
||||
* The hook to check backend response is success or not
|
||||
*
|
||||
* @param response Axios response
|
||||
*/
|
||||
isBackendSuccess: (response: AxiosResponse<ResponseData>) => boolean;
|
||||
/**
|
||||
* The hook after backend request fail
|
||||
*
|
||||
* For example: You can handle the expired token in this hook
|
||||
*
|
||||
* @param response Axios response
|
||||
* @param instance Axios instance
|
||||
*/
|
||||
onBackendFail: (
|
||||
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
|
||||
*
|
||||
* For example: You can show error message in this hook
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
onError: (error: AxiosError<ResponseData>) => void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ResponseMap {
|
||||
blob: Blob;
|
||||
text: string;
|
||||
arrayBuffer: ArrayBuffer;
|
||||
stream: ReadableStream<Uint8Array>;
|
||||
document: Document;
|
||||
}
|
||||
export type ResponseType = keyof ResponseMap | 'json';
|
||||
|
||||
export type MappedType<R extends ResponseType, JsonType = any> = R extends keyof ResponseMap
|
||||
? ResponseMap[R]
|
||||
: JsonType;
|
||||
|
||||
export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<AxiosRequestConfig, 'responseType'> & {
|
||||
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;
|
||||
/**
|
||||
* cancel all request
|
||||
*
|
||||
* if the request provide abort controller sign from config, it will not collect in the abort controller map
|
||||
*/
|
||||
cancelAllRequest: () => void;
|
||||
/** you can set custom state in the request instance */
|
||||
state: T;
|
||||
}
|
||||
|
||||
/** 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 type FlatResponseSuccessData<T = any, ResponseData = any> = {
|
||||
data: T;
|
||||
error: null;
|
||||
response: AxiosResponse<ResponseData>;
|
||||
};
|
||||
|
||||
export type FlatResponseFailData<ResponseData = any> = {
|
||||
data: null;
|
||||
error: AxiosError<ResponseData>;
|
||||
response: AxiosResponse<ResponseData>;
|
||||
};
|
||||
|
||||
export type FlatResponseData<T = any, ResponseData = any> =
|
||||
| FlatResponseSuccessData<T, ResponseData>
|
||||
| FlatResponseFailData<ResponseData>;
|
||||
|
||||
export interface FlatRequestInstance<S = Record<string, unknown>, ResponseData = any> extends RequestInstanceCommon<S> {
|
||||
<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig<R>
|
||||
): Promise<FlatResponseData<MappedType<R, T>, ResponseData>>;
|
||||
}
|
||||
20
packages/axios/tsconfig.json
Normal file
20
packages/axios/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { colord, extend } from 'colord';
|
||||
import type { HslColor } from 'colord';
|
||||
import labPlugin from 'colord/plugins/lab';
|
||||
|
||||
extend([labPlugin]);
|
||||
|
||||
export function isValidColor(color: string) {
|
||||
return colord(color).isValid();
|
||||
}
|
||||
|
||||
export function getHex(color: string) {
|
||||
return colord(color).toHex();
|
||||
}
|
||||
|
||||
export function getRgb(color: string) {
|
||||
return colord(color).toRgb();
|
||||
}
|
||||
|
||||
export function getHsl(color: string) {
|
||||
return colord(color).toHsl();
|
||||
}
|
||||
|
||||
export function getDeltaE(color1: string, color2: string) {
|
||||
return colord(color1).delta(color2);
|
||||
}
|
||||
|
||||
export function transformHslToHex(color: HslColor) {
|
||||
return colord(color).toHex();
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { getColorPaletteFamily } from './palette';
|
||||
import { getColorName } from './name';
|
||||
import type { ColorPalette, ColorPaletteNumber, ColorPaletteItem, ColorPaletteFamily } from './type';
|
||||
import defaultPalettes from './json/palette.json';
|
||||
|
||||
/**
|
||||
* get color palette by provided color and color name
|
||||
* @param color the provided color
|
||||
* @param colorName color name
|
||||
*/
|
||||
export function getColorPalette(color: string, colorName: string) {
|
||||
const colorPaletteFamily = getColorPaletteFamily(color, colorName);
|
||||
|
||||
const colorMap = new Map<ColorPaletteNumber, ColorPaletteItem>();
|
||||
|
||||
colorPaletteFamily.palettes.forEach(palette => {
|
||||
colorMap.set(palette.number, palette);
|
||||
});
|
||||
|
||||
const mainColor = colorMap.get(500) as ColorPaletteItem;
|
||||
const matchColor = colorPaletteFamily.palettes.find(palette => palette.hexcode === color) as ColorPaletteItem;
|
||||
|
||||
const colorPalette: ColorPalette = {
|
||||
...colorPaletteFamily,
|
||||
colorMap,
|
||||
main: mainColor,
|
||||
match: matchColor
|
||||
};
|
||||
|
||||
return colorPalette;
|
||||
}
|
||||
|
||||
export default getColorPalette;
|
||||
|
||||
/**
|
||||
* the builtin color palettes
|
||||
*/
|
||||
const colorPalettes = defaultPalettes as ColorPaletteFamily[];
|
||||
|
||||
export { getColorName, colorPalettes };
|
||||
|
||||
export type { ColorPalette, ColorPaletteNumber, ColorPaletteItem, ColorPaletteFamily };
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,274 +0,0 @@
|
||||
[
|
||||
{
|
||||
"key": "red",
|
||||
"palettes": [
|
||||
{ "hexcode": "#fef2f2", "number": 50, "name": "Bridesmaid" },
|
||||
{ "hexcode": "#fee2e2", "number": 100, "name": "Pippin" },
|
||||
{ "hexcode": "#fecaca", "number": 200, "name": "Your Pink" },
|
||||
{ "hexcode": "#fca5a5", "number": 300, "name": "Cornflower Lilac" },
|
||||
{ "hexcode": "#f87171", "number": 400, "name": "Bittersweet" },
|
||||
{ "hexcode": "#ef4444", "number": 500, "name": "Cinnabar" },
|
||||
{ "hexcode": "#dc2626", "number": 600, "name": "Persian Red" },
|
||||
{ "hexcode": "#b91c1c", "number": 700, "name": "Thunderbird" },
|
||||
{ "hexcode": "#991b1b", "number": 800, "name": "Old Brick" },
|
||||
{ "hexcode": "#7f1d1d", "number": 900, "name": "Falu Red" },
|
||||
{ "hexcode": "#450a0a", "number": 950, "name": "Mahogany" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "orange",
|
||||
"palettes": [
|
||||
{ "hexcode": "#fff7ed", "number": 50, "name": "Serenade" },
|
||||
{ "hexcode": "#ffedd5", "number": 100, "name": "Derby" },
|
||||
{ "hexcode": "#fed7aa", "number": 200, "name": "Caramel" },
|
||||
{ "hexcode": "#fdba74", "number": 300, "name": "Macaroni and Cheese" },
|
||||
{ "hexcode": "#fb923c", "number": 400, "name": "Neon Carrot" },
|
||||
{ "hexcode": "#f97316", "number": 500, "name": "Ecstasy" },
|
||||
{ "hexcode": "#ea580c", "number": 600, "name": "Trinidad" },
|
||||
{ "hexcode": "#c2410c", "number": 700, "name": "Tia Maria" },
|
||||
{ "hexcode": "#9a3412", "number": 800, "name": "Tabasco" },
|
||||
{ "hexcode": "#7c2d12", "number": 900, "name": "Pueblo" },
|
||||
{ "hexcode": "#431407", "number": 950, "name": "Rebel" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "amber",
|
||||
"palettes": [
|
||||
{ "hexcode": "#fffbeb", "number": 50, "name": "Island Spice" },
|
||||
{ "hexcode": "#fef3c7", "number": 100, "name": "Beeswax" },
|
||||
{ "hexcode": "#fde68a", "number": 200, "name": "Sweet Corn" },
|
||||
{ "hexcode": "#fcd34d", "number": 300, "name": "Mustard" },
|
||||
{ "hexcode": "#fbbf24", "number": 400, "name": "Lightning Yellow" },
|
||||
{ "hexcode": "#f59e0b", "number": 500, "name": "California" },
|
||||
{ "hexcode": "#d97706", "number": 600, "name": "Christine" },
|
||||
{ "hexcode": "#b45309", "number": 700, "name": "Vesuvius" },
|
||||
{ "hexcode": "#92400e", "number": 800, "name": "Korma" },
|
||||
{ "hexcode": "#78350f", "number": 900, "name": "Copper Canyon" },
|
||||
{ "hexcode": "#451a03", "number": 950, "name": "Brown Pod" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "yellow",
|
||||
"palettes": [
|
||||
{ "hexcode": "#fefce8", "number": 50, "name": "Orange White" },
|
||||
{ "hexcode": "#fef9c3", "number": 100, "name": "Lemon Chiffon" },
|
||||
{ "hexcode": "#fef08a", "number": 200, "name": "Sweet Corn" },
|
||||
{ "hexcode": "#fde047", "number": 300, "name": "Bright Sun" },
|
||||
{ "hexcode": "#facc15", "number": 400, "name": "Candlelight" },
|
||||
{ "hexcode": "#eab308", "number": 500, "name": "Corn" },
|
||||
{ "hexcode": "#ca8a04", "number": 600, "name": "Pirate Gold" },
|
||||
{ "hexcode": "#a16207", "number": 700, "name": "Mai Tai" },
|
||||
{ "hexcode": "#854d0e", "number": 800, "name": "Korma" },
|
||||
{ "hexcode": "#713f12", "number": 900, "name": "Sepia" },
|
||||
{ "hexcode": "#422006", "number": 950, "name": "Dark Ebony" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "lime",
|
||||
"palettes": [
|
||||
{ "hexcode": "#f7fee7", "number": 50, "name": "Spring Sun" },
|
||||
{ "hexcode": "#ecfccb", "number": 100, "name": "Chiffon" },
|
||||
{ "hexcode": "#d9f99d", "number": 200, "name": "Gossip" },
|
||||
{ "hexcode": "#bef264", "number": 300, "name": "Sulu" },
|
||||
{ "hexcode": "#a3e635", "number": 400, "name": "Conifer" },
|
||||
{ "hexcode": "#84cc16", "number": 500, "name": "Lima" },
|
||||
{ "hexcode": "#65a30d", "number": 600, "name": "Christi" },
|
||||
{ "hexcode": "#4d7c0f", "number": 700, "name": "Green Leaf" },
|
||||
{ "hexcode": "#3f6212", "number": 800, "name": "Dell" },
|
||||
{ "hexcode": "#365314", "number": 900, "name": "Clover" },
|
||||
{ "hexcode": "#1a2e05", "number": 950, "name": "Deep Forest Green" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "green",
|
||||
"palettes": [
|
||||
{ "hexcode": "#f0fdf4", "number": 50, "name": "Ottoman" },
|
||||
{ "hexcode": "#dcfce7", "number": 100, "name": "Blue Romance" },
|
||||
{ "hexcode": "#bbf7d0", "number": 200, "name": "Magic Mint" },
|
||||
{ "hexcode": "#86efac", "number": 300, "name": "Algae Green" },
|
||||
{ "hexcode": "#4ade80", "number": 400, "name": "Emerald" },
|
||||
{ "hexcode": "#22c55e", "number": 500, "name": "Malachite" },
|
||||
{ "hexcode": "#16a34a", "number": 600, "name": "Salem" },
|
||||
{ "hexcode": "#15803d", "number": 700, "name": "Jewel" },
|
||||
{ "hexcode": "#166534", "number": 800, "name": "Jewel" },
|
||||
{ "hexcode": "#14532d", "number": 900, "name": "Green Pea" },
|
||||
{ "hexcode": "#052e16", "number": 950, "name": "English Holly" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "emerald",
|
||||
"palettes": [
|
||||
{ "hexcode": "#ecfdf5", "number": 50, "name": "White Ice" },
|
||||
{ "hexcode": "#d1fae5", "number": 100, "name": "Granny Apple" },
|
||||
{ "hexcode": "#a7f3d0", "number": 200, "name": "Magic Mint" },
|
||||
{ "hexcode": "#6ee7b7", "number": 300, "name": "Bermuda" },
|
||||
{ "hexcode": "#34d399", "number": 400, "name": "Shamrock" },
|
||||
{ "hexcode": "#10b981", "number": 500, "name": "Mountain Meadow" },
|
||||
{ "hexcode": "#059669", "number": 600, "name": "Green Haze" },
|
||||
{ "hexcode": "#047857", "number": 700, "name": "Watercourse" },
|
||||
{ "hexcode": "#065f46", "number": 800, "name": "Watercourse" },
|
||||
{ "hexcode": "#064e3b", "number": 900, "name": "Evening Sea" },
|
||||
{ "hexcode": "#022c22", "number": 950, "name": "Burnham" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "teal",
|
||||
"palettes": [
|
||||
{ "hexcode": "#f0fdfa", "number": 50, "name": "White Ice" },
|
||||
{ "hexcode": "#ccfbf1", "number": 100, "name": "Scandal" },
|
||||
{ "hexcode": "#99f6e4", "number": 200, "name": "Ice Cold" },
|
||||
{ "hexcode": "#5eead4", "number": 300, "name": "Turquoise Blue" },
|
||||
{ "hexcode": "#2dd4bf", "number": 400, "name": "Turquoise" },
|
||||
{ "hexcode": "#14b8a6", "number": 500, "name": "Java" },
|
||||
{ "hexcode": "#0d9488", "number": 600, "name": "Blue Chill" },
|
||||
{ "hexcode": "#0f766e", "number": 700, "name": "Genoa" },
|
||||
{ "hexcode": "#115e59", "number": 800, "name": "Eden" },
|
||||
{ "hexcode": "#134e4a", "number": 900, "name": "Eden" },
|
||||
{ "hexcode": "#042f2e", "number": 950, "name": "Tiber" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "cyan",
|
||||
"palettes": [
|
||||
{ "hexcode": "#ecfeff", "number": 50, "name": "Bubbles" },
|
||||
{ "hexcode": "#cffafe", "number": 100, "name": "Oyster Bay" },
|
||||
{ "hexcode": "#a5f3fc", "number": 200, "name": "Anakiwa" },
|
||||
{ "hexcode": "#67e8f9", "number": 300, "name": "Spray" },
|
||||
{ "hexcode": "#22d3ee", "number": 400, "name": "Bright Turquoise" },
|
||||
{ "hexcode": "#06b6d4", "number": 500, "name": "Cerulean" },
|
||||
{ "hexcode": "#0891b2", "number": 600, "name": "Bondi Blue" },
|
||||
{ "hexcode": "#0e7490", "number": 700, "name": "Blue Chill" },
|
||||
{ "hexcode": "#155e75", "number": 800, "name": "Blumine" },
|
||||
{ "hexcode": "#164e63", "number": 900, "name": "Chathams Blue" },
|
||||
{ "hexcode": "#083344", "number": 950, "name": "Tarawera" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "sky",
|
||||
"palettes": [
|
||||
{ "hexcode": "#f0f9ff", "number": 50, "name": "Alice Blue" },
|
||||
{ "hexcode": "#e0f2fe", "number": 100, "name": "Pattens Blue" },
|
||||
{ "hexcode": "#bae6fd", "number": 200, "name": "French Pass" },
|
||||
{ "hexcode": "#7dd3fc", "number": 300, "name": "Malibu" },
|
||||
{ "hexcode": "#38bdf8", "number": 400, "name": "Picton Blue" },
|
||||
{ "hexcode": "#0ea5e9", "number": 500, "name": "Cerulean" },
|
||||
{ "hexcode": "#0284c7", "number": 600, "name": "Lochmara" },
|
||||
{ "hexcode": "#0369a1", "number": 700, "name": "Bahama Blue" },
|
||||
{ "hexcode": "#075985", "number": 800, "name": "Venice Blue" },
|
||||
{ "hexcode": "#0c4a6e", "number": 900, "name": "Chathams Blue" },
|
||||
{ "hexcode": "#082f49", "number": 950, "name": "Blue Whale" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "blue",
|
||||
"palettes": [
|
||||
{ "hexcode": "#eff6ff", "number": 50, "name": "Zumthor" },
|
||||
{ "hexcode": "#dbeafe", "number": 100, "name": "Hawkes Blue" },
|
||||
{ "hexcode": "#bfdbfe", "number": 200, "name": "Tropical Blue" },
|
||||
{ "hexcode": "#93c5fd", "number": 300, "name": "Malibu" },
|
||||
{ "hexcode": "#60a5fa", "number": 400, "name": "Cornflower Blue" },
|
||||
{ "hexcode": "#3b82f6", "number": 500, "name": "Dodger Blue" },
|
||||
{ "hexcode": "#2563eb", "number": 600, "name": "Royal Blue" },
|
||||
{ "hexcode": "#1d4ed8", "number": 700, "name": "Cerulean Blue" },
|
||||
{ "hexcode": "#1e40af", "number": 800, "name": "Persian Blue" },
|
||||
{ "hexcode": "#1e3a8a", "number": 900, "name": "Bay of Many" },
|
||||
{ "hexcode": "#172554", "number": 950, "name": "Bunting" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "indigo",
|
||||
"palettes": [
|
||||
{ "hexcode": "#eef2ff", "number": 50, "name": "Zircon" },
|
||||
{ "hexcode": "#e0e7ff", "number": 100, "name": "Hawkes Blue" },
|
||||
{ "hexcode": "#c7d2fe", "number": 200, "name": "Periwinkle" },
|
||||
{ "hexcode": "#a5b4fc", "number": 300, "name": "Perano" },
|
||||
{ "hexcode": "#818cf8", "number": 400, "name": "Portage" },
|
||||
{ "hexcode": "#6366f1", "number": 500, "name": "Royal Blue" },
|
||||
{ "hexcode": "#4f46e5", "number": 600, "name": "Royal Blue" },
|
||||
{ "hexcode": "#4338ca", "number": 700, "name": "Governor Bay" },
|
||||
{ "hexcode": "#3730a3", "number": 800, "name": "Governor Bay" },
|
||||
{ "hexcode": "#312e81", "number": 900, "name": "Minsk" },
|
||||
{ "hexcode": "#1e1b4b", "number": 950, "name": "Port Gore" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "violet",
|
||||
"palettes": [
|
||||
{ "hexcode": "#f5f3ff", "number": 50, "name": "Titan White" },
|
||||
{ "hexcode": "#ede9fe", "number": 100, "name": "Titan White" },
|
||||
{ "hexcode": "#ddd6fe", "number": 200, "name": "Fog" },
|
||||
{ "hexcode": "#c4b5fd", "number": 300, "name": "Melrose" },
|
||||
{ "hexcode": "#a78bfa", "number": 400, "name": "Dull Lavender" },
|
||||
{ "hexcode": "#8b5cf6", "number": 500, "name": "Medium Purple" },
|
||||
{ "hexcode": "#7c3aed", "number": 600, "name": "Purple Heart" },
|
||||
{ "hexcode": "#6d28d9", "number": 700, "name": "Purple Heart" },
|
||||
{ "hexcode": "#5b21b6", "number": 800, "name": "Purple Heart" },
|
||||
{ "hexcode": "#4c1d95", "number": 900, "name": "Daisy Bush" },
|
||||
{ "hexcode": "#2e1065", "number": 950, "name": "Violent Violet" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "purple",
|
||||
"palettes": [
|
||||
{ "hexcode": "#faf5ff", "number": 50, "name": "Magnolia" },
|
||||
{ "hexcode": "#f3e8ff", "number": 100, "name": "Blue Chalk" },
|
||||
{ "hexcode": "#e9d5ff", "number": 200, "name": "Blue Chalk" },
|
||||
{ "hexcode": "#d8b4fe", "number": 300, "name": "Mauve" },
|
||||
{ "hexcode": "#c084fc", "number": 400, "name": "Heliotrope" },
|
||||
{ "hexcode": "#a855f7", "number": 500, "name": "Medium Purple" },
|
||||
{ "hexcode": "#9333ea", "number": 600, "name": "Electric Violet" },
|
||||
{ "hexcode": "#7e22ce", "number": 700, "name": "Purple Heart" },
|
||||
{ "hexcode": "#6b21a8", "number": 800, "name": "Seance" },
|
||||
{ "hexcode": "#581c87", "number": 900, "name": "Daisy Bush" },
|
||||
{ "hexcode": "#3b0764", "number": 950, "name": "Christalle" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "fuchsia",
|
||||
"palettes": [
|
||||
{ "hexcode": "#fdf4ff", "number": 50, "name": "White Pointer" },
|
||||
{ "hexcode": "#fae8ff", "number": 100, "name": "White Pointer" },
|
||||
{ "hexcode": "#f5d0fe", "number": 200, "name": "Mauve" },
|
||||
{ "hexcode": "#f0abfc", "number": 300, "name": "Mauve" },
|
||||
{ "hexcode": "#e879f9", "number": 400, "name": "Heliotrope" },
|
||||
{ "hexcode": "#d946ef", "number": 500, "name": "Heliotrope" },
|
||||
{ "hexcode": "#c026d3", "number": 600, "name": "Fuchsia Pink" },
|
||||
{ "hexcode": "#a21caf", "number": 700, "name": "Violet Eggplant" },
|
||||
{ "hexcode": "#86198f", "number": 800, "name": "Seance" },
|
||||
{ "hexcode": "#701a75", "number": 900, "name": "Seance" },
|
||||
{ "hexcode": "#4a044e", "number": 950, "name": "Clairvoyant" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "pink",
|
||||
"palettes": [
|
||||
{ "hexcode": "#fdf2f8", "number": 50, "name": "Wisp Pink" },
|
||||
{ "hexcode": "#fce7f3", "number": 100, "name": "Carousel Pink" },
|
||||
{ "hexcode": "#fbcfe8", "number": 200, "name": "Classic Rose" },
|
||||
{ "hexcode": "#f9a8d4", "number": 300, "name": "Lavender Pink" },
|
||||
{ "hexcode": "#f472b6", "number": 400, "name": "Persian Pink" },
|
||||
{ "hexcode": "#ec4899", "number": 500, "name": "Brilliant Rose" },
|
||||
{ "hexcode": "#db2777", "number": 600, "name": "Cerise" },
|
||||
{ "hexcode": "#be185d", "number": 700, "name": "Maroon Flush" },
|
||||
{ "hexcode": "#9d174d", "number": 800, "name": "Disco" },
|
||||
{ "hexcode": "#831843", "number": 900, "name": "Disco" },
|
||||
{ "hexcode": "#500724", "number": 950, "name": "Cab Sav" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "rose",
|
||||
"palettes": [
|
||||
{ "hexcode": "#fff1f2", "number": 50, "name": "Lavender blush" },
|
||||
{ "hexcode": "#ffe4e6", "number": 100, "name": "Cosmos" },
|
||||
{ "hexcode": "#fecdd3", "number": 200, "name": "Pastel Pink" },
|
||||
{ "hexcode": "#fda4af", "number": 300, "name": "Sweet Pink" },
|
||||
{ "hexcode": "#fb7185", "number": 400, "name": "Froly" },
|
||||
{ "hexcode": "#f43f5e", "number": 500, "name": "Radical Red" },
|
||||
{ "hexcode": "#e11d48", "number": 600, "name": "Amaranth" },
|
||||
{ "hexcode": "#be123c", "number": 700, "name": "Cardinal" },
|
||||
{ "hexcode": "#9f1239", "number": 800, "name": "Shiraz" },
|
||||
{ "hexcode": "#881337", "number": 900, "name": "Claret" },
|
||||
{ "hexcode": "#4c0519", "number": 950, "name": "Cab Sav" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,95 +0,0 @@
|
||||
import { isValidColor, getHsl, getDeltaE, transformHslToHex } from './color';
|
||||
import { getColorName } from './name';
|
||||
import type { ColorPaletteFamily, ColorPaletteFamilyWithNearestPalette } from './type';
|
||||
import defaultPalettes from './json/palette.json';
|
||||
|
||||
export function getNearestColorPaletteFamily(color: string, families: ColorPaletteFamily[]) {
|
||||
const familyWithConfig = families.map(family => {
|
||||
const palettes = family.palettes.map(palette => {
|
||||
return {
|
||||
...palette,
|
||||
delta: getDeltaE(color, palette.hexcode)
|
||||
};
|
||||
});
|
||||
|
||||
const nearestPalette = palettes.reduce((prev, curr) => (prev.delta < curr.delta ? prev : curr));
|
||||
|
||||
return {
|
||||
...family,
|
||||
palettes,
|
||||
nearestPalette
|
||||
};
|
||||
});
|
||||
|
||||
const nearestPaletteFamily = familyWithConfig.reduce((prev, curr) =>
|
||||
prev.nearestPalette.delta < curr.nearestPalette.delta ? prev : curr
|
||||
);
|
||||
|
||||
const { l } = getHsl(color);
|
||||
|
||||
const paletteFamily: ColorPaletteFamilyWithNearestPalette = {
|
||||
...nearestPaletteFamily,
|
||||
nearestLightnessPalette: nearestPaletteFamily.palettes.reduce((prev, curr) => {
|
||||
const { l: prevLightness } = getHsl(prev.hexcode);
|
||||
const { l: currLightness } = getHsl(curr.hexcode);
|
||||
|
||||
const deltaPrev = Math.abs(prevLightness - l);
|
||||
const deltaCurr = Math.abs(currLightness - l);
|
||||
|
||||
return deltaPrev < deltaCurr ? prev : curr;
|
||||
})
|
||||
};
|
||||
|
||||
return paletteFamily;
|
||||
}
|
||||
|
||||
export function getColorPaletteFamily(color: string, colorName: string) {
|
||||
if (!isValidColor(color)) {
|
||||
throw new Error('Invalid color, please check color value!');
|
||||
}
|
||||
|
||||
const { h: h1, s: s1 } = getHsl(color);
|
||||
|
||||
const { nearestLightnessPalette, palettes } = getNearestColorPaletteFamily(
|
||||
color,
|
||||
defaultPalettes as ColorPaletteFamily[]
|
||||
);
|
||||
|
||||
const { number, hexcode } = nearestLightnessPalette;
|
||||
|
||||
const { h: h2, s: s2 } = getHsl(hexcode);
|
||||
|
||||
const deltaH = h1 - h2 || h2;
|
||||
|
||||
const sRatio = s1 / s2;
|
||||
|
||||
const colorPaletteFamily: ColorPaletteFamily = {
|
||||
key: colorName,
|
||||
palettes: palettes.map(palette => {
|
||||
let hexValue = color;
|
||||
|
||||
const isSame = number === palette.number;
|
||||
|
||||
if (!isSame) {
|
||||
const { h: h3, s: s3, l } = getHsl(palette.hexcode);
|
||||
|
||||
const newH = deltaH < 0 ? h3 + deltaH : deltaH;
|
||||
const newS = s3 * sRatio;
|
||||
|
||||
hexValue = transformHslToHex({
|
||||
h: newH,
|
||||
s: newS,
|
||||
l
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
hexcode: hexValue,
|
||||
number: palette.number,
|
||||
name: getColorName(hexValue)
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
return colorPaletteFamily;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* the color palette number
|
||||
*/
|
||||
export type ColorPaletteNumber = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
|
||||
|
||||
/**
|
||||
* the color palette item
|
||||
*/
|
||||
export type ColorPaletteItem = {
|
||||
/**
|
||||
* the color hexcode
|
||||
*/
|
||||
hexcode: string;
|
||||
/**
|
||||
* the color number
|
||||
* @link {@link ColorPaletteNumber}
|
||||
*/
|
||||
number: ColorPaletteNumber;
|
||||
/**
|
||||
* the color name
|
||||
*/
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ColorPaletteFamily = {
|
||||
/**
|
||||
* the color palette family key
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* the color palette family's palettes
|
||||
*/
|
||||
palettes: ColorPaletteItem[];
|
||||
};
|
||||
|
||||
export type ColorPaletteWithDelta = ColorPaletteItem & {
|
||||
delta: number;
|
||||
};
|
||||
|
||||
export type ColorPaletteItemWithName = ColorPaletteItem & {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ColorPaletteFamilyWithNearestPalette = ColorPaletteFamily & {
|
||||
nearestPalette: ColorPaletteWithDelta;
|
||||
nearestLightnessPalette: ColorPaletteWithDelta;
|
||||
};
|
||||
|
||||
export type ColorPalette = ColorPaletteFamily & {
|
||||
/**
|
||||
* the color map of the palette
|
||||
*/
|
||||
colorMap: Map<ColorPaletteNumber, ColorPaletteItem>;
|
||||
/**
|
||||
* the main color of the palette
|
||||
* @description which number is 500
|
||||
*/
|
||||
main: ColorPaletteItemWithName;
|
||||
/**
|
||||
* the match color of the palette
|
||||
*/
|
||||
match: ColorPaletteItemWithName;
|
||||
};
|
||||
@@ -1,17 +1,16 @@
|
||||
{
|
||||
"name": "@sa/color-palette",
|
||||
"version": "1.0.0",
|
||||
"name": "@sa/color",
|
||||
"version": "1.3.7",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"./src/*"
|
||||
]
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"colord": "2.9.3"
|
||||
}
|
||||
}
|
||||
2
packages/color/src/constant/index.ts
Normal file
2
packages/color/src/constant/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './name';
|
||||
export * from './palette';
|
||||
1579
packages/color/src/constant/name.ts
Normal file
1579
packages/color/src/constant/name.ts
Normal file
File diff suppressed because it is too large
Load Diff
356
packages/color/src/constant/palette.ts
Normal file
356
packages/color/src/constant/palette.ts
Normal file
@@ -0,0 +1,356 @@
|
||||
import type { ColorPaletteFamily } from '../types';
|
||||
|
||||
export const colorPalettes: ColorPaletteFamily[] = [
|
||||
{
|
||||
name: 'Slate',
|
||||
palettes: [
|
||||
{ hex: '#f8fafc', number: 50 },
|
||||
{ hex: '#f1f5f9', number: 100 },
|
||||
{ hex: '#e2e8f0', number: 200 },
|
||||
{ hex: '#cbd5e1', number: 300 },
|
||||
{ hex: '#94a3b8', number: 400 },
|
||||
{ hex: '#64748b', number: 500 },
|
||||
{ hex: '#475569', number: 600 },
|
||||
{ hex: '#334155', number: 700 },
|
||||
{ hex: '#1e293b', number: 800 },
|
||||
{ hex: '#0f172a', number: 900 },
|
||||
{ hex: '#020617', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Gray',
|
||||
palettes: [
|
||||
{ hex: '#f9fafb', number: 50 },
|
||||
{ hex: '#f3f4f6', number: 100 },
|
||||
{ hex: '#e5e7eb', number: 200 },
|
||||
{ hex: '#d1d5db', number: 300 },
|
||||
{ hex: '#9ca3af', number: 400 },
|
||||
{ hex: '#6b7280', number: 500 },
|
||||
{ hex: '#4b5563', number: 600 },
|
||||
{ hex: '#374151', number: 700 },
|
||||
{ hex: '#1f2937', number: 800 },
|
||||
{ hex: '#111827', number: 900 },
|
||||
{ hex: '#030712', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Zinc',
|
||||
palettes: [
|
||||
{ hex: '#fafafa', number: 50 },
|
||||
{ hex: '#f4f4f5', number: 100 },
|
||||
{ hex: '#e4e4e7', number: 200 },
|
||||
{ hex: '#d4d4d8', number: 300 },
|
||||
{ hex: '#a1a1aa', number: 400 },
|
||||
{ hex: '#71717a', number: 500 },
|
||||
{ hex: '#52525b', number: 600 },
|
||||
{ hex: '#3f3f46', number: 700 },
|
||||
{ hex: '#27272a', number: 800 },
|
||||
{ hex: '#18181b', number: 900 },
|
||||
{ hex: '#09090b', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Neutral',
|
||||
palettes: [
|
||||
{ hex: '#fafafa', number: 50 },
|
||||
{ hex: '#f5f5f5', number: 100 },
|
||||
{ hex: '#e5e5e5', number: 200 },
|
||||
{ hex: '#d4d4d4', number: 300 },
|
||||
{ hex: '#a3a3a3', number: 400 },
|
||||
{ hex: '#737373', number: 500 },
|
||||
{ hex: '#525252', number: 600 },
|
||||
{ hex: '#404040', number: 700 },
|
||||
{ hex: '#262626', number: 800 },
|
||||
{ hex: '#171717', number: 900 },
|
||||
{ hex: '#0a0a0a', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Stone',
|
||||
palettes: [
|
||||
{ hex: '#fafaf9', number: 50 },
|
||||
{ hex: '#f5f5f4', number: 100 },
|
||||
{ hex: '#e7e5e4', number: 200 },
|
||||
{ hex: '#d6d3d1', number: 300 },
|
||||
{ hex: '#a8a29e', number: 400 },
|
||||
{ hex: '#78716c', number: 500 },
|
||||
{ hex: '#57534e', number: 600 },
|
||||
{ hex: '#44403c', number: 700 },
|
||||
{ hex: '#292524', number: 800 },
|
||||
{ hex: '#1c1917', number: 900 },
|
||||
{ hex: '#0c0a09', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Red',
|
||||
palettes: [
|
||||
{ hex: '#fef2f2', number: 50 },
|
||||
{ hex: '#fee2e2', number: 100 },
|
||||
{ hex: '#fecaca', number: 200 },
|
||||
{ hex: '#fca5a5', number: 300 },
|
||||
{ hex: '#f87171', number: 400 },
|
||||
{ hex: '#ef4444', number: 500 },
|
||||
{ hex: '#dc2626', number: 600 },
|
||||
{ hex: '#b91c1c', number: 700 },
|
||||
{ hex: '#991b1b', number: 800 },
|
||||
{ hex: '#7f1d1d', number: 900 },
|
||||
{ hex: '#450a0a', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Orange',
|
||||
palettes: [
|
||||
{ hex: '#fff7ed', number: 50 },
|
||||
{ hex: '#ffedd5', number: 100 },
|
||||
{ hex: '#fed7aa', number: 200 },
|
||||
{ hex: '#fdba74', number: 300 },
|
||||
{ hex: '#fb923c', number: 400 },
|
||||
{ hex: '#f97316', number: 500 },
|
||||
{ hex: '#ea580c', number: 600 },
|
||||
{ hex: '#c2410c', number: 700 },
|
||||
{ hex: '#9a3412', number: 800 },
|
||||
{ hex: '#7c2d12', number: 900 },
|
||||
{ hex: '#431407', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Amber',
|
||||
palettes: [
|
||||
{ hex: '#fffbeb', number: 50 },
|
||||
{ hex: '#fef3c7', number: 100 },
|
||||
{ hex: '#fde68a', number: 200 },
|
||||
{ hex: '#fcd34d', number: 300 },
|
||||
{ hex: '#fbbf24', number: 400 },
|
||||
{ hex: '#f59e0b', number: 500 },
|
||||
{ hex: '#d97706', number: 600 },
|
||||
{ hex: '#b45309', number: 700 },
|
||||
{ hex: '#92400e', number: 800 },
|
||||
{ hex: '#78350f', number: 900 },
|
||||
{ hex: '#451a03', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Yellow',
|
||||
palettes: [
|
||||
{ hex: '#fefce8', number: 50 },
|
||||
{ hex: '#fef9c3', number: 100 },
|
||||
{ hex: '#fef08a', number: 200 },
|
||||
{ hex: '#fde047', number: 300 },
|
||||
{ hex: '#facc15', number: 400 },
|
||||
{ hex: '#eab308', number: 500 },
|
||||
{ hex: '#ca8a04', number: 600 },
|
||||
{ hex: '#a16207', number: 700 },
|
||||
{ hex: '#854d0e', number: 800 },
|
||||
{ hex: '#713f12', number: 900 },
|
||||
{ hex: '#422006', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Lime',
|
||||
palettes: [
|
||||
{ hex: '#f7fee7', number: 50 },
|
||||
{ hex: '#ecfccb', number: 100 },
|
||||
{ hex: '#d9f99d', number: 200 },
|
||||
{ hex: '#bef264', number: 300 },
|
||||
{ hex: '#a3e635', number: 400 },
|
||||
{ hex: '#84cc16', number: 500 },
|
||||
{ hex: '#65a30d', number: 600 },
|
||||
{ hex: '#4d7c0f', number: 700 },
|
||||
{ hex: '#3f6212', number: 800 },
|
||||
{ hex: '#365314', number: 900 },
|
||||
{ hex: '#1a2e05', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Green',
|
||||
palettes: [
|
||||
{ hex: '#f0fdf4', number: 50 },
|
||||
{ hex: '#dcfce7', number: 100 },
|
||||
{ hex: '#bbf7d0', number: 200 },
|
||||
{ hex: '#86efac', number: 300 },
|
||||
{ hex: '#4ade80', number: 400 },
|
||||
{ hex: '#22c55e', number: 500 },
|
||||
{ hex: '#16a34a', number: 600 },
|
||||
{ hex: '#15803d', number: 700 },
|
||||
{ hex: '#166534', number: 800 },
|
||||
{ hex: '#14532d', number: 900 },
|
||||
{ hex: '#052e16', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Emerald',
|
||||
palettes: [
|
||||
{ hex: '#ecfdf5', number: 50 },
|
||||
{ hex: '#d1fae5', number: 100 },
|
||||
{ hex: '#a7f3d0', number: 200 },
|
||||
{ hex: '#6ee7b7', number: 300 },
|
||||
{ hex: '#34d399', number: 400 },
|
||||
{ hex: '#10b981', number: 500 },
|
||||
{ hex: '#059669', number: 600 },
|
||||
{ hex: '#047857', number: 700 },
|
||||
{ hex: '#065f46', number: 800 },
|
||||
{ hex: '#064e3b', number: 900 },
|
||||
{ hex: '#022c22', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Teal',
|
||||
palettes: [
|
||||
{ hex: '#f0fdfa', number: 50 },
|
||||
{ hex: '#ccfbf1', number: 100 },
|
||||
{ hex: '#99f6e4', number: 200 },
|
||||
{ hex: '#5eead4', number: 300 },
|
||||
{ hex: '#2dd4bf', number: 400 },
|
||||
{ hex: '#14b8a6', number: 500 },
|
||||
{ hex: '#0d9488', number: 600 },
|
||||
{ hex: '#0f766e', number: 700 },
|
||||
{ hex: '#115e59', number: 800 },
|
||||
{ hex: '#134e4a', number: 900 },
|
||||
{ hex: '#042f2e', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Cyan',
|
||||
palettes: [
|
||||
{ hex: '#ecfeff', number: 50 },
|
||||
{ hex: '#cffafe', number: 100 },
|
||||
{ hex: '#a5f3fc', number: 200 },
|
||||
{ hex: '#67e8f9', number: 300 },
|
||||
{ hex: '#22d3ee', number: 400 },
|
||||
{ hex: '#06b6d4', number: 500 },
|
||||
{ hex: '#0891b2', number: 600 },
|
||||
{ hex: '#0e7490', number: 700 },
|
||||
{ hex: '#155e75', number: 800 },
|
||||
{ hex: '#164e63', number: 900 },
|
||||
{ hex: '#083344', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Sky',
|
||||
palettes: [
|
||||
{ hex: '#f0f9ff', number: 50 },
|
||||
{ hex: '#e0f2fe', number: 100 },
|
||||
{ hex: '#bae6fd', number: 200 },
|
||||
{ hex: '#7dd3fc', number: 300 },
|
||||
{ hex: '#38bdf8', number: 400 },
|
||||
{ hex: '#0ea5e9', number: 500 },
|
||||
{ hex: '#0284c7', number: 600 },
|
||||
{ hex: '#0369a1', number: 700 },
|
||||
{ hex: '#075985', number: 800 },
|
||||
{ hex: '#0c4a6e', number: 900 },
|
||||
{ hex: '#082f49', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Blue',
|
||||
palettes: [
|
||||
{ hex: '#eff6ff', number: 50 },
|
||||
{ hex: '#dbeafe', number: 100 },
|
||||
{ hex: '#bfdbfe', number: 200 },
|
||||
{ hex: '#93c5fd', number: 300 },
|
||||
{ hex: '#60a5fa', number: 400 },
|
||||
{ hex: '#3b82f6', number: 500 },
|
||||
{ hex: '#2563eb', number: 600 },
|
||||
{ hex: '#1d4ed8', number: 700 },
|
||||
{ hex: '#1e40af', number: 800 },
|
||||
{ hex: '#1e3a8a', number: 900 },
|
||||
{ hex: '#172554', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Indigo',
|
||||
palettes: [
|
||||
{ hex: '#eef2ff', number: 50 },
|
||||
{ hex: '#e0e7ff', number: 100 },
|
||||
{ hex: '#c7d2fe', number: 200 },
|
||||
{ hex: '#a5b4fc', number: 300 },
|
||||
{ hex: '#818cf8', number: 400 },
|
||||
{ hex: '#6366f1', number: 500 },
|
||||
{ hex: '#4f46e5', number: 600 },
|
||||
{ hex: '#4338ca', number: 700 },
|
||||
{ hex: '#3730a3', number: 800 },
|
||||
{ hex: '#312e81', number: 900 },
|
||||
{ hex: '#1e1b4b', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Violet',
|
||||
palettes: [
|
||||
{ hex: '#f5f3ff', number: 50 },
|
||||
{ hex: '#ede9fe', number: 100 },
|
||||
{ hex: '#ddd6fe', number: 200 },
|
||||
{ hex: '#c4b5fd', number: 300 },
|
||||
{ hex: '#a78bfa', number: 400 },
|
||||
{ hex: '#8b5cf6', number: 500 },
|
||||
{ hex: '#7c3aed', number: 600 },
|
||||
{ hex: '#6d28d9', number: 700 },
|
||||
{ hex: '#5b21b6', number: 800 },
|
||||
{ hex: '#4c1d95', number: 900 },
|
||||
{ hex: '#2e1065', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Purple',
|
||||
palettes: [
|
||||
{ hex: '#faf5ff', number: 50 },
|
||||
{ hex: '#f3e8ff', number: 100 },
|
||||
{ hex: '#e9d5ff', number: 200 },
|
||||
{ hex: '#d8b4fe', number: 300 },
|
||||
{ hex: '#c084fc', number: 400 },
|
||||
{ hex: '#a855f7', number: 500 },
|
||||
{ hex: '#9333ea', number: 600 },
|
||||
{ hex: '#7e22ce', number: 700 },
|
||||
{ hex: '#6b21a8', number: 800 },
|
||||
{ hex: '#581c87', number: 900 },
|
||||
{ hex: '#3b0764', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Fuchsia',
|
||||
palettes: [
|
||||
{ hex: '#fdf4ff', number: 50 },
|
||||
{ hex: '#fae8ff', number: 100 },
|
||||
{ hex: '#f5d0fe', number: 200 },
|
||||
{ hex: '#f0abfc', number: 300 },
|
||||
{ hex: '#e879f9', number: 400 },
|
||||
{ hex: '#d946ef', number: 500 },
|
||||
{ hex: '#c026d3', number: 600 },
|
||||
{ hex: '#a21caf', number: 700 },
|
||||
{ hex: '#86198f', number: 800 },
|
||||
{ hex: '#701a75', number: 900 },
|
||||
{ hex: '#4a044e', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Pink',
|
||||
palettes: [
|
||||
{ hex: '#fdf2f8', number: 50 },
|
||||
{ hex: '#fce7f3', number: 100 },
|
||||
{ hex: '#fbcfe8', number: 200 },
|
||||
{ hex: '#f9a8d4', number: 300 },
|
||||
{ hex: '#f472b6', number: 400 },
|
||||
{ hex: '#ec4899', number: 500 },
|
||||
{ hex: '#db2777', number: 600 },
|
||||
{ hex: '#be185d', number: 700 },
|
||||
{ hex: '#9d174d', number: 800 },
|
||||
{ hex: '#831843', number: 900 },
|
||||
{ hex: '#500724', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Rose',
|
||||
palettes: [
|
||||
{ hex: '#fff1f2', number: 50 },
|
||||
{ hex: '#ffe4e6', number: 100 },
|
||||
{ hex: '#fecdd3', number: 200 },
|
||||
{ hex: '#fda4af', number: 300 },
|
||||
{ hex: '#fb7185', number: 400 },
|
||||
{ hex: '#f43f5e', number: 500 },
|
||||
{ hex: '#e11d48', number: 600 },
|
||||
{ hex: '#be123c', number: 700 },
|
||||
{ hex: '#9f1239', number: 800 },
|
||||
{ hex: '#881337', number: 900 },
|
||||
{ hex: '#4c0519', number: 950 }
|
||||
]
|
||||
}
|
||||
];
|
||||
7
packages/color/src/index.ts
Normal file
7
packages/color/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { colorPalettes } from './constant';
|
||||
|
||||
export * from './palette';
|
||||
export * from './shared';
|
||||
export { colorPalettes };
|
||||
|
||||
export * from './types';
|
||||
176
packages/color/src/palette/antd.ts
Normal file
176
packages/color/src/palette/antd.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import type { AnyColor, HsvColor } from 'colord';
|
||||
import { getHex, getHsv, isValidColor, mixColor } from '../shared';
|
||||
import type { ColorIndex } from '../types';
|
||||
|
||||
/** Hue step */
|
||||
const hueStep = 2;
|
||||
/** Saturation step, light color part */
|
||||
const saturationStep = 16;
|
||||
/** Saturation step, dark color part */
|
||||
const saturationStep2 = 5;
|
||||
/** Brightness step, light color part */
|
||||
const brightnessStep1 = 5;
|
||||
/** Brightness step, dark color part */
|
||||
const brightnessStep2 = 15;
|
||||
/** Light color count, main color up */
|
||||
const lightColorCount = 5;
|
||||
/** Dark color count, main color down */
|
||||
const darkColorCount = 4;
|
||||
|
||||
/**
|
||||
* Get AntD palette color by index
|
||||
*
|
||||
* @param color - Color
|
||||
* @param index - The color index of color palette (the main color index is 6)
|
||||
* @returns Hex color
|
||||
*/
|
||||
export function getAntDPaletteColorByIndex(color: AnyColor, index: ColorIndex): string {
|
||||
if (!isValidColor(color)) {
|
||||
throw new Error('invalid input color value');
|
||||
}
|
||||
|
||||
if (index === 6) {
|
||||
return getHex(color);
|
||||
}
|
||||
|
||||
const isLight = index < 6;
|
||||
const hsv = getHsv(color);
|
||||
const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1;
|
||||
|
||||
const newHsv: HsvColor = {
|
||||
h: getHue(hsv, i, isLight),
|
||||
s: getSaturation(hsv, i, isLight),
|
||||
v: getValue(hsv, i, isLight)
|
||||
};
|
||||
|
||||
return getHex(newHsv);
|
||||
}
|
||||
|
||||
/** Map of dark color index and opacity */
|
||||
const darkColorMap = [
|
||||
{ index: 7, opacity: 0.15 },
|
||||
{ index: 6, opacity: 0.25 },
|
||||
{ index: 5, opacity: 0.3 },
|
||||
{ index: 5, opacity: 0.45 },
|
||||
{ index: 5, opacity: 0.65 },
|
||||
{ index: 5, opacity: 0.85 },
|
||||
{ index: 5, opacity: 0.9 },
|
||||
{ index: 4, opacity: 0.93 },
|
||||
{ index: 3, opacity: 0.95 },
|
||||
{ index: 2, opacity: 0.97 },
|
||||
{ index: 1, opacity: 0.98 }
|
||||
];
|
||||
|
||||
/**
|
||||
* Get AntD color palette
|
||||
*
|
||||
* @param color - Color
|
||||
* @param darkTheme - Dark theme
|
||||
* @param darkThemeMixColor - Dark theme mix color (default: #141414)
|
||||
*/
|
||||
export function getAntDColorPalette(color: AnyColor, darkTheme = false, darkThemeMixColor = '#141414'): string[] {
|
||||
const indexes: ColorIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
|
||||
const patterns = indexes.map(index => getAntDPaletteColorByIndex(color, index));
|
||||
|
||||
if (darkTheme) {
|
||||
const darkPatterns = darkColorMap.map(({ index, opacity }) => {
|
||||
const darkColor = mixColor(darkThemeMixColor, patterns[index], opacity);
|
||||
|
||||
return darkColor;
|
||||
});
|
||||
|
||||
return darkPatterns.map(item => getHex(item));
|
||||
}
|
||||
|
||||
return patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hue
|
||||
*
|
||||
* @param hsv - Hsv format color
|
||||
* @param i - The relative distance from 6
|
||||
* @param isLight - Is light color
|
||||
*/
|
||||
function getHue(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
let hue: number;
|
||||
|
||||
const hsvH = Math.round(hsv.h);
|
||||
|
||||
if (hsvH >= 60 && hsvH <= 240) {
|
||||
hue = isLight ? hsvH - hueStep * i : hsvH + hueStep * i;
|
||||
} else {
|
||||
hue = isLight ? hsvH + hueStep * i : hsvH - hueStep * i;
|
||||
}
|
||||
|
||||
if (hue < 0) {
|
||||
hue += 360;
|
||||
}
|
||||
|
||||
if (hue >= 360) {
|
||||
hue -= 360;
|
||||
}
|
||||
|
||||
return hue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saturation
|
||||
*
|
||||
* @param hsv - Hsv format color
|
||||
* @param i - The relative distance from 6
|
||||
* @param isLight - Is light color
|
||||
*/
|
||||
function getSaturation(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
if (hsv.h === 0 && hsv.s === 0) {
|
||||
return hsv.s;
|
||||
}
|
||||
|
||||
let saturation: number;
|
||||
|
||||
if (isLight) {
|
||||
saturation = hsv.s - saturationStep * i;
|
||||
} else if (i === darkColorCount) {
|
||||
saturation = hsv.s + saturationStep;
|
||||
} else {
|
||||
saturation = hsv.s + saturationStep2 * i;
|
||||
}
|
||||
|
||||
if (saturation > 100) {
|
||||
saturation = 100;
|
||||
}
|
||||
|
||||
if (isLight && i === lightColorCount && saturation > 10) {
|
||||
saturation = 10;
|
||||
}
|
||||
|
||||
if (saturation < 6) {
|
||||
saturation = 6;
|
||||
}
|
||||
|
||||
return saturation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value of hsv
|
||||
*
|
||||
* @param hsv - Hsv format color
|
||||
* @param i - The relative distance from 6
|
||||
* @param isLight - Is light color
|
||||
*/
|
||||
function getValue(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
let value: number;
|
||||
|
||||
if (isLight) {
|
||||
value = hsv.v + brightnessStep1 * i;
|
||||
} else {
|
||||
value = hsv.v - brightnessStep2 * i;
|
||||
}
|
||||
|
||||
if (value > 100) {
|
||||
value = 100;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
45
packages/color/src/palette/index.ts
Normal file
45
packages/color/src/palette/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { AnyColor } from 'colord';
|
||||
import { getHex } from '../shared';
|
||||
import type { ColorPaletteNumber } from '../types';
|
||||
import { getRecommendedColorPalette } from './recommend';
|
||||
import { getAntDColorPalette } from './antd';
|
||||
|
||||
/**
|
||||
* get color palette by provided color
|
||||
*
|
||||
* @param color
|
||||
* @param recommended whether to get recommended color palette (the provided color may not be the main color)
|
||||
*/
|
||||
export function getColorPalette(color: AnyColor, recommended = false) {
|
||||
const colorMap = new Map<ColorPaletteNumber, string>();
|
||||
|
||||
if (recommended) {
|
||||
const colorPalette = getRecommendedColorPalette(getHex(color));
|
||||
colorPalette.palettes.forEach(palette => {
|
||||
colorMap.set(palette.number, palette.hex);
|
||||
});
|
||||
} else {
|
||||
const colors = getAntDColorPalette(color);
|
||||
|
||||
const colorNumbers: ColorPaletteNumber[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
|
||||
|
||||
colorNumbers.forEach((number, index) => {
|
||||
colorMap.set(number, colors[index]);
|
||||
});
|
||||
}
|
||||
|
||||
return colorMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color palette color by number
|
||||
*
|
||||
* @param color the provided color
|
||||
* @param number the color palette number
|
||||
* @param recommended whether to get recommended color palette (the provided color may not be the main color)
|
||||
*/
|
||||
export function getPaletteColorByNumber(color: AnyColor, number: ColorPaletteNumber, recommended = false) {
|
||||
const colorMap = getColorPalette(color, recommended);
|
||||
|
||||
return colorMap.get(number as ColorPaletteNumber)!;
|
||||
}
|
||||
152
packages/color/src/palette/recommend.ts
Normal file
152
packages/color/src/palette/recommend.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { getColorName, getDeltaE, getHsl, isValidColor, transformHslToHex } from '../shared';
|
||||
import { colorPalettes } from '../constant';
|
||||
import type {
|
||||
ColorPalette,
|
||||
ColorPaletteFamily,
|
||||
ColorPaletteFamilyWithNearestPalette,
|
||||
ColorPaletteMatch,
|
||||
ColorPaletteNumber
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
* get recommended color palette by provided color
|
||||
*
|
||||
* @param color the provided color
|
||||
*/
|
||||
export function getRecommendedColorPalette(color: string) {
|
||||
const colorPaletteFamily = getRecommendedColorPaletteFamily(color);
|
||||
|
||||
const colorMap = new Map<ColorPaletteNumber, ColorPalette>();
|
||||
|
||||
colorPaletteFamily.palettes.forEach(palette => {
|
||||
colorMap.set(palette.number, palette);
|
||||
});
|
||||
|
||||
const mainColor = colorMap.get(500)!;
|
||||
const matchColor = colorPaletteFamily.palettes.find(palette => palette.hex === color)!;
|
||||
|
||||
const colorPalette: ColorPaletteMatch = {
|
||||
...colorPaletteFamily,
|
||||
colorMap,
|
||||
main: mainColor,
|
||||
match: matchColor
|
||||
};
|
||||
|
||||
return colorPalette;
|
||||
}
|
||||
|
||||
/**
|
||||
* get recommended palette color by provided color
|
||||
*
|
||||
* @param color the provided color
|
||||
* @param number the color palette number
|
||||
*/
|
||||
export function getRecommendedPaletteColorByNumber(color: string, number: ColorPaletteNumber) {
|
||||
const colorPalette = getRecommendedColorPalette(color);
|
||||
|
||||
const { hex } = colorPalette.colorMap.get(number)!;
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color palette family by provided color and color name
|
||||
*
|
||||
* @param color the provided color
|
||||
*/
|
||||
export function getRecommendedColorPaletteFamily(color: string) {
|
||||
if (!isValidColor(color)) {
|
||||
throw new Error('Invalid color, please check color value!');
|
||||
}
|
||||
|
||||
let colorName = getColorName(color);
|
||||
|
||||
colorName = colorName.toLowerCase().replace(/\s/g, '-');
|
||||
|
||||
const { h: h1, s: s1 } = getHsl(color);
|
||||
|
||||
const { nearestLightnessPalette, palettes } = getNearestColorPaletteFamily(color, colorPalettes);
|
||||
|
||||
const { number, hex } = nearestLightnessPalette;
|
||||
|
||||
const { h: h2, s: s2 } = getHsl(hex);
|
||||
|
||||
const deltaH = h1 - h2;
|
||||
|
||||
const sRatio = s1 / s2;
|
||||
|
||||
const colorPaletteFamily: ColorPaletteFamily = {
|
||||
name: colorName,
|
||||
palettes: palettes.map(palette => {
|
||||
let hexValue = color;
|
||||
|
||||
const isSame = number === palette.number;
|
||||
|
||||
if (!isSame) {
|
||||
const { h: h3, s: s3, l } = getHsl(palette.hex);
|
||||
|
||||
const newH = deltaH < 0 ? h3 + deltaH : h3 - deltaH;
|
||||
const newS = s3 * sRatio;
|
||||
|
||||
hexValue = transformHslToHex({
|
||||
h: newH,
|
||||
s: newS,
|
||||
l
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
hex: hexValue,
|
||||
number: palette.number
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
return colorPaletteFamily;
|
||||
}
|
||||
|
||||
/**
|
||||
* get nearest color palette family
|
||||
*
|
||||
* @param color color
|
||||
* @param families color palette families
|
||||
*/
|
||||
function getNearestColorPaletteFamily(color: string, families: ColorPaletteFamily[]) {
|
||||
const familyWithConfig = families.map(family => {
|
||||
const palettes = family.palettes.map(palette => {
|
||||
return {
|
||||
...palette,
|
||||
delta: getDeltaE(color, palette.hex)
|
||||
};
|
||||
});
|
||||
|
||||
const nearestPalette = palettes.reduce((prev, curr) => (prev.delta < curr.delta ? prev : curr));
|
||||
|
||||
return {
|
||||
...family,
|
||||
palettes,
|
||||
nearestPalette
|
||||
};
|
||||
});
|
||||
|
||||
const nearestPaletteFamily = familyWithConfig.reduce((prev, curr) =>
|
||||
prev.nearestPalette.delta < curr.nearestPalette.delta ? prev : curr
|
||||
);
|
||||
|
||||
const { l } = getHsl(color);
|
||||
|
||||
const paletteFamily: ColorPaletteFamilyWithNearestPalette = {
|
||||
...nearestPaletteFamily,
|
||||
nearestLightnessPalette: nearestPaletteFamily.palettes.reduce((prev, curr) => {
|
||||
const { l: prevLightness } = getHsl(prev.hex);
|
||||
const { l: currLightness } = getHsl(curr.hex);
|
||||
|
||||
const deltaPrev = Math.abs(prevLightness - l);
|
||||
const deltaCurr = Math.abs(currLightness - l);
|
||||
|
||||
return deltaPrev < deltaCurr ? prev : curr;
|
||||
})
|
||||
};
|
||||
|
||||
return paletteFamily;
|
||||
}
|
||||
93
packages/color/src/shared/colord.ts
Normal file
93
packages/color/src/shared/colord.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { colord, extend } from 'colord';
|
||||
import namesPlugin from 'colord/plugins/names';
|
||||
import mixPlugin from 'colord/plugins/mix';
|
||||
import labPlugin from 'colord/plugins/lab';
|
||||
import type { AnyColor, HslColor, RgbColor } from 'colord';
|
||||
|
||||
extend([namesPlugin, mixPlugin, labPlugin]);
|
||||
|
||||
export function isValidColor(color: AnyColor) {
|
||||
return colord(color).isValid();
|
||||
}
|
||||
|
||||
export function getHex(color: AnyColor) {
|
||||
return colord(color).toHex();
|
||||
}
|
||||
|
||||
export function getRgb(color: AnyColor) {
|
||||
return colord(color).toRgb();
|
||||
}
|
||||
|
||||
export function getHsl(color: AnyColor) {
|
||||
return colord(color).toHsl();
|
||||
}
|
||||
|
||||
export function getHsv(color: AnyColor) {
|
||||
return colord(color).toHsv();
|
||||
}
|
||||
|
||||
export function getDeltaE(color1: AnyColor, color2: AnyColor) {
|
||||
return colord(color1).delta(color2);
|
||||
}
|
||||
|
||||
export function transformHslToHex(color: HslColor) {
|
||||
return colord(color).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add color alpha
|
||||
*
|
||||
* @param color - Color
|
||||
* @param alpha - Alpha (0 - 1)
|
||||
*/
|
||||
export function addColorAlpha(color: AnyColor, alpha: number) {
|
||||
return colord(color).alpha(alpha).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix color
|
||||
*
|
||||
* @param firstColor - First color
|
||||
* @param secondColor - Second color
|
||||
* @param ratio - The ratio of the second color (0 - 1)
|
||||
*/
|
||||
export function mixColor(firstColor: AnyColor, secondColor: AnyColor, ratio: number) {
|
||||
return colord(firstColor).mix(secondColor, ratio).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform color with opacity to similar color without opacity
|
||||
*
|
||||
* @param color - Color
|
||||
* @param alpha - Alpha (0 - 1)
|
||||
* @param bgColor Background color (usually white or black)
|
||||
*/
|
||||
export function transformColorWithOpacity(color: AnyColor, alpha: number, bgColor = '#ffffff') {
|
||||
const originColor = addColorAlpha(color, alpha);
|
||||
const { r: oR, g: oG, b: oB } = colord(originColor).toRgb();
|
||||
|
||||
const { r: bgR, g: bgG, b: bgB } = colord(bgColor).toRgb();
|
||||
|
||||
function calRgb(or: number, bg: number, al: number) {
|
||||
return bg + (or - bg) * al;
|
||||
}
|
||||
|
||||
const resultRgb: RgbColor = {
|
||||
r: calRgb(oR, bgR, alpha),
|
||||
g: calRgb(oG, bgG, alpha),
|
||||
b: calRgb(oB, bgB, alpha)
|
||||
};
|
||||
|
||||
return colord(resultRgb).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is white color
|
||||
*
|
||||
* @param color - Color
|
||||
*/
|
||||
export function isWhiteColor(color: AnyColor) {
|
||||
return colord(color).isEqual('#ffffff');
|
||||
}
|
||||
|
||||
export { colord };
|
||||
2
packages/color/src/shared/index.ts
Normal file
2
packages/color/src/shared/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './colord';
|
||||
export * from './name';
|
||||
@@ -1,6 +1,11 @@
|
||||
import { getHex, getRgb, getHsl } from './color';
|
||||
import colorNames from './json/color-name.json';
|
||||
import { colorNames } from '../constant';
|
||||
import { getHex, getHsl, getRgb } from './colord';
|
||||
|
||||
/**
|
||||
* Get color name
|
||||
*
|
||||
* @param color
|
||||
*/
|
||||
export function getColorName(color: string) {
|
||||
const hex = getHex(color);
|
||||
const rgb = getRgb(color);
|
||||
@@ -17,15 +22,13 @@ export function getColorName(color: string) {
|
||||
colorNames.some((item, index) => {
|
||||
const [hexValue, colorName] = item;
|
||||
|
||||
const hexcode = `#${hexValue}`;
|
||||
|
||||
const match = hex === hexcode;
|
||||
const match = hex === hexValue;
|
||||
|
||||
if (match) {
|
||||
name = colorName;
|
||||
} else {
|
||||
const { r, g, b } = getRgb(hexcode);
|
||||
const { h, s, l } = getHsl(hexcode);
|
||||
const { r, g, b } = getRgb(hexValue);
|
||||
const { h, s, l } = getHsl(hexValue);
|
||||
|
||||
ndf1 = (rgb.r - r) ** 2 + (rgb.g - g) ** 2 + (rgb.b - b) ** 2;
|
||||
ndf2 = (hsl.h - h) ** 2 + (hsl.s - s) ** 2 + (hsl.l - l) ** 2;
|
||||
@@ -40,7 +43,7 @@ export function getColorName(color: string) {
|
||||
return match;
|
||||
});
|
||||
|
||||
name = cl < 0 ? 'Invalid Color' : colorNames[cl][1];
|
||||
name = colorNames[cl][1];
|
||||
|
||||
return name;
|
||||
}
|
||||
58
packages/color/src/types/index.ts
Normal file
58
packages/color/src/types/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* the color palette number
|
||||
*
|
||||
* the main color number is 500
|
||||
*/
|
||||
export type ColorPaletteNumber = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
|
||||
|
||||
/** the color palette */
|
||||
export type ColorPalette = {
|
||||
/** the color hex value */
|
||||
hex: string;
|
||||
/**
|
||||
* the color number
|
||||
*
|
||||
* - 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950
|
||||
*/
|
||||
number: ColorPaletteNumber;
|
||||
};
|
||||
|
||||
/** the color palette family */
|
||||
export type ColorPaletteFamily = {
|
||||
/** the color palette family name */
|
||||
name: string;
|
||||
/** the color palettes */
|
||||
palettes: ColorPalette[];
|
||||
};
|
||||
|
||||
/** the color palette with delta */
|
||||
export type ColorPaletteWithDelta = ColorPalette & {
|
||||
delta: number;
|
||||
};
|
||||
|
||||
/** the color palette family with nearest palette */
|
||||
export type ColorPaletteFamilyWithNearestPalette = ColorPaletteFamily & {
|
||||
nearestPalette: ColorPaletteWithDelta;
|
||||
nearestLightnessPalette: ColorPaletteWithDelta;
|
||||
};
|
||||
|
||||
/** the color palette match */
|
||||
export type ColorPaletteMatch = ColorPaletteFamily & {
|
||||
/** the color map of the palette */
|
||||
colorMap: Map<ColorPaletteNumber, ColorPalette>;
|
||||
/**
|
||||
* the main color of the palette
|
||||
*
|
||||
* which number is 500
|
||||
*/
|
||||
main: ColorPalette;
|
||||
/** the match color of the palette */
|
||||
match: ColorPalette;
|
||||
};
|
||||
|
||||
/**
|
||||
* The color index of color palette
|
||||
*
|
||||
* From left to right, the color is from light to dark, 6 is main color
|
||||
*/
|
||||
export type ColorIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
|
||||
20
packages/color/tsconfig.json
Normal file
20
packages/color/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import path from 'node:path';
|
||||
import { defineConfig } from 'vitepress';
|
||||
|
||||
export default defineConfig({
|
||||
title: 'Soybean Admin',
|
||||
description: '一个优雅、清新、漂亮的中后台模版',
|
||||
head: [
|
||||
['meta', { name: 'author', content: 'Soybean' }],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
name: 'keywords',
|
||||
content: 'soybean, soybean-admin, vite, vue, vue3, soybean-admin docs'
|
||||
}
|
||||
],
|
||||
['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no'
|
||||
}
|
||||
],
|
||||
['link', { rel: 'icon', href: '/favicon.ico' }]
|
||||
],
|
||||
srcDir: path.join(process.cwd(), 'packages/docs/src'),
|
||||
themeConfig: {
|
||||
logo: '/logo.svg',
|
||||
socialLinks: [{ icon: 'github', link: 'https://github.com/honghuangdc/soybean-admin' }],
|
||||
algolia: {
|
||||
appId: '98WN1RY04S',
|
||||
apiKey: '13e9f5767b774422a5880723d9c23265',
|
||||
indexName: 'soybean'
|
||||
},
|
||||
nav: [],
|
||||
sidebar: {}
|
||||
}
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
export const qqSvg = `
|
||||
<svg height="2500" viewBox="-1.94 0 124.879 145.085" width="2101" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="m60.503 142.237c-12.533 0-24.038-4.195-31.445-10.46-3.762 1.124-8.574 2.932-11.61 5.175-2.6 1.918-2.275 3.874-1.807 4.663 2.056 3.47 35.273 2.216 44.862 1.136zm0 0c12.535 0 24.039-4.195 31.447-10.46 3.76 1.124 8.573 2.932 11.61 5.175 2.598 1.918 2.274 3.874 1.805 4.663-2.056 3.47-35.272 2.216-44.862 1.136zm0 0"
|
||||
fill="#faab07"
|
||||
/>
|
||||
<path
|
||||
d="m60.576 67.119c20.698-.14 37.286-4.147 42.907-5.683 1.34-.367 2.056-1.024 2.056-1.024.005-.189.085-3.37.085-5.01 0-27.634-13.044-55.401-45.124-55.402-32.08.001-45.125 27.769-45.125 55.401 0 1.642.08 4.822.086 5.01 0 0 .583.615 1.65.913 5.19 1.444 22.09 5.65 43.312 5.795zm56.245 23.02c-1.283-4.129-3.034-8.944-4.808-13.568 0 0-1.02-.126-1.537.023-15.913 4.623-35.202 7.57-49.9 7.392h-.153c-14.616.175-33.774-2.737-49.634-7.315-.606-.175-1.802-.1-1.802-.1-1.774 4.624-3.525 9.44-4.808 13.568-6.119 19.69-4.136 27.838-2.627 28.02 3.239.392 12.606-14.821 12.606-14.821 0 15.459 13.957 39.195 45.918 39.413h.848c31.96-.218 45.917-23.954 45.917-39.413 0 0 9.368 15.213 12.607 14.822 1.508-.183 3.491-8.332-2.627-28.021"
|
||||
/>
|
||||
<path
|
||||
d="m49.085 40.824c-4.352.197-8.07-4.76-8.304-11.063-.236-6.305 3.098-11.576 7.45-11.773 4.347-.195 8.064 4.76 8.3 11.065.238 6.306-3.097 11.577-7.446 11.771m31.133-11.063c-.233 6.302-3.951 11.26-8.303 11.063-4.35-.195-7.684-5.465-7.446-11.77.236-6.305 3.952-11.26 8.3-11.066 4.352.197 7.686 5.468 7.449 11.773"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="m87.952 49.725c-1.162-2.575-12.875-5.445-27.374-5.445h-.156c-14.5 0-26.212 2.87-27.375 5.446a.863.863 0 0 0 -.085.367c0 .186.063.352.16.496.98 1.427 13.985 8.487 27.3 8.487h.156c13.314 0 26.319-7.058 27.299-8.487a.873.873 0 0 0 .16-.498.856.856 0 0 0 -.085-.365"
|
||||
fill="#faab07"
|
||||
/>
|
||||
<path
|
||||
d="m54.434 29.854c.199 2.49-1.167 4.702-3.046 4.943-1.883.242-3.568-1.58-3.768-4.07-.197-2.492 1.167-4.704 3.043-4.944 1.886-.244 3.574 1.58 3.771 4.07m11.956.833c.385-.689 3.004-4.312 8.427-2.993 1.425.347 2.084.857 2.223 1.057.205.296.262.718.053 1.286-.412 1.126-1.263 1.095-1.734.875-.305-.142-4.082-2.66-7.562 1.097-.24.257-.668.346-1.073.04-.407-.308-.574-.93-.334-1.362"
|
||||
/>
|
||||
<path
|
||||
d="m60.576 83.08h-.153c-9.996.12-22.116-1.204-33.854-3.518-1.004 5.818-1.61 13.132-1.09 21.853 1.316 22.043 14.407 35.9 34.614 36.1h.82c20.208-.2 33.298-14.057 34.616-36.1.52-8.723-.087-16.035-1.092-21.854-11.739 2.315-23.862 3.64-33.86 3.518"
|
||||
fill="#fff"
|
||||
/>
|
||||
<g fill="#eb1923">
|
||||
<path d="m32.102 81.235v21.693s9.937 2.004 19.893.616v-20.009c-6.307-.357-13.109-1.152-19.893-2.3" />
|
||||
<path
|
||||
d="m105.539 60.412s-19.33 6.102-44.963 6.275h-.153c-25.591-.172-44.896-6.255-44.962-6.275l-6.474 16.158c16.193 4.882 36.261 8.028 51.436 7.845h.153c15.175.183 35.242-2.963 51.437-7.845zm0 0"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,4 +0,0 @@
|
||||
import Theme from 'vitepress/theme';
|
||||
import './style.css';
|
||||
|
||||
export default Theme;
|
||||
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Customize default theme styling by overriding CSS variables:
|
||||
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
|
||||
*/
|
||||
|
||||
/**
|
||||
* Colors
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-c-brand: #646cff;
|
||||
--vp-c-brand-light: #747bff;
|
||||
--vp-c-brand-lighter: #9499ff;
|
||||
--vp-c-brand-lightest: #bcc0ff;
|
||||
--vp-c-brand-dark: #535bf2;
|
||||
--vp-c-brand-darker: #454ce1;
|
||||
--vp-c-brand-dimm: rgba(100, 108, 255, 0.08);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-button-brand-border: var(--vp-c-brand-light);
|
||||
--vp-button-brand-text: var(--vp-c-white);
|
||||
--vp-button-brand-bg: var(--vp-c-brand);
|
||||
--vp-button-brand-hover-border: var(--vp-c-brand-light);
|
||||
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-light);
|
||||
--vp-button-brand-active-border: var(--vp-c-brand-light);
|
||||
--vp-button-brand-active-text: var(--vp-c-white);
|
||||
--vp-button-brand-active-bg: var(--vp-button-brand-bg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Home
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||
120deg,
|
||||
var(--vp-c-brand-lightest) 30%,
|
||||
var(--vp-c-brand-darker)
|
||||
);
|
||||
|
||||
--vp-home-hero-image-background-image: linear-gradient(
|
||||
-45deg,
|
||||
var(--vp-c-brand-lightest) 30%,
|
||||
var(--vp-c-brand) 50%
|
||||
);
|
||||
--vp-home-hero-image-filter: blur(40px);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(56px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(72px);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Custom Block
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-custom-block-tip-border: var(--vp-c-brand);
|
||||
--vp-custom-block-tip-text: var(--vp-c-brand-darker);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-custom-block-tip-border: var(--vp-c-brand);
|
||||
--vp-custom-block-tip-text: var(--vp-c-brand-lightest);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Algolia
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.DocSearch {
|
||||
--docsearch-primary-color: var(--vp-c-brand) !important;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@sa/docs",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "vitepress dev",
|
||||
"build": "vitepress build",
|
||||
"serve": "vitepress serve"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "1.0.0-rc.24"
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
layout: home
|
||||
|
||||
title: Soybean Admin
|
||||
titleTemplate: 一个清新优雅的中后台模版
|
||||
|
||||
hero:
|
||||
name: Soybean Admin
|
||||
text: 清新优雅的中后台模版
|
||||
tagline: 基于 Vue3 + Vite3 + TS + NaiveUI + UnoCSS
|
||||
image:
|
||||
src: /logo.svg
|
||||
alt: Soybean Admin
|
||||
actions:
|
||||
- theme: brand
|
||||
text: 开始
|
||||
link: /guide/
|
||||
- theme: alt
|
||||
text: 介绍
|
||||
link: /guide/introduction
|
||||
- theme: alt
|
||||
text: 在 GitHub 上查看
|
||||
link: https://github.com/honghuangdc/soybean-admin
|
||||
|
||||
features:
|
||||
- icon: 🆕
|
||||
title: 最新流行技术栈
|
||||
details: 基于Vue3、Vite3、TS、NaiveUI和UnoCSS等最新技术栈开发
|
||||
- icon: 🦋
|
||||
title: 极高水准的代码规范
|
||||
details: 代码规范完善,代码结构清晰
|
||||
- icon: 🛠️
|
||||
title: 丰富的插件
|
||||
details: 常见的Web端插件示例实现
|
||||
- icon: 🔩
|
||||
title: 主题配置
|
||||
details: 丰富的主题配置及暗黑主题适配
|
||||
- icon: 🔗
|
||||
title: 基于文件的路由系统
|
||||
details: 自动生成路由声明、路由导入和路由模块
|
||||
- icon: 🔑
|
||||
title: 权限管理
|
||||
details: 完善的前后端权限管理方案
|
||||
---
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @type {import('eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
extends: [require.resolve('./ts.js'), require.resolve('./prettier.js')]
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* @type {import('eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
commonjs: true,
|
||||
es2024: true
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2024,
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
},
|
||||
sourceType: 'module'
|
||||
},
|
||||
ignorePatterns: [
|
||||
'node_modules',
|
||||
'*.min.*',
|
||||
'CHANGELOG.md',
|
||||
'dist',
|
||||
'LICENSE*',
|
||||
'output',
|
||||
'coverage',
|
||||
'public',
|
||||
'temp',
|
||||
'package-lock.json',
|
||||
'pnpm-lock.yaml',
|
||||
'yarn.lock',
|
||||
'__snapshots__',
|
||||
'!.github',
|
||||
'!.vitepress',
|
||||
'!.vscode'
|
||||
],
|
||||
plugins: ['n', 'promise'],
|
||||
extends: [require.resolve('../rules/all.js'), 'plugin:import/recommended'],
|
||||
rules: {
|
||||
// import
|
||||
'import/no-mutable-exports': 'error',
|
||||
'import/no-named-as-default': 'off'
|
||||
}
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
const prettierRules = require('../rules/prettier');
|
||||
|
||||
/**
|
||||
* @type {import('eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
extends: ['plugin:prettier/recommended'],
|
||||
rules: {
|
||||
'prettier/prettier': ['error', prettierRules]
|
||||
}
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* @type {import('eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [require.resolve('./js.js'), 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended'],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project: ['tsconfig.json', 'packages/*/tsconfig.json', 'examples/*/tsconfig.json', 'docs/*/tsconfig.json']
|
||||
}
|
||||
}
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.mts', '*.cts'],
|
||||
parser: '@typescript-eslint/parser'
|
||||
},
|
||||
{
|
||||
files: ['*.js', '*.mjs', '*.cjs', '*.cts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-var-requires': 'off'
|
||||
}
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
// TS
|
||||
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports', disallowTypeAnnotations: false }],
|
||||
'@typescript-eslint/no-empty-interface': [
|
||||
'error',
|
||||
{
|
||||
allowSingleExtends: true
|
||||
}
|
||||
],
|
||||
|
||||
// Override JS
|
||||
'no-redeclare': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
vars: 'all',
|
||||
args: 'all',
|
||||
ignoreRestSiblings: false,
|
||||
varsIgnorePattern: '^_',
|
||||
argsIgnorePattern: '^_'
|
||||
}
|
||||
],
|
||||
'no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-use-before-define': ['error', { functions: false, classes: false, variables: true }],
|
||||
'no-shadow': 'off',
|
||||
'@typescript-eslint/no-shadow': 'error',
|
||||
|
||||
// off
|
||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off'
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* @type {import('eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
extends: ['plugin:vue/vue3-recommended', require.resolve('./base.js')],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.vue'],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: {
|
||||
js: 'espree',
|
||||
jsx: 'espree',
|
||||
ts: '@typescript-eslint/parser',
|
||||
tsx: '@typescript-eslint/parser'
|
||||
},
|
||||
extraFileExtensions: ['.vue'],
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'no-undef': 'off' // TS will check un declared variables, if the script code is is in a .vue file, this rule should not disabled
|
||||
}
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off'
|
||||
}
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
const baseConfig = require('./configs/base');
|
||||
|
||||
/**
|
||||
* @type {import('eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = baseConfig;
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "eslint-config-sa",
|
||||
"version": "1.0.0",
|
||||
"description": "SoybeanAdmin's eslint config resets",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./vue": "./configs/vue.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/eslint": "8.44.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.9.0",
|
||||
"@typescript-eslint/parser": "6.9.0",
|
||||
"eslint": "8.52.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-import-resolver-typescript": "3.6.1",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"eslint-plugin-n": "16.2.0",
|
||||
"eslint-plugin-prettier": "5.0.1",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-vue": "9.18.0",
|
||||
"prettier": "3.0.3"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* @type {import('prettier').Options}
|
||||
*/
|
||||
module.exports = {
|
||||
printWidth: 120,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
quoteProps: 'as-needed',
|
||||
jsxSingleQuote: false,
|
||||
trailingComma: 'none',
|
||||
bracketSpacing: true,
|
||||
bracketSameLine: false,
|
||||
arrowParens: 'avoid',
|
||||
rangeStart: 0,
|
||||
rangeEnd: Number.POSITIVE_INFINITY,
|
||||
requirePragma: false,
|
||||
insertPragma: false,
|
||||
proseWrap: 'preserve',
|
||||
htmlWhitespaceSensitivity: 'ignore',
|
||||
vueIndentScriptAndStyle: false,
|
||||
endOfLine: 'lf',
|
||||
embeddedLanguageFormatting: 'auto',
|
||||
singleAttributePerLine: false
|
||||
};
|
||||
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"name": "@sa/hooks",
|
||||
"version": "1.0.0",
|
||||
"version": "1.3.7",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"./src/*"
|
||||
]
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/axios": "workspace:*",
|
||||
"@sa/utils": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import useBoolean from './use-boolean';
|
||||
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';
|
||||
|
||||
export { useBoolean, useLoading, useContext };
|
||||
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable };
|
||||
|
||||
export * from './use-signal';
|
||||
export * from './use-table';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* boolean
|
||||
* @param initValue init value
|
||||
* Boolean
|
||||
*
|
||||
* @param initValue Init value
|
||||
*/
|
||||
export default function useBoolean(initValue = false) {
|
||||
const bool = ref(initValue);
|
||||
|
||||
@@ -1,93 +1,81 @@
|
||||
import { inject, provide } from 'vue';
|
||||
import type { InjectionKey } from 'vue';
|
||||
|
||||
type ContextFn<T> = () => T;
|
||||
|
||||
/**
|
||||
* use context
|
||||
* @param contextName context name
|
||||
* @param fn context function
|
||||
* Use context
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // there are three vue files: A.vue, B.vue, C.vue, and A.vue is the parent component of B.vue and C.vue
|
||||
* ```ts
|
||||
* // there are three vue files: A.vue, B.vue, C.vue, and A.vue is the parent component of B.vue and C.vue
|
||||
*
|
||||
* // context.ts
|
||||
* import { ref } from 'vue';
|
||||
* import { useContext } from '@sa/hooks';
|
||||
* // context.ts
|
||||
* import { ref } from 'vue';
|
||||
* import { useContext } from '@sa/hooks';
|
||||
*
|
||||
* export const { setupStore, useStore } = useContext('demo', () => {
|
||||
* const count = ref(0);
|
||||
* export const { setupStore, useStore } = useContext('demo', () => {
|
||||
* const count = ref(0);
|
||||
*
|
||||
* function increment() {
|
||||
* count.value++;
|
||||
* }
|
||||
* function increment() {
|
||||
* count.value++;
|
||||
* }
|
||||
*
|
||||
* function decrement() {
|
||||
* count.value--;
|
||||
* }
|
||||
* function decrement() {
|
||||
* count.value--;
|
||||
* }
|
||||
*
|
||||
* return {
|
||||
* count,
|
||||
* increment,
|
||||
* decrement
|
||||
* };
|
||||
* })
|
||||
* ```
|
||||
* return {
|
||||
* count,
|
||||
* increment,
|
||||
* decrement
|
||||
* };
|
||||
* })
|
||||
* ``` // A.vue
|
||||
* ```vue
|
||||
* <template>
|
||||
* <div>A</div>
|
||||
* </template>
|
||||
* <script setup lang="ts">
|
||||
* import { setupStore } from './context';
|
||||
*
|
||||
* // A.vue
|
||||
* ```vue
|
||||
* <template>
|
||||
* <div>A</div>
|
||||
* </template>
|
||||
* <script setup lang="ts">
|
||||
* import { setupStore } from './context';
|
||||
* setupStore();
|
||||
* // const { increment } = setupStore(); // also can control the store in the parent component
|
||||
* </script>
|
||||
* ``` // B.vue
|
||||
* ```vue
|
||||
* <template>
|
||||
* <div>B</div>
|
||||
* </template>
|
||||
* <script setup lang="ts">
|
||||
* import { useStore } from './context';
|
||||
*
|
||||
* setupStore();
|
||||
* // const { increment } = setupStore(); // also can control the store in the parent component
|
||||
* </script>
|
||||
* ```
|
||||
* // B.vue
|
||||
* ```vue
|
||||
* <template>
|
||||
* <div>B</div>
|
||||
* </template>
|
||||
* <script setup lang="ts">
|
||||
* import { useStore } from './context';
|
||||
* const { count, increment } = useStore();
|
||||
* </script>
|
||||
* ```;
|
||||
*
|
||||
* const { count, increment } = useStore();
|
||||
* </script>
|
||||
* ```
|
||||
* // C.vue is same as B.vue
|
||||
*
|
||||
* // C.vue is same as B.vue
|
||||
* @param contextName Context name
|
||||
* @param fn Context function
|
||||
*/
|
||||
export default function useContext<T>(contextName: string, fn: ContextFn<T>) {
|
||||
const { useProvide, useInject } = createContext<T>(contextName);
|
||||
export default function useContext<T extends (...args: any[]) => any>(contextName: string, fn: T) {
|
||||
type Context = ReturnType<T>;
|
||||
|
||||
const context = fn();
|
||||
const { useProvide, useInject: useStore } = createContext<Context>(contextName);
|
||||
|
||||
/**
|
||||
* setup store in the parent component
|
||||
*/
|
||||
function setupStore() {
|
||||
function setupStore(...args: Parameters<T>) {
|
||||
const context: Context = fn(...args);
|
||||
return useProvide(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* use store in the child component
|
||||
*/
|
||||
function useStore() {
|
||||
return useInject();
|
||||
}
|
||||
|
||||
return {
|
||||
/** Setup store in the parent component */
|
||||
setupStore,
|
||||
/** Use store in the child component */
|
||||
useStore
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* create context
|
||||
*/
|
||||
/** Create context */
|
||||
function createContext<T>(contextName: string) {
|
||||
const injectKey: InjectionKey<T> = Symbol(contextName);
|
||||
|
||||
|
||||
49
packages/hooks/src/use-count-down.ts
Normal file
49
packages/hooks/src/use-count-down.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { computed, onScopeDispose, ref } from 'vue';
|
||||
import { useRafFn } from '@vueuse/core';
|
||||
|
||||
/**
|
||||
* count down
|
||||
*
|
||||
* @param seconds - count down seconds
|
||||
*/
|
||||
export default function useCountDown(seconds: number) {
|
||||
const FPS_PER_SECOND = 60;
|
||||
|
||||
const fps = ref(0);
|
||||
|
||||
const count = computed(() => Math.ceil(fps.value / FPS_PER_SECOND));
|
||||
|
||||
const isCounting = computed(() => fps.value > 0);
|
||||
|
||||
const { pause, resume } = useRafFn(
|
||||
() => {
|
||||
if (fps.value > 0) {
|
||||
fps.value -= 1;
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
function start(updateSeconds: number = seconds) {
|
||||
fps.value = FPS_PER_SECOND * updateSeconds;
|
||||
resume();
|
||||
}
|
||||
|
||||
function stop() {
|
||||
fps.value = 0;
|
||||
pause();
|
||||
}
|
||||
|
||||
onScopeDispose(() => {
|
||||
pause();
|
||||
});
|
||||
|
||||
return {
|
||||
count,
|
||||
isCounting,
|
||||
start,
|
||||
stop
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import useBoolean from './use-boolean';
|
||||
|
||||
/**
|
||||
* loading
|
||||
* @param initValue init value
|
||||
* Loading
|
||||
*
|
||||
* @param initValue Init value
|
||||
*/
|
||||
export default function useLoading(initValue = false) {
|
||||
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initValue);
|
||||
|
||||
79
packages/hooks/src/use-request.ts
Normal file
79
packages/hooks/src/use-request.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { ref } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { createFlatRequest } from '@sa/axios';
|
||||
import type {
|
||||
AxiosError,
|
||||
CreateAxiosDefaults,
|
||||
CustomAxiosRequestConfig,
|
||||
MappedType,
|
||||
RequestOption,
|
||||
ResponseType
|
||||
} from '@sa/axios';
|
||||
import useLoading from './use-loading';
|
||||
|
||||
export type HookRequestInstanceResponseSuccessData<T = any> = {
|
||||
data: Ref<T>;
|
||||
error: Ref<null>;
|
||||
};
|
||||
|
||||
export type HookRequestInstanceResponseFailData<ResponseData = any> = {
|
||||
data: Ref<null>;
|
||||
error: Ref<AxiosError<ResponseData>>;
|
||||
};
|
||||
|
||||
export type HookRequestInstanceResponseData<T = any, ResponseData = any> = {
|
||||
loading: Ref<boolean>;
|
||||
} & (HookRequestInstanceResponseSuccessData<T> | HookRequestInstanceResponseFailData<ResponseData>);
|
||||
|
||||
export interface HookRequestInstance<ResponseData = any> {
|
||||
<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
): HookRequestInstanceResponseData<MappedType<R, T>, ResponseData>;
|
||||
cancelRequest: (requestId: string) => void;
|
||||
cancelAllRequest: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a hook request instance
|
||||
*
|
||||
* @param axiosConfig
|
||||
* @param options
|
||||
*/
|
||||
export default function createHookRequest<ResponseData = any>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const request = createFlatRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const hookRequest: HookRequestInstance<ResponseData> = function hookRequest<T = any, 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>;
|
||||
|
||||
startLoading();
|
||||
|
||||
request(config).then(res => {
|
||||
if (res.data) {
|
||||
data.value = res.data;
|
||||
} else {
|
||||
error.value = res.error;
|
||||
}
|
||||
|
||||
endLoading();
|
||||
});
|
||||
|
||||
return {
|
||||
loading,
|
||||
data,
|
||||
error
|
||||
};
|
||||
} as HookRequestInstance<ResponseData>;
|
||||
|
||||
hookRequest.cancelRequest = request.cancelRequest;
|
||||
hookRequest.cancelAllRequest = request.cancelAllRequest;
|
||||
|
||||
return hookRequest;
|
||||
}
|
||||
144
packages/hooks/src/use-signal.ts
Normal file
144
packages/hooks/src/use-signal.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
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;
|
||||
}
|
||||
50
packages/hooks/src/use-svg-icon-render.ts
Normal file
50
packages/hooks/src/use-svg-icon-render.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { h } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
/**
|
||||
* Svg icon render hook
|
||||
*
|
||||
* @param SvgIcon Svg icon component
|
||||
*/
|
||||
export default function useSvgIconRender(SvgIcon: Component) {
|
||||
interface IconConfig {
|
||||
/** Iconify icon name */
|
||||
icon?: string;
|
||||
/** Local icon name */
|
||||
localIcon?: string;
|
||||
/** Icon color */
|
||||
color?: string;
|
||||
/** Icon size */
|
||||
fontSize?: number;
|
||||
}
|
||||
|
||||
type IconStyle = Partial<Pick<CSSStyleDeclaration, 'color' | 'fontSize'>>;
|
||||
|
||||
/**
|
||||
* Svg icon VNode
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
const SvgIconVNode = (config: IconConfig) => {
|
||||
const { color, fontSize, icon, localIcon } = config;
|
||||
|
||||
const style: IconStyle = {};
|
||||
|
||||
if (color) {
|
||||
style.color = color;
|
||||
}
|
||||
if (fontSize) {
|
||||
style.fontSize = `${fontSize}px`;
|
||||
}
|
||||
|
||||
if (!icon && !localIcon) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return () => h(SvgIcon, { icon, localIcon, style });
|
||||
};
|
||||
|
||||
return {
|
||||
SvgIconVNode
|
||||
};
|
||||
}
|
||||
152
packages/hooks/src/use-table.ts
Normal file
152
packages/hooks/src/use-table.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import type { Ref } 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 type ApiFn = (args: any) => Promise<unknown>;
|
||||
|
||||
export type TableColumnCheck = {
|
||||
prop: string;
|
||||
label: string;
|
||||
checked: boolean;
|
||||
};
|
||||
|
||||
export type TableDataWithIndex<T> = T & { index: number };
|
||||
|
||||
export type TransformedData<T> = {
|
||||
data: TableDataWithIndex<T>[];
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type Transformer<T, Response> = (response: Response) => TransformedData<T>;
|
||||
|
||||
export type TableConfig<A extends ApiFn, T, C> = {
|
||||
/** api function to get table data */
|
||||
apiFn: A;
|
||||
/** api params */
|
||||
apiParams?: Parameters<A>[0];
|
||||
/** transform api response to table data */
|
||||
transformer: Transformer<T, Awaited<ReturnType<A>>>;
|
||||
/** columns factory */
|
||||
columns: () => C[];
|
||||
/**
|
||||
* get column checks
|
||||
*
|
||||
* @param columns
|
||||
*/
|
||||
getColumnChecks: (columns: C[]) => TableColumnCheck[];
|
||||
/**
|
||||
* get columns
|
||||
*
|
||||
* @param columns
|
||||
*/
|
||||
getColumns: (columns: C[], checks: TableColumnCheck[]) => C[];
|
||||
/**
|
||||
* callback when response fetched
|
||||
*
|
||||
* @param transformed transformed data
|
||||
*/
|
||||
onFetched?: (transformed: TransformedData<T>) => MaybePromise<void>;
|
||||
/**
|
||||
* whether to get data immediately
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
immediate?: boolean;
|
||||
};
|
||||
|
||||
export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<A, T, C>) {
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
const { bool: empty, setBool: setEmpty } = useBoolean();
|
||||
|
||||
const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config;
|
||||
|
||||
const searchParams: NonNullable<Parameters<A>[0]> = reactive(jsonClone({ ...apiParams }));
|
||||
|
||||
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));
|
||||
|
||||
function reloadColumns() {
|
||||
allColumns.value = config.columns();
|
||||
|
||||
const checkMap = new Map(columnChecks.value.map(col => [col.prop, col.checked]));
|
||||
|
||||
const defaultChecks = getColumnChecks(allColumns.value);
|
||||
|
||||
columnChecks.value = defaultChecks.map(col => ({
|
||||
...col,
|
||||
checked: checkMap.get(col.prop) ?? col.checked
|
||||
}));
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
startLoading();
|
||||
|
||||
const formattedParams = formatSearchParams(searchParams);
|
||||
|
||||
const response = await apiFn(formattedParams);
|
||||
|
||||
const transformed = transformer(response as Awaited<ReturnType<A>>);
|
||||
|
||||
data.value = transformed.data;
|
||||
|
||||
setEmpty(transformed.data.length === 0);
|
||||
|
||||
await config.onFetched?.(transformed);
|
||||
|
||||
endLoading();
|
||||
}
|
||||
|
||||
function formatSearchParams(params: Record<string, unknown>) {
|
||||
const formattedParams: Record<string, unknown> = {};
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined) {
|
||||
formattedParams[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return formattedParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* update search params
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
function updateSearchParams(params: Partial<Parameters<A>[0]>) {
|
||||
Object.assign(searchParams, params);
|
||||
}
|
||||
|
||||
/** reset search params */
|
||||
function resetSearchParams() {
|
||||
Object.assign(searchParams, jsonClone(apiParams));
|
||||
}
|
||||
|
||||
if (immediate) {
|
||||
getData();
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
empty,
|
||||
data,
|
||||
columns,
|
||||
columnChecks,
|
||||
reloadColumns,
|
||||
getData,
|
||||
searchParams,
|
||||
updateSearchParams,
|
||||
resetSearchParams
|
||||
};
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"types": ["node"]
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"extends": "sa/vue",
|
||||
"rules": {
|
||||
"vue/multi-word-component-names": "off"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,19 @@
|
||||
{
|
||||
"name": "@sa/materials",
|
||||
"version": "1.0.0",
|
||||
"version": "1.3.7",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"./src/*"
|
||||
]
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*"
|
||||
"@sa/utils": "workspace:*",
|
||||
"simplebar-vue": "2.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typed-css-modules": "0.8.0"
|
||||
"typed-css-modules": "0.9.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import AdminLayout from './libs/admin-layout';
|
||||
import AdminLayout, { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID } from './libs/admin-layout';
|
||||
import PageTab from './libs/page-tab';
|
||||
import SimpleScrollbar from './libs/simple-scrollbar';
|
||||
|
||||
export { AdminLayout, PageTab };
|
||||
export { AdminLayout, LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX, PageTab, SimpleScrollbar };
|
||||
export * from './types';
|
||||
|
||||
@@ -14,4 +14,5 @@ declare const styles: {
|
||||
readonly 'sider-padding-top': string;
|
||||
readonly 'sider-padding-bottom': string;
|
||||
};
|
||||
export = styles;
|
||||
|
||||
export default styles;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import AdminLayout from './index.vue';
|
||||
import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID } from './shared';
|
||||
|
||||
export default AdminLayout;
|
||||
export { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX };
|
||||
|
||||
@@ -1,130 +1,7 @@
|
||||
<template>
|
||||
<div :class="['relative h-full', commonClass]" :style="cssVars">
|
||||
<div
|
||||
:id="isWrapperScroll ? scrollElId : undefined"
|
||||
:class="['flex flex-col h-full', commonClass, scrollWrapperClass, { 'overflow-y-auto': isWrapperScroll }]"
|
||||
>
|
||||
<!-- Header -->
|
||||
<template v-if="showHeader">
|
||||
<header
|
||||
v-show="!fullContent"
|
||||
:class="[
|
||||
style['layout-header'],
|
||||
'flex-shrink-0',
|
||||
commonClass,
|
||||
headerClass,
|
||||
headerLeftGapClass,
|
||||
{ 'absolute top-0 left-0 w-full': fixedHeaderAndTab }
|
||||
]"
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
</header>
|
||||
<div
|
||||
v-show="!fullContent && fixedHeaderAndTab"
|
||||
:class="[style['layout-header-placement'], 'flex-shrink-0 overflow-hidden']"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Tab -->
|
||||
<template v-if="showTab">
|
||||
<div
|
||||
v-show="!fullContent"
|
||||
:class="[
|
||||
style['layout-tab'],
|
||||
'flex-shrink-0',
|
||||
commonClass,
|
||||
tabClass,
|
||||
{ 'top-0!': !showHeader },
|
||||
leftGapClass,
|
||||
{ 'absolute left-0 w-full': fixedHeaderAndTab }
|
||||
]"
|
||||
>
|
||||
<slot name="tab"></slot>
|
||||
</div>
|
||||
<div
|
||||
v-show="!fullContent && fixedHeaderAndTab"
|
||||
:class="[style['layout-tab-placement'], 'flex-shrink-0 overflow-hidden']"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Sider -->
|
||||
<template v-if="showSider">
|
||||
<aside
|
||||
v-show="!fullContent"
|
||||
:class="[
|
||||
'absolute left-0 top-0 h-full',
|
||||
commonClass,
|
||||
siderClass,
|
||||
siderPaddingClass,
|
||||
siderCollapse ? style['layout-sider_collapsed'] : style['layout-sider']
|
||||
]"
|
||||
>
|
||||
<slot name="sider"></slot>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<!-- Mobile Sider -->
|
||||
<template v-if="showMobileSider">
|
||||
<aside
|
||||
:class="[
|
||||
'absolute left-0 top-0 w-0 h-full bg-white',
|
||||
commonClass,
|
||||
mobileSiderClass,
|
||||
style['layout-mobile-sider'],
|
||||
siderCollapse ? 'overflow-hidden' : style['layout-sider']
|
||||
]"
|
||||
>
|
||||
<slot name="sider"></slot>
|
||||
</aside>
|
||||
<div
|
||||
v-show="!siderCollapse"
|
||||
:class="['absolute left-0 top-0 w-full h-full bg-[rgba(0,0,0,0.2)]', style['layout-mobile-sider-mask']]"
|
||||
@click="handleClickMask"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main
|
||||
:id="isContentScroll ? scrollElId : undefined"
|
||||
:class="[
|
||||
'flex flex-col flex-grow',
|
||||
commonClass,
|
||||
contentClass,
|
||||
leftGapClass,
|
||||
{ 'overflow-y-auto': isContentScroll }
|
||||
]"
|
||||
>
|
||||
<slot></slot>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<template v-if="showFooter">
|
||||
<footer
|
||||
v-show="!fullContent"
|
||||
:class="[
|
||||
style['layout-footer'],
|
||||
'flex-shrink-0',
|
||||
commonClass,
|
||||
footerClass,
|
||||
footerLeftGapClass,
|
||||
{ 'absolute left-0 bottom-0 w-full': fixedFooter }
|
||||
]"
|
||||
>
|
||||
<slot name="footer"></slot>
|
||||
</footer>
|
||||
<div
|
||||
v-show="!fullContent && fixedFooter"
|
||||
:class="[style['layout-footer-placement'], 'flex-shrink-0 overflow-hidden']"
|
||||
></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { AdminLayoutProps } from '../../types';
|
||||
import { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX, createLayoutCssVars } from './shared';
|
||||
import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID, createLayoutCssVars } from './shared';
|
||||
import style from './index.module.css';
|
||||
|
||||
defineOptions({
|
||||
@@ -152,10 +29,8 @@ const props = withDefaults(defineProps<AdminLayoutProps>(), {
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
/**
|
||||
* @description: click the mask when layout mode is mobile
|
||||
*/
|
||||
(e: 'click-mobile-sider-mask'): void;
|
||||
/** Update siderCollapse */
|
||||
(e: 'update:siderCollapse', collapse: boolean): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
@@ -163,17 +38,18 @@ const emit = defineEmits<Emits>();
|
||||
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/** main */
|
||||
/** Main */
|
||||
default?: SlotFn;
|
||||
/** header */
|
||||
/** Header */
|
||||
header?: SlotFn;
|
||||
/** tab */
|
||||
/** Tab */
|
||||
tab?: SlotFn;
|
||||
/** sider */
|
||||
/** Sider */
|
||||
sider?: SlotFn;
|
||||
/** footer */
|
||||
/** Footer */
|
||||
footer?: SlotFn;
|
||||
};
|
||||
|
||||
const slots = defineSlots<Slots>();
|
||||
|
||||
const cssVars = computed(() => createLayoutCssVars(props));
|
||||
@@ -232,8 +108,130 @@ const siderPaddingClass = computed(() => {
|
||||
});
|
||||
|
||||
function handleClickMask() {
|
||||
emit('click-mobile-sider-mask');
|
||||
emit('update:siderCollapse', true);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative h-full" :class="[commonClass]" :style="cssVars">
|
||||
<div
|
||||
:id="isWrapperScroll ? scrollElId : undefined"
|
||||
class="h-full flex flex-col"
|
||||
:class="[commonClass, scrollWrapperClass, { 'overflow-y-auto': isWrapperScroll }]"
|
||||
>
|
||||
<!-- Header -->
|
||||
<template v-if="showHeader">
|
||||
<header
|
||||
v-show="!fullContent"
|
||||
class="flex-shrink-0"
|
||||
:class="[
|
||||
style['layout-header'],
|
||||
commonClass,
|
||||
headerClass,
|
||||
headerLeftGapClass,
|
||||
{ 'absolute top-0 left-0 w-full': fixedHeaderAndTab }
|
||||
]"
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
</header>
|
||||
<div
|
||||
v-show="!fullContent && fixedHeaderAndTab"
|
||||
class="flex-shrink-0 overflow-hidden"
|
||||
:class="[style['layout-header-placement']]"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Tab -->
|
||||
<template v-if="showTab">
|
||||
<div
|
||||
class="flex-shrink-0"
|
||||
:class="[
|
||||
style['layout-tab'],
|
||||
commonClass,
|
||||
tabClass,
|
||||
{ 'top-0!': fullContent || !showHeader },
|
||||
leftGapClass,
|
||||
{ 'absolute left-0 w-full': fixedHeaderAndTab }
|
||||
]"
|
||||
>
|
||||
<slot name="tab"></slot>
|
||||
</div>
|
||||
<div
|
||||
v-show="fullContent || fixedHeaderAndTab"
|
||||
class="flex-shrink-0 overflow-hidden"
|
||||
:class="[style['layout-tab-placement']]"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Sider -->
|
||||
<template v-if="showSider">
|
||||
<aside
|
||||
v-show="!fullContent"
|
||||
class="absolute left-0 top-0 h-full"
|
||||
:class="[
|
||||
commonClass,
|
||||
siderClass,
|
||||
siderPaddingClass,
|
||||
siderCollapse ? style['layout-sider_collapsed'] : style['layout-sider']
|
||||
]"
|
||||
>
|
||||
<slot name="sider"></slot>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<!-- Mobile Sider -->
|
||||
<template v-if="showMobileSider">
|
||||
<aside
|
||||
class="absolute left-0 top-0 h-full w-0 bg-white"
|
||||
:class="[
|
||||
commonClass,
|
||||
mobileSiderClass,
|
||||
style['layout-mobile-sider'],
|
||||
siderCollapse ? 'overflow-hidden' : style['layout-sider']
|
||||
]"
|
||||
>
|
||||
<slot name="sider"></slot>
|
||||
</aside>
|
||||
<div
|
||||
v-show="!siderCollapse"
|
||||
class="absolute left-0 top-0 h-full w-full bg-[rgba(0,0,0,0.2)]"
|
||||
:class="[style['layout-mobile-sider-mask']]"
|
||||
@click="handleClickMask"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main
|
||||
:id="isContentScroll ? scrollElId : undefined"
|
||||
class="flex flex-col flex-grow"
|
||||
:class="[commonClass, contentClass, leftGapClass, { 'overflow-y-auto': isContentScroll }]"
|
||||
>
|
||||
<slot></slot>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<template v-if="showFooter">
|
||||
<footer
|
||||
v-show="!fullContent"
|
||||
class="flex-shrink-0"
|
||||
:class="[
|
||||
style['layout-footer'],
|
||||
commonClass,
|
||||
footerClass,
|
||||
footerLeftGapClass,
|
||||
{ 'absolute left-0 bottom-0 w-full': fixedFooter }
|
||||
]"
|
||||
>
|
||||
<slot name="footer"></slot>
|
||||
</footer>
|
||||
<div
|
||||
v-show="!fullContent && fixedFooter"
|
||||
class="flex-shrink-0 overflow-hidden"
|
||||
:class="[style['layout-footer-placement']]"
|
||||
></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import type { AdminLayoutProps, LayoutCssVarsProps, LayoutCssVars } from '../../types';
|
||||
import type { AdminLayoutProps, LayoutCssVars, LayoutCssVarsProps } from '../../types';
|
||||
|
||||
/**
|
||||
* the id of the scroll element of the layout
|
||||
*/
|
||||
/** The id of the scroll element of the layout */
|
||||
export const LAYOUT_SCROLL_EL_ID = '__SCROLL_EL_ID__';
|
||||
|
||||
/**
|
||||
* the max z-index of the layout
|
||||
*/
|
||||
/** The max z-index of the layout */
|
||||
export const LAYOUT_MAX_Z_INDEX = 100;
|
||||
|
||||
/**
|
||||
* create layout css vars by css vars props
|
||||
* @param props css vars props
|
||||
* Create layout css vars by css vars props
|
||||
*
|
||||
* @param props Css vars props
|
||||
*/
|
||||
function createLayoutCssVarsByCssVarsProps(props: LayoutCssVarsProps) {
|
||||
const cssVars: LayoutCssVars = {
|
||||
@@ -32,7 +29,8 @@ function createLayoutCssVarsByCssVarsProps(props: LayoutCssVarsProps) {
|
||||
}
|
||||
|
||||
/**
|
||||
* create layout css vars
|
||||
* Create layout css vars
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
export function createLayoutCssVars(props: AdminLayoutProps) {
|
||||
|
||||
@@ -1,7 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageTabProps } from '../../types';
|
||||
import style from './index.module.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'ButtonTab'
|
||||
});
|
||||
|
||||
defineProps<PageTabProps>();
|
||||
|
||||
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The center content of the tab
|
||||
*/
|
||||
default?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The left content of the tab
|
||||
*/
|
||||
prefix?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The right content of the tab
|
||||
*/
|
||||
suffix?: SlotFn;
|
||||
};
|
||||
|
||||
defineSlots<Slots>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-12px whitespace-nowrap border-(1px solid) rounded-4px px-12px py-4px"
|
||||
:class="[
|
||||
':soy: relative inline-flex justify-center items-center gap-12px px-12px py-4px border-1px border-solid rounded-4px cursor-pointer whitespace-nowrap',
|
||||
style['button-tab'],
|
||||
{ [style['button-tab_dark']]: darkMode },
|
||||
{ [style['button-tab_active']]: active },
|
||||
@@ -14,36 +50,4 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import style from './index.module.css';
|
||||
import type { PageTabProps } from '../../types';
|
||||
|
||||
defineOptions({
|
||||
name: 'ButtonTab'
|
||||
});
|
||||
|
||||
defineProps<PageTabProps>();
|
||||
|
||||
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/**
|
||||
* slot
|
||||
* @description the center content of the tab
|
||||
*/
|
||||
default?: SlotFn;
|
||||
/**
|
||||
* slot
|
||||
* @description the left content of the tab
|
||||
*/
|
||||
prefix?: SlotFn;
|
||||
/**
|
||||
* slot
|
||||
* @description the right content of the tab
|
||||
*/
|
||||
suffix?: SlotFn;
|
||||
};
|
||||
defineSlots<Slots>();
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<template>
|
||||
<svg style="width: 100%; height: 100%">
|
||||
<defs>
|
||||
<symbol id="geometry-left" viewBox="0 0 214 36">
|
||||
<path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z"></path>
|
||||
</symbol>
|
||||
<symbol id="geometry-right" viewBox="0 0 214 36">
|
||||
<use xlink:href="#geometry-left"></use>
|
||||
</symbol>
|
||||
<clipPath>
|
||||
<rect width="100%" height="100%" x="0"></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg width="51%" height="100%">
|
||||
<use xlink:href="#geometry-left" width="214" height="36" fill="currentColor"></use>
|
||||
</svg>
|
||||
<g transform="scale(-1, 1)">
|
||||
<svg width="51%" height="100%" x="-100%" y="0">
|
||||
<use xlink:href="#geometry-right" width="214" height="36" fill="currentColor"></use>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'ChromeTabBg'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg class="size-full">
|
||||
<defs>
|
||||
<symbol id="geometry-left" viewBox="0 0 214 36">
|
||||
<path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z" />
|
||||
</symbol>
|
||||
<symbol id="geometry-right" viewBox="0 0 214 36">
|
||||
<use xlink:href="#geometry-left" />
|
||||
</symbol>
|
||||
<clipPath>
|
||||
<rect width="100%" height="100%" x="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg width="51%" height="100%">
|
||||
<use xlink:href="#geometry-left" width="214" height="36" fill="currentColor" />
|
||||
</svg>
|
||||
<g transform="scale(-1, 1)">
|
||||
<svg width="51%" height="100%" x="-100%" y="0">
|
||||
<use xlink:href="#geometry-right" width="214" height="36" fill="currentColor" />
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,27 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
':soy: relative inline-flex justify-center items-center gap-16px -mr-18px px-24px py-6px cursor-pointer whitespace-nowrap',
|
||||
style['chrome-tab'],
|
||||
{ [style['chrome-tab_dark']]: darkMode },
|
||||
{ [style['chrome-tab_active']]: active },
|
||||
{ [style['chrome-tab_active_dark']]: active && darkMode }
|
||||
]"
|
||||
>
|
||||
<div :class="[':soy: absolute left-0 top-0 -z-1 w-full h-full pointer-events-none', style['chrome-tab__bg']]">
|
||||
<ChromeTabBg />
|
||||
</div>
|
||||
<slot name="prefix"></slot>
|
||||
<slot></slot>
|
||||
<slot name="suffix"></slot>
|
||||
<div :class="[':soy: absolute right-7px w-1px h-16px bg-#1f2225', style['chrome-tab-divider']]"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PageTabProps } from '../../types';
|
||||
import ChromeTabBg from './chrome-tab-bg.vue';
|
||||
import style from './index.module.css';
|
||||
import type { PageTabProps } from '../../types';
|
||||
|
||||
defineOptions({
|
||||
name: 'ChromeTab'
|
||||
@@ -33,18 +13,21 @@ type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/**
|
||||
* slot
|
||||
* @description the center content of the tab
|
||||
* Slot
|
||||
*
|
||||
* The center content of the tab
|
||||
*/
|
||||
default?: SlotFn;
|
||||
/**
|
||||
* slot
|
||||
* @description the left content of the tab
|
||||
* Slot
|
||||
*
|
||||
* The left content of the tab
|
||||
*/
|
||||
prefix?: SlotFn;
|
||||
/**
|
||||
* slot
|
||||
* @description the right content of the tab
|
||||
* Slot
|
||||
*
|
||||
* The right content of the tab
|
||||
*/
|
||||
suffix?: SlotFn;
|
||||
};
|
||||
@@ -52,4 +35,24 @@ type Slots = {
|
||||
defineSlots<Slots>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-16px whitespace-nowrap px-24px py-6px -mr-18px"
|
||||
:class="[
|
||||
style['chrome-tab'],
|
||||
{ [style['chrome-tab_dark']]: darkMode },
|
||||
{ [style['chrome-tab_active']]: active },
|
||||
{ [style['chrome-tab_active_dark']]: active && darkMode }
|
||||
]"
|
||||
>
|
||||
<div class=":soy: pointer-events-none absolute left-0 top-0 h-full w-full -z-1" :class="[style['chrome-tab__bg']]">
|
||||
<ChromeTabBg />
|
||||
</div>
|
||||
<slot name="prefix"></slot>
|
||||
<slot></slot>
|
||||
<slot name="suffix"></slot>
|
||||
<div class=":soy: absolute right-7px h-16px w-1px bg-#1f2225" :class="[style['chrome-tab-divider']]"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
background-color: var(--soy-primary-color-opacity2);
|
||||
}
|
||||
|
||||
.button-tab .icon_close:hover {
|
||||
.button-tab .svg-close:hover {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
background-color: var(--soy-primary-color);
|
||||
}
|
||||
|
||||
.button-tab_dark .icon_close:hover {
|
||||
.button-tab_dark .svg-close:hover {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
@@ -70,17 +70,17 @@
|
||||
color: var(--soy-primary-color2);
|
||||
}
|
||||
|
||||
.chrome-tab .icon_close:hover {
|
||||
.chrome-tab .svg-close:hover {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
background-color: #9ca3af;
|
||||
}
|
||||
|
||||
.chrome-tab_active .icon_close:hover {
|
||||
.chrome-tab_active .svg-close:hover {
|
||||
background-color: var(--soy-primary-color);
|
||||
}
|
||||
|
||||
.chrome-tab_dark .icon_close:hover {
|
||||
.chrome-tab_dark .svg-close:hover {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@ declare const styles: {
|
||||
readonly 'button-tab_dark': string;
|
||||
readonly 'button-tab_active': string;
|
||||
readonly 'button-tab_active_dark': string;
|
||||
readonly icon_close: string;
|
||||
readonly 'chrome-tab': string;
|
||||
readonly 'chrome-tab_active': string;
|
||||
readonly 'chrome-tab__bg': string;
|
||||
readonly 'chrome-tab_active_dark': string;
|
||||
readonly 'chrome-tab_dark': string;
|
||||
readonly 'chrome-tab-divider': string;
|
||||
readonly 'svg-close': string;
|
||||
};
|
||||
export = styles;
|
||||
|
||||
export default styles;
|
||||
|
||||
@@ -1,26 +1,12 @@
|
||||
<template>
|
||||
<component :is="activeTabComponent.component" :class="activeTabComponent.class" :style="cssVars" v-bind="bindProps">
|
||||
<template #prefix>
|
||||
<slot name="prefix"></slot>
|
||||
</template>
|
||||
<slot></slot>
|
||||
<template #suffix>
|
||||
<slot name="suffix">
|
||||
<IconClose v-if="closable" :class="[style['icon_close']]" @click="handleClose" />
|
||||
</slot>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import { createTabCssVars, ACTIVE_COLOR } from './shared';
|
||||
import type { PageTabMode, PageTabProps } from '../../types';
|
||||
import { ACTIVE_COLOR, createTabCssVars } from './shared';
|
||||
import ChromeTab from './chrome-tab.vue';
|
||||
import ButtonTab from './button-tab.vue';
|
||||
import IconClose from './icon-close.vue';
|
||||
import SvgClose from './svg-close.vue';
|
||||
import style from './index.module.css';
|
||||
import type { PageTabProps, PageTabMode } from '../../types';
|
||||
|
||||
defineOptions({
|
||||
name: 'PageTab'
|
||||
@@ -39,28 +25,6 @@ interface Emits {
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/**
|
||||
* slot
|
||||
* @description the center content of the tab
|
||||
*/
|
||||
default?: SlotFn;
|
||||
/**
|
||||
* slot
|
||||
* @description the left content of the tab
|
||||
*/
|
||||
prefix?: SlotFn;
|
||||
/**
|
||||
* slot
|
||||
* @description the right content of the tab
|
||||
*/
|
||||
suffix?: SlotFn;
|
||||
};
|
||||
|
||||
defineSlots<Slots>();
|
||||
|
||||
const activeTabComponent = computed(() => {
|
||||
const { mode, chromeClass, buttonClass } = props;
|
||||
|
||||
@@ -91,4 +55,18 @@ function handleClose() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="activeTabComponent.component" :class="activeTabComponent.class" :style="cssVars" v-bind="bindProps">
|
||||
<template #prefix>
|
||||
<slot name="prefix"></slot>
|
||||
</template>
|
||||
<slot></slot>
|
||||
<template #suffix>
|
||||
<slot name="suffix">
|
||||
<SvgClose v-if="closable" :class="[style['svg-close']]" @click.stop="handleClose" />
|
||||
</slot>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { addColorAlpha, transformColorWithOpacity } from '@sa/utils';
|
||||
import type { PageTabCssVarsProps, PageTabCssVars } from '../../types';
|
||||
import { addColorAlpha, transformColorWithOpacity } from '@sa/color';
|
||||
import type { PageTabCssVars, PageTabCssVarsProps } from '../../types';
|
||||
|
||||
/**
|
||||
* the active color of the tab
|
||||
*/
|
||||
/** The active color of the tab */
|
||||
export const ACTIVE_COLOR = '#1890ff';
|
||||
|
||||
function createCssVars(props: PageTabCssVarsProps) {
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'SvgClose'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative inline-flex justify-center items-center w-16px h-16px text-14px rd-50%"
|
||||
@click.stop="handleClick"
|
||||
>
|
||||
<div class=":soy: relative h-16px w-16px inline-flex items-center justify-center rd-50% text-14px">
|
||||
<svg width="1em" height="1em" viewBox="0 0 1024 1024">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8L295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512L196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1l216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
|
||||
></path>
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'IconClose'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'click'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
function handleClick() {
|
||||
emit('click');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
3
packages/materials/src/libs/simple-scrollbar/index.ts
Normal file
3
packages/materials/src/libs/simple-scrollbar/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import SimpleScrollbar from './index.vue';
|
||||
|
||||
export default SimpleScrollbar;
|
||||
18
packages/materials/src/libs/simple-scrollbar/index.vue
Normal file
18
packages/materials/src/libs/simple-scrollbar/index.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import Simplebar from 'simplebar-vue';
|
||||
import 'simplebar-vue/dist/simplebar.min.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'SimpleScrollbar'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full flex-1-hidden">
|
||||
<Simplebar class="h-full">
|
||||
<slot />
|
||||
</Simplebar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,18 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'Tooltip'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<slot></slot>
|
||||
<div
|
||||
class="absolute z-10 hidden w-32 p-2 text-xs text-white bg-gray-800 rounded shadow-lg top-0 left-0 -mt-8 -ml-8"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,146 +1,156 @@
|
||||
/**
|
||||
* header config
|
||||
*/
|
||||
/** Header config */
|
||||
interface AdminLayoutHeaderConfig {
|
||||
/**
|
||||
* whether header is visible
|
||||
* Whether header is visible
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
headerVisible?: boolean;
|
||||
/**
|
||||
* header class
|
||||
* Header class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
headerClass?: string;
|
||||
/**
|
||||
* header height
|
||||
* Header height
|
||||
*
|
||||
* @default 56px
|
||||
*/
|
||||
headerHeight?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* tab config
|
||||
*/
|
||||
/** Tab config */
|
||||
interface AdminLayoutTabConfig {
|
||||
/**
|
||||
* whether tab is visible
|
||||
* Whether tab is visible
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
tabVisible?: boolean;
|
||||
/**
|
||||
* tab class
|
||||
* Tab class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
tabClass?: string;
|
||||
/**
|
||||
* tab height
|
||||
* Tab height
|
||||
*
|
||||
* @default 48px
|
||||
*/
|
||||
tabHeight?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* sider config
|
||||
*/
|
||||
/** Sider config */
|
||||
interface AdminLayoutSiderConfig {
|
||||
/**
|
||||
* whether sider is visible
|
||||
* Whether sider is visible
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
siderVisible?: boolean;
|
||||
/**
|
||||
* sider class
|
||||
* Sider class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
siderClass?: string;
|
||||
/**
|
||||
* mobile sider class
|
||||
* Mobile sider class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
mobileSiderClass?: string;
|
||||
/**
|
||||
* sider collapse status
|
||||
* Sider collapse status
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
siderCollapse?: boolean;
|
||||
/**
|
||||
* sider width when collapse is false
|
||||
* Sider width when collapse is false
|
||||
*
|
||||
* @default '220px'
|
||||
*/
|
||||
siderWidth?: number;
|
||||
/**
|
||||
* sider width when collapse is true
|
||||
* Sider width when collapse is true
|
||||
*
|
||||
* @default '64px'
|
||||
*/
|
||||
siderCollapsedWidth?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* content config
|
||||
*/
|
||||
/** Content config */
|
||||
export interface AdminLayoutContentConfig {
|
||||
/**
|
||||
* content class
|
||||
* Content class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
contentClass?: string;
|
||||
/**
|
||||
* whether content is full the page
|
||||
* @description if true, other elements will be hidden by `display: none`
|
||||
* Whether content is full the page
|
||||
*
|
||||
* If true, other elements will be hidden by `display: none`
|
||||
*/
|
||||
fullContent?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* footer config
|
||||
*/
|
||||
/** Footer config */
|
||||
export interface AdminLayoutFooterConfig {
|
||||
/**
|
||||
* whether footer is visible
|
||||
* Whether footer is visible
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
footerVisible?: boolean;
|
||||
/**
|
||||
* whether footer is fixed
|
||||
* Whether footer is fixed
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
fixedFooter?: boolean;
|
||||
/**
|
||||
* footer class
|
||||
* Footer class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
footerClass?: string;
|
||||
/**
|
||||
* footer height
|
||||
* Footer height
|
||||
*
|
||||
* @default 48px
|
||||
*/
|
||||
footerHeight?: number;
|
||||
/**
|
||||
* whether footer is on the right side
|
||||
* @description when the layout is vertical, the footer is on the right side
|
||||
* Whether footer is on the right side
|
||||
*
|
||||
* When the layout is vertical, the footer is on the right side
|
||||
*/
|
||||
rightFooter?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* layout mode
|
||||
* - horizontal
|
||||
* - vertical
|
||||
* Layout mode
|
||||
*
|
||||
* - Horizontal
|
||||
* - Vertical
|
||||
*/
|
||||
export type LayoutMode = 'horizontal' | 'vertical';
|
||||
|
||||
/**
|
||||
* the scroll mode when content overflow
|
||||
* - wrapper: the layout component's wrapper element has a scrollbar
|
||||
* - content: the layout component's content element has a scrollbar
|
||||
* The scroll mode when content overflow
|
||||
*
|
||||
* - Wrapper: the layout component's wrapper element has a scrollbar
|
||||
* - Content: the layout component's content element has a scrollbar
|
||||
*
|
||||
* @default 'wrapper'
|
||||
*/
|
||||
export type LayoutScrollMode = 'wrapper' | 'content';
|
||||
|
||||
/**
|
||||
* admin layout props
|
||||
*/
|
||||
/** Admin layout props */
|
||||
export interface AdminLayoutProps
|
||||
extends AdminLayoutHeaderConfig,
|
||||
AdminLayoutTabConfig,
|
||||
@@ -148,52 +158,58 @@ export interface AdminLayoutProps
|
||||
AdminLayoutContentConfig,
|
||||
AdminLayoutFooterConfig {
|
||||
/**
|
||||
* layout mode
|
||||
* Layout mode
|
||||
*
|
||||
* - {@link LayoutMode}
|
||||
*/
|
||||
mode?: LayoutMode;
|
||||
/** is mobile layout */
|
||||
/** Is mobile layout */
|
||||
isMobile?: boolean;
|
||||
/**
|
||||
* scroll mode
|
||||
* Scroll mode
|
||||
*
|
||||
* - {@link ScrollMode}
|
||||
*/
|
||||
scrollMode?: LayoutScrollMode;
|
||||
/**
|
||||
* the id of the scroll element of the layout
|
||||
* @description it can be used to get the corresponding Dom and scroll it
|
||||
* The id of the scroll element of the layout
|
||||
*
|
||||
* It can be used to get the corresponding Dom and scroll it
|
||||
*
|
||||
* @example
|
||||
* use the default id by import
|
||||
* ```ts
|
||||
* import { adminLayoutScrollElId } from '@sa/vue-materials';
|
||||
* ```
|
||||
*
|
||||
* @default
|
||||
* ```ts
|
||||
* const adminLayoutScrollElId = '__ADMIN_LAYOUT_SCROLL_EL_ID__'
|
||||
* ```
|
||||
* @example use the default id by import
|
||||
* ```ts
|
||||
* import { adminLayoutScrollElId } from '@sa/vue-materials';
|
||||
* ```
|
||||
*/
|
||||
scrollElId?: string;
|
||||
/**
|
||||
* the class of the scroll element
|
||||
*/
|
||||
/** The class of the scroll element */
|
||||
scrollElClass?: string;
|
||||
/**
|
||||
* the class of the scroll wrapper element
|
||||
*/
|
||||
/** The class of the scroll wrapper element */
|
||||
scrollWrapperClass?: string;
|
||||
/**
|
||||
* the common class of the layout
|
||||
* @description is can be used to configure the transition animation
|
||||
* The common class of the layout
|
||||
*
|
||||
* Is can be used to configure the transition animation
|
||||
*
|
||||
* @default 'transition-all-300'
|
||||
*/
|
||||
commonClass?: string;
|
||||
/**
|
||||
* whether fix the header and tab
|
||||
* Whether fix the header and tab
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
fixedTop?: boolean;
|
||||
/**
|
||||
* the max z-index of the layout
|
||||
* @description the z-index of Header,Tab,Sider and Footer will not exceed this value
|
||||
* The max z-index of the layout
|
||||
*
|
||||
* The z-index of Header,Tab,Sider and Footer will not exceed this value
|
||||
*/
|
||||
maxZIndex?: number;
|
||||
}
|
||||
@@ -222,48 +238,44 @@ export type LayoutCssVars = {
|
||||
};
|
||||
|
||||
/**
|
||||
* the mode of the tab
|
||||
* - button: button style
|
||||
* - chrome: chrome style
|
||||
* The mode of the tab
|
||||
*
|
||||
* - Button: button style
|
||||
* - Chrome: chrome style
|
||||
*
|
||||
* @default chrome
|
||||
*/
|
||||
export type PageTabMode = 'button' | 'chrome';
|
||||
|
||||
export interface PageTabProps {
|
||||
/**
|
||||
* whether is dark mode
|
||||
*/
|
||||
/** Whether is dark mode */
|
||||
darkMode?: boolean;
|
||||
/**
|
||||
* the mode of the tab
|
||||
* The mode of the tab
|
||||
*
|
||||
* - {@link TabMode}
|
||||
*/
|
||||
mode?: PageTabMode;
|
||||
/**
|
||||
* the common class of the layout
|
||||
* @description is can be used to configure the transition animation
|
||||
* The common class of the layout
|
||||
*
|
||||
* Is can be used to configure the transition animation
|
||||
*
|
||||
* @default 'transition-all-300'
|
||||
*/
|
||||
commonClass?: string;
|
||||
/**
|
||||
* the class of the button tab
|
||||
*/
|
||||
/** The class of the button tab */
|
||||
buttonClass?: string;
|
||||
/**
|
||||
* the class of the chrome tab
|
||||
*/
|
||||
/** The class of the chrome tab */
|
||||
chromeClass?: string;
|
||||
/**
|
||||
* whether the tab is active
|
||||
*/
|
||||
/** Whether the tab is active */
|
||||
active?: boolean;
|
||||
/**
|
||||
* the color of the active tab
|
||||
*/
|
||||
/** The color of the active tab */
|
||||
activeColor?: string;
|
||||
/**
|
||||
* whether the tab is closable
|
||||
* @description show the close icon when true
|
||||
* Whether the tab is closable
|
||||
*
|
||||
* Show the close icon when true
|
||||
*/
|
||||
closable?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"types": ["node"]
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
{
|
||||
"name": "@sa/request",
|
||||
"version": "1.0.0",
|
||||
"name": "@sa/fetch",
|
||||
"version": "1.3.7",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"./src/*"
|
||||
]
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"ofetch": "1.3.3"
|
||||
"ofetch": "1.4.1"
|
||||
}
|
||||
}
|
||||
20
packages/ofetch/tsconfig.json
Normal file
20
packages/ofetch/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const path = require('path');
|
||||
const jiti = require('jiti')(__filename);
|
||||
|
||||
jiti(path.resolve(__dirname, './src/index.ts'));
|
||||
3
packages/scripts/bin.ts
Executable file
3
packages/scripts/bin.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
import './src/index.ts';
|
||||
@@ -1,28 +1,27 @@
|
||||
{
|
||||
"name": "@sa/scripts",
|
||||
"version": "1.0.0",
|
||||
"version": "1.3.7",
|
||||
"bin": {
|
||||
"sa": "./bin.ts"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"./src/*"
|
||||
]
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"bin": {
|
||||
"sa": "./bin.cjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"c12": "1.5.1",
|
||||
"@soybeanjs/changelog": "0.3.24",
|
||||
"bumpp": "9.7.1",
|
||||
"c12": "2.0.1",
|
||||
"cac": "6.7.14",
|
||||
"consola": "3.2.3",
|
||||
"enquirer": "2.4.1",
|
||||
"execa": "8.0.1",
|
||||
"jiti": "1.20.0",
|
||||
"lint-staged": "15.0.2",
|
||||
"npm-check-updates": "16.14.6",
|
||||
"rimraf": "5.0.5"
|
||||
"execa": "9.4.0",
|
||||
"kolorist": "1.8.0",
|
||||
"npm-check-updates": "17.1.3",
|
||||
"rimraf": "6.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
10
packages/scripts/src/commands/changelog.ts
Normal file
10
packages/scripts/src/commands/changelog.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { generateChangelog, generateTotalChangelog } from '@soybeanjs/changelog';
|
||||
import type { ChangelogOption } from '@soybeanjs/changelog';
|
||||
|
||||
export async function genChangelog(options?: Partial<ChangelogOption>, total = false) {
|
||||
if (total) {
|
||||
await generateTotalChangelog(options);
|
||||
} else {
|
||||
await generateChangelog(options);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import path from 'node:path';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import enquirer from 'enquirer';
|
||||
import { bgRed, red, green } from 'kolorist';
|
||||
import { prompt } from 'enquirer';
|
||||
import { execCommand } from '../shared';
|
||||
import type { CliOption } from '../types';
|
||||
import { locales } from '../locales';
|
||||
import type { Lang } from '../locales';
|
||||
|
||||
interface PromptObject {
|
||||
types: string;
|
||||
@@ -11,65 +11,74 @@ interface PromptObject {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export async function gitCommit(
|
||||
gitCommitTypes: CliOption['gitCommitTypes'],
|
||||
gitCommitScopes: CliOption['gitCommitScopes']
|
||||
) {
|
||||
const typesChoices = gitCommitTypes.map(([name, title]) => {
|
||||
const nameWithSuffix = `${name}:`;
|
||||
/**
|
||||
* Git commit with Conventional Commits standard
|
||||
*
|
||||
* @param lang
|
||||
*/
|
||||
export async function gitCommit(lang: Lang = 'en-us') {
|
||||
const { gitCommitMessages, gitCommitTypes, gitCommitScopes } = locales[lang];
|
||||
|
||||
const message = `${nameWithSuffix.padEnd(12)}${title}`;
|
||||
const typesChoices = gitCommitTypes.map(([value, msg]) => {
|
||||
const nameWithSuffix = `${value}:`;
|
||||
|
||||
const message = `${nameWithSuffix.padEnd(12)}${msg}`;
|
||||
|
||||
return {
|
||||
name,
|
||||
name: value,
|
||||
message
|
||||
};
|
||||
});
|
||||
|
||||
const scopesChoices = gitCommitScopes.map(([name, title]) => ({
|
||||
name,
|
||||
message: `${name.padEnd(30)} (${title})`
|
||||
const scopesChoices = gitCommitScopes.map(([value, msg]) => ({
|
||||
name: value,
|
||||
message: `${value.padEnd(30)} (${msg})`
|
||||
}));
|
||||
|
||||
const result = await enquirer.prompt<PromptObject>([
|
||||
const result = await prompt<PromptObject>([
|
||||
{
|
||||
name: 'types',
|
||||
type: 'select',
|
||||
message: 'Please select a type',
|
||||
message: gitCommitMessages.types,
|
||||
choices: typesChoices
|
||||
},
|
||||
{
|
||||
name: 'scopes',
|
||||
type: 'select',
|
||||
message: 'Please select a scope',
|
||||
message: gitCommitMessages.scopes,
|
||||
choices: scopesChoices
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
message: 'Please enter a description'
|
||||
message: gitCommitMessages.description
|
||||
}
|
||||
]);
|
||||
|
||||
const commitMsg = `${result.types}(${result.scopes}): ${result.description}`;
|
||||
const breaking = result.description.startsWith('!') ? '!' : '';
|
||||
|
||||
const description = result.description.replace(/^!/, '').trim();
|
||||
|
||||
const commitMsg = `${result.types}(${result.scopes})${breaking}: ${description}`;
|
||||
|
||||
await execCommand('git', ['commit', '-m', commitMsg], { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
export async function gitCommitVerify() {
|
||||
/** Git commit message verify */
|
||||
export async function gitCommitVerify(lang: Lang = 'en-us', ignores: RegExp[] = []) {
|
||||
const gitPath = await execCommand('git', ['rev-parse', '--show-toplevel']);
|
||||
|
||||
const gitMsgPath = path.join(gitPath, '.git', 'COMMIT_EDITMSG');
|
||||
|
||||
const commitMsg = readFileSync(gitMsgPath, 'utf8').trim();
|
||||
|
||||
const REG_EXP = /(?<type>[a-z]+)(\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i;
|
||||
if (ignores.some(regExp => regExp.test(commitMsg))) return;
|
||||
|
||||
const REG_EXP = /(?<type>[a-z]+)(?:\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i;
|
||||
|
||||
if (!REG_EXP.test(commitMsg)) {
|
||||
throw new Error(
|
||||
`${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green(
|
||||
'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org'
|
||||
)}`
|
||||
);
|
||||
const errorMsg = locales[lang].gitCommitVerify;
|
||||
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './git-commit';
|
||||
export * from './cleanup';
|
||||
export * from './update-pkg';
|
||||
export * from './prettier';
|
||||
export * from './lint-staged';
|
||||
export * from './changelog';
|
||||
export * from './release';
|
||||
export * from './router';
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export async function execLintStaged(config: Record<string, string | string[]>) {
|
||||
const lintStaged = (await import('lint-staged')).default;
|
||||
|
||||
return lintStaged({ config, allowEmpty: true });
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { execCommand } from '../shared';
|
||||
|
||||
export async function prettierWrite(writeGlob: string[]) {
|
||||
await execCommand('npx', ['prettier', '--write', '.', ...writeGlob], {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
}
|
||||
12
packages/scripts/src/commands/release.ts
Normal file
12
packages/scripts/src/commands/release.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { versionBump } from 'bumpp';
|
||||
|
||||
export async function release(execute = 'pnpm sa changelog', push = true) {
|
||||
await versionBump({
|
||||
files: ['**/package.json', '!**/node_modules'],
|
||||
execute,
|
||||
all: true,
|
||||
tag: true,
|
||||
commit: 'chore(projects): release v%s',
|
||||
push
|
||||
});
|
||||
}
|
||||
90
packages/scripts/src/commands/router.ts
Normal file
90
packages/scripts/src/commands/router.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import process from 'node:process';
|
||||
import path from 'node:path';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { existsSync, mkdirSync } from 'node:fs';
|
||||
import { prompt } from 'enquirer';
|
||||
import { green, red } from 'kolorist';
|
||||
|
||||
interface PromptObject {
|
||||
routeName: string;
|
||||
addRouteParams: boolean;
|
||||
routeParams: string;
|
||||
}
|
||||
|
||||
/** generate route */
|
||||
export async function generateRoute() {
|
||||
const result = await prompt<PromptObject>([
|
||||
{
|
||||
name: 'routeName',
|
||||
type: 'text',
|
||||
message: 'please enter route name',
|
||||
initial: 'demo-route_child'
|
||||
},
|
||||
{
|
||||
name: 'addRouteParams',
|
||||
type: 'confirm',
|
||||
message: 'add route params?',
|
||||
initial: false
|
||||
}
|
||||
]);
|
||||
|
||||
if (result.addRouteParams) {
|
||||
const answers = await prompt<PromptObject>({
|
||||
name: 'routeParams',
|
||||
type: 'text',
|
||||
message: 'please enter route params',
|
||||
initial: 'id'
|
||||
});
|
||||
|
||||
Object.assign(result, answers);
|
||||
}
|
||||
|
||||
const PAGE_DIR_NAME_PATTERN = /^[\w-]+[0-9a-zA-Z]+$/;
|
||||
|
||||
if (!PAGE_DIR_NAME_PATTERN.test(result.routeName)) {
|
||||
throw new Error(`${red('route name is invalid, it only allow letters, numbers, "-" or "_"')}.
|
||||
For example:
|
||||
(1) one level route: ${green('demo-route')}
|
||||
(2) two level route: ${green('demo-route_child')}
|
||||
(3) multi level route: ${green('demo-route_child_child')}
|
||||
(4) group route: ${green('_ignore_demo-route')}'
|
||||
`);
|
||||
}
|
||||
|
||||
const PARAM_REG = /^\w+$/g;
|
||||
|
||||
if (result.routeParams && !PARAM_REG.test(result.routeParams)) {
|
||||
throw new Error(red('route params is invalid, it only allow letters, numbers or "_".'));
|
||||
}
|
||||
|
||||
const cwd = process.cwd();
|
||||
|
||||
const [dir, ...rest] = result.routeName.split('_') as string[];
|
||||
|
||||
let routeDir = path.join(cwd, 'src', 'views', dir);
|
||||
|
||||
if (rest.length) {
|
||||
routeDir = path.join(routeDir, rest.join('_'));
|
||||
}
|
||||
|
||||
if (!existsSync(routeDir)) {
|
||||
mkdirSync(routeDir, { recursive: true });
|
||||
} else {
|
||||
throw new Error(red('route already exists'));
|
||||
}
|
||||
|
||||
const fileName = result.routeParams ? `[${result.routeParams}].vue` : 'index.vue';
|
||||
|
||||
const vueTemplate = `<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>${result.routeName}</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
`;
|
||||
|
||||
const filePath = path.join(routeDir, fileName);
|
||||
|
||||
await writeFile(filePath, vueTemplate);
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import process from 'node:process';
|
||||
import { loadConfig } from 'c12';
|
||||
import type { CliOption } from '../types';
|
||||
|
||||
const eslintExt = '*.{js,jsx,mjs,cjs,ts,tsx,vue}';
|
||||
|
||||
const defaultOptions: CliOption = {
|
||||
cwd: process.cwd(),
|
||||
cleanupDirs: [
|
||||
@@ -13,52 +12,18 @@ const defaultOptions: CliOption = {
|
||||
'**/node_modules',
|
||||
'!node_modules/**'
|
||||
],
|
||||
gitCommitTypes: [
|
||||
['feat', 'A new feature'],
|
||||
['fix', 'A bug fix'],
|
||||
['docs', 'Documentation only changes'],
|
||||
['style', 'Changes that do not affect the meaning of the code'],
|
||||
['refactor', 'A code change that neither fixes a bug nor adds a feature'],
|
||||
['perf', 'A code change that improves performance'],
|
||||
['test', 'Adding missing tests or correcting existing tests'],
|
||||
['build', 'Changes that affect the build system or external dependencies'],
|
||||
['ci', 'Changes to our CI configuration files and scripts'],
|
||||
['chore', "Other changes that don't modify src or test files"],
|
||||
['revert', 'Reverts a previous commit']
|
||||
],
|
||||
gitCommitScopes: [
|
||||
['projects', 'project'],
|
||||
['components', 'components'],
|
||||
['hooks', 'hook functions'],
|
||||
['utils', 'utils functions'],
|
||||
['types', 'TS declaration'],
|
||||
['styles', 'style'],
|
||||
['deps', 'project dependencies'],
|
||||
['release', 'release project'],
|
||||
['other', 'other changes']
|
||||
],
|
||||
ncuCommandArgs: ['--deep', '-u'],
|
||||
prettierWriteGlob: [
|
||||
`!**/${eslintExt}`,
|
||||
'!*.min.*',
|
||||
'!CHANGELOG.md',
|
||||
'!dist',
|
||||
'!LICENSE*',
|
||||
'!output',
|
||||
'!coverage',
|
||||
'!public',
|
||||
'!temp',
|
||||
'!package-lock.json',
|
||||
'!pnpm-lock.yaml',
|
||||
'!yarn.lock',
|
||||
'!.github',
|
||||
'!__snapshots__',
|
||||
'!node_modules'
|
||||
],
|
||||
lintStagedConfig: {
|
||||
[eslintExt]: 'eslint --fix',
|
||||
'*': 'sa prettier-write'
|
||||
}
|
||||
changelogOptions: {},
|
||||
gitCommitVerifyIgnores: [
|
||||
/^((Merge pull request)|(Merge (.*?) into (.*?)|(Merge branch (.*?)))(?:\r?\n)*$)/m,
|
||||
/^(Merge tag (.*?))(?:\r?\n)*$/m,
|
||||
/^(R|r)evert (.*)/,
|
||||
/^(amend|fixup|squash)!/,
|
||||
/^(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))/,
|
||||
/^Merge remote-tracking branch(\s*)(.*)/,
|
||||
/^Automatic merge(.*)/,
|
||||
/^Auto-merged (.*?) into (.*)/
|
||||
]
|
||||
};
|
||||
|
||||
export async function loadCliOptions(overrides?: Partial<CliOption>, cwd = process.cwd()) {
|
||||
|
||||
@@ -1,25 +1,58 @@
|
||||
import cac from 'cac';
|
||||
import { blue, lightGreen } from 'kolorist';
|
||||
import { version } from '../package.json';
|
||||
import { cleanup, updatePkg, gitCommit, gitCommitVerify, prettierWrite, execLintStaged } from './commands';
|
||||
import { cleanup, genChangelog, generateRoute, gitCommit, gitCommitVerify, release, updatePkg } from './commands';
|
||||
import { loadCliOptions } from './config';
|
||||
import type { Lang } from './locales';
|
||||
|
||||
type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'prettier-write' | 'lint-staged';
|
||||
type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'changelog' | 'release' | 'gen-route';
|
||||
|
||||
type CommandAction<A extends object> = (args?: A) => Promise<void> | void;
|
||||
|
||||
type CommandWithAction<A extends object = object> = Record<Command, { desc: string; action: CommandAction<A> }>;
|
||||
|
||||
interface CommandArg {
|
||||
/** Execute additional command after bumping and before git commit. Defaults to 'pnpm sa changelog' */
|
||||
execute?: string;
|
||||
/** Indicates whether to push the git commit and tag. Defaults to true */
|
||||
push?: boolean;
|
||||
/** Generate changelog by total tags */
|
||||
total?: boolean;
|
||||
/**
|
||||
* The glob pattern of dirs to clean up
|
||||
*
|
||||
* If not set, it will use the default value
|
||||
*
|
||||
* Multiple values use "," to separate them
|
||||
*/
|
||||
cleanupDir?: string;
|
||||
/**
|
||||
* display lang of cli
|
||||
*
|
||||
* @default 'en-us'
|
||||
*/
|
||||
lang?: Lang;
|
||||
}
|
||||
|
||||
export async function setupCli() {
|
||||
const cliOptions = await loadCliOptions();
|
||||
|
||||
const cli = cac(blue('soybean'));
|
||||
const cli = cac(blue('soybean-admin'));
|
||||
|
||||
cli.version(lightGreen(version)).help();
|
||||
cli
|
||||
.version(lightGreen(version))
|
||||
.option(
|
||||
'-e, --execute [command]',
|
||||
"Execute additional command after bumping and before git commit. Defaults to 'npx soy changelog'"
|
||||
)
|
||||
.option('-p, --push', 'Indicates whether to push the git commit and tag')
|
||||
.option('-t, --total', 'Generate changelog by total tags')
|
||||
.option(
|
||||
'-c, --cleanupDir <dir>',
|
||||
'The glob pattern of dirs to cleanup, If not set, it will use the default value, Multiple values use "," to separate them'
|
||||
)
|
||||
.option('-l, --lang <lang>', 'display lang of cli', { default: 'en-us', type: [String] })
|
||||
.help();
|
||||
|
||||
const commands: CommandWithAction<CommandArg> = {
|
||||
cleanup: {
|
||||
@@ -36,26 +69,32 @@ export async function setupCli() {
|
||||
},
|
||||
'git-commit': {
|
||||
desc: 'git commit, generate commit message which match Conventional Commits standard',
|
||||
action: async () => {
|
||||
await gitCommit(cliOptions.gitCommitTypes, cliOptions.gitCommitScopes);
|
||||
action: async args => {
|
||||
await gitCommit(args?.lang);
|
||||
}
|
||||
},
|
||||
'git-commit-verify': {
|
||||
desc: 'verify git commit message, make sure it match Conventional Commits standard',
|
||||
action: async () => {
|
||||
await gitCommitVerify();
|
||||
action: async args => {
|
||||
await gitCommitVerify(args?.lang, cliOptions.gitCommitVerifyIgnores);
|
||||
}
|
||||
},
|
||||
'prettier-write': {
|
||||
desc: 'run prettier --write',
|
||||
action: async () => {
|
||||
await prettierWrite(cliOptions.prettierWriteGlob);
|
||||
changelog: {
|
||||
desc: 'generate changelog',
|
||||
action: async args => {
|
||||
await genChangelog(cliOptions.changelogOptions, args?.total);
|
||||
}
|
||||
},
|
||||
'lint-staged': {
|
||||
desc: 'run lint-staged',
|
||||
release: {
|
||||
desc: 'release: update version, generate changelog, commit code',
|
||||
action: async args => {
|
||||
await release(args?.execute, args?.push);
|
||||
}
|
||||
},
|
||||
'gen-route': {
|
||||
desc: 'generate route',
|
||||
action: async () => {
|
||||
await execLintStaged(cliOptions.lintStagedConfig);
|
||||
await generateRoute();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
82
packages/scripts/src/locales/index.ts
Normal file
82
packages/scripts/src/locales/index.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { bgRed, green, red, yellow } from 'kolorist';
|
||||
|
||||
export type Lang = 'zh-cn' | 'en-us';
|
||||
|
||||
export const locales = {
|
||||
'zh-cn': {
|
||||
gitCommitMessages: {
|
||||
types: '请选择提交类型',
|
||||
scopes: '请选择提交范围',
|
||||
description: `请输入描述信息(${yellow('!')}开头表示破坏性改动`
|
||||
},
|
||||
gitCommitTypes: [
|
||||
['feat', '新功能'],
|
||||
['feat-wip', '开发中的功能,比如某功能的部分代码'],
|
||||
['fix', '修复Bug'],
|
||||
['docs', '只涉及文档更新'],
|
||||
['typo', '代码或文档勘误,比如错误拼写'],
|
||||
['style', '修改代码风格,不影响代码含义的变更'],
|
||||
['refactor', '代码重构,既不修复 bug 也不添加功能的代码变更'],
|
||||
['perf', '可提高性能的代码更改'],
|
||||
['optimize', '优化代码质量的代码更改'],
|
||||
['test', '添加缺失的测试或更正现有测试'],
|
||||
['build', '影响构建系统或外部依赖项的更改'],
|
||||
['ci', '对 CI 配置文件和脚本的更改'],
|
||||
['chore', '没有修改src或测试文件的其他变更'],
|
||||
['revert', '还原先前的提交']
|
||||
] as [string, string][],
|
||||
gitCommitScopes: [
|
||||
['projects', '项目'],
|
||||
['packages', '包'],
|
||||
['components', '组件'],
|
||||
['hooks', '钩子函数'],
|
||||
['utils', '工具函数'],
|
||||
['types', 'TS类型声明'],
|
||||
['styles', '代码风格'],
|
||||
['deps', '项目依赖'],
|
||||
['release', '发布项目新版本'],
|
||||
['other', '其他的变更']
|
||||
] as [string, string][],
|
||||
gitCommitVerify: `${bgRed(' 错误 ')} ${red('git 提交信息必须符合 Conventional Commits 标准!')}\n\n${green(
|
||||
'推荐使用命令 `pnpm commit` 生成符合 Conventional Commits 标准的提交信息。\n获取有关 Conventional Commits 的更多信息,请访问此链接: https://conventionalcommits.org'
|
||||
)}`
|
||||
},
|
||||
'en-us': {
|
||||
gitCommitMessages: {
|
||||
types: 'Please select a type',
|
||||
scopes: 'Please select a scope',
|
||||
description: `Please enter a description (add prefix ${yellow('!')} to indicate breaking change)`
|
||||
},
|
||||
gitCommitTypes: [
|
||||
['feat', 'A new feature'],
|
||||
['feat-wip', 'Features in development, such as partial code for a certain feature'],
|
||||
['fix', 'A bug fix'],
|
||||
['docs', 'Documentation only changes'],
|
||||
['typo', 'Code or document corrections, such as spelling errors'],
|
||||
['style', 'Changes that do not affect the meaning of the code'],
|
||||
['refactor', 'A code change that neither fixes a bug nor adds a feature'],
|
||||
['perf', 'A code change that improves performance'],
|
||||
['optimize', 'A code change that optimizes code quality'],
|
||||
['test', 'Adding missing tests or correcting existing tests'],
|
||||
['build', 'Changes that affect the build system or external dependencies'],
|
||||
['ci', 'Changes to our CI configuration files and scripts'],
|
||||
['chore', "Other changes that don't modify src or test files"],
|
||||
['revert', 'Reverts a previous commit']
|
||||
] as [string, string][],
|
||||
gitCommitScopes: [
|
||||
['projects', 'project'],
|
||||
['packages', 'packages'],
|
||||
['components', 'components'],
|
||||
['hooks', 'hook functions'],
|
||||
['utils', 'utils functions'],
|
||||
['types', 'TS declaration'],
|
||||
['styles', 'style'],
|
||||
['deps', 'project dependencies'],
|
||||
['release', 'release project'],
|
||||
['other', 'other changes']
|
||||
] as [string, string][],
|
||||
gitCommitVerify: `${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green(
|
||||
'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org'
|
||||
)}`
|
||||
}
|
||||
} satisfies Record<Lang, Record<string, unknown>>;
|
||||
@@ -3,5 +3,5 @@ import type { Options } from 'execa';
|
||||
export async function execCommand(cmd: string, args: string[], options?: Options) {
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa(cmd, args, options);
|
||||
return res?.stdout?.trim() || '';
|
||||
return (res?.stdout as string)?.trim() || '';
|
||||
}
|
||||
|
||||
@@ -1,37 +1,31 @@
|
||||
import type { ChangelogOption } from '@soybeanjs/changelog';
|
||||
|
||||
export interface CliOption {
|
||||
/**
|
||||
* the project root directory
|
||||
*/
|
||||
/** The project root directory */
|
||||
cwd: string;
|
||||
/**
|
||||
* cleanup dirs
|
||||
* Cleanup dirs
|
||||
*
|
||||
* Glob pattern syntax {@link https://github.com/isaacs/minimatch}
|
||||
*
|
||||
* @default
|
||||
* ```json
|
||||
* ["** /dist", "** /pnpm-lock.yaml", "** /node_modules", "!node_modules/**"]
|
||||
* ```
|
||||
* @description glob pattern syntax {@link https://github.com/isaacs/minimatch}
|
||||
*/
|
||||
cleanupDirs: string[];
|
||||
/**
|
||||
* git commit types
|
||||
*/
|
||||
gitCommitTypes: [string, string][];
|
||||
/**
|
||||
* git commit scopes
|
||||
*/
|
||||
gitCommitScopes: [string, string][];
|
||||
/**
|
||||
* npm-check-updates command args
|
||||
* @default ["--deep","-u"]
|
||||
* Npm-check-updates command args
|
||||
*
|
||||
* @default ['--deep', '-u']
|
||||
*/
|
||||
ncuCommandArgs: string[];
|
||||
/**
|
||||
* prettier write glob
|
||||
* @description glob pattern syntax {@link https://github.com/micromatch/micromatch}
|
||||
* Options of generate changelog
|
||||
*
|
||||
* @link https://github.com/soybeanjs/changelog
|
||||
*/
|
||||
prettierWriteGlob: string[];
|
||||
/**
|
||||
* lint-staged config
|
||||
*/
|
||||
lintStagedConfig: Record<string, string | string[]>;
|
||||
changelogOptions: Partial<ChangelogOption>;
|
||||
/** The ignore pattern list of git commit verify */
|
||||
gitCommitVerifyIgnores: RegExp[];
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"types": ["node"]
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*", "typings/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
15
packages/scripts/typings/pkg.d.ts
vendored
15
packages/scripts/typings/pkg.d.ts
vendored
@@ -1,15 +0,0 @@
|
||||
declare module 'lint-staged' {
|
||||
interface LintStagedOptions {
|
||||
config?: Record<string, string | string[]>;
|
||||
allowEmpty?: boolean;
|
||||
}
|
||||
|
||||
type LintStagedFn = (options: LintStagedOptions) => Promise<boolean>;
|
||||
interface LintStaged extends LintStagedFn {
|
||||
default: LintStagedFn;
|
||||
}
|
||||
|
||||
const lintStaged: LintStaged;
|
||||
|
||||
export default lintStaged;
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
{
|
||||
"name": "@sa/uno-preset",
|
||||
"version": "1.0.0",
|
||||
"version": "1.3.7",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"./src/*"
|
||||
]
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,23 +5,21 @@ import type { Theme } from '@unocss/preset-uno';
|
||||
|
||||
export function presetSoybeanAdmin(): Preset<Theme> {
|
||||
const preset: Preset<Theme> = {
|
||||
name: 'preset-soybean-tool',
|
||||
name: 'preset-soybean-admin',
|
||||
shortcuts: [
|
||||
{
|
||||
'wh-full': 'w-full h-full'
|
||||
},
|
||||
{
|
||||
'flex-center': 'flex justify-center items-center',
|
||||
'flex-x-center': 'flex justify-center',
|
||||
'flex-y-center': 'flex items-center',
|
||||
'flex-vertical': 'flex flex-col',
|
||||
'flex-vertical-center': 'flex-center flex-col',
|
||||
'flex-vertical-stretch': 'flex-vertical items-stretch',
|
||||
'flex-col': 'flex flex-col',
|
||||
'flex-col-center': 'flex-center flex-col',
|
||||
'flex-col-stretch': 'flex-col items-stretch',
|
||||
'i-flex-center': 'inline-flex justify-center items-center',
|
||||
'i-flex-x-center': 'inline-flex justify-center',
|
||||
'i-flex-y-center': 'inline-flex items-center',
|
||||
'i-flex-vertical': 'inline-flex flex-col',
|
||||
'i-flex-vertical-stretch': 'i-flex-vertical items-stretch',
|
||||
'i-flex-col': 'flex-col inline-flex',
|
||||
'i-flex-col-center': 'flex-col i-flex-center',
|
||||
'i-flex-col-stretch': 'i-flex-col items-stretch',
|
||||
'flex-1-hidden': 'flex-1 overflow-hidden'
|
||||
},
|
||||
{
|
||||
@@ -33,7 +31,7 @@ export function presetSoybeanAdmin(): Preset<Theme> {
|
||||
'absolute-tr': 'absolute-rt',
|
||||
'absolute-bl': 'absolute-lb',
|
||||
'absolute-br': 'absolute-rb',
|
||||
'absolute-center': 'absolute-lt flex-center wh-full',
|
||||
'absolute-center': 'absolute-lt flex-center size-full',
|
||||
'fixed-lt': 'fixed left-0 top-0',
|
||||
'fixed-lb': 'fixed left-0 bottom-0',
|
||||
'fixed-rt': 'fixed right-0 top-0',
|
||||
@@ -42,7 +40,7 @@ export function presetSoybeanAdmin(): Preset<Theme> {
|
||||
'fixed-tr': 'fixed-rt',
|
||||
'fixed-bl': 'fixed-lb',
|
||||
'fixed-br': 'fixed-rb',
|
||||
'fixed-center': 'fixed-lt flex-center wh-full'
|
||||
'fixed-center': 'fixed-lt flex-center size-full'
|
||||
},
|
||||
{
|
||||
'nowrap-hidden': 'overflow-hidden whitespace-nowrap',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user