+
@@ -603,15 +625,18 @@ watch(activeLibraryProject, (project) => {
bodyClass="px-6 pb-3 pt-1"
@close="closeDeleteDialog"
>
-
- 确定删除“{{ deleteTarget?.name }}”吗?如果项目里已有题目或笔记,后端会阻止删除。
+
+ 确定删除”{{ deleteTarget?.name }}”吗?
+
+
+ 项目内的所有题目或笔记将一并删除,且不可恢复。
取消
-
- {{ deleteSaving ? '删除中...' : '删除' }}
+
+ {{ deleteSaving ? '删除中...' : deleteCountdown > 0 ? `删除 (${deleteCountdown}s)` : '删除' }}
From 1a50cad19b4348dc2d980a9d8f95b122ffb2868b Mon Sep 17 00:00:00 2001
From: wyh <13423906455@163.com>
Date: Mon, 8 Jun 2026 10:21:05 +0800
Subject: [PATCH 09/10] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E6=A8=A1?=
=?UTF-8?q?=E5=9E=8B=E5=A4=9A=E9=80=89=EF=BC=8C=E4=BF=AE=E5=A4=8D=E7=AC=94?=
=?UTF-8?q?=E8=AE=B0=E7=BA=A7=E8=81=94=E5=88=A0=E9=99=A4=EF=BC=8C=E4=BF=9D?=
=?UTF-8?q?=E5=AD=98=E9=85=8D=E7=BD=AE=E5=90=8E=E5=88=B7=E6=96=B0=E6=A8=A1?=
=?UTF-8?q?=E5=9E=8B=E5=88=97=E8=A1=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- ProviderDialog 模型选择改为单选,移除逗号拼接逻辑
- 后端移除 model_name 逗号分割逻辑,split_models 返回单元素列表
- 删除笔记本项目时级联清理 NoteTagMapping
- 设置页保存配置后同时刷新 modelOptions,工作台立即显示新模型名
- 笔记删除后刷新项目计数
---
backend/core/config.py | 2 +-
backend/db/crud/projects.py | 9 ++--
backend/routes/settings.py | 4 +-
.../features/app/settings/ProviderDialog.vue | 45 ++++++++-----------
frontend/src/composables/useSplitPipeline.js | 9 +---
frontend/src/views/app/AppLayout.vue | 4 +-
frontend/src/views/app/NoteView.vue | 4 +-
frontend/src/views/app/SettingsView.vue | 8 +---
frontend/src/views/app/WorkspaceView.vue | 16 +++----
9 files changed, 38 insertions(+), 63 deletions(-)
diff --git a/backend/core/config.py b/backend/core/config.py
index a5038b69..0fcc28e6 100644
--- a/backend/core/config.py
+++ b/backend/core/config.py
@@ -504,7 +504,7 @@ def load_providers_from_db(
if owns_db:
db = SessionLocal()
try:
- for category in [("openai"), ("anthropic")]:
+ for category in ("openai", "anthropic"):
provider = get_active_provider(db, user_id, category)
if provider and provider.api_key:
cfg = self.build_provider_config(
diff --git a/backend/db/crud/projects.py b/backend/db/crud/projects.py
index 6b3b3164..993584ab 100644
--- a/backend/db/crud/projects.py
+++ b/backend/db/crud/projects.py
@@ -5,8 +5,8 @@
from sqlalchemy.orm import Session
from db.models import (
- ChatSession, Note, Project, Question, QuestionEmbedding,
- QuestionTagMapping, UploadBatch,
+ ChatSession, Note, NoteTagMapping, Project, Question,
+ QuestionEmbedding, QuestionTagMapping, UploadBatch,
)
@@ -146,7 +146,10 @@ def delete_project(db: Session, project_id: int, user_id=None) -> bool:
db.query(QuestionTagMapping).filter(QuestionTagMapping.question_id.in_(question_ids)).delete(synchronize_session=False)
db.query(Question).filter(Question.id.in_(question_ids)).delete(synchronize_session=False)
- db.query(Note).filter(Note.project_id == project.id).delete()
+ note_ids = [n.id for n in db.query(Note.id).filter(Note.project_id == project.id).all()]
+ if note_ids:
+ db.query(NoteTagMapping).filter(NoteTagMapping.note_id.in_(note_ids)).delete(synchronize_session=False)
+ db.query(Note).filter(Note.id.in_(note_ids)).delete(synchronize_session=False)
db.query(UploadBatch).filter(UploadBatch.project_id == project.id).delete()
db.delete(project)
diff --git a/backend/routes/settings.py b/backend/routes/settings.py
index 817a9492..c6e4ebc6 100644
--- a/backend/routes/settings.py
+++ b/backend/routes/settings.py
@@ -298,7 +298,7 @@ def list_models():
else:
provider = crud.get_active_provider(db, user_id, provider_type)
if provider:
- api_key = api_key or provider.api_key or ""
+ api_key = provider.api_key or ""
base_url = base_url or provider.base_url or ""
if not api_key and provider_type in ("openai", "anthropic"):
system_provider = crud.get_active_system_provider(db, provider_type)
@@ -409,7 +409,7 @@ def test_paddleocr():
else:
provider = crud.get_active_provider(db, user_id, "paddleocr")
if provider:
- api_token = api_token or provider.api_key or ""
+ api_token = provider.api_key or ""
api_url = api_url or provider.base_url or ""
if not api_token or not api_url:
system_provider = crud.get_active_system_provider(db, "paddleocr")
diff --git a/frontend/src/components/features/app/settings/ProviderDialog.vue b/frontend/src/components/features/app/settings/ProviderDialog.vue
index 71095b7e..b4427553 100644
--- a/frontend/src/components/features/app/settings/ProviderDialog.vue
+++ b/frontend/src/components/features/app/settings/ProviderDialog.vue
@@ -21,13 +21,12 @@ const emit = defineEmits(['close', 'confirm'])
const isEdit = computed(() => !!props.editData)
-const typeConfig = computed(() => ({
+const PROVIDER_TYPE_CONFIGS = {
openai: {
- title: isEdit.value ? '编辑 OpenAI 兼容供应商' : '添加 OpenAI 兼容供应商',
- iconBg: 'bg-gray-50 dark:bg-white/[0.04] border border-gray-100 dark:border-white/[0.08]',
+ titleAdd: '添加 OpenAI 兼容供应商',
+ titleEdit: '编辑 OpenAI 兼容供应商',
iconCls: 'fa-bolt text-slate-600 dark:text-slate-400',
imgIcon: '/src/assets/provider-openai.svg',
- btnCls: 'bg-slate-900 hover:bg-slate-800 text-white dark:bg-[#f7f8f8] dark:hover:bg-white dark:text-[#1b1b1d]',
namePlaceholder: '例如:DeepSeek / Qwen / Moonshot',
urlPlaceholder: '留空使用 OpenAI 官方,或填入 https://api.deepseek.com 等',
modelPlaceholder: 'gpt-4o-mini',
@@ -37,11 +36,10 @@ const typeConfig = computed(() => ({
urlLabel: 'Base URL',
},
anthropic: {
- title: isEdit.value ? '编辑 Anthropic 供应商' : '添加 Anthropic 供应商',
- iconBg: 'bg-gray-50 dark:bg-white/[0.04] border border-gray-100 dark:border-white/[0.08]',
+ titleAdd: '添加 Anthropic 供应商',
+ titleEdit: '编辑 Anthropic 供应商',
iconCls: 'fa-brain text-slate-600 dark:text-slate-400',
imgIcon: '/src/assets/provider-anthropic.svg',
- btnCls: 'bg-slate-900 hover:bg-slate-800 text-white dark:bg-[#f7f8f8] dark:hover:bg-white dark:text-[#1b1b1d]',
namePlaceholder: '例如:Claude Official',
urlPlaceholder: '留空使用 Anthropic 官方',
modelPlaceholder: 'claude-sonnet-4-20250514',
@@ -51,11 +49,10 @@ const typeConfig = computed(() => ({
urlLabel: 'Base URL',
},
paddleocr: {
- title: isEdit.value ? '编辑 PaddleOCR 服务' : '添加 PaddleOCR 服务',
- iconBg: 'bg-gray-50 dark:bg-white/[0.04] border border-gray-100 dark:border-white/[0.08]',
+ titleAdd: '添加 PaddleOCR 服务',
+ titleEdit: '编辑 PaddleOCR 服务',
iconCls: 'fa-eye text-slate-600 dark:text-slate-400',
imgIcon: '/src/assets/provider-paddleocr.svg',
- btnCls: 'bg-slate-900 hover:bg-slate-800 text-white dark:bg-[#f7f8f8] dark:hover:bg-white dark:text-[#1b1b1d]',
namePlaceholder: '例如:PaddleOCR 官方',
urlPlaceholder: 'https://paddleocr.aistudio-app.com/api/v2/ocr/jobs',
modelPlaceholder: 'PaddleOCR-VL-1.5',
@@ -64,7 +61,17 @@ const typeConfig = computed(() => ({
secretPlaceholder: '输入 API Token',
urlLabel: 'API URL',
},
-}[props.type]))
+}
+
+const SHARED_ICON_BG = 'bg-gray-50 dark:bg-white/[0.04] border border-gray-100 dark:border-white/[0.08]'
+const SHARED_BTN_CLS = 'bg-slate-900 hover:bg-slate-800 text-white dark:bg-[#f7f8f8] dark:hover:bg-white dark:text-[#1b1b1d]'
+
+const typeConfig = computed(() => ({
+ ...PROVIDER_TYPE_CONFIGS[props.type],
+ title: isEdit.value ? PROVIDER_TYPE_CONFIGS[props.type].titleEdit : PROVIDER_TYPE_CONFIGS[props.type].titleAdd,
+ iconBg: SHARED_ICON_BG,
+ btnCls: SHARED_BTN_CLS,
+}))
const defaultForm = () => ({
name: '',
@@ -203,8 +210,6 @@ const confirm = () => {
emit('confirm', { ...form.value })
}
-const inputCls = 'w-full rounded-xl border border-slate-200/80 bg-white px-4 py-2.5 text-sm text-slate-800 placeholder-slate-400 transition-colors focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-slate-800/80 dark:text-slate-200 dark:placeholder-slate-500'
-
// 自定义下拉
const openDropdown = ref(null) // 'model_name' | 'light_model_name' | null
const toggleDropdown = (field) => {
@@ -414,20 +419,6 @@ const selectOption = (field, value) => {