@skyfox2000/webui 1.0.13 → 1.2.0
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/lib/assets/modules/file-upload-CBUcsUnR.js +170 -0
- package/lib/assets/modules/form-validate-CgX7aR7T.js +297 -0
- package/lib/assets/modules/index-Civhd8xG.js +112 -0
- package/lib/assets/modules/index-DQMdt51R.js +726 -0
- package/lib/assets/modules/{index-BEWJ_qAH.js → index-DmWrkTXX.js} +1 -1
- package/lib/assets/modules/{menuTabs-BXdbFZor.js → menuTabs-BRYvFWA-.js} +131 -121
- package/lib/assets/modules/settingInfo-BZakNKIN.js +999 -0
- package/lib/assets/modules/uploadList-B7XoxGOh.js +278 -0
- package/lib/components/common/icon/index.vue.d.ts +1 -1
- package/lib/components/content/dialog/index.vue.d.ts +1 -1
- package/lib/components/content/drawer/index.vue.d.ts +1 -1
- package/lib/components/content/form/index.vue.d.ts +1 -1
- package/lib/components/content/search/index.vue.d.ts +1 -1
- package/lib/components/content/table/index.vue.d.ts +1 -1
- package/lib/components/content/table/tableOperate.vue.d.ts +1 -1
- package/lib/components/content/toolbar/icontool.vue.d.ts +1 -1
- package/lib/components/content/toolbar/index.vue.d.ts +1 -1
- package/lib/components/content/tree/index.vue.d.ts +1 -1
- package/lib/components/form/transfer/transferTable.vue.d.ts +1 -1
- package/lib/components/form/treeSelect/index.vue.d.ts +1 -1
- package/lib/components/form/upload/uploadList.vue.d.ts +1 -1
- package/lib/const/options.d.ts +32 -0
- package/lib/directives/enter-submit.d.ts +4 -0
- package/lib/directives/index.d.ts +2 -0
- package/lib/directives/permission.d.ts +5 -0
- package/lib/es/AceEditor/index.js +9 -8
- package/lib/es/BasicLayout/index.js +28 -24
- package/lib/es/Error403/index.js +15 -10
- package/lib/es/Error404/index.js +15 -10
- package/lib/es/ExcelForm/index.js +380 -175
- package/lib/es/UploadForm/index.js +23 -20
- package/lib/index.d.ts +42 -2
- package/lib/router/index.d.ts +16 -0
- package/lib/stores/appInfo.d.ts +34 -0
- package/lib/stores/hostInfo.d.ts +9 -0
- package/lib/stores/pageInfo.d.ts +18 -0
- package/lib/stores/pinia.d.ts +3 -0
- package/lib/stores/settingInfo.d.ts +8 -0
- package/lib/stores/userInfo.d.ts +21 -0
- package/lib/typings/data.d.ts +80 -0
- package/lib/typings/form.d.ts +171 -0
- package/lib/typings/menu.d.ts +7 -0
- package/lib/typings/option.d.ts +175 -0
- package/lib/typings/page.d.ts +69 -0
- package/lib/typings/table.d.ts +181 -0
- package/lib/typings/tools.d.ts +130 -0
- package/lib/typings/tree.d.ts +72 -0
- package/lib/typings/upload.d.ts +161 -0
- package/lib/typings/urls.d.ts +69 -0
- package/lib/utils/cache.d.ts +23 -0
- package/lib/utils/data.d.ts +6 -0
- package/lib/utils/download.d.ts +4 -0
- package/lib/utils/eventbus.d.ts +16 -0
- package/lib/utils/export-table.d.ts +12 -0
- package/lib/utils/file-upload.d.ts +15 -0
- package/lib/utils/form-excel.d.ts +30 -0
- package/lib/utils/form-validate.d.ts +29 -0
- package/lib/utils/form.d.ts +9 -0
- package/lib/utils/icon-loader.d.ts +125 -0
- package/lib/utils/isEmpty.d.ts +1 -0
- package/lib/utils/main-openapis.d.ts +9 -0
- package/lib/utils/menu.d.ts +6 -0
- package/lib/utils/options.d.ts +10 -0
- package/lib/utils/page.d.ts +25 -0
- package/lib/utils/table.d.ts +21 -0
- package/lib/utils/tools.d.ts +18 -0
- package/lib/utils/tree.d.ts +3 -0
- package/lib/vite-env.d.ts +8 -0
- package/lib/webui.css +1 -1
- package/lib/webui.es.js +1020 -854
- package/package.json +7 -6
- package/src/components/common/icon/appicon.vue +1 -1
- package/src/components/common/icon/fullscreen.vue +2 -1
- package/src/components/common/icon/index.vue +1 -1
- package/src/components/common/icon/layoutIcon.vue +1 -1
- package/src/components/common/icon/projectIcon.vue +1 -1
- package/src/components/common/icon/toolIcon.vue +1 -1
- package/src/components/content/dialog/excelForm.vue +2 -2
- package/src/components/content/dialog/index.vue +1 -1
- package/src/components/content/dialog/uploadForm.vue +7 -6
- package/src/components/content/drawer/index.vue +43 -18
- package/src/components/content/form/formItem.vue +1 -1
- package/src/components/content/form/index.vue +1 -1
- package/src/components/content/search/index.vue +1 -1
- package/src/components/content/search/searchItem.vue +1 -1
- package/src/components/content/table/index.vue +8 -5
- package/src/components/content/table/tableOperate.vue +8 -4
- package/src/components/content/toolbar/icontool.vue +2 -2
- package/src/components/content/toolbar/index.vue +9 -5
- package/src/components/content/tree/index.vue +1 -1
- package/src/components/error/error403.vue +2 -2
- package/src/components/error/error404.vue +2 -2
- package/src/components/form/autoComplete/index.vue +1 -1
- package/src/components/form/cascader/index.vue +1 -2
- package/src/components/form/checkbox/index.vue +11 -5
- package/src/components/form/datePicker/index.vue +1 -1
- package/src/components/form/input/index.vue +1 -1
- package/src/components/form/input/inputNumber.vue +1 -1
- package/src/components/form/input/inputPassword.vue +1 -1
- package/src/components/form/radio/index.vue +1 -1
- package/src/components/form/radio/radioStatus.vue +1 -1
- package/src/components/form/rangePicker/index.vue +1 -1
- package/src/components/form/select/index.vue +1 -1
- package/src/components/form/switch/index.vue +7 -3
- package/src/components/form/textarea/index.vue +1 -1
- package/src/components/form/transfer/index.vue +1 -1
- package/src/components/form/transfer/transferTable.vue +42 -22
- package/src/components/form/treeSelect/index.vue +2 -3
- package/src/components/form/upload/uploadList.vue +1 -1
- package/src/components/layout/breadcrumb/index.vue +1 -1
- package/src/components/layout/header/headerExits.vue +1 -1
- package/src/components/layout/header/index.vue +1 -1
- package/src/components/layout/header/user.vue +2 -1
- package/src/components/layout/menu/index.vue +9 -3
- package/src/components/layout/menu/menuTabs.vue +10 -12
- package/src/components/layout/page/basicLayout.vue +1 -1
- package/src/const/options.ts +114 -0
- package/src/directives/enter-submit.ts +13 -0
- package/src/directives/index.ts +26 -0
- package/src/directives/permission.ts +144 -0
- package/src/index.ts +201 -0
- package/src/router/index.ts +196 -0
- package/src/stores/appInfo.ts +471 -0
- package/src/stores/hostInfo.ts +117 -0
- package/src/stores/pageInfo.ts +131 -0
- package/src/stores/pinia.ts +10 -0
- package/src/stores/settingInfo.ts +53 -0
- package/src/stores/userInfo.ts +392 -0
- package/src/typings/data.d.ts +81 -0
- package/src/typings/form.d.ts +172 -0
- package/src/typings/menu.d.ts +7 -0
- package/src/typings/option.d.ts +177 -0
- package/src/typings/page.d.ts +70 -0
- package/src/typings/table.d.ts +182 -0
- package/src/typings/tools.d.ts +131 -0
- package/src/typings/tree.d.ts +73 -0
- package/src/typings/upload.d.ts +162 -0
- package/src/typings/urls.d.ts +70 -0
- package/src/utils/cache.ts +175 -0
- package/src/utils/data.ts +189 -0
- package/src/utils/download.ts +80 -0
- package/src/utils/eventbus.ts +78 -0
- package/src/utils/export-table.ts +155 -0
- package/src/utils/file-upload.ts +304 -0
- package/src/utils/form-excel.ts +523 -0
- package/src/utils/form-validate.ts +368 -0
- package/src/utils/form.ts +188 -0
- package/src/utils/icon-loader.ts +412 -0
- package/src/utils/isEmpty.ts +18 -0
- package/src/utils/main-openapis.ts +72 -0
- package/src/utils/menu.ts +89 -0
- package/src/utils/options.ts +324 -0
- package/src/utils/page.ts +262 -0
- package/src/utils/table.ts +274 -0
- package/src/utils/tools.ts +362 -0
- package/src/utils/tree.ts +28 -0
- package/tsconfig.json +1 -8
- package/vite.config.ts +7 -4
- package/lib/assets/modules/index-BahGnrAq.js +0 -415
- package/lib/assets/modules/index-BoKIa2sr.js +0 -109
- package/lib/assets/modules/index-D47Ci-T3.js +0 -107
- package/lib/assets/modules/uploadList-Dzlg47V0.js +0 -182
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { defineComponent, h, Ref, ref, toRaw, watch } from 'vue';
|
|
2
|
+
import message from 'vue-m-message';
|
|
3
|
+
|
|
4
|
+
type IconCacheData = {
|
|
5
|
+
/// 存储到window里面的key
|
|
6
|
+
windowKey: string;
|
|
7
|
+
/// 是否单色
|
|
8
|
+
monoColor: boolean;
|
|
9
|
+
/// 实际的Icon脚本字符串
|
|
10
|
+
scriptContent: string;
|
|
11
|
+
/// 超时时间
|
|
12
|
+
expireTime: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type IconFontOptions = {
|
|
16
|
+
/**
|
|
17
|
+
* 加载的图标集地址
|
|
18
|
+
*/
|
|
19
|
+
iconUrl: string;
|
|
20
|
+
/**
|
|
21
|
+
* 是否单色图标集,会替换清空所有fill参数
|
|
22
|
+
*/
|
|
23
|
+
monoColor?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* 图标名称
|
|
26
|
+
*/
|
|
27
|
+
icon?: string;
|
|
28
|
+
/**
|
|
29
|
+
* 图标集
|
|
30
|
+
*/
|
|
31
|
+
icons?: string[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const ICON_CACHE_TIME = 7 * 24 * 60 * 60 * 1000; /// 7天
|
|
35
|
+
const loadingPromises: Map<string, Promise<void>> = new Map(); // 用于存储加载中的 Promise
|
|
36
|
+
const loadedIcons: Set<string> = new Set(); // 用于缓存已加载的图标
|
|
37
|
+
const ToolIcons: Ref<Record<string, any>> = ref({}); // 图标库
|
|
38
|
+
|
|
39
|
+
export const ICONS_LIB = ToolIcons;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param options 参数配置
|
|
44
|
+
* - iconUrl 加载的图标集地址
|
|
45
|
+
* - monoColor: 是否单色图标
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
export const createFromIconfont = (options: IconFontOptions) => {
|
|
49
|
+
if (!options.iconUrl) return circleLoading;
|
|
50
|
+
const isLoaded = ref(true); // 添加状态管理,表示图标库是否加载完成
|
|
51
|
+
const Icon = ref('');
|
|
52
|
+
let existIcon = true;
|
|
53
|
+
if (options.icons?.length) {
|
|
54
|
+
// 检查图标库是否存在
|
|
55
|
+
existIcon = options.icons.every((icon: string) => ToolIcons.value[icon] !== undefined);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 如果图标不存在,则加载图标库
|
|
59
|
+
if ((options.icon && !ToolIcons.value[options.icon]) || !existIcon) {
|
|
60
|
+
loadIconfont(options, isLoaded).then(() => {
|
|
61
|
+
parseCacheIcons(options.iconUrl, options.monoColor || false);
|
|
62
|
+
isLoaded.value = true; // 设置加载完成状态
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return defineComponent({
|
|
67
|
+
props: {
|
|
68
|
+
icon: {
|
|
69
|
+
type: String,
|
|
70
|
+
},
|
|
71
|
+
icons: {
|
|
72
|
+
type: Array<string>,
|
|
73
|
+
},
|
|
74
|
+
iconIndex: {
|
|
75
|
+
type: Number,
|
|
76
|
+
},
|
|
77
|
+
clickable: {
|
|
78
|
+
type: Boolean,
|
|
79
|
+
},
|
|
80
|
+
angle: {
|
|
81
|
+
type: Number,
|
|
82
|
+
},
|
|
83
|
+
flip: {
|
|
84
|
+
type: Boolean,
|
|
85
|
+
},
|
|
86
|
+
class: {
|
|
87
|
+
type: [String, Array<string>],
|
|
88
|
+
default: '',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
emits: ['update:iconIndex'],
|
|
92
|
+
setup(props, { emit }) {
|
|
93
|
+
const curIcon = ref<string | undefined>(props.icon);
|
|
94
|
+
const curIndex = ref(0);
|
|
95
|
+
const handleClick = () => {
|
|
96
|
+
if (props.icons) curIndex.value++; // 自增 curIndex
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
watch(
|
|
100
|
+
() => props.iconIndex,
|
|
101
|
+
(newIndex) => {
|
|
102
|
+
if (newIndex !== undefined) {
|
|
103
|
+
curIndex.value = newIndex;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{ immediate: true },
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
watch(
|
|
110
|
+
() => curIndex.value,
|
|
111
|
+
(newIndex) => {
|
|
112
|
+
if (props.icons) {
|
|
113
|
+
if (newIndex >= props.icons.length) {
|
|
114
|
+
curIndex.value = 0; // 超过长度则重置为 0
|
|
115
|
+
}
|
|
116
|
+
curIcon.value = props.icons[curIndex.value];
|
|
117
|
+
updateIcon(Icon, curIcon, curIcon.value);
|
|
118
|
+
emit('update:iconIndex', curIndex.value);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{ immediate: true },
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
watch(
|
|
125
|
+
() => ToolIcons.value,
|
|
126
|
+
() => {
|
|
127
|
+
updateIcon(Icon, curIcon, curIcon.value);
|
|
128
|
+
},
|
|
129
|
+
{ deep: true },
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
watch(
|
|
133
|
+
() => props.icon,
|
|
134
|
+
(newIcon) => {
|
|
135
|
+
updateIcon(Icon, curIcon, newIcon);
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
updateIcon(Icon, curIcon, curIcon.value);
|
|
139
|
+
|
|
140
|
+
return { isLoaded, Icon, handleClick }; // 返回状态以供模板使用
|
|
141
|
+
},
|
|
142
|
+
render() {
|
|
143
|
+
if (!isLoaded.value) {
|
|
144
|
+
return circleLoading;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let existingClass = this.class;
|
|
148
|
+
let classString = '';
|
|
149
|
+
|
|
150
|
+
if (Array.isArray(existingClass)) {
|
|
151
|
+
classString = existingClass.join(' ');
|
|
152
|
+
} else if (typeof existingClass === 'string') {
|
|
153
|
+
classString = existingClass;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let finalClasses = [classString];
|
|
157
|
+
|
|
158
|
+
if (!/\bw-/.test(classString) && !/$w-/.test(classString)) {
|
|
159
|
+
finalClasses.push('w-4');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!/\bh-/.test(classString) && !/$h-/.test(classString)) {
|
|
163
|
+
finalClasses.push('h-4');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 根据传入的 icon 渲染对应的组件
|
|
167
|
+
return Icon.value
|
|
168
|
+
? h(toRaw(Icon.value), {
|
|
169
|
+
onClick: this.$props.clickable ? this.handleClick : undefined,
|
|
170
|
+
class: `${finalClasses.join(' ')}`,
|
|
171
|
+
style: {
|
|
172
|
+
fill: 'currentColor',
|
|
173
|
+
cursor: this.$props.clickable ? 'pointer' : 'default',
|
|
174
|
+
transform: getIconTransform(this.angle, this.flip),
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
: h('div', { class: 'w-[14px] h-[14px]' }); // 占位符
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 远程加载图标集
|
|
184
|
+
* @param url 远程图标库地址
|
|
185
|
+
*/
|
|
186
|
+
const loadIconfont = (options: IconFontOptions, isLoaded: Ref<Boolean>): Promise<void> => {
|
|
187
|
+
let url = options.iconUrl;
|
|
188
|
+
if (!url) {
|
|
189
|
+
console.error('图标库地址不能为空');
|
|
190
|
+
return Promise.resolve();
|
|
191
|
+
}
|
|
192
|
+
if (loadingPromises.has(url)) {
|
|
193
|
+
return Promise.resolve(); // 如果正在加载中,返回该 URL 对应的 Promise
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const cacheData = getIconCache(url);
|
|
197
|
+
|
|
198
|
+
// 如果有未过期且内容相同的缓存
|
|
199
|
+
if (cacheData) {
|
|
200
|
+
return new Promise<void>(async (resolve) => {
|
|
201
|
+
loadScript(cacheData.scriptContent);
|
|
202
|
+
return resolve();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// 需要远程加载
|
|
207
|
+
isLoaded.value = false;
|
|
208
|
+
|
|
209
|
+
// 创建一个新的 Promise 并存储在 Map 中
|
|
210
|
+
const loadingPromise = new Promise<void>(async (resolve) => {
|
|
211
|
+
await loadAndCacheScript(url, options.monoColor || false);
|
|
212
|
+
resolve();
|
|
213
|
+
loadingPromises.delete(url);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
loadingPromises.set(url, loadingPromise);
|
|
217
|
+
|
|
218
|
+
return loadingPromise;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const loadAndCacheScript = async (url: string, monoColor: boolean) => {
|
|
222
|
+
try {
|
|
223
|
+
const response = await fetch(url);
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
message.error('网络异常,无法加载图标库!');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
let scriptContent = await response.text();
|
|
229
|
+
|
|
230
|
+
/// 是否单色
|
|
231
|
+
scriptContent = monoColor ? scriptContent.replace(/fill=\"[^\"]*\"/gi, '') : scriptContent;
|
|
232
|
+
|
|
233
|
+
let reg = /^window\.(\w+)\s*=/;
|
|
234
|
+
const matches = scriptContent.match(reg);
|
|
235
|
+
if (matches && matches.length > 1) {
|
|
236
|
+
let windowKey = matches[1];
|
|
237
|
+
storeIconCache(url, windowKey, monoColor, scriptContent);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
loadScript(scriptContent);
|
|
241
|
+
return;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error('网络异常,无法加载图标库:', error);
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const loadScript = (scriptContent: string) => {
|
|
249
|
+
const script = document.createElement('script');
|
|
250
|
+
script.textContent = scriptContent;
|
|
251
|
+
document.head.appendChild(script);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 存储图标缓存到 localStorage
|
|
256
|
+
* @param key 缓存键
|
|
257
|
+
* @param svgString SVG 字符串
|
|
258
|
+
*/
|
|
259
|
+
const storeIconCache = (key: string, windowKey: string, monoColor: boolean, scriptContent: string): void => {
|
|
260
|
+
let cacheKey = `icon_cache:${key}`;
|
|
261
|
+
const cache = {
|
|
262
|
+
windowKey,
|
|
263
|
+
monoColor,
|
|
264
|
+
scriptContent,
|
|
265
|
+
expireTime: Date.now() + ICON_CACHE_TIME, // 缓存时间:7天
|
|
266
|
+
};
|
|
267
|
+
localStorage.setItem(cacheKey, JSON.stringify(cache));
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 从 localStorage 读取图标缓存
|
|
272
|
+
* @param key 缓存键
|
|
273
|
+
* @returns 缓存内容
|
|
274
|
+
*/
|
|
275
|
+
const getIconCache = (key: string): IconCacheData | null => {
|
|
276
|
+
let cacheKey = `icon_cache:${key}`;
|
|
277
|
+
const cache = localStorage.getItem(cacheKey);
|
|
278
|
+
if (!cache) return null;
|
|
279
|
+
|
|
280
|
+
let parsedCache: IconCacheData;
|
|
281
|
+
try {
|
|
282
|
+
parsedCache = JSON.parse(cache);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error('解析缓存失败:', error);
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 检查缓存是否过期
|
|
289
|
+
if (parsedCache.expireTime < Date.now()) {
|
|
290
|
+
localStorage.removeItem(key);
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return parsedCache;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 检查图标库是否过期
|
|
299
|
+
*/
|
|
300
|
+
export const checkIconsExpired = () => {
|
|
301
|
+
const currentTime = Date.now();
|
|
302
|
+
for (const key in localStorage) {
|
|
303
|
+
if (key.startsWith('icon_cache:')) {
|
|
304
|
+
const cache = localStorage.getItem(key);
|
|
305
|
+
if (cache) {
|
|
306
|
+
const parsedCache: IconCacheData = JSON.parse(cache);
|
|
307
|
+
if (parsedCache.expireTime < currentTime) {
|
|
308
|
+
localStorage.removeItem(key);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const updateIcon = (Icon: Ref<string>, curIcon: Ref<string | undefined>, newIcon?: string) => {
|
|
316
|
+
setTimeout(() => {
|
|
317
|
+
curIcon.value = newIcon;
|
|
318
|
+
if (curIcon.value) {
|
|
319
|
+
Icon.value = ToolIcons.value[curIcon.value];
|
|
320
|
+
}
|
|
321
|
+
}, 1);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export const circleLoading = h(
|
|
325
|
+
'svg',
|
|
326
|
+
{ class: 'w-4 h-4', viewBox: '0 0 50 50', style: { animation: 'rotate 2s linear infinite' } },
|
|
327
|
+
[
|
|
328
|
+
h('circle', {
|
|
329
|
+
cx: '25',
|
|
330
|
+
cy: '25',
|
|
331
|
+
r: '20',
|
|
332
|
+
stroke: 'currentColor',
|
|
333
|
+
fill: 'none',
|
|
334
|
+
style: { strokeWidth: '3 !important' }, // 强制应用线宽
|
|
335
|
+
}),
|
|
336
|
+
h('circle', {
|
|
337
|
+
cx: '25',
|
|
338
|
+
cy: '6',
|
|
339
|
+
r: '6',
|
|
340
|
+
fill: 'currentColor',
|
|
341
|
+
}),
|
|
342
|
+
],
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* 匹配缓存中解析已加载的图标库
|
|
347
|
+
* @param url 远程图标库地址
|
|
348
|
+
* @param monoColor 是否单色
|
|
349
|
+
*/
|
|
350
|
+
const parseCacheIcons = (url: string, monoColor: boolean) => {
|
|
351
|
+
const cacheData = getIconCache(url);
|
|
352
|
+
Object.keys(window).forEach((key) => {
|
|
353
|
+
if (key.startsWith('_iconfont_svg_string_') && cacheData?.windowKey === key && !loadedIcons.has(key)) {
|
|
354
|
+
loadedIcons.add(key);
|
|
355
|
+
parseIcons(key, monoColor);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 从window中解析已加载的图标库
|
|
362
|
+
* @param key window中的key
|
|
363
|
+
* @param monoColor 是否单色
|
|
364
|
+
*/
|
|
365
|
+
export const parseIcons = (key: string, monoColor: boolean) => {
|
|
366
|
+
const svgString = (window as Record<string, any>)[key];
|
|
367
|
+
|
|
368
|
+
const parser = new DOMParser();
|
|
369
|
+
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml');
|
|
370
|
+
const newSymbols = svgDoc.getElementsByTagName('symbol');
|
|
371
|
+
|
|
372
|
+
// 获取全局的 SVG symbol 定义
|
|
373
|
+
const globalSymbols = document.querySelectorAll('symbol');
|
|
374
|
+
|
|
375
|
+
for (const newSymbol of newSymbols) {
|
|
376
|
+
const id = newSymbol.id;
|
|
377
|
+
const globalSymbol = Array.from(globalSymbols).find((s) => s.id === id);
|
|
378
|
+
if (globalSymbol && monoColor) {
|
|
379
|
+
globalSymbol.innerHTML = newSymbol.innerHTML;
|
|
380
|
+
// 如果全局中存在相同id的symbol,则删除其子元素的fill属性
|
|
381
|
+
globalSymbol.querySelectorAll('*').forEach((element) => {
|
|
382
|
+
if (element.hasAttribute('fill')) {
|
|
383
|
+
element.removeAttribute('fill');
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!ToolIcons.value[id]) {
|
|
389
|
+
ToolIcons.value[id] = h('svg', { fill: 'currentColor' }, [h('use', { 'xlink:href': `#${id}` })]);
|
|
390
|
+
} else {
|
|
391
|
+
/// 优先使用平台图标
|
|
392
|
+
// console.warn('已存在图标名:', id);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 图标显示效果转换
|
|
399
|
+
* @param angle 旋转角度
|
|
400
|
+
* @param flip 翻转
|
|
401
|
+
* @returns
|
|
402
|
+
*/
|
|
403
|
+
export const getIconTransform = (angle?: number, flip?: boolean): string => {
|
|
404
|
+
let transform = '';
|
|
405
|
+
if (angle) {
|
|
406
|
+
transform += `rotate(${angle}deg)`;
|
|
407
|
+
}
|
|
408
|
+
if (flip) {
|
|
409
|
+
transform += ' scaleX(-1)';
|
|
410
|
+
}
|
|
411
|
+
return transform;
|
|
412
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 判断数据是否为空
|
|
3
|
+
* @param data 待检查的数据
|
|
4
|
+
* @returns 如果数据为空返回 true,否则返回 false
|
|
5
|
+
*/
|
|
6
|
+
export const isEmpty = <T>(data: T | null | undefined): boolean => {
|
|
7
|
+
// 处理 null、undefined 和 空字符串
|
|
8
|
+
if (data === null || data === undefined || data === '') return true;
|
|
9
|
+
|
|
10
|
+
// 处理数组
|
|
11
|
+
if (Array.isArray(data)) return data.length === 0;
|
|
12
|
+
|
|
13
|
+
// 处理对象
|
|
14
|
+
if (typeof data === 'object') return Object.keys(data).length === 0;
|
|
15
|
+
|
|
16
|
+
// 其他类型不为空
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useAppInfo } from '@/stores/appInfo';
|
|
2
|
+
import { useHostInfo } from '@/stores/hostInfo';
|
|
3
|
+
import { useUserInfo } from '@/stores/userInfo';
|
|
4
|
+
import { ApiResponse } from '@skyfox2000/fapi';
|
|
5
|
+
import { AppInfo, HostInfo, LoginInfo, UserInfo, mainAppApis } from '@skyfox2000/microbase';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 获取HostInfo
|
|
9
|
+
*/
|
|
10
|
+
export const getHostInfo = (): HostInfo => {
|
|
11
|
+
if (mainAppApis.value) return mainAppApis.value.getHostInfo();
|
|
12
|
+
const hostInfoStore = useHostInfo();
|
|
13
|
+
return hostInfoStore.hostInfo;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 获取当前应用信息
|
|
18
|
+
*/
|
|
19
|
+
export const getAppInfo = (): AppInfo => {
|
|
20
|
+
if (mainAppApis.value) return mainAppApis.value.getAppInfo();
|
|
21
|
+
const appInfoStore = useAppInfo();
|
|
22
|
+
return appInfoStore.appInfo;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// export const userRegister = () => {
|
|
26
|
+
// if (mainAppApis.value && mainAppApis.value.userRegister) return mainAppApis.value.userRegister();
|
|
27
|
+
// const userInfoStore = useUserInfo()
|
|
28
|
+
// return userInfoStore.userRegister();
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 用户登录接口
|
|
33
|
+
*/
|
|
34
|
+
export const userLogin = (loginInfo: LoginInfo): Promise<ApiResponse<LoginInfo> | void> => {
|
|
35
|
+
if (mainAppApis.value && mainAppApis.value.userLogin) return mainAppApis.value.userLogin(loginInfo);
|
|
36
|
+
const userInfoStore = useUserInfo();
|
|
37
|
+
return userInfoStore.login(loginInfo, true);
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* 用户注销接口
|
|
41
|
+
*/
|
|
42
|
+
export const userLogout = (): Promise<ApiResponse<void> | void> => {
|
|
43
|
+
if (mainAppApis.value && mainAppApis.value.userLogout) return mainAppApis.value.userLogout();
|
|
44
|
+
const userInfoStore = useUserInfo();
|
|
45
|
+
return userInfoStore.logout();
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* 获取授权码
|
|
49
|
+
* @returns 授权码
|
|
50
|
+
*/
|
|
51
|
+
export const getToken = (): string => {
|
|
52
|
+
if (mainAppApis.value && mainAppApis.value.getToken) return mainAppApis.value.getToken();
|
|
53
|
+
const userInfoStore = useUserInfo();
|
|
54
|
+
return userInfoStore.getToken();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 获取当前用户信息
|
|
59
|
+
*/
|
|
60
|
+
export const getUserInfo = (): UserInfo => {
|
|
61
|
+
if (mainAppApis.value && mainAppApis.value.getUserInfo) return mainAppApis.value.getUserInfo();
|
|
62
|
+
const userInfoStore = useUserInfo();
|
|
63
|
+
return userInfoStore.getUserInfo();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 子应用推送路由变更
|
|
68
|
+
*/
|
|
69
|
+
export const mainAppPush = (subPath: string) => {
|
|
70
|
+
const appInfoStore = useAppInfo();
|
|
71
|
+
appInfoStore.push(subPath);
|
|
72
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useAppInfo } from '@/stores/appInfo';
|
|
2
|
+
import { usePageInfo } from '@/stores/pageInfo';
|
|
3
|
+
import { useUserInfo } from '@/stores/userInfo';
|
|
4
|
+
import { BreadcrumbRoute } from '@/typings/menu';
|
|
5
|
+
import { RouteRecord } from '@skyfox2000/microbase';
|
|
6
|
+
import { Component, h, reactive } from 'vue';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 初始化菜单信息
|
|
10
|
+
* @param routes 路由信息
|
|
11
|
+
* @param menuData 菜单信息
|
|
12
|
+
*/
|
|
13
|
+
export const initMenu = <T>(
|
|
14
|
+
routes: RouteRecord[],
|
|
15
|
+
menuData: T[],
|
|
16
|
+
iconCom: Component,
|
|
17
|
+
iconProps?: Record<string, any>,
|
|
18
|
+
) => {
|
|
19
|
+
const result = routeToMenu<T>(routes, [], iconCom, iconProps);
|
|
20
|
+
result.forEach((item) => menuData.push(item));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 转换路由为菜单树结构
|
|
25
|
+
* @param routes 路由
|
|
26
|
+
* @param parentPath 上级路径
|
|
27
|
+
* @returns
|
|
28
|
+
*/
|
|
29
|
+
const routeToMenu = <T>(
|
|
30
|
+
routes: RouteRecord[],
|
|
31
|
+
parentPath: string[],
|
|
32
|
+
iconCom: Component,
|
|
33
|
+
iconProps?: Record<string, any>,
|
|
34
|
+
): T[] => {
|
|
35
|
+
const result: T[] = [];
|
|
36
|
+
const userInfoStore = useUserInfo();
|
|
37
|
+
for (const route of routes) {
|
|
38
|
+
if (
|
|
39
|
+
route.redirect ||
|
|
40
|
+
route.path.includes('/login') ||
|
|
41
|
+
route.path.includes('/error') ||
|
|
42
|
+
route.path.includes('/:page(.*)')
|
|
43
|
+
) {
|
|
44
|
+
// 非业务菜单
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
/// 角色匹配,继续处理
|
|
48
|
+
if (route.meta?.roles?.length && !userInfoStore.hasRole(route.meta.roles)) {
|
|
49
|
+
// 角色不匹配,跳过
|
|
50
|
+
if (route.meta?.permission && !userInfoStore.hasPermit(route.meta.permission, ':page')) {
|
|
51
|
+
// 权限不匹配,跳过
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/// 角色匹配,继续处理
|
|
56
|
+
const paths = route.path.split('/');
|
|
57
|
+
paths.unshift(...parentPath);
|
|
58
|
+
const children = routeToMenu(route.children || [], paths, iconCom, iconProps);
|
|
59
|
+
const newMenu: T = {
|
|
60
|
+
key: paths.join('/'),
|
|
61
|
+
label: route.name?.toString() ?? '',
|
|
62
|
+
title: route.name?.toString() ?? '',
|
|
63
|
+
icon: route.icon ? h(iconCom, { ...iconProps, icon: route.icon }) : undefined,
|
|
64
|
+
children: children.length > 0 ? children : undefined,
|
|
65
|
+
} as T;
|
|
66
|
+
if (children.length > 0 || !route.children) result.push(newMenu);
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const crumbs: BreadcrumbRoute[] = reactive([]);
|
|
72
|
+
/**
|
|
73
|
+
* 显示面包屑数据
|
|
74
|
+
*/
|
|
75
|
+
export const showBreadcrumb = () => {
|
|
76
|
+
crumbs.length = 0;
|
|
77
|
+
const appInfoStore = useAppInfo();
|
|
78
|
+
const pageInfoStore = usePageInfo();
|
|
79
|
+
let fullPath = pageInfoStore.TabActive;
|
|
80
|
+
const matched = appInfoStore.matchedRoutes(fullPath);
|
|
81
|
+
matched.forEach((item: RouteRecord, index: number) => {
|
|
82
|
+
const crumb: BreadcrumbRoute = {
|
|
83
|
+
index: index,
|
|
84
|
+
path: item.path,
|
|
85
|
+
breadcrumbName: item.name?.toString() ?? '',
|
|
86
|
+
};
|
|
87
|
+
crumbs.push(crumb);
|
|
88
|
+
});
|
|
89
|
+
};
|