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

73 lines
2.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: 后台页面(列表/表单/详情)开发范式 —— 新增或修改 views 下页面时必读
globs: ["src/views/**/*.vue", "src/views/**/*.ts", "src/components/**/*.vue"]
---
# 页面开发范式
## 标准列表页结构
一个规范的列表页自上而下固定为四段:
```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` 重新拉取,**禁止在模板里调用接口**。
## 标准数据流
```ts
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 })` 调用:
```vue
<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 缓存,不得每次打开弹窗都重新请求。