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,186 @@
|
|
|
1
|
+
/* http://meyerweb.com/eric/tools/css/reset/
|
|
2
|
+
v2.0 | 20110126
|
|
3
|
+
License: none (public domain)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
html,
|
|
7
|
+
body,
|
|
8
|
+
div,
|
|
9
|
+
span,
|
|
10
|
+
applet,
|
|
11
|
+
object,
|
|
12
|
+
iframe,
|
|
13
|
+
h1,
|
|
14
|
+
h2,
|
|
15
|
+
h3,
|
|
16
|
+
h4,
|
|
17
|
+
h5,
|
|
18
|
+
h6,
|
|
19
|
+
p,
|
|
20
|
+
blockquote,
|
|
21
|
+
pre,
|
|
22
|
+
a,
|
|
23
|
+
abbr,
|
|
24
|
+
acronym,
|
|
25
|
+
address,
|
|
26
|
+
big,
|
|
27
|
+
cite,
|
|
28
|
+
code,
|
|
29
|
+
del,
|
|
30
|
+
dfn,
|
|
31
|
+
em,
|
|
32
|
+
img,
|
|
33
|
+
ins,
|
|
34
|
+
kbd,
|
|
35
|
+
q,
|
|
36
|
+
s,
|
|
37
|
+
samp,
|
|
38
|
+
small,
|
|
39
|
+
strike,
|
|
40
|
+
strong,
|
|
41
|
+
sub,
|
|
42
|
+
sup,
|
|
43
|
+
tt,
|
|
44
|
+
var,
|
|
45
|
+
b,
|
|
46
|
+
u,
|
|
47
|
+
i,
|
|
48
|
+
center,
|
|
49
|
+
dl,
|
|
50
|
+
dt,
|
|
51
|
+
dd,
|
|
52
|
+
ol,
|
|
53
|
+
ul,
|
|
54
|
+
li,
|
|
55
|
+
fieldset,
|
|
56
|
+
form,
|
|
57
|
+
label,
|
|
58
|
+
legend,
|
|
59
|
+
table,
|
|
60
|
+
caption,
|
|
61
|
+
tbody,
|
|
62
|
+
tfoot,
|
|
63
|
+
thead,
|
|
64
|
+
tr,
|
|
65
|
+
th,
|
|
66
|
+
td,
|
|
67
|
+
article,
|
|
68
|
+
aside,
|
|
69
|
+
canvas,
|
|
70
|
+
details,
|
|
71
|
+
embed,
|
|
72
|
+
figure,
|
|
73
|
+
figcaption,
|
|
74
|
+
footer,
|
|
75
|
+
header,
|
|
76
|
+
hgroup,
|
|
77
|
+
menu,
|
|
78
|
+
nav,
|
|
79
|
+
output,
|
|
80
|
+
ruby,
|
|
81
|
+
section,
|
|
82
|
+
summary,
|
|
83
|
+
time,
|
|
84
|
+
mark,
|
|
85
|
+
audio,
|
|
86
|
+
video {
|
|
87
|
+
margin: 0;
|
|
88
|
+
padding: 0;
|
|
89
|
+
border: 0;
|
|
90
|
+
font-size: 100%;
|
|
91
|
+
font: inherit;
|
|
92
|
+
vertical-align: baseline;
|
|
93
|
+
}
|
|
94
|
+
/* HTML5 display-role reset for older browsers */
|
|
95
|
+
article,
|
|
96
|
+
aside,
|
|
97
|
+
details,
|
|
98
|
+
figcaption,
|
|
99
|
+
figure,
|
|
100
|
+
footer,
|
|
101
|
+
header,
|
|
102
|
+
hgroup,
|
|
103
|
+
menu,
|
|
104
|
+
nav,
|
|
105
|
+
section {
|
|
106
|
+
display: block;
|
|
107
|
+
}
|
|
108
|
+
body {
|
|
109
|
+
line-height: 1;
|
|
110
|
+
}
|
|
111
|
+
ol,
|
|
112
|
+
ul {
|
|
113
|
+
list-style: none;
|
|
114
|
+
}
|
|
115
|
+
a {
|
|
116
|
+
text-decoration: none;
|
|
117
|
+
}
|
|
118
|
+
blockquote,
|
|
119
|
+
q {
|
|
120
|
+
quotes: none;
|
|
121
|
+
}
|
|
122
|
+
blockquote:before,
|
|
123
|
+
blockquote:after,
|
|
124
|
+
q:before,
|
|
125
|
+
q:after {
|
|
126
|
+
content: "";
|
|
127
|
+
content: none;
|
|
128
|
+
}
|
|
129
|
+
table {
|
|
130
|
+
border-collapse: collapse;
|
|
131
|
+
border-spacing: 0;
|
|
132
|
+
}
|
|
133
|
+
#classManagement .luboConfig .el-icon-close:before {
|
|
134
|
+
content: "关闭";
|
|
135
|
+
font-size: 12px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#classManagement .el-dialog__title {
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
}
|
|
141
|
+
#classManagement .luboConfig .el-dialog__footer {
|
|
142
|
+
text-align: center;
|
|
143
|
+
}
|
|
144
|
+
#classManagement .luboConfig .el-dialog__header {
|
|
145
|
+
padding: 6px 0;
|
|
146
|
+
border-bottom: 1px solid #ccc;
|
|
147
|
+
}
|
|
148
|
+
#classManagement .luboConfig .el-dialog__headerbtn {
|
|
149
|
+
top: 13px;
|
|
150
|
+
right: 64px;
|
|
151
|
+
font-size: 12px;
|
|
152
|
+
}
|
|
153
|
+
#classManagement .luboConfig .el-dialog {
|
|
154
|
+
border: 1px solid #ccc;
|
|
155
|
+
}
|
|
156
|
+
#classManagement .luboConfig .el-dialog__body {
|
|
157
|
+
display: block;
|
|
158
|
+
}
|
|
159
|
+
#classManagement .luboConfig .el-button {
|
|
160
|
+
padding: 8px 40px;
|
|
161
|
+
background-color: #169bd5;
|
|
162
|
+
font-size: 12px;
|
|
163
|
+
}
|
|
164
|
+
#classManagement .rightConfig .el-dialog__header {
|
|
165
|
+
width: 95%;
|
|
166
|
+
margin: 0 auto;
|
|
167
|
+
padding: 10px 0;
|
|
168
|
+
border-bottom: 2px solid orange;
|
|
169
|
+
}
|
|
170
|
+
#classManagement .rightConfig .el-button--primary {
|
|
171
|
+
background-color: orange;
|
|
172
|
+
border-color: orange;
|
|
173
|
+
}
|
|
174
|
+
#classManagement .rightConfig .el-dialog__footer {
|
|
175
|
+
text-align: center;
|
|
176
|
+
}
|
|
177
|
+
#classManagement .rightConfig .el-dialog__headerbtn {
|
|
178
|
+
top: 13px;
|
|
179
|
+
right: 18px;
|
|
180
|
+
font-size: 14px;
|
|
181
|
+
}
|
|
182
|
+
#classManagement .el-table .cell,
|
|
183
|
+
#classManagement .el-table__body-wrapper,
|
|
184
|
+
#classManagement .el-table {
|
|
185
|
+
overflow: visible;
|
|
186
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* 状态颜色 */
|
|
3
|
+
--color-brand: #674fde;
|
|
4
|
+
--color-success: #22ac38;
|
|
5
|
+
--color-warning: #ef8700;
|
|
6
|
+
--color-danger: #d20303;
|
|
7
|
+
--color-info: #909399;
|
|
8
|
+
|
|
9
|
+
/* 背景颜色 */
|
|
10
|
+
/* 页面背景 */
|
|
11
|
+
--bg-color-page: #eee;
|
|
12
|
+
/* 主要区块背景 */
|
|
13
|
+
--bg-block-color-primary: #f6f7fb;
|
|
14
|
+
/* 深色区块背景 */
|
|
15
|
+
--bg-block-color-dark: #807d8b;
|
|
16
|
+
/* 品牌色区块背景 */
|
|
17
|
+
--bg-block-color-brand: color.adjust(#674fde, $lightness: 35%);
|
|
18
|
+
/* 表格行1背景 */
|
|
19
|
+
--bg-td-1-color: #fff;
|
|
20
|
+
/* 表格行2背景 */
|
|
21
|
+
--bg-td-2-color: #f4f7f9;
|
|
22
|
+
/* 滚动条颜色 */
|
|
23
|
+
--scrollbar-color: #cccccc;
|
|
24
|
+
|
|
25
|
+
/* 文字颜色 */
|
|
26
|
+
/* 主要文字颜色 */
|
|
27
|
+
--text-color-primary: #151515;
|
|
28
|
+
/* 常规文字颜色 */
|
|
29
|
+
--text-color-regular: #2f2f2f;
|
|
30
|
+
/* 次要文字颜色 */
|
|
31
|
+
--text-color-secondary: #666;
|
|
32
|
+
/* 占位符颜色 */
|
|
33
|
+
--text-color-placeholder: #f9fbfc;
|
|
34
|
+
--text-color-disabled: #ced4da;
|
|
35
|
+
/* 白色文字颜色 */
|
|
36
|
+
--text-color-white: #fff;
|
|
37
|
+
/* 品牌色文字颜色 */
|
|
38
|
+
--text-color-brand: #674fde;
|
|
39
|
+
--text-color-warning: #ef8700;
|
|
40
|
+
--text-color-danger: #d20303;
|
|
41
|
+
--text-color-success: #22ac38;
|
|
42
|
+
--text-color-blue: #2d95ed;
|
|
43
|
+
|
|
44
|
+
/* 边框颜色 */
|
|
45
|
+
--border-color-primary: #dddddd;
|
|
46
|
+
--border-color-brand: #b8aafe;
|
|
47
|
+
|
|
48
|
+
--line-height-base: 1.5;
|
|
49
|
+
--line-height-sm: 1.4;
|
|
50
|
+
--line-height-lg: 1.6;
|
|
51
|
+
|
|
52
|
+
--font-size-large-4: 3.4rem;
|
|
53
|
+
--font-size-large-3: 2.4rem;
|
|
54
|
+
--font-size-large-2: 2rem;
|
|
55
|
+
--font-size-large-1: 1.8rem;
|
|
56
|
+
--font-size-base: 1.6rem;
|
|
57
|
+
--font-size-small-1: 1.4rem;
|
|
58
|
+
--font-size-small-2: 1.2rem;
|
|
59
|
+
|
|
60
|
+
--text-underline-offset: 0.2rem;
|
|
61
|
+
|
|
62
|
+
--font-family-regular: "Alibaba PuHuiTi";
|
|
63
|
+
--font-family-bold: "Alibaba-PuHuiTi-Medium";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* 暗黑模式变量 */
|
|
67
|
+
[data-theme="dark"] {
|
|
68
|
+
/* 状态颜色(暗黑模式下保持不变或微调) */
|
|
69
|
+
--color-brand: #080808;
|
|
70
|
+
--color-success: #22ac38;
|
|
71
|
+
--color-warning: #ef8700;
|
|
72
|
+
--color-danger: #ff4444;
|
|
73
|
+
--color-info: #909399;
|
|
74
|
+
|
|
75
|
+
/* 背景颜色(暗黑模式) */
|
|
76
|
+
--bg-color-page: #1a1a1a;
|
|
77
|
+
--bg-block-color-primary: #252525;
|
|
78
|
+
--bg-block-color-dark: #2d2d2d;
|
|
79
|
+
--bg-block-color-brand: #{color.adjust(#8b7df0, $lightness: -20%)};
|
|
80
|
+
--bg-td-1-color: #1e1e1e;
|
|
81
|
+
--bg-td-2-color: #252525;
|
|
82
|
+
--scrollbar-color: #4a4a4a;
|
|
83
|
+
|
|
84
|
+
/* 文字颜色(暗黑模式) */
|
|
85
|
+
--text-color-primary: #e5e5e5;
|
|
86
|
+
--text-color-regular: #d0d0d0;
|
|
87
|
+
--text-color-secondary: #a0a0a0;
|
|
88
|
+
--text-color-placeholder: #666666;
|
|
89
|
+
--text-color-disabled: #555555;
|
|
90
|
+
--text-color-white: #ffffff;
|
|
91
|
+
--text-color-brand: #8b7df0;
|
|
92
|
+
--text-color-warning: #ef8700;
|
|
93
|
+
--text-color-danger: #ff4444;
|
|
94
|
+
--text-color-success: #22ac38;
|
|
95
|
+
--text-color-blue: #4da6ff;
|
|
96
|
+
|
|
97
|
+
/* 边框颜色(暗黑模式) */
|
|
98
|
+
--border-color-primary: #404040;
|
|
99
|
+
--border-color-brand: #6b5dd9;
|
|
100
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="bar-chart">
|
|
3
|
+
<div
|
|
4
|
+
v-if="source.title"
|
|
5
|
+
class="chart-title"
|
|
6
|
+
>
|
|
7
|
+
{{ source.title }}
|
|
8
|
+
</div>
|
|
9
|
+
<div class="chart-body">
|
|
10
|
+
<EChart
|
|
11
|
+
:option="option"
|
|
12
|
+
theme="light"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
<div
|
|
16
|
+
class="legend"
|
|
17
|
+
:class="{
|
|
18
|
+
'legend-bottom-center': legendPosition === 'bottom-center',
|
|
19
|
+
'legend-top-right': legendPosition === 'top-right',
|
|
20
|
+
}"
|
|
21
|
+
>
|
|
22
|
+
<div
|
|
23
|
+
v-for="(s, idx) in source.series"
|
|
24
|
+
:key="`${s.name}-${idx}`"
|
|
25
|
+
class="legend-item"
|
|
26
|
+
>
|
|
27
|
+
<span
|
|
28
|
+
class="dot"
|
|
29
|
+
:style="{ backgroundColor: s.color }"
|
|
30
|
+
></span>
|
|
31
|
+
<span class="label">{{ s.name }}</span>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import type { EChartsOption } from "echarts";
|
|
39
|
+
import { computed } from "vue";
|
|
40
|
+
import { getResponsiveFontSize } from "@/utils/responsive";
|
|
41
|
+
import { clamp, useResizeTrigger, wrapLabel } from "@/utils/com-methods";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 图表数据源(原始数据层)。
|
|
45
|
+
* - title:图表标题
|
|
46
|
+
* - categories:x 轴类目
|
|
47
|
+
* - series[].values:与 categories 一一对应的百分比数值(0~100)
|
|
48
|
+
* - series[].name/color:展示元信息(图例名称、柱子颜色)
|
|
49
|
+
*/
|
|
50
|
+
type BarChartSource = {
|
|
51
|
+
title?: string;
|
|
52
|
+
categories: string[];
|
|
53
|
+
barWidth?: number;
|
|
54
|
+
series: Array<{ name: string; color: string; values: number[] }>;
|
|
55
|
+
legendPosition?: "top-right" | "bottom-center";
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 父组件传递的数据源
|
|
60
|
+
*/
|
|
61
|
+
const props = defineProps<{
|
|
62
|
+
barChartData: BarChartSource;
|
|
63
|
+
}>();
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 当前组件使用的数据源。
|
|
67
|
+
*/
|
|
68
|
+
const source = props.barChartData;
|
|
69
|
+
const legendPosition = computed(() =>
|
|
70
|
+
source.legendPosition === "bottom-center" ? "bottom-center" : "top-right"
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 图表渲染选项(EChartsOption):
|
|
75
|
+
* - 依赖 windowSize 来触发重算,以便响应式字号在 resize 后生效
|
|
76
|
+
* - 颜色/名称来自 source
|
|
77
|
+
* - 结构化结果来自 model(由 source 转换而来)
|
|
78
|
+
*/
|
|
79
|
+
const option = computed<EChartsOption>(() => {
|
|
80
|
+
void windowSize.value;
|
|
81
|
+
|
|
82
|
+
const model = buildChartModel(source);
|
|
83
|
+
const textColor = "#333";
|
|
84
|
+
const fontSize10 = getResponsiveFontSize(10);
|
|
85
|
+
const fontSize12 = getResponsiveFontSize(12);
|
|
86
|
+
const fontSize14 = getResponsiveFontSize(14);
|
|
87
|
+
const fontSize20 = getResponsiveFontSize(20);
|
|
88
|
+
const fontSize30 = getResponsiveFontSize(30);
|
|
89
|
+
const xAxisLabelMaxChars = 6;
|
|
90
|
+
const barMaxWidth =
|
|
91
|
+
typeof source.barWidth === "number" && source.barWidth > 0
|
|
92
|
+
? source.barWidth
|
|
93
|
+
: clamp(fontSize20, 30, 40);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
animation: false,
|
|
97
|
+
grid: { left: fontSize30, right: fontSize20, top: fontSize20, bottom: fontSize30 },
|
|
98
|
+
tooltip: {
|
|
99
|
+
trigger: "axis",
|
|
100
|
+
axisPointer: { type: "shadow" },
|
|
101
|
+
backgroundColor: "rgba(0,0,0,0.82)",
|
|
102
|
+
borderWidth: 0,
|
|
103
|
+
textStyle: { color: "#fff", fontSize: fontSize12 },
|
|
104
|
+
/**
|
|
105
|
+
* tooltip formatter:输入 params 为当前类目上的多个 series 数据点。
|
|
106
|
+
* 这里拼接 HTML 字符串展示类目与两组对比值。
|
|
107
|
+
*/
|
|
108
|
+
formatter: (params: any) => {
|
|
109
|
+
const list = Array.isArray(params) ? params : [];
|
|
110
|
+
const category = list?.[0]?.axisValue ?? "";
|
|
111
|
+
const lines = [category];
|
|
112
|
+
list.forEach((p: any) => {
|
|
113
|
+
const name = p.seriesName ?? "";
|
|
114
|
+
const v = p.value ?? 0;
|
|
115
|
+
lines.push(`${name}:${v}%`);
|
|
116
|
+
});
|
|
117
|
+
return lines.join("<br/>");
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
xAxis: {
|
|
121
|
+
type: "category",
|
|
122
|
+
data: source.categories,
|
|
123
|
+
axisLine: { lineStyle: { color: "rgba(0,0,0,0.2)" } },
|
|
124
|
+
axisTick: { show: false },
|
|
125
|
+
axisLabel: {
|
|
126
|
+
color: textColor,
|
|
127
|
+
fontSize: fontSize12,
|
|
128
|
+
interval: 0,
|
|
129
|
+
lineHeight: Math.round(fontSize12 * 1.6),
|
|
130
|
+
formatter: (value: string) => wrapLabel(value, xAxisLabelMaxChars, 2),
|
|
131
|
+
},
|
|
132
|
+
splitLine: { show: true, lineStyle: { color: "rgba(0,0,0,0.12)", type: "dashed" } },
|
|
133
|
+
},
|
|
134
|
+
yAxis: {
|
|
135
|
+
type: "value",
|
|
136
|
+
min: 0,
|
|
137
|
+
max: model.yMax,
|
|
138
|
+
axisLine: { show: false },
|
|
139
|
+
axisTick: { show: false },
|
|
140
|
+
axisLabel: {
|
|
141
|
+
color: textColor,
|
|
142
|
+
fontSize: fontSize12,
|
|
143
|
+
formatter: (v: number) => `${v}%`,
|
|
144
|
+
},
|
|
145
|
+
name: "百分比(%)",
|
|
146
|
+
nameTextStyle: { color: textColor, fontSize: fontSize14 },
|
|
147
|
+
splitLine: { show: true, lineStyle: { color: "rgba(0,0,0,0.12)", type: "dashed" } },
|
|
148
|
+
},
|
|
149
|
+
series: source.series.map((s) => ({
|
|
150
|
+
name: s.name,
|
|
151
|
+
type: "bar",
|
|
152
|
+
data: s.values,
|
|
153
|
+
barMaxWidth,
|
|
154
|
+
itemStyle: { color: s.color },
|
|
155
|
+
label: {
|
|
156
|
+
show: true,
|
|
157
|
+
position: "top",
|
|
158
|
+
color: textColor,
|
|
159
|
+
fontSize: fontSize10,
|
|
160
|
+
formatter: (p: any) => `${p.value}`,
|
|
161
|
+
},
|
|
162
|
+
})),
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const { windowSize } = useResizeTrigger();
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 将 Source 转换为图表 Model(只做数据整形与派生值计算,不关心 UI 布局)。
|
|
170
|
+
* 输出内容:
|
|
171
|
+
* - yMax:y 轴上限(至少为 100,并向上取整到 10 的整数倍)
|
|
172
|
+
*/
|
|
173
|
+
function buildChartModel(data: BarChartSource) {
|
|
174
|
+
const values = data.series.flatMap((s) => s.values);
|
|
175
|
+
const maxValue = values.length ? Math.max(...values) : 0;
|
|
176
|
+
const yMax = Math.max(100, Math.ceil(maxValue / 10) * 10);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
yMax,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
</script>
|
|
183
|
+
|
|
184
|
+
<style scoped lang="scss">
|
|
185
|
+
.bar-chart {
|
|
186
|
+
position: relative;
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
background: #fff;
|
|
190
|
+
height: 100%;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.chart-title {
|
|
194
|
+
font-size: 1.4rem;
|
|
195
|
+
line-height: 1;
|
|
196
|
+
text-align: center;
|
|
197
|
+
padding-top: 2rem;
|
|
198
|
+
color: #333;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.chart-body {
|
|
202
|
+
flex: 1;
|
|
203
|
+
min-height: 0;
|
|
204
|
+
padding: 2rem;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.legend {
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
gap: 2rem;
|
|
211
|
+
|
|
212
|
+
.legend-item {
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: center;
|
|
215
|
+
gap: 0.5rem;
|
|
216
|
+
color: #333;
|
|
217
|
+
font-size: 1.2rem;
|
|
218
|
+
line-height: 1;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.dot {
|
|
222
|
+
width: 1.2rem;
|
|
223
|
+
height: 1.2rem;
|
|
224
|
+
border-radius: 2px;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.legend-top-right {
|
|
229
|
+
position: absolute;
|
|
230
|
+
right: 4rem;
|
|
231
|
+
top: 5.5rem;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.legend-bottom-center {
|
|
235
|
+
justify-content: center;
|
|
236
|
+
padding-bottom: 2rem;
|
|
237
|
+
}
|
|
238
|
+
</style>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="chartRef"
|
|
4
|
+
class="echart-container"
|
|
5
|
+
></div>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { ref, onMounted, onUnmounted, watch, nextTick } from "vue";
|
|
10
|
+
import type { EChartsOption } from "echarts";
|
|
11
|
+
import type { ECharts, ECElementEvent } from "echarts/core";
|
|
12
|
+
import { echarts } from "@/utils/echarts";
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(
|
|
15
|
+
defineProps<{
|
|
16
|
+
option: EChartsOption;
|
|
17
|
+
theme?: string;
|
|
18
|
+
autoResize?: boolean;
|
|
19
|
+
autoRefresh?: boolean;
|
|
20
|
+
}>(),
|
|
21
|
+
{
|
|
22
|
+
option: () => ({}),
|
|
23
|
+
theme: "",
|
|
24
|
+
autoResize: true,
|
|
25
|
+
autoRefresh: true,
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const emit = defineEmits<{
|
|
30
|
+
(e: "chart-click", params: ECElementEvent): void;
|
|
31
|
+
(e: "zr-click", params: { offsetX: number; offsetY: number }, instance: ECharts): void;
|
|
32
|
+
}>();
|
|
33
|
+
|
|
34
|
+
const chartRef = ref<HTMLElement>();
|
|
35
|
+
const isInitialized = ref(false); // 添加初始化标记
|
|
36
|
+
|
|
37
|
+
let chartInstance: ECharts | null = null;
|
|
38
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
39
|
+
|
|
40
|
+
const initChart = () => {
|
|
41
|
+
if (!chartRef.value) return;
|
|
42
|
+
|
|
43
|
+
chartInstance = echarts.init(chartRef.value, props.theme);
|
|
44
|
+
|
|
45
|
+
chartInstance.on("click", (params: ECElementEvent) => {
|
|
46
|
+
emit("chart-click", params);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// 监听 ZRender 的点击事件(覆盖整个画布)
|
|
50
|
+
chartInstance.getZr().on("click", (params: any) => {
|
|
51
|
+
if (chartInstance) {
|
|
52
|
+
emit("zr-click", params, chartInstance);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
if (chartInstance) {
|
|
58
|
+
chartInstance.setOption(props.option);
|
|
59
|
+
isInitialized.value = true; // 标记已初始化
|
|
60
|
+
}
|
|
61
|
+
}, 100);
|
|
62
|
+
|
|
63
|
+
if (props.autoResize) {
|
|
64
|
+
resizeObserver = new ResizeObserver(() => {
|
|
65
|
+
chartInstance?.resize();
|
|
66
|
+
});
|
|
67
|
+
resizeObserver.observe(chartRef.value);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const updateChart = () => {
|
|
72
|
+
if (chartInstance) {
|
|
73
|
+
chartInstance.clear();
|
|
74
|
+
chartInstance.setOption(props.option);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
watch(
|
|
79
|
+
() => props.option,
|
|
80
|
+
() => {
|
|
81
|
+
// 只有在已经初始化后才响应 watch
|
|
82
|
+
if (!isInitialized.value) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
nextTick(() => {
|
|
87
|
+
updateChart();
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
{ deep: true }
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// TODO 新增自动刷新机制,为了演示。后续需修改为父级先获取最新的数据,并且给此组件赋值最新数据,来触发刷新
|
|
94
|
+
let refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
95
|
+
const startAutoRefresh = () => {
|
|
96
|
+
refreshTimer = setInterval(() => {
|
|
97
|
+
updateChart();
|
|
98
|
+
}, 60 * 1000);
|
|
99
|
+
};
|
|
100
|
+
const stopAutoRefresh = () => {
|
|
101
|
+
if (refreshTimer) {
|
|
102
|
+
clearInterval(refreshTimer);
|
|
103
|
+
refreshTimer = null;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
onMounted(() => {
|
|
108
|
+
nextTick(() => {
|
|
109
|
+
initChart();
|
|
110
|
+
if (props.autoRefresh) {
|
|
111
|
+
startAutoRefresh();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
onUnmounted(() => {
|
|
117
|
+
stopAutoRefresh();
|
|
118
|
+
if (resizeObserver) {
|
|
119
|
+
resizeObserver.disconnect();
|
|
120
|
+
}
|
|
121
|
+
if (chartInstance) {
|
|
122
|
+
chartInstance.dispose();
|
|
123
|
+
chartInstance = null;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
defineExpose({
|
|
128
|
+
getInstance: () => chartInstance,
|
|
129
|
+
resize: () => chartInstance?.resize(),
|
|
130
|
+
clear: () => chartInstance?.clear(),
|
|
131
|
+
updateChart,
|
|
132
|
+
});
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<style lang="scss" scoped>
|
|
136
|
+
.echart-container {
|
|
137
|
+
width: 100%;
|
|
138
|
+
height: 100%;
|
|
139
|
+
}
|
|
140
|
+
</style>
|