Authored by Bad

feat: 添加获取计划分类下拉列表的API调用并优化相关组件

@@ -54,7 +54,10 @@ export const trainingPlanApi = { @@ -54,7 +54,10 @@ export const trainingPlanApi = {
54 // 获取计划下拉列表 54 // 获取计划下拉列表
55 getPlanList: async () => { 55 getPlanList: async () => {
56 return await request.get({ url: `/plan/simple-list`, }) 56 return await request.get({ url: `/plan/simple-list`, })
  57 + },
  58 + // 获取计划分类下拉列表
  59 + getPlanCategoryList: async () => {
  60 + return await request.get({ url: `/plan/category/simple-list`, })
57 } 61 }
58 62
59 -  
60 } 63 }
1 <template> 1 <template>
2 - <!-- 采用芋道全局封装的 Dialog 组件,内置滚动和自适应处理 --> 2 + <!-- 采用芋道全局封装的 Dialog 组件 -->
3 <Dialog v-model="dialogVisible" :title="dialogTitle" width="800px"> 3 <Dialog v-model="dialogVisible" :title="dialogTitle" width="800px">
4 <el-form ref="formRef" v-loading="formLoading" :model="formData" :rules="formRules" label-width="120px"> 4 <el-form ref="formRef" v-loading="formLoading" :model="formData" :rules="formRules" label-width="120px">
5 <el-form-item label="动作名称" prop="name"> 5 <el-form-item label="动作名称" prop="name">
@@ -70,27 +70,23 @@ @@ -70,27 +70,23 @@
70 </Dialog> 70 </Dialog>
71 </template> 71 </template>
72 72
73 -<script setup lang="ts">  
74 -import { ref, reactive } from 'vue'  
75 -import type { FormInstance, FormRules } from 'element-plus' 73 +<script setup>
76 import { MotionCategoryApi } from '@/api/store/training/partCategory' 74 import { MotionCategoryApi } from '@/api/store/training/partCategory'
77 import { MusclesApi } from '@/api/store/training/muscle' 75 import { MusclesApi } from '@/api/store/training/muscle'
78 import { EquipmentsApi } from '@/api/store/training/tool' 76 import { EquipmentsApi } from '@/api/store/training/tool'
79 -import { Editor } from '@/components/Editor'  
80 -import UploadVideo from '@/components/UploadFile/src/UploadVideo.vue'  
81 import { ExercisesApi } from '@/api/store/training/pose' 77 import { ExercisesApi } from '@/api/store/training/pose'
82 78
83 defineOptions({ name: 'ActionFormDialog' }) 79 defineOptions({ name: 'ActionFormDialog' })
84 80
85 -const message = useMessage() // 引入芋道全局提示组件 81 +const message = useMessage() // 芋道全局提示组件
86 82
87 -const dialogVisible = ref(false) // 弹窗可见性  
88 -const dialogTitle = ref('') // 弹窗标题  
89 -const formLoading = ref(false) // 遮罩状态  
90 -const formType = ref('') // 表单行为:'create' 或 'update' 83 +const dialogVisible = ref(false)
  84 +const dialogTitle = ref('')
  85 +const formLoading = ref(false)
  86 +const formType = ref('')
91 87
92 -const formRef = ref<FormInstance>()  
93 -const formData = ref<any>({ 88 +const formRef = ref()
  89 +const formData = ref({
94 id: undefined, 90 id: undefined,
95 name: '', 91 name: '',
96 categoryId: undefined, 92 categoryId: undefined,
@@ -106,37 +102,34 @@ const formData = ref<any>({ @@ -106,37 +102,34 @@ const formData = ref<any>({
106 }) 102 })
107 103
108 // 基础下拉选项数据集 104 // 基础下拉选项数据集
109 -const muscleList = ref<any[]>([])  
110 -const categoryList = ref<any[]>([])  
111 -const toolList = ref<any[]>([]) 105 +const muscleList = ref([])
  106 +const categoryList = ref([])
  107 +const toolList = ref([])
112 108
113 // 可选主要训练部位:排除已选次要部位 109 // 可选主要训练部位:排除已选次要部位
114 const primaryAvailableMuscles = computed(() => { 110 const primaryAvailableMuscles = computed(() => {
115 const secondaryIds = new Set(formData.value.secondaryMuscles ?? []) 111 const secondaryIds = new Set(formData.value.secondaryMuscles ?? [])
116 - return muscleList.value.filter(item => !secondaryIds.has(item.id)) 112 + return muscleList.value.filter((item) => !secondaryIds.has(item.id))
117 }) 113 })
118 114
119 // 可选次要训练部位:排除已选主要部位 115 // 可选次要训练部位:排除已选主要部位
120 const secondaryAvailableMuscles = computed(() => { 116 const secondaryAvailableMuscles = computed(() => {
121 const primaryIds = new Set(formData.value.primaryMuscles ?? []) 117 const primaryIds = new Set(formData.value.primaryMuscles ?? [])
122 - return muscleList.value.filter(item => !primaryIds.has(item.id)) 118 + return muscleList.value.filter((item) => !primaryIds.has(item.id))
123 }) 119 })
124 120
125 -const formRules = reactive<FormRules>({ 121 +// 表单校验规则(已移除 primaryMuscles、secondaryMuscles、urlRealPerson、url3dAnimation 的必填规则)
  122 +const formRules = reactive({
126 name: [{ required: true, message: '请输入动作名称', trigger: 'blur' }], 123 name: [{ required: true, message: '请输入动作名称', trigger: 'blur' }],
127 categoryId: [{ required: true, message: '请选择部位分类', trigger: 'change' }], 124 categoryId: [{ required: true, message: '请选择部位分类', trigger: 'change' }],
128 equipmentId: [{ required: true, message: '请选择用具分类', trigger: 'change' }], 125 equipmentId: [{ required: true, message: '请选择用具分类', trigger: 'change' }],
129 exerciseType: [{ required: true, message: '请选择动作类型', trigger: 'change' }], 126 exerciseType: [{ required: true, message: '请选择动作类型', trigger: 'change' }],
130 - primaryMuscles: [{ required: true, message: '请选择主要训练部位', trigger: 'change' }],  
131 - secondaryMuscles: [{ required: true, message: '请选择次要训练部位', trigger: 'change' }],  
132 - urlRealPerson: [{ required: true, message: '请上传真人演示地址', trigger: 'blur' }],  
133 urlTutorial: [{ required: true, message: '请上传教程地址', trigger: 'blur' }], 127 urlTutorial: [{ required: true, message: '请上传教程地址', trigger: 'blur' }],
134 - url3dAnimation: [{ required: true, message: '请上传3D动图', trigger: 'blur' }],  
135 - stepDescription: [{ required: true, message: '请输入动作步骤', trigger: 'blur' }], 128 + stepDescription: [{ required: true, message: '请输入动作步骤', trigger: 'blur' }]
136 }) 129 })
137 130
138 -// 安全解析 JSON,避免前端异常崩溃  
139 -const safeParseArray = (val: any): any[] => { 131 +// 安全解析 JSON 字符串/数组,容错处理
  132 +const safeParseArray = (val) => {
140 if (!val) return [] 133 if (!val) return []
141 if (Array.isArray(val)) return val 134 if (Array.isArray(val)) return val
142 try { 135 try {
@@ -152,26 +145,19 @@ const loadOptions = async () => { @@ -152,26 +145,19 @@ const loadOptions = async () => {
152 if (muscleList.value.length === 0) { 145 if (muscleList.value.length === 0) {
153 const res = await MusclesApi.getsubCategoriesList() 146 const res = await MusclesApi.getsubCategoriesList()
154 muscleList.value = res || [] 147 muscleList.value = res || []
155 - console.log('细分锻炼部位列表/肌肉res:', res);  
156 - console.log('muscleList 赋值后', muscleList.value)  
157 -  
158 } 148 }
159 if (categoryList.value.length === 0) { 149 if (categoryList.value.length === 0) {
160 const res = await MotionCategoryApi.getCategoriesList() 150 const res = await MotionCategoryApi.getCategoriesList()
161 categoryList.value = res || [] 151 categoryList.value = res || []
162 - console.log('部位列表res:', res);  
163 - console.log('categoryList 赋值后', categoryList.value)  
164 } 152 }
165 if (toolList.value.length === 0) { 153 if (toolList.value.length === 0) {
166 const res = await EquipmentsApi.getEquipmentList() 154 const res = await EquipmentsApi.getEquipmentList()
167 toolList.value = res || [] 155 toolList.value = res || []
168 - console.log('器械列表res:', res);  
169 - console.log('categoryList 赋值后', categoryList.value)  
170 } 156 }
171 } 157 }
172 158
173 -// 暴露给父组件调用的 open 方法(标准芋道设计模式)  
174 -const open = async (type: string, id?: number) => { 159 +// 暴露给父组件调用的 open 方法
  160 +const open = async (type, id) => {
175 dialogVisible.value = true 161 dialogVisible.value = true
176 dialogTitle.value = type === 'create' ? '新增动作' : '编辑动作' 162 dialogTitle.value = type === 'create' ? '新增动作' : '编辑动作'
177 formType.value = type 163 formType.value = type
@@ -185,10 +171,9 @@ const open = async (type: string, id?: number) => { @@ -185,10 +171,9 @@ const open = async (type: string, id?: number) => {
185 formLoading.value = true 171 formLoading.value = true
186 try { 172 try {
187 const data = await ExercisesApi.getExercises(id) 173 const data = await ExercisesApi.getExercises(id)
188 - console.log(data, "getExercises");  
189 - // 处理回显:将后端保存的 String (如 "[1,2]") 格式化为 el-select 所需的 Array  
190 - data.primaryMuscles = safeParseArray(JSON.parse(data.primaryMuscles || '[]'))  
191 - data.secondaryMuscles = safeParseArray(JSON.parse(data.secondaryMuscles || '[]')) 174 + // 回显处理:安全解析后端保存的数组或 JSON 字符串
  175 + data.primaryMuscles = safeParseArray(data.primaryMuscles)
  176 + data.secondaryMuscles = safeParseArray(data.secondaryMuscles)
192 formData.value = data 177 formData.value = data
193 } catch (err) { 178 } catch (err) {
194 console.error(err) 179 console.error(err)
@@ -231,15 +216,13 @@ const submitForm = async () => { @@ -231,15 +216,13 @@ const submitForm = async () => {
231 216
232 formLoading.value = true 217 formLoading.value = true
233 try { 218 try {
234 - // 组装提交参数:将选中数组 JSON 序列化为后端所需的 String 格式 219 + // 组装提交参数:即使未选择部位,也会转为空数组 JSON 字符串 "[]" 传给后端
235 const submitData = { 220 const submitData = {
236 ...formData.value, 221 ...formData.value,
237 primaryMuscles: JSON.stringify(formData.value.primaryMuscles || []), 222 primaryMuscles: JSON.stringify(formData.value.primaryMuscles || []),
238 secondaryMuscles: JSON.stringify(formData.value.secondaryMuscles || []) 223 secondaryMuscles: JSON.stringify(formData.value.secondaryMuscles || [])
239 } 224 }
240 225
241 - console.log(submitData, "submitData");  
242 -  
243 if (formType.value === 'create') { 226 if (formType.value === 'create') {
244 await ExercisesApi.addExercises(submitData) 227 await ExercisesApi.addExercises(submitData)
245 message.success('新增成功') 228 message.success('新增成功')
@@ -248,7 +231,7 @@ const submitForm = async () => { @@ -248,7 +231,7 @@ const submitForm = async () => {
248 message.success('修改成功') 231 message.success('修改成功')
249 } 232 }
250 dialogVisible.value = false 233 dialogVisible.value = false
251 - emit('success') // 触发父页面重新加载列表 234 + emit('success')
252 } catch (err) { 235 } catch (err) {
253 console.error(err) 236 console.error(err)
254 } finally { 237 } finally {
@@ -258,7 +241,6 @@ const submitForm = async () => { @@ -258,7 +241,6 @@ const submitForm = async () => {
258 </script> 241 </script>
259 242
260 <style scoped> 243 <style scoped>
261 -/* 使用简单的全局通用间距配置,移除过多深层样式覆盖 */  
262 .w-full { 244 .w-full {
263 width: 100%; 245 width: 100%;
264 } 246 }
@@ -66,7 +66,7 @@ @@ -66,7 +66,7 @@
66 </div> 66 </div>
67 </template> 67 </template>
68 68
69 -<script setup lang="ts"> 69 +<script setup>
70 import { ref, reactive, onMounted } from 'vue' 70 import { ref, reactive, onMounted } from 'vue'
71 import { dateFormatter } from '@/utils/formatTime' 71 import { dateFormatter } from '@/utils/formatTime'
72 import lostImg from '@/assets/imgs/lost.png' 72 import lostImg from '@/assets/imgs/lost.png'
@@ -78,10 +78,10 @@ defineOptions({ name: 'SystemActionManage' }) @@ -78,10 +78,10 @@ defineOptions({ name: 'SystemActionManage' })
78 const message = useMessage() // 芋道消息组件 78 const message = useMessage() // 芋道消息组件
79 79
80 const queryFormRef = ref() 80 const queryFormRef = ref()
81 -const formRef = ref() // 弹窗组件的 ref 句柄  
82 -const loading = ref<boolean>(false)  
83 -const list = ref<any[]>([])  
84 -const total = ref<number>(0) 81 +const formRef = ref()
  82 +const loading = ref(false)
  83 +const list = ref([])
  84 +const total = ref(0)
85 85
86 const queryParams = reactive({ 86 const queryParams = reactive({
87 pageNo: 1, 87 pageNo: 1,
@@ -119,12 +119,12 @@ const resetQuery = () => { @@ -119,12 +119,12 @@ const resetQuery = () => {
119 } 119 }
120 120
121 // 标准打开弹窗方式(通过组件 ref 句柄触发) 121 // 标准打开弹窗方式(通过组件 ref 句柄触发)
122 -const openForm = (type: string, id?: number) => { 122 +const openForm = (type, id) => {
123 formRef.value.open(type, id) 123 formRef.value.open(type, id)
124 } 124 }
125 125
126 // 标准删除动作(采用 useMessage) 126 // 标准删除动作(采用 useMessage)
127 -const handleDelete = async (id: number) => { 127 +const handleDelete = async (id) => {
128 try { 128 try {
129 await message.confirm('确定要删除当前动作吗?删除后不可恢复!') 129 await message.confirm('确定要删除当前动作吗?删除后不可恢复!')
130 await ExercisesApi.deleteExercises(id) 130 await ExercisesApi.deleteExercises(id)
@@ -298,8 +298,14 @@ const loadTemplateDetail = async (index, templateId) => { @@ -298,8 +298,14 @@ const loadTemplateDetail = async (index, templateId) => {
298 } 298 }
299 299
300 const loadCategoryList = async () => { 300 const loadCategoryList = async () => {
301 - const res = await trainingPlanApi.getPlanList()  
302 - categoryList.value = res || [] 301 + try {
  302 + const res = await trainingPlanApi.getPlanCategoryList()
  303 + categoryList.value = res || []
  304 + } catch (err) {
  305 + console.log(err);
  306 +
  307 + }
  308 +
303 } 309 }
304 310
305 const reloadTemplateDetail = (index) => { 311 const reloadTemplateDetail = (index) => {