fix(faq): persist active defaults for medical consults
This commit is contained in:
@@ -4,7 +4,7 @@ from typing import Optional
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import DateTime, ForeignKey, String, Text, UniqueConstraint, func
|
from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text, UniqueConstraint, func
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ class FaqCategory(Base):
|
|||||||
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||||
sort_order: Mapped[int] = mapped_column(nullable=False, default=0)
|
sort_order: Mapped[int] = mapped_column(nullable=False, default=0)
|
||||||
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default="true")
|
||||||
icon: Mapped[Optional[str]] = mapped_column(String(30), nullable=True)
|
icon: Mapped[Optional[str]] = mapped_column(String(30), nullable=True)
|
||||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||||
updated_at: Mapped[datetime] = mapped_column(
|
updated_at: Mapped[datetime] = mapped_column(
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class FaqItem(Base):
|
|||||||
answer: Mapped[str] = mapped_column(Text, nullable=False)
|
answer: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
keywords: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
keywords: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||||
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
||||||
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default="true")
|
||||||
created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||||
updated_at: Mapped[datetime] = mapped_column(
|
updated_at: Mapped[datetime] = mapped_column(
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from sqlalchemy import text
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.crud.faq_category import create_category
|
||||||
|
from app.crud.faq_item import create_item
|
||||||
|
from app.schemas.faq import CategoryCreate, FaqCreate
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_category_defaults_to_active(db_session):
|
||||||
|
result = await db_session.execute(text("SELECT id FROM studies LIMIT 1"))
|
||||||
|
study_id = result.scalar_one()
|
||||||
|
|
||||||
|
category = await create_category(
|
||||||
|
db_session,
|
||||||
|
CategoryCreate(study_id=study_id, name="AE", sort_order=0),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert category.is_active is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_item_defaults_to_active(db_session):
|
||||||
|
result = await db_session.execute(text("SELECT id FROM studies LIMIT 1"))
|
||||||
|
study_id = result.scalar_one()
|
||||||
|
category = await create_category(
|
||||||
|
db_session,
|
||||||
|
CategoryCreate(study_id=study_id, name=f"AE-{uuid.uuid4().hex[:8]}", sort_order=0),
|
||||||
|
)
|
||||||
|
|
||||||
|
item = await create_item(
|
||||||
|
db_session,
|
||||||
|
FaqCreate(study_id=study_id, category_id=category.id, question="AE 如何记录?"),
|
||||||
|
created_by=uuid.uuid4(),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert item.is_active is True
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<template #reference>
|
<template #reference>
|
||||||
<div class="row-icon" :class="{ 'row-icon--selected': cat.icon }" title="选择图标">
|
<div class="row-icon" :class="{ 'row-icon--selected': cat.icon }" title="选择图标">
|
||||||
<FaqIcon v-if="cat.icon" :icon="cat.icon" />
|
<FaqIcon v-if="cat.icon" :icon="cat.icon" />
|
||||||
<span v-else class="row-icon-placeholder"><Grid /></span>
|
<span v-else class="row-icon-placeholder"><FaqIcon icon="fa-icons" /></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="icon-popover-grid">
|
<div class="icon-popover-grid">
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { ArrowDown, ArrowUp, Delete, Grid, Plus } from "@element-plus/icons-vue";
|
import { ArrowDown, ArrowUp, Delete, Plus } from "@element-plus/icons-vue";
|
||||||
import FaqIcon from "./FaqIcon.vue";
|
import FaqIcon from "./FaqIcon.vue";
|
||||||
import { createFaqCategory, updateFaqCategory, deleteFaqCategory } from "../api/faqs";
|
import { createFaqCategory, updateFaqCategory, deleteFaqCategory } from "../api/faqs";
|
||||||
import type { FaqCategory } from "../api/faqs";
|
import type { FaqCategory } from "../api/faqs";
|
||||||
|
|||||||
@@ -1,58 +1,164 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="iconComponent" />
|
<svg
|
||||||
|
class="faq-icon"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
aria-hidden="true"
|
||||||
|
focusable="false"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
v-for="path in iconPaths"
|
||||||
|
:key="path"
|
||||||
|
:d="path"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import type { Component } from "vue";
|
|
||||||
import {
|
|
||||||
Calendar,
|
|
||||||
Camera,
|
|
||||||
Collection,
|
|
||||||
DocumentChecked,
|
|
||||||
Filter,
|
|
||||||
FirstAidKit,
|
|
||||||
Flag,
|
|
||||||
Grid,
|
|
||||||
Help,
|
|
||||||
List,
|
|
||||||
Management,
|
|
||||||
Memo,
|
|
||||||
More,
|
|
||||||
OfficeBuilding,
|
|
||||||
Reading,
|
|
||||||
ScaleToOriginal,
|
|
||||||
Sell,
|
|
||||||
SetUp,
|
|
||||||
User,
|
|
||||||
} from "@element-plus/icons-vue";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
icon?: string | null;
|
icon?: string | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const iconMap: Record<string, Component> = {
|
const fallbackIcon = "fa-th-large";
|
||||||
"fa-th-large": Grid,
|
|
||||||
"fa-ruler-combined": ScaleToOriginal,
|
const icons: Record<string, string[]> = {
|
||||||
"fa-calendar-check": Calendar,
|
"fa-th-large": [
|
||||||
"fa-filter": Filter,
|
"M4 4h7v7H4z",
|
||||||
"fa-flag-checkered": Flag,
|
"M13 4h7v7h-7z",
|
||||||
"fa-capsules": FirstAidKit,
|
"M4 13h7v7H4z",
|
||||||
"fa-prescription-bottle": Sell,
|
"M13 13h7v7h-7z",
|
||||||
"fa-vial": SetUp,
|
],
|
||||||
"fa-heartbeat": Help,
|
"fa-ruler-combined": [
|
||||||
"fa-pills": FirstAidKit,
|
"M4 17 17 4l3 3L7 20H4z",
|
||||||
"fa-file-medical": DocumentChecked,
|
"M14 7l3 3",
|
||||||
"fa-camera": Camera,
|
"M11 10l2 2",
|
||||||
"fa-notes-medical": Memo,
|
"M8 13l3 3",
|
||||||
"fa-clipboard-list": List,
|
],
|
||||||
"fa-syringe": FirstAidKit,
|
"fa-calendar-check": [
|
||||||
"fa-flask": Management,
|
"M5 5h14v15H5z",
|
||||||
"fa-book-medical": Reading,
|
"M8 3v4",
|
||||||
"fa-user-md": User,
|
"M16 3v4",
|
||||||
"fa-hospital": OfficeBuilding,
|
"M5 10h14",
|
||||||
"fa-ellipsis-h": More,
|
"M8 15l2 2 5-5",
|
||||||
|
],
|
||||||
|
"fa-filter": [
|
||||||
|
"M4 5h16l-6 7v5l-4 2v-7z",
|
||||||
|
],
|
||||||
|
"fa-flag-checkered": [
|
||||||
|
"M5 21V4",
|
||||||
|
"M5 5h12l-2 4 2 4H5",
|
||||||
|
"M9 5v8",
|
||||||
|
"M13 5v8",
|
||||||
|
"M7 9h10",
|
||||||
|
],
|
||||||
|
"fa-capsules": [
|
||||||
|
"M10 8 6 12a4 4 0 0 0 6 6l4-4a4 4 0 0 0-6-6z",
|
||||||
|
"M8 14l4 4",
|
||||||
|
"M15 5a4 4 0 0 1 6 6l-3 3-6-6z",
|
||||||
|
],
|
||||||
|
"fa-prescription-bottle": [
|
||||||
|
"M8 3h8",
|
||||||
|
"M9 6h6l1 3v12H8V9z",
|
||||||
|
"M10 13h4",
|
||||||
|
"M12 11v4",
|
||||||
|
],
|
||||||
|
"fa-vial": [
|
||||||
|
"M10 3h8",
|
||||||
|
"M13 4v6l-7 7a3 3 0 0 0 4 4l7-7h4",
|
||||||
|
"M9 16l5 5",
|
||||||
|
],
|
||||||
|
"fa-heartbeat": [
|
||||||
|
"M20 8c0-3-4-5-8-1-4-4-8-2-8 1 0 6 8 11 8 11s3-2 5-5",
|
||||||
|
"M3 13h4l2-4 4 8 2-4h6",
|
||||||
|
],
|
||||||
|
"fa-pills": [
|
||||||
|
"M9 7 6 10a4 4 0 0 0 6 6l3-3a4 4 0 0 0-6-6z",
|
||||||
|
"M8 14l4 4",
|
||||||
|
"M16 8h5",
|
||||||
|
"M18.5 5.5v5",
|
||||||
|
],
|
||||||
|
"fa-file-medical": [
|
||||||
|
"M6 3h9l3 3v18H6z",
|
||||||
|
"M14 3v5h5",
|
||||||
|
"M10 15h6",
|
||||||
|
"M13 12v6",
|
||||||
|
],
|
||||||
|
"fa-camera": [
|
||||||
|
"M4 8h4l2-3h4l2 3h4v13H4z",
|
||||||
|
"M9 14a3 3 0 1 0 6 0 3 3 0 0 0-6 0z",
|
||||||
|
],
|
||||||
|
"fa-notes-medical": [
|
||||||
|
"M7 3h10v4H7z",
|
||||||
|
"M5 5h14v19H5z",
|
||||||
|
"M9 14h6",
|
||||||
|
"M12 11v6",
|
||||||
|
],
|
||||||
|
"fa-clipboard-list": [
|
||||||
|
"M8 4h8v4H8z",
|
||||||
|
"M6 6h12v18H6z",
|
||||||
|
"M9 12h1",
|
||||||
|
"M13 12h4",
|
||||||
|
"M9 16h1",
|
||||||
|
"M13 16h4",
|
||||||
|
],
|
||||||
|
"fa-syringe": [
|
||||||
|
"M14 4l6 6",
|
||||||
|
"M16 2l-3 3",
|
||||||
|
"M22 8l-3 3",
|
||||||
|
"M4 20l7-7",
|
||||||
|
"M8 10l6 6",
|
||||||
|
"M10 8l6 6-7 7H4v-5z",
|
||||||
|
],
|
||||||
|
"fa-flask": [
|
||||||
|
"M9 3h6",
|
||||||
|
"M10 3v6l-5 9a3 3 0 0 0 3 4h8a3 3 0 0 0 3-4l-5-9V3",
|
||||||
|
"M8 16h8",
|
||||||
|
],
|
||||||
|
"fa-book-medical": [
|
||||||
|
"M6 4h13v17H7a3 3 0 0 1 0-6h12",
|
||||||
|
"M10 10h6",
|
||||||
|
"M13 7v6",
|
||||||
|
],
|
||||||
|
"fa-user-md": [
|
||||||
|
"M9 8a4 4 0 1 0 8 0 4 4 0 0 0-8 0z",
|
||||||
|
"M5 21a8 8 0 0 1 16 0",
|
||||||
|
"M8 18l4 3 4-3",
|
||||||
|
],
|
||||||
|
"fa-hospital": [
|
||||||
|
"M5 21V5h14v16",
|
||||||
|
"M3 21h18",
|
||||||
|
"M10 10h4",
|
||||||
|
"M12 8v6",
|
||||||
|
"M8 17h2",
|
||||||
|
"M14 17h2",
|
||||||
|
],
|
||||||
|
"fa-ellipsis-h": [
|
||||||
|
"M5 12h.01",
|
||||||
|
"M12 12h.01",
|
||||||
|
"M19 12h.01",
|
||||||
|
],
|
||||||
|
"fa-icons": [
|
||||||
|
"M4 5h7v7H4z",
|
||||||
|
"M14 4l6 6",
|
||||||
|
"M17 4v6h6",
|
||||||
|
"M6 19a3 3 0 1 0 0-6 3 3 0 0 0 0 6z",
|
||||||
|
"M15 15h6v6h-6z",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const iconComponent = computed(() => iconMap[props.icon || ""] || Collection);
|
const iconPaths = computed(() => icons[props.icon || fallbackIcon] || icons[fallbackIcon]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.faq-icon {
|
||||||
|
display: block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user