first commit

This commit is contained in:
2026-10-25 23:14:53 +08:00
commit 4e9f3dcb13
25 changed files with 932 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
---
description: 项目 overview —— portal-mini-front 后台管理系统的技术栈、目录结构与运行方式
globs: ["**/*"]
alwaysApply: true
---
# 项目概述
## 产品定位
`portal-mini-front` 是一个**后台管理系统前端**(Portal 管理后台),面向内部运营/管理员,核心场景是:登录鉴权 → 菜单/路由权限 → 数据列表(查询/分页/筛选)→ 增删改查表单 → 数据看板。
## 技术栈(固定选型,不得随意替换)
| 领域 | 选型 | 说明 |
| --- | --- | --- |
| 框架 | Vue 3.4+ | 仅使用 Composition API |
| 语言 | TypeScript 5.x | 严格模式,禁止 `any` |
| 构建 | Vite 5+ | |
| UI 库 | Element Plus | 主题色、组件尺寸统一在应用入口配置 |
| 脚手架基线 | `vue3-element-admin` | 目录结构、请求封装、权限方案以该模板为基准 |
| 状态管理 | Pinia | 使用 setup store 语法 |
| 路由 | Vue Router 4 | 动态路由 + 权限守卫 |
| HTTP | Axios | 统一封装于 `src/utils/request.ts` |
| 图标 | `@element-plus/icons-vue` + 本地 svg sprite | |
| 工具库 | VueUse(`@vueuse/core`) | 优先复用,不重复造轮子 |
| 代码规范 | ESLint + Prettier + husky/lint-staged | 提交前自动校验 |
> 引入任何新依赖前必须说明理由;能用 Element Plus / VueUse / 现有 utils 解决的,不允许加新库。
## 目录结构约定
```
src/
├── api/ # 接口层,按业务模块划分,不得在组件内直接写请求
│ └── {module}/
│ └── index.ts # 导出 {Module}API 对象
├── components/ # 跨模块复用的全局组件(自动注册)
├── composables/ # 跨模块复用的组合式函数 useXxx
├── directives/ # 自定义指令,如 v-hasPerm
├── enums/ # 枚举与常量字典
├── layouts/ # 布局骨架(侧边栏/顶栏/标签栏/内容区)
├── router/ # 静态路由 + 动态路由 + 守卫
├── store/modules/ # Pinia 模块
├── styles/ # 全局样式、变量、Element Plus 覆盖
├── types/ # 全局与模块类型声明(*.d.ts 或 *.ts)
├── utils/ # request / storage / 通用工具
└── views/ # 页面,按业务模块划分
└── {module}/
├── index.vue # 列表主页(必须)
└── components/ # 仅该模块使用的子组件(表单弹窗等)
```
命名规则:
- 目录、文件名统一 **kebab-case**;Vue 组件文件用 **PascalCase.vue** 或目录 + `index.vue`。
- 页面路由 path 与 `views/{module}` 目录保持一致。
## 常用命令
```bash
pnpm dev # 本地开发
pnpm build:prod # 生产构建
pnpm preview # 预览构建产物
pnpm lint # ESLint 检查并修复
pnpm type-check # vue-tsc 类型检查
```
## 环境
- 环境变量文件:`.env.development` / `.env.production`,变量必须以 `VITE_` 开头。
- 接口地址统一走 `VITE_APP_BASE_API`,本地通过 Vite `server.proxy` 解决跨域,**不允许在代码里硬编码域名或端口**。
+44
View File
@@ -0,0 +1,44 @@
---
description: TypeScript / Vue3 通用编码规范,适用于所有 .ts / .vue / .scss 文件
globs: ["src/**/*.{ts,vue,js,scss,css}"]
alwaysApply: true
---
# 编码规范
## TypeScript
1. **禁止 `any`**。不确定类型用 `unknown` 后收窄,或定义显式类型;实在无法确定的第三方数据用 `Record<string, unknown>`。
2. 业务数据结构必须有 **显式 interface**,统一放在 `src/types/`,命名 `XxxVO / XxxForm / XxxQuery`,不内联在组件里。
3. 使用 `import type { Xxx } from '@/types/xxx'` 导入纯类型,避免运行时冗余引入。
4. 枚举统一定义在 `src/enums/`,使用 `const` 对象 + `as const` 或 `enum`,**禁止散落的魔法字符串/数字**。
5. 空值处理优先用可选链 `?.` 与空值合并 `??`,避免多层 `&&` 嵌套。
6. 不允许使用 `// @ts-ignore`、`eslint-disable` 掩盖问题;确有必要的必须写清原因。
## Vue 组件
1. 组件一律 `<script setup lang="ts">`,禁止 Options API。
2. `defineProps` / `defineEmits` 必须带类型:
```vue
const props = defineProps<{ modelValue: string; disabled?: boolean }>()
const emit = defineEmits<{ (e: 'update:modelValue', v: string): void }>()
```
3. Props 命名 camelCase,模板中绑定用 kebab-case;禁止在子组件内直接修改 props。
4. 单向数据流:子组件通过 emit 或 `defineExpose` 暴露方法,父组件通过 `ref` 调用。
5. 响应式选择:基础数据用 `ref`,对象表单用 `reactive`;**禁止对 `reactive` 整体重新赋值**,需要重置时用 `Object.assign`。
6. 副作用统一写在 `watch` / `watchEffect` / `onMounted`,并注意在 `onUnmounted` 清理定时器与事件监听。
7. 计算属性必须无副作用;有副作用的逻辑放到方法或 watch。
8. `v-if` 与 `v-for` 不得同处一个元素;`v-for` 必须提供稳定 `:key`(禁止用 index)。
9. 组件文件超过 300 行必须拆分;表单弹窗拆成独立子组件。
## 模板与样式
1. 模板保持简洁,复杂判断抽成 computed,禁止在模板里写长表达式。
2. 样式必须 `<style scoped lang="scss">`;覆盖 Element Plus 内部样式用 `:deep()`,且必须限定父级作用域,禁止全局裸写。
3. 颜色、圆角、间距等使用 `src/styles/variables.scss` 中的变量,**禁止硬编码色值**。
4. 布局优先使用 Element Plus 的 `el-row/el-col` 与 flex 工具类,不写魔法 `margin-top: 17px`。
## 注释与命名
1. 注释只解释"为什么",不解释"做了什么"。禁止无意义注释、禁止注释掉的代码块。
2. 函数/变量命名见名知意,布尔值用 `is/has/can` 前缀,事件处理用 `handleXxx`,接口调用用 `fetchXxx / createXxx / updateXxx / deleteXxx`。
3. 代码中不得出现 `console.log`、调试代码与 TODO 之外的临时标记。
## Git 提交
使用 Conventional Commits:`feat|fix|refactor|perf|style|docs|chore|test(scope): subject`,一次提交只做一件事。
+77
View File
@@ -0,0 +1,77 @@
---
description: 接口层与类型定义规范 —— 所有 src/api、src/types、src/utils/request.ts 相关代码必须遵守
globs: ["src/api/**/*.ts", "src/types/**/*.ts", "src/utils/**/*.ts", "src/**/*.d.ts"]
---
# 接口层与类型规范
## 铁律
1. **组件中不得出现 axios / request 的直接调用**,必须与后端交互的逻辑全部收敛到 `src/api/{module}/index.ts`。
2. **接口返回结构不得在组件里二次解包判断**,统一由 `request` 拦截器处理。
3. 未在 `src/types/` 定义类型的接口不得上线。
## 请求封装(`src/utils/request.ts`)
基于 Axios 单例,必须实现:
```ts
// 统一响应结构
interface ApiResponse<T> {
code: string | number
data: T
msg: string
}
```
- 请求拦截器:注入 `Authorization` Token、统一处理 GET 参数序列化。
- 响应拦截器:
- `code` 为成功值时**直接返回 `response.data.data`**(业务数据),调用方无需 `.data.data`。
- 业务失败:统一 `ElMessage.error(msg)`,并返回 `Promise.reject`。
- `401`:清除用户态 → 跳转登录;`403`:提示无权限。
- 网络错误 / 超时:统一提示,禁止调用方各自 try/catch 弹提示。
- 导出 `request<T>(config): Promise<T>`,泛型为业务数据类型。
## API 模块写法
每个业务模块一个文件,导出命名空间化的 API 对象:
```ts
// src/api/system/user/index.ts
import request from '@/utils/request'
import type { UserVO, UserQuery, UserForm } from '@/types/system/user'
const BASE_URL = '/api/v1/users'
export const UserAPI = {
/** 分页查询用户列表 */
query(params: UserQuery) {
return request<PageResult<UserVO>>({ url: `${BASE_URL}/page`, method: 'get', params })
},
detail(id: number) {
return request<UserVO>({ url: `${BASE_URL}/${id}`, method: 'get' })
},
create(data: UserForm) {
return request<number>({ url: BASE_URL, method: 'post', data })
},
update(id: number, data: UserForm) {
return request<boolean>({ url: `${BASE_URL}/${id}`, method: 'put', data })
},
delete(ids: string) {
return request<boolean>({ url: `${BASE_URL}/${ids}`, method: 'delete' })
},
}
```
约定:
- URL 前缀集中定义为 `BASE_URL`,禁止散写完整路径。
- 方法命名:`query / detail / create / update / delete / export / import / updateStatus` 等,语义固定。
- 分页统一返回 `PageResult<T>`(字段 `list` 与 `total`,在 `src/types/common.d.ts` 定义)。
## 类型定义
- 位置:`src/types/{module}/{entity}.ts` 或 `src/types/{module}.d.ts`。
- 三类基础模型必须区分:
- `XxxQuery`:列表查询参数(含 `pageNum`、`pageSize` 及筛选字段,全部可选)。
- `XxxVO`:后端返回的完整实体(含 `id`、`createTime` 等只读字段)。
- `XxxForm`:新增/编辑表单(不含只读字段,所有字段可选性按后端要求)。
- 禁止用 `XxxVO` 直接充当表单类型;编辑时用 `Object.assign(form, row)` 或显式字段映射。
## Mock
接口未就绪时优先与后端约定字段后先写类型与 API,再在 `mock/` 下补临时数据,**禁止为了跑通页面而放宽类型**。
+72
View File
@@ -0,0 +1,72 @@
---
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 缓存,不得每次打开弹窗都重新请求。
+43
View File
@@ -0,0 +1,43 @@
---
description: Element Plus 使用规范、权限控制与 Pinia 状态管理约定
globs: ["src/**/*.{vue,ts}", "src/styles/**/*.scss", "src/router/**/*.ts", "src/store/**/*.ts", "src/directives/**/*.ts"]
---
# UI、权限与状态管理
## Element Plus
1. 组件与图标通过 `unplugin-vue-components` / `unplugin-auto-import` **按需自动导入**;必须手动补充的(如 `ElMessage`、`ElMessageBox`、`ElLoading`)在 `src/types/element-plus.d.ts` 声明,禁止全量 `import ElementPlus from 'element-plus'`。
2. 消息提示统一使用 `ElMessage` / `ElMessageBox` / `ElNotification`,**禁止 `alert` / `confirm` 原生弹窗**。
- 成功 `ElMessage.success`,失败由拦截器统一处理,删除确认用 `ElMessageBox.confirm`。
- 同一操作不要堆叠多条 message。
3. 全局默认配置(size、zIndex、中文语言包)在 `main.ts` 或 `App.vue` 的 `ElConfigProvider` 中统一设置,不在每个组件重复传 `size`。
4. 表单统一 `label-width` 与布局:搜索栏 `inline`,弹窗表单 `label-position="right"`,标签宽度按项目约定(默认 `100px`)。
5. 表格空数据、图片加载失败等边界状态必须处理(`empty-text`、默认头像)。
6. 主题定制通过 `src/styles/element/index.scss` 覆盖 SCSS 变量,**禁止用 `!important` 打补丁**;确需覆盖内部样式时用 `:deep()` 且限定作用域。
## 权限
1. 采用「动态路由 + 按钮级权限」双层控制:
- 菜单/页面:后端返回路由表,前端 `router/index.ts` 中动态 `addRoute`,静态路由仅保留登录、404、首页。
- 按钮:`v-hasPerm="['sys:user:add']"` 指令,指令实现从用户 store 读取权限集合,无权限时**移除 DOM 节点**而非仅禁用。
2. 路由元信息 `meta` 字段固定:`title`、`icon`、`hidden`、`roles`、`keepAlive`、`breadcrumb`。
3. 路由守卫必须处理:已登录访问登录页跳转首页、无 Token 跳登录并携带 `redirect`、路由不存在跳 404。
4. 权限判断不得散落在业务代码里写字符串比较,统一走指令或 store 中的 `hasPermission()`。
## Pinia
1. 一律使用 **setup store** 语法:
```ts
export const useUserStore = defineStore('user', () => {
const token = ref('')
const permissions = ref<string[]>([])
function resetToken() { token.value = '' }
return { token, permissions, resetToken }
}, { persist: true })
```
2. 一个业务域一个 store,放 `src/store/modules/{module}.ts`,index 统一导出。
3. 需要持久化的字段(token、主题、标签栏)必须显式声明 `persist` 路径,**禁止整个 store 无脑持久化**,敏感信息不落 localStorage。
4. 组件内不得直接修改其他模块的 store 状态,需通过该 store 暴露的 action。
5. 接口请求优先放 API 层;store 只做状态存放与跨组件共享,不承载大量业务过程。
## 路由与页面缓存
- 页面组件 `name` 必须与路由 `name` 一致,否则 `<keep-alive>` 的 `include/exclude` 失效。
- 列表页默认开启缓存;从详情页返回时保留查询条件,从菜单进入时重置。
+29
View File
@@ -0,0 +1,29 @@
---
description: 需求到交付的协作流程 —— 新增功能、修改代码、联调与自测时的执行顺序
globs: ["**/*"]
---
# 协作流程
## 接到需求后的执行顺序
1. **先确认契约**:与后端确认接口路径、请求/响应字段、分页结构、错误码;未确认前不得凭猜测写页面。
2. **先写类型**:在 `src/types/{module}/` 下定义 `Query / VO / Form`。
3. **再写 API**:`src/api/{module}/index.ts`。
4. **最后写页面**:列表 → 表单弹窗 → 详情(按需)。
5. **自测**:`pnpm type-check` 与 `pnpm lint` 必须通过;再手动走通「空数据 / 查询 / 新增 / 编辑 / 删除 / 无权限 / 接口报错」七条路径。
## 代码修改原则
- 修改前先读完整文件与相关调用方,禁止盲目整文件重写。
- 优先用局部小改动( targeted edit )解决;确需重构时先说明影响面。
- 不要顺手重构无关代码、不要调整无关格式、不要删除他人注释。
- 不要新增未被要求的功能、文档文件(README/md)或测试用例。
## 产出要求
- 生成的代码必须可直接运行:补齐 import、类型、路由注册、store 注册等所有必要接线。
- 新增页面必须在对应路由/菜单中注册,并保证能真实访问。
- 新增依赖需说明用途与体积影响,优先选择已在技术栈内的方案。
## 禁止事项
- 提交前遗留 `console.log`、调试代码、TODO 未处理项。
- 使用已废弃 API(Vue 2 写法、`$refs` 字符串、`filters`、Element UI 组件名)。
- 为了跑通而放宽类型、加 `any`、`@ts-ignore`、关闭 ESLint 规则。