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.
Files changed (104) hide show
  1. package/README.md +121 -0
  2. package/bin/create-vue-template.js +565 -0
  3. package/package.json +28 -0
  4. package/templates/vue-project/.browserslistrc +3 -0
  5. package/templates/vue-project/.editorconfig +28 -0
  6. package/templates/vue-project/.env.development +2 -0
  7. package/templates/vue-project/.env.production +2 -0
  8. package/templates/vue-project/.eslintrc.cjs +76 -0
  9. package/templates/vue-project/.keep +0 -0
  10. package/templates/vue-project/.node-version +1 -0
  11. package/templates/vue-project/.prettierignore +13 -0
  12. package/templates/vue-project/.prettierrc +20 -0
  13. package/templates/vue-project/.prettierrc.txt +130 -0
  14. package/templates/vue-project/.stylelintrc.json +94 -0
  15. package/templates/vue-project/README.md +24 -0
  16. package/templates/vue-project/babel.config.js +5 -0
  17. package/templates/vue-project/index.html +34 -0
  18. package/templates/vue-project/package.json +75 -0
  19. package/templates/vue-project/public/favicon.ico +0 -0
  20. package/templates/vue-project/public/static/img/ai-default.jpg +0 -0
  21. package/templates/vue-project/public/static/img/image.png +0 -0
  22. package/templates/vue-project/public/static/img/ppt1.png +0 -0
  23. package/templates/vue-project/public/static/img/ppt2.png +0 -0
  24. package/templates/vue-project/public/static/img/ppt3.png +0 -0
  25. package/templates/vue-project/public/static/js/config.js +11 -0
  26. package/templates/vue-project/public/static/js/dataConfig.js +1143 -0
  27. package/templates/vue-project/src/App.vue +10 -0
  28. package/templates/vue-project/src/api/error-handler.ts +60 -0
  29. package/templates/vue-project/src/api/http.ts +254 -0
  30. package/templates/vue-project/src/api/services/aicebd.ts +47 -0
  31. package/templates/vue-project/src/api/services/base.ts +18 -0
  32. package/templates/vue-project/src/api/services/umse.ts +17 -0
  33. package/templates/vue-project/src/assets/font/Alibaba-PuHuiTi-Medium.otf +0 -0
  34. package/templates/vue-project/src/assets/font/Alibaba-PuHuiTi-Regular.otf +0 -0
  35. package/templates/vue-project/src/assets/font/DOUYINSANSBOLD.OTF +0 -0
  36. package/templates/vue-project/src/assets/font/Pangmen-Title.TTF +0 -0
  37. package/templates/vue-project/src/assets/font/font.css +25 -0
  38. package/templates/vue-project/src/assets/iconfont/iconfont.css +402 -0
  39. package/templates/vue-project/src/assets/iconfont/iconfont.js +66 -0
  40. package/templates/vue-project/src/assets/iconfont/iconfont.json +688 -0
  41. package/templates/vue-project/src/assets/iconfont/iconfont.ttf +0 -0
  42. package/templates/vue-project/src/assets/iconfont/iconfont.woff +0 -0
  43. package/templates/vue-project/src/assets/iconfont/iconfont.woff2 +0 -0
  44. package/templates/vue-project/src/assets/images/Click-tap.png +0 -0
  45. package/templates/vue-project/src/assets/images/Effects.png +0 -0
  46. package/templates/vue-project/src/assets/images/bg.png +0 -0
  47. package/templates/vue-project/src/assets/images/erCode.png +0 -0
  48. package/templates/vue-project/src/assets/images/header-bg.png +0 -0
  49. package/templates/vue-project/src/assets/images/logo.png +0 -0
  50. package/templates/vue-project/src/assets/scss/common.scss +530 -0
  51. package/templates/vue-project/src/assets/styles/element-overrides.css +53 -0
  52. package/templates/vue-project/src/assets/styles/reset.css +186 -0
  53. package/templates/vue-project/src/assets/styles/theme.css +100 -0
  54. package/templates/vue-project/src/components/BarChart.vue +238 -0
  55. package/templates/vue-project/src/components/echarts/EChart.vue +140 -0
  56. package/templates/vue-project/src/composables/useTheme.ts +84 -0
  57. package/templates/vue-project/src/main.ts +111 -0
  58. package/templates/vue-project/src/mocks/base.ts +37 -0
  59. package/templates/vue-project/src/mocks/umse.ts +31 -0
  60. package/templates/vue-project/src/router/index.ts +32 -0
  61. package/templates/vue-project/src/shims-vue.d.ts +19 -0
  62. package/templates/vue-project/src/store/index.ts +18 -0
  63. package/templates/vue-project/src/store/modules/user.ts +85 -0
  64. package/templates/vue-project/src/types/DTO/aicebd.d.ts +60 -0
  65. package/templates/vue-project/src/types/DTO/base.d.ts +26 -0
  66. package/templates/vue-project/src/types/DTO/global.d.ts +48 -0
  67. package/templates/vue-project/src/types/VO/teachingLog.d.ts +15 -0
  68. package/templates/vue-project/src/types/auto-imports.d.ts +73 -0
  69. package/templates/vue-project/src/types/components.d.ts +17 -0
  70. package/templates/vue-project/src/types/element-plus.d.ts +15 -0
  71. package/templates/vue-project/src/types/js-cookie.d.ts +1 -0
  72. package/templates/vue-project/src/types/unocss.d.ts +2 -0
  73. package/templates/vue-project/src/types/vite-plugins.d.ts +3 -0
  74. package/templates/vue-project/src/types/vue-router.d.ts +1 -0
  75. package/templates/vue-project/src/types/window-config.d.ts +12 -0
  76. package/templates/vue-project/src/utils/com-methods.ts +307 -0
  77. package/templates/vue-project/src/utils/echarts.ts +111 -0
  78. package/templates/vue-project/src/utils/number.ts +99 -0
  79. package/templates/vue-project/src/utils/rem.ts +82 -0
  80. package/templates/vue-project/src/utils/responsive.ts +103 -0
  81. package/templates/vue-project/src/utils/time.ts +314 -0
  82. package/templates/vue-project/src/utils/tracker.ts +527 -0
  83. package/templates/vue-project/src/utils/validators.ts +85 -0
  84. package/templates/vue-project/src/utils/window.ts +132 -0
  85. package/templates/vue-project/src/views/home/Home.vue +60 -0
  86. package/templates/vue-project/src/views/home/composables/useUserAuth.ts +13 -0
  87. package/templates/vue-project/src/views/teachingLog/TeachingLog.vue +40 -0
  88. package/templates/vue-project/src/views/teachingLog/__tests__/TeachingEffect.test.ts +96 -0
  89. package/templates/vue-project/src/views/teachingLog/__tests__/TeachingHighlight.test.ts +66 -0
  90. package/templates/vue-project/src/views/teachingLog/__tests__/TeachingLog.test.ts +34 -0
  91. package/templates/vue-project/src/views/teachingLog/components/TeachingEffect.vue +458 -0
  92. package/templates/vue-project/src/views/teachingLog/components/TeachingHighlight.vue +181 -0
  93. package/templates/vue-project/src/views/teachingLog/composables/useEffectTooltip.ts +88 -0
  94. package/templates/vue-project/src/views/teachingLog/composables/useEffectTrendChart.ts +160 -0
  95. package/templates/vue-project/tests/setup.ts +27 -0
  96. package/templates/vue-project/tsconfig.json +24 -0
  97. package/templates/vue-project/tsconfig.node.json +41 -0
  98. package/templates/vue-project/uno.config.ts +84 -0
  99. package/templates/vue-project/vite.config.ts +216 -0
  100. package/templates/vue-project/vue3_ai_prompt.md +652 -0
  101. package/templates/vue-project/vue3_ai_prompt_basic.md +722 -0
  102. package/templates/vue-project/vue3_ai_prompt_full.md +1021 -0
  103. package/templates/vue-project/vue3_ai_prompt_unocss.md +768 -0
  104. 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>