---
description: 后台页面(列表/表单/详情)开发范式 —— 新增或修改 views 下页面时必读
globs: ["src/views/**/*.vue", "src/views/**/*.ts", "src/components/**/*.vue"]
---
# 页面开发范式
## 标准列表页结构
一个规范的列表页自上而下固定为四段:
```vue
```
- 搜索栏支持回车查询与"重置"重置到初始查询条件。
- 表格必须 `v-loading="loading"`、`border`、操作列 `fixed="right"`。
- 分页器 `layout="total, sizes, prev, pager, next, jumper"`,与 `queryParams.pageNum/pageSize` 双向联动。
- 查询条件与分页状态变更时用 `watch` 或统一 `handleQuery` 重新拉取,**禁止在模板里调用接口**。
## 标准数据流
```ts
const loading = ref(false)
const total = ref(0)
const list = ref([])
const queryParams = reactive({ 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 })` 调用:
```vue
```
2. 弹窗内部:`el-dialog` + `el-form`,必须有 `ref`、`rules`、`:model`;关闭时 `resetFields()` 并清空隐藏主键。
3. 提交按钮在请求期间设置 `confirmLoading`,防止重复提交。
4. 新增与编辑复用同一弹窗,通过是否有 `id` 区分,标题用 computed 切换。
## 删除与危险操作
- 删除必须走 `ElMessageBox.confirm` 二次确认,成功后 `ElMessage.success` + 刷新列表。
- 若当前页删完且非第一页,需回退 `pageNum` 再查询。
## 表单校验
- 规则写在 `rules` 常量里(`const rules = reactive>({...})`),不在模板内联。
- 提交前必须 `await formRef.value.validate()`,校验失败直接返回。
- 后端返回字段级错误需映射到 `formRef` 对应字段。
## 页面内复用
- 仅当前页面使用的子组件放 `views/{module}/components/`,**不要**放进全局 `src/components/`。
- 跨页面复用的逻辑抽成 `useXxx` 组合式函数,放 `src/composables/`。
- 字典/枚举下拉数据统一走 Pinia 的字典 store 缓存,不得每次打开弹窗都重新请求。