Major Changes: - Add StreamingService with OpenAI Compatible format - Upgrade Chat component V2 with Ant Design X integration - Implement AIA module with 12 intelligent agents - Update API routes to unified /api/v1 prefix - Update system documentation Backend (~1300 lines): - common/streaming: OpenAI Compatible adapter - modules/aia: 12 agents, conversation service, streaming integration - Update route versions (RVW, PKB to v1) Frontend (~3500 lines): - modules/aia: AgentHub + ChatWorkspace (100% prototype restoration) - shared/Chat: AIStreamChat, ThinkingBlock, useAIStream Hook - Update API endpoints to v1 Documentation: - AIA module status guide - Universal capabilities catalog - System overview updates - All module documentation sync Tested: Stream response verified, authentication working Status: AIA V2.0 core completed (85%)
950 lines
22 KiB
Markdown
950 lines
22 KiB
Markdown
# 隞<><E99A9E>閫<EFBFBD><E996AB>
|
||
|
||
> **<2A><>𧋦嚗?* v1.0
|
||
> **<2A>𥕦遣<F0A595A6>交<EFBFBD>嚗?* 2025-10-10
|
||
> **<2A><>鍂<EFBFBD><E98D82>凒嚗?* <20>滨垢嚗㇌eact/TypeScript嚗? <20>𡒊垢嚗𠃊ode.js/TypeScript嚗?
|
||
---
|
||
|
||
## <20><> <20>桀<EFBFBD>
|
||
|
||
1. [<EFBFBD>𡁶鍂閫<EFBFBD><EFBFBD>](#<23>𡁶鍂閫<E98D82><E996AB>)
|
||
2. [TypeScript閫<EFBFBD><EFBFBD>](#typescript閫<74><E996AB>)
|
||
3. [React閫<EFBFBD><EFBFBD>](#react閫<74><E996AB>)
|
||
4. [Node.js<6A>𡒊垢閫<E59EA2><E996AB>](#nodejs<6A>𡒊垢閫<E59EA2><E996AB>)
|
||
5. [<EFBFBD>賢<EFBFBD>閫<EFBFBD><EFBFBD>](#<23>賢<EFBFBD>閫<EFBFBD><E996AB>)
|
||
6. [瘜券<EFBFBD>閫<EFBFBD><EFBFBD>](#瘜券<E7989C>閫<EFBFBD><E996AB>)
|
||
7. [Git<EFBFBD>𣂷漱閫<EFBFBD><EFBFBD>](#git<69>𣂷漱閫<E6BCB1><E996AB>)
|
||
|
||
---
|
||
|
||
## <20><> 撟喳蝱<E596B3>賢<EFBFBD>雿輻鍂閫<E98D82><E996AB>嚗?025-11-16 <20>啣<EFBFBD>嚗?
|
||
> **潃?<3F>滩<EFBFBD><E6BBA9>鞟內**嚗𡁜像<F0A1819C>啣歇<E595A3>𣂷<EFBFBD>摰峕㟲<E5B395><E39FB2>抅蝖<E68A85>霈暹鴌<E69AB9>滚𦛚
|
||
> **霂衣<E99C82>閫<EFBFBD><E996AB>**嚗靀鈭穃<E988AD><E7A983>笔<EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8><EFBFBD>(./08-鈭穃<E988AD><E7A983>笔<EFBFBD><E7AC94>𤏸<EFBFBD><F0A48FB8>?md)
|
||
> **霂衣<E99C82><E8A1A3><EFBFBD>﹝**嚗靀撟喳蝱<E596B3>箇<EFBFBD>霈暹鴌閫<E9B48C><E996AB>](../09-<2D>嗆<EFBFBD>摰墧鴌/04-撟喳蝱<E596B3>箇<EFBFBD>霈暹鴌閫<E9B48C><E996AB>.md)
|
||
|
||
### 敹<>◆憭滨鍂<E6BBA8><E98D82>像<EFBFBD>唳<EFBFBD><E594B3>?
|
||
**銝𡁜𦛚璅∪<E79285>嚗㇁SL/AIA/PKB/DC蝑㚁<E89D91>蝳<EFBFBD>迫<EFBFBD>滚<EFBFBD>摰䂿緵隞乩<E99A9E><E4B9A9>蠘<EFBFBD>嚗?*
|
||
|
||
| <20>滚𦛚 | 撖澆<E69296><E6BE86>孵<EFBFBD> | <20>券<EFBFBD>?|
|
||
|------|---------|------|
|
||
| **摮睃<E691AE><E79D83>滚𦛚** | `import { storage } from '@/common/storage'` | <20><>辣銝𠹺<E98A9D>銝贝蝸 |
|
||
| **<EFBFBD>亙<EFBFBD>蝟餌<EFBFBD>** | `import { logger } from '@/common/logging'` | <20><><EFBFBD><EFBFBD>𡝗𠯫敹?|
|
||
| **撘<>郊隞餃𦛚** | `import { jobQueue } from '@/common/jobs'` | <20>踵𧒄<E8B8B5>港遙<E6B8AF>?|
|
||
| **蝻枏<E89DBB><E69E8F>滚𦛚** | `import { cache } from '@/common/cache'` | <20><><EFBFBD>撘讐<E69298>摮?|
|
||
| **<EFBFBD>唳旿摨?* | `import { prisma } from '@/config/database'` | <20>唳旿摨𤘪<E691A8>雿?|
|
||
| **LLM<4C>賢<EFBFBD>** | `import { LLMFactory } from '@/common/llm'` | LLM靚<4D>鍂 |
|
||
|
||
---
|
||
|
||
### <20>?甇<>&蝷箔<E89DB7>嚗帋蝙<E5B88B>典像<E585B8>唳<EFBFBD><E594B3>?
|
||
```typescript
|
||
// backend/src/modules/asl/services/literatureService.ts
|
||
import { storage } from '@/common/storage'
|
||
import { logger } from '@/common/logging'
|
||
import { jobQueue } from '@/common/jobs'
|
||
import { cache } from '@/common/cache'
|
||
import { prisma } from '@/config/database'
|
||
|
||
export class LiteratureService {
|
||
async uploadPDF(projectId: string, pdfBuffer: Buffer) {
|
||
// 1. 雿輻鍂撟喳蝱摮睃<E691AE><E79D83>滚𦛚
|
||
const key = `asl/projects/${projectId}/pdfs/${Date.now()}.pdf`
|
||
const url = await storage.upload(key, pdfBuffer)
|
||
|
||
// 2. 雿輻鍂撟喳蝱<E596B3>亙<EFBFBD>蝟餌<E89D9F>
|
||
logger.info('PDF uploaded', { projectId, url })
|
||
|
||
// 3. 雿輻鍂撟喳蝱<E596B3>唳旿摨? const literature = await prisma.aslLiterature.create({
|
||
data: { projectId, pdfUrl: url, pdfFileSize: pdfBuffer.length }
|
||
})
|
||
|
||
// 4. 雿輻鍂撟喳蝱蝻枏<E89DBB>
|
||
await cache.set(`literature:${literature.id}`, literature, 3600)
|
||
|
||
return literature
|
||
}
|
||
|
||
async startScreening(projectId: string, literatureIds: string[]) {
|
||
// 5. 雿輻鍂撟喳蝱撘<E89DB1>郊隞餃𦛚嚗<F0A69B9A>鵭<EFBFBD>園𡢿隞餃𦛚敹<F0A69B9A>◆撘<E29786>郊嚗? const job = await jobQueue.push('asl:screening', {
|
||
projectId,
|
||
literatureIds
|
||
})
|
||
|
||
logger.info('Screening job created', { jobId: job.id })
|
||
return { jobId: job.id } // 蝡见朖餈𥪜<E9A488>
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### <20>?<3F>躰秤蝷箔<E89DB7>嚗𡁻<E59A97>憭滚<E686AD><E6BB9A>啣像<E595A3>啗<EFBFBD><E59597>?
|
||
```typescript
|
||
// <20>?<3F>躰秤嚗𡁜銁銝𡁜𦛚璅∪<E79285>銝剛䌊撌勗<E6928C><E58B97>啣<EFBFBD><E595A3>?// backend/src/modules/asl/storage/LocalStorage.ts <20>?銝滚<E98A9D>霂亙<E99C82><E4BA99>剁<EFBFBD>
|
||
import fs from 'fs'
|
||
|
||
export class LocalStorage {
|
||
async upload(file: Buffer) {
|
||
await fs.writeFile('./uploads/file.pdf', file) // <20>?<3F>滚<EFBFBD>摰䂿緵
|
||
return '/uploads/file.pdf'
|
||
}
|
||
}
|
||
|
||
// <20>?<3F>躰秤嚗𡁜銁銝𡁜𦛚璅∪<E79285>銝剛䌊撌勗<E6928C><E58B97>唳𠯫敹?// backend/src/modules/asl/logger/logger.ts <20>?銝滚<E98A9D>霂亙<E99C82><E4BA99>剁<EFBFBD>
|
||
import winston from 'winston'
|
||
|
||
export const logger = winston.createLogger({...}) // <20>?<3F>滚<EFBFBD>摰䂿緵
|
||
|
||
// <20>?<3F>躰秤嚗𡁏<E59A97>甈⊥鰵撱箸㺭<E7AEB8>桀<EFBFBD>餈墧𦻖
|
||
import { PrismaClient } from '@prisma/client'
|
||
|
||
export function getUser() {
|
||
const prisma = new PrismaClient() // <20>?餈墧𦻖瘜<F0A6BB96><E7989C>
|
||
return prisma.user.findMany()
|
||
}
|
||
```
|
||
|
||
**銝箔<E98A9D>銋<EFBFBD><E98A8B>霂荔<E99C82>**
|
||
- <20>?<3F>滚<EFBFBD>隞<EFBFBD><E99A9E>嚗屸𠗕隞亦輕<E4BAA6>?- <20>?銝滚<E98A9D>璅∪<E79285>摰䂿緵銝滢<E98A9D><E6BBA2>?- <20>?<3F>䭾<EFBFBD>蝏煺<E89D8F><E785BA><EFBFBD>揢<EFBFBD>臬<EFBFBD>嚗<EFBFBD>𧋦<EFBFBD>?鈭𤑳垢嚗?- <20>?瘚芾晶撘<E699B6><E69298>烐𧒄<E78390>?- <20>?鈭𤑳垢<F0A491B3>函蔡隡𡁜仃韐伐<E99F90>Serverless<73>𣂼<EFBFBD>嚗?
|
||
---
|
||
|
||
### <20><>辣銝𠹺<E98A9D>閫<EFBFBD><E996AB>
|
||
|
||
```typescript
|
||
// <20>?甇<>&嚗帋蝙<E5B88B>典<EFBFBD><E585B8>冽𡂝鞊∪<E99E8A>
|
||
const url = await storage.upload('asl/pdf/123.pdf', buffer)
|
||
|
||
// <20>?<3F>躰秤嚗𡁶凒<F0A181B6>交<EFBFBD>雿𨀣<E99BBF>隞嗥頂蝏?fs.writeFileSync('./uploads/123.pdf', buffer) // Serverless摰孵膥<E5ADB5>滚鍳隡帋腺憭?
|
||
// <20>?<3F>躰秤嚗𡁶′蝻𣇉<E89DBB>摮睃<E691AE>頝臬<E9A09D>
|
||
const filePath = 'D:/uploads/123.pdf' // Windows頝臬<E9A09D>嚗𥴰inux<75>䭾<EFBFBD>餈鞱<E9A488>
|
||
```
|
||
|
||
---
|
||
|
||
### 撘<>郊隞餃𦛚閫<F0A69B9A><E996AB>
|
||
|
||
```typescript
|
||
// <20>?甇<>&嚗𡁻鵭<F0A181BB>園𡢿隞餃𦛚嚗?10蝘𡜐<E89D98>敹<EFBFBD>◆撘<E29786>郊憭<E9838A><E686AD>
|
||
app.post('/screening/start', async (req, res) => {
|
||
const job = await jobQueue.push('asl:screening', data)
|
||
res.send({ jobId: job.id }) // 蝡见朖餈𥪜<E9A488>嚗䔶<E59A97>蝑匧<E89D91>摰峕<E691B0>
|
||
})
|
||
|
||
// <20>亥砭餈𥕦漲
|
||
app.get('/screening/jobs/:id', async (req, res) => {
|
||
const job = await jobQueue.getJob(req.params.id)
|
||
res.send({ status: job.status, progress: job.progress })
|
||
})
|
||
|
||
// <20>?<3F>躰秤嚗𡁜<E59A97>甇亦<E79487>敺<EFBFBD>鵭<EFBFBD>園𡢿隞餃𦛚
|
||
app.post('/screening/start', async (req, res) => {
|
||
const results = await processAllLiteratures(data) // <20>航<EFBFBD><E888AA><EFBFBD>閬?0<><30><EFBFBD>
|
||
res.send({ results }) // Serverless 30蝘坿<E89D98><E59DBF>塚<EFBFBD>
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
### <20>唳旿摨栞<E691A8><E6A09E>亥<EFBFBD><E4BAA5>?
|
||
```typescript
|
||
// <20>?甇<>&嚗帋蝙<E5B88B>典<EFBFBD>撅<EFBFBD>Prisma摰硺<E691B0>
|
||
import { prisma } from '@/config/database'
|
||
|
||
export async function getUsers() {
|
||
return await prisma.user.findMany()
|
||
}
|
||
|
||
// <20>?<3F>躰秤嚗𡁏<E59A97>甈⊥鰵撱箏<E692B1>靘?export async function getUsers() {
|
||
const prisma = new PrismaClient() // 餈墧𦻖<E5A2A7>啗<EFBFBD>堒偷嚗? return await prisma.user.findMany()
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### <20>亙<EFBFBD>閫<EFBFBD><E996AB>
|
||
|
||
```typescript
|
||
// <20>?甇<>&嚗帋蝙<E5B88B>典像<E585B8>唳𠯫敹㛖頂蝏?import { logger } from '@/common/logging'
|
||
|
||
logger.info('Operation successful', { userId, action: 'upload' })
|
||
logger.error('Operation failed', { error: err.message, userId })
|
||
|
||
// <20>?<3F>躰秤嚗帋蝙<E5B88B>牢onsole.log
|
||
console.log('Operation successful') // <20>䭾<EFBFBD><E4ADBE><EFBFBD>葉<EFBFBD>園<EFBFBD>嚗屸𠗕隞交䰻霂?
|
||
// <20>?<3F>躰秤嚗𡁜<E59A97><F0A1819C>砍𧑐<E7A08D>亙<EFBFBD><E4BA99><EFBFBD>辣
|
||
fs.appendFileSync('./app.log', 'Operation successful') // Serverless銝齿𣈲<E9BDBF>?```
|
||
|
||
---
|
||
|
||
## <EFBFBD>𡁶鍂閫<EFBFBD><EFBFBD>
|
||
|
||
### 隞<EFBFBD><EFBFBD>憌擧聢
|
||
- <EFBFBD>?雿輻鍂ESLint<EFBFBD>釶rettier蝏煺<EFBFBD>隞<EFBFBD><EFBFBD>憌擧聢
|
||
- <EFBFBD>?蝻抵<EFBFBD>嚗?銝芰征<EFBFBD>?- <EFBFBD>?摮㛖泵銝莎<EFBFBD>隡睃<EFBFBD>雿輻鍂<EFBFBD>訫<EFBFBD><EFBFBD>?`'`
|
||
- <EFBFBD>?銵<EFBFBD>偏嚗帋<EFBFBD><EFBFBD>惩<EFBFBD><EFBFBD>瘀<EFBFBD><EFBFBD>日<EFBFBD>敹<EFBFBD><EFBFBD>嚗?- <EFBFBD>?<EFBFBD>閗<EFBFBD><EFBFBD><EFBFBD>憭折鵭摨佗<EFBFBD>100摮㛖泵
|
||
- <EFBFBD>?雿輻鍂撠暸<EFBFBD><EFBFBD>堒噡嚗<EFBFBD>笆鞊~<EFBFBD><EFBFBD>㺭蝏<EFBFBD><EFBFBD>
|
||
|
||
### <EFBFBD><EFBFBD>辣蝏<EFBFBD><EFBFBD>
|
||
- <EFBFBD>?銝<EFBFBD>銝芣<EFBFBD>隞嗡<EFBFBD>銝芰<EFBFBD>隞?蝐?- <EFBFBD>?<EFBFBD>詨<EFBFBD><EFBFBD><EFBFBD>辣<EFBFBD>曉銁<EFBFBD>䔶<EFBFBD><EFBFBD>桀<EFBFBD>
|
||
- <EFBFBD>?雿輻鍂barrel exports嚗ǎndex.ts嚗?- <EFBFBD>?瘚贝<EFBFBD><EFBFBD><EFBFBD>辣銝擧<EFBFBD><EFBFBD><EFBFBD>辣<EFBFBD>𣬚𤌍敶?
|
||
```
|
||
src/
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> components/
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Button/
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Button.tsx
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Button.test.tsx
|
||
<EFBFBD>? <20>? <20>鎿<EFBFBD><E98EBF><EFBFBD> Button.styles.ts
|
||
<EFBFBD>? <20>? <20>婙<EFBFBD><E5A999><EFBFBD> index.ts # export { Button } from './Button'
|
||
```
|
||
|
||
### 隞<><E99A9E>瘜券<E7989C>
|
||
- <20>?憭齿<E686AD><E9BDBF>餉<EFBFBD>敹<EFBFBD>◆瘜券<E7989C>
|
||
- <20>?<3F>砍<EFBFBD>API敹<49>◆瘜券<E7989C>
|
||
- <20>?<3F>踹<EFBFBD><E8B8B9>删鍂瘜券<E7989C>
|
||
- <20>?雿輻鍂JSDoc<6F>澆<EFBFBD>
|
||
|
||
---
|
||
|
||
## TypeScript閫<74><E996AB>
|
||
|
||
### 蝐餃<E89D90>摰帋<E691B0>
|
||
|
||
**<2A>?<3F>刻<EFBFBD>嚗?*
|
||
```typescript
|
||
// 雿輻鍂interface摰帋<E691B0>撖寡情蝏𤘪<E89D8F>
|
||
interface User {
|
||
id: string
|
||
email: string
|
||
name?: string
|
||
}
|
||
|
||
// 雿輻鍂type摰帋<E691B0><E5B88B>𥪜<EFBFBD>蝐餃<E89D90>
|
||
type Status = 'active' | 'inactive' | 'suspended'
|
||
|
||
// 雿輻鍂enum摰帋<E691B0>撣賊<E692A3><E8B38A><EFBFBD><EFBFBD>
|
||
enum UserRole {
|
||
USER = 'user',
|
||
ADMIN = 'admin',
|
||
}
|
||
```
|
||
|
||
**<EFBFBD>?<3F>踹<EFBFBD>嚗?*
|
||
```typescript
|
||
// 銝滩<E98A9D>雿輻鍂any
|
||
function process(data: any) { // <20>? // ...
|
||
}
|
||
|
||
// 摨磰砲<E7A3B0>𡒊&蝐餃<E89D90>
|
||
function process(data: ProcessData) { // <20>? // ...
|
||
}
|
||
```
|
||
|
||
### 蝐餃<E89D90>撖澆<E69296>撖澆枂
|
||
|
||
```typescript
|
||
// types.ts
|
||
export interface Project {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
}
|
||
|
||
export type ProjectStatus = 'active' | 'archived'
|
||
|
||
// project.service.ts
|
||
import type { Project, ProjectStatus } from './types'
|
||
```
|
||
|
||
### 銝交聢璅∪<E79285>
|
||
```json
|
||
// tsconfig.json
|
||
{
|
||
"compilerOptions": {
|
||
"strict": true,
|
||
"noImplicitAny": true,
|
||
"strictNullChecks": true,
|
||
"strictFunctionTypes": true
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## React閫<74><E996AB>
|
||
|
||
### 蝏<>辣摰帋<E691B0>
|
||
|
||
**<EFBFBD>?<3F>刻<EFBFBD>嚗𡁜遆<F0A1819C>啁<EFBFBD>隞?+ Hooks**
|
||
```tsx
|
||
import { useState } from 'react'
|
||
|
||
interface ButtonProps {
|
||
label: string
|
||
onClick: () => void
|
||
variant?: 'primary' | 'secondary'
|
||
disabled?: boolean
|
||
}
|
||
|
||
export function Button({
|
||
label,
|
||
onClick,
|
||
variant = 'primary',
|
||
disabled = false
|
||
}: ButtonProps) {
|
||
const [isLoading, setIsLoading] = useState(false)
|
||
|
||
const handleClick = async () => {
|
||
setIsLoading(true)
|
||
try {
|
||
await onClick()
|
||
} finally {
|
||
setIsLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<button
|
||
onClick={handleClick}
|
||
disabled={disabled || isLoading}
|
||
className={`btn btn-${variant}`}
|
||
>
|
||
{isLoading ? 'Loading...' : label}
|
||
</button>
|
||
)
|
||
}
|
||
```
|
||
|
||
**<EFBFBD>?<3F>踹<EFBFBD>嚗𡁶掩蝏<E68EA9>辣**
|
||
```tsx
|
||
// <20>日<EFBFBD><E697A5>厩鸌畾𢠃<E795BE>瘙<EFBFBD><E79899><EFBFBD>血<EFBFBD>銝滢蝙<E6BBA2>函掩蝏<E68EA9>辣
|
||
class Button extends React.Component { ... } // <20>?```
|
||
|
||
### Hooks閫<EFBFBD><EFBFBD>
|
||
|
||
**<EFBFBD>?<EFBFBD>刻<EFBFBD>嚗朞䌊摰帋<EFBFBD>Hooks**
|
||
```typescript
|
||
// useProjects.ts
|
||
import { useState, useEffect } from 'react'
|
||
import { projectService } from '@/services'
|
||
import type { Project } from '@/types'
|
||
|
||
export function useProjects() {
|
||
const [projects, setProjects] = useState<Project[]>([])
|
||
const [loading, setLoading] = useState(false)
|
||
const [error, setError] = useState<Error | null>(null)
|
||
|
||
useEffect(() => {
|
||
loadProjects()
|
||
}, [])
|
||
|
||
const loadProjects = async () => {
|
||
setLoading(true)
|
||
setError(null)
|
||
try {
|
||
const data = await projectService.getProjects()
|
||
setProjects(data)
|
||
} catch (err) {
|
||
setError(err as Error)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
return { projects, loading, error, reload: loadProjects }
|
||
}
|
||
|
||
// 雿輻鍂
|
||
function ProjectList() {
|
||
const { projects, loading, error } = useProjects()
|
||
|
||
if (loading) return <Loading />
|
||
if (error) return <Error message={error.message} />
|
||
|
||
return (
|
||
<ul>
|
||
{projects.map(project => (
|
||
<li key={project.id}>{project.name}</li>
|
||
))}
|
||
</ul>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 蝏<>辣蝏<E8BEA3><E89D8F>
|
||
|
||
```tsx
|
||
// <20>?<3F>刻<EFBFBD><E588BB><EFBFBD><EFBFBD>隞嗥<E99A9E><E597A5>?import { useState, useEffect, useMemo, useCallback } from 'react'
|
||
import { useNavigate } from 'react-router-dom'
|
||
import { SomeComponent } from '@/components'
|
||
import { useCustomHook } from '@/hooks'
|
||
import type { SomeType } from '@/types'
|
||
|
||
interface ComponentProps {
|
||
// props摰帋<E691B0>
|
||
}
|
||
|
||
export function Component({ prop1, prop2 }: ComponentProps) {
|
||
// 1. Hooks
|
||
const navigate = useNavigate()
|
||
const [state, setState] = useState()
|
||
const { data } = useCustomHook()
|
||
|
||
// 2. 瘣曄<E798A3><E69B84>嗆<EFBFBD><E59786><EFBFBD>useMemo嚗? const computedValue = useMemo(() => {
|
||
return heavyComputation(data)
|
||
}, [data])
|
||
|
||
// 3. 鈭衤辣憭<E8BEA3><E686AD>嚗óseCallback嚗? const handleClick = useCallback(() => {
|
||
// 憭<><E686AD><EFBFBD>餉<EFBFBD>
|
||
}, [])
|
||
|
||
// 4. Effects
|
||
useEffect(() => {
|
||
// <20>臭<EFBFBD><E887AD>? }, [])
|
||
|
||
// 5. <20>拇<EFBFBD>餈𥪜<E9A488>嚗𡿨oading/Error嚗? if (!data) return <Loading />
|
||
|
||
// 6. 皜脫<E79A9C>
|
||
return (
|
||
<div>
|
||
{/* JSX */}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### <20>∩辣皜脫<E79A9C>
|
||
|
||
**<EFBFBD>?<3F>刻<EFBFBD>嚗?*
|
||
```tsx
|
||
// 蝞<><E89D9E>閙辺隞塚<E99A9E>雿輻鍂 &&
|
||
{isLoggedIn && <UserMenu />}
|
||
|
||
// if-else嚗帋蝙<E5B88B>其<EFBFBD><E585B6><EFBFBD><EFBFBD>蝞㛖泵
|
||
{isLoggedIn ? <UserMenu /> : <LoginButton />}
|
||
|
||
// 憭𡁏辺隞塚<E99A9E><E5A19A>𣂼<EFBFBD>銝箏遆<E7AE8F>唳<EFBFBD>蝏<EFBFBD>辣
|
||
function renderContent() {
|
||
if (loading) return <Loading />
|
||
if (error) return <Error />
|
||
if (data.length === 0) return <Empty />
|
||
return <DataList data={data} />
|
||
}
|
||
|
||
return <div>{renderContent()}</div>
|
||
```
|
||
|
||
**<EFBFBD>?<3F>踹<EFBFBD>嚗?*
|
||
```tsx
|
||
// <20>踹<EFBFBD>憭齿<E686AD><E9BDBF><EFBFBD><EFBFBD>憟𦯀<E6869F><F0A6AF80><EFBFBD><EFBFBD>蝞㛖泵
|
||
{condition1 ? (
|
||
condition2 ? <A /> : <B />
|
||
) : (
|
||
condition3 ? <C /> : <D />
|
||
)} // <20>?<3F>曆誑<E69B86><E8AA91>圾
|
||
```
|
||
|
||
---
|
||
|
||
## Node.js<6A>𡒊垢閫<E59EA2><E996AB>
|
||
|
||
### <20><>辣蝏<E8BEA3><E89D8F>
|
||
|
||
```
|
||
backend/src/
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> routes/ # 頝舐眏摰帋<E691B0>
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> auth.routes.ts
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> project.routes.ts
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> services/ # 銝𡁜𦛚<F0A1819C>餉<EFBFBD>
|
||
<EFBFBD>? <20>鎿<EFBFBD><E98EBF><EFBFBD> auth.service.ts
|
||
<EFBFBD>? <20>婙<EFBFBD><E5A999><EFBFBD> project.service.ts
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> controllers/ # <20>批<EFBFBD><E689B9>剁<EFBFBD><E58981>舫<EFBFBD>㚁<EFBFBD>
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> models/ # Prisma璅∪<E79285>
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> utils/ # 撌亙<E6928C><E4BA99>賣㺭
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> config/ # <20>滨蔭<E6BBA8>㰘蝸
|
||
<EFBFBD>鎿<EFBFBD><EFBFBD><EFBFBD> types/ # 蝐餃<E89D90>摰帋<E691B0>
|
||
<EFBFBD>婙<EFBFBD><EFBFBD><EFBFBD> server.ts # <20>亙藁<E4BA99><E89781>辣
|
||
```
|
||
|
||
### 頝舐眏摰帋<E691B0>
|
||
|
||
```typescript
|
||
// routes/project.routes.ts
|
||
import { FastifyInstance } from 'fastify'
|
||
import { projectService } from '../services/project.service'
|
||
import { authMiddleware } from '../middleware/auth'
|
||
|
||
export async function projectRoutes(server: FastifyInstance) {
|
||
// <20>瑕<EFBFBD>憿寧𤌍<E5AFA7>𡑒”
|
||
server.get(
|
||
'/api/v1/projects',
|
||
{
|
||
preHandler: [authMiddleware],
|
||
schema: {
|
||
querystring: {
|
||
type: 'object',
|
||
properties: {
|
||
page: { type: 'number' },
|
||
pageSize: { type: 'number' },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
async (request, reply) => {
|
||
const { page = 1, pageSize = 20 } = request.query as any
|
||
const userId = request.user.id
|
||
|
||
const result = await projectService.getProjects(userId, {
|
||
page,
|
||
pageSize,
|
||
})
|
||
|
||
return reply.send({
|
||
success: true,
|
||
data: result,
|
||
})
|
||
}
|
||
)
|
||
|
||
// <20>𥕦遣憿寧𤌍
|
||
server.post(
|
||
'/api/v1/projects',
|
||
{
|
||
preHandler: [authMiddleware],
|
||
schema: {
|
||
body: {
|
||
type: 'object',
|
||
required: ['name', 'description'],
|
||
properties: {
|
||
name: { type: 'string', minLength: 1, maxLength: 200 },
|
||
description: { type: 'string', minLength: 1 },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
async (request, reply) => {
|
||
const userId = request.user.id
|
||
const data = request.body as CreateProjectDto
|
||
|
||
const project = await projectService.createProject(userId, data)
|
||
|
||
return reply.code(201).send({
|
||
success: true,
|
||
data: project,
|
||
})
|
||
}
|
||
)
|
||
}
|
||
```
|
||
|
||
### Service撅?
|
||
```typescript
|
||
// services/project.service.ts
|
||
import { prisma } from '../lib/prisma'
|
||
import type { CreateProjectDto, UpdateProjectDto } from '../types'
|
||
|
||
export class ProjectService {
|
||
/**
|
||
* <20>瑕<EFBFBD><E79195>冽<EFBFBD><E586BD><EFBFBD>★<EFBFBD>桀<EFBFBD>銵? */
|
||
async getProjects(userId: string, options: PaginationOptions) {
|
||
const { page, pageSize } = options
|
||
|
||
const [items, total] = await Promise.all([
|
||
prisma.project.findMany({
|
||
where: { userId },
|
||
skip: (page - 1) * pageSize,
|
||
take: pageSize,
|
||
orderBy: { createdAt: 'desc' },
|
||
}),
|
||
prisma.project.count({ where: { userId } }),
|
||
])
|
||
|
||
return {
|
||
items,
|
||
pagination: {
|
||
page,
|
||
pageSize,
|
||
total,
|
||
totalPages: Math.ceil(total / pageSize),
|
||
hasNext: page * pageSize < total,
|
||
hasPrev: page > 1,
|
||
},
|
||
}
|
||
}
|
||
|
||
/**
|
||
* <20>𥕦遣憿寧𤌍
|
||
*/
|
||
async createProject(userId: string, data: CreateProjectDto) {
|
||
return prisma.project.create({
|
||
data: {
|
||
userId,
|
||
name: data.name,
|
||
description: data.description,
|
||
},
|
||
})
|
||
}
|
||
|
||
/**
|
||
* <20>湔鰵憿寧𤌍
|
||
*/
|
||
async updateProject(
|
||
userId: string,
|
||
projectId: string,
|
||
data: UpdateProjectDto
|
||
) {
|
||
// 撉諹<E69289><E8ABB9><EFBFBD><EFBFBD>
|
||
const project = await prisma.project.findFirst({
|
||
where: { id: projectId, userId },
|
||
})
|
||
|
||
if (!project) {
|
||
throw new Error('Project not found or unauthorized')
|
||
}
|
||
|
||
return prisma.project.update({
|
||
where: { id: projectId },
|
||
data,
|
||
})
|
||
}
|
||
|
||
/**
|
||
* <20>𣳇膄憿寧𤌍
|
||
*/
|
||
async deleteProject(userId: string, projectId: string) {
|
||
// 撉諹<E69289><E8ABB9><EFBFBD><EFBFBD>
|
||
const project = await prisma.project.findFirst({
|
||
where: { id: projectId, userId },
|
||
})
|
||
|
||
if (!project) {
|
||
throw new Error('Project not found or unauthorized')
|
||
}
|
||
|
||
await prisma.project.delete({
|
||
where: { id: projectId },
|
||
})
|
||
}
|
||
}
|
||
|
||
export const projectService = new ProjectService()
|
||
```
|
||
|
||
### <20>躰秤憭<E7A7A4><E686AD>
|
||
|
||
```typescript
|
||
// utils/errors.ts
|
||
export class AppError extends Error {
|
||
constructor(
|
||
public code: string,
|
||
public message: string,
|
||
public statusCode: number = 400,
|
||
public details?: any
|
||
) {
|
||
super(message)
|
||
this.name = 'AppError'
|
||
}
|
||
}
|
||
|
||
export class ValidationError extends AppError {
|
||
constructor(message: string, details?: any) {
|
||
super('VALIDATION_ERROR', message, 422, details)
|
||
}
|
||
}
|
||
|
||
export class UnauthorizedError extends AppError {
|
||
constructor(message: string = 'Unauthorized') {
|
||
super('UNAUTHORIZED', message, 401)
|
||
}
|
||
}
|
||
|
||
export class NotFoundError extends AppError {
|
||
constructor(resource: string) {
|
||
super('NOT_FOUND', `${resource} not found`, 404)
|
||
}
|
||
}
|
||
|
||
// 雿輻鍂
|
||
async function getProject(id: string) {
|
||
const project = await prisma.project.findUnique({ where: { id } })
|
||
|
||
if (!project) {
|
||
throw new NotFoundError('Project')
|
||
}
|
||
|
||
return project
|
||
}
|
||
```
|
||
|
||
### <20>躰秤憭<E7A7A4><E686AD>銝剝𡢿隞?
|
||
```typescript
|
||
// middleware/error-handler.ts
|
||
import { FastifyError, FastifyReply, FastifyRequest } from 'fastify'
|
||
import { AppError } from '../utils/errors'
|
||
|
||
export async function errorHandler(
|
||
error: FastifyError | AppError,
|
||
request: FastifyRequest,
|
||
reply: FastifyReply
|
||
) {
|
||
// 霈啣<E99C88><E595A3>躰秤
|
||
request.log.error(error)
|
||
|
||
// <20>芸<EFBFBD>銋厰<E98A8B>霂? if (error instanceof AppError) {
|
||
return reply.code(error.statusCode).send({
|
||
success: false,
|
||
error: {
|
||
code: error.code,
|
||
message: error.message,
|
||
details: error.details,
|
||
},
|
||
timestamp: new Date().toISOString(),
|
||
})
|
||
}
|
||
|
||
// Prisma<6D>躰秤
|
||
if (error.name === 'PrismaClientKnownRequestError') {
|
||
// 憭<><E686AD>Prisma<6D>孵<EFBFBD><E5ADB5>躰秤
|
||
return reply.code(400).send({
|
||
success: false,
|
||
error: {
|
||
code: 'DATABASE_ERROR',
|
||
message: 'Database operation failed',
|
||
},
|
||
timestamp: new Date().toISOString(),
|
||
})
|
||
}
|
||
|
||
// 暺䁅恕<E48185>躰秤
|
||
return reply.code(500).send({
|
||
success: false,
|
||
error: {
|
||
code: 'INTERNAL_ERROR',
|
||
message: 'Internal server error',
|
||
},
|
||
timestamp: new Date().toISOString(),
|
||
})
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## <20>賢<EFBFBD>閫<EFBFBD><E996AB>
|
||
|
||
### <20><>辣<EFBFBD>賢<EFBFBD>
|
||
|
||
| 蝐餃<E89D90> | <20>賢<EFBFBD><E8B3A2>孵<EFBFBD> | 蝷箔<E89DB7> |
|
||
|------|---------|------|
|
||
| React蝏<74>辣 | PascalCase | `Button.tsx`, `ProjectList.tsx` |
|
||
| Hooks | camelCase + use<73>滨<EFBFBD> | `useProjects.ts`, `useAuth.ts` |
|
||
| 撌亙<E6928C><E4BA99>賣㺭 | camelCase | `formatDate.ts`, `api.ts` |
|
||
| 蝐餃<E89D90>摰帋<E691B0> | camelCase + .types | `user.types.ts`, `api.types.ts` |
|
||
| 撣賊<E692A3> | camelCase + .constants | `routes.constants.ts` |
|
||
| 瘚贝<E7989A><E8B49D><EFBFBD>辣 | <20>峕<EFBFBD><E5B395><EFBFBD>辣 + .test | `Button.test.tsx` |
|
||
|
||
### <20>㗛<EFBFBD><E3979B>賢<EFBFBD>
|
||
|
||
```typescript
|
||
// <20>?<3F>刻<EFBFBD>
|
||
const userName = 'John' // camelCase
|
||
const USER_ROLE = 'admin' // 撣賊<E692A3><E8B38A>沃PPER_SNAKE_CASE
|
||
const isLoading = false // 撣<><E692A3><EFBFBD>潛鍂is/has/can<61>滨<EFBFBD>
|
||
const hasPermission = true
|
||
const canEdit = false
|
||
|
||
// <20>?<3F>踹<EFBFBD>
|
||
const user_name = 'John' // 銝滨鍂snake_case
|
||
const loading = false // 撣<><E692A3><EFBFBD>潛撩撠魀s<E9AD80>滨<EFBFBD>
|
||
const x = 10 // <20>䭾<EFBFBD>銋厩<E98A8B><E58EA9>㗛<EFBFBD><E3979B>?```
|
||
|
||
### <EFBFBD>賣㺭<EFBFBD>賢<EFBFBD>
|
||
|
||
```typescript
|
||
// <20>?<3F>刻<EFBFBD>
|
||
function getUser() { } // get: <20>瑕<EFBFBD><E79195>唳旿
|
||
function fetchProjects() { } // fetch: 撘<>郊<EFBFBD>瑕<EFBFBD>
|
||
function createProject() { } // create: <20>𥕦遣
|
||
function updateProject() { } // update: <20>湔鰵
|
||
function deleteProject() { } // delete: <20>𣳇膄
|
||
function handleClick() { } // handle: 鈭衤辣憭<E8BEA3><E686AD>
|
||
function validateEmail() { } // validate: 撉諹<E69289>
|
||
function formatDate() { } // format: <20>澆<EFBFBD><E6BE86>?
|
||
// <20>?<3F>踹<EFBFBD>
|
||
function data() { } // 銝齿<E98A9D>璆𡁜<E79286><F0A1819C>?function doSomething() { } // 憭芣芋蝟?function process() { } // 銝齿<E98A9D>蝖?```
|
||
|
||
### 蝏<EFBFBD>辣<EFBFBD>賢<EFBFBD>
|
||
|
||
```typescript
|
||
// <20>?<3F>刻<EFBFBD>
|
||
<Button /> // <20>箇<EFBFBD>蝏<EFBFBD>辣
|
||
<UserProfile /> // 銝𡁜𦛚蝏<F0A69B9A>辣
|
||
<ProjectList /> // <20>𡑒”蝏<E2809D>辣
|
||
<CreateProjectModal /> // 撘寧<E69298>蝏<EFBFBD>辣
|
||
|
||
// <20>?<3F>踹<EFBFBD>
|
||
<button /> // 銝滨鍂撠誩<E692A0>
|
||
<user_profile /> // 銝滨鍂snake_case
|
||
<ListProjects /> // <20>刻<EFBFBD>銝滩<E98A9D><E6BBA9>典<EFBFBD>
|
||
```
|
||
|
||
---
|
||
|
||
## 瘜券<E7989C>閫<EFBFBD><E996AB>
|
||
|
||
### JSDoc瘜券<E7989C>
|
||
|
||
```typescript
|
||
/**
|
||
* <20>𥕦遣<F0A595A6>圈★<E59C88>? * @param userId - <20>冽<EFBFBD>ID
|
||
* @param data - 憿寧𤌍<E5AFA7>唳旿
|
||
* @returns <20>𥕦遣<F0A595A6><E981A3>★<EFBFBD>桀笆鞊? * @throws {ValidationError} 敶𤘪㺭<F0A498AA>桅<EFBFBD>霂<EFBFBD>仃韐交𧒄
|
||
*/
|
||
async function createProject(
|
||
userId: string,
|
||
data: CreateProjectDto
|
||
): Promise<Project> {
|
||
// 摰䂿緵...
|
||
}
|
||
```
|
||
|
||
### 隞<><E99A9E>瘜券<E7989C>
|
||
|
||
```typescript
|
||
// <20>?憟賜<E6869F>瘜券<E7989C>嚗朞圾<E69C9E>𠹺蛹隞<E89BB9>銋?// 雿輻鍂setTimeout<75>踹<EFBFBD><E8B8B9>餃<EFBFBD>UI皜脫<E79A9C>
|
||
setTimeout(() => {
|
||
processLargeData()
|
||
}, 0)
|
||
|
||
// 蝑匧<E89D91>Dify憭<79><E686AD><EFBFBD><EFBFBD>﹝嚗峕<E59A97>憭𡁻<E686AD>霂?0甈?for (let i = 0; i < 10; i++) {
|
||
const status = await checkStatus()
|
||
if (status === 'completed') break
|
||
await sleep(2000)
|
||
}
|
||
|
||
// <20>?<3F>讐<EFBFBD>瘜券<E7989C>嚗𡁻<E59A97>憭滢誨<E6BBA2>?// 霈曄蔭loading銝演rue
|
||
setLoading(true)
|
||
|
||
// 靚<>鍂API
|
||
await api.getData()
|
||
```
|
||
|
||
---
|
||
|
||
## Git<69>𣂷漱閫<E6BCB1><E996AB>
|
||
|
||
### Commit Message<67>澆<EFBFBD>
|
||
|
||
```
|
||
<type>(<scope>): <subject>
|
||
|
||
<body>
|
||
|
||
<footer>
|
||
```
|
||
|
||
### Type蝐餃<E89D90>
|
||
|
||
| 蝐餃<E89D90> | 霂湔<E99C82> |
|
||
|------|------|
|
||
| feat | <20>啣<EFBFBD><E595A3>?|
|
||
| fix | Bug靽桀<E99DBD> |
|
||
| docs | <20><>﹝<EFBFBD>湔鰵 |
|
||
| style | 隞<><E99A9E><EFBFBD>澆<EFBFBD>嚗<EFBFBD><E59A97>敶勗<E695B6><E58B97>蠘<EFBFBD>嚗?|
|
||
| refactor | <20>齿<EFBFBD> |
|
||
| perf | <20>扯<EFBFBD>隡睃<E99AA1> |
|
||
| test | 瘚贝<E7989A><E8B49D>詨<EFBFBD> |
|
||
| chore | <20><>遣/撌亙<E6928C><E4BA99>睃𢆡 |
|
||
|
||
### 蝷箔<E89DB7>
|
||
|
||
```bash
|
||
# 憟賜<E6869F><E8B39C>𣂷漱
|
||
git commit -m "feat(auth): 摰䂿緵<E482BF>冽<EFBFBD><E586BD>餃<EFBFBD><E9A483>蠘<EFBFBD>"
|
||
git commit -m "fix(project): 靽桀<E99DBD>憿寧𤌍<E5AFA7>𣳇膄<F0A3B387><E88684><EFBFBD><EFBFBD>桅<EFBFBD>"
|
||
git commit -m "docs(api): <20>湔鰵API<50><49>﹝"
|
||
git commit -m "refactor(chat): 隡睃<E99AA1>瘨<EFBFBD><E798A8>蝏<EFBFBD>辣蝏𤘪<E89D8F>"
|
||
|
||
# 銝滚末<E6BB9A><E69CAB><EFBFBD>鈭?git commit -m "update" # <20>?憭芣芋蝟?git commit -m "fix bug" # <20>?瘝⊥<E7989D>霂湔<E99C82><E6B994>臭<EFBFBD>銋Ê̄ug
|
||
git commit -m "摰峕<E691B0><E5B395>蠘<EFBFBD>" # <20>?瘝⊥<E7989D>霂湔<E99C82><E6B994>臭<EFBFBD>銋<EFBFBD><E98A8B><EFBFBD>?```
|
||
|
||
---
|
||
|
||
## ESLint<6E>滨蔭
|
||
|
||
```javascript
|
||
// .eslintrc.js
|
||
module.exports = {
|
||
extends: [
|
||
'eslint:recommended',
|
||
'plugin:@typescript-eslint/recommended',
|
||
'plugin:react/recommended',
|
||
'plugin:react-hooks/recommended',
|
||
'prettier',
|
||
],
|
||
rules: {
|
||
'@typescript-eslint/no-explicit-any': 'error',
|
||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||
'react/react-in-jsx-scope': 'off',
|
||
'react/prop-types': 'off',
|
||
},
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Prettier<65>滨蔭
|
||
|
||
```json
|
||
{
|
||
"semi": false,
|
||
"singleQuote": true,
|
||
"tabWidth": 2,
|
||
"trailingComma": "es5",
|
||
"printWidth": 100,
|
||
"arrowParens": "avoid"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 隞<><E99A9E>Review璉<77><E79289>交<EFBFBD><E4BAA4>?
|
||
### <20>蠘<EFBFBD>
|
||
- [ ] <20>蠘<EFBFBD><E8A098>臬炏摰峕㟲摰䂿緵
|
||
- [ ] <20>臬炏<E887AC>厰<EFBFBD>瞍讐<E79E8D>颲寧<E9A2B2><E5AFA7><EFBFBD><EFBFBD>
|
||
- [ ] <20>躰秤憭<E7A7A4><E686AD><EFBFBD>臬炏摰<E7828F><E691B0>
|
||
|
||
### 隞<><E99A9E>韐券<E99F90>
|
||
- [ ] 隞<><E99A9E><EFBFBD>臬炏<E887AC>栞粉<E6A09E>梶<EFBFBD>閫?- [ ] <20>臬炏<E887AC>厰<EFBFBD>憭滢誨<E6BBA2>?- [ ] <20>賣㺭<E8B3A3>臬炏餈<E7828F>鵭嚗<E9B5AD>遣霈?50銵䕘<E98AB5>
|
||
- [ ] <20>臬炏<E887AC>萄<EFBFBD><E89084>賢<EFBFBD>閫<EFBFBD><E996AB>
|
||
|
||
### <20>扯<EFBFBD>
|
||
- [ ] <20>臬炏<E887AC>㗇<EFBFBD>扯<EFBFBD><E689AF>桅<EFBFBD>
|
||
- [ ] <20>臬炏<E887AC>劐<EFBFBD>敹<EFBFBD><E695B9><EFBFBD><EFBFBD><EFBFBD>皜脫<E79A9C>
|
||
- [ ] <20>唳旿摨𤘪䰻霂X糓<EFBCB8>虫<EFBFBD><E899AB>?
|
||
### 摰匧<E691B0>
|
||
- [ ] <20>臬炏<E887AC>农QL瘜典<E7989C>憌𡡞埯
|
||
- [ ] <20>臬炏<E887AC>华SS憌𡡞埯
|
||
- [ ] <20><><EFBFBD>撉諹<E69289><E8ABB9>臬炏摰<E7828F><E691B0>
|
||
|
||
### 瘚贝<E7989A>
|
||
- [ ] <20>臬炏<E887AC>匧<EFBFBD><E58CA7><EFBFBD><EFBFBD>霂?- [ ] 瘚贝<E7989A>閬<EFBFBD><E996AC><EFBFBD><EFBFBD>糓<EFBFBD>西雲憭?
|
||
---
|
||
|
||
**<EFBFBD><EFBFBD>﹝蝏湔擪嚗?* 閫<><E996AB><EFBFBD>湔鰵<E6B994><E9B0B5><EFBFBD>峕郊甇斗<E79487>獢?
|
||
**<EFBFBD><EFBFBD><EFBFBD>擧凒<EFBFBD>堆<EFBFBD>** 2025-10-10
|
||
**蝏湔擪<E6B994><E693AA><EFBFBD>** <20><><EFBFBD>航<EFBFBD>韐<EFBFBD>犖
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|