uniky 1.0.17 → 1.0.19
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/src/plugin/README.md +113 -0
- package/src/plugin/global.defined.ts +18 -18
- package/src/plugin/http.defined.ts +106 -0
- package/src/plugin/index.ts +8 -3
- package/src/plugin/pages.defined.ts +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# UniKy 插件系统
|
|
2
|
+
|
|
3
|
+
该目录包含 UniKy 框架的自动代码生成插件。
|
|
4
|
+
|
|
5
|
+
## 插件列表
|
|
6
|
+
|
|
7
|
+
### 1. global.defined.ts - 全局定义插件
|
|
8
|
+
|
|
9
|
+
自动收集 `src/_unikey/global` 文件夹下的所有导出内容,生成全局类型定义和安装文件。
|
|
10
|
+
|
|
11
|
+
### 2. pages.defined.ts - 页面路由插件
|
|
12
|
+
|
|
13
|
+
自动处理页面路由相关的代码生成。
|
|
14
|
+
|
|
15
|
+
### 3. http.defined.ts - HTTP 请求代码生成插件
|
|
16
|
+
|
|
17
|
+
基于 OpenAPI 规范自动生成类型安全的 HTTP 请求函数。
|
|
18
|
+
|
|
19
|
+
## HTTP 插件使用说明
|
|
20
|
+
|
|
21
|
+
### 工作原理
|
|
22
|
+
|
|
23
|
+
1. 插件会读取项目中的 OpenAPI 规范文件(支持多个位置)
|
|
24
|
+
2. 解析所有 API 路径和方法
|
|
25
|
+
3. 自动生成对应的请求函数
|
|
26
|
+
4. 生成的代码保存到 `src/_unikey/global/ky.ts`
|
|
27
|
+
|
|
28
|
+
### OpenAPI 文件位置
|
|
29
|
+
|
|
30
|
+
插件会按以下顺序查找 OpenAPI 规范文件:
|
|
31
|
+
|
|
32
|
+
1. `.uniky/oas/openapi.json`(推荐)
|
|
33
|
+
2. `openapi.json`
|
|
34
|
+
3. `swagger.json`
|
|
35
|
+
4. `api-docs.json`
|
|
36
|
+
|
|
37
|
+
### 生成规则
|
|
38
|
+
|
|
39
|
+
**路径命名转换:**
|
|
40
|
+
- `/api/sys.account/login` → `apiSysAccountLogin()`
|
|
41
|
+
- `/api/util.tool/upload` → `apiUtilToolUpload()`
|
|
42
|
+
|
|
43
|
+
**请求方法:**
|
|
44
|
+
- GET/DELETE 请求:`functionName(params?: any)`
|
|
45
|
+
- POST/PUT/PATCH 请求:`functionName(data?: any, params?: any)`
|
|
46
|
+
|
|
47
|
+
### 生成的代码结构
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// 1. uniKy 实例配置
|
|
51
|
+
export const _ky = uniKy.create({
|
|
52
|
+
prefixUrl: baseUrl,
|
|
53
|
+
hooks: { ... },
|
|
54
|
+
timeout: 30000
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 2. useKyData Hook
|
|
58
|
+
export function useKyData<T, O extends object = {}>(...)
|
|
59
|
+
|
|
60
|
+
// 3. API 请求函数
|
|
61
|
+
export function apiSysAccountLogin(data?: any, params?: any) {
|
|
62
|
+
return _ky.post('/api/sys.account/login', { data, params })
|
|
63
|
+
.then(res => res.data);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 使用示例
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// 直接调用
|
|
71
|
+
import { apiSysAccountLogin } from '@/_unikey/global/ky';
|
|
72
|
+
|
|
73
|
+
await apiSysAccountLogin({ username: 'admin', password: '123456' });
|
|
74
|
+
|
|
75
|
+
// 使用 Hook
|
|
76
|
+
import { useKyData, apiPitfallIndexQuery } from '@/_unikey/global/ky';
|
|
77
|
+
|
|
78
|
+
const { data, run } = useKyData(apiPitfallIndexQuery, []);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 重新生成代码
|
|
82
|
+
|
|
83
|
+
插件会在以下情况自动重新生成代码:
|
|
84
|
+
|
|
85
|
+
1. 项目启动时(如果文件不存在)
|
|
86
|
+
2. OpenAPI 规范文件发生变化时
|
|
87
|
+
|
|
88
|
+
如果需要手动触发重新生成,删除 `src/_unikey/global/ky.ts` 文件后重启开发服务器。
|
|
89
|
+
|
|
90
|
+
### 配置选项
|
|
91
|
+
|
|
92
|
+
在 `vite.config.ts` 中配置插件:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { unikyPlugin } from './.uniky/index';
|
|
96
|
+
|
|
97
|
+
export default defineConfig({
|
|
98
|
+
plugins: [
|
|
99
|
+
uni(),
|
|
100
|
+
unikyPlugin({
|
|
101
|
+
enablePages: true, // 启用页面插件
|
|
102
|
+
enableGlobal: true, // 启用全局定义插件
|
|
103
|
+
enableHttp: true // 启用 HTTP 插件(默认启用)
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 注意事项
|
|
110
|
+
|
|
111
|
+
1. **文件保护**:如果 `ky.ts` 文件已存在,插件不会覆盖,需手动删除后才会重新生成
|
|
112
|
+
2. **路径格式**:OpenAPI 中的路径需要符合 RESTful 规范
|
|
113
|
+
3. **自动导入**:生成的函数可以通过全局定义系统自动导入使用
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// 该插件用于作为 vite.config.ts 中 作为插件;
|
|
2
|
-
// 该插件用于收集 src
|
|
2
|
+
// 该插件用于收集 src/_unikey/global文件夹下的ts文件 所有export 导出的内容, 并将其合并为一个全局定义文件 src/_unikey/global.d.ts 文件 同时,生成一个 /src/_unikey/global.install.ts 文件.
|
|
3
3
|
// 以便在项目中全局使用这些类型定义,避免了手动维护全局类型定义文件的麻烦。
|
|
4
4
|
|
|
5
5
|
import type { Plugin } from 'vite';
|
|
@@ -14,9 +14,9 @@ interface ExportInfo {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export default function globalDefinedPlugin(): Plugin {
|
|
17
|
-
const globalDir = 'src
|
|
18
|
-
const globalDtsPath = 'src
|
|
19
|
-
const installTsPath = 'src
|
|
17
|
+
const globalDir = 'src/_unikey/global';
|
|
18
|
+
const globalDtsPath = 'src/_unikey/global.d.ts';
|
|
19
|
+
const installTsPath = 'src/_unikey/global.install.ts';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* 递归获取目录下所有 .ts 文件(排除 .d.ts 和 install.ts)
|
|
@@ -158,7 +158,7 @@ export default function globalDefinedPlugin(): Plugin {
|
|
|
158
158
|
|
|
159
159
|
// 按文件分组导出
|
|
160
160
|
for (const exp of allExports) {
|
|
161
|
-
const relativePath = path.relative('src
|
|
161
|
+
const relativePath = path.relative('src/_unikey', exp.filePath).replace(/\\/g, '/').replace(/\.ts$/, '');
|
|
162
162
|
if (!imports.has(relativePath)) {
|
|
163
163
|
imports.set(relativePath, new Set());
|
|
164
164
|
}
|
|
@@ -171,11 +171,11 @@ export default function globalDefinedPlugin(): Plugin {
|
|
|
171
171
|
content += '// 请勿手动修改\n\n';
|
|
172
172
|
|
|
173
173
|
// 生成 import 语句
|
|
174
|
-
|
|
174
|
+
Array.from(imports.entries()).forEach(([filePath, names]) => {
|
|
175
175
|
if (names.size > 0) {
|
|
176
176
|
content += `import type { ${Array.from(names).join(', ')} } from './${filePath}';\n`;
|
|
177
177
|
}
|
|
178
|
-
}
|
|
178
|
+
});
|
|
179
179
|
|
|
180
180
|
content += '\n';
|
|
181
181
|
|
|
@@ -187,11 +187,11 @@ export default function globalDefinedPlugin(): Plugin {
|
|
|
187
187
|
if (globalItems.length > 0) {
|
|
188
188
|
for (const exp of globalItems) {
|
|
189
189
|
if (exp.type === 'function') {
|
|
190
|
-
content += ` const ${exp.name}: typeof import('./${path.relative('src
|
|
190
|
+
content += ` const ${exp.name}: typeof import('./${path.relative('src/_unikey', exp.filePath).replace(/\\/g, '/').replace(/\.ts$/, '')}').${exp.name};\n`;
|
|
191
191
|
} else if (exp.type === 'const') {
|
|
192
|
-
content += ` const ${exp.name}: typeof import('./${path.relative('src
|
|
192
|
+
content += ` const ${exp.name}: typeof import('./${path.relative('src/_unikey', exp.filePath).replace(/\\/g, '/').replace(/\.ts$/, '')}').${exp.name};\n`;
|
|
193
193
|
} else if (exp.type === 'class') {
|
|
194
|
-
content += ` const ${exp.name}: typeof import('./${path.relative('src
|
|
194
|
+
content += ` const ${exp.name}: typeof import('./${path.relative('src/_unikey', exp.filePath).replace(/\\/g, '/').replace(/\.ts$/, '')}').${exp.name};\n`;
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
}
|
|
@@ -210,7 +210,7 @@ export default function globalDefinedPlugin(): Plugin {
|
|
|
210
210
|
|
|
211
211
|
// 按文件分组导出
|
|
212
212
|
for (const exp of allExports) {
|
|
213
|
-
const relativePath = './global/' + path.relative(path.join('src', '
|
|
213
|
+
const relativePath = './global/' + path.relative(path.join('src', '_unikey', 'global'), exp.filePath).replace(/\\/g, '/').replace(/\.ts$/, '');
|
|
214
214
|
if (!imports.has(relativePath)) {
|
|
215
215
|
imports.set(relativePath, { names: new Set(), hasDefault: false });
|
|
216
216
|
}
|
|
@@ -227,7 +227,7 @@ export default function globalDefinedPlugin(): Plugin {
|
|
|
227
227
|
content += '// 请勿手动修改\n\n';
|
|
228
228
|
|
|
229
229
|
// 生成 import 语句
|
|
230
|
-
|
|
230
|
+
Array.from(imports.entries()).forEach(([filePath, info]) => {
|
|
231
231
|
if (info.hasDefault && info.names.size > 0) {
|
|
232
232
|
content += `import ${info.defaultName}, { ${Array.from(info.names).join(', ')} } from '${filePath}';\n`;
|
|
233
233
|
} else if (info.hasDefault) {
|
|
@@ -235,7 +235,7 @@ export default function globalDefinedPlugin(): Plugin {
|
|
|
235
235
|
} else if (info.names.size > 0) {
|
|
236
236
|
content += `import { ${Array.from(info.names).join(', ')} } from '${filePath}';\n`;
|
|
237
237
|
}
|
|
238
|
-
}
|
|
238
|
+
});
|
|
239
239
|
|
|
240
240
|
content += '\n';
|
|
241
241
|
content += '/**\n';
|
|
@@ -247,7 +247,7 @@ export default function globalDefinedPlugin(): Plugin {
|
|
|
247
247
|
// 将导出的内容挂载到 globalThis
|
|
248
248
|
const globalItems = allExports.filter(exp => exp.type !== 'interface' && exp.type !== 'type');
|
|
249
249
|
for (const exp of globalItems) {
|
|
250
|
-
const name = exp.isDefault ? (imports.get('./global/' + path.relative(path.join('src', '
|
|
250
|
+
const name = exp.isDefault ? (imports.get('./global/' + path.relative(path.join('src', '_unikey', 'global'), exp.filePath).replace(/\\/g, '/').replace(/\.ts$/, ''))?.defaultName || exp.name) : exp.name;
|
|
251
251
|
content += ` (globalThis as any).${exp.name} = ${name};\n`;
|
|
252
252
|
}
|
|
253
253
|
|
|
@@ -305,25 +305,25 @@ export default function globalDefinedPlugin(): Plugin {
|
|
|
305
305
|
},
|
|
306
306
|
|
|
307
307
|
configureServer(server) {
|
|
308
|
-
// 监听 src
|
|
308
|
+
// 监听 src/_unikey/global 目录下的文件变化
|
|
309
309
|
server.watcher.add(globalDir);
|
|
310
310
|
|
|
311
311
|
server.watcher.on('change', (file) => {
|
|
312
|
-
if (file.includes('src
|
|
312
|
+
if (file.includes('src/_unikey/global') && file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.includes('global.install.ts')) {
|
|
313
313
|
console.log(`[globalDefined] 检测到文件变化: ${file}`);
|
|
314
314
|
generate();
|
|
315
315
|
}
|
|
316
316
|
});
|
|
317
317
|
|
|
318
318
|
server.watcher.on('add', (file) => {
|
|
319
|
-
if (file.includes('src
|
|
319
|
+
if (file.includes('src/_unikey/global') && file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.includes('global.install.ts')) {
|
|
320
320
|
console.log(`[globalDefined] 检测到新文件: ${file}`);
|
|
321
321
|
generate();
|
|
322
322
|
}
|
|
323
323
|
});
|
|
324
324
|
|
|
325
325
|
server.watcher.on('unlink', (file) => {
|
|
326
|
-
if (file.includes('src
|
|
326
|
+
if (file.includes('src/_unikey/global') && file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.includes('global.install.ts')) {
|
|
327
327
|
console.log(`[globalDefined] 检测到文件删除: ${file}`);
|
|
328
328
|
generate();
|
|
329
329
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// created by zhuxietong on 2026-01-30 23:22
|
|
2
|
+
// 该插件用于生成基础的 HTTP 请求配置
|
|
3
|
+
// 生成的代码将保存到 src/_unikey/global/ky.ts
|
|
4
|
+
|
|
5
|
+
import type { Plugin } from 'vite';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
|
|
9
|
+
export default function httpDefinedPlugin(): Plugin {
|
|
10
|
+
const outputPath = 'src/_unikey/global/ky.ts';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 生成基础请求配置代码
|
|
14
|
+
*/
|
|
15
|
+
function generateCode(): string {
|
|
16
|
+
let code = `// created by zhuxietong on 2026-01-30 23:22\n`;
|
|
17
|
+
code += `import { uniKy, useMountedLoad } from "uniky";\n`;
|
|
18
|
+
code += `import { ref } from "vue";\n\n`;
|
|
19
|
+
|
|
20
|
+
code += `const baseUrl = import.meta.env.VITE_API_BASE_URL;\n\n`;
|
|
21
|
+
|
|
22
|
+
code += `export const _ky = uniKy.create({\n`;
|
|
23
|
+
code += ` prefixUrl: baseUrl,\n`;
|
|
24
|
+
code += ` hooks: {\n`;
|
|
25
|
+
code += ` beforeRequest: [\n`;
|
|
26
|
+
code += ` (request, options) => {\n`;
|
|
27
|
+
code += ` const headers = options.headers || {};\n`;
|
|
28
|
+
code += ` try {\n`;
|
|
29
|
+
code += ` const user = uni.getStorageSync('user') || '{}';\n`;
|
|
30
|
+
code += ` const { token } = JSON.parse(user);\n`;
|
|
31
|
+
code += ` if (token) {\n`;
|
|
32
|
+
code += ` headers['Authorization'] = 'Bearer ' + token;\n`;
|
|
33
|
+
code += ` }\n`;
|
|
34
|
+
code += ` options.headers = headers;\n`;
|
|
35
|
+
code += ` } catch (e) {}\n`;
|
|
36
|
+
code += ` },\n`;
|
|
37
|
+
code += ` ],\n`;
|
|
38
|
+
code += ` afterResponse: [\n`;
|
|
39
|
+
code += ` (resp: any, request, options) => {},\n`;
|
|
40
|
+
code += ` ],\n`;
|
|
41
|
+
code += ` },\n`;
|
|
42
|
+
code += ` timeout: 30000,\n`;
|
|
43
|
+
code += `});\n\n`;
|
|
44
|
+
|
|
45
|
+
code += `export function useKyData<T, O extends object = {}>(req: (option: O) => Promise<T>, defaultValue?: T) {\n`;
|
|
46
|
+
code += ` const data = ref<T | undefined>(defaultValue);\n`;
|
|
47
|
+
code += ` const option = ref<any>({});\n`;
|
|
48
|
+
code += ` const run = () => {\n`;
|
|
49
|
+
code += ` return new Promise((resolve, reject) => {\n`;
|
|
50
|
+
code += ` req(option.value)\n`;
|
|
51
|
+
code += ` .then((res: any) => {\n`;
|
|
52
|
+
code += ` data.value = res;\n`;
|
|
53
|
+
code += ` resolve(res);\n`;
|
|
54
|
+
code += ` })\n`;
|
|
55
|
+
code += ` .catch((err) => {\n`;
|
|
56
|
+
code += ` reject(err);\n`;
|
|
57
|
+
code += ` });\n`;
|
|
58
|
+
code += ` });\n`;
|
|
59
|
+
code += ` };\n\n`;
|
|
60
|
+
code += ` useMountedLoad(({ option:o, query, json }) => {\n`;
|
|
61
|
+
code += ` option.value = option;\n`;
|
|
62
|
+
code += ` run()\n`;
|
|
63
|
+
code += ` .then(() => {})\n`;
|
|
64
|
+
code += ` .catch(() => {});\n`;
|
|
65
|
+
code += ` });\n\n`;
|
|
66
|
+
code += ` return {\n`;
|
|
67
|
+
code += ` data,\n`;
|
|
68
|
+
code += ` run,\n`;
|
|
69
|
+
code += ` };\n`;
|
|
70
|
+
code += `}\n`;
|
|
71
|
+
|
|
72
|
+
return code;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 生成文件
|
|
77
|
+
*/
|
|
78
|
+
function generate() {
|
|
79
|
+
try {
|
|
80
|
+
if (fs.existsSync(outputPath)) {
|
|
81
|
+
console.log(`[httpDefined] 文件已存在,跳过生成: ${outputPath}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const code = generateCode();
|
|
86
|
+
|
|
87
|
+
const dir = path.dirname(outputPath);
|
|
88
|
+
if (!fs.existsSync(dir)) {
|
|
89
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fs.writeFileSync(outputPath, code, 'utf-8');
|
|
93
|
+
console.log(`[httpDefined] 已生成 ${outputPath}`);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('[httpDefined] 生成失败:', error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
name: 'vite-plugin-http-defined',
|
|
101
|
+
|
|
102
|
+
buildStart() {
|
|
103
|
+
generate();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
package/src/plugin/index.ts
CHANGED
|
@@ -3,30 +3,35 @@ import type { Plugin } from 'vite';
|
|
|
3
3
|
|
|
4
4
|
import pagesDefinedPlugin from './pages.defined';
|
|
5
5
|
import globalDefinedPlugin from './global.defined';
|
|
6
|
+
import httpDefinedPlugin from './http.defined';
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
export interface UnikyPluginOptions {
|
|
10
11
|
enablePages?: boolean;
|
|
11
12
|
enableGlobal?: boolean;
|
|
13
|
+
enableHttp?: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export function unikyPlugin(options: UnikyPluginOptions = {}): Plugin[] {
|
|
15
|
-
const { enablePages = true, enableGlobal = true } = options;
|
|
17
|
+
const { enablePages = true, enableGlobal = true, enableHttp = true } = options;
|
|
16
18
|
|
|
17
19
|
const plugins: Plugin[] = [];
|
|
18
20
|
|
|
19
21
|
if (enablePages) {
|
|
20
22
|
plugins.push(pagesDefinedPlugin());
|
|
21
23
|
}
|
|
22
|
-
|
|
24
|
+
if (enableHttp) {
|
|
25
|
+
plugins.push(httpDefinedPlugin());
|
|
26
|
+
}
|
|
23
27
|
if (enableGlobal) {
|
|
24
28
|
plugins.push(globalDefinedPlugin());
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
|
|
27
32
|
return plugins;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
export { pagesDefinedPlugin, globalDefinedPlugin };
|
|
35
|
+
export { pagesDefinedPlugin, globalDefinedPlugin, httpDefinedPlugin };
|
|
31
36
|
|
|
32
37
|
export default unikyPlugin;
|
|
@@ -248,7 +248,7 @@ export default function pagesDefinedPlugin(): Plugin {
|
|
|
248
248
|
configResolved(config) {
|
|
249
249
|
// 确定 src 目录和输出路径
|
|
250
250
|
srcDir = path.resolve(config.root, 'src');
|
|
251
|
-
outputPath = path.resolve(srcDir, '
|
|
251
|
+
outputPath = path.resolve(srcDir, '_unikey/global/pages.ts');
|
|
252
252
|
},
|
|
253
253
|
|
|
254
254
|
buildStart() {
|