muscles.vue 11 KB
<!-- 原型设计图的肌肉页面,但是接口文档没有肌肉的接口,选择接口文档的细分锻炼部位 -->
<template>
  <div class="page-container">
    <!-- 搜索与新增区域 -->
    <ContentWrap class="plan-search-bar">
      <div class="search-bar">
        <div class="search-item">
          <label class="search-label">肌肉名称</label>
          <el-input v-model="searchForm.name" placeholder="肌肉名称" clearable @keyup.enter="handleSearch"
            class="search-input" />
          <div class="search-buttons">
            <el-button type="primary" @click="handleSearch">
              <el-icon>
                <Search />
              </el-icon>
              搜索
            </el-button>
            <el-button @click="handleReset">
              <el-icon>
                <Refresh />
              </el-icon>
              重置
            </el-button>
            <el-button type="primary" @click="handleAdd">
              <el-icon>
                <Plus />
              </el-icon>
              新增
            </el-button>
          </div>
        </div>
      </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" />
        <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>
      <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>
      <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 { Search, Refresh, Plus } from '@element-plus/icons-vue'
import { dateFormatter } from '@/utils/formatTime'

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

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

// ---------------------- 编辑功能相关变量 ----------------------
// 编辑弹窗显隐
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' }
  ]
})

// ---------------------- 新增功能相关变量 ----------------------
// 新增弹窗显隐
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' }
  ]
})

// 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)
    const result = res.data || res
    tableData.value = result.list || []
    pagination.total = result.total || 0

    // 分页边界处理
    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
  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()
    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>
.plan-search-bar {
  margin-bottom: 20px;
  box-shadow: 0 1px 2px rgb(0 0 0 / 5%);
}

/* 核心修复:自动换行 + 自适应布局 */
.search-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 15px 20px;
  flex-wrap: wrap;
}

.search-label {
  white-space: nowrap;
}

/* 输入框自适应宽度 */
.search-input {
  max-width: 300px;
  min-width: 200px;
  flex: 1;
}

/* 按钮组靠右,不挤压 */
.search-buttons {
  display: flex;
  gap: 10px;

  /* margin-left: auto; */
  flex-wrap: wrap;
}

:deep(.plan-search-bar) {
  padding: 0;
}

.pagination-wrapper {
  display: flex;
  padding: 12px 16px 0;
  justify-content: flex-end;
}
</style>