zant-admin 1.0.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/README.en.md +36 -0
- package/README.md +248 -0
- package/SCAFFOLD_README.md +215 -0
- package/bin/cli.js +99 -0
- package/bin/generator.js +503 -0
- package/bin/prompts.js +159 -0
- package/bin/utils.js +134 -0
- package/package.json +74 -0
- package/public/logo.png +0 -0
- package/src/App.vue +16 -0
- package/src/api/methods/logError.js +8 -0
- package/src/api/methods/logOperation.js +8 -0
- package/src/api/methods/login.js +6 -0
- package/src/api/methods/quartz.js +36 -0
- package/src/api/methods/region.js +16 -0
- package/src/api/methods/sysAccount.js +30 -0
- package/src/api/methods/sysDict.js +29 -0
- package/src/api/methods/sysDictItem.js +26 -0
- package/src/api/methods/sysMenu.js +42 -0
- package/src/api/methods/sysRole.js +35 -0
- package/src/api/methods/sysUser.js +25 -0
- package/src/api/methods/system.js +16 -0
- package/src/api/request.js +225 -0
- package/src/assets/css/style.css +70 -0
- package/src/assets/css/zcui.css +340 -0
- package/src/assets/imgs/loginbackground.svg +69 -0
- package/src/assets/imgs/logo.png +0 -0
- package/src/assets/imgs/md/1.png +0 -0
- package/src/assets/imgs/md/10.png +0 -0
- package/src/assets/imgs/md/11.png +0 -0
- package/src/assets/imgs/md/2.png +0 -0
- package/src/assets/imgs/md/3.png +0 -0
- package/src/assets/imgs/md/4.png +0 -0
- package/src/assets/imgs/md/5.png +0 -0
- package/src/assets/imgs/md/6.png +0 -0
- package/src/assets/imgs/md/7.png +0 -0
- package/src/assets/imgs/md/8.png +0 -0
- package/src/assets/imgs/md/9.png +0 -0
- package/src/components/FormTable.vue +875 -0
- package/src/components/IconPicker.vue +344 -0
- package/src/components/MainPage.vue +957 -0
- package/src/components/details/logErrorDetails.vue +58 -0
- package/src/components/details/logOperationDetails.vue +76 -0
- package/src/components/edit/QuartzEdit.vue +221 -0
- package/src/components/edit/SysAccountEdit.vue +178 -0
- package/src/components/edit/SysDictEdit.vue +114 -0
- package/src/components/edit/SysDictItemEdit.vue +134 -0
- package/src/components/edit/SysRoleEdit.vue +109 -0
- package/src/components/edit/sysMenuEdit.vue +305 -0
- package/src/config/index.js +74 -0
- package/src/directives/permission.js +45 -0
- package/src/main.js +38 -0
- package/src/router/index.js +270 -0
- package/src/stores/config.js +37 -0
- package/src/stores/dict.js +33 -0
- package/src/stores/menu.js +57 -0
- package/src/stores/user.js +21 -0
- package/src/utils/baseEcharts.js +661 -0
- package/src/utils/dictTemplate.js +26 -0
- package/src/utils/regionUtils.js +169 -0
- package/src/utils/useFormCRUD.js +60 -0
- package/src/views/baiscstatis/center.vue +463 -0
- package/src/views/baiscstatis/iframePage.vue +31 -0
- package/src/views/baiscstatis/notFound.vue +192 -0
- package/src/views/console.vue +771 -0
- package/src/views/demo/importexport.vue +123 -0
- package/src/views/demo/region.vue +240 -0
- package/src/views/demo/statistics.vue +195 -0
- package/src/views/home.vue +7 -0
- package/src/views/login.vue +272 -0
- package/src/views/operations/log/logError.vue +78 -0
- package/src/views/operations/log/logLogin.vue +66 -0
- package/src/views/operations/log/logOperation.vue +103 -0
- package/src/views/operations/log/logQuartz.vue +57 -0
- package/src/views/operations/quartz.vue +181 -0
- package/src/views/operations/serviceMonitoring.vue +134 -0
- package/src/views/system/sysAccount.vue +123 -0
- package/src/views/system/sysDict.vue +156 -0
- package/src/views/system/sysDictItem.vue +118 -0
- package/src/views/system/sysMenu.vue +223 -0
- package/src/views/system/sysRole.vue +184 -0
- package/templates/env.production +2 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<form-table
|
|
3
|
+
:formState="formState"
|
|
4
|
+
:columns="columns"
|
|
5
|
+
modulePath="Importexport"
|
|
6
|
+
:permissionModulePath="permissionModulePath"
|
|
7
|
+
ref="childRef"
|
|
8
|
+
>
|
|
9
|
+
<!-- 可以通过插槽自定义单元格 -->
|
|
10
|
+
</form-table>
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
</template>
|
|
14
|
+
<script setup>
|
|
15
|
+
import { ref, inject } from 'vue'
|
|
16
|
+
import FormTable from '@/components/FormTable.vue'
|
|
17
|
+
import sysRole from '@/api/methods/sysRole'
|
|
18
|
+
import { message } from 'ant-design-vue'
|
|
19
|
+
import { menuStore } from '@/stores/menu'
|
|
20
|
+
import sysMenu from '@/api/methods/sysMenu'
|
|
21
|
+
import { refreshRoutes } from '@/router'
|
|
22
|
+
// 注入父组件的方法
|
|
23
|
+
const menuinit = inject('menuinit')
|
|
24
|
+
// 权限模块路径
|
|
25
|
+
const permissionModulePath = ref('demo:importexport')
|
|
26
|
+
|
|
27
|
+
const formState = ref({
|
|
28
|
+
name: { label: '角色名称', value: '',defaultvalue:'', type: 'text' },
|
|
29
|
+
})
|
|
30
|
+
const columns = ref([
|
|
31
|
+
{
|
|
32
|
+
title: '角色名称',
|
|
33
|
+
dataIndex: 'name',
|
|
34
|
+
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
title: '备注',
|
|
38
|
+
dataIndex: 'remark',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: '是否启用',
|
|
42
|
+
dataIndex: 'isEnable',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
title: '创建时间',
|
|
46
|
+
dataIndex: 'createTime',
|
|
47
|
+
}
|
|
48
|
+
])
|
|
49
|
+
const childRef = ref(null)
|
|
50
|
+
|
|
51
|
+
//编辑
|
|
52
|
+
const editopen = ref(false)
|
|
53
|
+
const editRef = ref(null)
|
|
54
|
+
const edit = record => {
|
|
55
|
+
editRef.value.init(record.id)
|
|
56
|
+
editopen.value = true
|
|
57
|
+
}
|
|
58
|
+
const refreshData = () => {
|
|
59
|
+
childRef.value.tableLoad()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//修改是否启用
|
|
63
|
+
const handleSwitchChange = record => {
|
|
64
|
+
var data = {
|
|
65
|
+
id: record.id,
|
|
66
|
+
isEnabled: record.isEnable,
|
|
67
|
+
}
|
|
68
|
+
sysRole.updateIsEnabled(data).then(() => {
|
|
69
|
+
childRef.value.tableLoad()
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const empoweropen = ref(false)
|
|
74
|
+
const roleMenutreeData = ref([])
|
|
75
|
+
const halfCheckedKeys = ref([])
|
|
76
|
+
const checkedKeys = ref([])
|
|
77
|
+
const recordId = ref(0)
|
|
78
|
+
//关联菜单
|
|
79
|
+
const empower = record => {
|
|
80
|
+
recordId.value = record.id
|
|
81
|
+
sysRole.getRoleMenu({ roleId: record.id }).then(res => {
|
|
82
|
+
roleMenutreeData.value = res.data.output
|
|
83
|
+
checkedKeys.value = res.data.checkedKeys
|
|
84
|
+
empoweropen.value = true
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//处理Tree组件的check事件,获取半选状态的父节点
|
|
89
|
+
const onTreeCheck = (checkedKeysValue, e) => {
|
|
90
|
+
checkedKeys.value = checkedKeysValue
|
|
91
|
+
halfCheckedKeys.value = e.halfCheckedKeys || []
|
|
92
|
+
}
|
|
93
|
+
//关联菜单保存
|
|
94
|
+
const empowerSave = () => {
|
|
95
|
+
var data = {
|
|
96
|
+
Id: recordId.value,
|
|
97
|
+
Menus:checkedKeys.value.checked,
|
|
98
|
+
}
|
|
99
|
+
sysRole.savePower(data).then(() => {
|
|
100
|
+
const menu = menuStore()
|
|
101
|
+
sysMenu
|
|
102
|
+
.getRoutesMenu()
|
|
103
|
+
.then(res => {
|
|
104
|
+
menu.menus = res.data
|
|
105
|
+
message.success('成功', 1, () => {
|
|
106
|
+
if (menuinit) {
|
|
107
|
+
refreshRoutes().then(() => {
|
|
108
|
+
// 重新加载菜单树(如果你有菜单组件)
|
|
109
|
+
menuinit()
|
|
110
|
+
// 跳转到新菜单页面,或者刷新当前页
|
|
111
|
+
// router.replace(router.currentRoute.value.fullPath)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
empoweropen.value = false
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
.catch()
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<style></style>
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="region-container">
|
|
3
|
+
<a-card title="省市区联动选择" class="region-card">
|
|
4
|
+
<div class="region-selector">
|
|
5
|
+
<a-form layout="inline">
|
|
6
|
+
<a-form-item label="省份">
|
|
7
|
+
<a-select
|
|
8
|
+
v-model:value="selectedProvince"
|
|
9
|
+
placeholder="请选择省份"
|
|
10
|
+
style="width: 150px"
|
|
11
|
+
@change="handleProvinceChange"
|
|
12
|
+
:options="provinceOptions"
|
|
13
|
+
:filter-option="filterOption"
|
|
14
|
+
show-search
|
|
15
|
+
/>
|
|
16
|
+
</a-form-item>
|
|
17
|
+
<a-form-item label="城市">
|
|
18
|
+
<a-select
|
|
19
|
+
v-model:value="selectedCity"
|
|
20
|
+
placeholder="请选择城市"
|
|
21
|
+
style="width: 150px"
|
|
22
|
+
@change="handleCityChange"
|
|
23
|
+
:options="cityOptions"
|
|
24
|
+
:disabled="!selectedProvince"
|
|
25
|
+
:filter-option="filterOption"
|
|
26
|
+
show-search
|
|
27
|
+
/>
|
|
28
|
+
</a-form-item>
|
|
29
|
+
<a-form-item label="区县">
|
|
30
|
+
<a-select
|
|
31
|
+
v-model:value="selectedArea"
|
|
32
|
+
placeholder="请选择区县"
|
|
33
|
+
style="width: 150px"
|
|
34
|
+
@change="handleAreaChange"
|
|
35
|
+
:options="areaOptions"
|
|
36
|
+
:disabled="!selectedCity"
|
|
37
|
+
:filter-option="filterOption"
|
|
38
|
+
show-search
|
|
39
|
+
/>
|
|
40
|
+
</a-form-item>
|
|
41
|
+
</a-form>
|
|
42
|
+
<div class="selected-result" v-if="selectedAddress">
|
|
43
|
+
<a-alert :message="selectedAddress" type="success" show-icon />
|
|
44
|
+
<div class="code-display" v-if="selectedProvince || selectedCity || selectedArea">
|
|
45
|
+
<a-descriptions title="区域代码" :column="3" size="small">
|
|
46
|
+
<a-descriptions-item label="省份代码">{{ selectedProvince || '-' }}</a-descriptions-item>
|
|
47
|
+
<a-descriptions-item label="城市代码">{{ selectedCity || '-' }}</a-descriptions-item>
|
|
48
|
+
<a-descriptions-item label="区县代码">{{ selectedArea || '-' }}</a-descriptions-item>
|
|
49
|
+
</a-descriptions>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</a-card>
|
|
54
|
+
|
|
55
|
+
<a-card title="省市区组合选择" class="region-card">
|
|
56
|
+
<div class="region-cascader">
|
|
57
|
+
<a-form layout="inline">
|
|
58
|
+
<a-form-item label="地区选择">
|
|
59
|
+
<a-cascader
|
|
60
|
+
v-model:value="cascaderValue"
|
|
61
|
+
:options="cascaderOptions"
|
|
62
|
+
placeholder="请选择地区"
|
|
63
|
+
style="width: 300px"
|
|
64
|
+
@change="handleCascaderChange"
|
|
65
|
+
:filter-option="filterCascaderOption"
|
|
66
|
+
show-search
|
|
67
|
+
/>
|
|
68
|
+
</a-form-item>
|
|
69
|
+
</a-form>
|
|
70
|
+
<div class="cascader-result" v-if="cascaderAddress">
|
|
71
|
+
<a-alert :message="cascaderAddress" type="info" show-icon />
|
|
72
|
+
<div class="code-display" v-if="cascaderValue && cascaderValue.length > 0">
|
|
73
|
+
<a-descriptions title="区域代码" :column="3" size="small">
|
|
74
|
+
<a-descriptions-item label="省份代码">{{ cascaderValue[0] || '-' }}</a-descriptions-item>
|
|
75
|
+
<a-descriptions-item label="城市代码">{{ cascaderValue[1] || '-' }}</a-descriptions-item>
|
|
76
|
+
<a-descriptions-item label="区县代码">{{ cascaderValue[2] || '-' }}</a-descriptions-item>
|
|
77
|
+
</a-descriptions>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</a-card>
|
|
82
|
+
</div>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<script setup>
|
|
86
|
+
import { ref, onMounted, computed } from 'vue'
|
|
87
|
+
import regionUtils from '@/utils/regionUtils'
|
|
88
|
+
|
|
89
|
+
// 省市区联动选择相关数据
|
|
90
|
+
const selectedProvince = ref('')
|
|
91
|
+
const selectedCity = ref('')
|
|
92
|
+
const selectedArea = ref('')
|
|
93
|
+
const provinceOptions = ref([])
|
|
94
|
+
const cityOptions = ref([])
|
|
95
|
+
const areaOptions = ref([])
|
|
96
|
+
|
|
97
|
+
// 组合框相关数据
|
|
98
|
+
const cascaderValue = ref([])
|
|
99
|
+
const cascaderOptions = ref([])
|
|
100
|
+
|
|
101
|
+
// 计算属性:获取选中的完整地址
|
|
102
|
+
const selectedAddress = computed(() => {
|
|
103
|
+
return regionUtils.getFullAddressName(selectedProvince.value, selectedCity.value, selectedArea.value)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// 计算属性:获取组合框选中的完整地址
|
|
107
|
+
const cascaderAddress = computed(() => {
|
|
108
|
+
if (!cascaderValue.value || cascaderValue.value.length === 0) return ''
|
|
109
|
+
const [provinceCode, cityCode, areaCode] = cascaderValue.value
|
|
110
|
+
return regionUtils.getFullAddressName(provinceCode, cityCode, areaCode)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 处理省份选择变化
|
|
115
|
+
* @param {string} value - 选中的省份代码
|
|
116
|
+
*/
|
|
117
|
+
const handleProvinceChange = (value) => {
|
|
118
|
+
// 重置城市和区县选择
|
|
119
|
+
selectedCity.value = ''
|
|
120
|
+
selectedArea.value = ''
|
|
121
|
+
|
|
122
|
+
// 获取该省份下的城市列表
|
|
123
|
+
cityOptions.value = regionUtils.getCitiesByProvinceCode(value)
|
|
124
|
+
areaOptions.value = []
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 处理城市选择变化
|
|
129
|
+
* @param {string} value - 选中的城市代码
|
|
130
|
+
*/
|
|
131
|
+
const handleCityChange = (value) => {
|
|
132
|
+
// 重置区县选择
|
|
133
|
+
selectedArea.value = ''
|
|
134
|
+
|
|
135
|
+
// 获取该城市下的区县列表
|
|
136
|
+
areaOptions.value = regionUtils.getAreasByCityCode(value)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 处理区县选择变化
|
|
141
|
+
* @param {string} value - 选中的区县代码
|
|
142
|
+
*/
|
|
143
|
+
const handleAreaChange = (value) => {
|
|
144
|
+
// 这里可以添加区县选择后的处理逻辑
|
|
145
|
+
console.log('选中的区县代码:', value)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 处理组合框选择变化
|
|
150
|
+
* @param {Array} value - 选中的值数组 [省份代码, 城市代码, 区县代码]
|
|
151
|
+
*/
|
|
152
|
+
const handleCascaderChange = (value) => {
|
|
153
|
+
console.log('组合框选中的值:', value)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 搜索过滤函数
|
|
158
|
+
* @param {string} input - 输入的搜索关键词
|
|
159
|
+
* @param {Object} option - 选项对象
|
|
160
|
+
* @returns {boolean} 是否匹配
|
|
161
|
+
*/
|
|
162
|
+
const filterOption = (input, option) => {
|
|
163
|
+
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 级联选择器搜索过滤函数
|
|
168
|
+
* @param {string} inputValue - 输入的搜索关键词
|
|
169
|
+
* @param {Object} pathNode - 路径节点
|
|
170
|
+
* @returns {boolean} 是否匹配
|
|
171
|
+
*/
|
|
172
|
+
const filterCascaderOption = (inputValue, pathNode) => {
|
|
173
|
+
return pathNode.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) >= 0)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 构建级联选择器的选项数据
|
|
178
|
+
* @param {Array} provinces - 省份数据
|
|
179
|
+
* @returns {Array} 级联选择器选项数据
|
|
180
|
+
*/
|
|
181
|
+
const buildCascaderOptions = (provinces) => {
|
|
182
|
+
return provinces.map(province => {
|
|
183
|
+
// 获取省份下的所有城市
|
|
184
|
+
const cities = regionUtils.getCitiesByProvinceCode(province.value)
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
label: province.label,
|
|
188
|
+
value: province.value,
|
|
189
|
+
children: cities.map(city => {
|
|
190
|
+
// 获取城市下的所有区县
|
|
191
|
+
const areas = regionUtils.getAreasByCityCode(city.value)
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
label: city.label,
|
|
195
|
+
value: city.value,
|
|
196
|
+
children: areas.length > 0 ? areas : undefined
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 组件挂载时初始化数据
|
|
204
|
+
onMounted(async () => {
|
|
205
|
+
try {
|
|
206
|
+
// 获取所有省市区数据
|
|
207
|
+
await regionUtils.fetchAllAddressData()
|
|
208
|
+
|
|
209
|
+
// 设置省份数据
|
|
210
|
+
provinceOptions.value = regionUtils.getAllProvinces()
|
|
211
|
+
|
|
212
|
+
// 构建级联选择器数据
|
|
213
|
+
cascaderOptions.value = buildCascaderOptions(provinceOptions.value)
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error('初始化省市区数据失败:', error)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
</script>
|
|
219
|
+
|
|
220
|
+
<style scoped>
|
|
221
|
+
|
|
222
|
+
.region-card {
|
|
223
|
+
margin-bottom: 20px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.region-selector, .region-cascader {
|
|
227
|
+
padding: 20px 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.selected-result, .cascader-result {
|
|
231
|
+
margin-top: 20px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.code-display {
|
|
235
|
+
margin-top: 15px;
|
|
236
|
+
padding: 10px;
|
|
237
|
+
background-color: #f5f5f5;
|
|
238
|
+
border-radius: 6px;
|
|
239
|
+
}
|
|
240
|
+
</style>
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="statistics-container">
|
|
3
|
+
<a-row :gutter="16">
|
|
4
|
+
<!-- 柱状图 -->
|
|
5
|
+
<a-col :span="12">
|
|
6
|
+
<a-card title="销售数据统计" class="chart-card">
|
|
7
|
+
<div ref="barChartRef" class="chart-container"></div>
|
|
8
|
+
</a-card>
|
|
9
|
+
</a-col>
|
|
10
|
+
|
|
11
|
+
<!-- 饼图 -->
|
|
12
|
+
<a-col :span="12">
|
|
13
|
+
<a-card title="产品占比分析" class="chart-card">
|
|
14
|
+
<div ref="pieChartRef" class="chart-container"></div>
|
|
15
|
+
</a-card>
|
|
16
|
+
</a-col>
|
|
17
|
+
</a-row>
|
|
18
|
+
|
|
19
|
+
<a-row :gutter="16" style="margin-top: 16px;">
|
|
20
|
+
<!-- 折线图 -->
|
|
21
|
+
<a-col :span="24">
|
|
22
|
+
<a-card title="月度趋势分析" class="chart-card">
|
|
23
|
+
<div ref="lineChartRef" class="chart-container"></div>
|
|
24
|
+
</a-card>
|
|
25
|
+
</a-col>
|
|
26
|
+
</a-row>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup>
|
|
31
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
32
|
+
import { initBarChart, updateBarChart, initPieChart, updatePieChart, initLineChart, updateLineChart } from '@/utils/baseEcharts'
|
|
33
|
+
|
|
34
|
+
// 图表DOM引用
|
|
35
|
+
const barChartRef = ref(null)
|
|
36
|
+
const pieChartRef = ref(null)
|
|
37
|
+
const lineChartRef = ref(null)
|
|
38
|
+
|
|
39
|
+
// 图表实例
|
|
40
|
+
let barChartInstance = null
|
|
41
|
+
let pieChartInstance = null
|
|
42
|
+
let lineChartInstance = null
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 初始化所有图表
|
|
46
|
+
*/
|
|
47
|
+
const initCharts = () => {
|
|
48
|
+
// 初始化柱状图
|
|
49
|
+
if (barChartRef.value) {
|
|
50
|
+
barChartInstance = initBarChart(barChartRef.value, '销售额(万元)')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 初始化饼图
|
|
54
|
+
if (pieChartRef.value) {
|
|
55
|
+
pieChartInstance = initPieChart(pieChartRef.value, '产品占比')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 初始化折线图
|
|
59
|
+
if (lineChartRef.value) {
|
|
60
|
+
lineChartInstance = initLineChart(lineChartRef.value, '访问量(万次)')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 生成模拟数据
|
|
66
|
+
*/
|
|
67
|
+
const generateMockData = () => {
|
|
68
|
+
// 柱状图数据
|
|
69
|
+
const barData = {
|
|
70
|
+
xAxis: {
|
|
71
|
+
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
|
72
|
+
},
|
|
73
|
+
series: [
|
|
74
|
+
{
|
|
75
|
+
name: '产品A',
|
|
76
|
+
data: [120, 132, 101, 134, 90, 230, 210, 220, 182, 191, 234, 290]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: '产品B',
|
|
80
|
+
data: [220, 182, 191, 234, 290, 330, 310, 220, 182, 191, 234, 290]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: '产品C',
|
|
84
|
+
data: [150, 232, 201, 154, 190, 330, 410, 320, 282, 291, 334, 390]
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 饼图数据
|
|
90
|
+
const pieData = [
|
|
91
|
+
{ name: '电子产品', value: 335 },
|
|
92
|
+
{ name: '家居用品', value: 310 },
|
|
93
|
+
{ name: '服装鞋帽', value: 234 },
|
|
94
|
+
{ name: '食品饮料', value: 135 },
|
|
95
|
+
{ name: '图书音像', value: 1048 }
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
// 折线图数据
|
|
99
|
+
const lineData = {
|
|
100
|
+
xAxis: {
|
|
101
|
+
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
|
102
|
+
},
|
|
103
|
+
series: [
|
|
104
|
+
{
|
|
105
|
+
name: '网站访问量',
|
|
106
|
+
data: [820, 932, 901, 934, 1290, 1330, 1320]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'APP访问量',
|
|
110
|
+
data: [620, 732, 701, 734, 1090, 1130, 1120]
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: '小程序访问量',
|
|
114
|
+
data: [420, 532, 501, 534, 890, 930, 920]
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { barData, pieData, lineData }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 更新所有图表数据
|
|
124
|
+
*/
|
|
125
|
+
const updateCharts = () => {
|
|
126
|
+
const { barData, pieData, lineData } = generateMockData()
|
|
127
|
+
|
|
128
|
+
// 更新柱状图
|
|
129
|
+
if (barChartInstance) {
|
|
130
|
+
updateBarChart({
|
|
131
|
+
chartInstance: barChartInstance,
|
|
132
|
+
data: barData,
|
|
133
|
+
shouldShowSlider: false
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 更新饼图
|
|
138
|
+
if (pieChartInstance) {
|
|
139
|
+
updatePieChart({
|
|
140
|
+
chartInstance: pieChartInstance,
|
|
141
|
+
data: pieData
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 更新折线图
|
|
146
|
+
if (lineChartInstance) {
|
|
147
|
+
updateLineChart({
|
|
148
|
+
chartInstance: lineChartInstance,
|
|
149
|
+
data: lineData,
|
|
150
|
+
shouldShowSlider: false
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 窗口大小变化时重新调整图表大小
|
|
157
|
+
*/
|
|
158
|
+
const handleResize = () => {
|
|
159
|
+
barChartInstance?.resize()
|
|
160
|
+
pieChartInstance?.resize()
|
|
161
|
+
lineChartInstance?.resize()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 组件挂载时初始化图表
|
|
165
|
+
onMounted(() => {
|
|
166
|
+
initCharts()
|
|
167
|
+
updateCharts()
|
|
168
|
+
|
|
169
|
+
// 监听窗口大小变化
|
|
170
|
+
window.addEventListener('resize', handleResize)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// 组件卸载时清理资源
|
|
174
|
+
onUnmounted(() => {
|
|
175
|
+
// 销毁图表实例
|
|
176
|
+
barChartInstance?.dispose()
|
|
177
|
+
pieChartInstance?.dispose()
|
|
178
|
+
lineChartInstance?.dispose()
|
|
179
|
+
|
|
180
|
+
// 移除事件监听
|
|
181
|
+
window.removeEventListener('resize', handleResize)
|
|
182
|
+
})
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<style scoped>
|
|
186
|
+
|
|
187
|
+
.chart-card {
|
|
188
|
+
margin-bottom: 16px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.chart-container {
|
|
192
|
+
height: 300px;
|
|
193
|
+
width: 100%;
|
|
194
|
+
}
|
|
195
|
+
</style>
|