diff --git a/backend/app/schemas/_types.py b/backend/app/schemas/_types.py new file mode 100644 index 0000000..4db3fee --- /dev/null +++ b/backend/app/schemas/_types.py @@ -0,0 +1,22 @@ +from datetime import datetime, timezone +from typing import Annotated + +from pydantic import PlainSerializer + + +def _to_utc_iso(v: datetime | None) -> str | None: + if v is None: + return None + if v.tzinfo is None: + v = v.replace(tzinfo=timezone.utc) + else: + v = v.astimezone(timezone.utc) + return v.isoformat().replace("+00:00", "Z") + + +# 数据库里存的是 datetime.utcnow() 产出的 naive UTC。裸 ISO 字符串 +# 会被 JS new Date(...) 当本地时间解析, 少 8 小时。统一序列化为带 Z 的 +# UTC ISO, 前端/小程序拿到后 new Date(...) 会正确转成本地时区。 +UtcDatetime = Annotated[ + datetime, PlainSerializer(_to_utc_iso, return_type=str, when_used="json") +] diff --git a/backend/app/schemas/audit.py b/backend/app/schemas/audit.py index 1bb31ef..437fac8 100644 --- a/backend/app/schemas/audit.py +++ b/backend/app/schemas/audit.py @@ -1,7 +1,7 @@ -from datetime import datetime - from pydantic import BaseModel +from ._types import UtcDatetime + class AuditLogResponse(BaseModel): id: int @@ -10,7 +10,7 @@ class AuditLogResponse(BaseModel): target_type: str | None target_id: str | None detail_json: dict | None - created_at: datetime + created_at: UtcDatetime class StatsOverviewResponse(BaseModel): diff --git a/backend/app/schemas/chat.py b/backend/app/schemas/chat.py index 64e2f84..6cc12f3 100644 --- a/backend/app/schemas/chat.py +++ b/backend/app/schemas/chat.py @@ -1,7 +1,7 @@ -from datetime import datetime - from pydantic import BaseModel, Field +from ._types import UtcDatetime + class ConversationCreate(BaseModel): title: str = "新会话" @@ -10,8 +10,8 @@ class ConversationCreate(BaseModel): class ConversationResponse(BaseModel): id: int title: str - created_at: datetime - last_message_at: datetime | None + created_at: UtcDatetime + last_message_at: UtcDatetime | None class ChatStreamRequest(BaseModel): @@ -42,7 +42,7 @@ class MessageResponse(BaseModel): status: str token_usage: int pu_usage: int - created_at: datetime + created_at: UtcDatetime references: list[ReferenceResponse] attachments: list[AttachmentResponse] = Field(default_factory=list) diff --git a/backend/app/schemas/document.py b/backend/app/schemas/document.py index 5008971..7537300 100644 --- a/backend/app/schemas/document.py +++ b/backend/app/schemas/document.py @@ -1,7 +1,7 @@ -from datetime import datetime - from pydantic import BaseModel, Field +from ._types import UtcDatetime + class DocumentCreate(BaseModel): title: str = Field(min_length=1, max_length=255) @@ -31,4 +31,4 @@ class DocumentResponse(BaseModel): status: str owner_dept: str | None tags: list[str] - created_at: datetime + created_at: UtcDatetime diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index 4f079a7..d08b005 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -1,7 +1,7 @@ -from datetime import datetime - from pydantic import BaseModel, Field +from ._types import UtcDatetime + class UserCreate(BaseModel): phone: str = Field(min_length=6, max_length=32) @@ -34,7 +34,7 @@ class UserResponse(BaseModel): status: str role_codes: list[str] is_superuser: bool - created_at: datetime | None = None + created_at: UtcDatetime | None = None class BulkImportResult(BaseModel): diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css index 6d21878..60d7a70 100644 --- a/frontend/src/styles/main.css +++ b/frontend/src/styles/main.css @@ -835,16 +835,6 @@ button { white-space: nowrap; } -/* 共享文档 (ref.source_url 存在) 时, 标题变成蓝色外链, 悬停加下划线 */ -.bubble-reference-item summary .ref-title-link { - color: #1a73e8; - text-decoration: none; - cursor: pointer; -} -.bubble-reference-item summary .ref-title-link:hover { - text-decoration: underline; -} - /* 展开区域内的链接地址行, 显示完整 URL, 蓝色字, 可换行 */ .bubble-reference-item .ref-url-line { display: block; diff --git a/frontend/src/views/ChatView.vue b/frontend/src/views/ChatView.vue index abebd0e..b81961d 100644 --- a/frontend/src/views/ChatView.vue +++ b/frontend/src/views/ChatView.vue @@ -302,7 +302,7 @@ watch(messages, scrollToEnd, { deep: true }) @click="onBubbleClick" />