@mtn-ui-z/utils 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -0
- package/package.json +23 -0
- package/src/common.d.ts +3 -0
- package/src/common.ts +16 -0
- package/src/date.ts +34 -0
- package/src/dict.ts +193 -0
- package/src/dom.d.ts +8 -0
- package/src/dom.ts +20 -0
- package/src/function.d.ts +1 -0
- package/src/function.ts +9 -0
- package/src/index.d.ts +36 -0
- package/src/index.ts +61 -0
- package/src/interaction.d.ts +1 -0
- package/src/interaction.ts +10 -0
- package/src/media.d.ts +6 -0
- package/src/media.ts +11 -0
- package/src/modalRegistry.ts +37 -0
- package/src/permission.d.ts +27 -0
- package/src/permission.ts +165 -0
- package/src/request-types.ts +57 -0
- package/src/request.ts +178 -0
- package/src/state.d.ts +6 -0
- package/src/state.ts +11 -0
- package/src/storage.ts +143 -0
- package/src/theme.d.ts +107 -0
- package/src/theme.ts +502 -0
- package/src/types.d.ts +6 -0
- package/src/types.ts +11 -0
- package/src/useModal.ts +110 -0
- package/src/useTable.ts +307 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 权限管理模块
|
|
3
|
+
* 提供统一的权限检查功能与 usePermission composable
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
inject,
|
|
7
|
+
computed,
|
|
8
|
+
toValue,
|
|
9
|
+
unref,
|
|
10
|
+
type InjectionKey,
|
|
11
|
+
type Ref,
|
|
12
|
+
type MaybeRefOrGetter,
|
|
13
|
+
type ComputedRef
|
|
14
|
+
} from 'vue'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 权限检查函数类型
|
|
18
|
+
* @param permission 权限码(支持单个或多个,多个时满足任一即可)
|
|
19
|
+
* @returns 是否有权限
|
|
20
|
+
*/
|
|
21
|
+
export type PermissionChecker = (permission: string | string[]) => boolean
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 权限行为类型
|
|
25
|
+
* - hide: 无权限时隐藏元素
|
|
26
|
+
* - disable: 无权限时禁用元素
|
|
27
|
+
*/
|
|
28
|
+
export type PermissionAction = 'hide' | 'disable'
|
|
29
|
+
|
|
30
|
+
/** 将权限码规范为数组,供内部与 hasPermission / createPermissionChecker 复用 */
|
|
31
|
+
function normalizePermissionList(permission: string | string[]): string[] {
|
|
32
|
+
return Array.isArray(permission) ? permission : [permission]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 默认权限检查函数
|
|
37
|
+
* 在未配置自定义检查函数时使用,默认返回 true(所有人都有权限)
|
|
38
|
+
*/
|
|
39
|
+
export const defaultPermissionChecker: PermissionChecker = (_permission) => {
|
|
40
|
+
if (import.meta.env.DEV) {
|
|
41
|
+
console.warn(
|
|
42
|
+
'[mtn-ui] 未配置权限检查函数,默认返回 true。' +
|
|
43
|
+
'请在应用根组件中通过 provide 提供 PERMISSION_CHECKER_KEY。'
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 权限检查函数的 Provide/Inject Key
|
|
51
|
+
* 在应用根组件中通过 provide 提供自定义的权限检查函数
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* import { provide, ref, computed } from 'vue'
|
|
56
|
+
* import { PERMISSION_CHECKER_KEY } from '@mtn-ui-z/utils'
|
|
57
|
+
*
|
|
58
|
+
* const userPermissions = ref(['user:add', 'user:edit'])
|
|
59
|
+
* provide(PERMISSION_CHECKER_KEY, computed(() => {
|
|
60
|
+
* return (permission: string | string[]) => {
|
|
61
|
+
* const permArray = Array.isArray(permission) ? permission : [permission]
|
|
62
|
+
* return permArray.some((perm) => userPermissions.value.includes(perm))
|
|
63
|
+
* }
|
|
64
|
+
* }))
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export const PERMISSION_CHECKER_KEY: InjectionKey<Ref<PermissionChecker>> = Symbol('permissionChecker')
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 用户权限列表的 Provide/Inject Key(可选)
|
|
71
|
+
* 如果使用 Pinia/Vuex 管理权限,可以不使用此 Key
|
|
72
|
+
*/
|
|
73
|
+
export const USER_PERMISSIONS_KEY: InjectionKey<Ref<string[]>> = Symbol('userPermissions')
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 创建默认的权限检查函数
|
|
77
|
+
* 基于权限列表进行检查
|
|
78
|
+
*
|
|
79
|
+
* @param permissions 用户权限列表
|
|
80
|
+
* @returns 权限检查函数
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const userPermissions = ref(['user:add', 'user:edit'])
|
|
85
|
+
* const checker = createPermissionChecker(userPermissions)
|
|
86
|
+
*
|
|
87
|
+
* provide(PERMISSION_CHECKER_KEY, computed(() => checker))
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function createPermissionChecker(permissions: Ref<string[]>): PermissionChecker {
|
|
91
|
+
return (permission) =>
|
|
92
|
+
normalizePermissionList(permission).some((perm) => permissions.value.includes(perm))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 工具函数:检查是否有指定权限
|
|
97
|
+
* 可用于非组件场景的权限判断
|
|
98
|
+
*
|
|
99
|
+
* @param userPermissions 用户权限列表
|
|
100
|
+
* @param requiredPermission 需要的权限
|
|
101
|
+
* @returns 是否有权限
|
|
102
|
+
*/
|
|
103
|
+
export function hasPermission(
|
|
104
|
+
userPermissions: string[],
|
|
105
|
+
requiredPermission: string | string[]
|
|
106
|
+
): boolean {
|
|
107
|
+
return normalizePermissionList(requiredPermission).some((perm) =>
|
|
108
|
+
userPermissions.includes(perm)
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ==================== usePermission Composable ====================
|
|
113
|
+
|
|
114
|
+
export interface UsePermissionOptions {
|
|
115
|
+
/** 权限码(支持响应式) */
|
|
116
|
+
permission?: MaybeRefOrGetter<string | string[] | undefined>
|
|
117
|
+
/** 无权限时的行为(支持响应式) */
|
|
118
|
+
permissionAction?: MaybeRefOrGetter<PermissionAction | undefined>
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface UsePermissionReturn {
|
|
122
|
+
/** 是否有权限 */
|
|
123
|
+
hasPermission: ComputedRef<boolean>
|
|
124
|
+
/** 是否应该隐藏 */
|
|
125
|
+
shouldHide: ComputedRef<boolean>
|
|
126
|
+
/** 是否应该禁用 */
|
|
127
|
+
shouldDisable: ComputedRef<boolean>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 使用权限控制
|
|
132
|
+
* @param options 权限配置选项(支持响应式)
|
|
133
|
+
* @returns 权限状态
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const { hasPermission, shouldHide, shouldDisable } = usePermission({
|
|
138
|
+
* permission: props.permission,
|
|
139
|
+
* permissionAction: props.permissionAction,
|
|
140
|
+
* })
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function usePermission(options: UsePermissionOptions): UsePermissionReturn {
|
|
144
|
+
const permissionCheckerRef = inject<Ref<PermissionChecker> | null>(PERMISSION_CHECKER_KEY, null)
|
|
145
|
+
|
|
146
|
+
const hasPermission = computed(() => {
|
|
147
|
+
const p = toValue(options.permission)
|
|
148
|
+
if (!p) return true
|
|
149
|
+
const checker: PermissionChecker = permissionCheckerRef
|
|
150
|
+
? unref(permissionCheckerRef)
|
|
151
|
+
: defaultPermissionChecker
|
|
152
|
+
return checker(p)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const action = computed(() => toValue(options.permissionAction) ?? 'hide')
|
|
156
|
+
|
|
157
|
+
const shouldHide = computed(() => !hasPermission.value && action.value === 'hide')
|
|
158
|
+
const shouldDisable = computed(() => !hasPermission.value && action.value === 'disable')
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
hasPermission,
|
|
162
|
+
shouldHide,
|
|
163
|
+
shouldDisable
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: liaokt
|
|
3
|
+
* @Description: API 相关类型定义
|
|
4
|
+
* @Date: 2025-11-06 09:44:01
|
|
5
|
+
* @LastEditors: liaokt
|
|
6
|
+
* @LastEditTime: 2025-12-10 09:12:22
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** API 响应基础类型(Result 是 ApiResponse 的别名,保持向后兼容) */
|
|
10
|
+
export interface Result<T = unknown> {
|
|
11
|
+
code: number
|
|
12
|
+
message?: string
|
|
13
|
+
msg?: string
|
|
14
|
+
data: T
|
|
15
|
+
success?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** ApiResponse 作为 Result 的别名,保持向后兼容 */
|
|
19
|
+
export type ApiResponse<T = unknown> = Result<T>
|
|
20
|
+
|
|
21
|
+
/** 分页请求参数 */
|
|
22
|
+
export interface PaginationParams {
|
|
23
|
+
page: number
|
|
24
|
+
page_size: number
|
|
25
|
+
[key: string]: unknown
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 分页响应数据 */
|
|
29
|
+
export interface PaginationData<T> {
|
|
30
|
+
list: T[]
|
|
31
|
+
total: number
|
|
32
|
+
page: number
|
|
33
|
+
page_size: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 分页响应类型 */
|
|
37
|
+
export interface PaginationResponse<T> extends Result<PaginationData<T>> {}
|
|
38
|
+
|
|
39
|
+
/** 列表响应类型 */
|
|
40
|
+
export interface ListResponse<T> extends Result<T[]> {}
|
|
41
|
+
|
|
42
|
+
/** 响应处理结果接口 */
|
|
43
|
+
export interface ResponseResult<T = unknown> {
|
|
44
|
+
success: boolean
|
|
45
|
+
data: T | null
|
|
46
|
+
message: string
|
|
47
|
+
code: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 请求配置扩展 */
|
|
51
|
+
export interface RequestConfig {
|
|
52
|
+
hiddenError?: boolean
|
|
53
|
+
showLoading?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 默认成功码(与后端约定一致时可覆盖) */
|
|
57
|
+
export const DEFAULT_API_SUCCESS_CODE = 10200
|
package/src/request.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: liaokt
|
|
3
|
+
* @Description: 基于 axios 的请求封装,支持拦截器与可配置错误/401 处理
|
|
4
|
+
* @Date: 2025-11-06 09:14:28
|
|
5
|
+
* @LastEditors: liaokt
|
|
6
|
+
* @LastEditTime: 2025-12-26 10:23:57
|
|
7
|
+
*/
|
|
8
|
+
import axios from 'axios'
|
|
9
|
+
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
|
10
|
+
import { getStorage, removeStorage } from './storage'
|
|
11
|
+
import type { Result } from './request-types'
|
|
12
|
+
import { DEFAULT_API_SUCCESS_CODE } from './request-types'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 扩展 AxiosRequestConfig
|
|
16
|
+
*/
|
|
17
|
+
declare module 'axios' {
|
|
18
|
+
export interface AxiosRequestConfig {
|
|
19
|
+
hiddenError?: boolean
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CreateRequestOptions {
|
|
24
|
+
baseURL?: string
|
|
25
|
+
timeout?: number
|
|
26
|
+
/** 业务成功码,默认 10200 */
|
|
27
|
+
successCode?: number
|
|
28
|
+
/** 获取 token,用于请求头 Authorization */
|
|
29
|
+
getToken?: () => string | undefined
|
|
30
|
+
/** 显示错误提示,不传则仅 console */
|
|
31
|
+
showError?: (msg: string) => void
|
|
32
|
+
/** 401 未授权时的回调(如清 token、跳转登录) */
|
|
33
|
+
onUnauthorized?: (message?: string) => void
|
|
34
|
+
/** 存储 key:token、用户信息等,用于 401 时清除 */
|
|
35
|
+
storageKeysToClearOn401?: string[]
|
|
36
|
+
/** 响应头带 key 时保存(如验证码 key),不传则不处理 */
|
|
37
|
+
onSaveCaptchaKey?: (key: string) => void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let errorMessageTimer: ReturnType<typeof setTimeout> | null = null
|
|
41
|
+
|
|
42
|
+
function debouncedShowError(showErrorFn: (msg: string) => void, errorMsg: string): void {
|
|
43
|
+
if (errorMessageTimer) clearTimeout(errorMessageTimer)
|
|
44
|
+
errorMessageTimer = setTimeout(() => showErrorFn(errorMsg), 300)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 创建请求实例(可传入 showError、onUnauthorized 等与 UI/路由对接)
|
|
49
|
+
*/
|
|
50
|
+
export function createRequest(options: CreateRequestOptions = {}) {
|
|
51
|
+
const {
|
|
52
|
+
baseURL = '',
|
|
53
|
+
timeout = 10000,
|
|
54
|
+
successCode = DEFAULT_API_SUCCESS_CODE,
|
|
55
|
+
getToken,
|
|
56
|
+
showError = (msg) => console.error(msg),
|
|
57
|
+
onUnauthorized,
|
|
58
|
+
storageKeysToClearOn401 = ['token', 'userInfo'],
|
|
59
|
+
onSaveCaptchaKey
|
|
60
|
+
} = options
|
|
61
|
+
|
|
62
|
+
const service: AxiosInstance = axios.create({
|
|
63
|
+
baseURL,
|
|
64
|
+
timeout,
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json;charset=UTF-8'
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
service.interceptors.request.use(
|
|
71
|
+
(config) => {
|
|
72
|
+
const token = getToken?.() ?? getStorage<string>('token')
|
|
73
|
+
if (token) {
|
|
74
|
+
const h = config.headers as Record<string, string>
|
|
75
|
+
if (!h.Authorization) h.Authorization = `JWT ${token}`
|
|
76
|
+
}
|
|
77
|
+
const headers = config.headers as Record<string, string>
|
|
78
|
+
headers['X-Timestamp'] = String(Math.floor(Date.now() / 1000))
|
|
79
|
+
const csrfToken = typeof document !== 'undefined' && document.cookie
|
|
80
|
+
? document.cookie
|
|
81
|
+
.split('; ')
|
|
82
|
+
.find((row) => row.startsWith('csrftoken='))
|
|
83
|
+
?.split('=')[1]
|
|
84
|
+
: undefined
|
|
85
|
+
if (csrfToken) headers['X-CSRFToken'] = csrfToken
|
|
86
|
+
return config
|
|
87
|
+
},
|
|
88
|
+
(error: AxiosError) => {
|
|
89
|
+
console.error('请求错误:', error.message)
|
|
90
|
+
return Promise.reject(error)
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
service.interceptors.response.use(
|
|
95
|
+
(response: AxiosResponse) => {
|
|
96
|
+
const headers = response.headers as Record<string, string>
|
|
97
|
+
if (headers && 'key' in headers && onSaveCaptchaKey) {
|
|
98
|
+
onSaveCaptchaKey(headers.key as string)
|
|
99
|
+
}
|
|
100
|
+
if (response.config.responseType === 'blob') {
|
|
101
|
+
return response
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const body = response.data as Result
|
|
105
|
+
const code = body?.code
|
|
106
|
+
const msg = body?.message ?? body?.msg
|
|
107
|
+
|
|
108
|
+
if (code === successCode) {
|
|
109
|
+
return response.data
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!response.config.hiddenError) {
|
|
113
|
+
debouncedShowError(showError, msg ?? '请求失败')
|
|
114
|
+
}
|
|
115
|
+
return Promise.reject(response.data)
|
|
116
|
+
},
|
|
117
|
+
(error: AxiosError) => {
|
|
118
|
+
const status = error.response?.status
|
|
119
|
+
const errorData = error.response?.data as Result | undefined
|
|
120
|
+
let errorMessage: string | undefined
|
|
121
|
+
if (errorData && typeof errorData === 'object') {
|
|
122
|
+
errorMessage = (errorData as Result).message ?? (errorData as Result).msg
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
switch (status) {
|
|
126
|
+
case 401: {
|
|
127
|
+
storageKeysToClearOn401.forEach((key) => removeStorage(key))
|
|
128
|
+
const msg = errorMessage ?? '登录已失效,请重新登录'
|
|
129
|
+
if (!error?.config?.hiddenError) debouncedShowError(showError, msg)
|
|
130
|
+
onUnauthorized?.(msg)
|
|
131
|
+
return Promise.reject(error)
|
|
132
|
+
}
|
|
133
|
+
case 403:
|
|
134
|
+
errorMessage = errorMessage ?? '无访问权限'
|
|
135
|
+
break
|
|
136
|
+
case 404:
|
|
137
|
+
errorMessage = errorMessage ?? '请求地址不存在'
|
|
138
|
+
break
|
|
139
|
+
case 500:
|
|
140
|
+
errorMessage = errorMessage ?? '服务器内部错误'
|
|
141
|
+
break
|
|
142
|
+
default:
|
|
143
|
+
errorMessage = errorMessage ?? '网络请求失败'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!error?.config?.hiddenError && status !== 401 && status !== 404) {
|
|
147
|
+
debouncedShowError(showError, errorMessage ?? '网络请求失败')
|
|
148
|
+
}
|
|
149
|
+
return Promise.reject(error)
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
const request = {
|
|
154
|
+
get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
|
155
|
+
return service.get(url, config)
|
|
156
|
+
},
|
|
157
|
+
post<T = unknown>(url: string, data?: object, config?: AxiosRequestConfig): Promise<T> {
|
|
158
|
+
return service.post(url, data, config)
|
|
159
|
+
},
|
|
160
|
+
put<T = unknown>(url: string, data?: object, config?: AxiosRequestConfig): Promise<T> {
|
|
161
|
+
return service.put(url, data, config)
|
|
162
|
+
},
|
|
163
|
+
patch<T = unknown>(url: string, data?: object, config?: AxiosRequestConfig): Promise<T> {
|
|
164
|
+
return service.patch(url, data, config)
|
|
165
|
+
},
|
|
166
|
+
delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
|
167
|
+
return service.delete(url, config)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { request, axiosInstance: service }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 默认实例(无 UI/路由注入,仅控制台报错),应用内建议使用 createRequest 传入 showError/onUnauthorized */
|
|
175
|
+
const { request, axiosInstance } = createRequest()
|
|
176
|
+
|
|
177
|
+
export { request, axiosInstance }
|
|
178
|
+
export default axiosInstance
|
package/src/state.d.ts
ADDED
package/src/state.ts
ADDED
package/src/storage.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: liaokt
|
|
3
|
+
* @Description: 本地存储工具函数
|
|
4
|
+
* @Date: 2025-12-10
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function getStorageImpl(): Storage | null {
|
|
8
|
+
return typeof localStorage !== 'undefined' ? localStorage : null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getSessionImpl(): Storage | null {
|
|
12
|
+
return typeof sessionStorage !== 'undefined' ? sessionStorage : null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 获取本地存储数据
|
|
17
|
+
* @param key 键名
|
|
18
|
+
* @param defaultValue 默认值
|
|
19
|
+
*/
|
|
20
|
+
export function getStorage<T = unknown>(key: string, defaultValue?: T): T | undefined {
|
|
21
|
+
try {
|
|
22
|
+
const storage = getStorageImpl()
|
|
23
|
+
if (!storage) return defaultValue
|
|
24
|
+
const item = storage.getItem(key)
|
|
25
|
+
if (item === null) return defaultValue
|
|
26
|
+
return JSON.parse(item) as T
|
|
27
|
+
} catch {
|
|
28
|
+
return defaultValue
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 设置本地存储数据
|
|
34
|
+
* @param key 键名
|
|
35
|
+
* @param value 数据值
|
|
36
|
+
*/
|
|
37
|
+
export function setStorage<T = unknown>(key: string, value: T): void {
|
|
38
|
+
try {
|
|
39
|
+
const storage = getStorageImpl()
|
|
40
|
+
if (storage) storage.setItem(key, JSON.stringify(value))
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('存储数据失败:', error)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 移除本地存储数据
|
|
48
|
+
* @param key 键名
|
|
49
|
+
*/
|
|
50
|
+
export function removeStorage(key: string): void {
|
|
51
|
+
try {
|
|
52
|
+
const storage = getStorageImpl()
|
|
53
|
+
if (storage) storage.removeItem(key)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('删除数据失败:', error)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 清空所有本地存储数据
|
|
61
|
+
*/
|
|
62
|
+
export function clearStorage(): void {
|
|
63
|
+
try {
|
|
64
|
+
const storage = getStorageImpl()
|
|
65
|
+
if (storage) storage.clear()
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('清空存储失败:', error)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 检查本地存储中是否存在某个键
|
|
73
|
+
* @param key 键名
|
|
74
|
+
*/
|
|
75
|
+
export function hasStorage(key: string): boolean {
|
|
76
|
+
const storage = getStorageImpl()
|
|
77
|
+
return storage ? storage.getItem(key) !== null : false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 获取会话存储数据
|
|
82
|
+
* @param key 键名
|
|
83
|
+
* @param defaultValue 默认值
|
|
84
|
+
*/
|
|
85
|
+
export function getSession<T = unknown>(key: string, defaultValue?: T): T | undefined {
|
|
86
|
+
try {
|
|
87
|
+
const storage = getSessionImpl()
|
|
88
|
+
if (!storage) return defaultValue
|
|
89
|
+
const item = storage.getItem(key)
|
|
90
|
+
if (item === null) return defaultValue
|
|
91
|
+
return JSON.parse(item) as T
|
|
92
|
+
} catch {
|
|
93
|
+
return defaultValue
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 设置会话存储数据
|
|
99
|
+
* @param key 键名
|
|
100
|
+
* @param value 数据值
|
|
101
|
+
*/
|
|
102
|
+
export function setSession<T = unknown>(key: string, value: T): void {
|
|
103
|
+
try {
|
|
104
|
+
const storage = getSessionImpl()
|
|
105
|
+
if (storage) storage.setItem(key, JSON.stringify(value))
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('存储数据失败:', error)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 移除会话存储数据
|
|
113
|
+
* @param key 键名
|
|
114
|
+
*/
|
|
115
|
+
export function removeSession(key: string): void {
|
|
116
|
+
try {
|
|
117
|
+
const storage = getSessionImpl()
|
|
118
|
+
if (storage) storage.removeItem(key)
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('删除数据失败:', error)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 清空所有会话存储数据
|
|
126
|
+
*/
|
|
127
|
+
export function clearSession(): void {
|
|
128
|
+
try {
|
|
129
|
+
const storage = getSessionImpl()
|
|
130
|
+
if (storage) storage.clear()
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('清空存储失败:', error)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 检查会话存储中是否存在某个键
|
|
138
|
+
* @param key 键名
|
|
139
|
+
*/
|
|
140
|
+
export function hasSession(key: string): boolean {
|
|
141
|
+
const storage = getSessionImpl()
|
|
142
|
+
return storage ? storage.getItem(key) !== null : false
|
|
143
|
+
}
|
package/src/theme.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行时主题色类型声明
|
|
3
|
+
* 避免在部分环境下 TS 解析 node_modules 内 .ts 失败
|
|
4
|
+
*/
|
|
5
|
+
import type { Ref } from 'vue'
|
|
6
|
+
|
|
7
|
+
export interface ThemeColorOverrides {
|
|
8
|
+
primary?: string
|
|
9
|
+
primaryHover?: string
|
|
10
|
+
primaryActive?: string
|
|
11
|
+
success?: string
|
|
12
|
+
successHover?: string
|
|
13
|
+
warning?: string
|
|
14
|
+
warningHover?: string
|
|
15
|
+
danger?: string
|
|
16
|
+
dangerHover?: string
|
|
17
|
+
dangerActive?: string
|
|
18
|
+
info?: string
|
|
19
|
+
infoHover?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ThemeMode = 'light' | 'dark'
|
|
23
|
+
export type DensityMode = 'default' | 'compact'
|
|
24
|
+
|
|
25
|
+
export declare const MTN_THEME_PRESETS: readonly ['default', 'ocean']
|
|
26
|
+
|
|
27
|
+
export type MtnThemePreset = (typeof MTN_THEME_PRESETS)[number]
|
|
28
|
+
|
|
29
|
+
export interface MtnAntThemeToken {
|
|
30
|
+
colorPrimary?: string
|
|
31
|
+
colorPrimaryHover?: string
|
|
32
|
+
colorPrimaryActive?: string
|
|
33
|
+
colorLink?: string
|
|
34
|
+
colorLinkHover?: string
|
|
35
|
+
colorLinkActive?: string
|
|
36
|
+
borderRadius?: number
|
|
37
|
+
colorBgBase?: string
|
|
38
|
+
colorText?: string
|
|
39
|
+
colorTextSecondary?: string
|
|
40
|
+
colorBorder?: string
|
|
41
|
+
colorSuccess?: string
|
|
42
|
+
colorWarning?: string
|
|
43
|
+
colorError?: string
|
|
44
|
+
colorInfo?: string
|
|
45
|
+
controlHeight?: number
|
|
46
|
+
fontSize?: number
|
|
47
|
+
padding?: number
|
|
48
|
+
paddingXS?: number
|
|
49
|
+
paddingSM?: number
|
|
50
|
+
paddingLG?: number
|
|
51
|
+
margin?: number
|
|
52
|
+
marginXS?: number
|
|
53
|
+
marginSM?: number
|
|
54
|
+
marginLG?: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const themeModeRef: Ref<ThemeMode>
|
|
58
|
+
export const densityModeRef: Ref<DensityMode>
|
|
59
|
+
export const antThemeConfigRef: Ref<{ token: MtnAntThemeToken }>
|
|
60
|
+
|
|
61
|
+
export function syncAntTokensFromCssVars(): void
|
|
62
|
+
|
|
63
|
+
export interface ApplyMtnThemeOptions {
|
|
64
|
+
mode?: ThemeMode
|
|
65
|
+
density?: DensityMode
|
|
66
|
+
preset?: MtnThemePreset | string | null
|
|
67
|
+
colors?: ThemeColorOverrides
|
|
68
|
+
antToken?: Partial<MtnAntThemeToken>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function applyMtnTheme(opts: ApplyMtnThemeOptions): void
|
|
72
|
+
|
|
73
|
+
export function setThemeColors(colors: ThemeColorOverrides): void
|
|
74
|
+
export function setThemePrimary(
|
|
75
|
+
primary: string,
|
|
76
|
+
hoverLighten?: number,
|
|
77
|
+
activeDarken?: number
|
|
78
|
+
): void
|
|
79
|
+
export function setThemeMode(
|
|
80
|
+
mode: ThemeMode,
|
|
81
|
+
options?: { persist?: boolean; storageKey?: string }
|
|
82
|
+
): void
|
|
83
|
+
export function toggleThemeMode(options?: {
|
|
84
|
+
persist?: boolean
|
|
85
|
+
storageKey?: string
|
|
86
|
+
}): ThemeMode
|
|
87
|
+
export function getThemeMode(): ThemeMode
|
|
88
|
+
export function setDensityMode(
|
|
89
|
+
mode: DensityMode,
|
|
90
|
+
options?: { persist?: boolean; storageKey?: string }
|
|
91
|
+
): void
|
|
92
|
+
export function getDensityMode(): DensityMode
|
|
93
|
+
export function toggleDensityMode(options?: {
|
|
94
|
+
persist?: boolean
|
|
95
|
+
storageKey?: string
|
|
96
|
+
}): DensityMode
|
|
97
|
+
export function initDensityFromStorage(options?: { storageKey?: string }): DensityMode
|
|
98
|
+
export function persistDensityMode(mode: DensityMode, options?: { storageKey?: string }): void
|
|
99
|
+
export function initThemeFromStorage(options?: { storageKey?: string }): ThemeMode
|
|
100
|
+
export function persistThemeMode(mode: ThemeMode, options?: { storageKey?: string }): void
|
|
101
|
+
export function initTheme(options?: {
|
|
102
|
+
storageKey?: string
|
|
103
|
+
densityStorageKey?: string
|
|
104
|
+
preferColorScheme?: boolean
|
|
105
|
+
}): ThemeMode
|
|
106
|
+
export function setThemePreset(preset: MtnThemePreset | string | null | undefined): void
|
|
107
|
+
export function getThemePreset(): string | null
|