@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
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# @mtn-ui-z/utils
|
|
2
|
+
|
|
3
|
+
MTN UI 工具函数包,基于 VueUse 构建。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @mtn-ui-z/utils @vueuse/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 目录结构
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
packages/utils/src/
|
|
15
|
+
├── common.ts # 通用工具函数
|
|
16
|
+
├── function.ts # 函数工具(防抖、节流)
|
|
17
|
+
├── state.ts # 状态管理工具
|
|
18
|
+
├── dom.ts # DOM 操作工具
|
|
19
|
+
├── interaction.ts # 交互工具(鼠标、滚动等)
|
|
20
|
+
├── media.ts # 媒体查询工具
|
|
21
|
+
├── types.ts # 类型定义
|
|
22
|
+
└── index.ts # 主入口(重新导出所有内容)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 功能分类
|
|
26
|
+
|
|
27
|
+
### 通用工具 (`common.ts`)
|
|
28
|
+
|
|
29
|
+
- `classNames` - 格式化类名
|
|
30
|
+
|
|
31
|
+
### 函数工具 (`function.ts`)
|
|
32
|
+
|
|
33
|
+
- `useDebounceFn` - 防抖函数
|
|
34
|
+
- `useThrottleFn` - 节流函数
|
|
35
|
+
|
|
36
|
+
### 状态管理 (`state.ts`)
|
|
37
|
+
|
|
38
|
+
本包重新导出了 VueUse 的状态管理函数,包括:
|
|
39
|
+
|
|
40
|
+
- `useToggle` - 切换布尔值
|
|
41
|
+
- `useCounter` - 计数器
|
|
42
|
+
- `useLocalStorage` - 本地存储
|
|
43
|
+
- `useSessionStorage` - 会话存储
|
|
44
|
+
|
|
45
|
+
### DOM 操作 (`dom.ts`)
|
|
46
|
+
|
|
47
|
+
- `onClickOutside` - 点击外部区域
|
|
48
|
+
- `useEventListener` - 事件监听
|
|
49
|
+
- `useElementSize` - 元素尺寸
|
|
50
|
+
- `useResizeObserver` - 尺寸观察
|
|
51
|
+
- `useFocus` - 焦点管理
|
|
52
|
+
- `useHover` - 悬停状态
|
|
53
|
+
- `useClipboard` - 剪贴板操作
|
|
54
|
+
|
|
55
|
+
### 交互工具 (`interaction.ts`)
|
|
56
|
+
|
|
57
|
+
- `useMouse` - 鼠标位置
|
|
58
|
+
- `useScroll` - 滚动位置
|
|
59
|
+
- `useWindowSize` - 窗口尺寸
|
|
60
|
+
|
|
61
|
+
### 媒体查询 (`media.ts`)
|
|
62
|
+
|
|
63
|
+
- `useMediaQuery` - 媒体查询
|
|
64
|
+
- `usePreferredDark` - 系统暗色偏好
|
|
65
|
+
- `useDark` - 暗色模式
|
|
66
|
+
- `useColorMode` - 颜色模式
|
|
67
|
+
|
|
68
|
+
## 使用方式
|
|
69
|
+
|
|
70
|
+
### 全量导入(推荐)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { classNames, useDebounceFn, useToggle } from '@mtn-ui-z/utils'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 按分类导入
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// 通用工具
|
|
80
|
+
import { classNames } from '@mtn-ui-z/utils/common'
|
|
81
|
+
|
|
82
|
+
// 函数工具
|
|
83
|
+
import { useDebounceFn, useThrottleFn } from '@mtn-ui-z/utils/function'
|
|
84
|
+
|
|
85
|
+
// 状态管理
|
|
86
|
+
import { useToggle, useCounter } from '@mtn-ui-z/utils/state'
|
|
87
|
+
|
|
88
|
+
// DOM 操作
|
|
89
|
+
import { onClickOutside, useElementSize } from '@mtn-ui-z/utils/dom'
|
|
90
|
+
|
|
91
|
+
// 交互工具
|
|
92
|
+
import { useMouse, useScroll } from '@mtn-ui-z/utils/interaction'
|
|
93
|
+
|
|
94
|
+
// 媒体查询
|
|
95
|
+
import { useDark, useColorMode } from '@mtn-ui-z/utils/media'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 主题与密度 (`theme.ts`)
|
|
99
|
+
|
|
100
|
+
与 `@mtn-ui-z/theme` 的 CSS 变量、`data-theme`、`data-mtn-density` 对齐,并同步 Ant Design Vue `ConfigProvider` 的 `theme.token`:
|
|
101
|
+
|
|
102
|
+
- `initTheme` — 尽早调用;可从 `localStorage` 恢复亮/暗与密度
|
|
103
|
+
- `setThemePrimary` / `setThemeColors` / `setThemeMode` / `toggleThemeMode`
|
|
104
|
+
- `setDensityMode` / `toggleDensityMode` — 紧凑与舒适
|
|
105
|
+
- `antThemeConfigRef`、`themeModeRef`、`densityModeRef` — 供模板与 `ConfigProvider` 绑定
|
|
106
|
+
|
|
107
|
+
接入顺序与层叠约定见 `@mtn-ui-z/theme` README 与文档站「自定义主题」。
|
|
108
|
+
|
|
109
|
+
## 使用示例
|
|
110
|
+
|
|
111
|
+
### 防抖和节流
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { useDebounceFn, useThrottleFn } from '@mtn-ui-z/utils'
|
|
115
|
+
|
|
116
|
+
// 防抖
|
|
117
|
+
const debouncedSearch = useDebounceFn((query: string) => {
|
|
118
|
+
console.log('Search:', query)
|
|
119
|
+
}, 300)
|
|
120
|
+
|
|
121
|
+
// 节流
|
|
122
|
+
const throttledScroll = useThrottleFn(() => {
|
|
123
|
+
console.log('Scrolled')
|
|
124
|
+
}, 100)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 状态管理
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { useToggle, useCounter, useLocalStorage } from '@mtn-ui-z/utils'
|
|
131
|
+
|
|
132
|
+
// 切换状态
|
|
133
|
+
const [isOpen, toggle] = useToggle(false)
|
|
134
|
+
|
|
135
|
+
// 计数器
|
|
136
|
+
const { count, inc, dec, reset } = useCounter(0)
|
|
137
|
+
|
|
138
|
+
// 本地存储
|
|
139
|
+
const stored = useLocalStorage('key', 'default-value')
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### DOM 操作
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { onClickOutside, useElementSize } from '@mtn-ui-z/utils'
|
|
146
|
+
import { ref } from 'vue'
|
|
147
|
+
|
|
148
|
+
const target = ref<HTMLElement>()
|
|
149
|
+
|
|
150
|
+
// 点击外部区域
|
|
151
|
+
onClickOutside(target, () => {
|
|
152
|
+
console.log('Clicked outside')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// 元素尺寸
|
|
156
|
+
const { width, height } = useElementSize(target)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## 更多文档
|
|
160
|
+
|
|
161
|
+
查看 [VueUse 官方文档](https://vueuse.org/) 了解更多功能。
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mtn-ui-z/utils",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "MTN UI Utils",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"vue": "^3.5.0",
|
|
13
|
+
"@vueuse/core": "^11.0.0"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@vueuse/core": "^11.0.0",
|
|
17
|
+
"axios": "^1.7.0",
|
|
18
|
+
"dayjs": "^1.11.10"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/common.d.ts
ADDED
package/src/common.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用工具函数
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 格式化类名
|
|
7
|
+
* 工具函数,用于合并多个类名
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* classNames('foo', 'bar', false, 'baz') // => 'foo bar baz'
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function classNames(...classes: (string | undefined | null | false)[]): string {
|
|
15
|
+
return classes.filter(Boolean).join(' ')
|
|
16
|
+
}
|
package/src/date.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日期格式化工具
|
|
3
|
+
*/
|
|
4
|
+
import dayjs from 'dayjs'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 格式化日期(YYYY-MM-DD)
|
|
8
|
+
*/
|
|
9
|
+
export function formatDate(value: unknown): string {
|
|
10
|
+
if (!value) return ''
|
|
11
|
+
const date = dayjs(value as string | number | Date)
|
|
12
|
+
if (!date.isValid()) return String(value)
|
|
13
|
+
return date.format('YYYY-MM-DD')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 格式化日期时间(YYYY-MM-DD HH:mm:ss)
|
|
18
|
+
*/
|
|
19
|
+
export function formatDateTime(value: unknown): string {
|
|
20
|
+
if (!value) return ''
|
|
21
|
+
const date = dayjs(value as string | number | Date)
|
|
22
|
+
if (!date.isValid()) return String(value)
|
|
23
|
+
return date.format('YYYY-MM-DD HH:mm:ss')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 格式化年月(YYYY-MM)
|
|
28
|
+
*/
|
|
29
|
+
export function formatYearMonth(value: unknown): string {
|
|
30
|
+
if (!value) return ''
|
|
31
|
+
const date = dayjs(value as string | number | Date)
|
|
32
|
+
if (!date.isValid()) return String(value)
|
|
33
|
+
return date.format('YYYY-MM')
|
|
34
|
+
}
|
package/src/dict.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据字典:通过接口获取,与 DictSelect 约定一致的字典项结构
|
|
3
|
+
*/
|
|
4
|
+
import { ref, type Ref, inject, type InjectionKey } from 'vue'
|
|
5
|
+
import { request } from './request'
|
|
6
|
+
import type { Result } from './request-types'
|
|
7
|
+
import { getStorage, setStorage } from './storage'
|
|
8
|
+
|
|
9
|
+
/** 字典项结构(与 @mtn-ui-z/components DictSelect 一致) */
|
|
10
|
+
export interface DictItem {
|
|
11
|
+
dictLabel: string
|
|
12
|
+
dictValue: string
|
|
13
|
+
/** 颜色(如状态、标签场景下的展示色),可选 */
|
|
14
|
+
dictColor?: string
|
|
15
|
+
[key: string]: unknown
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 请求字典的接口:传入多个 dictKey,返回各 key 对应的字典项数组(一个接口) */
|
|
19
|
+
export type FetchDictFn = (dictKeys: string[]) => Promise<Record<string, DictItem[]>>
|
|
20
|
+
|
|
21
|
+
const MOCK_MAP: Record<string, DictItem[]> = {
|
|
22
|
+
order_status: [
|
|
23
|
+
{ dictLabel: '待支付', dictValue: '10' },
|
|
24
|
+
{ dictLabel: '已支付', dictValue: '20' },
|
|
25
|
+
{ dictLabel: '已取消', dictValue: '30' },
|
|
26
|
+
],
|
|
27
|
+
pay_type: [
|
|
28
|
+
{ dictLabel: '微信', dictValue: 'wx' },
|
|
29
|
+
{ dictLabel: '支付宝', dictValue: 'alipay' },
|
|
30
|
+
],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFAULT_LIST: DictItem[] = [
|
|
34
|
+
{ dictLabel: '选项一', dictValue: '1' },
|
|
35
|
+
{ dictLabel: '选项二', dictValue: '2' },
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
/** 默认假数据:一次传入多个 key,返回 key -> 字典项数组(暂无接口时使用) */
|
|
39
|
+
function defaultMockFetch(dictKeys: string[]): Promise<Record<string, DictItem[]>> {
|
|
40
|
+
const res: Record<string, DictItem[]> = {}
|
|
41
|
+
dictKeys.forEach(key => {
|
|
42
|
+
res[key] = MOCK_MAP[key] ?? DEFAULT_LIST
|
|
43
|
+
})
|
|
44
|
+
return Promise.resolve(res)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** localStorage 的 key 前缀,与 DictSelect 的 storageKeyPrefix 一致时可直接用 dictType 读缓存 */
|
|
48
|
+
export const DEFAULT_DICT_STORAGE_PREFIX = 'dict_'
|
|
49
|
+
|
|
50
|
+
/** 字典全局配置(可在 App 初始化时 provide,useDict 会 inject 读取) */
|
|
51
|
+
export interface DictGlobalConfig {
|
|
52
|
+
/** 字典接口地址,传入则通过 request 请求该 URL(params: { keys }),响应需为 Result<Record<string, DictItem[]>> */
|
|
53
|
+
dictApiUrl?: string
|
|
54
|
+
/** 请求完成后是否写入 localStorage,默认 true */
|
|
55
|
+
storeInStorage?: boolean
|
|
56
|
+
/** localStorage key 前缀,默认 dict_ */
|
|
57
|
+
storageKeyPrefix?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 注入 key(用于 provide/inject) */
|
|
61
|
+
export const DICT_CONFIG_KEY: InjectionKey<DictGlobalConfig> = Symbol('dictConfig')
|
|
62
|
+
|
|
63
|
+
export interface UseDictOptions {
|
|
64
|
+
/** 字典接口地址,传入则通过 request 请求该 URL(params: { keys }),响应需为 Result<Record<string, DictItem[]>> */
|
|
65
|
+
dictApiUrl?: string
|
|
66
|
+
/** 自定义请求方法,优先级高于 dictApiUrl;不传且无 dictApiUrl 时用内置假数据 */
|
|
67
|
+
fetchDict?: FetchDictFn
|
|
68
|
+
/** 请求完成后是否写入 localStorage,默认 true;按「一个字典一个 key」存储(key = storageKeyPrefix + dictKey) */
|
|
69
|
+
storeInStorage?: boolean
|
|
70
|
+
/** localStorage key 前缀,默认 dict_,单条 key 如 dict_order_status */
|
|
71
|
+
storageKeyPrefix?: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 按字典 key 列表调用一次接口获取多组字典,返回 key -> 字典项数组 的响应式对象。
|
|
76
|
+
* 用法:useDict(['order_status', 'pay_type']),返回的 ref 中 result.value.order_status 即为该 key 的 DictItem[]。
|
|
77
|
+
* 暂无接口时使用内置假数据;有接口后传入 fetchDict(dictKeys) 即可(接口入参为多个 key,出参为 Record<string, DictItem[]>)。
|
|
78
|
+
*/
|
|
79
|
+
function fetchByRequest(dictApiUrl: string, keys: string[]): Promise<Record<string, DictItem[]>> {
|
|
80
|
+
return request
|
|
81
|
+
.post<Result<Record<string, DictItem[]>>>(dictApiUrl, { dict_code: keys })
|
|
82
|
+
.then(res => normalizeDictMap(res.data))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeDictItem(item: DictItem): DictItem {
|
|
86
|
+
return {
|
|
87
|
+
...item,
|
|
88
|
+
dictLabel: String(item.dictLabel ?? item.dict_label ?? ''),
|
|
89
|
+
dictValue: String(item.dictValue ?? item.dict_value ?? ''),
|
|
90
|
+
dictColor:
|
|
91
|
+
typeof item.dictColor === 'string'
|
|
92
|
+
? item.dictColor
|
|
93
|
+
: typeof item.dict_color === 'string'
|
|
94
|
+
? item.dict_color
|
|
95
|
+
: undefined
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizeDictMap(data: Record<string, DictItem[]>): Record<string, DictItem[]> {
|
|
100
|
+
const normalized: Record<string, DictItem[]> = {}
|
|
101
|
+
Object.entries(data ?? {}).forEach(([dictKey, list]) => {
|
|
102
|
+
normalized[dictKey] = Array.isArray(list) ? list.map(item => normalizeDictItem(item)) : []
|
|
103
|
+
})
|
|
104
|
+
return normalized
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 正在请求中的 promise(避免同一 key 并发重复请求) */
|
|
108
|
+
const pendingRequests = new Map<string, Promise<Record<string, DictItem[]>>>()
|
|
109
|
+
|
|
110
|
+
export function useDict(
|
|
111
|
+
keys: string[],
|
|
112
|
+
options: UseDictOptions = {}
|
|
113
|
+
): Ref<Record<string, DictItem[]>> {
|
|
114
|
+
const result = ref<Record<string, DictItem[]>>({}) as Ref<Record<string, DictItem[]>>
|
|
115
|
+
|
|
116
|
+
// 读取全局配置(App 级别 provide 的),局部 options 优先级更高
|
|
117
|
+
const globalConfig = inject(DICT_CONFIG_KEY, {})
|
|
118
|
+
const dictApiUrl = options.dictApiUrl ?? globalConfig.dictApiUrl
|
|
119
|
+
const storeInStorage = options.storeInStorage ?? globalConfig.storeInStorage ?? true
|
|
120
|
+
const prefix =
|
|
121
|
+
options.storageKeyPrefix ?? globalConfig.storageKeyPrefix ?? DEFAULT_DICT_STORAGE_PREFIX
|
|
122
|
+
|
|
123
|
+
// 根据配置决定请求方式:局部 fetchDict > 全局 dictApiUrl > 内置 mock
|
|
124
|
+
const fetchDict =
|
|
125
|
+
options.fetchDict ??
|
|
126
|
+
(dictApiUrl ? (dictKeys: string[]) => fetchByRequest(dictApiUrl, dictKeys) : defaultMockFetch)
|
|
127
|
+
|
|
128
|
+
// 优先从 localStorage 读取已有缓存,减少不必要的请求
|
|
129
|
+
const cachedResult: Record<string, DictItem[]> = {}
|
|
130
|
+
const missingKeys: string[] = []
|
|
131
|
+
|
|
132
|
+
for (const key of keys) {
|
|
133
|
+
const cached = getStorage<DictItem[]>(prefix + key)
|
|
134
|
+
if (cached !== undefined && cached.length > 0) {
|
|
135
|
+
cachedResult[key] = cached.map(item => normalizeDictItem(item))
|
|
136
|
+
} else {
|
|
137
|
+
missingKeys.push(key)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 已有缓存的处理
|
|
142
|
+
if (Object.keys(cachedResult).length > 0) {
|
|
143
|
+
result.value = cachedResult
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 全部命中缓存,无需请求
|
|
147
|
+
if (missingKeys.length === 0) {
|
|
148
|
+
return result
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 构造新请求,并缓存 promise 引用(按 sorted keys 做去重)
|
|
152
|
+
const pendingKey = missingKeys.sort().join(',')
|
|
153
|
+
const existingPending = pendingRequests.get(pendingKey)
|
|
154
|
+
|
|
155
|
+
if (existingPending) {
|
|
156
|
+
existingPending
|
|
157
|
+
.then(data => {
|
|
158
|
+
const normalizedData = normalizeDictMap(data)
|
|
159
|
+
if (storeInStorage) {
|
|
160
|
+
Object.entries(normalizedData).forEach(([dictKey, list]) => {
|
|
161
|
+
setStorage(prefix + dictKey, list)
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
result.value = { ...result.value, ...normalizedData }
|
|
165
|
+
})
|
|
166
|
+
.catch(() => {
|
|
167
|
+
result.value = { ...result.value, ...Object.fromEntries(missingKeys.map(k => [k, []])) }
|
|
168
|
+
})
|
|
169
|
+
return result
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const reqPromise = fetchDict(missingKeys)
|
|
173
|
+
pendingRequests.set(pendingKey, reqPromise)
|
|
174
|
+
|
|
175
|
+
reqPromise
|
|
176
|
+
.then(data => {
|
|
177
|
+
const normalizedData = normalizeDictMap(data)
|
|
178
|
+
result.value = { ...result.value, ...normalizedData }
|
|
179
|
+
if (storeInStorage) {
|
|
180
|
+
Object.entries(normalizedData).forEach(([dictKey, list]) => {
|
|
181
|
+
setStorage(prefix + dictKey, list)
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
.catch(() => {
|
|
186
|
+
result.value = { ...result.value, ...Object.fromEntries(missingKeys.map(k => [k, []])) }
|
|
187
|
+
})
|
|
188
|
+
.finally(() => {
|
|
189
|
+
pendingRequests.delete(pendingKey)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
return result
|
|
193
|
+
}
|
package/src/dom.d.ts
ADDED
package/src/dom.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: liaokt
|
|
3
|
+
* @Description:
|
|
4
|
+
* @Date: 2026-01-21 10:19:31
|
|
5
|
+
* @LastEditors: liaokt
|
|
6
|
+
* @LastEditTime: 2026-01-26 16:09:27
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* DOM 操作工具
|
|
10
|
+
* 与 DOM 元素交互相关的工具函数
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
onClickOutside,
|
|
15
|
+
useEventListener,
|
|
16
|
+
useElementSize,
|
|
17
|
+
useResizeObserver,
|
|
18
|
+
useFocus,
|
|
19
|
+
useClipboard
|
|
20
|
+
} from '@vueuse/core'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useDebounceFn, useThrottleFn } from '@vueuse/core'
|
package/src/function.ts
ADDED
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mtn-ui-z/utils 类型声明入口
|
|
3
|
+
* 供 package.json "types" 指向,避免部分环境下 TS 无法解析 node_modules 内 .ts
|
|
4
|
+
*/
|
|
5
|
+
export * from './common'
|
|
6
|
+
export * from './function'
|
|
7
|
+
export * from './state'
|
|
8
|
+
export * from './storage'
|
|
9
|
+
export * from './dom'
|
|
10
|
+
export * from './interaction'
|
|
11
|
+
export * from './media'
|
|
12
|
+
export * from './permission'
|
|
13
|
+
export * from './theme'
|
|
14
|
+
export * from './dict'
|
|
15
|
+
export * from './request-types'
|
|
16
|
+
export {
|
|
17
|
+
createRequest,
|
|
18
|
+
request,
|
|
19
|
+
axiosInstance,
|
|
20
|
+
type CreateRequestOptions
|
|
21
|
+
} from './request'
|
|
22
|
+
|
|
23
|
+
// 表格状态管理
|
|
24
|
+
export * from './useTable'
|
|
25
|
+
|
|
26
|
+
// Modal 命令式调用
|
|
27
|
+
export * from './useModal'
|
|
28
|
+
|
|
29
|
+
// Modal 注册表
|
|
30
|
+
export * from './modalRegistry'
|
|
31
|
+
|
|
32
|
+
// 日期格式化
|
|
33
|
+
export * from './date'
|
|
34
|
+
|
|
35
|
+
// 类型定义
|
|
36
|
+
export * from './types'
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MTN UI Utils
|
|
3
|
+
*
|
|
4
|
+
* 基于 VueUse 的工具函数集合
|
|
5
|
+
*
|
|
6
|
+
* @module @mtn-ui-z/utils
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// 通用工具
|
|
10
|
+
export * from './common'
|
|
11
|
+
|
|
12
|
+
// 函数工具
|
|
13
|
+
export * from './function'
|
|
14
|
+
|
|
15
|
+
// 状态管理
|
|
16
|
+
export * from './state'
|
|
17
|
+
|
|
18
|
+
// 本地/会话存储
|
|
19
|
+
export * from './storage'
|
|
20
|
+
|
|
21
|
+
// DOM 操作
|
|
22
|
+
export * from './dom'
|
|
23
|
+
|
|
24
|
+
// 交互工具
|
|
25
|
+
export * from './interaction'
|
|
26
|
+
|
|
27
|
+
// 媒体查询
|
|
28
|
+
export * from './media'
|
|
29
|
+
|
|
30
|
+
// 权限管理
|
|
31
|
+
export * from './permission'
|
|
32
|
+
|
|
33
|
+
// 主题色(运行时)
|
|
34
|
+
export * from './theme'
|
|
35
|
+
|
|
36
|
+
// 数据字典
|
|
37
|
+
export * from './dict'
|
|
38
|
+
|
|
39
|
+
// 请求与 API 类型
|
|
40
|
+
export * from './request-types'
|
|
41
|
+
export {
|
|
42
|
+
createRequest,
|
|
43
|
+
request,
|
|
44
|
+
axiosInstance,
|
|
45
|
+
type CreateRequestOptions
|
|
46
|
+
} from './request'
|
|
47
|
+
|
|
48
|
+
// 表格状态管理
|
|
49
|
+
export * from './useTable'
|
|
50
|
+
|
|
51
|
+
// Modal 命令式调用
|
|
52
|
+
export * from './useModal'
|
|
53
|
+
|
|
54
|
+
// Modal 注册表
|
|
55
|
+
export * from './modalRegistry'
|
|
56
|
+
|
|
57
|
+
// 日期格式化
|
|
58
|
+
export * from './date'
|
|
59
|
+
|
|
60
|
+
// 类型定义
|
|
61
|
+
export * from './types'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useMouse, useScroll, useWindowSize } from '@vueuse/core'
|
package/src/media.d.ts
ADDED
package/src/media.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modal 注册表
|
|
3
|
+
* 存储已注册的 Modal 组件定义,供 MxModalProvider 渲染使用
|
|
4
|
+
*/
|
|
5
|
+
import type { Component } from 'vue'
|
|
6
|
+
|
|
7
|
+
/** Modal 组件注册项 */
|
|
8
|
+
interface ModalRegistryItem {
|
|
9
|
+
component: Component
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const registry = new Map<string, ModalRegistryItem>()
|
|
13
|
+
|
|
14
|
+
export const ModalRegistry = {
|
|
15
|
+
/**
|
|
16
|
+
* 注册一个 Modal 组件
|
|
17
|
+
* @param id 唯一标识
|
|
18
|
+
* @param component Vue 组件
|
|
19
|
+
*/
|
|
20
|
+
register(id: string, component: Component) {
|
|
21
|
+
registry.set(id, { component })
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 注销一个 Modal 组件
|
|
26
|
+
*/
|
|
27
|
+
unregister(id: string) {
|
|
28
|
+
registry.delete(id)
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 根据 id 获取已注册的组件
|
|
33
|
+
*/
|
|
34
|
+
get(id: string): Component | undefined {
|
|
35
|
+
return registry.get(id)?.component
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { InjectionKey, Ref, MaybeRefOrGetter, ComputedRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
export type PermissionChecker = (permission: string | string[]) => boolean
|
|
4
|
+
export type PermissionAction = 'hide' | 'disable'
|
|
5
|
+
|
|
6
|
+
export const defaultPermissionChecker: PermissionChecker
|
|
7
|
+
export const PERMISSION_CHECKER_KEY: InjectionKey<Ref<PermissionChecker>>
|
|
8
|
+
export const USER_PERMISSIONS_KEY: InjectionKey<Ref<string[]>>
|
|
9
|
+
|
|
10
|
+
export function createPermissionChecker(permissions: Ref<string[]>): PermissionChecker
|
|
11
|
+
export function hasPermission(
|
|
12
|
+
userPermissions: string[],
|
|
13
|
+
requiredPermission: string | string[]
|
|
14
|
+
): boolean
|
|
15
|
+
|
|
16
|
+
export interface UsePermissionOptions {
|
|
17
|
+
permission?: MaybeRefOrGetter<string | string[] | undefined>
|
|
18
|
+
permissionAction?: MaybeRefOrGetter<PermissionAction | undefined>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UsePermissionReturn {
|
|
22
|
+
hasPermission: ComputedRef<boolean>
|
|
23
|
+
shouldHide: ComputedRef<boolean>
|
|
24
|
+
shouldDisable: ComputedRef<boolean>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function usePermission(options: UsePermissionOptions): UsePermissionReturn
|