create-dwy 0.2.0 → 0.3.1
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/bin/index.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { readFileSync } from 'fs'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { dirname, resolve } from 'path'
|
|
3
6
|
import { program } from 'commander'
|
|
4
7
|
import { createProject } from '../src/create.js'
|
|
5
8
|
import { syncClaude, syncProjectClaude } from '../src/sync.js'
|
|
6
9
|
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'))
|
|
12
|
+
|
|
7
13
|
program
|
|
8
14
|
.name('dwy')
|
|
9
15
|
.description('Project scaffolding and Claude Code config sync')
|
|
10
|
-
.version(
|
|
16
|
+
.version(pkg.version)
|
|
11
17
|
|
|
12
18
|
program
|
|
13
19
|
.command('create [name]')
|
package/package.json
CHANGED
package/src/sync.js
CHANGED
|
@@ -6,7 +6,7 @@ export async function syncClaude() {
|
|
|
6
6
|
console.log(chalk.blue('Syncing global Claude configuration...'))
|
|
7
7
|
|
|
8
8
|
const repoDir = await ensureRepoCache()
|
|
9
|
-
const sourceDir = path.join(repoDir, 'templates', 'claude-global')
|
|
9
|
+
const sourceDir = path.join(repoDir, 'claude-cli', 'templates', 'claude-global')
|
|
10
10
|
const targetDir = path.join(process.env.HOME, '.claude')
|
|
11
11
|
|
|
12
12
|
if (!await fs.pathExists(sourceDir)) {
|
|
@@ -62,7 +62,7 @@ export async function syncProjectClaude() {
|
|
|
62
62
|
const template = marker.template || 'web'
|
|
63
63
|
|
|
64
64
|
const repoDir = await ensureRepoCache()
|
|
65
|
-
const sourceDir = path.join(repoDir, 'templates', 'project', template, '.claude')
|
|
65
|
+
const sourceDir = path.join(repoDir, 'claude-cli', 'templates', 'project', template, '.claude')
|
|
66
66
|
const targetDir = path.join(process.cwd(), '.claude')
|
|
67
67
|
|
|
68
68
|
if (!await fs.pathExists(sourceDir)) {
|
package/src/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
9
9
|
|
|
10
10
|
export const PACKAGE_ROOT = path.resolve(__dirname, '..')
|
|
11
11
|
export const CACHE_DIR = path.join(process.env.HOME, '.dwy', 'cache')
|
|
12
|
-
export const DWY_REPO_URL = 'https://gitee.com/snailyuanyuan/dwy-
|
|
12
|
+
export const DWY_REPO_URL = 'https://gitee.com/snailyuanyuan/dwy-shared.git'
|
|
13
13
|
|
|
14
14
|
export async function ensureRepoCache() {
|
|
15
15
|
await fs.ensureDir(CACHE_DIR)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core
|
|
3
|
+
description: "@danweiyuan/core 前端工具库速查。触发条件:需要 HTTP 请求封装、localStorage、表单校验、日期格式化、Vue composable 工具时。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @danweiyuan/core 工具库速查
|
|
7
|
+
|
|
8
|
+
Vue 3 项目通用工具库,包含 5 个模块:request、storage、validators、date、hooks。
|
|
9
|
+
|
|
10
|
+
## 安装
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add @danweiyuan/core
|
|
14
|
+
# peerDependencies: axios ^1.0.0, vue ^3.4.0
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createRequest, useStorage, isPhone, formatDate, useDebounce } from '@danweiyuan/core'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 查阅源码
|
|
22
|
+
|
|
23
|
+
每个模块源码在 `frontend/core/src/{module}/index.ts`,函数简短可直接阅读。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## request — Axios 插件化封装
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createRequest, tokenPlugin, headerPlugin, unwrapPlugin, refreshTokenPlugin } from '@danweiyuan/core'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### createRequest(options?)
|
|
34
|
+
|
|
35
|
+
创建带插件系统的 Axios 实例。插件按顺序执行请求/响应拦截。
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const http = createRequest({
|
|
39
|
+
baseURL: '/api', // 默认 '/api'
|
|
40
|
+
timeout: 30000, // 默认 30000
|
|
41
|
+
plugins: [
|
|
42
|
+
tokenPlugin({ getToken: () => localStorage.getItem('token') }),
|
|
43
|
+
unwrapPlugin(),
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 内置插件
|
|
49
|
+
|
|
50
|
+
| 插件 | 用途 | 参数 |
|
|
51
|
+
|------|------|------|
|
|
52
|
+
| tokenPlugin | 自动注入 Bearer token | `{ getToken: () => string \| null }` |
|
|
53
|
+
| headerPlugin | 注入自定义请求头 | `{ name: string, getValue: () => string \| null }` |
|
|
54
|
+
| unwrapPlugin | 解包 `{ code, data, message }` 响应,code !== 200 时 reject | 无参数 |
|
|
55
|
+
| refreshTokenPlugin | 401 自动刷新 token 并重试 | `{ getRefreshToken, refreshFn, onRefreshFail, isLoginUrl? }` |
|
|
56
|
+
|
|
57
|
+
### RequestPlugin 接口
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
interface RequestPlugin {
|
|
61
|
+
onRequest?: (config: InternalAxiosRequestConfig) => config | Promise<config>
|
|
62
|
+
onResponse?: (response: AxiosResponse) => response | Promise<response>
|
|
63
|
+
onResponseError?: (error: AxiosError) => any
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### refreshTokenPlugin 详解
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
refreshTokenPlugin({
|
|
71
|
+
getRefreshToken: () => localStorage.getItem('refreshToken'),
|
|
72
|
+
refreshFn: async (refreshToken) => {
|
|
73
|
+
// 调用刷新接口,返回新 access token
|
|
74
|
+
const { data } = await http.post('/auth/refresh', { refreshToken })
|
|
75
|
+
localStorage.setItem('token', data.token)
|
|
76
|
+
return data.token
|
|
77
|
+
},
|
|
78
|
+
onRefreshFail: () => {
|
|
79
|
+
// 刷新失败,跳转登录
|
|
80
|
+
localStorage.clear()
|
|
81
|
+
window.location.href = '/login'
|
|
82
|
+
},
|
|
83
|
+
isLoginUrl: (url) => url.includes('/auth/login'), // 可选,默认检测 /auth/login
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## storage — localStorage 封装
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { useStorage, storage } from '@danweiyuan/core'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### useStorage(key, defaultValue) — 响应式
|
|
96
|
+
|
|
97
|
+
返回 Vue `Ref<T>`,自动 JSON 序列化,深度 watch 自动同步。设为 null/undefined 时自动 removeItem。
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const token = useStorage<string>('token', '')
|
|
101
|
+
const config = useStorage<{ theme: string }>('config', { theme: 'light' })
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### storage — 静态工具对象
|
|
105
|
+
|
|
106
|
+
| 方法 | 签名 | 说明 |
|
|
107
|
+
|------|------|------|
|
|
108
|
+
| get | `get<T>(key, defaultValue?): T \| undefined` | JSON 解析失败时返回原始字符串 |
|
|
109
|
+
| set | `set(key, value): void` | JSON.stringify 后存储 |
|
|
110
|
+
| remove | `remove(key): void` | 删除指定 key |
|
|
111
|
+
| clear | `clear(): void` | 清空全部 |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## validators — 校验函数
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { isPhone, isEmail, isIdCard, isUrl, isRequired, minLength, maxLength } from '@danweiyuan/core'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| 函数 | 签名 | 规则 |
|
|
122
|
+
|------|------|------|
|
|
123
|
+
| isPhone | `(value: string) => boolean` | 中国手机号 1[3-9]开头 11 位 |
|
|
124
|
+
| isEmail | `(value: string) => boolean` | 基础邮箱格式 |
|
|
125
|
+
| isIdCard | `(value: string) => boolean` | 18 位身份证(末位可为 X/x) |
|
|
126
|
+
| isUrl | `(value: string) => boolean` | 使用 `new URL()` 校验 |
|
|
127
|
+
| isRequired | `(value: any) => boolean` | 非 null/undefined/空字符串/空数组。0 和 false 视为有效 |
|
|
128
|
+
| minLength | `(value: string, min: number) => boolean` | 最小长度 |
|
|
129
|
+
| maxLength | `(value: string, max: number) => boolean` | 最大长度 |
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## date — 日期格式化
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { formatDate, formatDateTime, formatTime, formatRelativeTime } from '@danweiyuan/core'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
所有函数接收 `string | number | Date` 类型输入。
|
|
140
|
+
|
|
141
|
+
| 函数 | 输出格式 | 示例 |
|
|
142
|
+
|------|---------|------|
|
|
143
|
+
| formatDate | `YYYY-MM-DD` | `2025-03-15` |
|
|
144
|
+
| formatDateTime | `YYYY-MM-DD HH:mm:ss` | `2025-03-15 14:30:00` |
|
|
145
|
+
| formatTime | `HH:mm` | `14:30` |
|
|
146
|
+
| formatRelativeTime | 中文相对时间 | `刚刚` / `5 分钟前` / `3 小时前` / `7 天前` / 超过 30 天返回 `toLocaleDateString('zh-CN')` |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## hooks — Vue Composables
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { useDebounce, useClickOutside, useEventListener } from '@danweiyuan/core'
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### useDebounce(value, delay?)
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const search = ref('')
|
|
160
|
+
const debouncedSearch = useDebounce(search, 300) // 默认 300ms
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
返回防抖后的 `Ref<T>`,仅在源值停止变化 delay 毫秒后更新。
|
|
164
|
+
|
|
165
|
+
### useClickOutside(target, handler)
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const popoverRef = ref<HTMLElement | null>(null)
|
|
169
|
+
useClickOutside(popoverRef, () => { isOpen.value = false })
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
点击 target 元素外部时触发 handler。自动在 onMounted/onBeforeUnmount 绑定/解绑。
|
|
173
|
+
|
|
174
|
+
### useEventListener(target, event, handler, options?)
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
useEventListener(window, 'resize', onResize)
|
|
178
|
+
useEventListener(elementRef, 'scroll', onScroll, { passive: true })
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
target 可以是 `EventTarget` 或 `Ref<EventTarget>`。自动在 onMounted/onBeforeUnmount 绑定/解绑。
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: danweiyuan-base
|
|
3
|
+
description: "danweiyuan-base FastAPI 后端基础设施速查。触发条件:使用 FastAPI 构建后端、配置数据库/Redis/JWT/异常处理时。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# danweiyuan-base 后端基础设施速查
|
|
7
|
+
|
|
8
|
+
FastAPI 项目基础设施包,Python 3.11+,全异步。8 个扁平模块,无子包。
|
|
9
|
+
|
|
10
|
+
## 安装
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install danweiyuan-base
|
|
14
|
+
# 或
|
|
15
|
+
uv add danweiyuan-base
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from danweiyuan_base.config import BaseSettings
|
|
20
|
+
from danweiyuan_base.database import Base, TimestampMixin, create_async_engine_factory, create_session_factory
|
|
21
|
+
from danweiyuan_base.security import hash_password, verify_password, create_token, decode_token
|
|
22
|
+
from danweiyuan_base.exceptions import NotFoundError, BusinessError, register_exception_handlers
|
|
23
|
+
from danweiyuan_base.response import success, fail, paginated
|
|
24
|
+
from danweiyuan_base.pagination import PaginationParams, paginate
|
|
25
|
+
from danweiyuan_base.cache import configure as configure_redis, get_redis, close_redis
|
|
26
|
+
from danweiyuan_base.dependencies import create_get_db
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 查阅源码
|
|
30
|
+
|
|
31
|
+
每个模块在 `backend/src/danweiyuan_base/{module}.py`,单文件设计可直接阅读。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## config — Pydantic Settings
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from danweiyuan_base.config import BaseSettings
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
子类化后使用,从 `.env` 或环境变量读取配置。
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
class Settings(BaseSettings):
|
|
45
|
+
app_name: str = "My API"
|
|
46
|
+
# 继承的必填字段:database_url, redis_url, secret_key
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
50
|
+
|------|------|--------|------|
|
|
51
|
+
| database_url | str | **必填** | 异步数据库 URL |
|
|
52
|
+
| redis_url | str | **必填** | Redis URL |
|
|
53
|
+
| secret_key | str | **必填** | JWT 签名密钥 |
|
|
54
|
+
| jwt_algorithm | str | `"HS256"` | JWT 算法 |
|
|
55
|
+
| access_token_expire_minutes | int | `30` | Token 过期分钟 |
|
|
56
|
+
| debug | bool | `False` | 调试模式 |
|
|
57
|
+
| allowed_origins | list[str] | `[]` | CORS 允许域名 |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## database — 异步 SQLAlchemy
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from danweiyuan_base.database import Base, TimestampMixin, create_async_engine_factory, create_session_factory
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 模型定义
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
71
|
+
from sqlalchemy import String
|
|
72
|
+
|
|
73
|
+
class User(Base, TimestampMixin):
|
|
74
|
+
__tablename__ = "users"
|
|
75
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
76
|
+
name: Mapped[str] = mapped_column(String(50))
|
|
77
|
+
# TimestampMixin 自动添加 created_at, updated_at
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 引擎和会话
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
engine = create_async_engine_factory(settings.database_url, echo=settings.debug)
|
|
84
|
+
# 参数: database_url, pool_size=5, max_overflow=10, echo=False
|
|
85
|
+
# SQLite 自动跳过连接池参数
|
|
86
|
+
|
|
87
|
+
session_factory = create_session_factory(engine)
|
|
88
|
+
# 返回 async_sessionmaker[AsyncSession],expire_on_commit=False
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## dependencies — FastAPI 依赖注入
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from danweiyuan_base.dependencies import create_get_db
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### create_get_db(session_factory)
|
|
100
|
+
|
|
101
|
+
生成 `get_db` FastAPI Depends 函数,自动管理会话生命周期。
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
get_db = create_get_db(session_factory)
|
|
105
|
+
|
|
106
|
+
@router.get("/users/{user_id}")
|
|
107
|
+
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
|
|
108
|
+
...
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## security — JWT + bcrypt
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from danweiyuan_base.security import hash_password, verify_password, create_token, decode_token
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
所有函数无状态,密钥通过参数传入。
|
|
120
|
+
|
|
121
|
+
| 函数 | 签名 | 说明 |
|
|
122
|
+
|------|------|------|
|
|
123
|
+
| hash_password | `(password: str) -> str` | bcrypt 哈希 |
|
|
124
|
+
| verify_password | `(plain: str, hashed: str) -> bool` | 验证密码 |
|
|
125
|
+
| create_token | `(data: dict, secret: str, expires_minutes: int, algorithm="HS256") -> str` | 创建 JWT,自动添加 exp |
|
|
126
|
+
| decode_token | `(token: str, secret: str, algorithm="HS256") -> dict \| None` | 解码 JWT,失败返回 None |
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
hashed = hash_password("my-password")
|
|
130
|
+
is_valid = verify_password("my-password", hashed)
|
|
131
|
+
|
|
132
|
+
token = create_token({"sub": str(user.id)}, settings.secret_key, settings.access_token_expire_minutes)
|
|
133
|
+
payload = decode_token(token, settings.secret_key) # {"sub": "1", "exp": ...} 或 None
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## exceptions — 异常体系
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from danweiyuan_base.exceptions import (
|
|
142
|
+
AppError, NotFoundError, BusinessError, PermissionDeniedError, AuthenticationError,
|
|
143
|
+
register_exception_handlers,
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 异常层级
|
|
148
|
+
|
|
149
|
+
| 异常类 | HTTP 状态码 | code | message |
|
|
150
|
+
|--------|-----------|------|---------|
|
|
151
|
+
| AppError(message, code) | — | 自定义 | 自定义 |
|
|
152
|
+
| NotFoundError(resource) | 404 | `NOT_FOUND` | `{resource}不存在` |
|
|
153
|
+
| BusinessError(message, code) | 422 | 自定义 | 自定义 |
|
|
154
|
+
| PermissionDeniedError() | 403 | `PERMISSION_DENIED` | `权限不足` |
|
|
155
|
+
| AuthenticationError() | 401 | `AUTHENTICATION_FAILED` | `认证失败` |
|
|
156
|
+
|
|
157
|
+
### 注册到 FastAPI
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
app = FastAPI()
|
|
161
|
+
register_exception_handlers(app)
|
|
162
|
+
# 之后在 service 层直接 raise NotFoundError("用户") 即可返回 404
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
响应格式统一为 `{"code": "ERROR_CODE", "message": "描述"}`。
|
|
166
|
+
|
|
167
|
+
### 使用原则
|
|
168
|
+
|
|
169
|
+
- **service 层**抛业务异常(NotFoundError, BusinessError 等)
|
|
170
|
+
- **router 层**不写 try/except,由 register_exception_handlers 统一处理
|
|
171
|
+
- **禁止**在 service 层抛 HTTPException
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## response — 统一响应
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from danweiyuan_base.response import success, fail, paginated
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| 函数 | 签名 | 返回结构 |
|
|
182
|
+
|------|------|---------|
|
|
183
|
+
| success | `(data=None, message="success") -> dict` | `{"code": 200, "message": ..., "data": ..., "timestamp": ...}` |
|
|
184
|
+
| fail | `(code=400, message="fail") -> dict` | `{"code": ..., "message": ..., "data": None, "timestamp": ...}` |
|
|
185
|
+
| paginated | `(items, total, page, page_size) -> dict` | success 包装 `{"items": [...], "total": N, "page": N, "page_size": N}` |
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
@router.get("/users/{user_id}")
|
|
189
|
+
async def get_user(user_id: int, service=Depends(get_user_service)):
|
|
190
|
+
user = await service.get_by_id(user_id)
|
|
191
|
+
return success(data=UserResponse.model_validate(user))
|
|
192
|
+
|
|
193
|
+
@router.get("/users")
|
|
194
|
+
async def list_users(params: PaginationParams = Depends(), service=Depends(get_user_service)):
|
|
195
|
+
items, total = await service.list(params)
|
|
196
|
+
return paginated(items, total, params.page, params.page_size)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## pagination — 分页工具
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from danweiyuan_base.pagination import PaginationParams, paginate, OffsetLimit
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### PaginationParams
|
|
208
|
+
|
|
209
|
+
FastAPI Depends 注入的查询参数模型。
|
|
210
|
+
|
|
211
|
+
| 字段 | 类型 | 默认 | 约束 |
|
|
212
|
+
|------|------|------|------|
|
|
213
|
+
| page | int | 1 | >= 1 |
|
|
214
|
+
| page_size | int | 20 | 1 ~ 100 |
|
|
215
|
+
|
|
216
|
+
### paginate(page, page_size) -> OffsetLimit
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
offset_limit = paginate(params.page, params.page_size)
|
|
220
|
+
# OffsetLimit(offset=0, limit=20) — 可直接用于 SQLAlchemy .offset().limit()
|
|
221
|
+
stmt = select(User).offset(offset_limit.offset).limit(offset_limit.limit)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## cache — 异步 Redis
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from danweiyuan_base import cache
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
| 函数 | 签名 | 说明 |
|
|
233
|
+
|------|------|------|
|
|
234
|
+
| cache.configure | `(redis_url: str) -> None` | 设置 Redis URL(启动时调用一次) |
|
|
235
|
+
| cache.get_redis | `async () -> Redis` | 获取/创建共享连接(未 configure 时抛 RuntimeError) |
|
|
236
|
+
| cache.close_redis | `async () -> None` | 关闭连接(安全幂等) |
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
# lifespan 中初始化
|
|
240
|
+
@asynccontextmanager
|
|
241
|
+
async def lifespan(app: FastAPI):
|
|
242
|
+
cache.configure(settings.redis_url)
|
|
243
|
+
yield
|
|
244
|
+
await cache.close_redis()
|
|
245
|
+
|
|
246
|
+
# 业务中使用
|
|
247
|
+
redis = await cache.get_redis()
|
|
248
|
+
await redis.set("key", "value", ex=3600)
|
|
249
|
+
val = await redis.get("key")
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 典型项目接入流程
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
# config.py
|
|
258
|
+
from danweiyuan_base.config import BaseSettings
|
|
259
|
+
|
|
260
|
+
class Settings(BaseSettings):
|
|
261
|
+
app_name: str = "My API"
|
|
262
|
+
|
|
263
|
+
settings = Settings()
|
|
264
|
+
|
|
265
|
+
# database.py
|
|
266
|
+
from danweiyuan_base.database import Base, create_async_engine_factory, create_session_factory
|
|
267
|
+
from danweiyuan_base.dependencies import create_get_db
|
|
268
|
+
|
|
269
|
+
engine = create_async_engine_factory(settings.database_url)
|
|
270
|
+
session_factory = create_session_factory(engine)
|
|
271
|
+
get_db = create_get_db(session_factory)
|
|
272
|
+
|
|
273
|
+
# main.py
|
|
274
|
+
from danweiyuan_base.exceptions import register_exception_handlers
|
|
275
|
+
from danweiyuan_base import cache
|
|
276
|
+
|
|
277
|
+
app = FastAPI(lifespan=lifespan)
|
|
278
|
+
register_exception_handlers(app)
|
|
279
|
+
```
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: eui
|
|
3
|
+
description: "@danweiyuan/eui Vue 3 组件库速查。触发条件:使用 Vue 3 构建页面、选择 UI 组件、查询 eui 组件 API 时。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @danweiyuan/eui 组件速查
|
|
7
|
+
|
|
8
|
+
基于 Reka-ui 原语层 + shadcn-vue 设计风格 + Element Plus 式 API 的 Vue 3 组件库。
|
|
9
|
+
|
|
10
|
+
## 安装与引入
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add @danweiyuan/eui
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// 按需导入(推荐)
|
|
18
|
+
import { EButton, EInput, ETable } from '@danweiyuan/eui'
|
|
19
|
+
|
|
20
|
+
// 全局注册(Vue plugin)
|
|
21
|
+
import EUI from '@danweiyuan/eui'
|
|
22
|
+
app.use(EUI)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// 主题 CSS(必须引入)
|
|
27
|
+
import '@danweiyuan/eui/theme' // 基础样式 + tokens
|
|
28
|
+
import '@danweiyuan/eui/theme/dark' // 暗色模式(可选)
|
|
29
|
+
import '@danweiyuan/eui/theme/tokens' // 仅 design tokens(可选)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 命名约定
|
|
33
|
+
|
|
34
|
+
- 组件前缀 `E`:`EButton`, `EInput`, `EDialog`
|
|
35
|
+
- Props 类型:`E{Name}Props`(如 `EButtonProps`)
|
|
36
|
+
- Events 类型:`E{Name}Emits`(如 `EInputEmits`)
|
|
37
|
+
- CVA 变体:`{name}Variants`(如 `buttonVariants`)
|
|
38
|
+
- 样式合并工具:`cn()` = clsx + tailwind-merge
|
|
39
|
+
- 尺寸统一类型:`Size = 'sm' | 'default' | 'lg'`
|
|
40
|
+
|
|
41
|
+
## 查阅完整 API
|
|
42
|
+
|
|
43
|
+
每个组件的完整 props/slots/events 定义在源码中:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
frontend/eui/src/components/{component-name}/types.ts
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
需要具体某个组件的详细 API 时,直接读取对应的 `types.ts` 文件。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 组件索引
|
|
54
|
+
|
|
55
|
+
### 基础组件
|
|
56
|
+
|
|
57
|
+
| 组件 | 用途 | 关键 Props |
|
|
58
|
+
|------|------|-----------|
|
|
59
|
+
| EButton | 按钮 | variant(default/destructive/outline/secondary/ghost/link), size(default/sm/lg/icon), loading, disabled |
|
|
60
|
+
| EBadge | 标签徽章 | variant(default/secondary/destructive/outline) |
|
|
61
|
+
| EAlert | 警告提示 | variant(default/destructive), title, description, closable |
|
|
62
|
+
| ECard | 卡片容器 | title, description |
|
|
63
|
+
| ELabel | 标签文本 | for |
|
|
64
|
+
| ESeparator | 分隔线 | orientation(horizontal/vertical) |
|
|
65
|
+
| ETypography | 排版文本 | variant, as |
|
|
66
|
+
| EKbd | 键盘按键展示 | — |
|
|
67
|
+
| ESpinner | 加载动画 | size(sm/default/lg) |
|
|
68
|
+
| EProgress | 进度条 | modelValue(0-100) |
|
|
69
|
+
| ESkeleton | 骨架屏 | — |
|
|
70
|
+
| EEmpty | 空状态 | title, description, icon |
|
|
71
|
+
| EAvatar | 头像 | src, alt, fallback, size |
|
|
72
|
+
| EImage | 图片 | src, alt, fit, lazy, fallback, preview |
|
|
73
|
+
|
|
74
|
+
### 表单组件
|
|
75
|
+
|
|
76
|
+
| 组件 | 用途 | 关键 Props |
|
|
77
|
+
|------|------|-----------|
|
|
78
|
+
| EInput | 输入框 | v-model, type, placeholder, size, clearable, showPassword, disabled, readonly, maxlength |
|
|
79
|
+
| ETextarea | 多行输入 | v-model, placeholder, rows, autoResize, maxlength, disabled |
|
|
80
|
+
| ENumberField | 数字输入 | v-model, min, max, step, size, disabled |
|
|
81
|
+
| ESelect | 下拉选择 | v-model, options(Option[]), placeholder, clearable, size, disabled |
|
|
82
|
+
| ENativeSelect | 原生 select | v-model, options(SelectOption[]), placeholder, disabled |
|
|
83
|
+
| ECombobox | 组合框 | v-model, options(Option[]), placeholder, emptyText, disabled |
|
|
84
|
+
| EAutocomplete | 自动补全 | v-model, fetchSuggestions(async fn), debounce, placeholder |
|
|
85
|
+
| ECheckbox | 复选框 | v-model, label, disabled, indeterminate |
|
|
86
|
+
| ERadio | 单选组 | v-model, options(Option[]), direction(horizontal/vertical), disabled |
|
|
87
|
+
| ESwitch | 开关 | v-model, label, size, disabled |
|
|
88
|
+
| ETagsInput | 标签输入 | v-model(string[]), placeholder, max, size, disabled |
|
|
89
|
+
| EDatePicker | 日期选择 | v-model, placeholder, format, clearable, disabled |
|
|
90
|
+
| ETimePicker | 时间选择 | v-model, placeholder, format, hourStep, minuteStep, disabled |
|
|
91
|
+
| EColorPicker | 颜色选择 | v-model, presets(string[]), showAlpha, disabled |
|
|
92
|
+
| ERate | 评分 | v-model, max, allowHalf, showText, disabled |
|
|
93
|
+
| ESlider | 滑块 | v-model(number[]), min, max, step, orientation, disabled |
|
|
94
|
+
| ECascader | 级联选择 | v-model, options(CascaderOption[]), placeholder, filterable, clearable |
|
|
95
|
+
| ETreeSelect | 树选择 | v-model, data(TreeNode[]), placeholder, multiple, checkable, disabled |
|
|
96
|
+
| EMention | @提及输入 | v-model, options(MentionOption[]), prefix, loading, placeholder |
|
|
97
|
+
| EInputOTP | OTP 验证码 | v-model, length, disabled, placeholder |
|
|
98
|
+
| EPinInput | PIN 输入 | v-model(string[]), length, mask, otp, type(text/number) |
|
|
99
|
+
| EUpload | 文件上传 | v-model(UploadFile[]), action, accept, multiple, limit, listType(text/picture/picture-card), drag |
|
|
100
|
+
| ETransfer | 穿梭框 | v-model, data(TransferItem[]), filterable, titles([string, string]) |
|
|
101
|
+
| EForm | 表单容器 | model, rules(Zod/FormRule[]), labelWidth, labelPosition(left/right/top), size, disabled |
|
|
102
|
+
| EFormItem | 表单项 | (配合 EForm 使用) |
|
|
103
|
+
| EField | 字段布局 | orientation(vertical/horizontal/responsive), invalid |
|
|
104
|
+
| EInputGroup | 输入组 | 子组件 EInputGroupAddon(align) |
|
|
105
|
+
|
|
106
|
+
### 数据展示
|
|
107
|
+
|
|
108
|
+
| 组件 | 用途 | 关键 Props |
|
|
109
|
+
|------|------|-----------|
|
|
110
|
+
| ETable | 表格 | data, columns(TableColumn[]), loading, rowKey, striped, bordered, selectable, selectedKeys |
|
|
111
|
+
| EVirtualTable | 虚拟滚动表格 | columns(VirtualTableColumn[]), data, height, estimateRowHeight, rowKey, loading |
|
|
112
|
+
| EDescriptions | 描述列表 | items(DescriptionItem[]), columns, bordered, title |
|
|
113
|
+
| ETimeline | 时间线 | items(TimelineItem[]), reverse |
|
|
114
|
+
| EStatistic | 统计数值 | title, value, prefix, suffix, precision |
|
|
115
|
+
| ETree | 树形控件 | data(TreeNode[]), v-model, checkable, expandedKeys, defaultExpandAll |
|
|
116
|
+
| ECalendar | 日历 | v-model, multiple, disabled, locale, minValue, maxValue |
|
|
117
|
+
| ERangeCalendar | 日期范围日历 | v-model({start, end}), disabled, locale, minValue, maxValue |
|
|
118
|
+
| EPagination | 分页 | v-model, total, pageSize, showSizeChanger, pageSizes, showTotal |
|
|
119
|
+
| EBreadcrumb | 面包屑 | items(BreadcrumbItem[]), separator |
|
|
120
|
+
|
|
121
|
+
### 弹层与反馈
|
|
122
|
+
|
|
123
|
+
| 组件 | 用途 | 关键 Props |
|
|
124
|
+
|------|------|-----------|
|
|
125
|
+
| EDialog | 对话框 | v-model:open, title, description, showClose, maxWidth |
|
|
126
|
+
| EDrawer | 抽屉 | v-model:open, title, description, direction(top/right/bottom/left), showClose |
|
|
127
|
+
| ESheet | 侧边面板 | v-model:open, title, description, side(top/right/bottom/left), showClose |
|
|
128
|
+
| EAlertDialog | 确认对话框 | v-model:open, title, description, confirmText, cancelText |
|
|
129
|
+
| ETooltip | 提示气泡 | content, side, sideOffset, delayDuration, disabled |
|
|
130
|
+
| EPopover | 弹出卡片 | v-model:open, side, sideOffset, align |
|
|
131
|
+
| EHoverCard | 悬停卡片 | openDelay, closeDelay |
|
|
132
|
+
| EToast | 轻提示 | position, expand, richColors(配合 `toast()` 函数使用) |
|
|
133
|
+
| EContextMenu | 右键菜单 | items(ContextMenuItem[]) |
|
|
134
|
+
|
|
135
|
+
### 导航与布局
|
|
136
|
+
|
|
137
|
+
| 组件 | 用途 | 关键 Props |
|
|
138
|
+
|------|------|-----------|
|
|
139
|
+
| ETabs | 标签页 | v-model, items(TabItem[]) |
|
|
140
|
+
| EMenu | 侧边菜单 | v-model, items(MenuItem[]), collapsed |
|
|
141
|
+
| EDropdown | 下拉菜单 | items(DropdownMenuItem[]), side, align, sideOffset |
|
|
142
|
+
| EAccordion | 手风琴 | v-model, items(AccordionItemOption[]), multiple, collapsible |
|
|
143
|
+
| ECollapsible | 折叠面板 | v-model(boolean), disabled |
|
|
144
|
+
| EStepper | 步骤条 | v-model, items(StepperItem[]), direction(horizontal/vertical) |
|
|
145
|
+
| ECarousel | 轮播 | loop, direction, showArrows, showDots, autoplay, interval。子组件 ECarouselItem |
|
|
146
|
+
| EScrollArea | 自定义滚动区 | type(auto/always/scroll/hover) |
|
|
147
|
+
| EInfiniteScroll | 无限滚动 | loading, disabled, distance |
|
|
148
|
+
|
|
149
|
+
### 复合组件
|
|
150
|
+
|
|
151
|
+
以下组件由多个子组件组合使用,通过 `provide/inject` 共享状态:
|
|
152
|
+
|
|
153
|
+
| 组件 | 子组件 | 用途 |
|
|
154
|
+
|------|--------|------|
|
|
155
|
+
| ESidebar | ESidebarProvider, ESidebar, ESidebarTrigger, ESidebarContent, ESidebarHeader, ESidebarFooter, ESidebarGroup, ESidebarGroupLabel, ESidebarGroupContent, ESidebarMenu, ESidebarMenuItem, ESidebarMenuButton | 侧边栏。Provider(defaultOpen, v-model:open) |
|
|
156
|
+
| ECommand | ECommandInput, ECommandList, ECommandEmpty, ECommandGroup, ECommandItem, ECommandSeparator, ECommandShortcut | 命令面板(配合 useCommand) |
|
|
157
|
+
| ENavigationMenu | ENavigationMenuList, ENavigationMenuItem, ENavigationMenuTrigger, ENavigationMenuContent, ENavigationMenuLink, ENavigationMenuViewport | 导航菜单 |
|
|
158
|
+
| EMenubar | EMenubarMenu, EMenubarTrigger, EMenubarContent, EMenubarItem, EMenubarSeparator, EMenubarCheckboxItem, EMenubarRadioGroup, EMenubarRadioItem, EMenubarSub, EMenubarSubTrigger, EMenubarSubContent | 菜单栏 |
|
|
159
|
+
| EResizable | EResizablePanelGroup(direction), EResizablePanel(defaultSize, minSize, maxSize), EResizableHandle(withHandle) | 可调整大小面板 |
|
|
160
|
+
|
|
161
|
+
### 其他
|
|
162
|
+
|
|
163
|
+
| 组件 | 用途 | 关键 Props |
|
|
164
|
+
|------|------|-----------|
|
|
165
|
+
| EConfigProvider | 全局配置注入 | size, zIndex, locale(Record) |
|
|
166
|
+
| EToggle | 切换按钮 | v-model, variant(default/outline), size, disabled |
|
|
167
|
+
| EToggleGroup | 切换按钮组 | v-model, type(single/multiple), variant, size, disabled |
|
|
168
|
+
| EButtonGroup | 按钮组 | orientation(horizontal/vertical) |
|
|
169
|
+
| EItem | 列表项 | variant(default/outline/muted), size(default/sm), as |
|
|
170
|
+
| EAspectRatio | 宽高比容器 | ratio |
|
|
171
|
+
| EAffix | 固钉 | offset, position(top/bottom) |
|
|
172
|
+
| EWatermark | 水印 | content, fontSize, rotate, gap, opacity |
|
|
173
|
+
| EChartContainer | 图表容器 | config(ChartConfig), id |
|
|
174
|
+
|
|
175
|
+
### 业务组件
|
|
176
|
+
|
|
177
|
+
| 组件 | 用途 | 关键 Props |
|
|
178
|
+
|------|------|-----------|
|
|
179
|
+
| EDataPage | 数据列表页 | columns(TableColumn[]), fetchFn(async), searchable, pageSize。内置表格 + 分页 + 搜索 |
|
|
180
|
+
| EFormDialog | 表单弹窗 | v-model:open, title, size, loading, confirmText, cancelText |
|
|
181
|
+
| EConfirmDialog | 确认弹窗 | v-model:open, title, message, type(info/warning/error), confirmText, cancelText |
|
|
182
|
+
| EAdminLayout | 后台管理布局 | logo, title, menuItems(MenuItem[]), v-model:activeKey, v-model:collapsed, headerHeight, sidebarWidth |
|
|
183
|
+
| ELoginLayout | 登录页布局 | title, logo, background |
|
|
184
|
+
| EAIChat | AI 对话 | messages(ChatMessage[]), loading, placeholder |
|
|
185
|
+
| ETimetableGrid | 课程表/时间表 | data(TimetableItem[]), startHour, endHour, days |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Composables
|
|
190
|
+
|
|
191
|
+
| Composable | 用途 | 返回值 |
|
|
192
|
+
|------------|------|--------|
|
|
193
|
+
| useMessage() | 全局消息提示 | { success, warning, error, info } — 每个接收 string 或 MessageOptions |
|
|
194
|
+
| useNotification() | 通知 | { success, warning, error, info } — 接收 NotificationOptions(title, message, position, duration) |
|
|
195
|
+
| useMessageBox() | 对话确认 | { alert, confirm, prompt } — 返回 Promise<'confirm' \| 'cancel'> |
|
|
196
|
+
| useTheme() | 主题管理 | { isDark, theme, setTheme, toggleDark, colorTheme, setColorTheme } |
|
|
197
|
+
| useConfigProvider() | 读取全局配置 | { size, zIndex, locale } |
|
|
198
|
+
| useFormField() | 表单字段上下文 | { id, name, formItemId, valid, isDirty, isTouched, error }(需在 FormField 内使用) |
|
|
199
|
+
|
|
200
|
+
## 主题系统
|
|
201
|
+
|
|
202
|
+
- **Design Tokens**: CSS 自定义属性,定义在 `tokens.css`,前缀 `--`(如 `--primary`, `--background`)
|
|
203
|
+
- **暗色模式**: `dark.css`,通过 `<html class="dark">` 切换
|
|
204
|
+
- **色彩主题**: 7 种(neutral/blue/green/rose/orange/violet/slate),通过 `useTheme().setColorTheme()` 切换
|
|
205
|
+
- **样式工具**: 使用 `cn()` 合并 Tailwind 类名,自动解决冲突
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { cn } from '@danweiyuan/eui'
|
|
209
|
+
cn('px-2 py-1', isActive && 'bg-primary text-white', className)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## 通用类型
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
type Size = 'sm' | 'default' | 'lg'
|
|
216
|
+
|
|
217
|
+
interface Option {
|
|
218
|
+
label: string
|
|
219
|
+
value: string | number
|
|
220
|
+
disabled?: boolean
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interface GroupedOption {
|
|
224
|
+
label: string
|
|
225
|
+
options: Option[]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface MenuItem {
|
|
229
|
+
key: string
|
|
230
|
+
label: string
|
|
231
|
+
icon?: string
|
|
232
|
+
children?: MenuItem[]
|
|
233
|
+
disabled?: boolean
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface TableColumn<T = any> {
|
|
237
|
+
key: string
|
|
238
|
+
title: string
|
|
239
|
+
width?: number | string
|
|
240
|
+
minWidth?: number
|
|
241
|
+
sortable?: boolean
|
|
242
|
+
fixed?: 'left' | 'right'
|
|
243
|
+
align?: 'left' | 'center' | 'right'
|
|
244
|
+
render?: (row: T, index: number) => any
|
|
245
|
+
}
|
|
246
|
+
```
|