@yuer678/create-kb 0.3.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/LICENSE +21 -0
- package/dist/index.js +196 -0
- package/package.json +48 -0
- package/templates/ai/.env.example +7 -0
- package/templates/ai/README.md +39 -0
- package/templates/ai/package.json +42 -0
- package/templates/ai/public/index.html +117 -0
- package/templates/ai/src/app.ts +22 -0
- package/templates/ai/src/config.ts +13 -0
- package/templates/ai/src/index.ts +6 -0
- package/templates/ai/src/lib/llm.ts +24 -0
- package/templates/ai/src/routes/chat.ts +72 -0
- package/templates/ai/test/chat.spec.ts +27 -0
- package/templates/ai/tsconfig.json +13 -0
- package/templates/ai/vitest.config.ts +8 -0
- package/templates/api/README.md +50 -0
- package/templates/api/package.json +42 -0
- package/templates/api/src/app.ts +84 -0
- package/templates/api/src/data/options.ts +17 -0
- package/templates/api/src/data/regions.ts +71 -0
- package/templates/api/src/data/users.ts +76 -0
- package/templates/api/src/index.ts +9 -0
- package/templates/api/src/middleware/error.ts +28 -0
- package/templates/api/src/middleware/validate.ts +22 -0
- package/templates/api/src/query.ts +106 -0
- package/templates/api/src/routes/health.ts +21 -0
- package/templates/api/src/routes/options.ts +19 -0
- package/templates/api/src/routes/regions.ts +31 -0
- package/templates/api/src/routes/users.ts +57 -0
- package/templates/api/src/server.ts +31 -0
- package/templates/api/src/types.ts +8 -0
- package/templates/api/test/app.spec.ts +216 -0
- package/templates/api/tsconfig.json +13 -0
- package/templates/api/vitest.config.ts +8 -0
- package/templates/base/README.md +25 -0
- package/templates/base/index.html +12 -0
- package/templates/base/package.json +32 -0
- package/templates/base/src/App.vue +46 -0
- package/templates/base/src/main.ts +6 -0
- package/templates/base/tsconfig.json +18 -0
- package/templates/base/vite.config.ts +6 -0
- package/templates/electron/README.md +32 -0
- package/templates/electron/package.json +54 -0
- package/templates/electron/src/main/index.ts +36 -0
- package/templates/electron/src/preload/index.ts +10 -0
- package/templates/electron/src/renderer/index.html +22 -0
- package/templates/electron/src/renderer/renderer.ts +14 -0
- package/templates/electron/tsconfig.json +13 -0
- package/templates/fullstack/Dockerfile +16 -0
- package/templates/fullstack/README.md +37 -0
- package/templates/fullstack/docker-compose.yml +26 -0
- package/templates/fullstack/package.json +30 -0
- package/templates/fullstack/pnpm-workspace.yaml +3 -0
- package/templates/fullstack/server/package.json +41 -0
- package/templates/fullstack/server/src/app.ts +84 -0
- package/templates/fullstack/server/src/data/options.ts +17 -0
- package/templates/fullstack/server/src/data/regions.ts +71 -0
- package/templates/fullstack/server/src/data/users.ts +76 -0
- package/templates/fullstack/server/src/index.ts +9 -0
- package/templates/fullstack/server/src/middleware/error.ts +28 -0
- package/templates/fullstack/server/src/middleware/validate.ts +22 -0
- package/templates/fullstack/server/src/query.ts +106 -0
- package/templates/fullstack/server/src/routes/health.ts +21 -0
- package/templates/fullstack/server/src/routes/options.ts +19 -0
- package/templates/fullstack/server/src/routes/regions.ts +31 -0
- package/templates/fullstack/server/src/routes/users.ts +57 -0
- package/templates/fullstack/server/src/server.ts +31 -0
- package/templates/fullstack/server/src/types.ts +8 -0
- package/templates/fullstack/server/test/app.spec.ts +216 -0
- package/templates/fullstack/server/tsconfig.json +13 -0
- package/templates/fullstack/server/vitest.config.ts +8 -0
- package/templates/fullstack/web/index.html +12 -0
- package/templates/fullstack/web/package.json +30 -0
- package/templates/fullstack/web/src/App.vue +108 -0
- package/templates/fullstack/web/src/main.ts +4 -0
- package/templates/fullstack/web/tsconfig.json +13 -0
- package/templates/fullstack/web/vite.config.ts +16 -0
- package/templates/react/README.md +8 -0
- package/templates/react/index.html +12 -0
- package/templates/react/package.json +34 -0
- package/templates/react/src/App.css +32 -0
- package/templates/react/src/App.tsx +16 -0
- package/templates/react/src/main.tsx +9 -0
- package/templates/react/tsconfig.json +20 -0
- package/templates/react/vite.config.ts +9 -0
- package/templates/starter/README.md +28 -0
- package/templates/starter/index.html +12 -0
- package/templates/starter/package.json +32 -0
- package/templates/starter/src/App.vue +125 -0
- package/templates/starter/src/main.ts +6 -0
- package/templates/starter/tsconfig.json +18 -0
- package/templates/starter/vite.config.ts +6 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// ⚠️ 自动生成文件,请勿直接修改。
|
|
3
|
+
// 源:packages/api/src —— 改动请改源文件后运行 `pnpm sync:api-template`。
|
|
4
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
5
|
+
import request from 'supertest'
|
|
6
|
+
import type { Server } from 'node:http'
|
|
7
|
+
import { createApp } from '../src/app'
|
|
8
|
+
import { OPTIONS_TOTAL } from '../src/data/options'
|
|
9
|
+
import { REGION_NODE_COUNT } from '../src/data/regions'
|
|
10
|
+
import { USERS_TOTAL } from '../src/data/users'
|
|
11
|
+
|
|
12
|
+
let server: Server
|
|
13
|
+
let base: string
|
|
14
|
+
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
const app = createApp({ name: 'test-api' })
|
|
17
|
+
await new Promise<void>((resolve) => {
|
|
18
|
+
server = app.listen(0, () => resolve())
|
|
19
|
+
})
|
|
20
|
+
const address = server.address()
|
|
21
|
+
if (address && typeof address === 'object') {
|
|
22
|
+
base = `http://127.0.0.1:${address.port}`
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
afterAll(() => {
|
|
27
|
+
server?.close()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('健康检查与根路由', () => {
|
|
31
|
+
it('GET /api/health 返回 ok 与服务标识', async () => {
|
|
32
|
+
const res = await request(base).get('/api/health')
|
|
33
|
+
expect(res.status).toBe(200)
|
|
34
|
+
expect(res.body.status).toBe('ok')
|
|
35
|
+
expect(res.body.name).toBe('test-api')
|
|
36
|
+
expect(typeof res.body.uptime).toBe('number')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('GET /health 无前缀同样可用(容器探活)', async () => {
|
|
40
|
+
const res = await request(base).get('/health')
|
|
41
|
+
expect(res.status).toBe(200)
|
|
42
|
+
expect(res.body.status).toBe('ok')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('GET / 列出可用接口', async () => {
|
|
46
|
+
const res = await request(base).get('/')
|
|
47
|
+
expect(res.status).toBe(200)
|
|
48
|
+
expect(res.body.prefix).toBe('/api')
|
|
49
|
+
expect(res.body.endpoints.length).toBeGreaterThan(0)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('GET /api/users 分页 / 排序 / 搜索', () => {
|
|
54
|
+
it('默认返回第 1 页 10 条,total 为全量', async () => {
|
|
55
|
+
const res = await request(base).get('/api/users')
|
|
56
|
+
expect(res.status).toBe(200)
|
|
57
|
+
expect(res.body.list).toHaveLength(10)
|
|
58
|
+
expect(res.body.total).toBe(USERS_TOTAL)
|
|
59
|
+
expect(res.body.page).toBe(1)
|
|
60
|
+
expect(res.body.pageSize).toBe(10)
|
|
61
|
+
expect(res.body.list[0].id).toBe(1)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('翻页返回对应片段且不重叠', async () => {
|
|
65
|
+
const first = await request(base).get('/api/users?page=1&pageSize=20')
|
|
66
|
+
const second = await request(base).get('/api/users?page=2&pageSize=20')
|
|
67
|
+
expect(first.body.list).toHaveLength(20)
|
|
68
|
+
expect(second.body.list).toHaveLength(20)
|
|
69
|
+
expect(second.body.list[0].id).toBe(21)
|
|
70
|
+
const overlap = second.body.list.filter((row: { id: number }) =>
|
|
71
|
+
first.body.list.some((item: { id: number }) => item.id === row.id),
|
|
72
|
+
)
|
|
73
|
+
expect(overlap).toHaveLength(0)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('pageSize 非法时回退默认值,page 越界返回空列表', async () => {
|
|
77
|
+
const fallback = await request(base).get('/api/users?pageSize=0')
|
|
78
|
+
expect(fallback.body.pageSize).toBe(10)
|
|
79
|
+
|
|
80
|
+
const beyond = await request(base).get('/api/users?page=999')
|
|
81
|
+
expect(beyond.body.list).toHaveLength(0)
|
|
82
|
+
expect(beyond.body.total).toBe(USERS_TOTAL)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('keyword 模糊匹配姓名 / 公司 / 邮箱', async () => {
|
|
86
|
+
const res = await request(base).get('/api/users?keyword=%E6%98%9F%E5%B0%98')
|
|
87
|
+
// "星尘" 命中星尘科技
|
|
88
|
+
expect(res.body.total).toBeGreaterThan(0)
|
|
89
|
+
for (const row of res.body.list) {
|
|
90
|
+
expect(row.company).toContain('星尘')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const byEmail = await request(base).get('/api/users?keyword=user12@')
|
|
94
|
+
expect(byEmail.body.list[0].email).toBe('user12@example.com')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('sortBy=score&order=desc 正确降序,白名单外字段被忽略', async () => {
|
|
98
|
+
const desc = await request(base).get('/api/users?sortBy=score&order=desc&pageSize=5')
|
|
99
|
+
const scores = desc.body.list.map((row: { score: number }) => row.score)
|
|
100
|
+
expect([...scores].sort((a: number, b: number) => b - a)).toEqual(scores)
|
|
101
|
+
|
|
102
|
+
const asc = await request(base).get('/api/users?sortBy=score&order=asc&pageSize=5')
|
|
103
|
+
const ascScores = asc.body.list.map((row: { score: number }) => row.score)
|
|
104
|
+
expect([...ascScores].sort((a: number, b: number) => a - b)).toEqual(ascScores)
|
|
105
|
+
|
|
106
|
+
// 不在白名单里 → 保持原始顺序
|
|
107
|
+
const ignored = await request(base).get('/api/users?sortBy=__proto__&order=desc')
|
|
108
|
+
expect(ignored.body.list[0].id).toBe(1)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('GET /api/regions 树与懒加载', () => {
|
|
113
|
+
it('GET /api/regions/tree 返回完整嵌套树', async () => {
|
|
114
|
+
const res = await request(base).get('/api/regions/tree')
|
|
115
|
+
expect(res.status).toBe(200)
|
|
116
|
+
expect(res.body.total).toBe(REGION_NODE_COUNT)
|
|
117
|
+
expect(res.body.list).toHaveLength(8)
|
|
118
|
+
expect(res.body.list[0].children).toHaveLength(6)
|
|
119
|
+
expect(res.body.list[0].children[0].children).toHaveLength(8)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('GET /api/regions 不带 parent 返回顶层', async () => {
|
|
123
|
+
const res = await request(base).get('/api/regions')
|
|
124
|
+
expect(res.body.parent).toBeNull()
|
|
125
|
+
expect(res.body.list).toHaveLength(8)
|
|
126
|
+
expect(res.body.leaf).toBe(false)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('GET /api/regions?parent=a1 只返回下一级且不带 children', async () => {
|
|
130
|
+
const res = await request(base).get('/api/regions?parent=a1')
|
|
131
|
+
expect(res.body.list).toHaveLength(6)
|
|
132
|
+
expect(res.body.parent).toBe('a1')
|
|
133
|
+
for (const node of res.body.list) {
|
|
134
|
+
expect(node).not.toHaveProperty('children')
|
|
135
|
+
expect(node.leaf).toBe(false)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('末级节点的下一级为空且 leaf 为 true', async () => {
|
|
140
|
+
const res = await request(base).get('/api/regions?parent=a1-c1-b1')
|
|
141
|
+
expect(res.body.list).toHaveLength(0)
|
|
142
|
+
expect(res.body.leaf).toBe(true)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('parent 不存在返回 404', async () => {
|
|
146
|
+
const res = await request(base).get('/api/regions?parent=not-exist')
|
|
147
|
+
expect(res.status).toBe(404)
|
|
148
|
+
expect(res.body.code).toBe(404)
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('GET /api/options 搜索与分页', () => {
|
|
153
|
+
it('默认第 1 页 10 条,total 为全量', async () => {
|
|
154
|
+
const res = await request(base).get('/api/options')
|
|
155
|
+
expect(res.body.list).toHaveLength(10)
|
|
156
|
+
expect(res.body.total).toBe(OPTIONS_TOTAL)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('pageSize 足够大时一次取回全部', async () => {
|
|
160
|
+
const res = await request(base).get('/api/options?pageSize=500')
|
|
161
|
+
expect(res.body.list).toHaveLength(OPTIONS_TOTAL)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('keyword 过滤后 total 是过滤后的条数', async () => {
|
|
165
|
+
const res = await request(base).get('/api/options?keyword=%E5%80%99%E9%80%89%E9%A1%B9%2001')
|
|
166
|
+
expect(res.body.total).toBeLessThan(OPTIONS_TOTAL)
|
|
167
|
+
expect(res.body.total).toBeGreaterThan(0)
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
describe('写操作、校验与错误处理', () => {
|
|
172
|
+
it('POST /api/users 参数不合法返回 400 与字段错误', async () => {
|
|
173
|
+
const res = await request(base).post('/api/users').send({ name: '' })
|
|
174
|
+
expect(res.status).toBe(400)
|
|
175
|
+
expect(res.body.code).toBe(400)
|
|
176
|
+
expect(res.body.errors.name).toBeTruthy()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('POST /api/users 创建成功返回 201', async () => {
|
|
180
|
+
const res = await request(base).post('/api/users').send({ name: '新用户', score: 88 })
|
|
181
|
+
expect(res.status).toBe(201)
|
|
182
|
+
expect(res.body.name).toBe('新用户')
|
|
183
|
+
expect(res.body.score).toBe(88)
|
|
184
|
+
expect(res.body.id).toBeGreaterThan(USERS_TOTAL)
|
|
185
|
+
|
|
186
|
+
const detail = await request(base).get(`/api/users/${res.body.id}`)
|
|
187
|
+
expect(detail.status).toBe(200)
|
|
188
|
+
expect(detail.body.name).toBe('新用户')
|
|
189
|
+
|
|
190
|
+
const removed = await request(base).delete(`/api/users/${res.body.id}`)
|
|
191
|
+
expect(removed.status).toBe(204)
|
|
192
|
+
|
|
193
|
+
const again = await request(base).delete(`/api/users/${res.body.id}`)
|
|
194
|
+
expect(again.status).toBe(404)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('GET /api/users/:id 不存在返回 404 与中文提示', async () => {
|
|
198
|
+
const res = await request(base).get('/api/users/999999')
|
|
199
|
+
expect(res.status).toBe(404)
|
|
200
|
+
expect(res.body.message).toContain('用户不存在')
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('未知路由返回 404', async () => {
|
|
204
|
+
const res = await request(base).get('/api/not-exist')
|
|
205
|
+
expect(res.status).toBe(404)
|
|
206
|
+
expect(res.body.code).toBe(404)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('预检请求返回 204 并带 CORS 头', async () => {
|
|
210
|
+
const res = await request(base)
|
|
211
|
+
.options('/api/users')
|
|
212
|
+
.set('Origin', 'http://localhost:5173')
|
|
213
|
+
expect(res.status).toBe(204)
|
|
214
|
+
expect(res.headers['access-control-allow-origin']).toBe('http://localhost:5173')
|
|
215
|
+
})
|
|
216
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Fullstack</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "web",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite --port 5173",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"vue": "^3.5.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@vitejs/plugin-vue": "^6.0.0",
|
|
16
|
+
"typescript": "^5.9.0",
|
|
17
|
+
"vite": "^8.0.0"
|
|
18
|
+
},
|
|
19
|
+
"overrides": {
|
|
20
|
+
"js-yaml": "^4.3.2",
|
|
21
|
+
"fast-uri": "^3.1.0",
|
|
22
|
+
"electron": "^39.8.10"
|
|
23
|
+
},
|
|
24
|
+
"pnpm": {
|
|
25
|
+
"overrides": {
|
|
26
|
+
"js-yaml": "^4.1.1",
|
|
27
|
+
"fast-uri": "^3.1.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
const items = ref<{ id: number; name: string; done: boolean }[]>([])
|
|
5
|
+
const name = ref('')
|
|
6
|
+
const error = ref('')
|
|
7
|
+
|
|
8
|
+
const API = import.meta.env.VITE_API ?? 'http://localhost:3000'
|
|
9
|
+
|
|
10
|
+
async function load() {
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch(`${API}/items`)
|
|
13
|
+
items.value = await res.json()
|
|
14
|
+
error.value = ''
|
|
15
|
+
} catch {
|
|
16
|
+
error.value = '无法连接 API(请先启动后端)'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function add() {
|
|
21
|
+
if (!name.value) return
|
|
22
|
+
await fetch(`${API}/items`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'content-type': 'application/json' },
|
|
25
|
+
body: JSON.stringify({ name: name.value }),
|
|
26
|
+
})
|
|
27
|
+
name.value = ''
|
|
28
|
+
await load()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
onMounted(load)
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<main class="page">
|
|
36
|
+
<h1>Fullstack 模板</h1>
|
|
37
|
+
<p class="sub">前端 Vue + 后端 Express,通过 HTTP 通信</p>
|
|
38
|
+
<p v-if="error" class="error">{{ error }}</p>
|
|
39
|
+
<form class="row" @submit.prevent="add">
|
|
40
|
+
<input v-model="name" placeholder="新任务名称" />
|
|
41
|
+
<button type="submit">添加</button>
|
|
42
|
+
</form>
|
|
43
|
+
<ul class="list">
|
|
44
|
+
<li v-for="item in items" :key="item.id">
|
|
45
|
+
<span :class="{ done: item.done }">{{ item.name }}</span>
|
|
46
|
+
<small>#{{ item.id }}</small>
|
|
47
|
+
</li>
|
|
48
|
+
</ul>
|
|
49
|
+
</main>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
* {
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
}
|
|
56
|
+
body {
|
|
57
|
+
margin: 0;
|
|
58
|
+
font-family: system-ui, sans-serif;
|
|
59
|
+
background: #f6f7f9;
|
|
60
|
+
}
|
|
61
|
+
.page {
|
|
62
|
+
max-width: 560px;
|
|
63
|
+
margin: 48px auto;
|
|
64
|
+
padding: 24px;
|
|
65
|
+
background: #fff;
|
|
66
|
+
border-radius: 12px;
|
|
67
|
+
box-shadow: 0 2px 12px rgb(0 0 0 / 8%);
|
|
68
|
+
}
|
|
69
|
+
.sub {
|
|
70
|
+
color: #888;
|
|
71
|
+
}
|
|
72
|
+
.error {
|
|
73
|
+
color: #d33;
|
|
74
|
+
font-size: 14px;
|
|
75
|
+
}
|
|
76
|
+
.row {
|
|
77
|
+
display: flex;
|
|
78
|
+
gap: 8px;
|
|
79
|
+
}
|
|
80
|
+
.row input {
|
|
81
|
+
flex: 1;
|
|
82
|
+
padding: 8px 12px;
|
|
83
|
+
border: 1px solid #ddd;
|
|
84
|
+
border-radius: 8px;
|
|
85
|
+
}
|
|
86
|
+
.row button {
|
|
87
|
+
padding: 8px 16px;
|
|
88
|
+
border: none;
|
|
89
|
+
border-radius: 8px;
|
|
90
|
+
background: #3b82f6;
|
|
91
|
+
color: #fff;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
}
|
|
94
|
+
.list {
|
|
95
|
+
padding: 0;
|
|
96
|
+
list-style: none;
|
|
97
|
+
}
|
|
98
|
+
.list li {
|
|
99
|
+
display: flex;
|
|
100
|
+
justify-content: space-between;
|
|
101
|
+
padding: 10px 0;
|
|
102
|
+
border-bottom: 1px solid #eee;
|
|
103
|
+
}
|
|
104
|
+
.done {
|
|
105
|
+
text-decoration: line-through;
|
|
106
|
+
color: #999;
|
|
107
|
+
}
|
|
108
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"jsx": "preserve",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"types": ["vite/client"],
|
|
10
|
+
"noEmit": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src", "vite.config.ts"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [vue()],
|
|
6
|
+
server: {
|
|
7
|
+
port: 5173,
|
|
8
|
+
proxy: {
|
|
9
|
+
'/api': {
|
|
10
|
+
target: 'http://localhost:3000',
|
|
11
|
+
changeOrigin: true,
|
|
12
|
+
rewrite: (p) => p.replace(/^\/api/, ''),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{projectName}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{packageName}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"react": "^19.0.0",
|
|
14
|
+
"react-dom": "^19.0.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/react": "^19.0.0",
|
|
18
|
+
"@types/react-dom": "^19.0.0",
|
|
19
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
20
|
+
"typescript": "^5.9.0",
|
|
21
|
+
"vite": "^8.0.0"
|
|
22
|
+
},
|
|
23
|
+
"overrides": {
|
|
24
|
+
"js-yaml": "^4.3.2",
|
|
25
|
+
"fast-uri": "^3.1.0",
|
|
26
|
+
"electron": "^39.8.10"
|
|
27
|
+
},
|
|
28
|
+
"pnpm": {
|
|
29
|
+
"overrides": {
|
|
30
|
+
"js-yaml": "^4.1.1",
|
|
31
|
+
"fast-uri": "^3.1.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
body {
|
|
5
|
+
margin: 0;
|
|
6
|
+
font-family: system-ui, sans-serif;
|
|
7
|
+
background: #f6f7f9;
|
|
8
|
+
}
|
|
9
|
+
.page {
|
|
10
|
+
max-width: 480px;
|
|
11
|
+
margin: 64px auto;
|
|
12
|
+
padding: 32px;
|
|
13
|
+
background: #fff;
|
|
14
|
+
border-radius: 12px;
|
|
15
|
+
box-shadow: 0 2px 12px rgb(0 0 0 / 8%);
|
|
16
|
+
text-align: center;
|
|
17
|
+
}
|
|
18
|
+
.sub {
|
|
19
|
+
color: #888;
|
|
20
|
+
}
|
|
21
|
+
.btn {
|
|
22
|
+
padding: 8px 20px;
|
|
23
|
+
border: none;
|
|
24
|
+
border-radius: 8px;
|
|
25
|
+
background: #3b82f6;
|
|
26
|
+
color: #fff;
|
|
27
|
+
font-size: 15px;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
}
|
|
30
|
+
.btn:hover {
|
|
31
|
+
background: #2563eb;
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import './App.css'
|
|
3
|
+
|
|
4
|
+
export default function App() {
|
|
5
|
+
const [count, setCount] = useState(0)
|
|
6
|
+
const appName = '{{projectName}}'
|
|
7
|
+
return (
|
|
8
|
+
<main className="page">
|
|
9
|
+
<h1>{appName}</h1>
|
|
10
|
+
<p className="sub">React 19 + Vite + TypeScript 模板</p>
|
|
11
|
+
<button className="btn" onClick={() => setCount((c) => c + 1)}>
|
|
12
|
+
点击计数:{count}
|
|
13
|
+
</button>
|
|
14
|
+
</main>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["src"]
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
基于 [KB UI](https://github.com/)(@kb/ui)的 Vue 3 + Vite + TypeScript 完整示例模板。
|
|
4
|
+
|
|
5
|
+
包含:表单联动(Input/Checkbox/Radio/Select)、数据表格(Table + 自定义单元格)、
|
|
6
|
+
消息提示(message)、对话框(Dialog)、暗色主题切换。
|
|
7
|
+
|
|
8
|
+
## 命令
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm install # 安装依赖
|
|
12
|
+
pnpm dev # 启动开发服务器
|
|
13
|
+
pnpm build # 生产构建
|
|
14
|
+
pnpm preview # 预览构建产物
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 组件库
|
|
18
|
+
|
|
19
|
+
全量引入 @kb/ui(`src/main.ts`):
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import KbUi from 'kb-ui-vue'
|
|
23
|
+
import '@kb/ui/styles/index.css'
|
|
24
|
+
|
|
25
|
+
createApp(App).use(KbUi).mount('#app')
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
暗色主题:给 `<html>` 设置 `data-theme="dark"` 即可。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{projectName}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{packageName}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"kb-ui-vue": "{{kbVersion}}",
|
|
13
|
+
"vue": "^3.5.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@vitejs/plugin-vue": "^6.0.0",
|
|
17
|
+
"typescript": "^5.9.0",
|
|
18
|
+
"vite": "^8.0.0",
|
|
19
|
+
"vue-tsc": "^3.0.0"
|
|
20
|
+
},
|
|
21
|
+
"overrides": {
|
|
22
|
+
"js-yaml": "^4.3.2",
|
|
23
|
+
"fast-uri": "^3.1.0",
|
|
24
|
+
"electron": "^39.8.10"
|
|
25
|
+
},
|
|
26
|
+
"pnpm": {
|
|
27
|
+
"overrides": {
|
|
28
|
+
"js-yaml": "^4.1.1",
|
|
29
|
+
"fast-uri": "^3.1.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|