feat(diary): Phase 1.3 完善修复 — 贴纸/主题 CRUD + 管理端对接 + HMS 清理
H7 贴纸 CRUD: - POST /diary/sticker-packs — 创建贴纸包 - DELETE /diary/sticker-packs/:id — 软删除贴纸包 - POST /diary/sticker-packs/:id/stickers — 添加贴纸 H8 主题编辑/停用: - PUT /diary/topics/:id — 编辑主题 (标题/描述/截止日期) - PATCH /diary/topics/:id/deactivate — 停用主题 管理端前端: - ClassList.tsx 对接 update/deactivate/reset-code (含 Popconfirm 确认) - JournalList.tsx 班级筛选改用 classApi.listAll() - classes.ts 新增 listAll/update/deactivate/resetCode API M2 HMS 遗留清理: - 删除 copilot.ts, healthFixtures.ts, healthHandlers.ts - AuditLogViewer 资源类型 → 日记模块 - auth.test.ts / renderWithProviders health.* → diary.* M4 编辑器加载: - EditorPage journalId 非空时从 Isar 恢复笔画/元素/标签/心情/标题 77 tests passed, cargo check ✅, tsc ✅, flutter analyze ✅
This commit is contained in:
@@ -10,7 +10,7 @@ use erp_core::error::AppError;
|
||||
use erp_core::rbac::require_permission;
|
||||
use erp_core::types::{ApiResponse, TenantContext};
|
||||
|
||||
use crate::dto::{StickerPackResp, StickerResp, TemplateResp};
|
||||
use crate::dto::{CreateStickerPackReq, CreateStickerReq, StickerPackResp, StickerResp, TemplateResp};
|
||||
use crate::service::sticker_service::StickerService;
|
||||
use crate::state::DiaryState;
|
||||
|
||||
@@ -85,7 +85,125 @@ where
|
||||
Ok(Json(ApiResponse::ok(resp)))
|
||||
}
|
||||
|
||||
/// 模板查询参数
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/diary/sticker-packs",
|
||||
request_body = CreateStickerPackReq,
|
||||
responses(
|
||||
(status = 200, description = "创建成功", body = ApiResponse<StickerPackResp>),
|
||||
(status = 400, description = "验证失败"),
|
||||
(status = 401, description = "未授权"),
|
||||
(status = 403, description = "权限不足"),
|
||||
),
|
||||
security(("bearer_auth" = [])),
|
||||
tag = "贴纸管理"
|
||||
)]
|
||||
/// POST /api/v1/diary/sticker-packs
|
||||
///
|
||||
/// 创建贴纸包。需要 `diary.class.manage` 权限(管理端)。
|
||||
pub async fn create_sticker_pack<S>(
|
||||
State(state): State<DiaryState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Json(req): Json<CreateStickerPackReq>,
|
||||
) -> Result<Json<ApiResponse<StickerPackResp>>, AppError>
|
||||
where
|
||||
DiaryState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "diary.class.manage")?;
|
||||
|
||||
if req.name.trim().is_empty() {
|
||||
return Err(AppError::Validation("贴纸包名称不能为空".to_string()));
|
||||
}
|
||||
|
||||
let resp = StickerService::create_sticker_pack(
|
||||
ctx.tenant_id,
|
||||
ctx.user_id,
|
||||
&req,
|
||||
&state.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(ApiResponse::ok(resp)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/diary/sticker-packs/{pack_id}",
|
||||
params(("pack_id" = Uuid, Path, description = "贴纸包ID")),
|
||||
responses(
|
||||
(status = 200, description = "删除成功"),
|
||||
(status = 401, description = "未授权"),
|
||||
(status = 403, description = "权限不足"),
|
||||
(status = 404, description = "贴纸包不存在"),
|
||||
),
|
||||
security(("bearer_auth" = [])),
|
||||
tag = "贴纸管理"
|
||||
)]
|
||||
/// DELETE /api/v1/diary/sticker-packs/:pack_id
|
||||
///
|
||||
/// 删除贴纸包(软删除)。需要 `diary.class.manage` 权限。
|
||||
pub async fn delete_sticker_pack<S>(
|
||||
State(state): State<DiaryState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path(pack_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
DiaryState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "diary.class.manage")?;
|
||||
|
||||
StickerService::delete_sticker_pack(ctx.tenant_id, pack_id, ctx.user_id, &state.db).await?;
|
||||
|
||||
Ok(Json(ApiResponse::ok(())))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/diary/sticker-packs/{pack_id}/stickers",
|
||||
params(("pack_id" = Uuid, Path, description = "贴纸包ID")),
|
||||
request_body = CreateStickerReq,
|
||||
responses(
|
||||
(status = 200, description = "创建成功", body = ApiResponse<StickerResp>),
|
||||
(status = 400, description = "验证失败"),
|
||||
(status = 401, description = "未授权"),
|
||||
(status = 403, description = "权限不足"),
|
||||
(status = 404, description = "贴纸包不存在"),
|
||||
),
|
||||
security(("bearer_auth" = [])),
|
||||
tag = "贴纸管理"
|
||||
)]
|
||||
/// POST /api/v1/diary/sticker-packs/:pack_id/stickers
|
||||
///
|
||||
/// 在贴纸包内添加贴纸。需要 `diary.class.manage` 权限。
|
||||
pub async fn create_sticker<S>(
|
||||
State(state): State<DiaryState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path(pack_id): Path<Uuid>,
|
||||
Json(req): Json<CreateStickerReq>,
|
||||
) -> Result<Json<ApiResponse<StickerResp>>, AppError>
|
||||
where
|
||||
DiaryState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "diary.class.manage")?;
|
||||
|
||||
if req.name.trim().is_empty() {
|
||||
return Err(AppError::Validation("贴纸名称不能为空".to_string()));
|
||||
}
|
||||
|
||||
let resp = StickerService::create_sticker(
|
||||
ctx.tenant_id,
|
||||
ctx.user_id,
|
||||
pack_id,
|
||||
&req,
|
||||
&state.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(ApiResponse::ok(resp)))
|
||||
}
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct TemplateQuery {
|
||||
pub category: Option<String>,
|
||||
|
||||
@@ -8,7 +8,7 @@ use erp_core::error::AppError;
|
||||
use erp_core::rbac::require_permission;
|
||||
use erp_core::types::{ApiResponse, TenantContext};
|
||||
|
||||
use crate::dto::{CreateTopicReq, TopicResp};
|
||||
use crate::dto::{CreateTopicReq, TopicResp, UpdateTopicReq};
|
||||
use crate::service::topic_service::TopicService;
|
||||
use crate::state::DiaryState;
|
||||
|
||||
@@ -88,3 +88,90 @@ where
|
||||
let resp = TopicService::list_topics(ctx.tenant_id, class_id, &state.db).await?;
|
||||
Ok(Json(ApiResponse::ok(resp)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/api/v1/diary/topics/{topic_id}",
|
||||
params(("topic_id" = Uuid, Path, description = "主题ID")),
|
||||
request_body = UpdateTopicReq,
|
||||
responses(
|
||||
(status = 200, description = "更新成功", body = ApiResponse<TopicResp>),
|
||||
(status = 400, description = "验证失败"),
|
||||
(status = 401, description = "未授权"),
|
||||
(status = 403, description = "权限不足"),
|
||||
(status = 404, description = "主题不存在"),
|
||||
(status = 409, description = "版本冲突"),
|
||||
),
|
||||
security(("bearer_auth" = [])),
|
||||
tag = "主题布置"
|
||||
)]
|
||||
/// PUT /api/v1/diary/topics/:topic_id
|
||||
///
|
||||
/// 更新主题信息。需要 `diary.topic.assign` 权限(仅布置者可编辑)。
|
||||
pub async fn update_topic<S>(
|
||||
State(state): State<DiaryState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path(topic_id): Path<Uuid>,
|
||||
Json(req): Json<UpdateTopicReq>,
|
||||
) -> Result<Json<ApiResponse<TopicResp>>, AppError>
|
||||
where
|
||||
DiaryState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "diary.topic.assign")?;
|
||||
|
||||
if let Some(ref title) = req.title {
|
||||
if title.trim().is_empty() {
|
||||
return Err(AppError::Validation("主题标题不能为空".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
let resp = TopicService::update_topic(
|
||||
ctx.tenant_id,
|
||||
ctx.user_id,
|
||||
topic_id,
|
||||
&req,
|
||||
&state.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(ApiResponse::ok(resp)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
path = "/api/v1/diary/topics/{topic_id}/deactivate",
|
||||
params(("topic_id" = Uuid, Path, description = "主题ID")),
|
||||
responses(
|
||||
(status = 200, description = "停用成功", body = ApiResponse<TopicResp>),
|
||||
(status = 401, description = "未授权"),
|
||||
(status = 403, description = "权限不足"),
|
||||
(status = 404, description = "主题不存在"),
|
||||
),
|
||||
security(("bearer_auth" = [])),
|
||||
tag = "主题布置"
|
||||
)]
|
||||
/// PATCH /api/v1/diary/topics/:topic_id/deactivate
|
||||
///
|
||||
/// 停用主题。需要 `diary.topic.assign` 权限(仅布置者可停用)。
|
||||
pub async fn deactivate_topic<S>(
|
||||
State(state): State<DiaryState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path(topic_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<TopicResp>>, AppError>
|
||||
where
|
||||
DiaryState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "diary.topic.assign")?;
|
||||
|
||||
let resp = TopicService::deactivate_topic(
|
||||
ctx.tenant_id,
|
||||
ctx.user_id,
|
||||
topic_id,
|
||||
&state.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(ApiResponse::ok(resp)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user