From 2826aa439be7f7b3417ac68dead9cd0334bca163 Mon Sep 17 00:00:00 2001 From: Afish <1593699665@qq.com> Date: Thu, 25 Jun 2026 12:53:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=A8=E9=9D=A2=E9=87=8D=E6=9E=84=20UI=20?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E7=B3=BB=E7=BB=9F=20+=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=8C=87=E7=BA=B9=E5=8C=B9=E9=85=8D=20+=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E5=88=97=E8=A1=A8=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UI 设计 token 统一:硬编码色值替换为暖色调 CSS 自定义属性(brass/accent 主题) - 配置 UI 补全:新增 15 个此前缺失的 Pydantic 模型字段 - DocTagChecker 布局:固定 100vh,仅节点列表滚动,详情面板始终可见 - 节点项简化:flex 单行 + 固定宽度列约束,长文本内部换行撑高 - 过滤无内容 figure_image 节点 - 后端:移除 XML 指纹匹配,改为位置顺序绑定(zip) - 样式定义修正:段落属性(段前/段后距、行距、缩进)写入 XML 样式定义 --- CLAUDE.md | 176 +++++++++ WordFormatUI/src/App.vue | 342 +++++------------- WordFormatUI/src/components/ConfigSidebar.vue | 73 +--- WordFormatUI/src/components/DocTagChecker.vue | 48 +-- .../src/components/FileUploadPanel.vue | 10 +- .../src/components/FormatActionPanel.vue | 8 +- WordFormatUI/src/components/GlobalToast.vue | 18 +- .../src/components/NodeDetailPanel.vue | 66 ++-- WordFormatUI/src/components/NodeListPanel.vue | 112 +++--- WordFormatUI/src/components/SettingsPage.vue | 42 +-- .../src/config-generator/ConfigGenerator.vue | 13 +- .../components/AbstractConfig.vue | 192 +++------- .../components/AcknowledgementsConfig.vue | 96 +---- .../components/ConfigSection.vue | 58 +-- .../components/FiguresConfig.vue | 62 +--- .../components/FormatConfig.vue | 103 ++++-- .../components/GlobalFormatConfig.vue | 14 +- .../components/HeadingsConfig.vue | 101 +----- .../components/NumberingConfig.vue | 43 ++- .../components/ReferencesConfig.vue | 103 +----- .../components/TablesConfig.vue | 57 +-- .../components/WarningFieldsConfig.vue | 20 +- .../components/YamlOutput.vue | 52 ++- WordFormatUI/src/config-generator/utils.js | 27 +- docs/usage.md | 34 ++ .../api/static/assets/index-CkR2UYnL.js | 60 --- .../api/static/assets/index-D1xSiJtF.js | 60 +++ .../api/static/assets/index-D7BJOfFL.css | 1 - .../api/static/assets/index-DFE6ITTp.css | 1 + src/wordformat/api/static/index.html | 4 +- src/wordformat/base.py | 5 +- src/wordformat/rules/node.py | 12 +- src/wordformat/set_style.py | 50 +-- src/wordformat/utils.py | 38 -- .../word_structure/document_builder.py | 2 - tests/test_core.py | 49 +-- tests/test_integration.py | 41 +-- uv.lock | 2 +- 38 files changed, 881 insertions(+), 1314 deletions(-) create mode 100644 CLAUDE.md delete mode 100644 src/wordformat/api/static/assets/index-CkR2UYnL.js create mode 100644 src/wordformat/api/static/assets/index-D1xSiJtF.js delete mode 100644 src/wordformat/api/static/assets/index-D7BJOfFL.css create mode 100644 src/wordformat/api/static/assets/index-DFE6ITTp.css diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1552911 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,176 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project overview + +WordFormat is a Python CLI tool that checks and auto-corrects formatting in academic Word documents (.docx). It uses an ONNX BERT model to classify paragraphs (heading, abstract, body text, keywords, references, etc.), then validates and applies formatting rules defined in a YAML config file. + +**Entry points**: `wordf` and `wordformat` commands both map to `wordformat.cli:main`. + +## Git conventions + +- **不要添加 `Co-Authored-By` 到 commit message 中。** + +## Build & test commands + +```bash +# Install dev environment (creates venv, installs deps, downloads ONNX model) +make install + +# Run all tests (with coverage, must reach 85%) +make tests + +# Run a single test file +pytest tests/test_rules.py -v + +# Run a specific test +pytest tests/test_rules.py::TestAbstractTitleContentENBase -v + +# Run tests matching a keyword +pytest tests/ -k "Abstract" -v + +# Lint & format +ruff check src/ +ruff format src/ + +# Run API server locally +make server +# or: wordf startapi + +# Lint only (no tests) +make lint + +# Build distributable package +make build + +# Build Vue UI and copy into api/static +make build-ui + +# Clean build artifacts +make clean + +# Run pre-commit checks on all files +pre-commit run --all-files +``` + +## Install variants + +```bash +pip install -e "." # core only +pip install -e ".[api]" # core + FastAPI server +pip install -e ".[test]" # core + pytest plugins +pip install -e ".[dev]" # everything (api, test, pre-commit, ruff, pyinstaller) +``` + +## Pre-commit hooks + +Pre-commit runs on `pre-commit` and `pre-push` stages, configured in `.pre-commit-config.yaml`: + +- **sync-version** — mirrors `pyproject.toml` version into `_version.py` +- **end-of-file-fixer** / **trailing-whitespace** / **debug-statements** / **check-yaml** / **pretty-format-json** — standard fixers +- **pyupgrade** — auto-modernizes Python syntax (`--py3-plus`) +- **ruff** — lint + format (auto-fix) + +## Ruff config (pyproject.toml) + +- **Line length**: 108 (pycodestyle), 200 (docstrings) +- **Complexity**: max 10 (mccabe) +- **Quote style**: double, space indent +- **Per-file ignores**: `__init__.py` (F401/F403/E501), `tree.py`/`cli.py` (T201 print), `body.py`/`heading.py`/`numbering.py`/`set_style.py`/`utils.py` (C901 complexity) + +## Environment variables + +| Variable | Purpose | Default | +|----------|---------|---------| +| `WORDFORMAT_BASE_DIR` | Override working directory | auto-detected from project root | +| `WORDFORMAT_API_KEY` | API key for model service | `""` | +| `WORDFORMAT_MODEL` | Model identifier | `""` | +| `WORDFORMAT_MODEL_URL` | Model service URL | `""` | +| `BATCH_SIZE` | ONNX inference batch size | `64` | +| `HOST` / `PORT` | API server bind | `127.0.0.1` / `8000` | + +## Architecture: core data flow + +The tool operates in two phases that are intentionally separated so users can inspect and manually adjust the intermediate JSON before applying formatting: + +``` +wordf gj (generate JSON) wordf cf / wordf af (check/apply format) +───────────────────── ─────────────────────────────────── +.docx → ONNX classify → JSON tree → match paragraphs by + flat JSON per para position order (zip) → + per-node style check/apply +``` + +**Phase 1 — Classification** (`set_tag.py` → `base.py`): +- `DocxBase.parse()` loads a .docx, iterates paragraphs, batches them through ONNX BERT inference (`agent/onnx_infer.py`), and returns a flat list of `{category, text, score, comment}` dicts saved as JSON. + +**Phase 2 — Tree building** (`word_structure/`): +- `DocumentBuilder.build_from_json()` feeds the flat JSON list into `DocumentTreeBuilder.build_tree()`, which creates a hierarchical `FormatNode` tree. `LEVEL_MAP` in `word_structure/settings.py` maps category strings to numeric levels; `CATEGORY_TO_CLASS` maps categories to `FormatNode` subclasses. +- `node_factory.create_node()` instantiates the right `FormatNode` subclass and calls `load_config()`, which resolves the node's `CONFIG_PATH` (e.g. `"abstract.english"`) against the Pydantic root config model (`NodeConfigRoot`). + +**Phase 3 — Matching & formatting** (`set_style.py`): +- Each paragraph in the document is matched to a tree node by position order: `_flatten_tree_nodes()` converts the tree to a flat DFS list, then `zip()` pairs nodes with `document.paragraphs` by index. +- Before formatting, `node.apply_replace(doc)` checks for a `replace` field in the JSON value dict; if present, it substitutes the paragraph's run text with the replacement string. +- The tree is also mutated: `promote_bodytext_in_subtrees_of_type()` replaces generic `body_text` nodes under specific parents (e.g. `AbstractTitleCN`) with typed content nodes (e.g. `AbstractContentCN`). +- `apply_format_check_to_all_nodes()` recursively traverses the tree. For each node it calls `node.check_format(doc)` or `node.apply_format(doc)`, which delegate to `node._base(doc, p, r)` — the boolean flags control whether paragraph styles (`p`) and run styles (`r`) are checked (diffed) or applied (written). + +**Phase 4 — Numbering** (apply mode only, `numbering.py`): +- `process_heading_numbering()` strips manual heading numbers from run text and applies Word auto-numbering definitions. + +## Key directories & files + +| Path | Purpose | +|------|---------| +| `src/wordformat/cli.py` | CLI entry point (`gj`/`cf`/`af`/`tree`/`startapi` subcommands) | +| `src/wordformat/set_style.py` | Orchestrator: tree flattening (`_flatten_tree_nodes`), position-based paragraph matching, subtree promotion, text replace, calls check/apply on each node | +| `src/wordformat/set_tag.py` | Classification entry point: loads doc, calls `DocxBase`, returns JSON | +| `src/wordformat/base.py` | `DocxBase`: docx loading + ONNX batch inference | +| `src/wordformat/rules/node.py` | `FormatNode` base class with `load_config`, `check_format`, `apply_format`, `add_comment` | +| `src/wordformat/rules/abstract.py` | Abstract title/content/title-content nodes (CN + EN) | +| `src/wordformat/rules/heading.py` | Heading level 1/2/3 nodes with custom config loading | +| `src/wordformat/rules/keywords.py` | Keywords nodes with tag detection and count validation | +| `src/wordformat/style/check_format.py` | `CharacterStyle` (run-level) and `ParagraphStyle` (para-level) with diff/apply logic | +| `src/wordformat/style/style_enum.py` | Enums: `FontSize`, `FontColor`, `FontName`, `Alignment`, `LineSpacingRule`, etc. | +| `src/wordformat/config/datamodel.py` | Pydantic v2 models for all config sections (`GlobalFormatConfig`, `AbstractConfig`, `HeadingsConfig`, `NumberingConfig`, etc.) | +| `src/wordformat/config/config.py` | `LazyConfig` singleton for YAML loading and caching | +| `src/wordformat/numbering.py` | Heading auto-numbering (clear manual + apply Word numbering) | +| `src/wordformat/tree.py` | `Tree`, `TreeNode`, `Stack` data structures | +| `src/wordformat/word_structure/` | Tree building from flat JSON (`DocumentBuilder`, `DocumentTreeBuilder`, `node_factory`, `settings`) | +| `src/wordformat/api/__init__.py` | FastAPI app (unusual: defined in `__init__.py`, not a separate module). 3 main endpoints + download. Serves Vue SPA from `api/static/`. | +| `src/wordformat/agent/` | `onnx_infer.py` (ONNX model inference), `message.py` (messaging) | +| `src/wordformat/settings.py` | Global config: `BASE_DIR`, `SERVER_HOST`, model/env settings | +| `tests/` | 5 test files with ~850 tests, ~93% coverage | +| `presets/` | Per-university preset YAML configs | +| `wordformat-skill/` | AI assistant skill definition for Claude Code integration | + +## FormatNode subclass pattern + +Each `FormatNode` subclass declares three class-level attributes and implements `_base(doc, p, r)`: + +- `NODE_TYPE` — dot-separated path used by `TreeNode.load_config()` to extract dict config from the full YAML tree +- `CONFIG_MODEL` — Pydantic model class for type-safe config access via `self.pydantic_config` +- `CONFIG_PATH` — dot-separated path used by `FormatNode.load_config()` to resolve the Pydantic config object via `getattr` + +When adding a new node type, register it in `word_structure/settings.py` (`CATEGORY_TO_CLASS` and `LEVEL_MAP`) and in `rules/__init__.py`. + +## Test conventions + +- Tests are organized by module: `test_core.py` (tree, stack, numbering, utils, DocxBase), `test_style.py` (style enums, CharacterStyle, ParagraphStyle), `test_rules.py` (all FormatNode subclasses), `test_integration.py` (config, cross-module, CLI, API), `test_coverage_boost.py` (edge case coverage). +- Known bugs are documented in tests with `"""BUG: ..."""` docstrings — the test asserts the current (buggy) behavior. When fixing, update the test to assert the correct behavior. +- Tests use `python-docx` `Document()` to create in-memory test docs rather than real .docx files. Patches are applied via `unittest.mock.patch` at the module level (e.g. `patch("wordformat.base.onnx_batch_infer", ...)`). +- `conftest.py` provides shared fixtures: `doc` (in-memory Document), a mock ONNX session, and config reset between tests. + +## Config model structure + +The YAML config is validated by `NodeConfigRoot` (in `datamodel.py`), which wraps: +- `abstract: AbstractConfig` → `chinese: AbstractChineseConfig`, `english: AbstractEnglishConfig` +- `headings: HeadingsConfig` → `level_1/level_2/level_3: HeadingLevelConfig` +- `body_text: GlobalFormatConfig` +- `references: GlobalFormatConfig` +- `captions: dict[str, GlobalFormatConfig]` +- `numbering: NumberingConfig` +- `style_checks_warning: WarningFieldConfig` +- `replace: dict[str, str]` + +`GlobalFormatConfig` carries all glyph + paragraph format fields (font, size, color, bold, italic, alignment, spacing, indent, etc.). `AbstractChineseConfig` and `AbstractEnglishConfig` each hold a `title` and `content` sub-config (both `GlobalFormatConfig` subclasses), enabling `AbstractTitleContentCN`/`AbstractTitleContentEN` to apply different styles to title-runs vs content-runs within the same paragraph. diff --git a/WordFormatUI/src/App.vue b/WordFormatUI/src/App.vue index 6277724..c4a17cb 100644 --- a/WordFormatUI/src/App.vue +++ b/WordFormatUI/src/App.vue @@ -1,66 +1,35 @@ @@ -75,41 +44,20 @@ import yaml from 'js-yaml'; import {defaultConfig, mergeWithDefaults} from "./config-generator/utils"; import { loadSettings } from './utils/settings'; -// 全局 toast 引用 const toastRef = ref(null); - -// 活动标签页 const activeTab = ref('config'); - -// 生成的配置 const generatedConfig = ref(JSON.parse(JSON.stringify(defaultConfig))); - -// 配置生成器引用 const configGeneratorRef = ref(null); - -// 隐藏的 file input 引用(用于加载配置) const fileInputRef = ref(null); -// 处理配置更新 -const handleConfigUpdated = (config) => { - generatedConfig.value = config; -}; +const handleConfigUpdated = (config) => { generatedConfig.value = config; }; -// 生命周期:组件挂载后自动执行 onMounted(() => { - // 从 localStorage 恢复用户的后端连接配置 loadSettings(); - - // 初始化默认配置 generatedConfig.value = JSON.parse(JSON.stringify(defaultConfig)); - - // 注册全局 toast(供 main.js errorHandler 使用) - if (toastRef.value) { - window.__toast = toastRef.value.toast; - } + if (toastRef.value) { window.__toast = toastRef.value.toast; } }); -// 保存配置 → 浏览器下载 const saveConfig = () => { if (!generatedConfig.value) return; try { @@ -117,47 +65,31 @@ const saveConfig = () => { const blob = new Blob([yamlContent], { type: 'application/x-yaml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); - a.href = url; - a.download = 'wordformat-config.yaml'; - document.body.appendChild(a); - a.click(); - URL.revokeObjectURL(url); - document.body.removeChild(a); - toastRef.value?.toast.success('配置已下载!'); + a.href = url; a.download = 'wordformat-config.yaml'; + document.body.appendChild(a); a.click(); + URL.revokeObjectURL(url); document.body.removeChild(a); + toastRef.value?.toast.success('配置已下载'); } catch (error) { - console.error('保存配置失败:', error); - toastRef.value?.toast.error('保存配置失败:' + error.message); + toastRef.value?.toast.error('保存失败:' + error.message); } }; -// 加载配置 → 触发隐藏 file input -const loadConfig = () => { - fileInputRef.value?.click(); -}; +const loadConfig = () => { fileInputRef.value?.click(); }; -// file input 选择文件后的回调 const onConfigFileSelected = async (e) => { - const file = e.target.files?.[0]; - if (!file) return; + const file = e.target.files?.[0]; if (!file) return; try { const yamlContent = await file.text(); const config = yaml.load(yamlContent); const merged = mergeWithDefaults(config, defaultConfig); generatedConfig.value = merged; - if (configGeneratorRef.value) { - configGeneratorRef.value.importConfig(merged); - } - toastRef.value?.toast.success('配置加载成功!'); + if (configGeneratorRef.value) { configGeneratorRef.value.importConfig(merged); } + toastRef.value?.toast.success('配置加载成功'); } catch (error) { - console.error('加载配置失败:', error); - toastRef.value?.toast.error('加载配置失败:' + error.message); - } finally { - // 重置 input 以便重复选择同一文件 - e.target.value = ''; - } + toastRef.value?.toast.error('加载失败:' + error.message); + } finally { e.target.value = ''; } }; -// 保存配置到服务器 configs 目录 const API_BASE = window.__API_BASE__ || ''; const saveConfigToServer = async () => { if (!generatedConfig.value) return; @@ -166,200 +98,120 @@ const saveConfigToServer = async () => { const fn = prompt('请输入配置文件名(如 my-thesis.yaml):', 'custom.yaml'); if (!fn) return; const res = await fetch(`${API_BASE}/configs/save`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: fn, content: yamlContent }), }); const json = await res.json(); - if (json.code === 200) { - toastRef.value?.toast.success(json.msg); - } else { - toastRef.value?.toast.error(json.msg || '保存失败'); - } - } catch (error) { - toastRef.value?.toast.error('保存配置失败:' + error.message); - } + if (json.code === 200) { toastRef.value?.toast.success(json.msg); } + else { toastRef.value?.toast.error(json.msg || '保存失败'); } + } catch (error) { toastRef.value?.toast.error('保存失败:' + error.message); } }; -// 从服务器侧边栏加载配置 const onServerConfigSelected = ({ filename, content }) => { try { const config = yaml.load(content); const merged = mergeWithDefaults(config, defaultConfig); generatedConfig.value = merged; - if (configGeneratorRef.value) { - configGeneratorRef.value.importConfig(merged); - } - toastRef.value?.toast.success(`已加载配置: ${filename}`); - } catch (error) { - toastRef.value?.toast.error('解析配置失败:' + error.message); - } + if (configGeneratorRef.value) { configGeneratorRef.value.importConfig(merged); } + toastRef.value?.toast.success(`已加载: ${filename}`); + } catch (error) { toastRef.value?.toast.error('解析失败:' + error.message); } }; - + diff --git a/WordFormatUI/src/components/ConfigSidebar.vue b/WordFormatUI/src/components/ConfigSidebar.vue index d645ae1..ae64ebe 100644 --- a/WordFormatUI/src/components/ConfigSidebar.vue +++ b/WordFormatUI/src/components/ConfigSidebar.vue @@ -14,8 +14,8 @@ > {{ cfg }} -
加载中...
-
+
加载中...
+
暂无配置文件
将 YAML 放入 configs/ 目录
@@ -37,14 +37,9 @@ async function fetchConfigs() { try { const res = await fetch(`${API_BASE}/configs`) const json = await res.json() - if (json.code === 200) { - configs.value = json.data || [] - } - } catch (e) { - console.error('获取配置列表失败:', e) - } finally { - loading.value = false - } + if (json.code === 200) { configs.value = json.data || [] } + } catch (e) { console.error('获取配置列表失败:', e) } + finally { loading.value = false } } async function selectConfig(filename) { @@ -55,9 +50,7 @@ async function selectConfig(filename) { activeConfig.value = filename emit('config-selected', { filename, content: json.data }) } - } catch (e) { - console.error('读取配置失败:', e) - } + } catch (e) { console.error('读取配置失败:', e) } } onMounted(fetchConfigs) @@ -65,49 +58,23 @@ onMounted(fetchConfigs) diff --git a/WordFormatUI/src/components/DocTagChecker.vue b/WordFormatUI/src/components/DocTagChecker.vue index f421b3c..9a67c4b 100644 --- a/WordFormatUI/src/components/DocTagChecker.vue +++ b/WordFormatUI/src/components/DocTagChecker.vue @@ -213,10 +213,12 @@ const callGenerateJsonApi = async () => { ) if (res.data && Array.isArray(res.data.json_data)) { - nodeData.value = res.data.json_data.map((node, idx) => ({ - ...node, - id: idx // 用于追踪(非必须) - })) + nodeData.value = res.data.json_data + .filter(node => node.category !== 'figure_image') + .map((node, idx) => ({ + ...node, + id: idx + })) isFileLoaded.value = true selectedNodeIndex.value = -1 } else { @@ -415,11 +417,13 @@ const handleJsonImport = async (e) => { alert('JSON 格式错误:期望一个节点数组') return } - // 补全缺失的 id 字段 - nodeData.value = json.map((node, idx) => ({ - ...node, - id: node.id ?? idx - })) + // 补全缺失的 id 字段,并过滤无内容的 figure_image + nodeData.value = json + .filter(node => node.category !== 'figure_image') + .map((node, idx) => ({ + ...node, + id: node.id ?? idx + })) isFileLoaded.value = true selectedNodeIndex.value = -1 alert(`导入成功:${nodeData.value.length} 个节点`) @@ -438,24 +442,24 @@ watch(isFileLoaded, (loaded) => { diff --git a/WordFormatUI/src/components/FileUploadPanel.vue b/WordFormatUI/src/components/FileUploadPanel.vue index b449b5c..749020f 100644 --- a/WordFormatUI/src/components/FileUploadPanel.vue +++ b/WordFormatUI/src/components/FileUploadPanel.vue @@ -28,11 +28,11 @@ const onYamlChange = (e) => emit('update:yamlFile', e.target.files[0]) diff --git a/WordFormatUI/src/components/FormatActionPanel.vue b/WordFormatUI/src/components/FormatActionPanel.vue index ec3a01e..76d9ff2 100644 --- a/WordFormatUI/src/components/FormatActionPanel.vue +++ b/WordFormatUI/src/components/FormatActionPanel.vue @@ -14,8 +14,8 @@ defineEmits(['check-format', 'apply-format']) .format-btn-group { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; } .btn { padding: 6px 12px; font-size: 12px; font-weight: 500; border: none; border-radius: 6px; cursor: pointer; transition: all .2s; font-family: inherit; } .btn:disabled { opacity: 0.4; cursor: not-allowed; } -.btn-amber { background: #d97706; color: #fef3c7; } -.btn-amber:hover:not(:disabled) { background: #b45309; } -.btn-green { background: #22c55e; color: #052e16; } -.btn-green:hover:not(:disabled) { background: #16a34a; } +.btn-amber { background: var(--brass); color: var(--ink); font-weight: 600; } +.btn-amber:hover:not(:disabled) { background: var(--brass-dim); } +.btn-green { background: var(--green); color: var(--ink); font-weight: 600; } +.btn-green:hover:not(:disabled) { background: var(--green-dim); } diff --git a/WordFormatUI/src/components/GlobalToast.vue b/WordFormatUI/src/components/GlobalToast.vue index ebc9899..4e6e20c 100644 --- a/WordFormatUI/src/components/GlobalToast.vue +++ b/WordFormatUI/src/components/GlobalToast.vue @@ -69,25 +69,25 @@ defineExpose({ toast }); gap: 10px; padding: 12px 16px; border-radius: 10px; - background: #1e293b; + background: var(--paper); box-shadow: 0 8px 32px rgba(0,0,0,.35); min-width: 280px; max-width: 480px; pointer-events: auto; font-size: 14px; - border: 1px solid #334155; + border: 1px solid var(--border); } -.toast-msg { flex: 1; color: #e2e8f0; } +.toast-msg { flex: 1; color: var(--text); } .toast-close { background: none; border: none; font-size: 22px; - cursor: pointer; color: #64748b; padding: 0 4px; line-height: 1; + cursor: pointer; color: var(--text-muted); padding: 0 4px; line-height: 1; } -.toast-close:hover { color: #e2e8f0; } +.toast-close:hover { color: var(--text); } -.toast-info { border-left: 4px solid #3b82f6; } -.toast-success { border-left: 4px solid #22c55e; } -.toast-warn { border-left: 4px solid #f59e0b; } -.toast-error { border-left: 4px solid #ef4444; } +.toast-info { border-left: 4px solid var(--brass); } +.toast-success { border-left: 4px solid var(--green); } +.toast-warn { border-left: 4px solid var(--brass-dim); } +.toast-error { border-left: 4px solid var(--red); } .toast-enter-active { transition: all .3s ease; } .toast-leave-active { transition: all .2s ease; } diff --git a/WordFormatUI/src/components/NodeDetailPanel.vue b/WordFormatUI/src/components/NodeDetailPanel.vue index 291482f..370dcaa 100644 --- a/WordFormatUI/src/components/NodeDetailPanel.vue +++ b/WordFormatUI/src/components/NodeDetailPanel.vue @@ -45,10 +45,6 @@
置信度得分
{{ currentNode.score.toFixed(4) }}(判定阈值:{{ scoreThreshold }})
-
-
节点指纹
-
{{ currentNode.fingerprint || '[无指纹]' }}
-
节点注释
{{ currentNode.comment || '[无注释]' }}
@@ -148,75 +144,73 @@ const resultText = computed(() => { diff --git a/WordFormatUI/src/components/NodeListPanel.vue b/WordFormatUI/src/components/NodeListPanel.vue index f0264b3..b168013 100644 --- a/WordFormatUI/src/components/NodeListPanel.vue +++ b/WordFormatUI/src/components/NodeListPanel.vue @@ -39,7 +39,6 @@ R
{{ node.comment || '无注释' }} - {{ node.fingerprint?.slice(0, 8) || '无' }}
@@ -63,15 +62,16 @@ defineEmits(['select-node']) diff --git a/WordFormatUI/src/components/SettingsPage.vue b/WordFormatUI/src/components/SettingsPage.vue index 3d21b37..8f68c04 100644 --- a/WordFormatUI/src/components/SettingsPage.vue +++ b/WordFormatUI/src/components/SettingsPage.vue @@ -21,7 +21,7 @@
- +
@@ -56,24 +56,24 @@ function resetDefaults() { resetSettings(); host.value = backendSettings.host; p diff --git a/WordFormatUI/src/config-generator/ConfigGenerator.vue b/WordFormatUI/src/config-generator/ConfigGenerator.vue index b7db939..06afda9 100644 --- a/WordFormatUI/src/config-generator/ConfigGenerator.vue +++ b/WordFormatUI/src/config-generator/ConfigGenerator.vue @@ -33,6 +33,11 @@ +
+
+ +
+
@@ -155,7 +160,13 @@ defineExpose({ .config-generator { width: 100%; } .app-content { display: flex; gap: 20px; width: 100%; } .config-sections { flex: 1; min-width: 0; } -.yaml-preview { width: 380px; min-width: 280px; position: sticky; top: 84px; align-self: flex-start; max-height: calc(100vh - 110px); overflow-y: auto; } +.yaml-preview { width: 380px; min-width: 260px; position: sticky; top: 72px; align-self: flex-start; max-height: calc(100vh - 100px); overflow-y: auto; } + +.form-item { margin-bottom: 5px; display: flex; align-items: center; gap: 8px; } +.form-item label { font-size: 12px; font-weight: 500; color: var(--text-secondary); } +.form-item input[type="text"] { padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; font-size: 13px; background: var(--ink); color: var(--text); outline: none; flex: 1; max-width: 300px; } +.form-item input[type="text"]:focus { border-color: var(--brass); } + @media (max-width: 1200px) { .app-content { flex-direction: column; } .yaml-preview { width: 100%; position: static; max-height: none; } } @media (max-width: 768px) { .app-content { gap: 10px; } } diff --git a/WordFormatUI/src/config-generator/components/AbstractConfig.vue b/WordFormatUI/src/config-generator/components/AbstractConfig.vue index 19ee08a..9d64bc0 100644 --- a/WordFormatUI/src/config-generator/components/AbstractConfig.vue +++ b/WordFormatUI/src/config-generator/components/AbstractConfig.vue @@ -2,181 +2,75 @@

中文摘要

中文标题

-
-
-

中文内容

英文摘要

英文标题

-
-
-

英文内容

关键词配置

中文关键词

-
-
- -
-
- - -
-
- - -
-
- -
+
+ +
+
+ +
-
中文关键词内容格式
+
中文关键词内容格式
- -
中文关键词标签格式("关键词:")
+
中文关键词标签("关键词:")

英文关键词

-
-
- -
-
- - -
-
- - -
+
+ +
+
+ +
-
英文关键词内容格式
+
英文关键词内容格式
- -
英文关键词标签格式("Keywords:")
+
英文关键词标签("Keywords:")
diff --git a/WordFormatUI/src/config-generator/components/AcknowledgementsConfig.vue b/WordFormatUI/src/config-generator/components/AcknowledgementsConfig.vue index c1e9e75..b41a2b7 100644 --- a/WordFormatUI/src/config-generator/components/AcknowledgementsConfig.vue +++ b/WordFormatUI/src/config-generator/components/AcknowledgementsConfig.vue @@ -1,10 +1,7 @@