Files
2026-10-25 23:14:53 +08:00

2.9 KiB
Raw Permalink Blame History

description, globs
description globs
后台页面(列表/表单/详情)开发范式 —— 新增或修改 views 下页面时必读
src/views/**/*.vue
src/views/**/*.ts
src/components/**/*.vue

页面开发范式

标准列表页结构

一个规范的列表页自上而下固定为四段:

<template>
  <div class="app-container">
    <!-- 1. 搜索栏:el-form inline + 查询/重置 -->
    <!-- 2. 工具栏:新增 / 批量删除 / 导入导出 -->
    <!-- 3. 表格:el-table + 操作列 -->
    <!-- 4. 分页:el-pagination -->
  </div>
</template>
  • 搜索栏支持回车查询与"重置"重置到初始查询条件。
  • 表格必须 v-loading="loading"、border、操作列 fixed="right"。
  • 分页器 layout="total, sizes, prev, pager, next, jumper",与 queryParams.pageNum/pageSize 双向联动。
  • 查询条件与分页状态变更时用 watch 或统一 handleQuery 重新拉取,禁止在模板里调用接口。

标准数据流

const loading = ref(false)
const total = ref(0)
const list = ref<UserVO[]>([])
const queryParams = reactive<UserQuery>({ pageNum: 1, pageSize: 10 })

async function handleQuery() {
  loading.value = true
  try {
    const { list: rows, total: t } = await UserAPI.query(queryParams)
    list.value = rows
    total.value = t
  } finally {
    loading.value = false   // loading 必须在 finally 里关闭
  }
}

onMounted(handleQuery)
  • 请求失败由 request 拦截器统一提示,页面只负责数据与 loading;确需额外处理时才 catch。
  • loading 必须放在 finally 关闭,避免异常时永久转圈。

表单弹窗

  1. 弹窗组件独立文件,父组件通过 ref + defineExpose({ open }) 调用:
    <UserForm ref="formRef" @submit="handleQuery" />
    
  2. 弹窗内部:el-dialog + el-form,必须有 ref、rules、:model;关闭时 resetFields() 并清空隐藏主键。
  3. 提交按钮在请求期间设置 confirmLoading,防止重复提交。
  4. 新增与编辑复用同一弹窗,通过是否有 id 区分,标题用 computed 切换。

删除与危险操作

  • 删除必须走 ElMessageBox.confirm 二次确认,成功后 ElMessage.success + 刷新列表。
  • 若当前页删完且非第一页,需回退 pageNum 再查询。

表单校验

  • 规则写在 rules 常量里(const rules = reactive<FormRules<UserForm>>({...})),不在模板内联。
  • 提交前必须 await formRef.value.validate(),校验失败直接返回。
  • 后端返回字段级错误需映射到 formRef 对应字段。

页面内复用

  • 仅当前页面使用的子组件放 views/{module}/components/,不要放进全局 src/components/。
  • 跨页面复用的逻辑抽成 useXxx 组合式函数,放 src/composables/。
  • 字典/枚举下拉数据统一走 Pinia 的字典 store 缓存,不得每次打开弹窗都重新请求。