muscles.vue 12.2 KB
<!-- 原型设计图的肌肉页面,但是接口文档没有肌肉的接口,选择接口文档的细分锻炼部位 -->
<template>
  <div class="page-container">
    <!-- 搜索与新增区域 -->
    <ContentWrap>
      <div class="search-bar">
        <el-input v-model="searchForm.name" placeholder="肌肉名称" clearable @keyup.enter="handleSearch" />
        <el-button @click="handleSearch">
          <el-icon>
            <Search />
          </el-icon>搜索
        </el-button>
        <el-button @click="handleReset">
          <el-icon>
            <Refresh />
          </el-icon>重置
        </el-button>
        <el-button @click="handleAdd">
          <el-icon>
            <Plus />
          </el-icon>新增
        </el-button>
      </div>
    </ContentWrap>

    <ContentWrap>
      <!-- 表格区域 -->
      <el-table :data="tableData" stripe style="width: 100%" v-loading="loading">
        <!-- ID列 -->
        <el-table-column prop="id" label="ID" sortable align="center" />
        <!-- 肌肉名称列(对应接口的name字段) -->
        <el-table-column prop="name" label="肌肉名称" align="center" />
        <!-- 关联大类ID列 -->
        <!-- <el-table-column prop="categoryId" label="关联大类ID" align="center" /> -->
        <el-table-column prop="createTime" label="创建时间" align="center" :formatter="dateFormatter" />
        <el-table-column prop="updateTime" label="更新时间" align="center" min-width="100" :formatter="dateFormatter" />
        <!-- 操作列 -->
        <el-table-column label="操作" align="center">
          <template #default="scope">
            <el-button icon="Edit" size="small" link style="color: #409EFF;" @click="handleEdit(scope.row)">
              编辑
            </el-button>
            <el-button icon="Delete" size="small" link style="color: #F56C6C;" @click="handleDelete(scope.row)">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- 空数据提示 -->
      <div v-if="!loading && tableData.length === 0" style=" padding: 50px 0;text-align: center;">
        <el-empty description="暂无相关数据" />
      </div>
      <!-- 分页区域:-->
      <div class="pagination-wrapper" style="margin-top: 20px; text-align: right;">
        <el-pagination v-model:current-page="pagination.pageNo" v-model:page-size="pagination.pageSize"
          :total="pagination.total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper"
          style="text-align: left;" @size-change="handleSizeChange" @current-change="handleCurrentChange"
          :disabled="pagination.total === 0" />
      </div>
    </ContentWrap>

    <ContentWrap>
      <!-- 编辑弹窗 -->
      <el-dialog v-model="editDialogVisible" title="编辑肌肉信息" @close="resetEditForm">
        <el-form ref="editFormRef" :rules="editRules" :model="editForm" label-width="100px">
          <el-form-item label="肌肉名称" prop="name">
            <el-input v-model="editForm.name" placeholder="请输入肌肉名称" />
          </el-form-item>
          <!-- <el-form-item label="关联大类ID" prop="categoryId">
          <el-input v-model.number="editForm.categoryId" type="number" placeholder="请输入关联大类ID" />
        </el-form-item> -->
        </el-form>
        <template #footer>
          <el-button @click="editDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="submitEditForm">确定</el-button>
        </template>
      </el-dialog>

      <!-- 新增弹窗 -->
      <el-dialog v-model="addDialogVisible" title="新增肌肉信息" @close="resetAddForm">
        <el-form :model="addForm" :rules="addRules" ref="addFormRef" label-width="100px">
          <el-form-item label="肌肉名称" prop="name">
            <el-input v-model="addForm.name" placeholder="请输入肌肉名称" />
          </el-form-item>
          <!-- <el-form-item label="关联大类ID" prop="categoryId">
          <el-input v-model.number="addForm.categoryId" type="number" placeholder="请输入关联大类ID" />
        </el-form-item> -->
        </el-form>
        <template #footer>
          <el-button @click="addDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="submitAddForm">确定</el-button>
        </template>
      </el-dialog>
    </ContentWrap>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted, watch } from 'vue'
import { ElMessage, ElMessageBox, ElEmpty, ElForm, ElFormItem, ElInput, ElDialog } from 'element-plus'
// 导入你提供的接口文件
import { MusclesApi, type MusclesVO } from '@/api/store/training/muscle'

import { dateFormatter } from '@/utils/formatTime'

// 表格数据(对应接口返回的列表)
const tableData = ref<MusclesVO[]>([])
// 加载中状态
const loading = ref(false)
// 搜索表单:保持原有结构
const searchForm = reactive({
  name: ''
})

// 分页参数
const pagination = reactive({
  pageNo: 1, // 替换原currentPage
  pageSize: 10, // 替换原pageSize
  total: 0 // 替换原total
})

// ---------------------- 编辑功能相关变量 ----------------------
// 编辑弹窗显隐
const editDialogVisible = ref(false)
// 编辑表单Ref(用于表单验证)
const editFormRef = ref<InstanceType<typeof ElForm>>()
// 编辑表单数据
const editForm = reactive<MusclesVO>({
  id: 0,
  categoryId: 0,
  name: ''
})
// 编辑表单校验规则
const editRules = reactive({
  name: [
    { required: true, message: '请输入肌肉名称', trigger: 'blur' },
    { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }
  ],
  categoryId: [
    { required: true, message: '请输入关联大类ID', trigger: 'blur' },
    { type: 'number', min: 1, message: '关联大类ID必须为正整数', trigger: 'blur' }
  ]
})

// ---------------------- 新增功能相关变量 ----------------------
// 新增弹窗显隐
const addDialogVisible = ref(false)
// 新增表单Ref(用于表单验证)
const addFormRef = ref<InstanceType<typeof ElForm>>()
// 新增表单数据(id无需填写,由后端生成)
const addForm = reactive<Omit<MusclesVO, 'id'>>({
  categoryId: 0,
  name: ''
})
// 新增表单校验规则(和编辑规则一致)
const addRules = reactive({
  name: [
    { required: true, message: '请输入肌肉名称', trigger: 'blur' },
    { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }
  ],
  categoryId: [
    { required: true, message: '请输入关联大类ID', trigger: 'blur' },
    { type: 'number', min: 1, message: '关联大类ID必须为正整数', trigger: 'blur' }
  ]
})

// 2. 核心方法:获取表格数据
const getMusclesList = async () => {
  try {
    loading.value = true
    // 构造请求参数:参数转字符串传递
    const params = {
      pageNo: pagination.pageNo + '', // 转字符串
      pageSize: pagination.pageSize + '', // 转字符串
      name: searchForm.name // 搜索的肌肉名称
    }
    // 调用接口获取数据
    const res = await MusclesApi.getMusclesPage(params)
    // 兼容不同的接口返回格式(优先取res.data,没有则取res本身)
    const result = res.data || res
    tableData.value = result.list || []
    pagination.total = result.total || 0
    console.log('肌肉分页接口返回数据:', res)

    // 分页边界处理:如果当前页大于总页数,自动切到最后一页
    const totalPages = Math.ceil(pagination.total / pagination.pageSize)
    if (pagination.pageNo > totalPages && totalPages > 0) {
      pagination.pageNo = totalPages
      getMusclesList() // 重新请求最后一页数据
    }
  } catch (error) {
    ElMessage.error('获取肌肉列表失败,请重试')
    console.error('获取列表失败:', error)
    tableData.value = []
    pagination.total = 0
  } finally {
    loading.value = false
  }
}

// 3. 页面挂载时自动加载数据
onMounted(() => {
  getMusclesList()
})

// 监听总条数变化,处理页码越界问题
watch([() => pagination.total, () => pagination.pageSize], () => {
  const totalPages = Math.ceil(pagination.total / pagination.pageSize)
  if (pagination.pageNo > totalPages && totalPages > 0) {
    pagination.pageNo = totalPages
  }
})

// 4. 事件处理函数
// 搜索:保持原有逻辑,仅调整分页参数名
const handleSearch = () => {
  pagination.pageNo = 1 // 搜索时重置页码为1
  getMusclesList()
}

// 重置:保持原有逻辑,仅调整分页参数名
const handleReset = () => {
  searchForm.name = ''
  pagination.pageNo = 1
  pagination.pageSize = 10
  getMusclesList()
}

// ---------------------- 新增功能相关方法 ----------------------
// 打开新增弹窗
const handleAdd = () => {
  resetAddForm()
  addDialogVisible.value = true
}

// 重置新增表单
const resetAddForm = () => {
  addForm.name = ''
  addForm.categoryId = 0
  // 清空表单验证状态
  if (addFormRef.value) {
    addFormRef.value.clearValidate()
  }
}

// 提交新增表单
const submitAddForm = async () => {
  if (!addFormRef.value) return
  try {
    // 表单验证
    await addFormRef.value.validate()
    // 调用新增接口(无需传id,由后端生成)
    await MusclesApi.createMuscles({
      ...addForm,
      id: 0 // 占位,后端会自动生成
    })
    ElMessage.success('新增成功!')
    // 关闭弹窗
    addDialogVisible.value = false
    // 重新加载列表数据
    getMusclesList()
  } catch (error) {
    if (error !== 'cancel') { // 排除表单验证取消的情况
      ElMessage.error('新增失败,请重试')
      console.error('新增失败:', error)
    }
  }
}

// ---------------------- 编辑功能相关方法 ----------------------
// 打开编辑弹窗并回显数据
const handleEdit = async (row: MusclesVO) => {
  try {
    // 清空编辑表单
    resetEditForm()
    // 打开弹窗
    editDialogVisible.value = true
    // 调用接口获取详情
    const res = await MusclesApi.getMuscles(row.id)
    const detail = res.data || res
    // 回显数据到编辑表单
    editForm.id = detail.id
    editForm.name = detail.name
    editForm.categoryId = detail.categoryId
  } catch (error) {
    ElMessage.error('获取肌肉详情失败,请重试')
    console.error('获取详情失败:', error)
  }
}

// 重置编辑表单
const resetEditForm = () => {
  editForm.id = 0
  editForm.name = ''
  editForm.categoryId = 0
  // 清空表单验证状态
  if (editFormRef.value) {
    editFormRef.value.clearValidate()
  }
}

// 提交编辑表单
const submitEditForm = async () => {
  if (!editFormRef.value) return
  try {
    // 表单验证
    await editFormRef.value.validate()
    // 调用更新接口
    await MusclesApi.updateMuscles({ ...editForm })
    ElMessage.success('编辑成功!')
    // 关闭弹窗
    editDialogVisible.value = false
    // 重新加载列表数据
    getMusclesList()
  } catch (error) {
    if (error !== 'cancel') { // 排除表单验证取消的情况
      ElMessage.error('编辑失败,请重试')
      console.error('编辑失败:', error)
    }
  }
}

// 删除
const handleDelete = async (row: MusclesVO) => {
  try {
    // 确认删除
    await ElMessageBox.confirm(
      '此操作将永久删除该肌肉信息,是否继续?',
      '温馨提示',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }
    )
    // 调用删除接口
    await MusclesApi.deleteMuscles(row.id)
    ElMessage.success('删除成功!')
    // 重新获取列表
    getMusclesList()
  } catch (error) {
    if (error !== 'cancel') { // 排除用户取消的情况
      ElMessage.error('删除失败,请重试')
      console.error('删除失败:', error)
    }
  }
}

// ---------------------- 分页相关方法 ----------------
// 分页:每页条数改变
const handleSizeChange = (val: number) => {
  pagination.pageSize = val
  getMusclesList()
}

// 分页:当前页码改变
const handleCurrentChange = (val: number) => {
  pagination.pageNo = val
  getMusclesList()
}
</script>

<style scoped>
.page-container {
  /* min-height: 100vh;
  background: #fff; */
}

/* 新增分页样式 */
.pagination-wrapper {
  display: flex;
  padding: 12px 16px 0;
  justify-content: flex-end;
}
</style>