fix: E2E 测试发现的 10 项 BUG 修复 — 全栈验证通过
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 添加同名检查
This commit is contained in:
@@ -61,6 +61,9 @@ where
|
||||
{
|
||||
require_permission(&ctx, "health.banners.manage")?;
|
||||
req.sanitize();
|
||||
if req.title.as_ref().is_none_or(|t| t.trim().is_empty()) {
|
||||
return Err(AppError::Validation("轮播图标题不能为空".into()));
|
||||
}
|
||||
let result = banner_service::create_banner(&state, ctx.tenant_id, ctx.user_id, req.0).await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
@@ -70,6 +70,9 @@ where
|
||||
if req.name.trim().is_empty() {
|
||||
return Err(AppError::Validation("患者姓名不能为空".into()));
|
||||
}
|
||||
if req.name.len() > 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)))
|
||||
|
||||
@@ -48,6 +48,17 @@ pub async fn create_category(
|
||||
operator_id: Option<Uuid>,
|
||||
req: CreateCategoryReq,
|
||||
) -> HealthResult<CategoryResp> {
|
||||
// 同名分类检查
|
||||
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()),
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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?;
|
||||
|
||||
Reference in New Issue
Block a user