jsharness 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.harness/README.md +199 -0
- package/.harness/agents/code-reviewer/contract.yaml +64 -0
- package/.harness/agents/developer/contract.yaml +72 -0
- package/.harness/agents/gate-controller/contract.yaml +64 -0
- package/.harness/agents/project-manager/contract.yaml +77 -0
- package/.harness/agents/prompt-templates.md +352 -0
- package/.harness/agents/requirements-analyst/contract.yaml +64 -0
- package/.harness/agents/solution-designer/contract.yaml +75 -0
- package/.harness/agents/tester/contract.yaml +92 -0
- package/.harness/config/models.yaml +67 -0
- package/.harness/dev-map/backend/api-definition.md +131 -0
- package/.harness/dev-map/backend/auth-security.md +131 -0
- package/.harness/dev-map/backend/conventions-java.md +471 -0
- package/.harness/dev-map/backend/conventions.md +192 -0
- package/.harness/dev-map/backend/database.md +106 -0
- package/.harness/dev-map/backend/structure.md +140 -0
- package/.harness/dev-map/decisions.md +275 -0
- package/.harness/dev-map/frontend/api-integration.md +139 -0
- package/.harness/dev-map/frontend/components.md +178 -0
- package/.harness/dev-map/frontend/conventions.md +416 -0
- package/.harness/dev-map/frontend/state-management.md +170 -0
- package/.harness/dev-map/frontend/structure.md +103 -0
- package/.harness/dev-map/overview.md +267 -0
- package/.harness/docs/integration-test-plan.md +248 -0
- package/.harness/docs/team-guidelines/README.md +161 -0
- package/.harness/docs/team-guidelines/arch-team.md +811 -0
- package/.harness/docs/team-guidelines/collaboration.md +556 -0
- package/.harness/docs/team-guidelines/pm-team.md +337 -0
- package/.harness/docs/team-guidelines/qa-team.md +562 -0
- package/.harness/docs/team-guidelines/rd-team.md +714 -0
- package/.harness/docs/training-materials.md +280 -0
- package/.harness/gate/baseline.js +220 -0
- package/.harness/gate/checks/build-gates-frontend.js +152 -0
- package/.harness/gate/checks/build-gates-java.js +155 -0
- package/.harness/gate/checks/build-gates.js +119 -0
- package/.harness/gate/checks/engineering-consistency.js +138 -0
- package/.harness/gate/checks/security-quality.js +129 -0
- package/.harness/gate/checks/static-compliance.js +313 -0
- package/.harness/gate/checks/test-compliance.js +114 -0
- package/.harness/gate/index.js +315 -0
- package/.harness/mcp/config.yaml +435 -0
- package/.harness/rules/global/coding-standard.md +232 -0
- package/.harness/rules/global/commit-convention.md +165 -0
- package/.harness/rules/global/process-discipline.md +192 -0
- package/.harness/rules/global/security-baseline.md +306 -0
- package/.harness/rules/project/frontend-vue3.md +293 -0
- package/.harness/rules/project/java-backend.md +460 -0
- package/.harness/rules/project/web-specific.md +231 -0
- package/.harness/skills/build.md +192 -0
- package/.harness/skills/code-review.md +251 -0
- package/.harness/skills/docker-build.md +227 -0
- package/.harness/skills/docs-update.md +164 -0
- package/.harness/skills/java-build.md +261 -0
- package/.harness/skills/lint-check.md +482 -0
- package/.harness/skills/task-board-maintenance.md +105 -0
- package/.harness/skills/test-api.md +461 -0
- package/.harness/skills/test-e2e.md +431 -0
- package/.harness/skills/test-unit.md +649 -0
- package/.harness/skills/vue-frontend-build.md +344 -0
- package/.harness/specs/quality-feedback/implementation-guide.md +350 -0
- package/.harness/task-board.md +121 -0
- package/.harness/workflow/definition.yaml +504 -0
- package/.harness/workflow/validate.js +320 -0
- package/.harness/workflow/variants.yaml +253 -0
- package/README.md +237 -0
- package/bin/jsharness.js +53 -0
- package/lib/index.mjs +778 -0
- package/package.json +1 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# 前端分区 — 组件库与复用模式
|
|
2
|
+
|
|
3
|
+
## 通用 UI 组件库 (`components/ui/`) — Element Plus 二次封装
|
|
4
|
+
|
|
5
|
+
本项目基于 **Element Plus** 进行二次封装,确保全局风格统一和便捷使用。
|
|
6
|
+
|
|
7
|
+
### 已有组件清单
|
|
8
|
+
|
|
9
|
+
| 组件名 | 文件路径 | Props | 用途 | 基于 |
|
|
10
|
+
|--------|----------|-------|------|------|
|
|
11
|
+
| `AppButton` | `ui/button/AppButton.vue` | variant, size, loading, disabled | 按钮 | ElButton |
|
|
12
|
+
| `AppInput` | `ui/input/AppInput.vue` | type, error, prefix, suffix | 输入框 | ElInput |
|
|
13
|
+
| `AppModal` | `ui/modal/AppModal.vue` | v-model:visible, title, size | 弹窗 | ElDialog |
|
|
14
|
+
| `AppTable` | `ui/table/AppTable.vue` | columns, data, pagination | 数据表格 | ElTable |
|
|
15
|
+
| `AppCard` | `ui/card/AppCard.vue` | title, actions | 卡片容器 | 自定义 |
|
|
16
|
+
| `AppEmptyState` | `ui/empty-state/AppEmptyState.vue` | icon, title, description | 空状态占位 | 自定义 |
|
|
17
|
+
| `AppConfirmDialog` | `ui/confirm-dialog/AppConfirmDialog.vue` | title, message, onConfirm | 确认对话框 | ElMessageBox |
|
|
18
|
+
| `AppAvatar` | `ui/avatar/AppAvatar.vue` | src, name, size | 用户头像 | 自定义 |
|
|
19
|
+
|
|
20
|
+
### 组件设计规范
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<!-- ✅ 正确:组件接口清晰,支持多态 -->
|
|
24
|
+
<script setup lang="ts">
|
|
25
|
+
interface AppButtonProps {
|
|
26
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'danger'
|
|
27
|
+
size?: 'sm' | 'md' | 'lg'
|
|
28
|
+
loading?: boolean
|
|
29
|
+
disabled?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const props = withDefaults(defineProps<AppButtonProps>(), {
|
|
33
|
+
variant: 'primary',
|
|
34
|
+
size: 'md',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const emit = defineEmits<{
|
|
38
|
+
click: [event: MouseEvent]
|
|
39
|
+
}>()
|
|
40
|
+
|
|
41
|
+
const handleClick = (e: MouseEvent) => {
|
|
42
|
+
if (!props.disabled && !props.loading) {
|
|
43
|
+
emit('click', e)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<ElButton
|
|
50
|
+
:type="variant"
|
|
51
|
+
:size="size"
|
|
52
|
+
:loading="loading"
|
|
53
|
+
:disabled="disabled"
|
|
54
|
+
@click="handleClick"
|
|
55
|
+
>
|
|
56
|
+
<slot />
|
|
57
|
+
</ElButton>
|
|
58
|
+
</template>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// ✅ 正确:统一导出模式
|
|
63
|
+
// components/ui/button/index.ts
|
|
64
|
+
export { default as AppButton } from './AppButton.vue'
|
|
65
|
+
export type { AppButtonProps } from './AppButton.vue'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 业务功能组件 (`components/features/`)
|
|
69
|
+
|
|
70
|
+
### 组织方式
|
|
71
|
+
|
|
72
|
+
按**功能域**组织,每个功能域一个文件夹:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
features/
|
|
76
|
+
├── user-management/ # 用户管理相关
|
|
77
|
+
│ ├── UserList.vue # 用户列表
|
|
78
|
+
│ ├── UserDetail.vue # 用户详情
|
|
79
|
+
│ ├── UserForm.vue # 新建/编辑表单
|
|
80
|
+
│ ├── UserAvatar.vue # 头像组件
|
|
81
|
+
│ └── index.ts
|
|
82
|
+
├── data-dashboard/ # 数据看板相关
|
|
83
|
+
│ ├── StatsCard.vue # 统计卡片
|
|
84
|
+
│ ├── TrendChart.vue # 趋势图表
|
|
85
|
+
│ └── DataTable.vue # 数据表格
|
|
86
|
+
└── notification/ # 通知相关
|
|
87
|
+
├── NotificationList.vue
|
|
88
|
+
└── NotificationBadge.vue
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 复用模式
|
|
92
|
+
|
|
93
|
+
### 1. 组合模式(Composition / Slots)
|
|
94
|
+
|
|
95
|
+
```vue
|
|
96
|
+
<!-- 将复杂 UI 拆分为原子组件的组合 -->
|
|
97
|
+
<template>
|
|
98
|
+
<AppDataTable>
|
|
99
|
+
<AppDataTable.Header>
|
|
100
|
+
<AppDataTable.Column id="name" sortable>姓名</AppDataTable.Column>
|
|
101
|
+
<AppDataTable.Column id="email">邮箱</AppDataTable.Column>
|
|
102
|
+
</AppDataTable.Header>
|
|
103
|
+
<AppDataTable.Body>
|
|
104
|
+
<!-- 内容由 slot 注入 -->
|
|
105
|
+
</AppDataTable.Body>
|
|
106
|
+
</AppDataTable>
|
|
107
|
+
</template>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 2. Slots / Scoped Slots 模式
|
|
111
|
+
|
|
112
|
+
```vue
|
|
113
|
+
<!-- 让父组件控制内部渲染逻辑 -->
|
|
114
|
+
<template>
|
|
115
|
+
<AppCard>
|
|
116
|
+
<template #header>
|
|
117
|
+
<AppCard.Title>标题</AppCard.Title>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<!-- 子内容由使用者决定 -->
|
|
121
|
+
<slot />
|
|
122
|
+
|
|
123
|
+
<template #footer>
|
|
124
|
+
<AppButton @click="$emit('save')">保存</AppButton>
|
|
125
|
+
</template>
|
|
126
|
+
</AppCard>
|
|
127
|
+
</template>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 3. Composable + 组件分离(逻辑复用)
|
|
131
|
+
|
|
132
|
+
```vue
|
|
133
|
+
<!-- 逻辑抽离为 Composable,组件只负责渲染 -->
|
|
134
|
+
<script setup lang="ts">
|
|
135
|
+
// composables/useUserList.ts
|
|
136
|
+
function useUserList() {
|
|
137
|
+
const { data: users, loading, error, refresh } = useFetch('/api/users')
|
|
138
|
+
|
|
139
|
+
if (loading.value) return { isLoading: true }
|
|
140
|
+
if (error.value) return { isError: true, error, retry: refresh }
|
|
141
|
+
|
|
142
|
+
return { users, isLoading: false, isError: false, refresh }
|
|
143
|
+
}
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<template>
|
|
147
|
+
<!-- UserList.vue 只负责展示 -->
|
|
148
|
+
<div v-loading="isLoading">
|
|
149
|
+
<AppEmptyState v-if="isError" :title="'加载失败'" @retry="retry" />
|
|
150
|
+
<template v-else>
|
|
151
|
+
<UserCard v-for="user in users" :key="user.id" :user="user" />
|
|
152
|
+
</template>
|
|
153
|
+
</div>
|
|
154
|
+
</template>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 禁止的反模式
|
|
158
|
+
|
|
159
|
+
```vue
|
|
160
|
+
<!-- ❌ 禁止:在组件中直接 fetch(应使用 Composable) -->
|
|
161
|
+
<script setup lang="ts">
|
|
162
|
+
const userId = defineProp<string>('id')
|
|
163
|
+
const user = ref(null)
|
|
164
|
+
|
|
165
|
+
onMounted(async () => {
|
|
166
|
+
const res = await fetch(`/api/users/${userId}`)
|
|
167
|
+
user.value = await res.json()
|
|
168
|
+
}) // 应使用 useUser(id) Composable
|
|
169
|
+
</script>
|
|
170
|
+
|
|
171
|
+
<!-- ❌ 禁止:硬编码中文字符串(应使用 i18n) -->
|
|
172
|
+
<template>
|
|
173
|
+
<h1>用户信息</h1><!-- 应使用 {{ t('user.profile') }} -->
|
|
174
|
+
</template>
|
|
175
|
+
|
|
176
|
+
<!-- ❌ 禁止:巨型单文件组件 (>300 行) -->
|
|
177
|
+
<!-- 应拆分为子组件或提取 Composable -->
|
|
178
|
+
```
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# 前端分区 — 编码惯例 (Vue3 增强)
|
|
2
|
+
|
|
3
|
+
> **来源**: `files/frontend-project-conventions/SKILL.md` (v1.0.0)
|
|
4
|
+
> **归档日期**: 2026-05-21
|
|
5
|
+
> **技术栈**: Vue 3 + TypeScript + Vite + Pinia + Vue Router + Element Plus
|
|
6
|
+
|
|
7
|
+
## 技术栈版本速查
|
|
8
|
+
|
|
9
|
+
| 组件 | 推荐版本 | 说明 |
|
|
10
|
+
|------|---------|------|
|
|
11
|
+
| Vue | ^3.4.x | Composition API 正式稳定版 |
|
|
12
|
+
| TypeScript | ^5.3.x | strict mode 强制 |
|
|
13
|
+
| Vite | ^5.x | 构建工具 |
|
|
14
|
+
| Pinia | ^2.x | 官方状态管理 |
|
|
15
|
+
| Vue Router | ^4.x | 路由管理 |
|
|
16
|
+
| Element Plus | ^2.x | UI 组件库 |
|
|
17
|
+
| Axios | ^1.x | HTTP 客户端 |
|
|
18
|
+
| Vitest | ^1.x | 测试框架 |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 文件命名
|
|
23
|
+
|
|
24
|
+
| 类型 | 规范 | 示例 |
|
|
25
|
+
|------|------|------|
|
|
26
|
+
| Vue 单文件组件 | PascalCase | `UserProfile.vue` |
|
|
27
|
+
| Hook / 组合式函数 | camelCase + `use` 前缀 | `useUserProfile.ts` |
|
|
28
|
+
| 工具函数 | camelCase | `formatDate.ts` |
|
|
29
|
+
| 全局类型定义 | camelCase + types | `user.types.ts` |
|
|
30
|
+
| 常量/枚举 | UPPER_SNAKE_CASE | `API_ENDPOINTS.ts` |
|
|
31
|
+
| 样式文件 | 与组件同名 | `UserProfile.styles.ts` |
|
|
32
|
+
| 测试文件 | 组件名 + `.test/.spec` | `UserProfile.test.tsx` |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 标准目录结构详解
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
src/
|
|
40
|
+
├── api/ # API 接口封装层
|
|
41
|
+
│ ├── client.ts # Axios 实例创建、拦截器配置
|
|
42
|
+
│ ├── modules/ # 按业务模块拆分的 API 调用
|
|
43
|
+
│ │ └── user.ts # 用户相关 API(list/create/update/delete)
|
|
44
|
+
│ └── types/ # API 请求和响应的 TS 类型定义
|
|
45
|
+
│ └── user.d.ts # UserAPI 的 ReqVO/RespVO 类型
|
|
46
|
+
│
|
|
47
|
+
├── assets/ # 静态资源
|
|
48
|
+
│ ├── styles/ # 全局样式(variables.css / reset.css / global.css)
|
|
49
|
+
│ └── images/ # 图片资源(logo / icons / illustrations)
|
|
50
|
+
│
|
|
51
|
+
├── components/ # 可复用组件
|
|
52
|
+
│ ├── ui/ # 通用 UI 组件(无业务逻辑)
|
|
53
|
+
│ │ ├── Button/
|
|
54
|
+
│ │ │ ├── Button.vue
|
|
55
|
+
│ │ │ ├── Button.test.ts
|
|
56
|
+
│ │ │ └── index.ts
|
|
57
|
+
│ │ └── Modal/
|
|
58
|
+
│ └── business/ # 业务组件(含业务逻辑)
|
|
59
|
+
│ └── UserProfileCard/
|
|
60
|
+
│
|
|
61
|
+
├── pages/ # 页面级组件(路由对应)
|
|
62
|
+
│ ├── user/
|
|
63
|
+
│ │ ├── UserList.vue # 列表页
|
|
64
|
+
│ │ ├── UserDetail.vue # 详情页
|
|
65
|
+
│ │ └── UserCreate.vue # 创建/编辑页
|
|
66
|
+
│ └── dashboard/
|
|
67
|
+
│
|
|
68
|
+
├── router/ # 路由配置
|
|
69
|
+
│ ├── index.ts # 路由实例 + 首屏路由
|
|
70
|
+
│ ├── routes/ # 按模块拆分的路由定义
|
|
71
|
+
│ │ ├── user.ts
|
|
72
|
+
│ │ └── dashboard.ts
|
|
73
|
+
│ └── guards/ # 导航守卫(权限校验 / 登录拦截)
|
|
74
|
+
│ └── auth.ts
|
|
75
|
+
│
|
|
76
|
+
├── store/ # Pinia Store
|
|
77
|
+
│ ├── index.ts # Store 入口
|
|
78
|
+
│ └── modules/ # 按业务模块拆分
|
|
79
|
+
│ ├── user.ts # 用户状态(登录信息 / 权限列表)
|
|
80
|
+
│ └── app.ts # 应用全局状态(侧边栏 / 主题)
|
|
81
|
+
│
|
|
82
|
+
├── types/ # 全局共享类型定义
|
|
83
|
+
│ ├── user.d.ts # 用户相关类型接口
|
|
84
|
+
│ ├── api.d.ts # 通用 API 类型(PaginatedResult / CommonResult)
|
|
85
|
+
│ └── env.d.ts # 环境变量类型声明
|
|
86
|
+
│
|
|
87
|
+
├── utils/ # 工具函数
|
|
88
|
+
│ ├── format.ts # 格式化(日期/金额/手机号脱敏)
|
|
89
|
+
│ ├── request.ts # 封装请求方法(get/post/put/delete)
|
|
90
|
+
│ └── storage.ts # localStorage 封装
|
|
91
|
+
│
|
|
92
|
+
├── composables/ # Vue3 组合式函数(可复用逻辑)
|
|
93
|
+
│ ├── useAuth.ts # 认证状态管理
|
|
94
|
+
│ ├── usePagination.ts # 分页逻辑封装
|
|
95
|
+
│ └── usePermission.ts # 权限判断
|
|
96
|
+
│
|
|
97
|
+
├── layouts/ # 布局组件
|
|
98
|
+
│ ├── DefaultLayout.vue # 默认布局(Header + Sidebar + Content)
|
|
99
|
+
│ └── BlankLayout.vue # 空白布局(登录页等)
|
|
100
|
+
│
|
|
101
|
+
└── App.vue # 根组件
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 标准组件模板
|
|
107
|
+
|
|
108
|
+
```vue
|
|
109
|
+
<!--
|
|
110
|
+
* ComponentName.vue — {功能描述}
|
|
111
|
+
*
|
|
112
|
+
* @description {详细说明组件的功能和使用场景}
|
|
113
|
+
* @props {Type} propName - prop 说明
|
|
114
|
+
* @emits eventName - 触发时机和参数
|
|
115
|
+
* @author frontend-team
|
|
116
|
+
-->
|
|
117
|
+
<script setup lang="ts">
|
|
118
|
+
/**
|
|
119
|
+
* 外部依赖
|
|
120
|
+
*/
|
|
121
|
+
import { ref, computed, onMounted } from 'vue';
|
|
122
|
+
import { useRouter } from 'vue-router';
|
|
123
|
+
import { ElMessage } from 'element-plus';
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 类型定义
|
|
127
|
+
*/
|
|
128
|
+
interface Props {
|
|
129
|
+
id: string;
|
|
130
|
+
title: string;
|
|
131
|
+
loading?: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface Emits {
|
|
135
|
+
(e: 'update', value: string): void;
|
|
136
|
+
(e: 'delete', id: string): void;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Props & Emits
|
|
141
|
+
*/
|
|
142
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
143
|
+
loading: false,
|
|
144
|
+
});
|
|
145
|
+
const emit = defineEmits<Emits>();
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 响应式状态
|
|
149
|
+
*/
|
|
150
|
+
const isLoading = ref(false);
|
|
151
|
+
const dataList = ref<Item[]>([]);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 计算属性
|
|
155
|
+
*/
|
|
156
|
+
const hasData = computed(() => dataList.value.length > 0);
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 方法
|
|
160
|
+
*/
|
|
161
|
+
const handleFetch = async () => {
|
|
162
|
+
isLoading.value = true;
|
|
163
|
+
try {
|
|
164
|
+
// 业务逻辑
|
|
165
|
+
} catch (error) {
|
|
166
|
+
ElMessage.error('操作失败');
|
|
167
|
+
} finally {
|
|
168
|
+
isLoading.value = false;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const handleClick = () => {
|
|
173
|
+
emit('update', props.id);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 生命周期
|
|
178
|
+
*/
|
|
179
|
+
onMounted(() => {
|
|
180
|
+
handleFetch();
|
|
181
|
+
});
|
|
182
|
+
</script>
|
|
183
|
+
|
|
184
|
+
<template>
|
|
185
|
+
<div class="component-name">
|
|
186
|
+
<div v-loading="isLoading">
|
|
187
|
+
<!-- 内容区域 -->
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</template>
|
|
191
|
+
|
|
192
|
+
<style scoped lang="scss">
|
|
193
|
+
.component-name {
|
|
194
|
+
/* 样式 */
|
|
195
|
+
}
|
|
196
|
+
</style>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## API 封装模式
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// api/client.ts — Axios 实例配置
|
|
205
|
+
import axios from 'axios';
|
|
206
|
+
import { ElMessage } from 'element-plus';
|
|
207
|
+
import type { AxiosRequestConfig } from 'axios';
|
|
208
|
+
|
|
209
|
+
// 创建实例
|
|
210
|
+
const client = axios.create({
|
|
211
|
+
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
212
|
+
timeout: 15000,
|
|
213
|
+
headers: { 'Content-Type': 'application/json' },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// 请求拦截器:自动附加 Token
|
|
217
|
+
client.interceptors.request.use((config) => {
|
|
218
|
+
const token = localStorage.getItem('token');
|
|
219
|
+
if (token && config.headers) {
|
|
220
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
221
|
+
}
|
|
222
|
+
return config;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// 响应拦截器:统一错误处理
|
|
226
|
+
client.interceptors.response.use(
|
|
227
|
+
(response) => response.data,
|
|
228
|
+
(error) => {
|
|
229
|
+
const message = error.response?.data?.message || '网络异常';
|
|
230
|
+
ElMessage.error(message);
|
|
231
|
+
return Promise.reject(error);
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
export default client;
|
|
236
|
+
|
|
237
|
+
// api/modules/user.ts — 具体模块 API
|
|
238
|
+
import client from '../client';
|
|
239
|
+
import type { UserPageReqVO, UserRespVO, PageResult } from './types/user';
|
|
240
|
+
|
|
241
|
+
export const UserAPI = {
|
|
242
|
+
/** 分页查询用户 */
|
|
243
|
+
page(params: UserPageReqVO) {
|
|
244
|
+
return client.post<CommonResult<PageResult<UserRespVO>>>('/user/page', params);
|
|
245
|
+
},
|
|
246
|
+
/** 创建用户 */
|
|
247
|
+
create(data: UserCreateReqVO) {
|
|
248
|
+
return client.post<CommonResult<Long>>('/user/create', data);
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Router 配置约定
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// router/routes/user.ts — 懒加载路由
|
|
259
|
+
import type { RouteRecordRaw } from 'vue-router';
|
|
260
|
+
|
|
261
|
+
const userRoutes: RouteRecordRaw[] = [
|
|
262
|
+
{
|
|
263
|
+
path: '/user',
|
|
264
|
+
component: () => import('@/layouts/DefaultLayout.vue'),
|
|
265
|
+
meta: { title: '用户管理', requiresAuth: true },
|
|
266
|
+
children: [
|
|
267
|
+
{
|
|
268
|
+
path: '',
|
|
269
|
+
name: 'UserList',
|
|
270
|
+
component: () => import('@/pages/user/UserList.vue'),
|
|
271
|
+
meta: { title: '用户列表' },
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
path: ':id',
|
|
275
|
+
name: 'UserDetail',
|
|
276
|
+
component: () => import('@/pages/user/UserDetail.vue'),
|
|
277
|
+
meta: { title: '用户详情' },
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
export default userRoutes;
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**路由规范**:
|
|
287
|
+
- 所有页面组件必须 **懒加载** (`() => import(...)`)
|
|
288
|
+
- 路由元数据 `meta` 包含 `title` 和 `requiresAuth`
|
|
289
|
+
- 权限控制通过路由守卫统一处理
|
|
290
|
+
- 菜单结构与路由结构保持一致
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Store 组织方式
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// store/modules/user.ts — 标准 Store 结构
|
|
298
|
+
import { defineStore } from 'pinia';
|
|
299
|
+
import type { UserInfo } from '@/types/user';
|
|
300
|
+
|
|
301
|
+
interface UserState {
|
|
302
|
+
userInfo: UserInfo | null;
|
|
303
|
+
token: string;
|
|
304
|
+
permissions: string[];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export const useUserStore = defineStore('user', {
|
|
308
|
+
state: (): UserState => ({
|
|
309
|
+
userInfo: null,
|
|
310
|
+
token: localStorage.getItem('token') || '',
|
|
311
|
+
permissions: [],
|
|
312
|
+
}),
|
|
313
|
+
|
|
314
|
+
getters: {
|
|
315
|
+
isLoggedIn: (state) => !!state.token,
|
|
316
|
+
isAdmin: (state) => state.permissions.includes('admin'),
|
|
317
|
+
displayName: (state) => state.userInfo?.name || '未登录',
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
actions: {
|
|
321
|
+
async login(credentials: LoginCredentials) {
|
|
322
|
+
// ...
|
|
323
|
+
},
|
|
324
|
+
logout() {
|
|
325
|
+
this.$reset();
|
|
326
|
+
localStorage.removeItem('token');
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Store 规范**:
|
|
333
|
+
- State 字段必须有明确 TS 类型
|
|
334
|
+
- Getters 用于派生计算数据
|
|
335
|
+
- Actions 用于异步操作或批量修改
|
|
336
|
+
- 禁止在组件外直接修改 Store state
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Import 排序
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
// 1. Vue 相关
|
|
344
|
+
import { ref, computed, onMounted } from 'vue';
|
|
345
|
+
import { useRouter } from 'vue-router';
|
|
346
|
+
|
|
347
|
+
// 2. UI 库
|
|
348
|
+
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
349
|
+
|
|
350
|
+
// 3. 外部库(按字母序)
|
|
351
|
+
import axios from 'axios';
|
|
352
|
+
|
|
353
|
+
// 4. 内部共享模块(@/ 别名)
|
|
354
|
+
import { useUserStore } from '@/store/modules/user';
|
|
355
|
+
import { UserAPI } from '@/api/modules/user';
|
|
356
|
+
import type { User, UserRole } from '@/types/user';
|
|
357
|
+
|
|
358
|
+
// 5. 相对路径导入
|
|
359
|
+
import { Avatar } from './Avatar';
|
|
360
|
+
import type { UserProfileProps } from './types';
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## 组件编写惯例
|
|
366
|
+
|
|
367
|
+
### 函数组件 + 解构 Props
|
|
368
|
+
(见上方「标准组件模板」)
|
|
369
|
+
|
|
370
|
+
### 条件渲染
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
// ✅ 推荐:提前返回
|
|
374
|
+
function UserDetail({ user }: { user: User | null }) {
|
|
375
|
+
if (!user) return <EmptyState />;
|
|
376
|
+
if (user.isBanned) return <BannedNotice />;
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<div>
|
|
380
|
+
<h1>{user.name}</h1>
|
|
381
|
+
{/* 主要内容 */}
|
|
382
|
+
</div>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### 列表渲染
|
|
388
|
+
|
|
389
|
+
```tsx
|
|
390
|
+
// ✅ 推荐:带 key + 空状态处理
|
|
391
|
+
function UserList({ users }: { users: User[] }) {
|
|
392
|
+
if (users.length === 0) {
|
|
393
|
+
return <EmptyState title="暂无用户" description="点击上方按钮添加" />;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<ul>
|
|
398
|
+
{users.map(user => (
|
|
399
|
+
<li key={user.id}>
|
|
400
|
+
<UserCard user={user} />
|
|
401
|
+
</li>
|
|
402
|
+
))}
|
|
403
|
+
</ul>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 注释与文档规范
|
|
411
|
+
|
|
412
|
+
- **组件级注释**: 文件顶部 JSDoc,含名称、描述、props、events
|
|
413
|
+
- **函数级注释**: 复杂逻辑函数前加 JSDoc(@param / @returns / @throws)
|
|
414
|
+
- **行内注释**: 关键步骤解释"为什么",关注 why 而非 what
|
|
415
|
+
- **TODO 格式**: `// TODO: [场景] - 待[动作]`
|
|
416
|
+
- **禁止注释与代码不一致**
|