|
|
|
<template>
|
|
|
|
<!-- 直接用 defineModel 生成的 dialogVisible 做 v-model -->
|
|
|
|
<el-dialog v-model="dialogVisible" :title="title" width="800px" append-to-body>
|
|
|
|
<!-- 表单直接绑定 formDataModel(自动双向同步) -->
|
|
|
|
<el-form ref="formRef" :model="formDataModel" :rules="formRules" label-width="100px">
|
|
|
|
<el-form-item label="动作名称:" prop="name" label-suffix="*">
|
|
|
|
<el-input v-model="formDataModel.name" placeholder="请输入动作名称" maxlength="50" show-word-limit
|
|
|
|
style="width: 100%" />
|
|
|
|
<!-- 采用芋道全局封装的 Dialog 组件,内置滚动和自适应处理 -->
|
|
|
|
<Dialog v-model="dialogVisible" :title="dialogTitle" width="800px">
|
|
|
|
<el-form
|
|
|
|
ref="formRef"
|
|
|
|
v-loading="formLoading"
|
|
|
|
:model="formData"
|
|
|
|
:rules="formRules"
|
|
|
|
label-width="120px"
|
|
|
|
>
|
|
|
|
<el-form-item label="动作名称" prop="name">
|
|
|
|
<el-input
|
|
|
|
v-model="formData.name"
|
|
|
|
placeholder="请输入动作名称"
|
|
|
|
maxlength="50"
|
|
|
|
show-word-limit
|
|
|
|
/>
|
|
|
|
</el-form-item>
|
|
|
|
<el-form-item label="动作类型:" prop="exerciseType" label-suffix="*">
|
|
|
|
<el-select v-model="formDataModel.exerciseType" placeholder="请选择动作类型" style="width: 100%">
|
|
|
|
|
|
|
|
<el-form-item label="动作类型" prop="exerciseType">
|
|
|
|
<el-select v-model="formData.exerciseType" placeholder="请选择动作类型" class="w-full">
|
|
|
|
<el-option label="无氧运动" :value="0" />
|
|
|
|
<el-option label="有氧运动" :value="1" />
|
|
|
|
<el-option label="计次训练" :value="2" />
|
|
...
|
...
|
@@ -18,58 +28,77 @@ |
|
|
|
<el-option label="间歇训练" :value="6" />
|
|
|
|
</el-select>
|
|
|
|
</el-form-item>
|
|
|
|
<!-- 部位分类:下拉选择框 -->
|
|
|
|
<el-form-item label="部位分类:" prop="categoryId" label-suffix="*">
|
|
|
|
<el-select v-model="formDataModel.categoryId" placeholder="请选择部位分类" style="width: 100%"
|
|
|
|
:loading="categoryList.length === 0">
|
|
|
|
<el-option v-for="item in categoryList" :key="item.id" :label="item.name" :value="Number(item.id)" />
|
|
|
|
|
|
|
|
<el-form-item label="部位分类" prop="categoryId">
|
|
|
|
<el-select v-model="formData.categoryId" placeholder="请选择部位分类" class="w-full">
|
|
|
|
<el-option
|
|
|
|
v-for="item in categoryList"
|
|
|
|
:key="item.id"
|
|
|
|
:label="item.name"
|
|
|
|
:value="Number(item.id)"
|
|
|
|
/>
|
|
|
|
</el-select>
|
|
|
|
</el-form-item>
|
|
|
|
<el-form-item label="用具分类:" prop="equipmentId" label-suffix="*">
|
|
|
|
<el-select v-model="formDataModel.equipmentId" placeholder="请选择用具分类" style="width: 100%">
|
|
|
|
|
|
|
|
<el-form-item label="用具分类" prop="equipmentId">
|
|
|
|
<el-select v-model="formData.equipmentId" placeholder="请选择用具分类" class="w-full">
|
|
|
|
<el-option v-for="item in toolList" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
</el-select>
|
|
|
|
</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 primaryMuscleOptions" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
|
|
|
|
<el-form-item label="主要训练部位" prop="primaryMuscles">
|
|
|
|
<el-select
|
|
|
|
v-model="formData.primaryMuscles"
|
|
|
|
multiple
|
|
|
|
placeholder="请选择主要训练部位"
|
|
|
|
class="w-full"
|
|
|
|
>
|
|
|
|
<el-option v-for="item in muscleList" :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 secondaryMuscleOptions" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
|
|
|
|
<el-form-item label="次要训练部位" prop="secondaryMuscles">
|
|
|
|
<el-select
|
|
|
|
v-model="formData.secondaryMuscles"
|
|
|
|
multiple
|
|
|
|
placeholder="请选择次要训练部位"
|
|
|
|
class="w-full"
|
|
|
|
>
|
|
|
|
<el-option v-for="item in muscleList" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
</el-select>
|
|
|
|
</el-form-item>
|
|
|
|
<!-- 锻炼部位图 -->
|
|
|
|
<el-form-item label="锻炼部位图" prop="urlImage" class="form-item-margin upload-img-item">
|
|
|
|
<UploadImg v-model="formDataModel.urlImage" />
|
|
|
|
|
|
|
|
<el-form-item label="锻炼部位图" prop="urlImage">
|
|
|
|
<UploadImg v-model="formData.urlImage" />
|
|
|
|
</el-form-item>
|
|
|
|
<el-form-item label="3D动画地址" prop="url3dAnimation" label-suffix="*" class="upload-img-item upload-3d-item">
|
|
|
|
<UploadImg v-model="formDataModel.url3dAnimation" />
|
|
|
|
|
|
|
|
<el-form-item label="3D动画地址" prop="url3dAnimation">
|
|
|
|
<UploadImg v-model="formData.url3dAnimation" />
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
<el-form-item label="真人演示地址" prop="urlRealPerson" label-suffix="*">
|
|
|
|
<UploadVideo v-model="formDataModel.urlRealPerson" />
|
|
|
|
<el-form-item label="真人演示地址" prop="urlRealPerson">
|
|
|
|
<UploadVideo v-model="formData.urlRealPerson" />
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
<el-form-item label="教程地址" prop="urlTutorial" label-suffix="*">
|
|
|
|
<UploadVideo v-model="formDataModel.urlTutorial" />
|
|
|
|
<el-form-item label="教程地址" prop="urlTutorial">
|
|
|
|
<UploadVideo v-model="formData.urlTutorial" />
|
|
|
|
</el-form-item>
|
|
|
|
<el-form-item label="动作步骤描述:" prop="stepDescription" label-suffix="*" class="desc-item">
|
|
|
|
<Editor v-model="formDataModel.stepDescription" placeholder="请输入动作步骤描述" />
|
|
|
|
|
|
|
|
<el-form-item label="动作步骤描述" prop="stepDescription">
|
|
|
|
<Editor v-model="formData.stepDescription" placeholder="请输入动作步骤描述" />
|
|
|
|
</el-form-item>
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
<el-button @click="handleCancel">取消</el-button>
|
|
|
|
<el-button type="primary" :loading="formLoading" @click="handleSubmit">保存</el-button>
|
|
|
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确定</el-button>
|
|
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
|
|
</template>
|
|
|
|
</el-dialog>
|
|
|
|
</Dialog>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
|
|
import { ref, reactive, watch, onMounted, nextTick, computed } from 'vue'
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
<script setup lang="ts">
|
|
|
|
import { ref, reactive } from 'vue'
|
|
|
|
import type { FormInstance, FormRules } from 'element-plus'
|
|
|
|
import { MotionCategoryApi } from '@/api/store/training/partCategory'
|
|
|
|
import { MusclesApi } from '@/api/store/training/muscle'
|
|
|
|
import { EquipmentsApi } from '@/api/store/training/tool'
|
|
...
|
...
|
@@ -77,21 +106,22 @@ 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 })
|
|
|
|
defineOptions({ name: 'ActionFormDialog' })
|
|
|
|
|
|
|
|
const defaultImg = 'https://fitness-hcxtec-bucket.oss-cn-shenzhen.aliyuncs.com/20260424/muscle_back_1777018976173.png'
|
|
|
|
const message = useMessage() // 引入芋道全局提示组件
|
|
|
|
|
|
|
|
// 2. 自定义 v-model:表单数据(对应父组件 v-model:formData)
|
|
|
|
const formDataModel = defineModel('formData', {
|
|
|
|
type: Object, // 新增这一行
|
|
|
|
default: () => ({
|
|
|
|
const dialogVisible = ref(false) // 弹窗可见性
|
|
|
|
const dialogTitle = ref('') // 弹窗标题
|
|
|
|
const formLoading = ref(false) // 遮罩状态
|
|
|
|
const formType = ref('') // 表单行为:'create' 或 'update'
|
|
|
|
|
|
|
|
const formRef = ref<FormInstance>()
|
|
|
|
const formData = ref<any>({
|
|
|
|
id: undefined,
|
|
|
|
name: '',
|
|
|
|
categoryId: undefined,
|
|
|
|
equipmentId: undefined,
|
|
|
|
exerciseType: undefined,
|
|
|
|
// 封面图
|
|
|
|
urlImage: '',
|
|
|
|
url3dAnimation: '',
|
|
|
|
urlRealPerson: '',
|
|
...
|
...
|
@@ -99,182 +129,90 @@ const formDataModel = defineModel('formData', { |
|
|
|
stepDescription: '',
|
|
|
|
primaryMuscles: [],
|
|
|
|
secondaryMuscles: []
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
const muscleList = ref([]) // 肌肉列表(弹窗选择用)
|
|
|
|
const categoryList = ref([]) // 部位分类列表(弹窗选择用)
|
|
|
|
const toolList = ref([]) // 用具列表(弹窗选择用)
|
|
|
|
|
|
|
|
// 加这个监听,看是否收到父组件的 visible=true
|
|
|
|
watch(dialogVisible, async (newVal) => {
|
|
|
|
console.log('子组件收到 visible 变化:', newVal)
|
|
|
|
if (newVal) {
|
|
|
|
// 弹窗打开时,重新加载列表,确保选项数据存在
|
|
|
|
await Promise.all([
|
|
|
|
loadCategoryList(),
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// 接收父组件传的标题(普通 prop)
|
|
|
|
const props = defineProps({
|
|
|
|
title: { type: String, required: true, default: '新增动作' }
|
|
|
|
})
|
|
|
|
|
|
|
|
// 只定义提交事件(取消/关闭由 v-model 自动处理)
|
|
|
|
const emit = defineEmits(['submit'])
|
|
|
|
|
|
|
|
// 表单相关
|
|
|
|
const formLoading = ref(false)
|
|
|
|
const formRef = ref()
|
|
|
|
// 基础下拉选项数据集
|
|
|
|
const muscleList = ref<any[]>([])
|
|
|
|
const categoryList = ref<any[]>([])
|
|
|
|
const toolList = ref<any[]>([])
|
|
|
|
|
|
|
|
// 表单校验规则(不变)
|
|
|
|
const formRules = reactive({
|
|
|
|
const formRules = reactive<FormRules>({
|
|
|
|
name: [{ required: true, message: '请输入动作名称', trigger: 'blur' }],
|
|
|
|
categoryId: [{ required: true, message: '请选择部位分类', trigger: 'change' }],
|
|
|
|
equipmentId: [{ required: true, message: '请选择用具分类', trigger: 'change' }],
|
|
|
|
exerciseType: [{ required: true, message: '请选择动作类型', trigger: 'change' }],
|
|
|
|
primaryMuscles: [{ required: true, message: '请输入主要训练部位', trigger: 'change' }],
|
|
|
|
secondaryMuscles: [{ required: true, message: '请输入次要训练部位', trigger: 'change' }],
|
|
|
|
urlImage: [{ required: false, message: '请上传图片', trigger: 'blur' }],
|
|
|
|
primaryMuscles: [{ required: true, message: '请选择主要训练部位', trigger: 'change' }],
|
|
|
|
secondaryMuscles: [{ required: true, message: '请选择次要训练部位', trigger: 'change' }],
|
|
|
|
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))
|
|
|
|
})
|
|
|
|
// 安全解析 JSON,避免前端异常崩溃
|
|
|
|
const safeParseArray = (val: any): any[] => {
|
|
|
|
if (!val) return []
|
|
|
|
if (Array.isArray(val)) return val
|
|
|
|
try {
|
|
|
|
const parsed = typeof val === 'string' ? JSON.parse(val) : val
|
|
|
|
return Array.isArray(parsed) ? parsed : []
|
|
|
|
} catch {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 异步按需加载字典数据
|
|
|
|
const loadOptions = async () => {
|
|
|
|
if (muscleList.value.length === 0) {
|
|
|
|
const res = await MusclesApi.getMusclesPage({ pageNo: 1, pageSize: 100, name: '' })
|
|
|
|
muscleList.value = res.data?.list || res.list || []
|
|
|
|
}
|
|
|
|
if (categoryList.value.length === 0) {
|
|
|
|
const res = await MotionCategoryApi.getMotionCategoryPage({ pageNo: 1, pageSize: 100, name: '' })
|
|
|
|
categoryList.value = res.data?.list || res.list || []
|
|
|
|
}
|
|
|
|
if (toolList.value.length === 0) {
|
|
|
|
const res = await EquipmentsApi.getEquipmentsPage({ pageNo: 1, pageSize: 100, name: '' })
|
|
|
|
toolList.value = res.data?.list || res.list || []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 取消按钮:直接关闭弹窗(自动同步给父组件)
|
|
|
|
const handleCancel = () => {
|
|
|
|
// 暴露给父组件调用的 open 方法(标准芋道设计模式)
|
|
|
|
const open = async (type: string, id?: number) => {
|
|
|
|
dialogVisible.value = true
|
|
|
|
dialogTitle.value = type === 'create' ? '新增动作' : '编辑动作'
|
|
|
|
formType.value = type
|
|
|
|
resetForm()
|
|
|
|
dialogVisible.value = false // 自动触发 update:visible,父组件数据同步更新
|
|
|
|
}
|
|
|
|
|
|
|
|
// 提交表单:只需要把数据传给父组件
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
if (!formRef.value) return
|
|
|
|
try {
|
|
|
|
console.log('123456');
|
|
|
|
// 1. 加载分类等选择数据
|
|
|
|
await loadOptions()
|
|
|
|
|
|
|
|
await formRef.value.validate()
|
|
|
|
console.log('123456');
|
|
|
|
// 2. 如果是编辑动作,异步拉取最新后端行数据并格式化回显
|
|
|
|
if (id) {
|
|
|
|
formLoading.value = true
|
|
|
|
console.log('原始primaryMuscles:', formDataModel.value.primaryMuscles)
|
|
|
|
console.log('原始secondaryMuscles:', formDataModel.value.secondaryMuscles)
|
|
|
|
|
|
|
|
const form = formDataModel.value
|
|
|
|
const submitData = {
|
|
|
|
...formDataModel.value,
|
|
|
|
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')
|
|
|
|
} catch (error) {
|
|
|
|
ElMessage.error('表单校验失败,请检查必填项')
|
|
|
|
try {
|
|
|
|
const data = await ExercisesApi.getExercises(id)
|
|
|
|
console.log(data,"getExercises");
|
|
|
|
// 处理回显:将后端保存的 String (如 "[1,2]") 格式化为 el-select 所需的 Array
|
|
|
|
data.primaryMuscles = safeParseArray(JSON.parse(data.primaryMuscles || '[]'))
|
|
|
|
data.secondaryMuscles = safeParseArray(JSON.parse(data.secondaryMuscles || '[]'))
|
|
|
|
formData.value = data
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
|
|
|
} 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, {
|
|
|
|
formData.value = {
|
|
|
|
id: undefined,
|
|
|
|
name: '',
|
|
|
|
categoryId: undefined,
|
|
|
|
equipmentId: undefined,
|
|
|
|
exerciseType: 0,
|
|
|
|
exerciseType: undefined,
|
|
|
|
urlImage: '',
|
|
|
|
url3dAnimation: '',
|
|
|
|
urlRealPerson: '',
|
|
...
|
...
|
@@ -282,131 +220,53 @@ const resetForm = () => { |
|
|
|
stepDescription: '',
|
|
|
|
primaryMuscles: [],
|
|
|
|
secondaryMuscles: []
|
|
|
|
})
|
|
|
|
}
|
|
|
|
formRef.value?.resetFields()
|
|
|
|
}
|
|
|
|
|
|
|
|
//=====================加载数据====================
|
|
|
|
//加载肌肉列表(弹窗选择肌肉)
|
|
|
|
const loadMuscleList = async () => {
|
|
|
|
try {
|
|
|
|
const res = await MusclesApi.getMusclesPage({ pageNo: '1', pageSize: '100', name: '' })
|
|
|
|
const data = res.data || res
|
|
|
|
muscleList.value = data.list || []
|
|
|
|
} catch (err) {
|
|
|
|
console.error('加载肌肉列表失败:', err)
|
|
|
|
ElMessage.error('加载肌肉列表失败')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 暴露 open 方法供父组件利用 ref 调用
|
|
|
|
defineExpose({ open })
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 加载部位分类列表(弹窗选择部位分类)
|
|
|
|
*/
|
|
|
|
const loadCategoryList = async () => {
|
|
|
|
// 定义 success 回调事件,向父页面告知操作结果
|
|
|
|
const emit = defineEmits(['success'])
|
|
|
|
|
|
|
|
// 表单提交保存
|
|
|
|
const submitForm = async () => {
|
|
|
|
if (!formRef.value) return
|
|
|
|
const valid = await formRef.value.validate()
|
|
|
|
if (!valid) return
|
|
|
|
|
|
|
|
formLoading.value = true
|
|
|
|
try {
|
|
|
|
const res = await MotionCategoryApi.getMotionCategoryPage({ pageNo: '1', pageSize: '100', name: '' })
|
|
|
|
const data = res.data || res
|
|
|
|
categoryList.value = data.list || []
|
|
|
|
console.log('部位分类:', categoryList.value)
|
|
|
|
} catch (err) {
|
|
|
|
console.error('加载部位分类失败:', err)
|
|
|
|
ElMessage.error('加载部位分类失败')
|
|
|
|
// 组装提交参数:将选中数组 JSON 序列化为后端所需的 String 格式
|
|
|
|
const submitData = {
|
|
|
|
...formData.value,
|
|
|
|
primaryMuscles: JSON.stringify(formData.value.primaryMuscles || []),
|
|
|
|
secondaryMuscles: JSON.stringify(formData.value.secondaryMuscles || [])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const loadToolList = async () => {
|
|
|
|
try {
|
|
|
|
const res = await EquipmentsApi.getEquipmentsPage({ pageNo: '1', pageSize: '100', name: '' })
|
|
|
|
const data = res.list || []
|
|
|
|
toolList.value = data
|
|
|
|
console.log('用具分类:', toolList.value)
|
|
|
|
console.log(submitData,"submitData");
|
|
|
|
|
|
|
|
if (formType.value === 'create') {
|
|
|
|
await ExercisesApi.addExercises(submitData)
|
|
|
|
message.success('新增成功')
|
|
|
|
} else {
|
|
|
|
await ExercisesApi.updateExercises(submitData)
|
|
|
|
message.success('修改成功')
|
|
|
|
}
|
|
|
|
dialogVisible.value = false
|
|
|
|
emit('success') // 触发父页面重新加载列表
|
|
|
|
} catch (err) {
|
|
|
|
console.error('加载用具列表失败:', err)
|
|
|
|
ElMessage.error('加载用具列表失败')
|
|
|
|
console.error(err)
|
|
|
|
} finally {
|
|
|
|
formLoading.value = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
loadMuscleList()
|
|
|
|
loadCategoryList()
|
|
|
|
loadToolList()
|
|
|
|
})
|
|
|
|
|
|
|
|
// 暴露重置方法
|
|
|
|
defineExpose({ resetForm })
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
/* 1. 强制表单使用 flex 布局,label 和内容水平并排 */
|
|
|
|
:deep(.el-form-item) {
|
|
|
|
display: flex !important;
|
|
|
|
align-items: flex-start !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 2. 统一设置 label 宽度和对齐方式,同时给右侧留固定间距 */
|
|
|
|
:deep(.el-form-item__label) {
|
|
|
|
float: none !important;
|
|
|
|
width: 120px !important;
|
|
|
|
padding: 0 !important;
|
|
|
|
margin-right: 20px !important;
|
|
|
|
line-height: 32px;
|
|
|
|
text-align: right;
|
|
|
|
white-space: nowrap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 3. 让内容区域自动占满剩余宽度 */
|
|
|
|
:deep(.el-form-item__content) {
|
|
|
|
margin-left: 0 !important;
|
|
|
|
line-height: 32px;
|
|
|
|
flex: 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 4. 给所有上传类表单项单独适配垂直对齐 */
|
|
|
|
: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;
|
|
|
|
height: 120px;
|
|
|
|
cursor: pointer;
|
|
|
|
background: #f7f8fa;
|
|
|
|
border: 1px dashed #dcdfe6;
|
|
|
|
border-radius: 4px;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.plus {
|
|
|
|
margin-bottom: 4px;
|
|
|
|
font-size: 28px;
|
|
|
|
color: #999;
|
|
|
|
}
|
|
|
|
|
|
|
|
.tip {
|
|
|
|
font-size: 12px;
|
|
|
|
color: #999;
|
|
|
|
}
|
|
|
|
|
|
|
|
.url-text {
|
|
|
|
margin-left: 10px;
|
|
|
|
line-height: 120px;
|
|
|
|
/* 使用简单的全局通用间距配置,移除过多深层样式覆盖 */
|
|
|
|
.w-full {
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
</style> |
|
|
\ No newline at end of file |
...
|
...
|
|