|
...
|
...
|
@@ -32,27 +32,27 @@ |
|
|
|
</el-form-item>
|
|
|
|
<el-form-item label="主要训练部位:" prop="primaryMuscles" label-suffix="*">
|
|
|
|
<el-select v-model="formDataModel.primaryMuscles" multiple placeholder="请输入主要训练部位" style="width: 100%">
|
|
|
|
<el-option v-for="item in muscleList" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
<el-option v-for="item in primaryMuscleOptions" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
</el-select>
|
|
|
|
</el-form-item>
|
|
|
|
<el-form-item label="次要训练部位:" prop="secondaryMuscles" label-suffix="*">
|
|
|
|
<el-select v-model="formDataModel.secondaryMuscles" multiple placeholder="请输入次要训练部位" style="width: 100%">
|
|
|
|
<el-option v-for="item in muscleList" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
<el-option v-for="item in secondaryMuscleOptions" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
</el-select>
|
|
|
|
</el-form-item>
|
|
|
|
<!-- 封面图 -->
|
|
|
|
<el-form-item label="锻炼部位图" prop="urlImage" label-suffix="*" class="form-item-margin upload-img-item">
|
|
|
|
<!-- 锻炼部位图 -->
|
|
|
|
<el-form-item label="锻炼部位图" prop="urlImage" class="form-item-margin upload-img-item">
|
|
|
|
<UploadImg v-model="formDataModel.urlImage" />
|
|
|
|
</el-form-item>
|
|
|
|
<el-form-item label="3D动画地址" prop="url3dAnimation" label-suffix="*">
|
|
|
|
<UploadVideo v-model="formDataModel.url3dAnimation" />
|
|
|
|
<el-form-item label="3D动画地址" prop="url3dAnimation" label-suffix="*" class="upload-img-item upload-3d-item">
|
|
|
|
<UploadImg v-model="formDataModel.url3dAnimation" />
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
<el-form-item label="真人演示地址" prop="urlRealPerson">
|
|
|
|
<el-form-item label="真人演示地址" prop="urlRealPerson" label-suffix="*">
|
|
|
|
<UploadVideo v-model="formDataModel.urlRealPerson" />
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
<el-form-item label="教程地址" prop="urlTutorial">
|
|
|
|
<el-form-item label="教程地址" prop="urlTutorial" label-suffix="*">
|
|
|
|
<UploadVideo v-model="formDataModel.urlTutorial" />
|
|
|
|
</el-form-item>
|
|
|
|
<el-form-item label="动作步骤描述:" prop="stepDescription" label-suffix="*" class="desc-item">
|
|
...
|
...
|
@@ -68,17 +68,20 @@ |
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
|
|
import { ref, reactive, watch, onMounted, nextTick } from 'vue'
|
|
|
|
import { ref, reactive, watch, onMounted, nextTick, computed } from 'vue'
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
import { MotionCategoryApi } from '@/api/store/training/partCategory'
|
|
|
|
import { MusclesApi } from '@/api/store/training/muscle'
|
|
|
|
import { EquipmentsApi } from '@/api/store/training/tool'
|
|
|
|
import { Editor } from '@/components/Editor'
|
|
|
|
import UploadVideo from '@/components/UploadFile/src/UploadVideo.vue'
|
|
|
|
import { ExercisesApi } from '@/api/store/training/pose'
|
|
|
|
|
|
|
|
// 1. 自定义 v-model:弹窗显隐(对应父组件 v-model:visible)
|
|
|
|
const dialogVisible = defineModel('visible', { type: Boolean, default: false })
|
|
|
|
|
|
|
|
const defaultImg = 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260424/muscle_back_1777018976173.png'
|
|
|
|
|
|
|
|
// 2. 自定义 v-model:表单数据(对应父组件 v-model:formData)
|
|
|
|
const formDataModel = defineModel('formData', {
|
|
|
|
type: Object, // 新增这一行
|
|
...
|
...
|
@@ -113,10 +116,43 @@ watch(dialogVisible, async (newVal) => { |
|
|
|
loadToolList(),
|
|
|
|
loadMuscleList()
|
|
|
|
])
|
|
|
|
const form = formDataModel.value
|
|
|
|
// 调试打印
|
|
|
|
console.log('解析前-原始数据:', form)
|
|
|
|
console.log('解析前-primaryMuscles:', form.primaryMuscles, typeof form.primaryMuscles)
|
|
|
|
console.log('解析前-secondaryMuscles:', form.secondaryMuscles, typeof form.secondaryMuscles)
|
|
|
|
if (typeof form.primaryMuscles === 'string') {
|
|
|
|
try {
|
|
|
|
// 解析字符串 "[17, 16]" → 数组 [17, 16]
|
|
|
|
formDataModel.value.primaryMuscles = JSON.parse(form.primaryMuscles)
|
|
|
|
} catch (err) {
|
|
|
|
// 如果解析失败(比如空字符串、非法格式),就默认设为空数组
|
|
|
|
console.warn('primaryMuscles 解析失败,已重置为空数组', err)
|
|
|
|
formDataModel.value.primaryMuscles = []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 处理 secondaryMuscles(和上面逻辑一样)
|
|
|
|
if (typeof form.secondaryMuscles === 'string') {
|
|
|
|
try {
|
|
|
|
formDataModel.value.secondaryMuscles = JSON.parse(form.secondaryMuscles)
|
|
|
|
} catch (err) {
|
|
|
|
console.warn('secondaryMuscles 解析失败,已重置为空数组', err)
|
|
|
|
formDataModel.value.secondaryMuscles = []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
|
|
|
|
|
|
// 解析完成后,再打印一次,确认变成了数组
|
|
|
|
console.log('解析后-primaryMuscles:', formDataModel.value.primaryMuscles, typeof formDataModel.value.primaryMuscles)
|
|
|
|
console.log('解析后-secondaryMuscles:', formDataModel.value.secondaryMuscles, typeof formDataModel.value.secondaryMuscles)
|
|
|
|
|
|
|
|
// 列表加载完后,强制更新表单,触发el-select回显
|
|
|
|
nextTick(() => {
|
|
|
|
if (formRef.value) {
|
|
|
|
formRef.value.clearValidate()
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
...
|
...
|
@@ -140,13 +176,26 @@ const formRules = reactive({ |
|
|
|
categoryId: [{ required: true, message: '请选择部位分类', trigger: 'change' }],
|
|
|
|
equipmentId: [{ required: true, message: '请选择用具分类', trigger: 'change' }],
|
|
|
|
exerciseType: [{ required: true, message: '请选择动作类型', trigger: 'change' }],
|
|
|
|
primaryMuscles: [{ required: true, message: '请输入主要训练部位', trigger: 'blur' }],
|
|
|
|
secondaryMuscles: [{ required: true, message: '请输入次要训练部位', trigger: 'blur' }],
|
|
|
|
urlImage: [{ required: true, message: '请上传图片', trigger: 'blur' }],
|
|
|
|
primaryMuscles: [{ required: true, message: '请输入主要训练部位', trigger: 'change' }],
|
|
|
|
secondaryMuscles: [{ required: true, message: '请输入次要训练部位', trigger: 'change' }],
|
|
|
|
urlImage: [{ required: false, message: '请上传图片', trigger: 'blur' }],
|
|
|
|
urlRealPerson: [{ required: true, message: '请上传真人演示地址', trigger: 'blur' }],
|
|
|
|
urlTutorial: [{ required: true, message: '请上传教程地址', trigger: 'blur' }],
|
|
|
|
url3dAnimation: [{ required: true, message: '请上传3D动图', trigger: 'blur' }],
|
|
|
|
stepDescription: [{ required: true, message: '请输入动作步骤', trigger: 'blur' }],
|
|
|
|
})
|
|
|
|
|
|
|
|
// 次要部位选项 = 全部肌肉 - 已选主要部位
|
|
|
|
const secondaryMuscleOptions = computed(() => {
|
|
|
|
const primaryIds = formDataModel.value.primaryMuscles || []
|
|
|
|
return muscleList.value.filter(item => !primaryIds.includes(item.id))
|
|
|
|
})
|
|
|
|
|
|
|
|
// 主要部位选项 = 全部肌肉 - 已选次要部位
|
|
|
|
const primaryMuscleOptions = computed(() => {
|
|
|
|
const secondaryIds = formDataModel.value.secondaryMuscles || []
|
|
|
|
return muscleList.value.filter(item => !secondaryIds.includes(item.id))
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 取消按钮:直接关闭弹窗(自动同步给父组件)
|
|
...
|
...
|
@@ -159,27 +208,65 @@ const handleCancel = () => { |
|
|
|
const handleSubmit = async () => {
|
|
|
|
if (!formRef.value) return
|
|
|
|
try {
|
|
|
|
console.log('123456');
|
|
|
|
|
|
|
|
await formRef.value.validate()
|
|
|
|
console.log('123456');
|
|
|
|
formLoading.value = true
|
|
|
|
// 👇 加这行!看原始数据是不是嵌套数组
|
|
|
|
console.log('原始primaryMuscles:', formDataModel.value.primaryMuscles)
|
|
|
|
console.log('原始secondaryMuscles:', formDataModel.value.secondaryMuscles)
|
|
|
|
// 👇 核心处理:将数组转为逗号分隔的字符串
|
|
|
|
|
|
|
|
const form = formDataModel.value
|
|
|
|
const submitData = {
|
|
|
|
...formDataModel.value,
|
|
|
|
// 如果数组为空,传空字符串 "" 否则会变成 "undefined"
|
|
|
|
primaryMuscles: formDataModel.value.primaryMuscles || [],
|
|
|
|
secondaryMuscles: formDataModel.value.secondaryMuscles || [],
|
|
|
|
urlImage: form.urlImage || defaultImg,
|
|
|
|
// primaryMuscles: formDataModel.value.primaryMuscles || [],
|
|
|
|
// secondaryMuscles: formDataModel.value.secondaryMuscles || [],
|
|
|
|
primaryMuscles: JSON.stringify(formDataModel.value.primaryMuscles || []),
|
|
|
|
secondaryMuscles: JSON.stringify(formDataModel.value.secondaryMuscles || []),
|
|
|
|
}
|
|
|
|
console.log('------------------');
|
|
|
|
|
|
|
|
if (submitData.id) {
|
|
|
|
await ExercisesApi.updateExercises(submitData) // 编辑:调用更新接口
|
|
|
|
console.log('动作编辑接口数据', submitData)
|
|
|
|
ElMessage.success('动作编辑成功!')
|
|
|
|
} else {
|
|
|
|
console.log('动作新增接口数据', submitData)
|
|
|
|
await ExercisesApi.addExercises(submitData) // 新增:调用创建接口
|
|
|
|
ElMessage.success('动作新增成功!')
|
|
|
|
}
|
|
|
|
// 触发提交事件,数据已通过 v-model 同步,直接传即可
|
|
|
|
emit('submit', submitData)
|
|
|
|
emit('submit')
|
|
|
|
} catch (error) {
|
|
|
|
ElMessage.error('表单校验失败,请检查必填项')
|
|
|
|
} finally {
|
|
|
|
formLoading.value = false
|
|
|
|
// resetForm()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// const handleFormSubmit = async (submitData) => {
|
|
|
|
// try {
|
|
|
|
// // ✅ 直接传数组,axios 自动转成 JSON 字符串
|
|
|
|
// const formatData = {
|
|
|
|
// ...submitData,
|
|
|
|
// primaryMuscles: JSON.stringify(submitData.primaryMuscles || []),
|
|
|
|
// secondaryMuscles: JSON.stringify(submitData.secondaryMuscles || [])
|
|
|
|
// }
|
|
|
|
// console.log('提交的数据', formatData);
|
|
|
|
|
|
|
|
// // 关闭弹窗
|
|
|
|
// handleDialogClose()
|
|
|
|
// // 重新查询数据
|
|
|
|
// handleQuery()
|
|
|
|
// } catch (err) {
|
|
|
|
// console.error('提交失败:', err)
|
|
|
|
// ElMessage.error(submitData.id ? '更新失败,请稍后重试' : '新增失败,请稍后重试')
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// 重置表单(只操作本地 model,自动同步父组件)
|
|
|
|
const resetForm = () => {
|
|
|
|
Object.assign(formDataModel.value, {
|
|
...
|
...
|
@@ -278,13 +365,22 @@ defineExpose({ resetForm }) |
|
|
|
:deep(.el-form-item.upload-img-item .el-form-item__label) {
|
|
|
|
/* 上传组件高度高,让 label 顶部对齐 */
|
|
|
|
line-height: 120px !important;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* .upload-img-item {
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
} */
|
|
|
|
|
|
|
|
/* 单独给封面图增加上下间距 */
|
|
|
|
.form-item-margin {
|
|
|
|
margin: 30px 5px !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
.upload-3d-item {
|
|
|
|
margin-top: 20px !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
.upload-box {
|
|
|
|
display: flex;
|
|
|
|
width: 120px;
|
...
|
...
|
|