bohui-vue 1.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 +121 -0
- package/bin/create-vue-template.js +565 -0
- package/package.json +28 -0
- package/templates/vue-project/.browserslistrc +3 -0
- package/templates/vue-project/.editorconfig +28 -0
- package/templates/vue-project/.env.development +2 -0
- package/templates/vue-project/.env.production +2 -0
- package/templates/vue-project/.eslintrc.cjs +76 -0
- package/templates/vue-project/.keep +0 -0
- package/templates/vue-project/.node-version +1 -0
- package/templates/vue-project/.prettierignore +13 -0
- package/templates/vue-project/.prettierrc +20 -0
- package/templates/vue-project/.prettierrc.txt +130 -0
- package/templates/vue-project/.stylelintrc.json +94 -0
- package/templates/vue-project/README.md +24 -0
- package/templates/vue-project/babel.config.js +5 -0
- package/templates/vue-project/index.html +34 -0
- package/templates/vue-project/package.json +75 -0
- package/templates/vue-project/public/favicon.ico +0 -0
- package/templates/vue-project/public/static/img/ai-default.jpg +0 -0
- package/templates/vue-project/public/static/img/image.png +0 -0
- package/templates/vue-project/public/static/img/ppt1.png +0 -0
- package/templates/vue-project/public/static/img/ppt2.png +0 -0
- package/templates/vue-project/public/static/img/ppt3.png +0 -0
- package/templates/vue-project/public/static/js/config.js +11 -0
- package/templates/vue-project/public/static/js/dataConfig.js +1143 -0
- package/templates/vue-project/src/App.vue +10 -0
- package/templates/vue-project/src/api/error-handler.ts +60 -0
- package/templates/vue-project/src/api/http.ts +254 -0
- package/templates/vue-project/src/api/services/aicebd.ts +47 -0
- package/templates/vue-project/src/api/services/base.ts +18 -0
- package/templates/vue-project/src/api/services/umse.ts +17 -0
- package/templates/vue-project/src/assets/font/Alibaba-PuHuiTi-Medium.otf +0 -0
- package/templates/vue-project/src/assets/font/Alibaba-PuHuiTi-Regular.otf +0 -0
- package/templates/vue-project/src/assets/font/DOUYINSANSBOLD.OTF +0 -0
- package/templates/vue-project/src/assets/font/Pangmen-Title.TTF +0 -0
- package/templates/vue-project/src/assets/font/font.css +25 -0
- package/templates/vue-project/src/assets/iconfont/iconfont.css +402 -0
- package/templates/vue-project/src/assets/iconfont/iconfont.js +66 -0
- package/templates/vue-project/src/assets/iconfont/iconfont.json +688 -0
- package/templates/vue-project/src/assets/iconfont/iconfont.ttf +0 -0
- package/templates/vue-project/src/assets/iconfont/iconfont.woff +0 -0
- package/templates/vue-project/src/assets/iconfont/iconfont.woff2 +0 -0
- package/templates/vue-project/src/assets/images/Click-tap.png +0 -0
- package/templates/vue-project/src/assets/images/Effects.png +0 -0
- package/templates/vue-project/src/assets/images/bg.png +0 -0
- package/templates/vue-project/src/assets/images/erCode.png +0 -0
- package/templates/vue-project/src/assets/images/header-bg.png +0 -0
- package/templates/vue-project/src/assets/images/logo.png +0 -0
- package/templates/vue-project/src/assets/scss/common.scss +530 -0
- package/templates/vue-project/src/assets/styles/element-overrides.css +53 -0
- package/templates/vue-project/src/assets/styles/reset.css +186 -0
- package/templates/vue-project/src/assets/styles/theme.css +100 -0
- package/templates/vue-project/src/components/BarChart.vue +238 -0
- package/templates/vue-project/src/components/echarts/EChart.vue +140 -0
- package/templates/vue-project/src/composables/useTheme.ts +84 -0
- package/templates/vue-project/src/main.ts +111 -0
- package/templates/vue-project/src/mocks/base.ts +37 -0
- package/templates/vue-project/src/mocks/umse.ts +31 -0
- package/templates/vue-project/src/router/index.ts +32 -0
- package/templates/vue-project/src/shims-vue.d.ts +19 -0
- package/templates/vue-project/src/store/index.ts +18 -0
- package/templates/vue-project/src/store/modules/user.ts +85 -0
- package/templates/vue-project/src/types/DTO/aicebd.d.ts +60 -0
- package/templates/vue-project/src/types/DTO/base.d.ts +26 -0
- package/templates/vue-project/src/types/DTO/global.d.ts +48 -0
- package/templates/vue-project/src/types/VO/teachingLog.d.ts +15 -0
- package/templates/vue-project/src/types/auto-imports.d.ts +73 -0
- package/templates/vue-project/src/types/components.d.ts +17 -0
- package/templates/vue-project/src/types/element-plus.d.ts +15 -0
- package/templates/vue-project/src/types/js-cookie.d.ts +1 -0
- package/templates/vue-project/src/types/unocss.d.ts +2 -0
- package/templates/vue-project/src/types/vite-plugins.d.ts +3 -0
- package/templates/vue-project/src/types/vue-router.d.ts +1 -0
- package/templates/vue-project/src/types/window-config.d.ts +12 -0
- package/templates/vue-project/src/utils/com-methods.ts +307 -0
- package/templates/vue-project/src/utils/echarts.ts +111 -0
- package/templates/vue-project/src/utils/number.ts +99 -0
- package/templates/vue-project/src/utils/rem.ts +82 -0
- package/templates/vue-project/src/utils/responsive.ts +103 -0
- package/templates/vue-project/src/utils/time.ts +314 -0
- package/templates/vue-project/src/utils/tracker.ts +527 -0
- package/templates/vue-project/src/utils/validators.ts +85 -0
- package/templates/vue-project/src/utils/window.ts +132 -0
- package/templates/vue-project/src/views/home/Home.vue +60 -0
- package/templates/vue-project/src/views/home/composables/useUserAuth.ts +13 -0
- package/templates/vue-project/src/views/teachingLog/TeachingLog.vue +40 -0
- package/templates/vue-project/src/views/teachingLog/__tests__/TeachingEffect.test.ts +96 -0
- package/templates/vue-project/src/views/teachingLog/__tests__/TeachingHighlight.test.ts +66 -0
- package/templates/vue-project/src/views/teachingLog/__tests__/TeachingLog.test.ts +34 -0
- package/templates/vue-project/src/views/teachingLog/components/TeachingEffect.vue +458 -0
- package/templates/vue-project/src/views/teachingLog/components/TeachingHighlight.vue +181 -0
- package/templates/vue-project/src/views/teachingLog/composables/useEffectTooltip.ts +88 -0
- package/templates/vue-project/src/views/teachingLog/composables/useEffectTrendChart.ts +160 -0
- package/templates/vue-project/tests/setup.ts +27 -0
- package/templates/vue-project/tsconfig.json +24 -0
- package/templates/vue-project/tsconfig.node.json +41 -0
- package/templates/vue-project/uno.config.ts +84 -0
- package/templates/vue-project/vite.config.ts +216 -0
- package/templates/vue-project/vue3_ai_prompt.md +652 -0
- package/templates/vue-project/vue3_ai_prompt_basic.md +722 -0
- package/templates/vue-project/vue3_ai_prompt_full.md +1021 -0
- package/templates/vue-project/vue3_ai_prompt_unocss.md +768 -0
- package/templates/vue-project//345/267/245/347/250/213/345/214/226/346/250/241/346/235/277/344/273/213/347/273/215.md +463 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "vue-router";
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted, ref, type Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export type ResizeSize = { width: number; height: number };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 监听 window resize,并更新一个响应式 size。
|
|
7
|
+
* - 若传入 targetRef,则优先使用容器 clientWidth/clientHeight;否则使用 window.innerWidth/innerHeight
|
|
8
|
+
* - 通过 setTimeout 延迟更新,避免频繁 resize 触发时抖动
|
|
9
|
+
*/
|
|
10
|
+
export type UseResizeTriggerOptions = {
|
|
11
|
+
targetRef?: Ref<HTMLElement | null>;
|
|
12
|
+
delay?: number;
|
|
13
|
+
immediate?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export function useResizeTrigger(options: UseResizeTriggerOptions = {}) {
|
|
16
|
+
const { targetRef, delay = 300, immediate = false } = options;
|
|
17
|
+
const windowSize = ref<ResizeSize>({ width: 0, height: 0 });
|
|
18
|
+
|
|
19
|
+
const resizeHandler = () => {
|
|
20
|
+
const update = () => {
|
|
21
|
+
const el = targetRef?.value ?? null;
|
|
22
|
+
windowSize.value = {
|
|
23
|
+
width: el?.clientWidth ?? window.innerWidth,
|
|
24
|
+
height: el?.clientHeight ?? window.innerHeight,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
if (delay > 0) {
|
|
28
|
+
setTimeout(update, delay);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
update();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
if (immediate) resizeHandler();
|
|
36
|
+
window.addEventListener("resize", resizeHandler);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
onBeforeUnmount(() => {
|
|
40
|
+
window.removeEventListener("resize", resizeHandler);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return { windowSize, resizeHandler };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 规范化文本:非字符串返回空串;字符串会 trim 去除首尾空白。
|
|
48
|
+
*/
|
|
49
|
+
export function normalizeText(v: unknown) {
|
|
50
|
+
return typeof v === "string" ? v.trim() : "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 解析颜色字符串:为空/非字符串时返回 fallback;否则返回 trim 后的颜色值。
|
|
55
|
+
*/
|
|
56
|
+
export function resolveColor(input: unknown, fallback: string) {
|
|
57
|
+
if (typeof input !== "string") return fallback;
|
|
58
|
+
const trimmed = input.trim();
|
|
59
|
+
return trimmed ? trimmed : fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 将未知类型转换为数字:不可转换(NaN/Infinity)时返回 fallback。
|
|
64
|
+
*/
|
|
65
|
+
export function toNumber(v: unknown, fallback = 0) {
|
|
66
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
67
|
+
return Number.isFinite(n) ? n : fallback;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 将数值限制在指定范围内。
|
|
72
|
+
*/
|
|
73
|
+
export function clamp(n: number, min: number, max: number) {
|
|
74
|
+
return Math.max(min, Math.min(max, n));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 将未知类型转换为数值并钳制到指定范围:不可转换(NaN/Infinity)时返回 min。
|
|
79
|
+
*/
|
|
80
|
+
export function clampNumber(v: unknown, min: number, max: number) {
|
|
81
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
82
|
+
if (!Number.isFinite(n)) return min;
|
|
83
|
+
return Math.max(min, Math.min(max, n));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 向上取整到最近的倍数。
|
|
88
|
+
*/
|
|
89
|
+
export function roundUp(n: number, step: number) {
|
|
90
|
+
if (step <= 0) return n;
|
|
91
|
+
return Math.ceil(n / step) * step;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 将 {x,y,width,height} 转为矩形边界,并做边界保护(不会超出 maxWidth/maxHeight)。
|
|
96
|
+
*/
|
|
97
|
+
export type RectBox = { left: number; top: number; right: number; bottom: number };
|
|
98
|
+
export function rectFromXYWH(
|
|
99
|
+
e: { x: number; y: number; width: number; height: number },
|
|
100
|
+
maxWidth: number,
|
|
101
|
+
maxHeight: number
|
|
102
|
+
): RectBox {
|
|
103
|
+
const left = clamp(e.x, 0, maxWidth);
|
|
104
|
+
const top = clamp(e.y, 0, maxHeight);
|
|
105
|
+
const width = clamp(e.width, 1, maxWidth - left);
|
|
106
|
+
const height = clamp(e.height, 1, maxHeight - top);
|
|
107
|
+
return { left, top, right: left + width, bottom: top + height };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 同步 Canvas 像素尺寸(按容器尺寸 + DPR),并将 ctx 坐标系缩放到 CSS 像素单位。
|
|
112
|
+
* - DOM 看到的尺寸:canvas style width/height
|
|
113
|
+
* - 实际绘制的像素尺寸:canvas width/height(乘 dpr,避免高分屏模糊)
|
|
114
|
+
*/
|
|
115
|
+
export type SyncCanvasSizeResult = {
|
|
116
|
+
width: number;
|
|
117
|
+
height: number;
|
|
118
|
+
dpr: number;
|
|
119
|
+
ctx: CanvasRenderingContext2D | null;
|
|
120
|
+
};
|
|
121
|
+
export function syncCanvasSizeToElement(
|
|
122
|
+
containerEl: HTMLElement,
|
|
123
|
+
canvasEl: HTMLCanvasElement
|
|
124
|
+
): SyncCanvasSizeResult {
|
|
125
|
+
const rect = containerEl.getBoundingClientRect();
|
|
126
|
+
const dpr = window.devicePixelRatio || 1;
|
|
127
|
+
const width = Math.max(1, Math.round(rect.width));
|
|
128
|
+
const height = Math.max(1, Math.round(rect.height));
|
|
129
|
+
|
|
130
|
+
canvasEl.width = Math.round(width * dpr);
|
|
131
|
+
canvasEl.height = Math.round(height * dpr);
|
|
132
|
+
canvasEl.style.width = `${width}px`;
|
|
133
|
+
canvasEl.style.height = `${height}px`;
|
|
134
|
+
|
|
135
|
+
const ctx = canvasEl.getContext("2d");
|
|
136
|
+
ctx?.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
137
|
+
|
|
138
|
+
return { width, height, dpr, ctx };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 监听元素尺寸变化并在变化时执行回调,返回清理函数。
|
|
143
|
+
* - 优先使用 ResizeObserver;不可用时回退到 window.resize
|
|
144
|
+
*/
|
|
145
|
+
export function observeElementResize(targetEl: Element, onResize: () => void) {
|
|
146
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
147
|
+
const ro = new ResizeObserver(() => onResize());
|
|
148
|
+
ro.observe(targetEl);
|
|
149
|
+
return () => ro.disconnect();
|
|
150
|
+
}
|
|
151
|
+
if (typeof window === "undefined") return () => {};
|
|
152
|
+
window.addEventListener("resize", onResize);
|
|
153
|
+
return () => window.removeEventListener("resize", onResize);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 包装标签文本:限制每行字符数与最大行数,超出部分以省略号结尾。
|
|
158
|
+
*/
|
|
159
|
+
export function wrapLabel(text: string, maxCharsPerLine: number, maxLines: number) {
|
|
160
|
+
const normalized = String(text ?? "");
|
|
161
|
+
if (!normalized) return "";
|
|
162
|
+
|
|
163
|
+
const chunks: string[] = [];
|
|
164
|
+
for (let i = 0; i < normalized.length; i += maxCharsPerLine) {
|
|
165
|
+
chunks.push(normalized.slice(i, i + maxCharsPerLine));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (chunks.length <= maxLines) return chunks.join("\n");
|
|
169
|
+
|
|
170
|
+
const kept = chunks.slice(0, maxLines);
|
|
171
|
+
const lastIndex = kept.length - 1;
|
|
172
|
+
kept[lastIndex] = `${kept[lastIndex]}…`;
|
|
173
|
+
return kept.join("\n");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 截断字符串:最多显示 maxChars 个字符,超出部分用 ... 表示。
|
|
178
|
+
*/
|
|
179
|
+
export function truncateLabel(text: string, maxChars: number) {
|
|
180
|
+
const normalized = String(text ?? "");
|
|
181
|
+
if (normalized.length <= maxChars) return normalized;
|
|
182
|
+
return `${normalized.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 从对象上安全读取字符串属性;不存在或非字符串返回空串。
|
|
187
|
+
*/
|
|
188
|
+
export function getObjectString(obj: unknown, key: string) {
|
|
189
|
+
if (!obj || typeof obj !== "object") return "";
|
|
190
|
+
if (!(key in obj)) return "";
|
|
191
|
+
const v = (obj as Record<string, unknown>)[key];
|
|
192
|
+
return typeof v === "string" ? v : "";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 从对象上安全读取字符串/数字属性,并转换为字符串;不存在返回空串。
|
|
197
|
+
*/
|
|
198
|
+
export function getObjectStringOrNumber(obj: unknown, key: string) {
|
|
199
|
+
if (!obj || typeof obj !== "object") return "";
|
|
200
|
+
if (!(key in obj)) return "";
|
|
201
|
+
const v = (obj as Record<string, unknown>)[key];
|
|
202
|
+
return typeof v === "string" || typeof v === "number" ? String(v) : "";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 从对象上安全读取数值属性(支持 number 与可转数字的 string);不可用时返回 0。
|
|
207
|
+
*/
|
|
208
|
+
export function getObjectNumber(obj: unknown, key: string) {
|
|
209
|
+
if (!obj || typeof obj !== "object") return 0;
|
|
210
|
+
if (!(key in obj)) return 0;
|
|
211
|
+
return toNumber((obj as Record<string, unknown>)[key]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 将十六进制颜色字符串转换为 RGB 对象。
|
|
216
|
+
* 支持 #RGB 与 #RRGGBB。
|
|
217
|
+
*/
|
|
218
|
+
export function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
|
219
|
+
const cleaned = String(hex ?? "")
|
|
220
|
+
.replace("#", "")
|
|
221
|
+
.trim();
|
|
222
|
+
const full =
|
|
223
|
+
cleaned.length === 3
|
|
224
|
+
? cleaned
|
|
225
|
+
.split("")
|
|
226
|
+
.map((c) => c + c)
|
|
227
|
+
.join("")
|
|
228
|
+
: cleaned;
|
|
229
|
+
const n = parseInt(full, 16);
|
|
230
|
+
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 将 RGB 值转换为十六进制颜色字符串(#RRGGBB)。
|
|
235
|
+
*/
|
|
236
|
+
export function rgbToHex(r: number, g: number, b: number) {
|
|
237
|
+
const to = (n: number) =>
|
|
238
|
+
Math.max(0, Math.min(255, Math.round(n)))
|
|
239
|
+
.toString(16)
|
|
240
|
+
.padStart(2, "0");
|
|
241
|
+
return `#${to(r)}${to(g)}${to(b)}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 将十六进制颜色变暗(amount:0~1,越大越暗)。
|
|
246
|
+
*/
|
|
247
|
+
export function darkenHex(hex: string, amount: number) {
|
|
248
|
+
const { r, g, b } = hexToRgb(hex);
|
|
249
|
+
const k = Math.max(0, Math.min(1, 1 - amount));
|
|
250
|
+
return rgbToHex(r * k, g * k, b * k);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 将十六进制颜色转换为 rgba(alpha) 字符串。
|
|
255
|
+
* 支持 #RGB 与 #RRGGBB。
|
|
256
|
+
*/
|
|
257
|
+
export function hexToRgba(hex: string, alpha: number) {
|
|
258
|
+
const cleaned = String(hex ?? "")
|
|
259
|
+
.replace("#", "")
|
|
260
|
+
.trim();
|
|
261
|
+
const full =
|
|
262
|
+
cleaned.length === 3
|
|
263
|
+
? cleaned
|
|
264
|
+
.split("")
|
|
265
|
+
.map((c) => c + c)
|
|
266
|
+
.join("")
|
|
267
|
+
: cleaned;
|
|
268
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
269
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
270
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
271
|
+
const a = clampNumber(alpha, 0, 1);
|
|
272
|
+
return `rgba(${Number.isFinite(r) ? r : 0},${Number.isFinite(g) ? g : 0},${
|
|
273
|
+
Number.isFinite(b) ? b : 0
|
|
274
|
+
},${a})`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 获取文本像素宽度。
|
|
279
|
+
* - estimate:按字符类型粗略估算(ASCII 0.6,其它 1)
|
|
280
|
+
* - canvas:使用 canvas.measureText 精确测量(无 document 或无法获取 ctx 时回退为估算)
|
|
281
|
+
*/
|
|
282
|
+
export type TextWidthStrategy = "estimate" | "canvas";
|
|
283
|
+
let measureCanvas: HTMLCanvasElement | null = null;
|
|
284
|
+
export function getTextWidth(
|
|
285
|
+
text: string,
|
|
286
|
+
fontSizePx: number,
|
|
287
|
+
strategy: TextWidthStrategy = "estimate"
|
|
288
|
+
) {
|
|
289
|
+
const str = String(text ?? "");
|
|
290
|
+
|
|
291
|
+
if (strategy === "canvas") {
|
|
292
|
+
if (typeof document === "undefined") {
|
|
293
|
+
return str.length * fontSizePx;
|
|
294
|
+
}
|
|
295
|
+
measureCanvas ??= document.createElement("canvas");
|
|
296
|
+
const ctx = measureCanvas.getContext("2d");
|
|
297
|
+
if (!ctx) return str.length * fontSizePx;
|
|
298
|
+
ctx.font = `${fontSizePx}px sans-serif`;
|
|
299
|
+
return ctx.measureText(str).width;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let units = 0;
|
|
303
|
+
for (const ch of str) {
|
|
304
|
+
units += ch.charCodeAt(0) <= 0x007f ? 0.6 : 1;
|
|
305
|
+
}
|
|
306
|
+
return units * fontSizePx;
|
|
307
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECharts 按需引入配置
|
|
3
|
+
* 只引入项目中使用的图表类型,减小打包体积
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 引入 echarts 核心模块
|
|
7
|
+
import * as echarts from "echarts/core";
|
|
8
|
+
|
|
9
|
+
// 引入图表类型
|
|
10
|
+
import {
|
|
11
|
+
BarChart,
|
|
12
|
+
PictorialBarChart,
|
|
13
|
+
LineChart,
|
|
14
|
+
PieChart,
|
|
15
|
+
RadarChart,
|
|
16
|
+
MapChart,
|
|
17
|
+
ScatterChart,
|
|
18
|
+
CustomChart,
|
|
19
|
+
} from "echarts/charts";
|
|
20
|
+
|
|
21
|
+
// 引入组件
|
|
22
|
+
import {
|
|
23
|
+
TitleComponent,
|
|
24
|
+
TooltipComponent,
|
|
25
|
+
GridComponent,
|
|
26
|
+
LegendComponent,
|
|
27
|
+
DatasetComponent,
|
|
28
|
+
TransformComponent,
|
|
29
|
+
GeoComponent,
|
|
30
|
+
VisualMapComponent,
|
|
31
|
+
MarkLineComponent,
|
|
32
|
+
MarkPointComponent,
|
|
33
|
+
GraphicComponent,
|
|
34
|
+
} from "echarts/components";
|
|
35
|
+
|
|
36
|
+
// 引入渲染器(必须)
|
|
37
|
+
import { LabelLayout, UniversalTransition } from "echarts/features";
|
|
38
|
+
import { CanvasRenderer } from "echarts/renderers";
|
|
39
|
+
|
|
40
|
+
// 注册必须的组件
|
|
41
|
+
echarts.use([
|
|
42
|
+
// 图表类型
|
|
43
|
+
BarChart,
|
|
44
|
+
PictorialBarChart,
|
|
45
|
+
LineChart,
|
|
46
|
+
PieChart,
|
|
47
|
+
RadarChart,
|
|
48
|
+
MapChart,
|
|
49
|
+
ScatterChart,
|
|
50
|
+
CustomChart,
|
|
51
|
+
|
|
52
|
+
// 组件
|
|
53
|
+
TitleComponent,
|
|
54
|
+
TooltipComponent,
|
|
55
|
+
GridComponent,
|
|
56
|
+
LegendComponent,
|
|
57
|
+
DatasetComponent,
|
|
58
|
+
TransformComponent,
|
|
59
|
+
GeoComponent,
|
|
60
|
+
VisualMapComponent,
|
|
61
|
+
MarkLineComponent,
|
|
62
|
+
MarkPointComponent,
|
|
63
|
+
GraphicComponent,
|
|
64
|
+
|
|
65
|
+
// 功能
|
|
66
|
+
LabelLayout,
|
|
67
|
+
UniversalTransition,
|
|
68
|
+
|
|
69
|
+
// 渲染器
|
|
70
|
+
CanvasRenderer,
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
// 导出配置好的 echarts
|
|
74
|
+
export { echarts };
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 使用说明:
|
|
78
|
+
*
|
|
79
|
+
* 如果需要添加新的图表类型,按照以下步骤:
|
|
80
|
+
*
|
|
81
|
+
* 1. 从 'echarts/charts' 引入对应的图表
|
|
82
|
+
* 例如:import { GaugeChart } from 'echarts/charts'
|
|
83
|
+
*
|
|
84
|
+
* 2. 在 echarts.use([...]) 中注册
|
|
85
|
+
* 例如:echarts.use([..., GaugeChart])
|
|
86
|
+
*
|
|
87
|
+
* 常用图表类型:
|
|
88
|
+
* - BarChart 柱状图/条形图
|
|
89
|
+
* - LineChart 折线图/面积图
|
|
90
|
+
* - PieChart 饼图/环形图
|
|
91
|
+
* - ScatterChart 散点图/气泡图
|
|
92
|
+
* - CustomChart 自定义图表
|
|
93
|
+
* - RadarChart 雷达图
|
|
94
|
+
* - MapChart 地图
|
|
95
|
+
* - TreeChart 树图
|
|
96
|
+
* - TreemapChart 矩形树图
|
|
97
|
+
* - GraphChart 关系图
|
|
98
|
+
* - GaugeChart 仪表盘
|
|
99
|
+
* - FunnelChart 漏斗图
|
|
100
|
+
* - ParallelChart 平行坐标系
|
|
101
|
+
* - SankeyChart 桑基图
|
|
102
|
+
* - BoxplotChart 箱线图
|
|
103
|
+
* - CandlestickChart K线图
|
|
104
|
+
* - EffectScatterChart 涟漪特效散点图
|
|
105
|
+
* - LinesChart 线图
|
|
106
|
+
* - HeatmapChart 热力图
|
|
107
|
+
* - PictorialBarChart 象形柱图
|
|
108
|
+
* - ThemeRiverChart 主题河流图
|
|
109
|
+
* - SunburstChart 旭日图
|
|
110
|
+
* - CustomChart 自定义图
|
|
111
|
+
*/
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数值单位转换工具函数
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type NumberUnit = "个" | "百" | "千" | "万" | "十万" | "百万" | "千万" | "亿";
|
|
6
|
+
|
|
7
|
+
export interface NumberUnitConversionResult {
|
|
8
|
+
value: number;
|
|
9
|
+
unit: NumberUnit;
|
|
10
|
+
displayText: string; // 格式化后的显示文本,如 "1.2万"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 将数值转换为带单位的显示格式
|
|
15
|
+
* @param value 原始数值
|
|
16
|
+
* @param minUnit 最小转换单位,小于此单位的值不进行转换(默认:"个")
|
|
17
|
+
* @param precision 小数位数(默认:1)
|
|
18
|
+
* @returns 转换后的数值、单位和显示文本
|
|
19
|
+
*/
|
|
20
|
+
export function convertNumberUnit(
|
|
21
|
+
value: number,
|
|
22
|
+
minUnit: NumberUnit = "万",
|
|
23
|
+
precision = 1
|
|
24
|
+
): NumberUnitConversionResult {
|
|
25
|
+
if (value === 0) {
|
|
26
|
+
return {
|
|
27
|
+
value: 0,
|
|
28
|
+
unit: "个",
|
|
29
|
+
displayText: "0",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const absValue = Math.abs(value);
|
|
34
|
+
const sign = value < 0 ? "-" : "";
|
|
35
|
+
|
|
36
|
+
// 定义单位转换规则(去掉十万,仅保留:个 / 百 / 千 / 万 / 百万 / 千万 / 亿)
|
|
37
|
+
const unitRules: Array<{ threshold: number; unit: NumberUnit }> = [
|
|
38
|
+
{ threshold: 100000000, unit: "亿" }, // >= 1亿
|
|
39
|
+
{ threshold: 10000000, unit: "千万" }, // >= 1千万 且 < 1亿
|
|
40
|
+
{ threshold: 1000000, unit: "百万" }, // >= 1百万 且 < 1千万
|
|
41
|
+
{ threshold: 10000, unit: "万" }, // >= 1万 且 < 1百万
|
|
42
|
+
{ threshold: 1000, unit: "千" }, // >= 1千 且 < 1万
|
|
43
|
+
{ threshold: 100, unit: "百" }, // >= 1百 且 < 1千
|
|
44
|
+
{ threshold: 0, unit: "个" }, // 个位
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// 确定最小转换单位的阈值
|
|
48
|
+
const minUnitThreshold = unitRules.find((rule) => rule.unit === minUnit)?.threshold ?? 0;
|
|
49
|
+
|
|
50
|
+
// 找到合适的转换单位
|
|
51
|
+
let selectedRule = unitRules.find(
|
|
52
|
+
(rule) => absValue >= rule.threshold && rule.threshold >= minUnitThreshold
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// 如果没有找到合适的单位(值小于最小单位),使用"个"
|
|
56
|
+
if (!selectedRule) {
|
|
57
|
+
selectedRule = { threshold: 0, unit: "个" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 计算转换后的数值
|
|
61
|
+
let convertedValue = absValue;
|
|
62
|
+
if (selectedRule.threshold > 0) {
|
|
63
|
+
convertedValue = absValue / selectedRule.threshold;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 格式化数值(保留指定位数的小数)
|
|
67
|
+
const multiplier = Math.pow(10, precision);
|
|
68
|
+
const roundedValue = Math.round(convertedValue * multiplier) / multiplier;
|
|
69
|
+
|
|
70
|
+
// 生成显示文本
|
|
71
|
+
let displayText = "";
|
|
72
|
+
if (selectedRule.unit === "个") {
|
|
73
|
+
displayText = `${sign}${Math.round(absValue)}`;
|
|
74
|
+
} else {
|
|
75
|
+
// 如果小数部分为0,显示整数
|
|
76
|
+
if (roundedValue % 1 === 0) {
|
|
77
|
+
displayText = `${sign}${Math.round(roundedValue)}${selectedRule.unit}`;
|
|
78
|
+
} else {
|
|
79
|
+
displayText = `${sign}${roundedValue.toFixed(precision)}${selectedRule.unit}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
value: sign === "-" ? -roundedValue : roundedValue,
|
|
85
|
+
unit: selectedRule.unit,
|
|
86
|
+
displayText,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 将数值拆分为单个数字数组(用于数字展示组件)
|
|
92
|
+
* @param value 数值
|
|
93
|
+
* @returns 数字数组,如 1234 => [1, 2, 3, 4]
|
|
94
|
+
*/
|
|
95
|
+
export function splitNumberToDigits(value: number): number[] {
|
|
96
|
+
return String(Math.abs(Math.round(value)))
|
|
97
|
+
.split("")
|
|
98
|
+
.map(Number);
|
|
99
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Description: 小于等于1920分辨率下的响应式方案
|
|
3
|
+
* @Version: 1.0
|
|
4
|
+
* @Autor: zhaozhenzhuo
|
|
5
|
+
* @Date: 2025-10-21 16:53:30
|
|
6
|
+
* @LastEditors: zhaozhenzhuo
|
|
7
|
+
* @LastEditTime: 2025-10-21 16:53:30
|
|
8
|
+
*/
|
|
9
|
+
/* eslint-disable */
|
|
10
|
+
(function (designWidth: number, maxWidth: number, minWidth: number) {
|
|
11
|
+
const doc = document;
|
|
12
|
+
const win = window;
|
|
13
|
+
const docEl = document.documentElement;
|
|
14
|
+
let tid: NodeJS.Timeout;
|
|
15
|
+
let rootItem: HTMLElement | HTMLStyleElement | null;
|
|
16
|
+
let rootStyle;
|
|
17
|
+
|
|
18
|
+
function refreshRem() {
|
|
19
|
+
let width = document.getElementById("app")?.getBoundingClientRect().width;
|
|
20
|
+
let rem;
|
|
21
|
+
if (!maxWidth) {
|
|
22
|
+
maxWidth = 1920;
|
|
23
|
+
}
|
|
24
|
+
if (!minWidth) {
|
|
25
|
+
minWidth = 1366;
|
|
26
|
+
}
|
|
27
|
+
if (width && width > maxWidth) {
|
|
28
|
+
if (width <= 2560) {
|
|
29
|
+
rem = 11;
|
|
30
|
+
} else {
|
|
31
|
+
rem = 12;
|
|
32
|
+
}
|
|
33
|
+
} else if (width && width < minWidth) {
|
|
34
|
+
rem = (minWidth * 11) / designWidth;
|
|
35
|
+
} else {
|
|
36
|
+
// 与淘宝做法不同,直接采用简单的rem换算方法1rem=10px
|
|
37
|
+
rem = ((width && width > 0 ? width : 1920) * 10) / designWidth;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 兼容UC开始
|
|
41
|
+
rootStyle = `html{font-size:${rem}px !important}`;
|
|
42
|
+
rootItem = document.getElementById("rootsize") || document.createElement("style");
|
|
43
|
+
if (!document.getElementById("rootsize")) {
|
|
44
|
+
document.getElementsByTagName("head")[0].appendChild(rootItem);
|
|
45
|
+
rootItem.id = "rootsize";
|
|
46
|
+
}
|
|
47
|
+
if (rootItem && (rootItem as any).styleSheet) {
|
|
48
|
+
(rootItem as any).styleSheet.disabled ||
|
|
49
|
+
((rootItem as any).styleSheet.cssText = rootStyle);
|
|
50
|
+
} else {
|
|
51
|
+
try {
|
|
52
|
+
rootItem.innerHTML = rootStyle;
|
|
53
|
+
} catch (f) {
|
|
54
|
+
rootItem.innerText = rootStyle;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// 兼容UC结束
|
|
58
|
+
docEl.style.fontSize = `${rem}px`;
|
|
59
|
+
}
|
|
60
|
+
refreshRem();
|
|
61
|
+
|
|
62
|
+
win.addEventListener(
|
|
63
|
+
"resize",
|
|
64
|
+
function () {
|
|
65
|
+
clearTimeout(tid); // 防止执行两次
|
|
66
|
+
tid = setTimeout(refreshRem, 300);
|
|
67
|
+
},
|
|
68
|
+
false
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
win.addEventListener(
|
|
72
|
+
"pageshow",
|
|
73
|
+
function (e) {
|
|
74
|
+
if (e.persisted) {
|
|
75
|
+
// 浏览器后退的时候重新计算
|
|
76
|
+
clearTimeout(tid);
|
|
77
|
+
tid = setTimeout(refreshRem, 300);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
false
|
|
81
|
+
);
|
|
82
|
+
})(1920, 1920, 1366);
|