vue2server7 7.0.114 → 7.0.115
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/package.json +1 -1
- package/1.js +0 -218
- package/2 +0 -6
- package/README.md +0 -13
- package/test/Vue3/351/241/271/347/233/256/345/274/200/345/217/221/350/247/204/350/214/203.md +0 -871
package/package.json
CHANGED
package/1.js
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import cors from "cors";
|
|
3
|
-
import axios from "axios";
|
|
4
|
-
import dotenv from "dotenv";
|
|
5
|
-
|
|
6
|
-
dotenv.config();
|
|
7
|
-
|
|
8
|
-
const app = express();
|
|
9
|
-
|
|
10
|
-
app.use(cors());
|
|
11
|
-
app.use(express.json({ limit: "20mb" }));
|
|
12
|
-
|
|
13
|
-
const PORT = Number(process.env.PORT || 3000);
|
|
14
|
-
const TARGET_URL = process.env.TARGET_URL;
|
|
15
|
-
const TARGET_API_KEY = process.env.TARGET_API_KEY;
|
|
16
|
-
const TARGET_APP_TAG = process.env.TARGET_APP_TAG || "proxyai";
|
|
17
|
-
const TARGET_MODEL = process.env.TARGET_MODEL || "qwen15-32b";
|
|
18
|
-
const TARGET_TEMPERATURE = Number(process.env.TARGET_TEMPERATURE || 0.1);
|
|
19
|
-
const TARGET_MAX_TOKENS = Number(process.env.TARGET_MAX_TOKENS || 20000);
|
|
20
|
-
|
|
21
|
-
function normalizeMessages(messages) {
|
|
22
|
-
if (!Array.isArray(messages)) return [];
|
|
23
|
-
|
|
24
|
-
return messages.map((item) => {
|
|
25
|
-
// Continue / OpenAI 标准格式直接透传
|
|
26
|
-
if (typeof item?.content === "string") {
|
|
27
|
-
return {
|
|
28
|
-
role: item.role || "user",
|
|
29
|
-
content: item.content
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 兼容 content 为数组的情况
|
|
34
|
-
if (Array.isArray(item?.content)) {
|
|
35
|
-
const text = item.content
|
|
36
|
-
.map((part) => {
|
|
37
|
-
if (typeof part === "string") return part;
|
|
38
|
-
if (part?.type === "text") return part.text || "";
|
|
39
|
-
return "";
|
|
40
|
-
})
|
|
41
|
-
.join("");
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
role: item.role || "user",
|
|
45
|
-
content: text
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
role: item?.role || "user",
|
|
51
|
-
content: ""
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function buildOpenAIResponse({ model, content, usage }) {
|
|
57
|
-
return {
|
|
58
|
-
id: `chatcmpl-${Date.now()}`,
|
|
59
|
-
object: "chat.completion",
|
|
60
|
-
created: Math.floor(Date.now() / 1000),
|
|
61
|
-
model,
|
|
62
|
-
choices: [
|
|
63
|
-
{
|
|
64
|
-
index: 0,
|
|
65
|
-
message: {
|
|
66
|
-
role: "assistant",
|
|
67
|
-
content: content ?? ""
|
|
68
|
-
},
|
|
69
|
-
finish_reason: "stop"
|
|
70
|
-
}
|
|
71
|
-
],
|
|
72
|
-
usage: usage || {
|
|
73
|
-
prompt_tokens: 0,
|
|
74
|
-
completion_tokens: 0,
|
|
75
|
-
total_tokens: 0
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
app.get("/health", (req, res) => {
|
|
81
|
-
res.json({ ok: true });
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
app.get("/v1/models", (req, res) => {
|
|
85
|
-
res.json({
|
|
86
|
-
object: "list",
|
|
87
|
-
data: [
|
|
88
|
-
{
|
|
89
|
-
id: TARGET_MODEL,
|
|
90
|
-
object: "model",
|
|
91
|
-
created: Math.floor(Date.now() / 1000),
|
|
92
|
-
owned_by: "continue-proxy"
|
|
93
|
-
}
|
|
94
|
-
]
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
app.post("/v1/chat/completions", async (req, res) => {
|
|
99
|
-
const requestId = `req_${Date.now()}`;
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const incoming = req.body || {};
|
|
103
|
-
const incomingMessages = normalizeMessages(incoming.messages);
|
|
104
|
-
|
|
105
|
-
// 严格参考 ProxyAI 配置来组织上游 body
|
|
106
|
-
const upstreamBody = {
|
|
107
|
-
stream: typeof incoming.stream === "boolean" ? incoming.stream : true,
|
|
108
|
-
model: incoming.model || TARGET_MODEL,
|
|
109
|
-
messages: incomingMessages,
|
|
110
|
-
temperature:
|
|
111
|
-
typeof incoming.temperature === "number"
|
|
112
|
-
? incoming.temperature
|
|
113
|
-
: TARGET_TEMPERATURE,
|
|
114
|
-
max_tokens:
|
|
115
|
-
typeof incoming.max_tokens === "number"
|
|
116
|
-
? incoming.max_tokens
|
|
117
|
-
: typeof incoming.max_completion_tokens === "number"
|
|
118
|
-
? incoming.max_completion_tokens
|
|
119
|
-
: TARGET_MAX_TOKENS
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const upstreamHeaders = {
|
|
123
|
-
Authorization: `Bearer ${TARGET_API_KEY}`,
|
|
124
|
-
"X-LLM-Application-Tag": TARGET_APP_TAG,
|
|
125
|
-
"Content-Type": "application/json"
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
console.log(`\n[${requestId}] ===== incoming body =====`);
|
|
129
|
-
console.log(JSON.stringify(incoming, null, 2));
|
|
130
|
-
|
|
131
|
-
console.log(`\n[${requestId}] ===== upstream headers =====`);
|
|
132
|
-
console.log({
|
|
133
|
-
Authorization: "Bearer ***",
|
|
134
|
-
"X-LLM-Application-Tag": TARGET_APP_TAG,
|
|
135
|
-
"Content-Type": "application/json"
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
console.log(`\n[${requestId}] ===== upstream body =====`);
|
|
139
|
-
console.log(JSON.stringify(upstreamBody, null, 2));
|
|
140
|
-
|
|
141
|
-
// 先按非流式转,最稳
|
|
142
|
-
// 因为很多内网服务 stream=true 时返回格式不一定被 Continue 正确识别
|
|
143
|
-
const forceNonStream = true;
|
|
144
|
-
if (forceNonStream) {
|
|
145
|
-
upstreamBody.stream = false;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const upstreamResp = await axios.post(TARGET_URL, upstreamBody, {
|
|
149
|
-
headers: upstreamHeaders,
|
|
150
|
-
timeout: 120000,
|
|
151
|
-
validateStatus: () => true
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
console.log(`\n[${requestId}] ===== upstream status =====`);
|
|
155
|
-
console.log(upstreamResp.status);
|
|
156
|
-
|
|
157
|
-
console.log(`\n[${requestId}] ===== upstream data =====`);
|
|
158
|
-
console.log(
|
|
159
|
-
typeof upstreamResp.data === "string"
|
|
160
|
-
? upstreamResp.data
|
|
161
|
-
: JSON.stringify(upstreamResp.data, null, 2)
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
if (upstreamResp.status >= 400) {
|
|
165
|
-
return res.status(upstreamResp.status).json({
|
|
166
|
-
error: {
|
|
167
|
-
message:
|
|
168
|
-
typeof upstreamResp.data === "string"
|
|
169
|
-
? upstreamResp.data
|
|
170
|
-
: JSON.stringify(upstreamResp.data),
|
|
171
|
-
type: "upstream_error",
|
|
172
|
-
code: upstreamResp.status
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const data = upstreamResp.data;
|
|
178
|
-
|
|
179
|
-
// 情况1:上游已经是 OpenAI 格式
|
|
180
|
-
if (data?.choices?.[0]?.message?.content !== undefined) {
|
|
181
|
-
return res.json(data);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 情况2:提取常见文本字段
|
|
185
|
-
const text =
|
|
186
|
-
data?.choices?.[0]?.text ??
|
|
187
|
-
data?.data?.content ??
|
|
188
|
-
data?.data?.text ??
|
|
189
|
-
data?.text ??
|
|
190
|
-
data?.reply ??
|
|
191
|
-
data?.result ??
|
|
192
|
-
"";
|
|
193
|
-
|
|
194
|
-
return res.json(
|
|
195
|
-
buildOpenAIResponse({
|
|
196
|
-
model: upstreamBody.model,
|
|
197
|
-
content: text,
|
|
198
|
-
usage: data?.usage
|
|
199
|
-
})
|
|
200
|
-
);
|
|
201
|
-
} catch (error) {
|
|
202
|
-
console.error("\n===== proxy exception =====");
|
|
203
|
-
console.error(error?.message);
|
|
204
|
-
console.error(error?.response?.data);
|
|
205
|
-
|
|
206
|
-
return res.status(500).json({
|
|
207
|
-
error: {
|
|
208
|
-
message: error?.message || "proxy internal error",
|
|
209
|
-
type: "proxy_internal_error",
|
|
210
|
-
code: 500
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
app.listen(PORT, () => {
|
|
217
|
-
console.log(`Proxy server running at http://127.0.0.1:${PORT}`);
|
|
218
|
-
});
|
package/2
DELETED
package/README.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
There are a few existing modules similar to Keyv, however Keyv is different because it:
|
|
2
|
-
|
|
3
|
-
Isn't bloated
|
|
4
|
-
Has a simple Promise based API
|
|
5
|
-
Suitable as a TTL based cache or persistent key-value store
|
|
6
|
-
Easily embeddable inside another module
|
|
7
|
-
Works with any storage that implements the Map API
|
|
8
|
-
Handles all JSON types plus Buffer
|
|
9
|
-
Supports namespaces
|
|
10
|
-
Wide range of efficient, well tested storage adapters
|
|
11
|
-
Connection errors are passed through (db failures won't kill your app)
|
|
12
|
-
Supports the current active LTS version of Node.js or high
|
|
13
|
-
|
package/test/Vue3/351/241/271/347/233/256/345/274/200/345/217/221/350/247/204/350/214/203.md
DELETED
|
@@ -1,871 +0,0 @@
|
|
|
1
|
-
# Vue3 项目开发规范
|
|
2
|
-
|
|
3
|
-
## 1. 文档说明
|
|
4
|
-
|
|
5
|
-
本文档用于规范 Vue3 项目的目录结构、代码风格、组件开发、路由管理、状态管理、接口请求、样式编写、Git 提交以及团队协作流程。
|
|
6
|
-
|
|
7
|
-
适用技术栈:
|
|
8
|
-
|
|
9
|
-
- Vue 3
|
|
10
|
-
- Vite
|
|
11
|
-
- TypeScript
|
|
12
|
-
- Vue Router
|
|
13
|
-
- Pinia
|
|
14
|
-
- Axios
|
|
15
|
-
- Element Plus / TDesign Vue Next
|
|
16
|
-
- ESLint
|
|
17
|
-
- Prettier
|
|
18
|
-
- Stylelint
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 2. 项目目录规范
|
|
23
|
-
|
|
24
|
-
推荐目录结构:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
src
|
|
28
|
-
├── api # 接口请求模块
|
|
29
|
-
├── assets # 静态资源
|
|
30
|
-
│ ├── images
|
|
31
|
-
│ ├── icons
|
|
32
|
-
│ └── fonts
|
|
33
|
-
├── components # 公共组件
|
|
34
|
-
├── composables # 组合式函数
|
|
35
|
-
├── constants # 常量配置
|
|
36
|
-
├── directives # 自定义指令
|
|
37
|
-
├── hooks # 业务 hooks
|
|
38
|
-
├── layouts # 页面布局
|
|
39
|
-
├── plugins # 插件注册
|
|
40
|
-
├── router # 路由配置
|
|
41
|
-
│ ├── index.ts
|
|
42
|
-
│ └── modules
|
|
43
|
-
├── stores # Pinia 状态管理
|
|
44
|
-
├── styles # 全局样式
|
|
45
|
-
├── types # TypeScript 类型声明
|
|
46
|
-
├── utils # 工具函数
|
|
47
|
-
├── views # 页面模块
|
|
48
|
-
├── App.vue
|
|
49
|
-
└── main.ts
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## 3. 文件命名规范
|
|
55
|
-
|
|
56
|
-
### 3.1 Vue 组件命名
|
|
57
|
-
|
|
58
|
-
组件文件使用大驼峰命名:
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
UserCard.vue
|
|
62
|
-
UserForm.vue
|
|
63
|
-
BaseTable.vue
|
|
64
|
-
SearchPanel.vue
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 3.2 页面文件命名
|
|
68
|
-
|
|
69
|
-
页面文件建议使用大驼峰命名:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
views/user/UserList.vue
|
|
73
|
-
views/user/UserDetail.vue
|
|
74
|
-
views/system/RoleManage.vue
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### 3.3 工具文件命名
|
|
78
|
-
|
|
79
|
-
工具文件使用小驼峰命名:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
formatDate.ts
|
|
83
|
-
storage.ts
|
|
84
|
-
request.ts
|
|
85
|
-
validate.ts
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### 3.4 类型文件命名
|
|
89
|
-
|
|
90
|
-
类型文件使用 `.type.ts` 后缀:
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
user.type.ts
|
|
94
|
-
order.type.ts
|
|
95
|
-
api.type.ts
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### 3.5 路由文件命名
|
|
99
|
-
|
|
100
|
-
路由文件使用 `.route.ts` 后缀:
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
user.route.ts
|
|
104
|
-
order.route.ts
|
|
105
|
-
system.route.ts
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## 4. 代码风格规范
|
|
111
|
-
|
|
112
|
-
### 4.1 统一使用 `<script setup>`
|
|
113
|
-
|
|
114
|
-
```vue
|
|
115
|
-
<script setup lang="ts">
|
|
116
|
-
import { ref } from 'vue'
|
|
117
|
-
|
|
118
|
-
const count = ref(0)
|
|
119
|
-
|
|
120
|
-
function handleAdd() {
|
|
121
|
-
count.value++
|
|
122
|
-
}
|
|
123
|
-
</script>
|
|
124
|
-
|
|
125
|
-
<template>
|
|
126
|
-
<button @click="handleAdd">
|
|
127
|
-
{{ count }}
|
|
128
|
-
</button>
|
|
129
|
-
</template>
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### 4.2 使用 TypeScript
|
|
133
|
-
|
|
134
|
-
所有 Vue 文件推荐使用:
|
|
135
|
-
|
|
136
|
-
```vue
|
|
137
|
-
<script setup lang="ts">
|
|
138
|
-
</script>
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### 4.3 禁止滥用 any
|
|
142
|
-
|
|
143
|
-
不推荐:
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
const user: any = {}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
推荐:
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
interface UserInfo {
|
|
153
|
-
id: number
|
|
154
|
-
username: string
|
|
155
|
-
avatar?: string
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const user = ref<UserInfo | null>(null)
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## 5. 组件开发规范
|
|
164
|
-
|
|
165
|
-
### 5.1 组件职责单一
|
|
166
|
-
|
|
167
|
-
一个组件只负责一个明确的功能,例如:
|
|
168
|
-
|
|
169
|
-
- 用户表格组件只负责展示用户列表
|
|
170
|
-
- 搜索组件只负责搜索条件输入
|
|
171
|
-
- 弹窗组件只负责表单录入和提交
|
|
172
|
-
|
|
173
|
-
### 5.2 Props 定义规范
|
|
174
|
-
|
|
175
|
-
必须定义 Props 类型。
|
|
176
|
-
|
|
177
|
-
```ts
|
|
178
|
-
interface Props {
|
|
179
|
-
title: string
|
|
180
|
-
loading?: boolean
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
184
|
-
loading: false
|
|
185
|
-
})
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
禁止使用:
|
|
189
|
-
|
|
190
|
-
```ts
|
|
191
|
-
defineProps(['title', 'loading'])
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### 5.3 Emits 定义规范
|
|
195
|
-
|
|
196
|
-
事件名建议使用 kebab-case。
|
|
197
|
-
|
|
198
|
-
```ts
|
|
199
|
-
const emit = defineEmits<{
|
|
200
|
-
'submit-form': [value: FormData]
|
|
201
|
-
'update:modelValue': [value: string]
|
|
202
|
-
}>()
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### 5.4 不直接修改 Props
|
|
206
|
-
|
|
207
|
-
错误写法:
|
|
208
|
-
|
|
209
|
-
```ts
|
|
210
|
-
props.visible = false
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
正确写法:
|
|
214
|
-
|
|
215
|
-
```ts
|
|
216
|
-
const emit = defineEmits<{
|
|
217
|
-
'update:visible': [value: boolean]
|
|
218
|
-
}>()
|
|
219
|
-
|
|
220
|
-
emit('update:visible', false)
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## 6. 页面开发规范
|
|
226
|
-
|
|
227
|
-
页面建议按以下顺序组织:
|
|
228
|
-
|
|
229
|
-
1. import 引入
|
|
230
|
-
2. 类型定义
|
|
231
|
-
3. 响应式数据
|
|
232
|
-
4. 计算属性
|
|
233
|
-
5. 方法函数
|
|
234
|
-
6. 生命周期
|
|
235
|
-
7. 监听器
|
|
236
|
-
|
|
237
|
-
示例:
|
|
238
|
-
|
|
239
|
-
```vue
|
|
240
|
-
<script setup lang="ts">
|
|
241
|
-
import { onMounted, ref } from 'vue'
|
|
242
|
-
import { getUserListApi } from '@/api/user.api'
|
|
243
|
-
import type { UserInfo } from '@/types/user.type'
|
|
244
|
-
|
|
245
|
-
const loading = ref(false)
|
|
246
|
-
const userList = ref<UserInfo[]>([])
|
|
247
|
-
|
|
248
|
-
async function getUserList() {
|
|
249
|
-
loading.value = true
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
const res = await getUserListApi()
|
|
253
|
-
userList.value = res.data
|
|
254
|
-
} finally {
|
|
255
|
-
loading.value = false
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
onMounted(() => {
|
|
260
|
-
getUserList()
|
|
261
|
-
})
|
|
262
|
-
</script>
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## 7. 路由规范
|
|
268
|
-
|
|
269
|
-
### 7.1 路由目录
|
|
270
|
-
|
|
271
|
-
```bash
|
|
272
|
-
router
|
|
273
|
-
├── index.ts
|
|
274
|
-
└── modules
|
|
275
|
-
├── user.route.ts
|
|
276
|
-
├── order.route.ts
|
|
277
|
-
└── system.route.ts
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
### 7.2 路由配置示例
|
|
281
|
-
|
|
282
|
-
```ts
|
|
283
|
-
import type { RouteRecordRaw } from 'vue-router'
|
|
284
|
-
|
|
285
|
-
const userRoutes: RouteRecordRaw[] = [
|
|
286
|
-
{
|
|
287
|
-
path: '/user',
|
|
288
|
-
name: 'User',
|
|
289
|
-
component: () => import('@/views/user/UserList.vue'),
|
|
290
|
-
meta: {
|
|
291
|
-
title: '用户管理',
|
|
292
|
-
requiresAuth: true,
|
|
293
|
-
permissions: ['user:list']
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
]
|
|
297
|
-
|
|
298
|
-
export default userRoutes
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### 7.3 路由 meta 规范
|
|
302
|
-
|
|
303
|
-
```ts
|
|
304
|
-
meta: {
|
|
305
|
-
title: '页面标题',
|
|
306
|
-
requiresAuth: true,
|
|
307
|
-
keepAlive: false,
|
|
308
|
-
permissions: ['user:list']
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
常用字段说明:
|
|
313
|
-
|
|
314
|
-
| 字段 | 说明 |
|
|
315
|
-
| --- | --- |
|
|
316
|
-
| title | 页面标题 |
|
|
317
|
-
| requiresAuth | 是否需要登录 |
|
|
318
|
-
| keepAlive | 是否缓存页面 |
|
|
319
|
-
| permissions | 页面权限标识 |
|
|
320
|
-
|
|
321
|
-
---
|
|
322
|
-
|
|
323
|
-
## 8. 状态管理规范
|
|
324
|
-
|
|
325
|
-
### 8.1 Store 文件命名
|
|
326
|
-
|
|
327
|
-
```bash
|
|
328
|
-
stores/user.store.ts
|
|
329
|
-
stores/app.store.ts
|
|
330
|
-
stores/permission.store.ts
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### 8.2 Pinia 示例
|
|
334
|
-
|
|
335
|
-
```ts
|
|
336
|
-
import { ref } from 'vue'
|
|
337
|
-
import { defineStore } from 'pinia'
|
|
338
|
-
|
|
339
|
-
export const useUserStore = defineStore('user', () => {
|
|
340
|
-
const token = ref('')
|
|
341
|
-
const username = ref('')
|
|
342
|
-
|
|
343
|
-
function setToken(value: string) {
|
|
344
|
-
token.value = value
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function clearUser() {
|
|
348
|
-
token.value = ''
|
|
349
|
-
username.value = ''
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
token,
|
|
354
|
-
username,
|
|
355
|
-
setToken,
|
|
356
|
-
clearUser
|
|
357
|
-
}
|
|
358
|
-
})
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
---
|
|
362
|
-
|
|
363
|
-
## 9. API 请求规范
|
|
364
|
-
|
|
365
|
-
### 9.1 API 目录结构
|
|
366
|
-
|
|
367
|
-
```bash
|
|
368
|
-
api
|
|
369
|
-
├── request.ts
|
|
370
|
-
├── user.api.ts
|
|
371
|
-
├── order.api.ts
|
|
372
|
-
└── system.api.ts
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### 9.2 接口命名规范
|
|
376
|
-
|
|
377
|
-
接口函数建议使用动词开头:
|
|
378
|
-
|
|
379
|
-
```ts
|
|
380
|
-
getUserList()
|
|
381
|
-
getUserDetail()
|
|
382
|
-
createUser()
|
|
383
|
-
updateUser()
|
|
384
|
-
deleteUser()
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
### 9.3 API 示例
|
|
388
|
-
|
|
389
|
-
```ts
|
|
390
|
-
import request from './request'
|
|
391
|
-
import type { CreateUserDTO, UserListParams } from '@/types/user.type'
|
|
392
|
-
|
|
393
|
-
export function getUserListApi(params: UserListParams) {
|
|
394
|
-
return request.get('/users', { params })
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export function createUserApi(data: CreateUserDTO) {
|
|
398
|
-
return request.post('/users', data)
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
export function updateUserApi(id: number, data: CreateUserDTO) {
|
|
402
|
-
return request.put(`/users/${id}`, data)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
export function deleteUserApi(id: number) {
|
|
406
|
-
return request.delete(`/users/${id}`)
|
|
407
|
-
}
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
---
|
|
411
|
-
|
|
412
|
-
## 10. Axios 封装规范
|
|
413
|
-
|
|
414
|
-
```ts
|
|
415
|
-
import axios from 'axios'
|
|
416
|
-
import type { AxiosError, AxiosResponse } from 'axios'
|
|
417
|
-
|
|
418
|
-
const request = axios.create({
|
|
419
|
-
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
420
|
-
timeout: 10000
|
|
421
|
-
})
|
|
422
|
-
|
|
423
|
-
request.interceptors.request.use(config => {
|
|
424
|
-
const token = localStorage.getItem('token')
|
|
425
|
-
|
|
426
|
-
if (token) {
|
|
427
|
-
config.headers.Authorization = `Bearer ${token}`
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return config
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
request.interceptors.response.use(
|
|
434
|
-
(response: AxiosResponse) => {
|
|
435
|
-
return response.data
|
|
436
|
-
},
|
|
437
|
-
(error: AxiosError) => {
|
|
438
|
-
return Promise.reject(error)
|
|
439
|
-
}
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
export default request
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
---
|
|
446
|
-
|
|
447
|
-
## 11. TypeScript 类型规范
|
|
448
|
-
|
|
449
|
-
### 11.1 通用响应类型
|
|
450
|
-
|
|
451
|
-
```ts
|
|
452
|
-
export interface ApiResponse<T = unknown> {
|
|
453
|
-
code: number
|
|
454
|
-
message: string
|
|
455
|
-
data: T
|
|
456
|
-
}
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### 11.2 分页类型
|
|
460
|
-
|
|
461
|
-
```ts
|
|
462
|
-
export interface PageParams {
|
|
463
|
-
page: number
|
|
464
|
-
pageSize: number
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export interface PageResult<T> {
|
|
468
|
-
list: T[]
|
|
469
|
-
total: number
|
|
470
|
-
page: number
|
|
471
|
-
pageSize: number
|
|
472
|
-
}
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### 11.3 用户类型示例
|
|
476
|
-
|
|
477
|
-
```ts
|
|
478
|
-
export interface UserInfo {
|
|
479
|
-
id: number
|
|
480
|
-
username: string
|
|
481
|
-
nickname: string
|
|
482
|
-
avatar?: string
|
|
483
|
-
status: number
|
|
484
|
-
createdAt: string
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
export interface UserListParams extends PageParams {
|
|
488
|
-
keyword?: string
|
|
489
|
-
status?: number
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
export interface CreateUserDTO {
|
|
493
|
-
username: string
|
|
494
|
-
password: string
|
|
495
|
-
nickname: string
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
---
|
|
500
|
-
|
|
501
|
-
## 12. 样式规范
|
|
502
|
-
|
|
503
|
-
### 12.1 样式目录
|
|
504
|
-
|
|
505
|
-
```bash
|
|
506
|
-
styles
|
|
507
|
-
├── index.scss
|
|
508
|
-
├── reset.scss
|
|
509
|
-
├── variables.scss
|
|
510
|
-
├── mixin.scss
|
|
511
|
-
└── transition.scss
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### 12.2 组件样式
|
|
515
|
-
|
|
516
|
-
组件样式默认使用 scoped。
|
|
517
|
-
|
|
518
|
-
```vue
|
|
519
|
-
<style scoped lang="scss">
|
|
520
|
-
.user-card {
|
|
521
|
-
padding: 16px;
|
|
522
|
-
border-radius: 8px;
|
|
523
|
-
background-color: #fff;
|
|
524
|
-
}
|
|
525
|
-
</style>
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
### 12.3 CSS 命名规范
|
|
529
|
-
|
|
530
|
-
推荐使用 BEM 命名。
|
|
531
|
-
|
|
532
|
-
```scss
|
|
533
|
-
.user-card {
|
|
534
|
-
&__header {
|
|
535
|
-
display: flex;
|
|
536
|
-
align-items: center;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
&__title {
|
|
540
|
-
font-size: 18px;
|
|
541
|
-
font-weight: 600;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
&--active {
|
|
545
|
-
border-color: #409eff;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
---
|
|
551
|
-
|
|
552
|
-
## 13. 环境变量规范
|
|
553
|
-
|
|
554
|
-
### 13.1 文件命名
|
|
555
|
-
|
|
556
|
-
```bash
|
|
557
|
-
.env.development
|
|
558
|
-
.env.test
|
|
559
|
-
.env.production
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
### 13.2 变量命名
|
|
563
|
-
|
|
564
|
-
Vite 项目环境变量必须使用 `VITE_` 前缀。
|
|
565
|
-
|
|
566
|
-
```env
|
|
567
|
-
VITE_APP_TITLE=Vue3后台管理系统
|
|
568
|
-
VITE_API_BASE_URL=https://api.example.com
|
|
569
|
-
VITE_UPLOAD_URL=https://upload.example.com
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
### 13.3 使用方式
|
|
573
|
-
|
|
574
|
-
```ts
|
|
575
|
-
const baseURL = import.meta.env.VITE_API_BASE_URL
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
---
|
|
579
|
-
|
|
580
|
-
## 14. 权限规范
|
|
581
|
-
|
|
582
|
-
权限建议分为:
|
|
583
|
-
|
|
584
|
-
- 登录权限
|
|
585
|
-
- 路由权限
|
|
586
|
-
- 菜单权限
|
|
587
|
-
- 按钮权限
|
|
588
|
-
- 接口权限
|
|
589
|
-
|
|
590
|
-
### 14.1 路由权限
|
|
591
|
-
|
|
592
|
-
```ts
|
|
593
|
-
meta: {
|
|
594
|
-
requiresAuth: true,
|
|
595
|
-
permissions: ['user:list']
|
|
596
|
-
}
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### 14.2 按钮权限
|
|
600
|
-
|
|
601
|
-
```vue
|
|
602
|
-
<el-button v-if="hasPermission('user:create')">
|
|
603
|
-
新增用户
|
|
604
|
-
</el-button>
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
---
|
|
608
|
-
|
|
609
|
-
## 15. 表单开发规范
|
|
610
|
-
|
|
611
|
-
### 15.1 表单数据
|
|
612
|
-
|
|
613
|
-
```ts
|
|
614
|
-
const formData = reactive({
|
|
615
|
-
username: '',
|
|
616
|
-
password: '',
|
|
617
|
-
status: 1
|
|
618
|
-
})
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
### 15.2 表单校验
|
|
622
|
-
|
|
623
|
-
```ts
|
|
624
|
-
const rules = {
|
|
625
|
-
username: [
|
|
626
|
-
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
|
627
|
-
],
|
|
628
|
-
password: [
|
|
629
|
-
{ required: true, message: '请输入密码', trigger: 'blur' }
|
|
630
|
-
]
|
|
631
|
-
}
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
### 15.3 提交规范
|
|
635
|
-
|
|
636
|
-
```ts
|
|
637
|
-
async function handleSubmit() {
|
|
638
|
-
try {
|
|
639
|
-
await formRef.value?.validate()
|
|
640
|
-
await createUserApi(formData)
|
|
641
|
-
} catch (error) {
|
|
642
|
-
console.error(error)
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
---
|
|
648
|
-
|
|
649
|
-
## 16. 列表页面规范
|
|
650
|
-
|
|
651
|
-
列表页面通常包含:
|
|
652
|
-
|
|
653
|
-
- 搜索区域
|
|
654
|
-
- 操作按钮区域
|
|
655
|
-
- 表格区域
|
|
656
|
-
- 分页区域
|
|
657
|
-
|
|
658
|
-
推荐状态命名:
|
|
659
|
-
|
|
660
|
-
```ts
|
|
661
|
-
const loading = ref(false)
|
|
662
|
-
const tableData = ref([])
|
|
663
|
-
const total = ref(0)
|
|
664
|
-
const queryParams = reactive({
|
|
665
|
-
page: 1,
|
|
666
|
-
pageSize: 10,
|
|
667
|
-
keyword: ''
|
|
668
|
-
})
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
---
|
|
672
|
-
|
|
673
|
-
## 17. 工具函数规范
|
|
674
|
-
|
|
675
|
-
工具函数必须保持职责单一。
|
|
676
|
-
|
|
677
|
-
```ts
|
|
678
|
-
export function formatDate(date: string | Date): string {
|
|
679
|
-
const d = new Date(date)
|
|
680
|
-
return d.toLocaleDateString()
|
|
681
|
-
}
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
不建议一个工具函数同时处理多个不相关功能。
|
|
685
|
-
|
|
686
|
-
---
|
|
687
|
-
|
|
688
|
-
## 18. 常量规范
|
|
689
|
-
|
|
690
|
-
常量统一放在 `constants` 目录。
|
|
691
|
-
|
|
692
|
-
```ts
|
|
693
|
-
export const USER_STATUS = {
|
|
694
|
-
ENABLED: 1,
|
|
695
|
-
DISABLED: 0
|
|
696
|
-
} as const
|
|
697
|
-
|
|
698
|
-
export const USER_STATUS_TEXT = {
|
|
699
|
-
[USER_STATUS.ENABLED]: '启用',
|
|
700
|
-
[USER_STATUS.DISABLED]: '禁用'
|
|
701
|
-
}
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
---
|
|
705
|
-
|
|
706
|
-
## 19. 代码提交规范
|
|
707
|
-
|
|
708
|
-
推荐使用 Conventional Commits。
|
|
709
|
-
|
|
710
|
-
```bash
|
|
711
|
-
feat: 新增用户管理页面
|
|
712
|
-
fix: 修复登录状态失效问题
|
|
713
|
-
docs: 更新项目开发文档
|
|
714
|
-
style: 调整页面样式
|
|
715
|
-
refactor: 重构接口请求封装
|
|
716
|
-
perf: 优化列表加载性能
|
|
717
|
-
test: 新增单元测试
|
|
718
|
-
chore: 修改构建配置
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
提交类型说明:
|
|
722
|
-
|
|
723
|
-
| 类型 | 说明 |
|
|
724
|
-
| --- | --- |
|
|
725
|
-
| feat | 新功能 |
|
|
726
|
-
| fix | 修复问题 |
|
|
727
|
-
| docs | 文档修改 |
|
|
728
|
-
| style | 样式或格式调整 |
|
|
729
|
-
| refactor | 代码重构 |
|
|
730
|
-
| perf | 性能优化 |
|
|
731
|
-
| test | 测试相关 |
|
|
732
|
-
| chore | 构建或工具配置 |
|
|
733
|
-
|
|
734
|
-
---
|
|
735
|
-
|
|
736
|
-
## 20. ESLint 与 Prettier 规范
|
|
737
|
-
|
|
738
|
-
### 20.1 Prettier 配置
|
|
739
|
-
|
|
740
|
-
```json
|
|
741
|
-
{
|
|
742
|
-
"semi": false,
|
|
743
|
-
"singleQuote": true,
|
|
744
|
-
"printWidth": 100,
|
|
745
|
-
"trailingComma": "none"
|
|
746
|
-
}
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
### 20.2 ESLint 规则示例
|
|
750
|
-
|
|
751
|
-
```js
|
|
752
|
-
export default [
|
|
753
|
-
{
|
|
754
|
-
rules: {
|
|
755
|
-
'vue/multi-word-component-names': 'off',
|
|
756
|
-
'no-console': 'warn',
|
|
757
|
-
'no-debugger': 'warn'
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
]
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
---
|
|
764
|
-
|
|
765
|
-
## 21. 性能优化规范
|
|
766
|
-
|
|
767
|
-
### 21.1 路由懒加载
|
|
768
|
-
|
|
769
|
-
```ts
|
|
770
|
-
component: () => import('@/views/user/UserList.vue')
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
### 21.2 组件按需加载
|
|
774
|
-
|
|
775
|
-
大型组件、弹窗、图表组件建议按需加载。
|
|
776
|
-
|
|
777
|
-
### 21.3 避免不必要的响应式
|
|
778
|
-
|
|
779
|
-
大数据列表可以使用 `shallowRef`。
|
|
780
|
-
|
|
781
|
-
```ts
|
|
782
|
-
const list = shallowRef([])
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
### 21.4 图片优化
|
|
786
|
-
|
|
787
|
-
- 图片压缩后再上传
|
|
788
|
-
- 小图标优先使用 SVG
|
|
789
|
-
- 大图使用懒加载
|
|
790
|
-
- 静态资源放入 CDN
|
|
791
|
-
|
|
792
|
-
---
|
|
793
|
-
|
|
794
|
-
## 22. 安全规范
|
|
795
|
-
|
|
796
|
-
- 不在前端代码中写死密钥
|
|
797
|
-
- 不在仓库中提交 `.env.production`
|
|
798
|
-
- 接口请求必须携带必要鉴权信息
|
|
799
|
-
- 用户输入内容需要校验
|
|
800
|
-
- 富文本内容需要防止 XSS
|
|
801
|
-
- 文件上传需要限制类型和大小
|
|
802
|
-
|
|
803
|
-
---
|
|
804
|
-
|
|
805
|
-
## 23. 开发注意事项
|
|
806
|
-
|
|
807
|
-
- 组件不要过大,复杂页面要拆分组件
|
|
808
|
-
- 接口请求统一放到 `api` 目录
|
|
809
|
-
- 公共逻辑抽离到 `composables` 或 `hooks`
|
|
810
|
-
- 不要直接操作 DOM,优先使用 Vue 响应式能力
|
|
811
|
-
- 列表渲染必须添加唯一 `key`
|
|
812
|
-
- 异步请求必须处理 loading 状态
|
|
813
|
-
- 删除、提交、重置等危险操作需要二次确认
|
|
814
|
-
- 禁止在组件中硬编码接口地址
|
|
815
|
-
- 禁止将大量业务逻辑写在模板中
|
|
816
|
-
|
|
817
|
-
---
|
|
818
|
-
|
|
819
|
-
## 24. 推荐 package.json scripts
|
|
820
|
-
|
|
821
|
-
```json
|
|
822
|
-
{
|
|
823
|
-
"scripts": {
|
|
824
|
-
"dev": "vite",
|
|
825
|
-
"build": "vue-tsc -b && vite build",
|
|
826
|
-
"preview": "vite preview",
|
|
827
|
-
"lint": "eslint .",
|
|
828
|
-
"format": "prettier --write src"
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
```
|
|
832
|
-
|
|
833
|
-
---
|
|
834
|
-
|
|
835
|
-
## 25. 项目启动流程
|
|
836
|
-
|
|
837
|
-
```bash
|
|
838
|
-
pnpm install
|
|
839
|
-
pnpm dev
|
|
840
|
-
```
|
|
841
|
-
|
|
842
|
-
生产构建:
|
|
843
|
-
|
|
844
|
-
```bash
|
|
845
|
-
pnpm build
|
|
846
|
-
```
|
|
847
|
-
|
|
848
|
-
本地预览:
|
|
849
|
-
|
|
850
|
-
```bash
|
|
851
|
-
pnpm preview
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
---
|
|
855
|
-
|
|
856
|
-
## 26. 总结
|
|
857
|
-
|
|
858
|
-
Vue3 项目开发应重点关注以下几点:
|
|
859
|
-
|
|
860
|
-
- 目录结构清晰
|
|
861
|
-
- 命名风格统一
|
|
862
|
-
- 组件职责单一
|
|
863
|
-
- 类型定义完善
|
|
864
|
-
- 接口统一封装
|
|
865
|
-
- 状态管理规范
|
|
866
|
-
- 样式风格一致
|
|
867
|
-
- 权限设计清晰
|
|
868
|
-
- Git 提交规范
|
|
869
|
-
- 构建部署流程稳定
|
|
870
|
-
|
|
871
|
-
良好的开发规范可以提升团队协作效率,降低维护成本,并让项目在长期迭代中保持稳定和可扩展。
|