Showing
13 changed files
with
580 additions
and
202 deletions
| @@ -2,25 +2,34 @@ | @@ -2,25 +2,34 @@ | ||
| 2 | <template> | 2 | <template> |
| 3 | <div class="page-container"> | 3 | <div class="page-container"> |
| 4 | <!-- 搜索与新增区域 --> | 4 | <!-- 搜索与新增区域 --> |
| 5 | - <ContentWrap> | 5 | + <ContentWrap class="plan-search-bar"> |
| 6 | <div class="search-bar"> | 6 | <div class="search-bar"> |
| 7 | - <el-input v-model="searchForm.name" placeholder="肌肉名称" clearable @keyup.enter="handleSearch" /> | ||
| 8 | - <el-button @click="handleSearch"> | 7 | + <div class="search-item"> |
| 8 | + <label class="search-label">肌肉名称</label> | ||
| 9 | + <el-input v-model="searchForm.name" placeholder="肌肉名称" clearable @keyup.enter="handleSearch" | ||
| 10 | + class="search-input" /> | ||
| 11 | + <div class="search-buttons"> | ||
| 12 | + <el-button type="primary" @click="handleSearch"> | ||
| 9 | <el-icon> | 13 | <el-icon> |
| 10 | <Search /> | 14 | <Search /> |
| 11 | - </el-icon>搜索 | 15 | + </el-icon> |
| 16 | + 搜索 | ||
| 12 | </el-button> | 17 | </el-button> |
| 13 | <el-button @click="handleReset"> | 18 | <el-button @click="handleReset"> |
| 14 | <el-icon> | 19 | <el-icon> |
| 15 | <Refresh /> | 20 | <Refresh /> |
| 16 | - </el-icon>重置 | 21 | + </el-icon> |
| 22 | + 重置 | ||
| 17 | </el-button> | 23 | </el-button> |
| 18 | - <el-button @click="handleAdd"> | 24 | + <el-button type="primary" @click="handleAdd"> |
| 19 | <el-icon> | 25 | <el-icon> |
| 20 | <Plus /> | 26 | <Plus /> |
| 21 | - </el-icon>新增 | 27 | + </el-icon> |
| 28 | + 新增 | ||
| 22 | </el-button> | 29 | </el-button> |
| 23 | </div> | 30 | </div> |
| 31 | + </div> | ||
| 32 | + </div> | ||
| 24 | </ContentWrap> | 33 | </ContentWrap> |
| 25 | 34 | ||
| 26 | <ContentWrap> | 35 | <ContentWrap> |
| @@ -30,8 +39,6 @@ | @@ -30,8 +39,6 @@ | ||
| 30 | <el-table-column prop="id" label="ID" sortable align="center" /> | 39 | <el-table-column prop="id" label="ID" sortable align="center" /> |
| 31 | <!-- 肌肉名称列(对应接口的name字段) --> | 40 | <!-- 肌肉名称列(对应接口的name字段) --> |
| 32 | <el-table-column prop="name" label="肌肉名称" align="center" /> | 41 | <el-table-column prop="name" label="肌肉名称" align="center" /> |
| 33 | - <!-- 关联大类ID列 --> | ||
| 34 | - <!-- <el-table-column prop="categoryId" label="关联大类ID" align="center" /> --> | ||
| 35 | <el-table-column prop="createTime" label="创建时间" align="center" :formatter="dateFormatter" /> | 42 | <el-table-column prop="createTime" label="创建时间" align="center" :formatter="dateFormatter" /> |
| 36 | <el-table-column prop="updateTime" label="更新时间" align="center" min-width="100" :formatter="dateFormatter" /> | 43 | <el-table-column prop="updateTime" label="更新时间" align="center" min-width="100" :formatter="dateFormatter" /> |
| 37 | <!-- 操作列 --> | 44 | <!-- 操作列 --> |
| @@ -59,16 +66,13 @@ | @@ -59,16 +66,13 @@ | ||
| 59 | </div> | 66 | </div> |
| 60 | </ContentWrap> | 67 | </ContentWrap> |
| 61 | 68 | ||
| 62 | - <ContentWrap> | 69 | + <!-- <ContentWrap> --> |
| 63 | <!-- 编辑弹窗 --> | 70 | <!-- 编辑弹窗 --> |
| 64 | <el-dialog v-model="editDialogVisible" title="编辑肌肉信息" @close="resetEditForm"> | 71 | <el-dialog v-model="editDialogVisible" title="编辑肌肉信息" @close="resetEditForm"> |
| 65 | <el-form ref="editFormRef" :rules="editRules" :model="editForm" label-width="100px"> | 72 | <el-form ref="editFormRef" :rules="editRules" :model="editForm" label-width="100px"> |
| 66 | <el-form-item label="肌肉名称" prop="name"> | 73 | <el-form-item label="肌肉名称" prop="name"> |
| 67 | <el-input v-model="editForm.name" placeholder="请输入肌肉名称" /> | 74 | <el-input v-model="editForm.name" placeholder="请输入肌肉名称" /> |
| 68 | </el-form-item> | 75 | </el-form-item> |
| 69 | - <!-- <el-form-item label="关联大类ID" prop="categoryId"> | ||
| 70 | - <el-input v-model.number="editForm.categoryId" type="number" placeholder="请输入关联大类ID" /> | ||
| 71 | - </el-form-item> --> | ||
| 72 | </el-form> | 76 | </el-form> |
| 73 | <template #footer> | 77 | <template #footer> |
| 74 | <el-button @click="editDialogVisible = false">取消</el-button> | 78 | <el-button @click="editDialogVisible = false">取消</el-button> |
| @@ -82,16 +86,13 @@ | @@ -82,16 +86,13 @@ | ||
| 82 | <el-form-item label="肌肉名称" prop="name"> | 86 | <el-form-item label="肌肉名称" prop="name"> |
| 83 | <el-input v-model="addForm.name" placeholder="请输入肌肉名称" /> | 87 | <el-input v-model="addForm.name" placeholder="请输入肌肉名称" /> |
| 84 | </el-form-item> | 88 | </el-form-item> |
| 85 | - <!-- <el-form-item label="关联大类ID" prop="categoryId"> | ||
| 86 | - <el-input v-model.number="addForm.categoryId" type="number" placeholder="请输入关联大类ID" /> | ||
| 87 | - </el-form-item> --> | ||
| 88 | </el-form> | 89 | </el-form> |
| 89 | <template #footer> | 90 | <template #footer> |
| 90 | <el-button @click="addDialogVisible = false">取消</el-button> | 91 | <el-button @click="addDialogVisible = false">取消</el-button> |
| 91 | <el-button type="primary" @click="submitAddForm">确定</el-button> | 92 | <el-button type="primary" @click="submitAddForm">确定</el-button> |
| 92 | </template> | 93 | </template> |
| 93 | </el-dialog> | 94 | </el-dialog> |
| 94 | - </ContentWrap> | 95 | + <!-- </ContentWrap> --> |
| 95 | </div> | 96 | </div> |
| 96 | </template> | 97 | </template> |
| 97 | 98 | ||
| @@ -100,7 +101,7 @@ import { ref, reactive, onMounted, watch } from 'vue' | @@ -100,7 +101,7 @@ import { ref, reactive, onMounted, watch } from 'vue' | ||
| 100 | import { ElMessage, ElMessageBox, ElEmpty, ElForm, ElFormItem, ElInput, ElDialog } from 'element-plus' | 101 | import { ElMessage, ElMessageBox, ElEmpty, ElForm, ElFormItem, ElInput, ElDialog } from 'element-plus' |
| 101 | // 导入你提供的接口文件 | 102 | // 导入你提供的接口文件 |
| 102 | import { MusclesApi, type MusclesVO } from '@/api/store/training/muscle' | 103 | import { MusclesApi, type MusclesVO } from '@/api/store/training/muscle' |
| 103 | - | 104 | +import { Search, Refresh, Plus } from '@element-plus/icons-vue' |
| 104 | import { dateFormatter } from '@/utils/formatTime' | 105 | import { dateFormatter } from '@/utils/formatTime' |
| 105 | 106 | ||
| 106 | // 表格数据(对应接口返回的列表) | 107 | // 表格数据(对应接口返回的列表) |
| @@ -114,9 +115,9 @@ const searchForm = reactive({ | @@ -114,9 +115,9 @@ const searchForm = reactive({ | ||
| 114 | 115 | ||
| 115 | // 分页参数 | 116 | // 分页参数 |
| 116 | const pagination = reactive({ | 117 | const pagination = reactive({ |
| 117 | - pageNo: 1, // 替换原currentPage | ||
| 118 | - pageSize: 10, // 替换原pageSize | ||
| 119 | - total: 0 // 替换原total | 118 | + pageNo: 1, |
| 119 | + pageSize: 10, | ||
| 120 | + total: 0 | ||
| 120 | }) | 121 | }) |
| 121 | 122 | ||
| 122 | // ---------------------- 编辑功能相关变量 ---------------------- | 123 | // ---------------------- 编辑功能相关变量 ---------------------- |
| @@ -135,10 +136,6 @@ const editRules = reactive({ | @@ -135,10 +136,6 @@ const editRules = reactive({ | ||
| 135 | name: [ | 136 | name: [ |
| 136 | { required: true, message: '请输入肌肉名称', trigger: 'blur' }, | 137 | { required: true, message: '请输入肌肉名称', trigger: 'blur' }, |
| 137 | { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' } | 138 | { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' } |
| 138 | - ], | ||
| 139 | - categoryId: [ | ||
| 140 | - { required: true, message: '请输入关联大类ID', trigger: 'blur' }, | ||
| 141 | - { type: 'number', min: 1, message: '关联大类ID必须为正整数', trigger: 'blur' } | ||
| 142 | ] | 139 | ] |
| 143 | }) | 140 | }) |
| 144 | 141 | ||
| @@ -157,10 +154,6 @@ const addRules = reactive({ | @@ -157,10 +154,6 @@ const addRules = reactive({ | ||
| 157 | name: [ | 154 | name: [ |
| 158 | { required: true, message: '请输入肌肉名称', trigger: 'blur' }, | 155 | { required: true, message: '请输入肌肉名称', trigger: 'blur' }, |
| 159 | { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' } | 156 | { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' } |
| 160 | - ], | ||
| 161 | - categoryId: [ | ||
| 162 | - { required: true, message: '请输入关联大类ID', trigger: 'blur' }, | ||
| 163 | - { type: 'number', min: 1, message: '关联大类ID必须为正整数', trigger: 'blur' } | ||
| 164 | ] | 157 | ] |
| 165 | }) | 158 | }) |
| 166 | 159 | ||
| @@ -168,25 +161,23 @@ const addRules = reactive({ | @@ -168,25 +161,23 @@ const addRules = reactive({ | ||
| 168 | const getMusclesList = async () => { | 161 | const getMusclesList = async () => { |
| 169 | try { | 162 | try { |
| 170 | loading.value = true | 163 | loading.value = true |
| 171 | - // 构造请求参数:参数转字符串传递 | 164 | + // 构造请求参数 |
| 172 | const params = { | 165 | const params = { |
| 173 | - pageNo: pagination.pageNo + '', // 转字符串 | ||
| 174 | - pageSize: pagination.pageSize + '', // 转字符串 | ||
| 175 | - name: searchForm.name // 搜索的肌肉名称 | 166 | + pageNo: pagination.pageNo + '', |
| 167 | + pageSize: pagination.pageSize + '', | ||
| 168 | + name: searchForm.name | ||
| 176 | } | 169 | } |
| 177 | // 调用接口获取数据 | 170 | // 调用接口获取数据 |
| 178 | const res = await MusclesApi.getMusclesPage(params) | 171 | const res = await MusclesApi.getMusclesPage(params) |
| 179 | - // 兼容不同的接口返回格式(优先取res.data,没有则取res本身) | ||
| 180 | const result = res.data || res | 172 | const result = res.data || res |
| 181 | tableData.value = result.list || [] | 173 | tableData.value = result.list || [] |
| 182 | pagination.total = result.total || 0 | 174 | pagination.total = result.total || 0 |
| 183 | - console.log('肌肉分页接口返回数据:', res) | ||
| 184 | 175 | ||
| 185 | - // 分页边界处理:如果当前页大于总页数,自动切到最后一页 | 176 | + // 分页边界处理 |
| 186 | const totalPages = Math.ceil(pagination.total / pagination.pageSize) | 177 | const totalPages = Math.ceil(pagination.total / pagination.pageSize) |
| 187 | if (pagination.pageNo > totalPages && totalPages > 0) { | 178 | if (pagination.pageNo > totalPages && totalPages > 0) { |
| 188 | pagination.pageNo = totalPages | 179 | pagination.pageNo = totalPages |
| 189 | - getMusclesList() // 重新请求最后一页数据 | 180 | + getMusclesList() |
| 190 | } | 181 | } |
| 191 | } catch (error) { | 182 | } catch (error) { |
| 192 | ElMessage.error('获取肌肉列表失败,请重试') | 183 | ElMessage.error('获取肌肉列表失败,请重试') |
| @@ -212,13 +203,13 @@ watch([() => pagination.total, () => pagination.pageSize], () => { | @@ -212,13 +203,13 @@ watch([() => pagination.total, () => pagination.pageSize], () => { | ||
| 212 | }) | 203 | }) |
| 213 | 204 | ||
| 214 | // 4. 事件处理函数 | 205 | // 4. 事件处理函数 |
| 215 | -// 搜索:保持原有逻辑,仅调整分页参数名 | 206 | +// 搜索 |
| 216 | const handleSearch = () => { | 207 | const handleSearch = () => { |
| 217 | - pagination.pageNo = 1 // 搜索时重置页码为1 | 208 | + pagination.pageNo = 1 |
| 218 | getMusclesList() | 209 | getMusclesList() |
| 219 | } | 210 | } |
| 220 | 211 | ||
| 221 | -// 重置:保持原有逻辑,仅调整分页参数名 | 212 | +// 重置 |
| 222 | const handleReset = () => { | 213 | const handleReset = () => { |
| 223 | searchForm.name = '' | 214 | searchForm.name = '' |
| 224 | pagination.pageNo = 1 | 215 | pagination.pageNo = 1 |
| @@ -237,7 +228,6 @@ const handleAdd = () => { | @@ -237,7 +228,6 @@ const handleAdd = () => { | ||
| 237 | const resetAddForm = () => { | 228 | const resetAddForm = () => { |
| 238 | addForm.name = '' | 229 | addForm.name = '' |
| 239 | addForm.categoryId = 0 | 230 | addForm.categoryId = 0 |
| 240 | - // 清空表单验证状态 | ||
| 241 | if (addFormRef.value) { | 231 | if (addFormRef.value) { |
| 242 | addFormRef.value.clearValidate() | 232 | addFormRef.value.clearValidate() |
| 243 | } | 233 | } |
| @@ -247,20 +237,16 @@ const resetAddForm = () => { | @@ -247,20 +237,16 @@ const resetAddForm = () => { | ||
| 247 | const submitAddForm = async () => { | 237 | const submitAddForm = async () => { |
| 248 | if (!addFormRef.value) return | 238 | if (!addFormRef.value) return |
| 249 | try { | 239 | try { |
| 250 | - // 表单验证 | ||
| 251 | await addFormRef.value.validate() | 240 | await addFormRef.value.validate() |
| 252 | - // 调用新增接口(无需传id,由后端生成) | ||
| 253 | await MusclesApi.createMuscles({ | 241 | await MusclesApi.createMuscles({ |
| 254 | ...addForm, | 242 | ...addForm, |
| 255 | - id: 0 // 占位,后端会自动生成 | 243 | + id: 0 |
| 256 | }) | 244 | }) |
| 257 | ElMessage.success('新增成功!') | 245 | ElMessage.success('新增成功!') |
| 258 | - // 关闭弹窗 | ||
| 259 | addDialogVisible.value = false | 246 | addDialogVisible.value = false |
| 260 | - // 重新加载列表数据 | ||
| 261 | getMusclesList() | 247 | getMusclesList() |
| 262 | } catch (error) { | 248 | } catch (error) { |
| 263 | - if (error !== 'cancel') { // 排除表单验证取消的情况 | 249 | + if (error !== 'cancel') { |
| 264 | ElMessage.error('新增失败,请重试') | 250 | ElMessage.error('新增失败,请重试') |
| 265 | console.error('新增失败:', error) | 251 | console.error('新增失败:', error) |
| 266 | } | 252 | } |
| @@ -271,14 +257,10 @@ const submitAddForm = async () => { | @@ -271,14 +257,10 @@ const submitAddForm = async () => { | ||
| 271 | // 打开编辑弹窗并回显数据 | 257 | // 打开编辑弹窗并回显数据 |
| 272 | const handleEdit = async (row: MusclesVO) => { | 258 | const handleEdit = async (row: MusclesVO) => { |
| 273 | try { | 259 | try { |
| 274 | - // 清空编辑表单 | ||
| 275 | resetEditForm() | 260 | resetEditForm() |
| 276 | - // 打开弹窗 | ||
| 277 | editDialogVisible.value = true | 261 | editDialogVisible.value = true |
| 278 | - // 调用接口获取详情 | ||
| 279 | const res = await MusclesApi.getMuscles(row.id) | 262 | const res = await MusclesApi.getMuscles(row.id) |
| 280 | const detail = res.data || res | 263 | const detail = res.data || res |
| 281 | - // 回显数据到编辑表单 | ||
| 282 | editForm.id = detail.id | 264 | editForm.id = detail.id |
| 283 | editForm.name = detail.name | 265 | editForm.name = detail.name |
| 284 | editForm.categoryId = detail.categoryId | 266 | editForm.categoryId = detail.categoryId |
| @@ -293,7 +275,6 @@ const resetEditForm = () => { | @@ -293,7 +275,6 @@ const resetEditForm = () => { | ||
| 293 | editForm.id = 0 | 275 | editForm.id = 0 |
| 294 | editForm.name = '' | 276 | editForm.name = '' |
| 295 | editForm.categoryId = 0 | 277 | editForm.categoryId = 0 |
| 296 | - // 清空表单验证状态 | ||
| 297 | if (editFormRef.value) { | 278 | if (editFormRef.value) { |
| 298 | editFormRef.value.clearValidate() | 279 | editFormRef.value.clearValidate() |
| 299 | } | 280 | } |
| @@ -303,17 +284,13 @@ const resetEditForm = () => { | @@ -303,17 +284,13 @@ const resetEditForm = () => { | ||
| 303 | const submitEditForm = async () => { | 284 | const submitEditForm = async () => { |
| 304 | if (!editFormRef.value) return | 285 | if (!editFormRef.value) return |
| 305 | try { | 286 | try { |
| 306 | - // 表单验证 | ||
| 307 | await editFormRef.value.validate() | 287 | await editFormRef.value.validate() |
| 308 | - // 调用更新接口 | ||
| 309 | await MusclesApi.updateMuscles({ ...editForm }) | 288 | await MusclesApi.updateMuscles({ ...editForm }) |
| 310 | ElMessage.success('编辑成功!') | 289 | ElMessage.success('编辑成功!') |
| 311 | - // 关闭弹窗 | ||
| 312 | editDialogVisible.value = false | 290 | editDialogVisible.value = false |
| 313 | - // 重新加载列表数据 | ||
| 314 | getMusclesList() | 291 | getMusclesList() |
| 315 | } catch (error) { | 292 | } catch (error) { |
| 316 | - if (error !== 'cancel') { // 排除表单验证取消的情况 | 293 | + if (error !== 'cancel') { |
| 317 | ElMessage.error('编辑失败,请重试') | 294 | ElMessage.error('编辑失败,请重试') |
| 318 | console.error('编辑失败:', error) | 295 | console.error('编辑失败:', error) |
| 319 | } | 296 | } |
| @@ -323,7 +300,6 @@ const submitEditForm = async () => { | @@ -323,7 +300,6 @@ const submitEditForm = async () => { | ||
| 323 | // 删除 | 300 | // 删除 |
| 324 | const handleDelete = async (row: MusclesVO) => { | 301 | const handleDelete = async (row: MusclesVO) => { |
| 325 | try { | 302 | try { |
| 326 | - // 确认删除 | ||
| 327 | await ElMessageBox.confirm( | 303 | await ElMessageBox.confirm( |
| 328 | '此操作将永久删除该肌肉信息,是否继续?', | 304 | '此操作将永久删除该肌肉信息,是否继续?', |
| 329 | '温馨提示', | 305 | '温馨提示', |
| @@ -333,13 +309,11 @@ const handleDelete = async (row: MusclesVO) => { | @@ -333,13 +309,11 @@ const handleDelete = async (row: MusclesVO) => { | ||
| 333 | type: 'warning' | 309 | type: 'warning' |
| 334 | } | 310 | } |
| 335 | ) | 311 | ) |
| 336 | - // 调用删除接口 | ||
| 337 | await MusclesApi.deleteMuscles(row.id) | 312 | await MusclesApi.deleteMuscles(row.id) |
| 338 | ElMessage.success('删除成功!') | 313 | ElMessage.success('删除成功!') |
| 339 | - // 重新获取列表 | ||
| 340 | getMusclesList() | 314 | getMusclesList() |
| 341 | } catch (error) { | 315 | } catch (error) { |
| 342 | - if (error !== 'cancel') { // 排除用户取消的情况 | 316 | + if (error !== 'cancel') { |
| 343 | ElMessage.error('删除失败,请重试') | 317 | ElMessage.error('删除失败,请重试') |
| 344 | console.error('删除失败:', error) | 318 | console.error('删除失败:', error) |
| 345 | } | 319 | } |
| @@ -361,12 +335,44 @@ const handleCurrentChange = (val: number) => { | @@ -361,12 +335,44 @@ const handleCurrentChange = (val: number) => { | ||
| 361 | </script> | 335 | </script> |
| 362 | 336 | ||
| 363 | <style scoped> | 337 | <style scoped> |
| 364 | -.page-container { | ||
| 365 | - /* min-height: 100vh; | ||
| 366 | - background: #fff; */ | 338 | +.plan-search-bar { |
| 339 | + margin-bottom: 20px; | ||
| 340 | + box-shadow: 0 1px 2px rgb(0 0 0 / 5%); | ||
| 341 | +} | ||
| 342 | + | ||
| 343 | +/* 核心修复:自动换行 + 自适应布局 */ | ||
| 344 | +.search-item { | ||
| 345 | + display: flex; | ||
| 346 | + align-items: center; | ||
| 347 | + gap: 10px; | ||
| 348 | + padding: 15px 20px; | ||
| 349 | + flex-wrap: wrap; | ||
| 350 | +} | ||
| 351 | + | ||
| 352 | +.search-label { | ||
| 353 | + white-space: nowrap; | ||
| 354 | +} | ||
| 355 | + | ||
| 356 | +/* 输入框自适应宽度 */ | ||
| 357 | +.search-input { | ||
| 358 | + max-width: 300px; | ||
| 359 | + min-width: 200px; | ||
| 360 | + flex: 1; | ||
| 361 | +} | ||
| 362 | + | ||
| 363 | +/* 按钮组靠右,不挤压 */ | ||
| 364 | +.search-buttons { | ||
| 365 | + display: flex; | ||
| 366 | + gap: 10px; | ||
| 367 | + | ||
| 368 | + /* margin-left: auto; */ | ||
| 369 | + flex-wrap: wrap; | ||
| 370 | +} | ||
| 371 | + | ||
| 372 | +:deep(.plan-search-bar) { | ||
| 373 | + padding: 0; | ||
| 367 | } | 374 | } |
| 368 | 375 | ||
| 369 | -/* 新增分页样式 */ | ||
| 370 | .pagination-wrapper { | 376 | .pagination-wrapper { |
| 371 | display: flex; | 377 | display: flex; |
| 372 | padding: 12px 16px 0; | 378 | padding: 12px 16px 0; |
| 1 | <!-- 部位分类页面,在接口文档没有找到部位分离的接口,用了动作分类的接口 --> | 1 | <!-- 部位分类页面,在接口文档没有找到部位分离的接口,用了动作分类的接口 --> |
| 2 | <template> | 2 | <template> |
| 3 | <div class="simple-category-page"> | 3 | <div class="simple-category-page"> |
| 4 | - <ContentWrap> | 4 | + <ContentWrap class="plan-search-bar"> |
| 5 | <!-- 搜索栏 --> | 5 | <!-- 搜索栏 --> |
| 6 | <div class="search-bar" style="padding: 15px; margin-bottom: 10px; background: #fff; border-radius: 6px;"> | 6 | <div class="search-bar" style="padding: 15px; margin-bottom: 10px; background: #fff; border-radius: 6px;"> |
| 7 | <div style="display: flex; align-items: center; gap: 10px;"> | 7 | <div style="display: flex; align-items: center; gap: 10px;"> |
| @@ -12,12 +12,14 @@ | @@ -12,12 +12,14 @@ | ||
| 12 | <el-icon> | 12 | <el-icon> |
| 13 | <Search /> | 13 | <Search /> |
| 14 | </el-icon> | 14 | </el-icon> |
| 15 | - 搜索</el-button> | 15 | + 搜索 |
| 16 | + </el-button> | ||
| 16 | <el-button @click="resetQuery"> | 17 | <el-button @click="resetQuery"> |
| 17 | <el-icon> | 18 | <el-icon> |
| 18 | <Refresh /> | 19 | <Refresh /> |
| 19 | </el-icon> | 20 | </el-icon> |
| 20 | - 重置</el-button> | 21 | + 重置 |
| 22 | + </el-button> | ||
| 21 | <!-- 新增按钮 --> | 23 | <!-- 新增按钮 --> |
| 22 | <el-button class="add-btn" @click="openAddForm"> | 24 | <el-button class="add-btn" @click="openAddForm"> |
| 23 | <el-icon> | 25 | <el-icon> |
| @@ -69,7 +71,8 @@ | @@ -69,7 +71,8 @@ | ||
| 69 | </div> | 71 | </div> |
| 70 | </div> | 72 | </div> |
| 71 | </ContentWrap> | 73 | </ContentWrap> |
| 72 | - <ContentWrap> | 74 | + |
| 75 | + <!-- <ContentWrap> --> | ||
| 73 | <!-- 新增/编辑弹窗(复用) --> | 76 | <!-- 新增/编辑弹窗(复用) --> |
| 74 | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> | 77 | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> |
| 75 | <el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px"> | 78 | <el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px"> |
| @@ -91,7 +94,7 @@ | @@ -91,7 +94,7 @@ | ||
| 91 | </el-button> | 94 | </el-button> |
| 92 | </template> | 95 | </template> |
| 93 | </el-dialog> | 96 | </el-dialog> |
| 94 | - </ContentWrap> | 97 | + <!-- </ContentWrap> --> |
| 95 | </div> | 98 | </div> |
| 96 | </template> | 99 | </template> |
| 97 | 100 | ||
| @@ -285,7 +288,13 @@ onMounted(() => { | @@ -285,7 +288,13 @@ onMounted(() => { | ||
| 285 | <style scoped> | 288 | <style scoped> |
| 286 | .simple-category-page { | 289 | .simple-category-page { |
| 287 | height: 100%; | 290 | height: 100%; |
| 288 | - background: #f5f5f5; | 291 | + |
| 292 | + /* background: #f5f5f5; */ | ||
| 293 | +} | ||
| 294 | + | ||
| 295 | +.plan-search-bar { | ||
| 296 | + margin-bottom: 20px; | ||
| 297 | + box-shadow: 0 1px 2px rgb(0 0 0 / 5%); | ||
| 289 | } | 298 | } |
| 290 | 299 | ||
| 291 | .add-btn { | 300 | .add-btn { |
| @@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
| 2 | <div class="page-container"> | 2 | <div class="page-container"> |
| 3 | <!-- 搜索栏 --> | 3 | <!-- 搜索栏 --> |
| 4 | <ContentWrap class="plan-search-bar"> | 4 | <ContentWrap class="plan-search-bar"> |
| 5 | - <div class="search-bar-wrapper"> | 5 | + <!-- <div class="search-bar-wrapper"> --> |
| 6 | <div class="search-item"> | 6 | <div class="search-item"> |
| 7 | <label class="search-label">计划大类名称</label> | 7 | <label class="search-label">计划大类名称</label> |
| 8 | <el-input v-model="searchForm.name" placeholder="请输入" class="search-input" clearable | 8 | <el-input v-model="searchForm.name" placeholder="请输入" class="search-input" clearable |
| @@ -28,8 +28,10 @@ | @@ -28,8 +28,10 @@ | ||
| 28 | </el-button> | 28 | </el-button> |
| 29 | </div> | 29 | </div> |
| 30 | </div> | 30 | </div> |
| 31 | - </div> | 31 | + <!-- </div> --> |
| 32 | </ContentWrap> | 32 | </ContentWrap> |
| 33 | + | ||
| 34 | + | ||
| 33 | <ContentWrap> | 35 | <ContentWrap> |
| 34 | <!-- 表格 --> | 36 | <!-- 表格 --> |
| 35 | <el-table v-loading="loading" :data="tableData" stripe style="width: 100%;"> | 37 | <el-table v-loading="loading" :data="tableData" stripe style="width: 100%;"> |
| @@ -251,7 +253,6 @@ onMounted(() => { | @@ -251,7 +253,6 @@ onMounted(() => { | ||
| 251 | /* 3. 标签样式(和计划页一致) */ | 253 | /* 3. 标签样式(和计划页一致) */ |
| 252 | .search-label { | 254 | .search-label { |
| 253 | white-space: nowrap; | 255 | white-space: nowrap; |
| 254 | - | ||
| 255 | } | 256 | } |
| 256 | 257 | ||
| 257 | /* 4. 输入框宽度(和计划页一致) */ | 258 | /* 4. 输入框宽度(和计划页一致) */ |
| @@ -32,27 +32,27 @@ | @@ -32,27 +32,27 @@ | ||
| 32 | </el-form-item> | 32 | </el-form-item> |
| 33 | <el-form-item label="主要训练部位:" prop="primaryMuscles" label-suffix="*"> | 33 | <el-form-item label="主要训练部位:" prop="primaryMuscles" label-suffix="*"> |
| 34 | <el-select v-model="formDataModel.primaryMuscles" multiple placeholder="请输入主要训练部位" style="width: 100%"> | 34 | <el-select v-model="formDataModel.primaryMuscles" multiple placeholder="请输入主要训练部位" style="width: 100%"> |
| 35 | - <el-option v-for="item in muscleList" :key="item.id" :label="item.name" :value="item.id" /> | 35 | + <el-option v-for="item in primaryMuscleOptions" :key="item.id" :label="item.name" :value="item.id" /> |
| 36 | </el-select> | 36 | </el-select> |
| 37 | </el-form-item> | 37 | </el-form-item> |
| 38 | <el-form-item label="次要训练部位:" prop="secondaryMuscles" label-suffix="*"> | 38 | <el-form-item label="次要训练部位:" prop="secondaryMuscles" label-suffix="*"> |
| 39 | <el-select v-model="formDataModel.secondaryMuscles" multiple placeholder="请输入次要训练部位" style="width: 100%"> | 39 | <el-select v-model="formDataModel.secondaryMuscles" multiple placeholder="请输入次要训练部位" style="width: 100%"> |
| 40 | - <el-option v-for="item in muscleList" :key="item.id" :label="item.name" :value="item.id" /> | 40 | + <el-option v-for="item in secondaryMuscleOptions" :key="item.id" :label="item.name" :value="item.id" /> |
| 41 | </el-select> | 41 | </el-select> |
| 42 | </el-form-item> | 42 | </el-form-item> |
| 43 | - <!-- 封面图 --> | ||
| 44 | - <el-form-item label="锻炼部位图" prop="urlImage" label-suffix="*" class="form-item-margin upload-img-item"> | 43 | + <!-- 锻炼部位图 --> |
| 44 | + <el-form-item label="锻炼部位图" prop="urlImage" class="form-item-margin upload-img-item"> | ||
| 45 | <UploadImg v-model="formDataModel.urlImage" /> | 45 | <UploadImg v-model="formDataModel.urlImage" /> |
| 46 | </el-form-item> | 46 | </el-form-item> |
| 47 | - <el-form-item label="3D动画地址" prop="url3dAnimation" label-suffix="*"> | ||
| 48 | - <UploadVideo v-model="formDataModel.url3dAnimation" /> | 47 | + <el-form-item label="3D动画地址" prop="url3dAnimation" label-suffix="*" class="upload-img-item upload-3d-item"> |
| 48 | + <UploadImg v-model="formDataModel.url3dAnimation" /> | ||
| 49 | </el-form-item> | 49 | </el-form-item> |
| 50 | 50 | ||
| 51 | - <el-form-item label="真人演示地址" prop="urlRealPerson"> | 51 | + <el-form-item label="真人演示地址" prop="urlRealPerson" label-suffix="*"> |
| 52 | <UploadVideo v-model="formDataModel.urlRealPerson" /> | 52 | <UploadVideo v-model="formDataModel.urlRealPerson" /> |
| 53 | </el-form-item> | 53 | </el-form-item> |
| 54 | 54 | ||
| 55 | - <el-form-item label="教程地址" prop="urlTutorial"> | 55 | + <el-form-item label="教程地址" prop="urlTutorial" label-suffix="*"> |
| 56 | <UploadVideo v-model="formDataModel.urlTutorial" /> | 56 | <UploadVideo v-model="formDataModel.urlTutorial" /> |
| 57 | </el-form-item> | 57 | </el-form-item> |
| 58 | <el-form-item label="动作步骤描述:" prop="stepDescription" label-suffix="*" class="desc-item"> | 58 | <el-form-item label="动作步骤描述:" prop="stepDescription" label-suffix="*" class="desc-item"> |
| @@ -68,17 +68,20 @@ | @@ -68,17 +68,20 @@ | ||
| 68 | 68 | ||
| 69 | <script setup> | 69 | <script setup> |
| 70 | 70 | ||
| 71 | -import { ref, reactive, watch, onMounted, nextTick } from 'vue' | 71 | +import { ref, reactive, watch, onMounted, nextTick, computed } from 'vue' |
| 72 | import { ElMessage } from 'element-plus' | 72 | import { ElMessage } from 'element-plus' |
| 73 | import { MotionCategoryApi } from '@/api/store/training/partCategory' | 73 | import { MotionCategoryApi } from '@/api/store/training/partCategory' |
| 74 | import { MusclesApi } from '@/api/store/training/muscle' | 74 | import { MusclesApi } from '@/api/store/training/muscle' |
| 75 | import { EquipmentsApi } from '@/api/store/training/tool' | 75 | import { EquipmentsApi } from '@/api/store/training/tool' |
| 76 | import { Editor } from '@/components/Editor' | 76 | import { Editor } from '@/components/Editor' |
| 77 | import UploadVideo from '@/components/UploadFile/src/UploadVideo.vue' | 77 | import UploadVideo from '@/components/UploadFile/src/UploadVideo.vue' |
| 78 | +import { ExercisesApi } from '@/api/store/training/pose' | ||
| 78 | 79 | ||
| 79 | // 1. 自定义 v-model:弹窗显隐(对应父组件 v-model:visible) | 80 | // 1. 自定义 v-model:弹窗显隐(对应父组件 v-model:visible) |
| 80 | const dialogVisible = defineModel('visible', { type: Boolean, default: false }) | 81 | const dialogVisible = defineModel('visible', { type: Boolean, default: false }) |
| 81 | 82 | ||
| 83 | +const defaultImg = 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260424/muscle_back_1777018976173.png' | ||
| 84 | + | ||
| 82 | // 2. 自定义 v-model:表单数据(对应父组件 v-model:formData) | 85 | // 2. 自定义 v-model:表单数据(对应父组件 v-model:formData) |
| 83 | const formDataModel = defineModel('formData', { | 86 | const formDataModel = defineModel('formData', { |
| 84 | type: Object, // 新增这一行 | 87 | type: Object, // 新增这一行 |
| @@ -113,10 +116,43 @@ watch(dialogVisible, async (newVal) => { | @@ -113,10 +116,43 @@ watch(dialogVisible, async (newVal) => { | ||
| 113 | loadToolList(), | 116 | loadToolList(), |
| 114 | loadMuscleList() | 117 | loadMuscleList() |
| 115 | ]) | 118 | ]) |
| 119 | + const form = formDataModel.value | ||
| 120 | + // 调试打印 | ||
| 121 | + console.log('解析前-原始数据:', form) | ||
| 122 | + console.log('解析前-primaryMuscles:', form.primaryMuscles, typeof form.primaryMuscles) | ||
| 123 | + console.log('解析前-secondaryMuscles:', form.secondaryMuscles, typeof form.secondaryMuscles) | ||
| 124 | + if (typeof form.primaryMuscles === 'string') { | ||
| 125 | + try { | ||
| 126 | + // 解析字符串 "[17, 16]" → 数组 [17, 16] | ||
| 127 | + formDataModel.value.primaryMuscles = JSON.parse(form.primaryMuscles) | ||
| 128 | + } catch (err) { | ||
| 129 | + // 如果解析失败(比如空字符串、非法格式),就默认设为空数组 | ||
| 130 | + console.warn('primaryMuscles 解析失败,已重置为空数组', err) | ||
| 131 | + formDataModel.value.primaryMuscles = [] | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + // 处理 secondaryMuscles(和上面逻辑一样) | ||
| 136 | + if (typeof form.secondaryMuscles === 'string') { | ||
| 137 | + try { | ||
| 138 | + formDataModel.value.secondaryMuscles = JSON.parse(form.secondaryMuscles) | ||
| 139 | + } catch (err) { | ||
| 140 | + console.warn('secondaryMuscles 解析失败,已重置为空数组', err) | ||
| 141 | + formDataModel.value.secondaryMuscles = [] | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | + // ------------------------------------------------------------- | ||
| 145 | + | ||
| 146 | + // 解析完成后,再打印一次,确认变成了数组 | ||
| 147 | + console.log('解析后-primaryMuscles:', formDataModel.value.primaryMuscles, typeof formDataModel.value.primaryMuscles) | ||
| 148 | + console.log('解析后-secondaryMuscles:', formDataModel.value.secondaryMuscles, typeof formDataModel.value.secondaryMuscles) | ||
| 149 | + | ||
| 116 | // 列表加载完后,强制更新表单,触发el-select回显 | 150 | // 列表加载完后,强制更新表单,触发el-select回显 |
| 117 | nextTick(() => { | 151 | nextTick(() => { |
| 118 | if (formRef.value) { | 152 | if (formRef.value) { |
| 119 | formRef.value.clearValidate() | 153 | formRef.value.clearValidate() |
| 154 | + | ||
| 155 | + | ||
| 120 | } | 156 | } |
| 121 | }) | 157 | }) |
| 122 | } | 158 | } |
| @@ -140,13 +176,26 @@ const formRules = reactive({ | @@ -140,13 +176,26 @@ const formRules = reactive({ | ||
| 140 | categoryId: [{ required: true, message: '请选择部位分类', trigger: 'change' }], | 176 | categoryId: [{ required: true, message: '请选择部位分类', trigger: 'change' }], |
| 141 | equipmentId: [{ required: true, message: '请选择用具分类', trigger: 'change' }], | 177 | equipmentId: [{ required: true, message: '请选择用具分类', trigger: 'change' }], |
| 142 | exerciseType: [{ required: true, message: '请选择动作类型', trigger: 'change' }], | 178 | exerciseType: [{ required: true, message: '请选择动作类型', trigger: 'change' }], |
| 143 | - primaryMuscles: [{ required: true, message: '请输入主要训练部位', trigger: 'blur' }], | ||
| 144 | - secondaryMuscles: [{ required: true, message: '请输入次要训练部位', trigger: 'blur' }], | ||
| 145 | - urlImage: [{ required: true, message: '请上传图片', trigger: 'blur' }], | 179 | + primaryMuscles: [{ required: true, message: '请输入主要训练部位', trigger: 'change' }], |
| 180 | + secondaryMuscles: [{ required: true, message: '请输入次要训练部位', trigger: 'change' }], | ||
| 181 | + urlImage: [{ required: false, message: '请上传图片', trigger: 'blur' }], | ||
| 182 | + urlRealPerson: [{ required: true, message: '请上传真人演示地址', trigger: 'blur' }], | ||
| 183 | + urlTutorial: [{ required: true, message: '请上传教程地址', trigger: 'blur' }], | ||
| 146 | url3dAnimation: [{ required: true, message: '请上传3D动图', trigger: 'blur' }], | 184 | url3dAnimation: [{ required: true, message: '请上传3D动图', trigger: 'blur' }], |
| 147 | stepDescription: [{ required: true, message: '请输入动作步骤', trigger: 'blur' }], | 185 | stepDescription: [{ required: true, message: '请输入动作步骤', trigger: 'blur' }], |
| 148 | }) | 186 | }) |
| 149 | 187 | ||
| 188 | +// 次要部位选项 = 全部肌肉 - 已选主要部位 | ||
| 189 | +const secondaryMuscleOptions = computed(() => { | ||
| 190 | + const primaryIds = formDataModel.value.primaryMuscles || [] | ||
| 191 | + return muscleList.value.filter(item => !primaryIds.includes(item.id)) | ||
| 192 | +}) | ||
| 193 | + | ||
| 194 | +// 主要部位选项 = 全部肌肉 - 已选次要部位 | ||
| 195 | +const primaryMuscleOptions = computed(() => { | ||
| 196 | + const secondaryIds = formDataModel.value.secondaryMuscles || [] | ||
| 197 | + return muscleList.value.filter(item => !secondaryIds.includes(item.id)) | ||
| 198 | +}) | ||
| 150 | 199 | ||
| 151 | 200 | ||
| 152 | // 取消按钮:直接关闭弹窗(自动同步给父组件) | 201 | // 取消按钮:直接关闭弹窗(自动同步给父组件) |
| @@ -159,27 +208,65 @@ const handleCancel = () => { | @@ -159,27 +208,65 @@ const handleCancel = () => { | ||
| 159 | const handleSubmit = async () => { | 208 | const handleSubmit = async () => { |
| 160 | if (!formRef.value) return | 209 | if (!formRef.value) return |
| 161 | try { | 210 | try { |
| 211 | + console.log('123456'); | ||
| 212 | + | ||
| 162 | await formRef.value.validate() | 213 | await formRef.value.validate() |
| 214 | + console.log('123456'); | ||
| 163 | formLoading.value = true | 215 | formLoading.value = true |
| 164 | - // 👇 加这行!看原始数据是不是嵌套数组 | ||
| 165 | console.log('原始primaryMuscles:', formDataModel.value.primaryMuscles) | 216 | console.log('原始primaryMuscles:', formDataModel.value.primaryMuscles) |
| 166 | console.log('原始secondaryMuscles:', formDataModel.value.secondaryMuscles) | 217 | console.log('原始secondaryMuscles:', formDataModel.value.secondaryMuscles) |
| 167 | - // 👇 核心处理:将数组转为逗号分隔的字符串 | 218 | + |
| 219 | + const form = formDataModel.value | ||
| 168 | const submitData = { | 220 | const submitData = { |
| 169 | ...formDataModel.value, | 221 | ...formDataModel.value, |
| 170 | - // 如果数组为空,传空字符串 "" 否则会变成 "undefined" | ||
| 171 | - primaryMuscles: formDataModel.value.primaryMuscles || [], | ||
| 172 | - secondaryMuscles: formDataModel.value.secondaryMuscles || [], | 222 | + urlImage: form.urlImage || defaultImg, |
| 223 | + // primaryMuscles: formDataModel.value.primaryMuscles || [], | ||
| 224 | + // secondaryMuscles: formDataModel.value.secondaryMuscles || [], | ||
| 225 | + primaryMuscles: JSON.stringify(formDataModel.value.primaryMuscles || []), | ||
| 226 | + secondaryMuscles: JSON.stringify(formDataModel.value.secondaryMuscles || []), | ||
| 227 | + } | ||
| 228 | + console.log('------------------'); | ||
| 229 | + | ||
| 230 | + if (submitData.id) { | ||
| 231 | + await ExercisesApi.updateExercises(submitData) // 编辑:调用更新接口 | ||
| 232 | + console.log('动作编辑接口数据', submitData) | ||
| 233 | + ElMessage.success('动作编辑成功!') | ||
| 234 | + } else { | ||
| 235 | + console.log('动作新增接口数据', submitData) | ||
| 236 | + await ExercisesApi.addExercises(submitData) // 新增:调用创建接口 | ||
| 237 | + ElMessage.success('动作新增成功!') | ||
| 173 | } | 238 | } |
| 174 | // 触发提交事件,数据已通过 v-model 同步,直接传即可 | 239 | // 触发提交事件,数据已通过 v-model 同步,直接传即可 |
| 175 | - emit('submit', submitData) | 240 | + emit('submit') |
| 176 | } catch (error) { | 241 | } catch (error) { |
| 177 | ElMessage.error('表单校验失败,请检查必填项') | 242 | ElMessage.error('表单校验失败,请检查必填项') |
| 178 | } finally { | 243 | } finally { |
| 179 | formLoading.value = false | 244 | formLoading.value = false |
| 245 | + // resetForm() | ||
| 180 | } | 246 | } |
| 181 | } | 247 | } |
| 182 | 248 | ||
| 249 | + | ||
| 250 | +// const handleFormSubmit = async (submitData) => { | ||
| 251 | +// try { | ||
| 252 | +// // ✅ 直接传数组,axios 自动转成 JSON 字符串 | ||
| 253 | +// const formatData = { | ||
| 254 | +// ...submitData, | ||
| 255 | +// primaryMuscles: JSON.stringify(submitData.primaryMuscles || []), | ||
| 256 | +// secondaryMuscles: JSON.stringify(submitData.secondaryMuscles || []) | ||
| 257 | +// } | ||
| 258 | +// console.log('提交的数据', formatData); | ||
| 259 | + | ||
| 260 | +// // 关闭弹窗 | ||
| 261 | +// handleDialogClose() | ||
| 262 | +// // 重新查询数据 | ||
| 263 | +// handleQuery() | ||
| 264 | +// } catch (err) { | ||
| 265 | +// console.error('提交失败:', err) | ||
| 266 | +// ElMessage.error(submitData.id ? '更新失败,请稍后重试' : '新增失败,请稍后重试') | ||
| 267 | +// } | ||
| 268 | +// } | ||
| 269 | + | ||
| 183 | // 重置表单(只操作本地 model,自动同步父组件) | 270 | // 重置表单(只操作本地 model,自动同步父组件) |
| 184 | const resetForm = () => { | 271 | const resetForm = () => { |
| 185 | Object.assign(formDataModel.value, { | 272 | Object.assign(formDataModel.value, { |
| @@ -278,13 +365,22 @@ defineExpose({ resetForm }) | @@ -278,13 +365,22 @@ defineExpose({ resetForm }) | ||
| 278 | :deep(.el-form-item.upload-img-item .el-form-item__label) { | 365 | :deep(.el-form-item.upload-img-item .el-form-item__label) { |
| 279 | /* 上传组件高度高,让 label 顶部对齐 */ | 366 | /* 上传组件高度高,让 label 顶部对齐 */ |
| 280 | line-height: 120px !important; | 367 | line-height: 120px !important; |
| 368 | + | ||
| 281 | } | 369 | } |
| 282 | 370 | ||
| 371 | +/* .upload-img-item { | ||
| 372 | + margin-bottom: 20rpx; | ||
| 373 | +} */ | ||
| 374 | + | ||
| 283 | /* 单独给封面图增加上下间距 */ | 375 | /* 单独给封面图增加上下间距 */ |
| 284 | .form-item-margin { | 376 | .form-item-margin { |
| 285 | margin: 30px 5px !important; | 377 | margin: 30px 5px !important; |
| 286 | } | 378 | } |
| 287 | 379 | ||
| 380 | +.upload-3d-item { | ||
| 381 | + margin-top: 20px !important; | ||
| 382 | +} | ||
| 383 | + | ||
| 288 | .upload-box { | 384 | .upload-box { |
| 289 | display: flex; | 385 | display: flex; |
| 290 | width: 120px; | 386 | width: 120px; |
| @@ -32,14 +32,15 @@ | @@ -32,14 +32,15 @@ | ||
| 32 | <!-- 表格区域 --> | 32 | <!-- 表格区域 --> |
| 33 | <ContentWrap> | 33 | <ContentWrap> |
| 34 | <el-table :data="tableData" stripe style="width: 100%" v-loading="loading" empty-text="暂无动作数据" | 34 | <el-table :data="tableData" stripe style="width: 100%" v-loading="loading" empty-text="暂无动作数据" |
| 35 | - :header-cell-style="{ background: '#f5f7fa', textAlign: 'center' }" :cell-style="{ textAlign: 'center' }"> | 35 | + :header-cell-style="{ background: '#f5f7fa', textAlign: 'center' }" |
| 36 | + :cell-style="{ textAlign: 'center', justifyContent: 'center' }"> | ||
| 36 | <el-table-column prop="name" label="动作名称" min-width="120" /> | 37 | <el-table-column prop="name" label="动作名称" min-width="120" /> |
| 37 | <!-- 封面 --> | 38 | <!-- 封面 --> |
| 38 | - <el-table-column prop="urlImage" label="封面" align="center" min-width="120"> | 39 | + <el-table-column prop="url3dAnimation" label="封面" align="center" min-width="120"> |
| 39 | <template #default="scope"> | 40 | <template #default="scope"> |
| 40 | <div class="cover-wrapper"> | 41 | <div class="cover-wrapper"> |
| 41 | <!-- 优先显示行数据封面,无则显示占位图 --> | 42 | <!-- 优先显示行数据封面,无则显示占位图 --> |
| 42 | - <img class="cover-img" :src="scope.row.urlImage || lostImg" alt="模板封面" /> | 43 | + <img class="cover-img" :src="scope.row.url3dAnimation || lostImg" alt="模板封面" /> |
| 43 | </div> | 44 | </div> |
| 44 | </template> | 45 | </template> |
| 45 | </el-table-column> | 46 | </el-table-column> |
| @@ -59,10 +60,11 @@ | @@ -59,10 +60,11 @@ | ||
| 59 | </el-table> | 60 | </el-table> |
| 60 | 61 | ||
| 61 | <!-- 分页组件 --> | 62 | <!-- 分页组件 --> |
| 62 | - <div class="pagination-wrapper"> | ||
| 63 | - <span class="goto-text">共 {{ total }} 条</span> | ||
| 64 | - <el-pagination v-model:current-page="pagination.pageNo" v-model:page-size="pagination.pageSize" | ||
| 65 | - :page-sizes="[10, 20, 50, 100]" :total="total" background layout="prev, pager, next, jumper, ->, sizes, total" | 63 | + <div class="pagination-wrapper" style="padding: 12px 16px;"> |
| 64 | + <!-- <span class="goto-text">共 {{ total }} 条</span> --> | ||
| 65 | + <!-- 共 {{ total }} 条 --> | ||
| 66 | + <el-pagination v-model:current-page="pagination.pageNo" v-model:page-size="pagination.pageSize" :total="total" | ||
| 67 | + :page-sizes="[10, 20, 50]" background layout="total, sizes, prev, pager, next, jumper" | ||
| 66 | @size-change="handleSizeChange" @current-change="handleCurrentChange" /> | 68 | @size-change="handleSizeChange" @current-change="handleCurrentChange" /> |
| 67 | </div> | 69 | </div> |
| 68 | </ContentWrap> | 70 | </ContentWrap> |
| @@ -192,40 +194,46 @@ const handleDelete = async (row: ExercisesVO) => { | @@ -192,40 +194,46 @@ const handleDelete = async (row: ExercisesVO) => { | ||
| 192 | } | 194 | } |
| 193 | 195 | ||
| 194 | // 处理表单提交(新增/编辑) | 196 | // 处理表单提交(新增/编辑) |
| 195 | -const handleFormSubmit = async (submitData: ExercisesSaveReqVO) => { | ||
| 196 | - try { | ||
| 197 | - // ✅ 直接传数组,axios 自动转成 JSON 字符串 | ||
| 198 | - const formatData = { | ||
| 199 | - ...submitData, | ||
| 200 | - primaryMuscles: JSON.stringify(submitData.primaryMuscles || []), | ||
| 201 | - secondaryMuscles: JSON.stringify(submitData.secondaryMuscles || []) | ||
| 202 | - } | ||
| 203 | - | ||
| 204 | - if (submitData.id) { | ||
| 205 | - await ExercisesApi.updateExercises(formatData) // 编辑:调用更新接口 | ||
| 206 | - console.log('动作编辑接口数据', submitData) | ||
| 207 | - ElMessage.success('动作编辑成功!') | ||
| 208 | - } else { | ||
| 209 | - console.log('动作新增接口数据', submitData) | ||
| 210 | - await ExercisesApi.addExercises(formatData) // 新增:调用创建接口 | ||
| 211 | - ElMessage.success('动作新增成功!') | ||
| 212 | - } | ||
| 213 | - // 关闭弹窗 | ||
| 214 | - handleDialogClose() | ||
| 215 | - // 重新查询数据 | 197 | +// const handleFormSubmit = async (submitData: ExercisesSaveReqVO) => { |
| 198 | +// try { | ||
| 199 | +// // ✅ 直接传数组,axios 自动转成 JSON 字符串 | ||
| 200 | +// const formatData = { | ||
| 201 | +// ...submitData, | ||
| 202 | +// primaryMuscles: JSON.stringify(submitData.primaryMuscles || []), | ||
| 203 | +// secondaryMuscles: JSON.stringify(submitData.secondaryMuscles || []) | ||
| 204 | +// } | ||
| 205 | +// console.log('提交的数据', formatData); | ||
| 206 | +// if (submitData.id) { | ||
| 207 | +// await ExercisesApi.updateExercises(formatData) // 编辑:调用更新接口 | ||
| 208 | +// console.log('动作编辑接口数据', submitData) | ||
| 209 | +// ElMessage.success('动作编辑成功!') | ||
| 210 | +// } else { | ||
| 211 | +// console.log('动作新增接口数据', submitData) | ||
| 212 | +// await ExercisesApi.addExercises(formatData) // 新增:调用创建接口 | ||
| 213 | +// ElMessage.success('动作新增成功!') | ||
| 214 | +// } | ||
| 215 | +// // 关闭弹窗 | ||
| 216 | +// handleDialogClose() | ||
| 217 | +// // 重新查询数据 | ||
| 218 | +// handleQuery() | ||
| 219 | +// } catch (err) { | ||
| 220 | +// console.error('提交失败:', err) | ||
| 221 | +// ElMessage.error(submitData.id ? '更新失败,请稍后重试' : '新增失败,请稍后重试') | ||
| 222 | +// } | ||
| 223 | +// } | ||
| 224 | + | ||
| 225 | +const handleFormSubmit = () => { | ||
| 216 | handleQuery() | 226 | handleQuery() |
| 217 | - } catch (err) { | ||
| 218 | - console.error('提交失败:', err) | ||
| 219 | - ElMessage.error(submitData.id ? '更新失败,请稍后重试' : '新增失败,请稍后重试') | ||
| 220 | - } | ||
| 221 | -} | ||
| 222 | - | ||
| 223 | -// 关闭弹窗 | ||
| 224 | -const handleDialogClose = () => { | ||
| 225 | dialogVisible.value = false | 227 | dialogVisible.value = false |
| 226 | resetFormData() | 228 | resetFormData() |
| 227 | } | 229 | } |
| 228 | 230 | ||
| 231 | +// 关闭弹窗 | ||
| 232 | +// const handleDialogClose = () => { | ||
| 233 | +// dialogVisible.value = false | ||
| 234 | +// resetFormData() | ||
| 235 | +// } | ||
| 236 | + | ||
| 229 | // 重置表单数据 | 237 | // 重置表单数据 |
| 230 | const resetFormData = () => { | 238 | const resetFormData = () => { |
| 231 | Object.assign(formData, { | 239 | Object.assign(formData, { |
| 1 | <template> | 1 | <template> |
| 2 | <div class="page-container"> | 2 | <div class="page-container"> |
| 3 | - <el-card class="search-bar"> | 3 | + <ContentWrap class="search-bar"> |
| 4 | <div class="search-item"> | 4 | <div class="search-item"> |
| 5 | <label class="search-label">学员</label> | 5 | <label class="search-label">学员</label> |
| 6 | <el-input v-model="searchForm.name" placeholder="请输入" class="search-input" @keyup.enter="handleSearch" | 6 | <el-input v-model="searchForm.name" placeholder="请输入" class="search-input" @keyup.enter="handleSearch" |
| @@ -26,10 +26,11 @@ | @@ -26,10 +26,11 @@ | ||
| 26 | </el-button> --> | 26 | </el-button> --> |
| 27 | </div> | 27 | </div> |
| 28 | </div> | 28 | </div> |
| 29 | - </el-card> | 29 | + </ContentWrap> |
| 30 | 30 | ||
| 31 | <!-- 列表区域 --> | 31 | <!-- 列表区域 --> |
| 32 | - <el-card class="table-container"> | 32 | + <!-- <div class="table-container"> --> |
| 33 | + <ContentWrap> | ||
| 33 | <el-table :data="tableData" v-loading="loading" empty-text="暂无数据" border> | 34 | <el-table :data="tableData" v-loading="loading" empty-text="暂无数据" border> |
| 34 | <el-table-column prop="username" label="学员" align="center" /> | 35 | <el-table-column prop="username" label="学员" align="center" /> |
| 35 | <el-table-column prop="name" label="训练计划名称" align="center" /> | 36 | <el-table-column prop="name" label="训练计划名称" align="center" /> |
| @@ -54,7 +55,8 @@ | @@ -54,7 +55,8 @@ | ||
| 54 | :total="pagination.total" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" | 55 | :total="pagination.total" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" |
| 55 | style="text-align: left;" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> | 56 | style="text-align: left;" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> |
| 56 | </div> | 57 | </div> |
| 57 | - </el-card> | 58 | + </ContentWrap> |
| 59 | + <!-- </div> --> | ||
| 58 | <!-- 详情弹窗 --> | 60 | <!-- 详情弹窗 --> |
| 59 | <StuTrainingDetailDialog v-model:visible="detailVisible" :id="currentId" /> | 61 | <StuTrainingDetailDialog v-model:visible="detailVisible" :id="currentId" /> |
| 60 | </div> | 62 | </div> |
| @@ -184,9 +186,9 @@ onMounted(() => { | @@ -184,9 +186,9 @@ onMounted(() => { | ||
| 184 | }) | 186 | }) |
| 185 | </script> | 187 | </script> |
| 186 | <style scoped> | 188 | <style scoped> |
| 187 | -.page-container { | ||
| 188 | - /* padding: 20px; */ | ||
| 189 | -} | 189 | +/* .page-container { |
| 190 | + | ||
| 191 | +} */ | ||
| 190 | 192 | ||
| 191 | /* 分页居右 */ | 193 | /* 分页居右 */ |
| 192 | .pagination-wrapper { | 194 | .pagination-wrapper { |
| @@ -196,13 +198,16 @@ onMounted(() => { | @@ -196,13 +198,16 @@ onMounted(() => { | ||
| 196 | } | 198 | } |
| 197 | 199 | ||
| 198 | .search-bar { | 200 | .search-bar { |
| 201 | + /* margin-bottom: 20px; */ | ||
| 199 | margin-bottom: 20px; | 202 | margin-bottom: 20px; |
| 203 | + box-shadow: 0 1px 2px rgb(0 0 0 / 5%); | ||
| 200 | } | 204 | } |
| 201 | 205 | ||
| 202 | .search-item { | 206 | .search-item { |
| 203 | display: flex; | 207 | display: flex; |
| 204 | align-items: center; | 208 | align-items: center; |
| 205 | gap: 10px; | 209 | gap: 10px; |
| 210 | + padding: 15px 20px; | ||
| 206 | } | 211 | } |
| 207 | 212 | ||
| 208 | .search-label { | 213 | .search-label { |
| 1 | <template> | 1 | <template> |
| 2 | <div class="supersets-page"> | 2 | <div class="supersets-page"> |
| 3 | - <ContentWrap> | 3 | + <ContentWrap class="plan-search-bar"> |
| 4 | <!-- 1. 搜索栏区域 --> | 4 | <!-- 1. 搜索栏区域 --> |
| 5 | <div class="search-bar"> | 5 | <div class="search-bar"> |
| 6 | <div class="search-item"> | 6 | <div class="search-item"> |
| @@ -21,7 +21,7 @@ | @@ -21,7 +21,7 @@ | ||
| 21 | </el-icon> | 21 | </el-icon> |
| 22 | 重置 | 22 | 重置 |
| 23 | </el-button> | 23 | </el-button> |
| 24 | - <el-button @click="openAddDialog"> | 24 | + <el-button type="primary" @click="openAddDialog"> |
| 25 | <el-icon> | 25 | <el-icon> |
| 26 | <Plus /> | 26 | <Plus /> |
| 27 | </el-icon> | 27 | </el-icon> |
| @@ -401,7 +401,12 @@ onMounted(async () => { | @@ -401,7 +401,12 @@ onMounted(async () => { | ||
| 401 | box-sizing: border-box; | 401 | box-sizing: border-box; |
| 402 | } | 402 | } |
| 403 | 403 | ||
| 404 | -.search-bar { | 404 | +.plan-search-bar { |
| 405 | + margin-bottom: 20px; | ||
| 406 | + box-shadow: 0 1px 2px rgb(0 0 0 / 5%); | ||
| 407 | +} | ||
| 408 | + | ||
| 409 | +/* .search-bar { | ||
| 405 | display: flex; | 410 | display: flex; |
| 406 | align-items: center; | 411 | align-items: center; |
| 407 | padding: 16px; | 412 | padding: 16px; |
| @@ -410,13 +415,13 @@ onMounted(async () => { | @@ -410,13 +415,13 @@ onMounted(async () => { | ||
| 410 | border-radius: 4px; | 415 | border-radius: 4px; |
| 411 | gap: 10px; | 416 | gap: 10px; |
| 412 | flex-wrap: nowrap; | 417 | flex-wrap: nowrap; |
| 413 | -} | 418 | +} */ |
| 414 | 419 | ||
| 415 | .search-item { | 420 | .search-item { |
| 416 | display: flex; | 421 | display: flex; |
| 417 | align-items: center; | 422 | align-items: center; |
| 418 | gap: 10px; | 423 | gap: 10px; |
| 419 | - width: 100%; | 424 | + padding: 15px 20px; |
| 420 | } | 425 | } |
| 421 | 426 | ||
| 422 | .search-label { | 427 | .search-label { |
| @@ -2,16 +2,14 @@ | @@ -2,16 +2,14 @@ | ||
| 2 | <template> | 2 | <template> |
| 3 | <div class="page-container"> | 3 | <div class="page-container"> |
| 4 | <!-- 搜索框区域 --> | 4 | <!-- 搜索框区域 --> |
| 5 | - <div class="equipments-page"> | ||
| 6 | - <ContentWrap> | 5 | + <!-- <div class="equipments-page"> --> |
| 6 | + <ContentWrap class="plan-search-bar"> | ||
| 7 | <!-- 搜索栏 --> | 7 | <!-- 搜索栏 --> |
| 8 | - <div class="search-bar" style="padding: 16px; margin-bottom: 16px; background: #fff; border-radius: 4px;"> | ||
| 9 | - <el-form :inline="true" :model="searchForm" class="demo-form-inline"> | ||
| 10 | - <el-form-item label="文件夹名称:"> | 8 | + <div class="search-bar"> |
| 9 | + <label class="search-label">文件夹名称</label> | ||
| 11 | <el-input v-model="searchForm.name" placeholder="请输入" style="width: 200px;" size="default" | 10 | <el-input v-model="searchForm.name" placeholder="请输入" style="width: 200px;" size="default" |
| 12 | @keyup.enter="handleSearch" /> | 11 | @keyup.enter="handleSearch" /> |
| 13 | - </el-form-item> | ||
| 14 | - <el-form-item> | 12 | + <div class="search-buttons"> |
| 15 | <el-button type="primary" @click="handleSearch" size="default"> | 13 | <el-button type="primary" @click="handleSearch" size="default"> |
| 16 | <el-icon> | 14 | <el-icon> |
| 17 | <Search /> | 15 | <Search /> |
| @@ -24,14 +22,13 @@ | @@ -24,14 +22,13 @@ | ||
| 24 | </el-icon> | 22 | </el-icon> |
| 25 | 重置 | 23 | 重置 |
| 26 | </el-button> | 24 | </el-button> |
| 27 | - <el-button class="add-btn" @click="handleAdd" size="default"> | 25 | + <el-button type="primary" @click="handleAdd" size="default"> |
| 28 | <el-icon> | 26 | <el-icon> |
| 29 | <Plus /> | 27 | <Plus /> |
| 30 | </el-icon> | 28 | </el-icon> |
| 31 | 新增 | 29 | 新增 |
| 32 | </el-button> | 30 | </el-button> |
| 33 | - </el-form-item> | ||
| 34 | - </el-form> | 31 | + </div> |
| 35 | </div> | 32 | </div> |
| 36 | </ContentWrap> | 33 | </ContentWrap> |
| 37 | <!-- 表格区域 --> | 34 | <!-- 表格区域 --> |
| @@ -57,14 +54,19 @@ | @@ -57,14 +54,19 @@ | ||
| 57 | </el-table-column> | 54 | </el-table-column> |
| 58 | </el-table> | 55 | </el-table> |
| 59 | </div> | 56 | </div> |
| 57 | + | ||
| 58 | + <div class="pagination-wrapper" style="padding: 12px 16px;"> | ||
| 59 | + <el-pagination v-model:current-page="pagination.pageNo" v-model:page-size="pagination.pageSize" | ||
| 60 | + :total="pagination.total" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" | ||
| 61 | + style="text-align: left;" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> | ||
| 62 | + </div> | ||
| 60 | </ContentWrap> | 63 | </ContentWrap> |
| 61 | 64 | ||
| 62 | <!-- 编辑/新增弹窗 --> | 65 | <!-- 编辑/新增弹窗 --> |
| 63 | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> | 66 | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> |
| 64 | <el-form ref="formRef" :model="formData" :rules="formRules" label-width="150px"> | 67 | <el-form ref="formRef" :model="formData" :rules="formRules" label-width="150px"> |
| 65 | <el-form-item label="大类分类名称:" prop="name" label-suffix="*"> | 68 | <el-form-item label="大类分类名称:" prop="name" label-suffix="*"> |
| 66 | - <el-input v-model="formData.name" placeholder="请输入分类名称" maxlength="50" show-word-limit | ||
| 67 | - style="width: 100%" /> | 69 | + <el-input v-model="formData.name" placeholder="请输入分类名称" maxlength="50" show-word-limit style="width: 100%" /> |
| 68 | </el-form-item> | 70 | </el-form-item> |
| 69 | <el-form-item label="模板" prop="templateIds"> | 71 | <el-form-item label="模板" prop="templateIds"> |
| 70 | <el-select v-model="formData.templateIds" placeholder="请选择模板" multiple clearable style="width: 100%" | 72 | <el-select v-model="formData.templateIds" placeholder="请选择模板" multiple clearable style="width: 100%" |
| @@ -73,8 +75,7 @@ | @@ -73,8 +75,7 @@ | ||
| 73 | </el-select> | 75 | </el-select> |
| 74 | </el-form-item> | 76 | </el-form-item> |
| 75 | <el-form-item label="封面" prop="urlCover" label-suffix="*"> | 77 | <el-form-item label="封面" prop="urlCover" label-suffix="*"> |
| 76 | - <UploadImg v-model="formData.urlCover" placeholder="封面" maxlength="50" show-word-limit | ||
| 77 | - style="width: 100%" /> | 78 | + <UploadImg v-model="formData.urlCover" placeholder="封面" maxlength="50" show-word-limit style="width: 100%" /> |
| 78 | </el-form-item> | 79 | </el-form-item> |
| 79 | <el-form-item label="模板大类介绍" prop="description" label-suffix="*"> | 80 | <el-form-item label="模板大类介绍" prop="description" label-suffix="*"> |
| 80 | <el-input v-model="formData.description" placeholder="请输入模板大类介绍" maxlength="50" show-word-limit | 81 | <el-input v-model="formData.description" placeholder="请输入模板大类介绍" maxlength="50" show-word-limit |
| @@ -89,12 +90,8 @@ | @@ -89,12 +90,8 @@ | ||
| 89 | 90 | ||
| 90 | 91 | ||
| 91 | <!-- 分页组件 --> | 92 | <!-- 分页组件 --> |
| 92 | - <div class="pagination-wrapper" style="padding: 12px 16px;"> | ||
| 93 | - <el-pagination v-model:current-page="pagination.pageNo" v-model:page-size="pagination.pageSize" | ||
| 94 | - :total="pagination.total" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" | ||
| 95 | - style="text-align: left;" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> | ||
| 96 | - </div> | ||
| 97 | - </div> | 93 | + |
| 94 | + <!-- </div> --> | ||
| 98 | 95 | ||
| 99 | </div> | 96 | </div> |
| 100 | </template> | 97 | </template> |
| @@ -306,6 +303,31 @@ onMounted(() => { | @@ -306,6 +303,31 @@ onMounted(() => { | ||
| 306 | }) | 303 | }) |
| 307 | </script> | 304 | </script> |
| 308 | <style scoped> | 305 | <style scoped> |
| 306 | +.plan-search-bar { | ||
| 307 | + margin-bottom: 20px; | ||
| 308 | + box-shadow: 0 1px 2px rgb(0 0 0 / 5%); | ||
| 309 | +} | ||
| 310 | + | ||
| 311 | +.search-bar { | ||
| 312 | + display: flex; | ||
| 313 | + align-items: center; | ||
| 314 | + gap: 10px; | ||
| 315 | + padding: 15px 20px; | ||
| 316 | + | ||
| 317 | +} | ||
| 318 | + | ||
| 319 | +.search-buttons { | ||
| 320 | + display: flex; | ||
| 321 | + gap: 10px; | ||
| 322 | + margin-left: 10px; | ||
| 323 | +} | ||
| 324 | + | ||
| 325 | +.pagination-wrapper { | ||
| 326 | + display: flex; | ||
| 327 | + padding: 12px 16px; | ||
| 328 | + justify-content: flex-end; | ||
| 329 | +} | ||
| 330 | + | ||
| 309 | /* 新样式:更美观、不变大小、不影响其他布局 */ | 331 | /* 新样式:更美观、不变大小、不影响其他布局 */ |
| 310 | .cover-img { | 332 | .cover-img { |
| 311 | width: 100px; | 333 | width: 100px; |
| @@ -41,6 +41,7 @@ | @@ -41,6 +41,7 @@ | ||
| 41 | 41 | ||
| 42 | <!-- 自重加重/减重 --> | 42 | <!-- 自重加重/减重 --> |
| 43 | <th v-if="type === 4 || type === 5" width="120">重量</th> | 43 | <th v-if="type === 4 || type === 5" width="120">重量</th> |
| 44 | + <th v-if="type === 4 || type === 5" width="120">次数</th> | ||
| 44 | <th v-if="type === 4 || type === 5" width="140">休息时间(秒)</th> | 45 | <th v-if="type === 4 || type === 5" width="140">休息时间(秒)</th> |
| 45 | 46 | ||
| 46 | <th v-if="type === 6" width="140">组数</th> | 47 | <th v-if="type === 6" width="140">组数</th> |
| @@ -134,6 +135,10 @@ | @@ -134,6 +135,10 @@ | ||
| 134 | @change="val => handleFieldChange(setIndex, 'weight', val)" /> | 135 | @change="val => handleFieldChange(setIndex, 'weight', val)" /> |
| 135 | </td> | 136 | </td> |
| 136 | <td v-if="type === 4 || type === 5"> | 137 | <td v-if="type === 4 || type === 5"> |
| 138 | + <el-input-number :model-value="set.reps" :min="0" style="width: 80%" | ||
| 139 | + @change="val => handleFieldChange(setIndex, 'reps', val)" /> | ||
| 140 | + </td> | ||
| 141 | + <td v-if="type === 4 || type === 5"> | ||
| 137 | <el-input-number :model-value="set.restTime" :min="0" style="width: 80%" | 142 | <el-input-number :model-value="set.restTime" :min="0" style="width: 80%" |
| 138 | @change="val => handleFieldChange(setIndex, 'restTime', val)" /> | 143 | @change="val => handleFieldChange(setIndex, 'restTime', val)" /> |
| 139 | </td> | 144 | </td> |
| @@ -217,7 +222,8 @@ watch(() => props.type, () => initAll(), { immediate: true }) | @@ -217,7 +222,8 @@ watch(() => props.type, () => initAll(), { immediate: true }) | ||
| 217 | 222 | ||
| 218 | // 新增 | 223 | // 新增 |
| 219 | const addSet = () => { | 224 | const addSet = () => { |
| 220 | - const newSet = { weight: 0, reps: 0, duration: 0, distance: 0, restTime: 0 } | 225 | + const newSetIndex = props.sets.length + 1; |
| 226 | + const newSet = { weight: 0, reps: 0, duration: 0, distance: 0, restTime: 0, setIndex: newSetIndex } | ||
| 221 | const newSets = [...props.sets, newSet] | 227 | const newSets = [...props.sets, newSet] |
| 222 | emit('update:sets', newSets) | 228 | emit('update:sets', newSets) |
| 223 | } | 229 | } |
| @@ -226,11 +232,17 @@ const addSet = () => { | @@ -226,11 +232,17 @@ const addSet = () => { | ||
| 226 | const delSet = (index) => { | 232 | const delSet = (index) => { |
| 227 | const newSets = [...props.sets] | 233 | const newSets = [...props.sets] |
| 228 | newSets.splice(index, 1) | 234 | newSets.splice(index, 1) |
| 229 | - emit('update:sets', newSets) | 235 | + const reorderedSets = newSets.map((item, i) => ({ |
| 236 | + ...item, | ||
| 237 | + setIndex: i + 1 | ||
| 238 | + })) | ||
| 239 | + | ||
| 240 | + emit('update:sets', reorderedSets) | ||
| 230 | } | 241 | } |
| 231 | </script> | 242 | </script> |
| 232 | 243 | ||
| 233 | <style scoped> | 244 | <style scoped> |
| 245 | +/* 表格 */ | ||
| 234 | .sets-table-wrapper { | 246 | .sets-table-wrapper { |
| 235 | margin: 16px 0; | 247 | margin: 16px 0; |
| 236 | } | 248 | } |
| @@ -244,4 +256,42 @@ const delSet = (index) => { | @@ -244,4 +256,42 @@ const delSet = (index) => { | ||
| 244 | width: 100%; | 256 | width: 100%; |
| 245 | border-collapse: collapse; | 257 | border-collapse: collapse; |
| 246 | } | 258 | } |
| 259 | + | ||
| 260 | +/* 表格容器 */ | ||
| 261 | +.sets-table-wrapper { | ||
| 262 | + margin: 20px 0; | ||
| 263 | + overflow-x: auto; | ||
| 264 | +} | ||
| 265 | + | ||
| 266 | +.table-title { | ||
| 267 | + margin-bottom: 10px; | ||
| 268 | + font-size: 14px; | ||
| 269 | + font-weight: 500; | ||
| 270 | + color: #666; | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +/* 表格核心样式 */ | ||
| 274 | +.sets-table { | ||
| 275 | + width: 100%; | ||
| 276 | + text-align: center; | ||
| 277 | + border-collapse: collapse; | ||
| 278 | +} | ||
| 279 | + | ||
| 280 | +.sets-table th, | ||
| 281 | +.sets-table td { | ||
| 282 | + padding: 12px 8px; | ||
| 283 | + text-align: center; | ||
| 284 | + vertical-align: middle; | ||
| 285 | + border: 1px solid #ddd; | ||
| 286 | + | ||
| 287 | +} | ||
| 288 | + | ||
| 289 | +.sets-table th { | ||
| 290 | + font-weight: 500; | ||
| 291 | + background: #fafafa; | ||
| 292 | +} | ||
| 293 | + | ||
| 294 | +.set-row:hover { | ||
| 295 | + background: #f9f9f9; | ||
| 296 | +} | ||
| 247 | </style> | 297 | </style> |
| @@ -36,7 +36,8 @@ | @@ -36,7 +36,8 @@ | ||
| 36 | </el-form-item> | 36 | </el-form-item> |
| 37 | 37 | ||
| 38 | <!-- 这里实现选择超级组或者动作组名称 --> | 38 | <!-- 这里实现选择超级组或者动作组名称 --> |
| 39 | - <el-form-item label-suffix="*" :label="unit.unitType === 1 ? '动作组名称' : '超级组名称'"> | 39 | + <el-form-item label-suffix="*" :label="unit.unitType === 1 ? '动作组名称' : '超级组名称'" class="row-form-item"> |
| 40 | + <div class="row-wrap"> | ||
| 40 | <el-select v-model="unit.id" @change="(val) => onSelectItem(unitIndex, val)"> | 41 | <el-select v-model="unit.id" @change="(val) => onSelectItem(unitIndex, val)"> |
| 41 | <template v-if="unit.unitType === 1"> | 42 | <template v-if="unit.unitType === 1"> |
| 42 | <!-- 需要接入动作接口 --> | 43 | <!-- 需要接入动作接口 --> |
| @@ -47,37 +48,32 @@ | @@ -47,37 +48,32 @@ | ||
| 47 | <el-option v-for="item in superGroupList" :key="item.id" :label="item.name" :value="item.id" /> | 48 | <el-option v-for="item in superGroupList" :key="item.id" :label="item.name" :value="item.id" /> |
| 48 | </template> | 49 | </template> |
| 49 | </el-select> | 50 | </el-select> |
| 50 | - </el-form-item> | ||
| 51 | - | ||
| 52 | <!-- 动作组头部 动作组名称+删除和增加动作组按钮--> | 51 | <!-- 动作组头部 动作组名称+删除和增加动作组按钮--> |
| 53 | <div class="unit-header"> | 52 | <div class="unit-header"> |
| 54 | - <el-form-item> | ||
| 55 | <el-button @click="delUnit(unitIndex)" :disabled="formData.units.length <= 1"> | 53 | <el-button @click="delUnit(unitIndex)" :disabled="formData.units.length <= 1"> |
| 56 | <template #icon> | 54 | <template #icon> |
| 57 | <Minus /> | 55 | <Minus /> |
| 58 | </template> | 56 | </template> |
| 59 | 删除单元 | 57 | 删除单元 |
| 60 | </el-button> | 58 | </el-button> |
| 61 | - </el-form-item> | ||
| 62 | - | ||
| 63 | - <el-form-item> | ||
| 64 | <el-button @click="AddUnit()"> | 59 | <el-button @click="AddUnit()"> |
| 65 | <template #icon> | 60 | <template #icon> |
| 66 | <Plus /> | 61 | <Plus /> |
| 67 | </template> | 62 | </template> |
| 68 | 新增单元 | 63 | 新增单元 |
| 69 | </el-button> | 64 | </el-button> |
| 70 | - </el-form-item> | ||
| 71 | </div> | 65 | </div> |
| 72 | - | 66 | + </div> |
| 67 | + </el-form-item> | ||
| 73 | <!-- 排序 --> | 68 | <!-- 排序 --> |
| 74 | <el-form-item label="排序" :prop="`units[${unitIndex}].sortOrder`"> | 69 | <el-form-item label="排序" :prop="`units[${unitIndex}].sortOrder`"> |
| 75 | <el-input v-model="unit.sortOrder" min="1" max="99" /> | 70 | <el-input v-model="unit.sortOrder" min="1" max="99" /> |
| 76 | </el-form-item> | 71 | </el-form-item> |
| 72 | + | ||
| 77 | <!-- 循环:动作组 1 个、超级组 N 个,都会渲染 --> | 73 | <!-- 循环:动作组 1 个、超级组 N 个,都会渲染 --> |
| 78 | <template v-for="(item, idx) in unit.exercises" :key="idx"> | 74 | <template v-for="(item, idx) in unit.exercises" :key="idx"> |
| 79 | <div style="margin: 16px 0;"> | 75 | <div style="margin: 16px 0;"> |
| 80 | - <div>动作名称:{{ getExerciseName(item.exerciseId) }}</div> | 76 | + <div class="action-name">动作名称:{{ getExerciseName(item.exerciseId) }}</div> |
| 81 | <ExerciseSetTable :type="item.exerciseType" :sets="item.sets" @update:sets="val => (item.sets = val)" /> | 77 | <ExerciseSetTable :type="item.exerciseType" :sets="item.sets" @update:sets="val => (item.sets = val)" /> |
| 82 | </div> | 78 | </div> |
| 83 | </template> | 79 | </template> |
| @@ -96,15 +92,15 @@ | @@ -96,15 +92,15 @@ | ||
| 96 | </template> | 92 | </template> |
| 97 | <script setup lang="ts"> | 93 | <script setup lang="ts"> |
| 98 | import { ref, onMounted, reactive, nextTick } from 'vue' | 94 | import { ref, onMounted, reactive, nextTick } from 'vue' |
| 99 | -import lostImg from '@/assets/imgs/lost.png' | ||
| 100 | import { MusclesApi } from '@/api/store/training/muscle' | 95 | import { MusclesApi } from '@/api/store/training/muscle' |
| 101 | import { ElMessage, ElMessageBox } from 'element-plus' | 96 | import { ElMessage, ElMessageBox } from 'element-plus' |
| 102 | import { Minus, Plus } from '@element-plus/icons-vue' | 97 | import { Minus, Plus } from '@element-plus/icons-vue' |
| 103 | import type { FormInstance } from 'element-plus' | 98 | import type { FormInstance } from 'element-plus' |
| 104 | -import './TemplateAdd.css' | 99 | +// import './TemplateAdd.css' |
| 105 | import { TrainingTemplatesApi } from '@/api/store/training/templates' | 100 | import { TrainingTemplatesApi } from '@/api/store/training/templates' |
| 106 | import { ExercisesApi } from '@/api/store/training/pose' | 101 | import { ExercisesApi } from '@/api/store/training/pose' |
| 107 | import ExerciseSetTable from '../templates/ExerciseSetTable.vue' | 102 | import ExerciseSetTable from '../templates/ExerciseSetTable.vue' |
| 103 | +import { SupersetsApi } from '@/api/store/training/Supersets' | ||
| 108 | 104 | ||
| 109 | //定义参数 | 105 | //定义参数 |
| 110 | const formRef = ref<FormInstance>() | 106 | const formRef = ref<FormInstance>() |
| @@ -114,7 +110,7 @@ const router = useRouter() | @@ -114,7 +110,7 @@ const router = useRouter() | ||
| 114 | const route = useRoute() | 110 | const route = useRoute() |
| 115 | const exercisesList = ref<any[]>([]) | 111 | const exercisesList = ref<any[]>([]) |
| 116 | const superGroupList = ref<any[]>([]) | 112 | const superGroupList = ref<any[]>([]) |
| 117 | -import { SupersetsApi } from '@/api/store/training/Supersets' | 113 | + |
| 118 | 114 | ||
| 119 | // 表单数据 | 115 | // 表单数据 |
| 120 | const formData = reactive({ | 116 | const formData = reactive({ |
| @@ -451,7 +447,7 @@ const handleSubmit = async () => { | @@ -451,7 +447,7 @@ const handleSubmit = async () => { | ||
| 451 | 447 | ||
| 452 | loading.value = true | 448 | loading.value = true |
| 453 | try { | 449 | try { |
| 454 | - // 👇 按接口文档转换提交数据 | 450 | + // 按接口文档转换提交数据 |
| 455 | const submitData = { | 451 | const submitData = { |
| 456 | templateId: formData.id, // 关键:前端id → 后端templateId | 452 | templateId: formData.id, // 关键:前端id → 后端templateId |
| 457 | groupId: formData.groupId, | 453 | groupId: formData.groupId, |
| @@ -543,3 +539,122 @@ watch( | @@ -543,3 +539,122 @@ watch( | ||
| 543 | { immediate: true } | 539 | { immediate: true } |
| 544 | ) | 540 | ) |
| 545 | </script> | 541 | </script> |
| 542 | + | ||
| 543 | +<style scoped> | ||
| 544 | +:deep(.el-form-item__label) { | ||
| 545 | + float: none !important; | ||
| 546 | + width: 68px !important; | ||
| 547 | + padding: 0 !important; | ||
| 548 | + margin-right: 20px !important; | ||
| 549 | + line-height: 32px; | ||
| 550 | + text-align: right; | ||
| 551 | + white-space: nowrap; | ||
| 552 | +} | ||
| 553 | + | ||
| 554 | +/* 让表单内容区域横向排列 */ | ||
| 555 | +.row-form-item :deep(.el-form-item__content) { | ||
| 556 | + display: flex; | ||
| 557 | + align-items: center; | ||
| 558 | + width: 100%; | ||
| 559 | +} | ||
| 560 | + | ||
| 561 | +/* 下拉框 + 按钮 同行容器 */ | ||
| 562 | +.row-wrap { | ||
| 563 | + display: flex; | ||
| 564 | + align-items: center; | ||
| 565 | + gap: 14px; | ||
| 566 | + width: 100%; | ||
| 567 | +} | ||
| 568 | + | ||
| 569 | +/* 下拉框占剩余宽度 */ | ||
| 570 | +.sel { | ||
| 571 | + flex: 1; | ||
| 572 | +} | ||
| 573 | + | ||
| 574 | +/* 按钮容器:不被压缩 */ | ||
| 575 | +.unit-header { | ||
| 576 | + display: flex; | ||
| 577 | + gap: 10px; | ||
| 578 | + flex-shrink: 0; | ||
| 579 | +} | ||
| 580 | + | ||
| 581 | +/* 下面是你原来保留的样式,不动 */ | ||
| 582 | + | ||
| 583 | +/* 动作组卡片 */ | ||
| 584 | +.unit-card { | ||
| 585 | + margin: 20px 0; | ||
| 586 | + border-radius: 6px; | ||
| 587 | +} | ||
| 588 | + | ||
| 589 | +/* 表格容器 */ | ||
| 590 | +.sets-table-wrapper { | ||
| 591 | + margin: 20px 0; | ||
| 592 | + overflow-x: auto; | ||
| 593 | +} | ||
| 594 | + | ||
| 595 | +.table-title { | ||
| 596 | + margin-bottom: 10px; | ||
| 597 | + font-size: 14px; | ||
| 598 | + font-weight: 500; | ||
| 599 | + color: #666; | ||
| 600 | +} | ||
| 601 | + | ||
| 602 | +/* 表格样式 */ | ||
| 603 | +.sets-table { | ||
| 604 | + width: 100%; | ||
| 605 | + text-align: center; | ||
| 606 | + border-collapse: collapse; | ||
| 607 | +} | ||
| 608 | + | ||
| 609 | +.sets-table th { | ||
| 610 | + padding: 10px 0; | ||
| 611 | + font-weight: 500; | ||
| 612 | + background: #fafafa; | ||
| 613 | +} | ||
| 614 | + | ||
| 615 | +.sets-table td { | ||
| 616 | + padding: 8px 0; | ||
| 617 | +} | ||
| 618 | + | ||
| 619 | +.set-row:hover { | ||
| 620 | + background: #f9f9f9; | ||
| 621 | +} | ||
| 622 | + | ||
| 623 | +.action-name { | ||
| 624 | + color: #666; | ||
| 625 | +} | ||
| 626 | + | ||
| 627 | +/* 按钮居中容器 */ | ||
| 628 | +.btn-center-wrapper { | ||
| 629 | + display: flex; | ||
| 630 | + width: 100%; | ||
| 631 | + padding-top: 15px; | ||
| 632 | + margin-top: 30px; | ||
| 633 | + border-top: 1px solid #eee; | ||
| 634 | + justify-content: center; | ||
| 635 | + gap: 12px; | ||
| 636 | +} | ||
| 637 | + | ||
| 638 | +/* 封面图样式 */ | ||
| 639 | +.sub-image { | ||
| 640 | + border: 1px solid #eee; | ||
| 641 | + border-radius: 10px; | ||
| 642 | +} | ||
| 643 | + | ||
| 644 | +/* 页面滚动容器 */ | ||
| 645 | +.page-contain { | ||
| 646 | + height: 100vh; | ||
| 647 | + overflow-y: auto; | ||
| 648 | + background-color: #f5f5f5; | ||
| 649 | + box-sizing: border-box; | ||
| 650 | +} | ||
| 651 | + | ||
| 652 | +/* 表单容器 */ | ||
| 653 | +.templateName { | ||
| 654 | + padding: 30px; | ||
| 655 | + margin: 0 auto; | ||
| 656 | + background: #fff; | ||
| 657 | + border-radius: 8px; | ||
| 658 | + box-shadow: 0 2px 12px rgb(0 0 0 / 5%); | ||
| 659 | +} | ||
| 660 | +</style> |
| 1 | <template> | 1 | <template> |
| 2 | <div class="page-container"> | 2 | <div class="page-container"> |
| 3 | <!-- 顶部搜索与操作栏 --> | 3 | <!-- 顶部搜索与操作栏 --> |
| 4 | - <ContentWrap> | ||
| 5 | - <div class="search-bar"> | 4 | + <ContentWrap class="plan-search-bar"> |
| 5 | + <div class="search-item"> | ||
| 6 | <div class="search-left"> | 6 | <div class="search-left"> |
| 7 | <label class="search-label">模板名称: </label> | 7 | <label class="search-label">模板名称: </label> |
| 8 | <el-input class="search-input" placeholder="请输入" v-model="searchForm.name" @keyup.enter="handleSearch" /> | 8 | <el-input class="search-input" placeholder="请输入" v-model="searchForm.name" @keyup.enter="handleSearch" /> |
| @@ -29,6 +29,7 @@ | @@ -29,6 +29,7 @@ | ||
| 29 | </div> | 29 | </div> |
| 30 | </div> | 30 | </div> |
| 31 | </ContentWrap> | 31 | </ContentWrap> |
| 32 | + | ||
| 32 | <ContentWrap> | 33 | <ContentWrap> |
| 33 | <!-- 数据列表 --> | 34 | <!-- 数据列表 --> |
| 34 | <div class="table-container"> | 35 | <div class="table-container"> |
| @@ -203,7 +204,7 @@ const load = async () => { | @@ -203,7 +204,7 @@ const load = async () => { | ||
| 203 | // 初始化加载列表 | 204 | // 初始化加载列表 |
| 204 | onMounted(async () => { | 205 | onMounted(async () => { |
| 205 | fetchList() | 206 | fetchList() |
| 206 | - load() | 207 | + // load() |
| 207 | }) | 208 | }) |
| 208 | </script> | 209 | </script> |
| 209 | 210 | ||
| @@ -212,6 +213,19 @@ onMounted(async () => { | @@ -212,6 +213,19 @@ onMounted(async () => { | ||
| 212 | padding: 20px; | 213 | padding: 20px; |
| 213 | } | 214 | } |
| 214 | 215 | ||
| 216 | +.plan-search-bar { | ||
| 217 | + margin-bottom: 20px; | ||
| 218 | + box-shadow: 0 1px 2px rgb(0 0 0 / 5%); | ||
| 219 | +} | ||
| 220 | + | ||
| 221 | +/* 2. 搜索项flex布局(核心,让标签、输入框、按钮横向对齐) */ | ||
| 222 | +.search-item { | ||
| 223 | + display: flex; | ||
| 224 | + align-items: center; | ||
| 225 | + gap: 10px; | ||
| 226 | + padding: 15px 20px; | ||
| 227 | +} | ||
| 228 | + | ||
| 215 | .search-bar { | 229 | .search-bar { |
| 216 | display: flex; | 230 | display: flex; |
| 217 | margin-right: 20px; | 231 | margin-right: 20px; |
| @@ -222,7 +236,7 @@ onMounted(async () => { | @@ -222,7 +236,7 @@ onMounted(async () => { | ||
| 222 | } | 236 | } |
| 223 | 237 | ||
| 224 | .search-label { | 238 | .search-label { |
| 225 | - font-weight: 500; | 239 | + white-space: nowrap; |
| 226 | } | 240 | } |
| 227 | 241 | ||
| 228 | .search-input { | 242 | .search-input { |
| @@ -232,7 +246,8 @@ onMounted(async () => { | @@ -232,7 +246,8 @@ onMounted(async () => { | ||
| 232 | 246 | ||
| 233 | .search-buttons { | 247 | .search-buttons { |
| 234 | display: flex; | 248 | display: flex; |
| 235 | - gap: 5px; | 249 | + gap: 10px; |
| 250 | + margin-left: 10px; | ||
| 236 | } | 251 | } |
| 237 | 252 | ||
| 238 | .table-container { | 253 | .table-container { |
| @@ -242,8 +257,9 @@ onMounted(async () => { | @@ -242,8 +257,9 @@ onMounted(async () => { | ||
| 242 | } | 257 | } |
| 243 | 258 | ||
| 244 | .pagination-wrapper { | 259 | .pagination-wrapper { |
| 245 | - margin-top: 20px; | ||
| 246 | - text-align: right; | 260 | + display: flex; |
| 261 | + padding: 12px 16px; | ||
| 262 | + justify-content: flex-end; | ||
| 247 | } | 263 | } |
| 248 | 264 | ||
| 249 | .cover-wrapper { | 265 | .cover-wrapper { |
| 1 | <template> | 1 | <template> |
| 2 | <!-- 用具分类页面 ,在接口文档没有找到用具分类的接口,用了器械的接口 --> | 2 | <!-- 用具分类页面 ,在接口文档没有找到用具分类的接口,用了器械的接口 --> |
| 3 | - <ContentWrap> | 3 | + <ContentWrap class="plan-search-bar"> |
| 4 | <!-- 搜索栏 --> | 4 | <!-- 搜索栏 --> |
| 5 | - <div class="search-bar"> | ||
| 6 | - <el-form :inline="true" :model="searchForm" class="demo-form-inline"> | ||
| 7 | - <el-form-item label="器械名称:"> | 5 | + <!-- <div class="search-bar"> --> |
| 6 | + <div class="search-item"> | ||
| 7 | + <label class="search-label">肌肉名称</label> | ||
| 8 | <el-input v-model="searchForm.name" placeholder="请输入" style="width: 200px;" size="default" | 8 | <el-input v-model="searchForm.name" placeholder="请输入" style="width: 200px;" size="default" |
| 9 | @keyup.enter="handleSearch" /> | 9 | @keyup.enter="handleSearch" /> |
| 10 | - </el-form-item> | ||
| 11 | - <el-form-item> | 10 | + <div class="search-buttons"> |
| 12 | <el-button type="primary" @click="handleSearch" size="default"> | 11 | <el-button type="primary" @click="handleSearch" size="default"> |
| 13 | <el-icon> | 12 | <el-icon> |
| 14 | <Search /> | 13 | <Search /> |
| @@ -21,15 +20,15 @@ | @@ -21,15 +20,15 @@ | ||
| 21 | </el-icon> | 20 | </el-icon> |
| 22 | 重置 | 21 | 重置 |
| 23 | </el-button> | 22 | </el-button> |
| 24 | - <el-button class="add-btn" @click="handleAdd" size="default"> | 23 | + <el-button type="primary" @click="handleAdd" size="default"> |
| 25 | <el-icon> | 24 | <el-icon> |
| 26 | <Plus /> | 25 | <Plus /> |
| 27 | </el-icon> | 26 | </el-icon> |
| 28 | 新增 | 27 | 新增 |
| 29 | </el-button> | 28 | </el-button> |
| 30 | - </el-form-item> | ||
| 31 | - </el-form> | ||
| 32 | </div> | 29 | </div> |
| 30 | + </div> | ||
| 31 | + <!-- </div> --> | ||
| 33 | </ContentWrap> | 32 | </ContentWrap> |
| 34 | 33 | ||
| 35 | <ContentWrap> | 34 | <ContentWrap> |
| @@ -262,6 +261,24 @@ onMounted(() => { | @@ -262,6 +261,24 @@ onMounted(() => { | ||
| 262 | background: #f5f7fa; | 261 | background: #f5f7fa; |
| 263 | } | 262 | } |
| 264 | 263 | ||
| 264 | +.plan-search-bar { | ||
| 265 | + margin-bottom: 20px; | ||
| 266 | + box-shadow: 0 1px 2px rgb(0 0 0 / 5%); | ||
| 267 | +} | ||
| 268 | + | ||
| 269 | +.search-item { | ||
| 270 | + display: flex; | ||
| 271 | + align-items: center; | ||
| 272 | + gap: 10px; | ||
| 273 | + padding: 15px 20px; | ||
| 274 | +} | ||
| 275 | + | ||
| 276 | +/* 3. 标签样式(和计划页一致) */ | ||
| 277 | +.search-label { | ||
| 278 | + white-space: nowrap; | ||
| 279 | +} | ||
| 280 | + | ||
| 281 | + | ||
| 265 | .add-btn { | 282 | .add-btn { |
| 266 | height: 32px; | 283 | height: 32px; |
| 267 | padding: 0 16px; | 284 | padding: 0 16px; |
| @@ -45,15 +45,8 @@ | @@ -45,15 +45,8 @@ | ||
| 45 | </el-form-item> | 45 | </el-form-item> |
| 46 | 46 | ||
| 47 | <el-form-item label="频率" prop="frequencyPerWeek" label-suffix="*"> | 47 | <el-form-item label="频率" prop="frequencyPerWeek" label-suffix="*"> |
| 48 | - <el-select v-model="SonFormData.frequencyPerWeek"> | ||
| 49 | - <el-option label="不限" :value="0" /> | ||
| 50 | - <el-option label="1练/周" :value="1" /> | ||
| 51 | - <el-option label="2练/周" :value="2" /> | ||
| 52 | - <el-option label="3练/周" :value="3" /> | ||
| 53 | - <el-option label="4练/周" :value="4" /> | ||
| 54 | - <el-option label="5练/周" :value="5" /> | ||
| 55 | - <el-option label="6练/周" :value="6" /> | ||
| 56 | - <el-option label="7练/周" :value="7" /> | 48 | + <el-select v-model="SonFormData.frequencyPerWeek" :disabled="templateCount > 1"> |
| 49 | + <el-option v-for="item in frequencyOptions" :key="item.value" :label="item.label" :value="item.value" /> | ||
| 57 | </el-select> | 50 | </el-select> |
| 58 | </el-form-item> | 51 | </el-form-item> |
| 59 | 52 | ||
| @@ -209,6 +202,39 @@ const templateDetailMap = ref({}) | @@ -209,6 +202,39 @@ const templateDetailMap = ref({}) | ||
| 209 | // 模板详情加载状态 | 202 | // 模板详情加载状态 |
| 210 | const templateLoading = ref({}) | 203 | const templateLoading = ref({}) |
| 211 | 204 | ||
| 205 | +// 控制练的频率 | ||
| 206 | +const templateCount = computed(() => SonFormData.value.templateIds.length) | ||
| 207 | +// 频率选项:1个模板显示全部,多个模板只显示对应数量 | ||
| 208 | +const frequencyOptions = computed(() => { | ||
| 209 | + const count = templateCount.value | ||
| 210 | + if (count === 1) { | ||
| 211 | + return [ | ||
| 212 | + { label: '1练/周', value: 1 }, | ||
| 213 | + { label: '2练/周', value: 2 }, | ||
| 214 | + { label: '3练/周', value: 3 }, | ||
| 215 | + { label: '4练/周', value: 4 }, | ||
| 216 | + { label: '5练/周', value: 5 }, | ||
| 217 | + { label: '6练/周', value: 6 }, | ||
| 218 | + ] | ||
| 219 | + } else { | ||
| 220 | + // 多个模板 → 只显示和模板数量一样的选项 | ||
| 221 | + return [{ label: `${count}练/周`, value: count }] | ||
| 222 | + } | ||
| 223 | +}) | ||
| 224 | + | ||
| 225 | +const syncFrequency = () => { | ||
| 226 | + const count = templateCount.value | ||
| 227 | + if (count > 1) { | ||
| 228 | + // 超过1个模板 → 强制频率 = 模板数量 | ||
| 229 | + SonFormData.value.frequencyPerWeek = count | ||
| 230 | + } | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | +// 监听模板数量变化,自动更新频率 | ||
| 234 | +watch(templateCount, () => { | ||
| 235 | + syncFrequency() | ||
| 236 | +}) | ||
| 237 | + | ||
| 212 | const formRules = reactive({ | 238 | const formRules = reactive({ |
| 213 | name: [{ required: true, message: '请输入计划名称', trigger: 'blur' }], | 239 | name: [{ required: true, message: '请输入计划名称', trigger: 'blur' }], |
| 214 | durationWeeks: [{ required: true, message: '请输入计划周期', trigger: 'blur' }], | 240 | durationWeeks: [{ required: true, message: '请输入计划周期', trigger: 'blur' }], |
| @@ -309,6 +335,7 @@ watch(dialogVisible, async (val) => { | @@ -309,6 +335,7 @@ watch(dialogVisible, async (val) => { | ||
| 309 | loadTemplateDetail(index, item.templateId) | 335 | loadTemplateDetail(index, item.templateId) |
| 310 | } | 336 | } |
| 311 | }) | 337 | }) |
| 338 | + syncFrequency() | ||
| 312 | } | 339 | } |
| 313 | formRef.value?.clearValidate() | 340 | formRef.value?.clearValidate() |
| 314 | }) | 341 | }) |
| @@ -399,6 +426,7 @@ const Addtemplate = (index) => { | @@ -399,6 +426,7 @@ const Addtemplate = (index) => { | ||
| 399 | templateDetailMap.value[i] = oldMap[i] | 426 | templateDetailMap.value[i] = oldMap[i] |
| 400 | } | 427 | } |
| 401 | }) | 428 | }) |
| 429 | + syncFrequency() | ||
| 402 | } | 430 | } |
| 403 | 431 | ||
| 404 | const deltemplate = (index) => { | 432 | const deltemplate = (index) => { |
| @@ -413,7 +441,7 @@ const deltemplate = (index) => { | @@ -413,7 +441,7 @@ const deltemplate = (index) => { | ||
| 413 | templateDetailMap.value[i] = oldMap[i] | 441 | templateDetailMap.value[i] = oldMap[i] |
| 414 | } | 442 | } |
| 415 | }) | 443 | }) |
| 416 | - | 444 | + syncFrequency() |
| 417 | } | 445 | } |
| 418 | onMounted(() => { | 446 | onMounted(() => { |
| 419 | loadTemplateList() | 447 | loadTemplateList() |
-
Please register or login to post a comment