superSets.vue 9.75 KB
<template>
  <div class="supersets-page">
    <!-- 搜索栏 -->
    <ContentWrap class="mb-20px">
      <el-form :inline="true" :model="queryParams" class="flex items-center">
        <el-form-item label="超级组名称" prop="name">
          <el-input
            v-model="queryParams.name"
            placeholder="请输入"
            clearable
            class="!w-220px"
            @keyup.enter="handleSearch"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">
            <Icon icon="ep:search" class="mr-5px" />搜索
          </el-button>
          <el-button @click="handleReset">
            <Icon icon="ep:refresh" class="mr-5px" />重置
          </el-button>
          <el-button type="primary" @click="openAddDialog">
            <Icon icon="ep:plus" class="mr-5px" />新增
          </el-button>
        </el-form-item>
      </el-form>
    </ContentWrap>

    <!-- 表格区域 -->
    <ContentWrap>
      <el-table v-loading="loading" :data="tableData" empty-text="暂无数据" stripe>
        <el-table-column prop="name" label="超级组名称" align="center" min-width="200" />
        <el-table-column prop="createTime" label="创建时间" align="center" width="220" :formatter="dateFormatter" />
        <el-table-column prop="updateTime" label="更新时间" align="center" min-width="180" :formatter="dateFormatter" />
        <el-table-column label="操作" align="center" width="200">
          <template #default="scope">
            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <Pagination
        :total="total"
        v-model:page="queryParams.pageNo"
        v-model:limit="queryParams.pageSize"
        @pagination="getList"
      />
    </ContentWrap>

    <!-- 新增/编辑弹窗 -->
    <Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
        <el-form-item label="超级组名称" prop="name">
          <el-input v-model="formData.name" placeholder="请输入" clearable />
        </el-form-item>
        <el-form-item label="动作" prop="exerciseIds">
          <el-select
            v-model="formData.exerciseIds"
            placeholder="请选择动作"
            multiple
            clearable
            filterable
            style="width: 100%"
          >
            <el-option
              v-for="item in exercisesList"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <!-- 主要训练部位(自动回显,只读) -->
        <el-form-item label="主要训练部位">
          <el-input :value="primaryMusclesText" disabled placeholder="选择动作后自动显示" />
        </el-form-item>
        <!-- 次要训练部位(自动回显,只读) -->
        <el-form-item label="次要训练部位">
          <el-input :value="secondaryMusclesText" disabled placeholder="选择动作后自动显示" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="dialogVisible = false">取 消</el-button>
      </template>
    </Dialog>
  </div>
</template>

<script setup>
import { ref, onMounted, reactive, computed, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { SupersetsApi } from '@/api/store/training/Supersets'
import { ExercisesApi } from '@/api/store/training/pose'
import { dateFormatter } from '@/utils/formatTime'

// ===================== 查询参数 =====================
const queryParams = reactive({
  pageNo: 1,
  pageSize: 10,
  name: ''
})
const total = ref(0)
const loading = ref(false)
const tableData = ref([])

// ===================== 弹窗状态 =====================
const dialogVisible = ref(false)
const isEdit = ref(false)
const dialogTitle = ref('')
const formRef = ref(null)

// ===================== 选项数据 =====================
const exercisesList = ref([])
const detailExercises = ref([]) // 编辑时存储详情接口返回的动作数据(含肌肉名称)

// ===================== 表单数据 =====================
const formData = reactive({
  id: 0,
  name: '',
  exerciseIds: []
})

const formRules = reactive({
  name: [
    { required: true, message: '请输入超级组名称', trigger: 'blur' },
    { min: 2, max: 50, message: '名称长度在 2 到 50 个字符', trigger: 'blur' }
  ],
  exerciseIds: [
    { required: true, message: '请选择组内动作', trigger: 'change' }
  ]
})

// ===================== 工具函数 =====================
const parseMuscles = (value) => {
  if (Array.isArray(value)) return value
  try { return JSON.parse(value) } catch { return [] }
}

// ===================== 计算属性:训练部位回显 =====================
// 查找动作对象:优先返回带肌肉名称(primaryMusclesName/secondaryMusclesName)的数据,
// 编辑时 detailExercises 可能不含名称字段,则回退到 exercisesList(含名称)
const findExercise = (exId) => {
  const inDetail = detailExercises.value.find((e) => e.id === exId)
  const inList = exercisesList.value.find((e) => e.id === exId)
  if (inDetail && Array.isArray(inDetail.primaryMusclesName)) return inDetail
  if (inList) return inList
  return inDetail || inList
}

// field 传入名称数组字段(primaryMusclesName / secondaryMusclesName)
const aggregateMuscles = (nameField) => {
  if (!formData.exerciseIds.length) return ''
  const nameSet = new Set()
  formData.exerciseIds.forEach((exId) => {
    const item = findExercise(exId)
    if (!item) return
    const list = Array.isArray(item[nameField]) ? item[nameField] : parseMuscles(item[nameField] || [])
    list.forEach((name) => nameSet.add(name))
  })
  return Array.from(nameSet).join('、')
}

const primaryMusclesText = computed(() => aggregateMuscles('primaryMusclesName'))
const secondaryMusclesText = computed(() => aggregateMuscles('secondaryMusclesName'))

// ===================== 数据加载 =====================
const loadExercisesList = async () => {
  try {
    const res = await ExercisesApi.getExercisesList(false)
    exercisesList.value = res || []
  } catch (err) {
    ElMessage.error('加载动作列表失败')
  }
}

const getList = async () => {
  loading.value = true
  try {
    const res = await SupersetsApi.getSupersetsPage({
      pageNo: queryParams.pageNo,
      pageSize: queryParams.pageSize,
      name: queryParams.name
    })
    const responseData = res.data || res
    const { total: resTotal = 0, list = [] } = responseData
    tableData.value = list
    total.value = resTotal
  } catch (err) {
    ElMessage.error('获取超级组列表失败')
  } finally {
    loading.value = false
  }
}

// ===================== 搜索与分页 =====================
const handleSearch = () => {
  queryParams.pageNo = 1
  getList()
}

const handleReset = () => {
  Object.assign(queryParams, { pageNo: 1, pageSize: 10, name: '' })
  getList()
}

// ===================== 新增 =====================
const openAddDialog = () => {
  isEdit.value = false
  dialogTitle.value = '新增超级组'
  resetForm()
  dialogVisible.value = true
}

// ===================== 编辑 =====================
const handleEdit = async (row) => {
  try {
    isEdit.value = true
    dialogTitle.value = '编辑超级组'
    resetForm()
    const res = await SupersetsApi.getSupersets(row.id)
    const detail = res.data || res
    detailExercises.value = detail.exercises || []
    const exerciseIds = (detail.exercises || []).map((item) => Number(item.id))
    Object.assign(formData, {
      id: detail.id,
      name: detail.name || '',
      exerciseIds
    })
    dialogVisible.value = true
  } catch (err) {
    ElMessage.error('加载编辑数据失败')
  }
}

// ===================== 删除 =====================
const handleDelete = async (row) => {
  try {
    await ElMessageBox.confirm(`确定要删除超级组「${row.name}」吗?`, '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    await SupersetsApi.deleteSupersets(row.id)
    ElMessage.success('删除成功')
    getList()
  } catch (err) {
    if (err !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}

// ===================== 表单操作 =====================
const resetForm = () => {
  if (formRef.value) {
    formRef.value.resetFields()
  }
  detailExercises.value = []
  Object.assign(formData, { id: 0, name: '', exerciseIds: [] })
}

const submitForm = async () => {
  if (!formRef.value) return
  try {
    await formRef.value.validate()
  } catch {
    return
  }
  try {
    const payload = {
      id: formData.id,
      name: formData.name,
      exerciseIds: formData.exerciseIds
    }
    if (isEdit.value) {
      await SupersetsApi.updateSupersets(payload)
      ElMessage.success('修改超级组成功!')
    } else {
      await SupersetsApi.createSupersets(payload)
      ElMessage.success('新增超级组成功!')
    }
    dialogVisible.value = false
    getList()
  } catch (err) {
    const msg = err?.response?.data?.msg || err?.message
    if (msg && typeof msg !== 'object') {
      ElMessage.error(msg)
    }
  }
}

// ===================== 弹窗关闭时重置表单 =====================
watch(dialogVisible, (val) => {
  if (!val) resetForm()
})

// ===================== 初始化 =====================
onMounted(async () => {
  await loadExercisesList()
  getList()
})
</script>

<style scoped>
.supersets-page {
  padding: 20px;
  min-height: 100vh;
  background-color: #f5f7fa;
  box-sizing: border-box;
}
</style>