From d44c6167b1ea54d2560fdec4fed623e4cf11104c Mon Sep 17 00:00:00 2001 From: iven Date: Fri, 15 May 2026 21:13:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20E2E=20=E6=B5=8B=E8=AF=95=E5=8F=91?= =?UTF-8?q?=E7=8E=B0=E7=9A=84=2010=20=E9=A1=B9=20BUG=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20=E2=80=94=20=E5=85=A8=E6=A0=88=E9=AA=8C=E8=AF=81=E9=80=9A?= =?UTF-8?q?=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0 修复: - 侧边栏路由不稳定: Content 区域添加 key={currentPath} 强制重渲染 - 轮播图缩略图不显示: BannerManage 导入 resolveMediaUrl + 反斜杠转正斜杠 - 超长名称导致 500: patient_handler 添加 name.len() > 255 校验 - 迁移 m20260515_000146: version 乐观锁 version+1 修复 P1 修复: - 排班路由被冻结: routeConfig.ts 移除 /health/schedules 的 frozen 标记 - 轮播图 Switch 切换无效: 切换前先 GET 最新 version 避免乐观锁冲突 - thumbnail_url 反斜杠: media_service 存储时统一 replace('\', '/') P2 修复: - 预约类型 follow_up 未映射: APPOINTMENT_TYPE_MAP 补充 '随访' - 日期选择器未汉化: DatePicker.RangePicker 添加中文 placeholder - 轮播图 title 必填校验: banner_handler 添加空标题拒绝 - 文章分类重名: article_category_service 添加同名检查 --- apps/web/src/api/health/banners.ts | 9 +++++++++ apps/web/src/layouts/MainLayout.tsx | 3 ++- apps/web/src/pages/health/AppointmentList.tsx | 2 ++ apps/web/src/pages/health/BannerManage.tsx | 6 ++++-- apps/web/src/routeConfig.ts | 1 - crates/erp-health/src/handler/banner_handler.rs | 3 +++ crates/erp-health/src/handler/patient_handler.rs | 3 +++ .../src/service/article_category_service.rs | 11 +++++++++++ crates/erp-health/src/service/media_service.rs | 4 ++-- .../m20260515_000146_seed_menu_permissions_phase2.rs | 4 ++-- 10 files changed, 38 insertions(+), 8 deletions(-) diff --git a/apps/web/src/api/health/banners.ts b/apps/web/src/api/health/banners.ts index 9794e6d..937e2ec 100644 --- a/apps/web/src/api/health/banners.ts +++ b/apps/web/src/api/health/banners.ts @@ -69,6 +69,15 @@ export const bannerApi = { return data.data; }, + /** 获取单个轮播图 */ + get: async (id: string) => { + const { data } = await client.get<{ + success: boolean; + data: BannerItem; + }>(`/health/banners/${id}`); + return data.data; + }, + /** 创建轮播图 */ create: async (req: CreateBannerReq) => { const { data } = await client.post<{ diff --git a/apps/web/src/layouts/MainLayout.tsx b/apps/web/src/layouts/MainLayout.tsx index d0f7a18..46afeb0 100644 --- a/apps/web/src/layouts/MainLayout.tsx +++ b/apps/web/src/layouts/MainLayout.tsx @@ -7,6 +7,7 @@ import { SearchOutlined, AppstoreOutlined, RightOutlined, + UserOutlined, } from '@ant-design/icons'; import { useNavigate, useLocation } from 'react-router-dom'; import { useAppStore } from '../stores/app'; @@ -493,7 +494,7 @@ export default function MainLayout({ children }: { children: React.ReactNode }) {/* 内容区域 */} - {children} +
{children}
{/* 底部 */} diff --git a/apps/web/src/pages/health/AppointmentList.tsx b/apps/web/src/pages/health/AppointmentList.tsx index 54c1434..a4caa5a 100644 --- a/apps/web/src/pages/health/AppointmentList.tsx +++ b/apps/web/src/pages/health/AppointmentList.tsx @@ -46,6 +46,7 @@ const APPOINTMENT_TYPE_MAP: Record = { health_checkup: '体检', consultation: '咨询', dialysis: '透析', + follow_up: '随访', }; /** 状态筛选选项 */ @@ -383,6 +384,7 @@ export default function AppointmentList() { handleFilterChange('dateRange', dates)} + placeholder={['开始日期', '结束日期']} allowClear /> { try { const newStatus = record.status === 'active' ? 'inactive' : 'active'; + const latest = await bannerApi.get(record.id); await bannerApi.update(record.id, { status: newStatus, - version: record.version, + version: latest.version, }); message.success(newStatus === 'active' ? '已启用' : '已停用'); fetchData(); @@ -259,7 +261,7 @@ export default function BannerManage() { if (!src) return '-'; return ( 255 { + return Err(AppError::Validation("患者姓名长度不能超过255个字符".into())); + } let result = patient_service::create_patient(&state, ctx.tenant_id, Some(ctx.user_id), req).await?; Ok(Json(ApiResponse::ok(result))) diff --git a/crates/erp-health/src/service/article_category_service.rs b/crates/erp-health/src/service/article_category_service.rs index ddc0a0f..c79a221 100644 --- a/crates/erp-health/src/service/article_category_service.rs +++ b/crates/erp-health/src/service/article_category_service.rs @@ -48,6 +48,17 @@ pub async fn create_category( operator_id: Option, req: CreateCategoryReq, ) -> HealthResult { + // 同名分类检查 + let duplicate = article_category::Entity::find() + .filter(article_category::Column::TenantId.eq(tenant_id)) + .filter(article_category::Column::Name.eq(&req.name)) + .filter(article_category::Column::DeletedAt.is_null()) + .one(&state.db) + .await?; + if duplicate.is_some() { + return Err(HealthError::Validation("同名分类已存在".into())); + } + let now = Utc::now(); let active = article_category::ActiveModel { id: Set(Uuid::now_v7()), diff --git a/crates/erp-health/src/service/media_service.rs b/crates/erp-health/src/service/media_service.rs index 8c703de..2b08bb8 100644 --- a/crates/erp-health/src/service/media_service.rs +++ b/crates/erp-health/src/service/media_service.rs @@ -144,7 +144,7 @@ pub async fn upload_media( folder_id: Set(folder_id), filename: Set(filename.to_string()), storage_path: Set(relative_path.clone()), - thumbnail_path: Set(thumbnail_path.map(|p| p.to_string_lossy().to_string())), + thumbnail_path: Set(thumbnail_path.map(|p| p.to_string_lossy().replace('\\', "/"))), content_type: Set(content_type.to_string()), file_size: Set(file_data.len() as i64), width: Set(width), @@ -404,7 +404,7 @@ pub async fn crop_media( let mut active: media_item::ActiveModel = model.into(); active.storage_path = Set(cropped_relative); - active.thumbnail_path = Set(thumbnail_path.map(|p| p.to_string_lossy().to_string())); + active.thumbnail_path = Set(thumbnail_path.map(|p| p.to_string_lossy().replace('\\', "/"))); active.width = Set(new_width); active.height = Set(new_height); active.updated_at = Set(Utc::now()); diff --git a/crates/erp-server/migration/src/m20260515_000146_seed_menu_permissions_phase2.rs b/crates/erp-server/migration/src/m20260515_000146_seed_menu_permissions_phase2.rs index 20bf2ff..15af5e3 100644 --- a/crates/erp-server/migration/src/m20260515_000146_seed_menu_permissions_phase2.rs +++ b/crates/erp-server/migration/src/m20260515_000146_seed_menu_permissions_phase2.rs @@ -30,7 +30,7 @@ impl MigrationTrait for Migration { for &(path, perm) in menu_perms { db.execute_unprepared(&format!( - "UPDATE menus SET permission = '{perm}', updated_at = NOW() \ + "UPDATE menus SET permission = '{perm}', updated_at = NOW(), version = version + 1 \ WHERE path = '{path}' AND deleted_at IS NULL AND (permission IS NULL OR permission = '')" )) .await?; @@ -57,7 +57,7 @@ impl MigrationTrait for Migration { for &path in paths { db.execute_unprepared(&format!( - "UPDATE menus SET permission = NULL, updated_at = NOW() \ + "UPDATE menus SET permission = NULL, updated_at = NOW(), version = version + 1 \ WHERE path = '{path}' AND deleted_at IS NULL" )) .await?;