trainingPlanTable.vue
8.13 KB
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
<template>
<div class="page-container">
<el-card class="search-bar">
<div class="search-item">
<label class="search-label">计划名称</label>
<el-input v-model="searchForm.name" placeholder="请输入计划名称" class="search-input" @keyup.enter="handleSearch"
clearable />
<div class="search-buttons">
<el-button type="primary" @click="handleSearch">
<el-icon>
<Search />
</el-icon>
搜索
</el-button>
<el-button @click="handleReset">
<el-icon>
<Refresh />
</el-icon>
重置
</el-button>
<el-button type="primary" @click="openAddDialog">
<el-icon>
<Plus />
</el-icon>
新增
</el-button>
</div>
</div>
</el-card>
<!-- 列表区域 -->
<el-card class="table-card">
<el-table v-loading="loading" :data="tableData" stripe style="width: 100%;">
<el-table-column prop="name" label="计划名称" align="center" sortable="custom" />
<el-table-column label="计划周期" align="center" :formatter="formatDurationWeeks" />
<el-table-column label="场景" align="center" :formatter="formatTrainingScene" />
<el-table-column label="训练频率" align="center" :formatter="formatFrequency" />
<el-table-column label="难度" align="center" :formatter="formatDifficulty" />
<el-table-column label="适合人群" align="center" :formatter="formatTargetPeople" />
<el-table-column prop="createTime" label="创建时间" align="center" :formatter="dateFormatter" />
<el-table-column prop="updateTime" label="更新时间" :formatter="dateFormatter" />
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button type="text" size="small" style=" margin-right: 8px;color: #409EFF;"
@click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" size="small" style="color: #F56C6C;"
@click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="pagination-wrapper" style="padding: 12px 16px;">
<el-pagination v-model:current-page="pagination.pageNo" v-model:page-size="pagination.pageSize"
:total="pagination.total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper"
style="text-align: left;" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
</el-card>
<!-- 弹窗组件 -->
<trainingPlanDialog v-model:visible="dialogVisible" :form-data="formData" :title="dialogTitle" @submit="getList" />
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { Search, Refresh, Plus } from '@element-plus/icons-vue'
import lostImg from '@/assets/imgs/lost.png' // 丢失图片占位图
import { dateFormatter } from '@/utils/formatTime'
import { trainingPlanApi } from '@/api/store/training/trainingPlan'
import TrainingPlanDialog from './trainingPlanDialog.vue'
const loading = ref(false)
const isEdit = ref(true)
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formData = ref({
templateIds: [{ templateId: undefined, sortOrder: 1 }]
}) // 传给弹窗的表单数据
// 分页参数
const pagination = reactive({
pageNo: 1,
pageSize: 10,
total: 0
})
// 文本映射=============
// 难度映射
const difficultyMap = {
1: '初阶',
2: '中阶',
3: '高阶'
}
// 适合人群映射
const targetPeopleMap = {
1: '不限',
2: '青少年',
3: '成年人',
4: '中年人',
5: '运动损伤'
}
// 训练场景映射
const trainingSceneMap = {
1: '不限',
2: '健身房',
3: '仅哑铃',
4: '仅哑铃 + 杠铃'
}
// 格式化难度
const formatDifficulty = (row) => {
return difficultyMap[row.difficultyLevel] || '未知'
}
// 格式化适合人群
const formatTargetPeople = (row) => {
return targetPeopleMap[row.targetPeople] || '未知'
}
// 格式化训练场景
const formatTrainingScene = (row) => {
return trainingSceneMap[row.trainingScene] || '未知'
}
// 格式化计划周期(在数字后加“周”)
const formatDurationWeeks = (row) => {
return row.durationWeeks ? `${row.durationWeeks}周` : '0周'
}
// 格式化训练频率(数字 + 练/周)
const formatFrequency = (row) => {
return row.frequencyPerWeek ? `${row.frequencyPerWeek}练/周` : '0练/周'
}
// 搜索表单
const searchForm = reactive({
name: ''
})
const tableData = ref([])
// 获取学员训练记录列表
const getList = async () => {
loading.value = true
try {
// 搜索条件
const params = {
pageNo: pagination.pageNo + '',
pageSize: pagination.pageSize + '',
name: searchForm.name,
}
const res = await trainingPlanApi.getTrainingPlan(params)
tableData.value = res.list || []
pagination.total = res.total || 0
console.log('计划分页接口返回数据:', res)
} catch (err) {
console.error('❌ 请求失败:', err)
ElMessage.error('请求失败,请检查网络或接口')
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = () => {
ElMessage.success('执行搜索:' + searchForm.name)
getList()
}
// 重置
const handleReset = () => {
pagination.pageNo = 1
pagination.pageSize = 10
searchForm.name = ''
ElMessage.success('已重置搜索条件')
getList()
}
// 新增
const openAddDialog = () => {
dialogTitle.value = '新增训练计划'
// 新增时彻底重置表单,清空编辑残留
// 彻底重置 formData,100%清空所有编辑残留,和子组件 SonFormData 初始结构完全一致
formData.value = {
id: undefined,
categoryId: 0,
name: '',
urlCover: '',
introduction: '',
durationWeeks: 1,
frequencyPerWeek: 1,
difficultyLevel: 1,
targetPeople: 1,
trainingScene: 1,
equipmentsSummary: '',
templateIds: [{ templateId: undefined, sortOrder: 1 }]
}
dialogVisible.value = true
}
// 编辑
const handleEdit = (row) => {
dialogTitle.value = '编辑训练计划'
// 保留完整表单结构,只覆盖后端有的字段
formData.value = {
// 先保留完整结构(不丢字段,这里没有新增时的数据,只是我创建formData的初始数据)
...formData.value,
// 再覆盖编辑数据,row里面是列表的字段,列表的字段和新增的字段的数量是不一样的。
...row
}
dialogVisible.value = true
}
// 删除
const handleDelete = async (id) => {
try {
await ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
// 能走到这里,说明用户点了确定
await trainingPlanApi.deleteTrainingPlan(id)
ElMessage.success('删除成功')
} catch (err) {
// 关键:判断是不是用户点击了取消
if (err === 'cancel' || err?.message === 'cancel') {
ElMessage.info('已取消删除')
return // 直接退出,不报错
}
// 只有真正的请求错误才走这里
console.error('❌ 请求失败:', err)
ElMessage.error('请求失败,请检查网络或接口')
} finally {
getList() // 刷新列表
}
}
// 处理分页大小变化
const handleSizeChange = (val) => {
pagination.pageSize = val
getList()
}
//
const handleCurrentChange = (val) => {
pagination.pageNo = val
getList()
}
onMounted(() => {
getList()
})
</script>
<style scoped>
.search-bar {
margin-bottom: 20px;
box-shadow: 0 1px 2px rgb(0 0 0 / 5%);
}
.search-item {
display: flex;
align-items: center;
gap: 10px;
}
.search-label {
/* width: 160px; */
/* font-weight: 500; */
}
.search-input {
width: 260px;
}
.search-buttons {
display: flex;
gap: 10px;
margin-left: 10px;
}
.table-card {
margin-top: 5px;
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 2px rgb(0 0 0 / 5%);
}
/* 分页居右 */
.pagination-wrapper {
display: flex;
padding: 12px 16px;
justify-content: flex-end;
}
</style>