Vue小案例:基于echarts的客户消费记账单

本文是在学习了ajax, vue2的一些基本语法知识,如指令, 计算属性, 生命周期等.以及在接触了echarts这款基于JS的数据可视化图库表和bootstrap框架后,为巩固所学做的小小案例.

涉及的一些知识分析可查看代码的详细注释

网页效果如下

完整代码实现如下,可按需取用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>小菜记账清单</title>
<!-- CSS only -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
<style>
.red {
color: red !important;
}

.search {
width: 300px;
margin: 20px 0;
}

.my-form {
display: flex;
margin: 20px 0;
}

.my-form input {
flex: 1;
margin-right: 20px;
}

.table> :not(:first-child) {
border-top: none;
}

.contain {
display: flex;
padding: 10px;
}

.list-box {
flex: 1;
padding: 0 30px;
}

.list-box a {
text-decoration: none;
}

.echarts-box {
width: 600px;
height: 400px;
padding: 30px;
margin: 0 auto;
border: 1px solid #ccc;
}

tfoot {
font-weight: bold;
}

@media screen and (max-width: 1000px) {
.contain {
flex-wrap: wrap;
}

.list-box {
width: 100%;
}

.echarts-box {
margin-top: 30px;
}
}
</style>
</head>

<body>
<div id="app">
<div class="contain">
<!-- 左侧列表 -->
<div class="list-box">

<!-- 添加资产 -->
<form class="my-form">
<input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
<input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
<button type="button" class="btn btn-primary" @click="add">添加账单</button>
</form>

<table class="table table-hover">
<thead>
<tr>
<th>编号</th>
<th>消费名称</th>
<th>消费价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in list " :key="item.id">
<td>{{index+1}}</td>
<td>{{item.name}}</td>
<td :class="{ red: item.price>500 }">{{item.price.toFixed(2)}}</td>
<td><a @click="del(item.id)" href="javascript:;">删除</a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4">消费总计: {{totalPrice.toFixed(2)}}</td>
</tr>
</tfoot>
</table>
</div>

<!-- 右侧图表 -->
<div class="echarts-box" id="main"></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 接口文档地址:
* https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
*
* 功能需求:
* 1. 基本渲染
* 2. 添加功能
* 3. 删除功能
* 4. 饼图渲染
* * (1) 初始化一个饼图 echarts.init(dom) mounted钩子实现
* (2) 根据数据实时更新饼图 echarts.setOption({ ... })

*/
const app = new Vue({
el: '#app',
data: {
list: [],
name: '',
price: '',
},
methods: {
async getList() {
const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
params: {
creator: '小黑'
}
})
this.list = res.data.data
// 更新图表
this.myChart.setOption({
series: [{
// 箭头函数返回对象要用括号包住
data: this.list.map(item => ({value: item.price, name: item.name}))
}]
})
},
async add() {
if (!this.name) {
alert('请输入消费名称')
return
}
if (typeof this.price !== 'number') {
alert('请输入正确的消费价格')
return
}
// 发送添加请求
const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
creator: '小黑',
name: this.name,
price: this.price
})
// 重新渲染一遍
this.getList()
this.name = ''
this.price = ''
},
async del(id) {
const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
this.getList()
}
},
computed: {
totalPrice() {
return this.list.reduce((sum, item) => sum + item.price, 0)
}
},
async created() {
const res = await axios.get('https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058', {
params: {
creator: '小黑'
}
})
this.list = res.data.data
},
mounted(){
this.myChart = echarts.init(document.querySelector('#main'))
this.myChart.setOption({
// 大标题
title: {
text: '消费账单列表',
left: 'center'
},
// 提示框
tooltip: {
trigger: 'item'
},
// 图例
legend: {
// 对齐方式
orient: 'vertical',
left: 'left'
},
// 数据项
series: [
{
name: '消费账单',
// 类型是饼图
type: 'pie',
data: [],
emphasis: {
itemStyle:{
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0,0,0,0.5)'
}
}
}
]
})
}
})

</script>
</body>

</html>