init: 初始化 hgmj 后台管理项目

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 17:48:09 +08:00
co-authored by Claude Opus 4.7
commit 397398b746
645 changed files with 179153 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
# Skills 导航
本目录提供项目级 Cursor Skills,面向本仓库的 `Vue2 + Element UI` 管理端开发。
## 当前可用 Skills
### 1) 页面与组件开发
- 路径:`vue2-element-dev/SKILL.md`
- 适用:新增/修改列表页、表单弹窗、复用组件
- 重点:页面结构、状态管理、方法组织、交付前检查
### 2) 接口联调
- 路径:`vue2-api-integration/SKILL.md`
- 适用:接口接入、参数映射、错误处理、鉴权异常处理
- 重点:请求层与页面层分工、响应兜底、401/403、防重复提交
### 3) 路由与权限接入
- 路径:`vue2-route-permission/SKILL.md`
- 适用:新增页面后接菜单、接路由、排查可见性/可访问问题
- 重点:模块路由拆分、`permissionRouter` 聚合、菜单激活与一致性
## 推荐使用顺序
新增业务功能时,建议按以下顺序使用:
1. 先看 `vue2-element-dev`,把页面骨架与交互结构搭好
2. 再用 `vue2-api-integration` 完成查询/提交联调和异常兜底
3. 最后用 `vue2-route-permission` 接入路由菜单并做入口验证
## 每套 Skill 的配套文件
每个 skill 目录都包含:
- `SKILL.md`:主规范与流程
- `examples.md`:可参考的最小示例
- `checklist.md`:提交前自检项
## 团队使用建议
- 优先复用现有页面模式,不做无关重构
- 新功能至少走一遍对应 `checklist.md`
- 若出现同类问题反复发生,可在对应 skill 中沉淀新增规则
+97
View File
@@ -0,0 +1,97 @@
---
name: commit-convention
description: 为本仓库生成符合团队规范的 Git 提交信息,并在用户要求提交代码时执行完整提交流程。用于 git commit、提交代码、写 commit message、创建提交、push 前整理提交等场景;Agent 代提交时必须使用 [Ai] 标记并写清业务变更。
---
# Git 提交规范(Agent 代提交)
## 触发条件
满足任一条件,**必须先加载本 skill**,再执行 `git add` / `git commit`:
- 用户明确要求:提交、commit、git commit、保存提交、push 前先 commit 等
- 用户规则中的「committing-changes-with-git」流程已启动
**未获用户明确授权时,不得执行 commit。**
---
## Agent 代提交:标记规则(强制)
由 Cursor Agent 发起的提交,**必须**在 subject 前加 `[Ai]`:
```text
[Ai] <type>(<scope>): <subject>
<body 可选:业务背景、影响范围、关联 Bug>
```
- `type`:`feat` | `fix` | `refactor` | `style` | `perf` | `chore` | `docs` | `test` | `build` | `ci`
- `scope`:模块/页面,如 `benefits`、`mine`、`community`、`player`
- 可在正文末尾追加 trailer(可选):
```text
Co-Authored-By: Cursor <noreply@cursor.com>
```
用户本人手动提交、且明确说明「人工提交」时,建议使用 `[Human]`;Agent **不要**替用户假标 `[Human]`。
---
## Subject 质量(强制)
提交说明必须让人一眼看懂**改了什么业务**或**修了什么问题**。
### 必须做到
1. 先并行执行:`git status`、`git diff`(含 staged)、`git log -5 --oneline` 了解风格与变更范围
2. subject 用**完整语义**描述,优先中文;可中英混用 scope
3. 多文件、多模块时:一条 commit 只包同一业务目标;若混杂无关改动,先询问是否拆分
4. body 写:原因、用户可见变化、风险点(如有)
### 严禁(subject 不得仅为或等同于)
`test`、`修改`、`update`、`fix`、`xxx`、`111`、`wip`、`temp`、`提交`、`save`、纯标点、纯数字、单字
### 示例
```text
# ✅
[Ai] feat(benefits): 新增七日签到弹窗与连续签到奖励展示
[Ai] fix(player): 修复横屏切换后 HLS 首帧黑屏
[Ai] refactor(mine): 签到任务列表抽离 useSignTask composable
# ❌
[Ai] fix: 修改
[Ai] feat: test
修改播放器
111
```
---
## 提交流程(与 user rule 对齐)
1. `git status` + `git diff` + `git log`(可并行)
2. 根据 diff **撰写**符合本规范的 message(HEREDOC 传 `-m`)
3. 只 `git add` 与本次任务相关的文件
4. **不得**提交 `.env`、密钥、凭据类文件;若用户在 staged 中含此类文件,警告并排除
5. `git commit` 后 `git status` 确认成功
6. hook 失败时:**不要** `commit --amend`,修 message 后**新建 commit**
7. **除非用户明确要求,否则不 `git push`**
---
## 输出给用户
提交完成后简要说明:
- commit hash(若有)
- 本次纳入的文件范围
- message 摘要(一行)
---
## 与本地 Hook / CI 的关系
本 skill 约束 **Agent 行为**。若仓库后续接入 `husky` + `commitlint` 或 CI,以仓库脚本校验为准;Agent 仍须预先满足 `[Ai]` 与 subject 质量,避免 hook 拒绝。
@@ -0,0 +1,63 @@
---
name: vue2-api-integration
description: Use when implementing or debugging API integration in this Vue2 project, including request parameter mapping, response normalization, business error handling, auth expiry handling, and submit/list flow consistency.
disable-model-invocation: true
---
# Vue2 API Integration
## 适用场景
- 新增或修改 `src/api/*.js` 接口方法
- 页面查询、提交、删除、状态切换的接口联调
- 线上/测试环境出现接口报错、数据格式不一致、权限失效
## 联调核心原则
- **一处定义接口**:请求方法统一维护在 `src/api`,页面层不写裸请求
- **参数先整形再发送**:空值、时间范围、布尔值、数字类型先归一化
- **响应做兜底**:列表、分页、详情都要有默认值,避免空指针
- **错误统一反馈**:用户看得懂、可操作;开发可定位
- **鉴权失效可恢复**:401/403 走统一处理,避免页面卡死
## 请求层约定
- GET 查询使用 `params`,POST/PUT 使用 `data`
- 接口函数命名表达业务动作:`getXxxList`、`createXxx`、`updateXxx`
- 同类接口保持参数结构一致,降低页面层分支逻辑
## 页面层约定
- 页面只做调用、状态切换、提示和数据映射
- 所有异步方法都要有 `loading` 生命周期(开始/结束)
- 提交类动作默认防重复:`submitLoading` 或按钮禁用
- 删除/危险操作必须确认后调用
## 常见数据映射
- 时间区间:`[startTime, endTime]` 拆成后端需要字段
- 开关状态:`true/false` 映射为后端约定值(如 `1/0`)
- 数字输入:提交前转数字,避免 `"1"`/`1` 混用
- 列表响应:`list`、`total` 必须有默认值
## 错误与鉴权处理
- 业务失败:提示明确失败原因,不吞错误
- 网络异常:提示“网络异常,请稍后重试”
- 401:提示登录失效并引导重新登录
- 403:提示无权限并保持当前页面可回退
- 异常分支必须收尾:关闭 loading、恢复按钮状态
## 推荐联调流程
1. 先确认接口文档字段和返回结构
2. 在 `src/api` 定义/更新请求函数
3. 页面接入并补齐参数整形与响应兜底
4. 跑通成功路径(查询/提交)
5. 验证失败路径(业务报错/网络异常/无权限)
6. 执行 lint 或 test-build 确认无新增阻断问题
## 快速参考
- 示例代码:见 [examples.md](examples.md)
- 联调检查表:见 [checklist.md](checklist.md)
@@ -0,0 +1,30 @@
# Checklist
## 接口定义检查
- [ ] 接口方法统一定义在 `src/api/*.js`
- [ ] GET 使用 `params`,POST/PUT 使用 `data`
- [ ] 方法名与业务动作匹配(get/create/update/delete)
- [ ] 请求路径、method、参数字段与后端约定一致
## 页面联调检查
- [ ] 查询参数已整形(时间范围、布尔、数字、空值)
- [ ] 列表响应有默认值(`list`、`total`)
- [ ] 提交按钮有防重复(`submitLoading`/禁用)
- [ ] 删除/危险动作有二次确认
- [ ] 异常场景会关闭 loading 并提示用户
## 错误与权限检查
- [ ] 业务错误提示清晰,不吞异常
- [ ] 网络异常提示可理解(可重试)
- [ ] 401 有登录失效处理
- [ ] 403 有无权限处理与回退路径
## 交付前检查
- [ ] 成功路径通过(查询/提交/删除)
- [ ] 失败路径通过(业务失败/网络异常/权限失败)
- [ ] 执行 `npm run lint` 或 `npm run test-build`
- [ ] 不提交无关改动
@@ -0,0 +1,78 @@
# Examples
## API 方法定义示例
```js
import request from "@/utils/request";
export function getCheckInList(params) {
return request({
url: "/signin/checkin/list",
method: "get",
params
});
}
export function saveCheckInConfig(data) {
return request({
url: "/signin/checkin/config/save",
method: "post",
data
});
}
```
## 查询参数整形示例
```js
buildQueryParams() {
const { dateRange, status, pageNum, pageSize, keyword } = this.queryForm;
return {
pageNum,
pageSize,
keyword: keyword ? keyword.trim() : "",
status: typeof status === "boolean" ? (status ? 1 : 0) : status,
startTime: dateRange && dateRange.length ? dateRange[0] : "",
endTime: dateRange && dateRange.length ? dateRange[1] : ""
};
}
```
## 列表请求与兜底示例
```js
async fetchList() {
this.loading = true;
try {
const params = this.buildQueryParams();
const res = await getCheckInList(params);
const data = (res && res.data) || {};
this.tableData = Array.isArray(data.list) ? data.list : [];
this.total = Number(data.total || 0);
} catch (e) {
this.$message.error("列表加载失败,请稍后重试");
} finally {
this.loading = false;
}
}
```
## 提交流程与防重复示例
```js
async handleSubmit() {
if (this.submitLoading) return;
this.submitLoading = true;
try {
await this.$refs.formRef.validate();
await saveCheckInConfig(this.formData);
this.$message.success("保存成功");
this.dialogVisible = false;
this.fetchList();
} catch (e) {
this.$message.error("保存失败,请检查后重试");
} finally {
this.submitLoading = false;
}
}
```
+70
View File
@@ -0,0 +1,70 @@
---
name: vue2-element-dev
description: Use when implementing or modifying Vue2 + Element UI pages/components in this repository, especially for list pages, form dialogs, API integration, route/menu wiring, and pre-delivery checks.
disable-model-invocation: true
---
# Vue2 Element Dev
## 适用场景
- 新增或修改 `src/views` 下的页面(列表、表单、弹窗)
- 新增或修改 `src/components` 下的复用组件
- 做接口联调、参数映射、提交与回显
- 做路由接入、导航菜单接入、权限相关入口调整
## 项目默认约定(平衡型)
- **优先复用现有模式**:先参考同目录页面写法,再补差异
- **命名可读**:页面目录、组件名、方法名优先语义化
- **数据分层**:查询参数、表单数据、UI 状态分开管理
- **接口调用单点化**:统一放到 `src/api/*.js`,页面仅做调用和状态处理
- **样式就近维护**:页面样式写在组件内,避免全局污染
## 页面开发流程
1. 先找一个同类型页面作为参考(表格页/配置页/弹窗页)
2. 搭出页面骨架:筛选区、操作区、表格区、分页区
3. 补齐数据结构:`list`、`loading`、`query`、`pagination`
4. 接入接口:查询、重置、增删改、状态切换
5. 完成交互反馈:确认弹窗、成功提示、失败兜底
6. 执行构建或 lint 验证,修复明显问题后再交付
## 推荐代码组织
- `data`
- 查询模型:`queryForm`
- 列表模型:`tableData`
- 状态模型:`loading` / `dialogVisible` / `submitLoading`
- `computed`
- 放衍生值,不放有副作用逻辑
- `methods`
- `fetchList`:统一列表查询入口
- `handleSearch` / `handleReset`
- `handleAdd` / `handleEdit` / `handleDelete`
- `handleSubmit`:表单提交统一出口
## 接口联调约定
- API 文件里维护请求函数,页面中只做调用
- 参数透传前做必要整形(时间范围、数字布尔转换)
- 提交前做前端必填校验;提交后统一提示
- 失败场景要收敛:关闭 loading、保留用户输入、提示可理解信息
## 路由与菜单接入约定
- 新页面需要补路由入口与导航入口
- 菜单文案与页面标题保持一致,避免多处命名不一致
- 权限字段沿用现有规则,不自创新字段名
## 交付前最低检查
- 页面可正常进入,主流程可跑通(查、增/改、删)
- 列表页分页/筛选联动正常
- 弹窗表单可回显、可校验、可提交
- 无明显控制台报错,构建或 lint 不出现新增阻断错误
## 快速参考
- 详细示例:见 [examples.md](examples.md)
- 提交前清单:见 [checklist.md](checklist.md)
@@ -0,0 +1,25 @@
# Checklist
## 开发中检查
- [ ] 页面结构完整(筛选、表格、分页/操作区)
- [ ] `loading` 状态在请求前后正确切换
- [ ] 查询、重置、分页逻辑一致
- [ ] 新增/编辑弹窗支持回显、校验、提交
- [ ] 删除/状态切换有确认提示与成功反馈
- [ ] API 参数字段与后端约定一致
## 接入检查
- [ ] 新页面路由已添加
- [ ] 导航菜单已添加或已挂接
- [ ] 页面标题、菜单名称、权限标识一致
- [ ] 未破坏已有页面入口
## 交付前检查
- [ ] 至少执行一次 `npm run lint` 或 `npm run test-build`
- [ ] 控制台无新增阻断错误
- [ ] 主流程可复现(查、增/改、删)
- [ ] 异常分支有兜底(失败提示、loading 关闭)
- [ ] 仅提交与本需求相关改动
+127
View File
@@ -0,0 +1,127 @@
# Examples
## 列表页最小骨架(Vue2 + Element UI)
```vue
<template>
<div class="page-wrap">
<el-form :inline="true" :model="queryForm">
<el-form-item label="关键词">
<el-input v-model="queryForm.keyword" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" v-loading="loading" border>
<el-table-column prop="id" label="ID" width="100" />
<el-table-column prop="name" label="名称" min-width="160" />
</el-table>
<el-pagination
:current-page="queryForm.pageNum"
:page-size="queryForm.pageSize"
:total="total"
layout="total, prev, pager, next"
@current-change="handlePageChange"
/>
</div>
</template>
<script>
import { getList } from "@/api/demo";
export default {
name: "DemoList",
data() {
return {
loading: false,
tableData: [],
total: 0,
queryForm: {
keyword: "",
pageNum: 1,
pageSize: 20
}
};
},
created() {
this.fetchList();
},
methods: {
async fetchList() {
this.loading = true;
try {
const res = await getList(this.queryForm);
this.tableData = res?.data?.list || [];
this.total = res?.data?.total || 0;
} finally {
this.loading = false;
}
},
handleSearch() {
this.queryForm.pageNum = 1;
this.fetchList();
},
handleReset() {
this.queryForm.keyword = "";
this.queryForm.pageNum = 1;
this.fetchList();
},
handlePageChange(page) {
this.queryForm.pageNum = page;
this.fetchList();
}
}
};
</script>
```
## API 文件示例
```js
import request from "@/utils/request";
export function getList(params) {
return request({
url: "/api/demo/list",
method: "get",
params
});
}
export function createItem(data) {
return request({
url: "/api/demo/create",
method: "post",
data
});
}
```
## 表单弹窗处理示例
```js
methods: {
openDialog(row) {
this.dialogVisible = true;
this.formData = row
? { id: row.id, name: row.name }
: { id: "", name: "" };
},
async handleSubmit() {
this.submitLoading = true;
try {
await this.$refs.formRef.validate();
await createItem(this.formData);
this.$message.success("保存成功");
this.dialogVisible = false;
this.fetchList();
} finally {
this.submitLoading = false;
}
}
}
```
@@ -0,0 +1,56 @@
---
name: vue2-route-permission
description: Use when adding or modifying route/menu/permission access in this Vue2 admin project, including module router files, permissionRouter aggregation, sidebar visibility, and route metadata consistency.
disable-model-invocation: true
---
# Vue2 Route Permission
## 适用场景
- 新增一个业务页面,需要接入菜单与路由
- 新增一个模块路由文件并接入 `permissionRouter`
- 调整页面可见性、菜单结构、入口名称
- 排查“路由已写但菜单不显示 / 可见但不可访问”问题
## 本项目路由结构认知
- 模块路由拆分在 `src/router/*.js`(例如 `signin.js`)
- 聚合路由在 `src/router/permissionRouter.js`
- 侧边栏使用 `store.state.routerList` 渲染(见 `src/views/main/components/nav.vue`)
## 接入原则
- **单模块独立**:每个业务域维护自己的路由文件
- **统一聚合出口**:模块路由必须在 `permissionRouter.js` 引入并加入数组
- **元信息一致**:`title`、`meta.title`、`name`、`path` 保持语义一致
- **层级清晰**:一级菜单挂在 `Main` 下,子页面在 `children`
## 新页面接入流程
1. 在 `src/views` 创建页面组件并命名清晰
2. 在对应模块路由文件新增子路由(或新建模块路由文件)
3. 设置基础字段:`path`、`name`、`title`、`meta.title`
4. 需要缓存时补 `meta.keepAlive`
5. 在 `permissionRouter.js` 引入并追加模块路由
6. 验证菜单显示、点击跳转、激活状态、刷新后可访问
## 字段约定
- `path`:使用短横线风格,避免含糊缩写
- `name`:驼峰命名,保持唯一
- `title`:用于菜单与页面文案,避免同义不同字
- `icon`:仅一级菜单使用,沿用 Element 图标
- `meta.title`:与 `title` 保持一致,方便统一读取
## 常见问题处理
- **菜单不显示**:检查模块路由是否已加入 `permissionRouter` 聚合数组
- **菜单显示但跳转异常**:检查 `path` 与组件导入是否正确
- **激活态不正确**:检查路由跳转后 `activeRouter` 是否对应当前 `path`
- **刷新丢失访问**:检查路由注册时机和权限数据初始化顺序
## 快速参考
- 接入示例:见 [examples.md](examples.md)
- 自检清单:见 [checklist.md](checklist.md)
@@ -0,0 +1,28 @@
# Checklist
## 新增路由检查
- [ ] 页面组件已创建且导出正常
- [ ] 模块路由文件中已添加对应子路由
- [ ] 子路由包含 `path`、`name`、`title`、`meta.title`
- [ ] `name` 唯一,`path` 可读且与业务语义一致
## 聚合与菜单检查
- [ ] 新模块路由已在 `permissionRouter.js` 引入
- [ ] 已加入聚合数组并保持风格一致
- [ ] 一级菜单 `title/icon` 设置合理
- [ ] 侧边栏可见,点击可跳转
## 权限与体验检查
- [ ] 刷新页面后路由仍可访问
- [ ] 无权限场景有明确反馈(如项目已有拦截逻辑)
- [ ] 当前菜单激活态与页面路径一致
- [ ] 页面标题与菜单文案一致
## 交付前检查
- [ ] 至少执行一次 `npm run lint` 或 `npm run test-build`
- [ ] 无新增阻断报错
- [ ] 仅提交与路由接入相关改动
@@ -0,0 +1,65 @@
# Examples
## 模块路由文件示例(`src/router/demo.js`)
```js
import Main from "../views/main";
import DemoList from "../views/demo/list";
import DemoConfig from "../views/demo/config";
const DemoRouter = {
path: "/demo",
component: Main,
title: "示例管理",
icon: "el-icon-menu",
meta: {
keepAlive: true
},
children: [
{
path: "/demo-list",
component: DemoList,
name: "demoList",
title: "示例列表",
meta: {
title: "示例列表",
keepAlive: true
}
},
{
path: "/demo-config",
component: DemoConfig,
name: "demoConfig",
title: "示例配置",
meta: {
title: "示例配置",
keepAlive: true
}
}
]
};
export default DemoRouter;
```
## 聚合接入示例(`src/router/permissionRouter.js`)
```js
import DemoRouter from "./demo";
const authRouter = [
// ...existing routers
DemoRouter
];
export default authRouter;
```
## 路由排查最小步骤
```js
// 1. 看 router 文件是否 export default
// 2. 看 permissionRouter 是否 import + push
// 3. 看 path/name/title/meta.title 是否齐全
// 4. 看页面组件路径是否正确
```