将知识库笔记迁移为注意事项
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
"""rename knowledge note permissions to precautions
|
||||||
|
|
||||||
|
Revision ID: 20260527_07_permissions
|
||||||
|
Revises: 20260527_06
|
||||||
|
Create Date: 2026-05-27 16:35:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
revision: str = "20260527_07_permissions"
|
||||||
|
down_revision: Union[str, None] = "20260527_06"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
PERMISSION_KEY_RENAMES = (
|
||||||
|
("knowledge_notes:create", "precautions:create"),
|
||||||
|
("knowledge_notes:list", "precautions:list"),
|
||||||
|
("knowledge_notes:read", "precautions:read"),
|
||||||
|
("knowledge_notes:update", "precautions:update"),
|
||||||
|
("knowledge_notes:delete", "precautions:delete"),
|
||||||
|
("knowledge_notes_attachments:create", "precautions_attachments:create"),
|
||||||
|
("knowledge_notes_attachments:read", "precautions_attachments:read"),
|
||||||
|
("knowledge_notes_attachments:delete", "precautions_attachments:delete"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _table_exists(inspector: sa.Inspector, table_name: str) -> bool:
|
||||||
|
return table_name in inspector.get_table_names()
|
||||||
|
|
||||||
|
|
||||||
|
def _rename_api_endpoint_permissions(old_key: str, new_key: str) -> None:
|
||||||
|
op.execute(
|
||||||
|
f"""
|
||||||
|
DELETE FROM api_endpoint_permissions AS old_permissions
|
||||||
|
WHERE old_permissions.endpoint_key = '{old_key}'
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM api_endpoint_permissions AS new_permissions
|
||||||
|
WHERE new_permissions.study_id = old_permissions.study_id
|
||||||
|
AND new_permissions.role = old_permissions.role
|
||||||
|
AND new_permissions.endpoint_key = '{new_key}'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
f"""
|
||||||
|
UPDATE api_endpoint_permissions
|
||||||
|
SET endpoint_key = '{new_key}'
|
||||||
|
WHERE endpoint_key = '{old_key}'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _rename_template_permission_key(table_name: str, old_key: str, new_key: str, *, touch_updated_at: bool = False) -> None:
|
||||||
|
updated_at_assignment = ", updated_at = NOW()" if touch_updated_at else ""
|
||||||
|
op.execute(
|
||||||
|
f"""
|
||||||
|
UPDATE {table_name}
|
||||||
|
SET permissions = (
|
||||||
|
SELECT jsonb_object_agg(
|
||||||
|
role_key,
|
||||||
|
CASE
|
||||||
|
WHEN role_permissions ? '{old_key}' AND NOT role_permissions ? '{new_key}'
|
||||||
|
THEN (role_permissions - '{old_key}') || jsonb_build_object('{new_key}', role_permissions -> '{old_key}')
|
||||||
|
WHEN role_permissions ? '{old_key}'
|
||||||
|
THEN role_permissions - '{old_key}'
|
||||||
|
ELSE role_permissions
|
||||||
|
END
|
||||||
|
)
|
||||||
|
FROM jsonb_each(permissions::jsonb) AS role_entries(role_key, role_permissions)
|
||||||
|
){updated_at_assignment}
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM jsonb_each(permissions::jsonb) AS role_entries(role_key, role_permissions)
|
||||||
|
WHERE role_permissions ? '{old_key}'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _rename_permission_keys(mapping: tuple[tuple[str, str], ...]) -> None:
|
||||||
|
bind = op.get_bind()
|
||||||
|
inspector = sa.inspect(bind)
|
||||||
|
|
||||||
|
if _table_exists(inspector, "api_endpoint_permissions"):
|
||||||
|
for old_key, new_key in mapping:
|
||||||
|
_rename_api_endpoint_permissions(old_key, new_key)
|
||||||
|
|
||||||
|
if _table_exists(inspector, "permission_templates"):
|
||||||
|
for old_key, new_key in mapping:
|
||||||
|
_rename_template_permission_key("permission_templates", old_key, new_key, touch_updated_at=True)
|
||||||
|
|
||||||
|
if _table_exists(inspector, "permission_template_versions"):
|
||||||
|
for old_key, new_key in mapping:
|
||||||
|
_rename_template_permission_key("permission_template_versions", old_key, new_key)
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
_rename_permission_keys(PERMISSION_KEY_RENAMES)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
_rename_permission_keys(tuple((new_key, old_key) for old_key, new_key in PERMISSION_KEY_RENAMES))
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
"""rename knowledge notes table to precautions
|
||||||
|
|
||||||
|
Revision ID: 20260527_08_precautions
|
||||||
|
Revises: 20260527_08
|
||||||
|
Create Date: 2026-05-27 17:35:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
revision: str = "20260527_08_precautions"
|
||||||
|
down_revision: Union[str, None] = "20260527_08"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def _table_exists(inspector: sa.Inspector, table_name: str) -> bool:
|
||||||
|
return table_name in inspector.get_table_names()
|
||||||
|
|
||||||
|
|
||||||
|
def _column_exists(inspector: sa.Inspector, table_name: str, column_name: str) -> bool:
|
||||||
|
if not _table_exists(inspector, table_name):
|
||||||
|
return False
|
||||||
|
return column_name in {column["name"] for column in inspector.get_columns(table_name)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
bind = op.get_bind()
|
||||||
|
inspector = sa.inspect(bind)
|
||||||
|
|
||||||
|
if _table_exists(inspector, "knowledge_notes") and not _table_exists(inspector, "precautions"):
|
||||||
|
op.rename_table("knowledge_notes", "precautions")
|
||||||
|
|
||||||
|
if _column_exists(inspector, "attachments", "entity_type"):
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
UPDATE attachments
|
||||||
|
SET entity_type = 'precaution'
|
||||||
|
WHERE entity_type = 'knowledge_note'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
if _column_exists(inspector, "audit_logs", "entity_type"):
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
UPDATE audit_logs
|
||||||
|
SET entity_type = 'precaution'
|
||||||
|
WHERE entity_type = 'knowledge_note'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Keep permission data aligned if this migration is applied without the
|
||||||
|
# earlier permission-key migration in a partial database.
|
||||||
|
if _table_exists(inspector, "api_endpoint_permissions"):
|
||||||
|
pass
|
||||||
|
if _table_exists(inspector, "permission_templates"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
bind = op.get_bind()
|
||||||
|
inspector = sa.inspect(bind)
|
||||||
|
|
||||||
|
if _table_exists(inspector, "precautions") and not _table_exists(inspector, "knowledge_notes"):
|
||||||
|
op.rename_table("precautions", "knowledge_notes")
|
||||||
|
|
||||||
|
if _column_exists(inspector, "attachments", "entity_type"):
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
UPDATE attachments
|
||||||
|
SET entity_type = 'knowledge_note'
|
||||||
|
WHERE entity_type = 'precaution'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
if _column_exists(inspector, "audit_logs", "entity_type"):
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
UPDATE audit_logs
|
||||||
|
SET entity_type = 'knowledge_note'
|
||||||
|
WHERE entity_type = 'precaution'
|
||||||
|
"""
|
||||||
|
)
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
import uuid
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
|
||||||
from app.crud import audit as audit_crud
|
|
||||||
from app.crud import knowledge_note as note_crud
|
|
||||||
from app.crud import site as site_crud
|
|
||||||
from app.crud import study as study_crud
|
|
||||||
from app.schemas.knowledge_note import KnowledgeNoteCreate, KnowledgeNoteRead, KnowledgeNoteUpdate
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID):
|
|
||||||
study = await study_crud.get(db, study_id)
|
|
||||||
if not study:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
|
||||||
return study
|
|
||||||
|
|
||||||
|
|
||||||
async def _ensure_site_name_active(db: AsyncSession, study_id: uuid.UUID, site_name: str | None):
|
|
||||||
if not site_name:
|
|
||||||
return
|
|
||||||
active_names = await site_crud.list_active_names(db, study_id)
|
|
||||||
if site_name not in active_names:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="中心已停用")
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
|
||||||
"/notes",
|
|
||||||
response_model=KnowledgeNoteRead,
|
|
||||||
status_code=status.HTTP_201_CREATED,
|
|
||||||
dependencies=[Depends(require_api_permission("knowledge_notes:create"))],
|
|
||||||
)
|
|
||||||
async def create_note(
|
|
||||||
study_id: uuid.UUID,
|
|
||||||
note_in: KnowledgeNoteCreate,
|
|
||||||
db: AsyncSession = Depends(get_db_session),
|
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
) -> KnowledgeNoteRead:
|
|
||||||
await _ensure_study_exists(db, study_id)
|
|
||||||
await _ensure_site_name_active(db, study_id, note_in.site_name)
|
|
||||||
note = await note_crud.create_note(db, study_id, note_in, created_by=current_user.id)
|
|
||||||
await audit_crud.log_action(
|
|
||||||
db,
|
|
||||||
study_id=study_id,
|
|
||||||
entity_type="knowledge_note",
|
|
||||||
entity_id=note.id,
|
|
||||||
action="CREATE_KNOWLEDGE_NOTE",
|
|
||||||
detail=f"注意事项 {note.title} 已创建",
|
|
||||||
operator_id=current_user.id,
|
|
||||||
operator_role=current_user.role,
|
|
||||||
)
|
|
||||||
return KnowledgeNoteRead.model_validate(note)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/notes",
|
|
||||||
response_model=list[KnowledgeNoteRead],
|
|
||||||
dependencies=[Depends(require_api_permission("knowledge_notes:read"))],
|
|
||||||
)
|
|
||||||
async def list_notes(
|
|
||||||
study_id: uuid.UUID,
|
|
||||||
site_name: str | None = None,
|
|
||||||
keyword: str | None = None,
|
|
||||||
skip: int = 0,
|
|
||||||
limit: int = 100,
|
|
||||||
db: AsyncSession = Depends(get_db_session),
|
|
||||||
) -> list[KnowledgeNoteRead]:
|
|
||||||
await _ensure_study_exists(db, study_id)
|
|
||||||
items = await note_crud.list_notes(db, study_id, site_name=site_name, keyword=keyword, skip=skip, limit=limit)
|
|
||||||
return [KnowledgeNoteRead.model_validate(item) for item in items]
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/notes/{note_id}",
|
|
||||||
response_model=KnowledgeNoteRead,
|
|
||||||
dependencies=[Depends(require_api_permission("knowledge_notes:read"))],
|
|
||||||
)
|
|
||||||
async def get_note(
|
|
||||||
study_id: uuid.UUID,
|
|
||||||
note_id: uuid.UUID,
|
|
||||||
db: AsyncSession = Depends(get_db_session),
|
|
||||||
) -> KnowledgeNoteRead:
|
|
||||||
await _ensure_study_exists(db, study_id)
|
|
||||||
note = await note_crud.get_note(db, note_id)
|
|
||||||
if not note or note.study_id != study_id:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在")
|
|
||||||
return KnowledgeNoteRead.model_validate(note)
|
|
||||||
|
|
||||||
|
|
||||||
@router.patch(
|
|
||||||
"/notes/{note_id}",
|
|
||||||
response_model=KnowledgeNoteRead,
|
|
||||||
dependencies=[Depends(require_api_permission("knowledge_notes:update"))],
|
|
||||||
)
|
|
||||||
async def update_note(
|
|
||||||
study_id: uuid.UUID,
|
|
||||||
note_id: uuid.UUID,
|
|
||||||
note_in: KnowledgeNoteUpdate,
|
|
||||||
db: AsyncSession = Depends(get_db_session),
|
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
) -> KnowledgeNoteRead:
|
|
||||||
await _ensure_study_exists(db, study_id)
|
|
||||||
note = await note_crud.get_note(db, note_id)
|
|
||||||
if not note or note.study_id != study_id:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在")
|
|
||||||
await _ensure_site_name_active(db, study_id, note.site_name)
|
|
||||||
note = await note_crud.update_note(db, note, note_in)
|
|
||||||
await audit_crud.log_action(
|
|
||||||
db,
|
|
||||||
study_id=study_id,
|
|
||||||
entity_type="knowledge_note",
|
|
||||||
entity_id=note_id,
|
|
||||||
action="UPDATE_KNOWLEDGE_NOTE",
|
|
||||||
detail=f"注意事项 {note_id} 已更新",
|
|
||||||
operator_id=current_user.id,
|
|
||||||
operator_role=current_user.role,
|
|
||||||
)
|
|
||||||
return KnowledgeNoteRead.model_validate(note)
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
|
||||||
"/notes/{note_id}",
|
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
|
||||||
dependencies=[Depends(require_api_permission("knowledge_notes:delete"))],
|
|
||||||
)
|
|
||||||
async def delete_note(
|
|
||||||
study_id: uuid.UUID,
|
|
||||||
note_id: uuid.UUID,
|
|
||||||
db: AsyncSession = Depends(get_db_session),
|
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
) -> None:
|
|
||||||
await _ensure_study_exists(db, study_id)
|
|
||||||
note = await note_crud.get_note(db, note_id)
|
|
||||||
if not note or note.study_id != study_id:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在")
|
|
||||||
await _ensure_site_name_active(db, study_id, note.site_name)
|
|
||||||
await note_crud.delete_note(db, note)
|
|
||||||
await audit_crud.log_action(
|
|
||||||
db,
|
|
||||||
study_id=study_id,
|
|
||||||
entity_type="knowledge_note",
|
|
||||||
entity_id=note_id,
|
|
||||||
action="DELETE_KNOWLEDGE_NOTE",
|
|
||||||
detail=f"注意事项 {note_id} 已删除",
|
|
||||||
operator_id=current_user.id,
|
|
||||||
operator_role=current_user.role,
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.core.deps import get_current_user, get_db_session, require_study_not_locked, require_api_permission
|
||||||
|
from app.crud import audit as audit_crud
|
||||||
|
from app.crud import precaution as precaution_crud
|
||||||
|
from app.crud import site as site_crud
|
||||||
|
from app.crud import study as study_crud
|
||||||
|
from app.schemas.precaution import PrecautionCreate, PrecautionRead, PrecautionUpdate
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID):
|
||||||
|
study = await study_crud.get(db, study_id)
|
||||||
|
if not study:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||||
|
return study
|
||||||
|
|
||||||
|
|
||||||
|
async def _ensure_site_name_active(db: AsyncSession, study_id: uuid.UUID, site_name: str | None):
|
||||||
|
if not site_name:
|
||||||
|
return
|
||||||
|
active_names = await site_crud.list_active_names(db, study_id)
|
||||||
|
if site_name not in active_names:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="中心已停用")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/precautions",
|
||||||
|
response_model=PrecautionRead,
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
dependencies=[Depends(require_api_permission("precautions:create"))],
|
||||||
|
)
|
||||||
|
async def create_precaution(
|
||||||
|
study_id: uuid.UUID,
|
||||||
|
precaution_in: PrecautionCreate,
|
||||||
|
db: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
) -> PrecautionRead:
|
||||||
|
await _ensure_study_exists(db, study_id)
|
||||||
|
await _ensure_site_name_active(db, study_id, precaution_in.site_name)
|
||||||
|
precaution = await precaution_crud.create_precaution(db, study_id, precaution_in, created_by=current_user.id)
|
||||||
|
await audit_crud.log_action(
|
||||||
|
db,
|
||||||
|
study_id=study_id,
|
||||||
|
entity_type="precaution",
|
||||||
|
entity_id=precaution.id,
|
||||||
|
action="CREATE_PRECAUTION",
|
||||||
|
detail=f"注意事项 {precaution.title} 已创建",
|
||||||
|
operator_id=current_user.id,
|
||||||
|
operator_role=current_user.role,
|
||||||
|
)
|
||||||
|
return PrecautionRead.model_validate(precaution)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/precautions",
|
||||||
|
response_model=list[PrecautionRead],
|
||||||
|
dependencies=[Depends(require_api_permission("precautions:read"))],
|
||||||
|
)
|
||||||
|
async def list_precautions(
|
||||||
|
study_id: uuid.UUID,
|
||||||
|
site_name: str | None = None,
|
||||||
|
keyword: str | None = None,
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
db: AsyncSession = Depends(get_db_session),
|
||||||
|
) -> list[PrecautionRead]:
|
||||||
|
await _ensure_study_exists(db, study_id)
|
||||||
|
items = await precaution_crud.list_precautions(db, study_id, site_name=site_name, keyword=keyword, skip=skip, limit=limit)
|
||||||
|
return [PrecautionRead.model_validate(item) for item in items]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/precautions/{precaution_id}",
|
||||||
|
response_model=PrecautionRead,
|
||||||
|
dependencies=[Depends(require_api_permission("precautions:read"))],
|
||||||
|
)
|
||||||
|
async def get_precaution(
|
||||||
|
study_id: uuid.UUID,
|
||||||
|
precaution_id: uuid.UUID,
|
||||||
|
db: AsyncSession = Depends(get_db_session),
|
||||||
|
) -> PrecautionRead:
|
||||||
|
await _ensure_study_exists(db, study_id)
|
||||||
|
precaution = await precaution_crud.get_precaution(db, precaution_id)
|
||||||
|
if not precaution or precaution.study_id != study_id:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在")
|
||||||
|
return PrecautionRead.model_validate(precaution)
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch(
|
||||||
|
"/precautions/{precaution_id}",
|
||||||
|
response_model=PrecautionRead,
|
||||||
|
dependencies=[Depends(require_api_permission("precautions:update"))],
|
||||||
|
)
|
||||||
|
async def update_precaution(
|
||||||
|
study_id: uuid.UUID,
|
||||||
|
precaution_id: uuid.UUID,
|
||||||
|
precaution_in: PrecautionUpdate,
|
||||||
|
db: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
) -> PrecautionRead:
|
||||||
|
await _ensure_study_exists(db, study_id)
|
||||||
|
precaution = await precaution_crud.get_precaution(db, precaution_id)
|
||||||
|
if not precaution or precaution.study_id != study_id:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在")
|
||||||
|
await _ensure_site_name_active(db, study_id, precaution.site_name)
|
||||||
|
precaution = await precaution_crud.update_precaution(db, precaution, precaution_in)
|
||||||
|
await audit_crud.log_action(
|
||||||
|
db,
|
||||||
|
study_id=study_id,
|
||||||
|
entity_type="precaution",
|
||||||
|
entity_id=precaution_id,
|
||||||
|
action="UPDATE_PRECAUTION",
|
||||||
|
detail=f"注意事项 {precaution_id} 已更新",
|
||||||
|
operator_id=current_user.id,
|
||||||
|
operator_role=current_user.role,
|
||||||
|
)
|
||||||
|
return PrecautionRead.model_validate(precaution)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/precautions/{precaution_id}",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(require_api_permission("precautions:delete"))],
|
||||||
|
)
|
||||||
|
async def delete_precaution(
|
||||||
|
study_id: uuid.UUID,
|
||||||
|
precaution_id: uuid.UUID,
|
||||||
|
db: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user=Depends(get_current_user),
|
||||||
|
) -> None:
|
||||||
|
await _ensure_study_exists(db, study_id)
|
||||||
|
precaution = await precaution_crud.get_precaution(db, precaution_id)
|
||||||
|
if not precaution or precaution.study_id != study_id:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在")
|
||||||
|
await _ensure_site_name_active(db, study_id, precaution.site_name)
|
||||||
|
await precaution_crud.delete_precaution(db, precaution)
|
||||||
|
await audit_crud.log_action(
|
||||||
|
db,
|
||||||
|
study_id=study_id,
|
||||||
|
entity_type="precaution",
|
||||||
|
entity_id=precaution_id,
|
||||||
|
action="DELETE_PRECAUTION",
|
||||||
|
detail=f"注意事项 {precaution_id} 已删除",
|
||||||
|
operator_id=current_user.id,
|
||||||
|
operator_role=current_user.role,
|
||||||
|
)
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import uuid
|
|
||||||
from typing import Sequence
|
|
||||||
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from app.models.knowledge_note import KnowledgeNote
|
|
||||||
from app.schemas.knowledge_note import KnowledgeNoteCreate, KnowledgeNoteUpdate
|
|
||||||
|
|
||||||
|
|
||||||
async def create_note(
|
|
||||||
db: AsyncSession,
|
|
||||||
study_id: uuid.UUID,
|
|
||||||
note_in: KnowledgeNoteCreate,
|
|
||||||
created_by: uuid.UUID | None,
|
|
||||||
) -> KnowledgeNote:
|
|
||||||
note = KnowledgeNote(
|
|
||||||
study_id=study_id,
|
|
||||||
site_name=note_in.site_name,
|
|
||||||
title=note_in.title,
|
|
||||||
content=note_in.content,
|
|
||||||
level=note_in.level,
|
|
||||||
created_by=created_by,
|
|
||||||
)
|
|
||||||
db.add(note)
|
|
||||||
await db.commit()
|
|
||||||
await db.refresh(note)
|
|
||||||
return note
|
|
||||||
|
|
||||||
|
|
||||||
async def get_note(db: AsyncSession, note_id: uuid.UUID) -> KnowledgeNote | None:
|
|
||||||
result = await db.execute(select(KnowledgeNote).where(KnowledgeNote.id == note_id))
|
|
||||||
return result.scalar_one_or_none()
|
|
||||||
|
|
||||||
|
|
||||||
async def list_notes(
|
|
||||||
db: AsyncSession,
|
|
||||||
study_id: uuid.UUID,
|
|
||||||
site_name: str | None = None,
|
|
||||||
keyword: str | None = None,
|
|
||||||
skip: int = 0,
|
|
||||||
limit: int = 100,
|
|
||||||
) -> Sequence[KnowledgeNote]:
|
|
||||||
stmt = (
|
|
||||||
select(KnowledgeNote)
|
|
||||||
.where(KnowledgeNote.study_id == study_id)
|
|
||||||
)
|
|
||||||
if site_name:
|
|
||||||
stmt = stmt.where(KnowledgeNote.site_name.ilike(f"%{site_name}%"))
|
|
||||||
if keyword:
|
|
||||||
stmt = stmt.where(KnowledgeNote.title.ilike(f"%{keyword}%"))
|
|
||||||
stmt = stmt.order_by(KnowledgeNote.updated_at.desc()).offset(skip).limit(limit)
|
|
||||||
result = await db.execute(stmt)
|
|
||||||
return result.scalars().all()
|
|
||||||
|
|
||||||
|
|
||||||
async def update_note(
|
|
||||||
db: AsyncSession, note: KnowledgeNote, note_in: KnowledgeNoteUpdate
|
|
||||||
) -> KnowledgeNote:
|
|
||||||
update_data = note_in.model_dump(exclude_unset=True)
|
|
||||||
for key, value in update_data.items():
|
|
||||||
setattr(note, key, value)
|
|
||||||
await db.commit()
|
|
||||||
await db.refresh(note)
|
|
||||||
return note
|
|
||||||
|
|
||||||
|
|
||||||
async def delete_note(db: AsyncSession, note: KnowledgeNote) -> None:
|
|
||||||
await db.delete(note)
|
|
||||||
await db.commit()
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import uuid
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.models.precaution import Precaution
|
||||||
|
from app.schemas.precaution import PrecautionCreate, PrecautionUpdate
|
||||||
|
|
||||||
|
|
||||||
|
async def create_precaution(
|
||||||
|
db: AsyncSession,
|
||||||
|
study_id: uuid.UUID,
|
||||||
|
precaution_in: PrecautionCreate,
|
||||||
|
created_by: uuid.UUID | None,
|
||||||
|
) -> Precaution:
|
||||||
|
precaution = Precaution(
|
||||||
|
study_id=study_id,
|
||||||
|
site_name=precaution_in.site_name,
|
||||||
|
title=precaution_in.title,
|
||||||
|
content=precaution_in.content,
|
||||||
|
level=precaution_in.level,
|
||||||
|
created_by=created_by,
|
||||||
|
)
|
||||||
|
db.add(precaution)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(precaution)
|
||||||
|
return precaution
|
||||||
|
|
||||||
|
|
||||||
|
async def get_precaution(db: AsyncSession, precaution_id: uuid.UUID) -> Precaution | None:
|
||||||
|
result = await db.execute(select(Precaution).where(Precaution.id == precaution_id))
|
||||||
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
|
||||||
|
async def list_precautions(
|
||||||
|
db: AsyncSession,
|
||||||
|
study_id: uuid.UUID,
|
||||||
|
site_name: str | None = None,
|
||||||
|
keyword: str | None = None,
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
) -> Sequence[Precaution]:
|
||||||
|
stmt = (
|
||||||
|
select(Precaution)
|
||||||
|
.where(Precaution.study_id == study_id)
|
||||||
|
)
|
||||||
|
if site_name:
|
||||||
|
stmt = stmt.where(Precaution.site_name.ilike(f"%{site_name}%"))
|
||||||
|
if keyword:
|
||||||
|
stmt = stmt.where(Precaution.title.ilike(f"%{keyword}%"))
|
||||||
|
stmt = stmt.order_by(Precaution.updated_at.desc()).offset(skip).limit(limit)
|
||||||
|
result = await db.execute(stmt)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
|
|
||||||
|
async def update_precaution(
|
||||||
|
db: AsyncSession, precaution: Precaution, precaution_in: PrecautionUpdate
|
||||||
|
) -> Precaution:
|
||||||
|
update_data = precaution_in.model_dump(exclude_unset=True)
|
||||||
|
for key, value in update_data.items():
|
||||||
|
setattr(precaution, key, value)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(precaution)
|
||||||
|
return precaution
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_precaution(db: AsyncSession, precaution: Precaution) -> None:
|
||||||
|
await db.delete(precaution)
|
||||||
|
await db.commit()
|
||||||
@@ -11,8 +11,8 @@ from sqlalchemy.orm import Mapped, mapped_column
|
|||||||
from app.db.base_class import Base
|
from app.db.base_class import Base
|
||||||
|
|
||||||
|
|
||||||
class KnowledgeNote(Base):
|
class Precaution(Base):
|
||||||
__tablename__ = "knowledge_notes"
|
__tablename__ = "precautions"
|
||||||
|
|
||||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||||
@@ -5,21 +5,21 @@ from typing import Optional
|
|||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
|
|
||||||
class KnowledgeNoteCreate(BaseModel):
|
class PrecautionCreate(BaseModel):
|
||||||
site_name: str
|
site_name: str
|
||||||
title: str
|
title: str
|
||||||
content: str
|
content: str
|
||||||
level: Optional[str] = None
|
level: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class KnowledgeNoteUpdate(BaseModel):
|
class PrecautionUpdate(BaseModel):
|
||||||
site_name: Optional[str] = None
|
site_name: Optional[str] = None
|
||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
content: Optional[str] = None
|
content: Optional[str] = None
|
||||||
level: Optional[str] = None
|
level: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class KnowledgeNoteRead(BaseModel):
|
class PrecautionRead(BaseModel):
|
||||||
id: uuid.UUID
|
id: uuid.UUID
|
||||||
study_id: uuid.UUID
|
study_id: uuid.UUID
|
||||||
site_name: str
|
site_name: str
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { apiDelete, apiGet, apiPatch, apiPost } from "./axios";
|
|
||||||
|
|
||||||
export const listKnowledgeNotes = (studyId: string, params?: Record<string, any>) =>
|
|
||||||
apiGet(`/api/v1/studies/${studyId}/knowledge/notes`, { params });
|
|
||||||
|
|
||||||
export const getKnowledgeNote = (studyId: string, noteId: string) =>
|
|
||||||
apiGet(`/api/v1/studies/${studyId}/knowledge/notes/${noteId}`);
|
|
||||||
|
|
||||||
export const createKnowledgeNote = (studyId: string, payload: Record<string, any>) =>
|
|
||||||
apiPost(`/api/v1/studies/${studyId}/knowledge/notes`, payload);
|
|
||||||
|
|
||||||
export const updateKnowledgeNote = (studyId: string, noteId: string, payload: Record<string, any>) =>
|
|
||||||
apiPatch(`/api/v1/studies/${studyId}/knowledge/notes/${noteId}`, payload);
|
|
||||||
|
|
||||||
export const deleteKnowledgeNote = (studyId: string, noteId: string) =>
|
|
||||||
apiDelete(`/api/v1/studies/${studyId}/knowledge/notes/${noteId}`);
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { apiDelete, apiGet, apiPatch, apiPost } from "./axios";
|
||||||
|
|
||||||
|
const baseUrl = (studyId: string) => `/api/v1/studies/${studyId}/shared-library/precautions`;
|
||||||
|
|
||||||
|
export const listPrecautions = (studyId: string, params?: Record<string, any>) =>
|
||||||
|
apiGet(baseUrl(studyId), { params });
|
||||||
|
|
||||||
|
export const getPrecaution = (studyId: string, precautionId: string) =>
|
||||||
|
apiGet(`${baseUrl(studyId)}/${precautionId}`);
|
||||||
|
|
||||||
|
export const createPrecaution = (studyId: string, payload: Record<string, any>) =>
|
||||||
|
apiPost(baseUrl(studyId), payload);
|
||||||
|
|
||||||
|
export const updatePrecaution = (studyId: string, precautionId: string, payload: Record<string, any>) =>
|
||||||
|
apiPatch(`${baseUrl(studyId)}/${precautionId}`, payload);
|
||||||
|
|
||||||
|
export const deletePrecaution = (studyId: string, precautionId: string) =>
|
||||||
|
apiDelete(`${baseUrl(studyId)}/${precautionId}`);
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<el-button @click="resetFilters">{{ TEXT.common.actions.reset }}</el-button>
|
<el-button @click="resetFilters">{{ TEXT.common.actions.reset }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-spacer"></div>
|
<div class="filter-spacer"></div>
|
||||||
<el-button v-if="canWriteSharedLibrary" type="primary" @click="goNew">
|
<el-button v-if="canWritePrecautions" type="primary" @click="goNew">
|
||||||
{{ TEXT.modules.knowledgeNotes.newTitle }}
|
{{ TEXT.modules.knowledgeNotes.newTitle }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
class="ctms-table"
|
class="ctms-table"
|
||||||
@row-click="onRowClick"
|
@row-click="onRowClick"
|
||||||
:row-class-name="noteRowClass"
|
:row-class-name="precautionRowClass"
|
||||||
table-layout="fixed"
|
table-layout="fixed"
|
||||||
>
|
>
|
||||||
<el-table-column prop="site_name" :label="TEXT.common.fields.site" show-overflow-tooltip />
|
<el-table-column prop="site_name" :label="TEXT.common.fields.site" show-overflow-tooltip />
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<el-table-column :label="TEXT.common.labels.actions" width="120">
|
<el-table-column :label="TEXT.common.labels.actions" width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="canWriteSharedLibrary"
|
v-if="canWritePrecautions"
|
||||||
link
|
link
|
||||||
type="danger"
|
type="danger"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -73,7 +73,7 @@ import { computed, onMounted, reactive, ref } from "vue";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { useStudyStore } from "../../store/study";
|
import { useStudyStore } from "../../store/study";
|
||||||
import { listKnowledgeNotes, deleteKnowledgeNote } from "../../api/knowledgeNotes";
|
import { listPrecautions, deletePrecaution } from "../../api/precautions";
|
||||||
import { fetchSites } from "../../api/sites";
|
import { fetchSites } from "../../api/sites";
|
||||||
import { displayDateTime } from "../../utils/display";
|
import { displayDateTime } from "../../utils/display";
|
||||||
import { usePermission } from "../../utils/permission";
|
import { usePermission } from "../../utils/permission";
|
||||||
@@ -98,7 +98,7 @@ const siteActiveMap = computed(() => {
|
|||||||
return map;
|
return map;
|
||||||
});
|
});
|
||||||
const isInactiveSite = (siteName?: string) => !!siteName && siteActiveMap.value[siteName] === false;
|
const isInactiveSite = (siteName?: string) => !!siteName && siteActiveMap.value[siteName] === false;
|
||||||
const noteRowClass = ({ row }: { row: any }) =>
|
const precautionRowClass = ({ row }: { row: any }) =>
|
||||||
`${row?.id ? "clickable-row" : ""}${isInactiveSite(row?.site_name) ? " row-inactive" : ""}`.trim();
|
`${row?.id ? "clickable-row" : ""}${isInactiveSite(row?.site_name) ? " row-inactive" : ""}`.trim();
|
||||||
const filteredItems = computed(() => {
|
const filteredItems = computed(() => {
|
||||||
const siteKeyword = filters.site_name.trim().toLowerCase();
|
const siteKeyword = filters.site_name.trim().toLowerCase();
|
||||||
@@ -112,7 +112,7 @@ const filteredItems = computed(() => {
|
|||||||
const sortedItems = computed(() =>
|
const sortedItems = computed(() =>
|
||||||
[...filteredItems.value].sort((a, b) => Number(isInactiveSite(a?.site_name)) - Number(isInactiveSite(b?.site_name)))
|
[...filteredItems.value].sort((a, b) => Number(isInactiveSite(a?.site_name)) - Number(isInactiveSite(b?.site_name)))
|
||||||
);
|
);
|
||||||
const canWriteSharedLibrary = computed(() => can("shared.library.write"));
|
const canWritePrecautions = computed(() => can("precautions.write"));
|
||||||
|
|
||||||
const loadSites = async () => {
|
const loadSites = async () => {
|
||||||
const studyId = study.currentStudy?.id;
|
const studyId = study.currentStudy?.id;
|
||||||
@@ -130,7 +130,7 @@ const load = async () => {
|
|||||||
if (!studyId) return;
|
if (!studyId) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const { data } = await listKnowledgeNotes(studyId, {}) as any;
|
const { data } = await listPrecautions(studyId, {}) as any;
|
||||||
items.value = Array.isArray(data) ? data : data.items || [];
|
items.value = Array.isArray(data) ? data : data.items || [];
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
|
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
|
||||||
@@ -144,8 +144,8 @@ const resetFilters = () => {
|
|||||||
filters.keyword = "";
|
filters.keyword = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const goNew = () => router.push("/knowledge/notes/new");
|
const goNew = () => router.push("/knowledge/precautions/new");
|
||||||
const goDetail = (id: string) => router.push(`/knowledge/notes/${id}`);
|
const goDetail = (id: string) => router.push(`/knowledge/precautions/${id}`);
|
||||||
const onRowClick = (row: any) => {
|
const onRowClick = (row: any) => {
|
||||||
if (!row?.id) return;
|
if (!row?.id) return;
|
||||||
goDetail(row.id);
|
goDetail(row.id);
|
||||||
@@ -161,7 +161,7 @@ const remove = async (row: any) => {
|
|||||||
const ok = await ElMessageBox.confirm(TEXT.common.confirm.delete, TEXT.common.labels.tips).catch(() => null);
|
const ok = await ElMessageBox.confirm(TEXT.common.confirm.delete, TEXT.common.labels.tips).catch(() => null);
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
try {
|
try {
|
||||||
await deleteKnowledgeNote(studyId, row.id);
|
await deletePrecaution(studyId, row.id);
|
||||||
ElMessage.success(TEXT.common.messages.deleteSuccess);
|
ElMessage.success(TEXT.common.messages.deleteSuccess);
|
||||||
load();
|
load();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
+12
-12
@@ -21,10 +21,10 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<AttachmentList
|
<AttachmentList
|
||||||
v-if="noteId"
|
v-if="precautionId"
|
||||||
:study-id="studyId"
|
:study-id="studyId"
|
||||||
entity-type="knowledge_note"
|
entity-type="precaution"
|
||||||
:entity-id="noteId"
|
:entity-id="precautionId"
|
||||||
:readonly="isReadOnly"
|
:readonly="isReadOnly"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,7 +35,7 @@ import { computed, onMounted, reactive, ref } from "vue";
|
|||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { useStudyStore } from "../../store/study";
|
import { useStudyStore } from "../../store/study";
|
||||||
import { getKnowledgeNote } from "../../api/knowledgeNotes";
|
import { getPrecaution } from "../../api/precautions";
|
||||||
import { fetchSites } from "../../api/sites";
|
import { fetchSites } from "../../api/sites";
|
||||||
import AttachmentList from "../../components/attachments/AttachmentList.vue";
|
import AttachmentList from "../../components/attachments/AttachmentList.vue";
|
||||||
import { usePermission } from "../../utils/permission";
|
import { usePermission } from "../../utils/permission";
|
||||||
@@ -46,7 +46,7 @@ const router = useRouter();
|
|||||||
const study = useStudyStore();
|
const study = useStudyStore();
|
||||||
const { can } = usePermission();
|
const { can } = usePermission();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const noteId = route.params.noteId as string;
|
const precautionId = route.params.id as string;
|
||||||
const studyId = study.currentStudy?.id || "";
|
const studyId = study.currentStudy?.id || "";
|
||||||
const sites = ref<any[]>([]);
|
const sites = ref<any[]>([]);
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@ const siteActiveMap = computed(() => {
|
|||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
});
|
});
|
||||||
const canWriteSharedLibrary = computed(() => can("shared.library.write"));
|
const canWritePrecautions = computed(() => can("precautions.write"));
|
||||||
const isReadOnly = computed(() => !canWriteSharedLibrary.value || (!!detail.site_name && siteActiveMap.value[detail.site_name] === false));
|
const isReadOnly = computed(() => !canWritePrecautions.value || (!!detail.site_name && siteActiveMap.value[detail.site_name] === false));
|
||||||
|
|
||||||
const loadSites = async () => {
|
const loadSites = async () => {
|
||||||
if (!studyId) return;
|
if (!studyId) return;
|
||||||
@@ -78,10 +78,10 @@ const loadSites = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
if (!studyId || !noteId) return;
|
if (!studyId || !precautionId) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const { data } = await getKnowledgeNote(studyId, noteId);
|
const { data } = await getPrecaution(studyId, precautionId);
|
||||||
Object.assign(detail, data);
|
Object.assign(detail, data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
|
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
|
||||||
@@ -92,12 +92,12 @@ const load = async () => {
|
|||||||
|
|
||||||
const goEdit = () => {
|
const goEdit = () => {
|
||||||
if (isReadOnly.value) {
|
if (isReadOnly.value) {
|
||||||
ElMessage.warning(canWriteSharedLibrary.value ? "中心已停用" : "权限不足");
|
ElMessage.warning(canWritePrecautions.value ? "中心已停用" : "权限不足");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push(`/knowledge/notes/${noteId}/edit`);
|
router.push(`/knowledge/precautions/${precautionId}/edit`);
|
||||||
};
|
};
|
||||||
const goBack = () => router.push("/knowledge/notes");
|
const goBack = () => router.push("/knowledge/precautions");
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadSites();
|
await loadSites();
|
||||||
+17
-17
@@ -30,10 +30,10 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<AttachmentList
|
<AttachmentList
|
||||||
v-if="isEdit && noteId"
|
v-if="isEdit && precautionId"
|
||||||
:study-id="studyId"
|
:study-id="studyId"
|
||||||
entity-type="knowledge_note"
|
entity-type="precaution"
|
||||||
:entity-id="noteId"
|
:entity-id="precautionId"
|
||||||
:readonly="isReadOnly"
|
:readonly="isReadOnly"
|
||||||
/>
|
/>
|
||||||
<el-card v-else class="hint-card unified-shell">
|
<el-card v-else class="hint-card unified-shell">
|
||||||
@@ -47,7 +47,7 @@ import { computed, onMounted, reactive, ref } from "vue";
|
|||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { useStudyStore } from "../../store/study";
|
import { useStudyStore } from "../../store/study";
|
||||||
import { createKnowledgeNote, getKnowledgeNote, updateKnowledgeNote } from "../../api/knowledgeNotes";
|
import { createPrecaution, getPrecaution, updatePrecaution } from "../../api/precautions";
|
||||||
import { fetchSites } from "../../api/sites";
|
import { fetchSites } from "../../api/sites";
|
||||||
import AttachmentList from "../../components/attachments/AttachmentList.vue";
|
import AttachmentList from "../../components/attachments/AttachmentList.vue";
|
||||||
import StateEmpty from "../../components/StateEmpty.vue";
|
import StateEmpty from "../../components/StateEmpty.vue";
|
||||||
@@ -61,8 +61,8 @@ const { can } = usePermission();
|
|||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const sites = ref<any[]>([]);
|
const sites = ref<any[]>([]);
|
||||||
|
|
||||||
const noteId = computed(() => route.params.noteId as string | undefined);
|
const precautionId = computed(() => route.params.id as string | undefined);
|
||||||
const isEdit = computed(() => !!noteId.value);
|
const isEdit = computed(() => !!precautionId.value);
|
||||||
const studyId = computed(() => study.currentStudy?.id || "");
|
const studyId = computed(() => study.currentStudy?.id || "");
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
@@ -79,8 +79,8 @@ const siteActiveMap = computed(() => {
|
|||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
});
|
});
|
||||||
const canWriteSharedLibrary = computed(() => can("shared.library.write"));
|
const canWritePrecautions = computed(() => can("precautions.write"));
|
||||||
const isReadOnly = computed(() => !canWriteSharedLibrary.value || (isEdit.value && !!form.site_name && siteActiveMap.value[form.site_name] === false));
|
const isReadOnly = computed(() => !canWritePrecautions.value || (isEdit.value && !!form.site_name && siteActiveMap.value[form.site_name] === false));
|
||||||
|
|
||||||
const loadSites = async () => {
|
const loadSites = async () => {
|
||||||
if (!studyId.value) return;
|
if (!studyId.value) return;
|
||||||
@@ -93,9 +93,9 @@ const loadSites = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
if (!isEdit.value || !studyId.value || !noteId.value) return;
|
if (!isEdit.value || !studyId.value || !precautionId.value) return;
|
||||||
try {
|
try {
|
||||||
const { data } = await getKnowledgeNote(studyId.value, noteId.value);
|
const { data } = await getPrecaution(studyId.value, precautionId.value);
|
||||||
Object.assign(form, {
|
Object.assign(form, {
|
||||||
site_name: data.site_name || "",
|
site_name: data.site_name || "",
|
||||||
title: data.title || "",
|
title: data.title || "",
|
||||||
@@ -110,7 +110,7 @@ const load = async () => {
|
|||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (!studyId.value) return;
|
if (!studyId.value) return;
|
||||||
if (isReadOnly.value) {
|
if (isReadOnly.value) {
|
||||||
ElMessage.warning(canWriteSharedLibrary.value ? "中心已停用" : "权限不足");
|
ElMessage.warning(canWritePrecautions.value ? "中心已停用" : "权限不足");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!form.site_name) {
|
if (!form.site_name) {
|
||||||
@@ -129,14 +129,14 @@ const submit = async () => {
|
|||||||
level: form.level || null,
|
level: form.level || null,
|
||||||
content: form.content,
|
content: form.content,
|
||||||
};
|
};
|
||||||
if (isEdit.value && noteId.value) {
|
if (isEdit.value && precautionId.value) {
|
||||||
await updateKnowledgeNote(studyId.value, noteId.value, payload);
|
await updatePrecaution(studyId.value, precautionId.value, payload);
|
||||||
ElMessage.success(TEXT.common.messages.saveSuccess);
|
ElMessage.success(TEXT.common.messages.saveSuccess);
|
||||||
router.push(`/knowledge/notes/${noteId.value}`);
|
router.push(`/knowledge/precautions/${precautionId.value}`);
|
||||||
} else {
|
} else {
|
||||||
const { data } = await createKnowledgeNote(studyId.value, payload);
|
const { data } = await createPrecaution(studyId.value, payload);
|
||||||
ElMessage.success(TEXT.common.messages.createSuccess);
|
ElMessage.success(TEXT.common.messages.createSuccess);
|
||||||
router.push(`/knowledge/notes/${data.id}`);
|
router.push(`/knowledge/precautions/${data.id}`);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.saveFailed);
|
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.saveFailed);
|
||||||
@@ -145,7 +145,7 @@ const submit = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const goBack = () => router.push("/knowledge/notes");
|
const goBack = () => router.push("/knowledge/precautions");
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadSites();
|
await loadSites();
|
||||||
Reference in New Issue
Block a user