diff --git a/docs/project_context.md b/docs/project_context.md
index 6bed984..9cc5de9 100644
--- a/docs/project_context.md
+++ b/docs/project_context.md
@@ -1,5 +1,13 @@
# 项目上下文记录
+## 2026-05-15:恢复前端工作台交互质量
+
+- 前端工作台布局重新对齐旧版 `54773f547f6fcb853d73785f05ff5ac39ab2e5f5` 的信息架构:左侧导航、顶部标题栏、紧凑后台式内容区,导航项使用“数据上传 / 处理历史 / 数据管理 / 脚本编辑 / 系统设置”。
+- `ScriptPanel.vue` 不再使用普通 textarea,改为 `monaco-editor` SQL 编辑器,保留脚本读取、保存、执行和状态轮询接口;支持 SQL 高亮、行号、缩略图、光标行列状态和未保存状态。
+- Monaco 通过 Vue 异步组件按需加载,避免脚本编辑器依赖进入首屏主包;Vite worker 类型由 `frontend/src/vite-env.d.ts` 提供。
+- `SettingsPanel.vue` 的字段提取配置恢复为结构化树状配置:字段名、字段类型、提取来源列表、搜索、增删和去重保存,不再要求用户直接编辑 JSON。
+- 新增前端运行依赖 `monaco-editor`,构建时仍会生成 `frontend/dist/`,该目录继续作为构建产物忽略,不进入版本库。
+
## 2026-05-15:修复 Windows 启动脚本编码问题
- `run.bat` 改为纯 ASCII 输出,并规范为 CRLF 行尾,避免 Windows `cmd` 在 PowerShell 中执行 UTF-8 中文批处理时把提示文本解析成碎片命令。
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index eebdbf0..b75138d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,6 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
+ "monaco-editor": "^0.55.1",
"naive-ui": "^2.42.0",
"vue": "^3.5.0"
},
@@ -922,6 +923,13 @@
"@types/lodash": "*"
}
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@vicons/ionicons5": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@vicons/ionicons5/-/ionicons5-0.13.0.tgz",
@@ -1144,6 +1152,15 @@
"date-fns": "^3.0.0 || ^4.0.0"
}
},
+ "node_modules/dompurify": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
+ "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
@@ -1273,6 +1290,28 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
+ "node_modules/marked": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
+ "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/monaco-editor": {
+ "version": "0.55.1",
+ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
+ "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
+ "license": "MIT",
+ "dependencies": {
+ "dompurify": "3.2.7",
+ "marked": "14.0.0"
+ }
+ },
"node_modules/muggle-string": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 1cf155b..30fe2f2 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -10,6 +10,7 @@
},
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
+ "monaco-editor": "^0.55.1",
"naive-ui": "^2.42.0",
"vue": "^3.5.0"
},
@@ -20,4 +21,3 @@
"vue-tsc": "^3.0.0"
}
}
-
diff --git a/frontend/src/AppShell.vue b/frontend/src/AppShell.vue
index c7baec6..c5b0723 100644
--- a/frontend/src/AppShell.vue
+++ b/frontend/src/AppShell.vue
@@ -2,7 +2,7 @@
+
-
+
{{ currentTitle }}
-
容量报表处理与数据维护
已登录
@@ -48,7 +52,7 @@
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 5ee859d..8b484ce 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -12,7 +12,7 @@ body,
}
body {
- color: #20242a;
+ color: #1f2933;
background: #eef1f4;
font-family:
Inter,
@@ -24,27 +24,41 @@ body {
.app-layout {
min-height: 100vh;
+ background: #eef1f4;
+}
+
+.app-sider {
+ background: #ffffff;
+ border-right: 1px solid #d9dee7;
+}
+
+.app-sider .n-layout-sider-scroll-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
}
.brand {
display: flex;
align-items: center;
+ flex-shrink: 0;
gap: 10px;
- height: 60px;
- padding: 0 16px;
- border-bottom: 1px solid #e6e9ee;
+ height: 64px;
+ padding: 0 18px;
+ border-bottom: 1px solid #e4e8ef;
}
.brand-mark {
display: grid;
+ flex: 0 0 auto;
width: 34px;
height: 34px;
place-items: center;
color: #fff;
font-size: 13px;
font-weight: 700;
- background: #2458a6;
- border-radius: 8px;
+ background: #1f5ea8;
+ border-radius: 6px;
}
.brand-mark.large {
@@ -60,30 +74,75 @@ body {
text-overflow: ellipsis;
}
+.app-menu {
+ flex: 1;
+ padding: 12px 8px;
+}
+
+.sider-footer {
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+ gap: 3px;
+ padding: 12px 18px;
+ color: #7b8492;
+ font-size: 12px;
+ border-top: 1px solid #e4e8ef;
+}
+
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
- height: 60px;
+ height: 64px;
padding: 0 20px;
background: #fff;
}
+.topbar-title {
+ min-width: 0;
+}
+
.page-title {
+ overflow: hidden;
+ max-width: 52vw;
font-size: 18px;
font-weight: 700;
+ white-space: nowrap;
+ text-overflow: ellipsis;
}
.page-subtitle,
.muted-line {
- margin-top: 2px;
- color: #6a7280;
+ color: #6f7885;
font-size: 12px;
}
.content {
min-width: 0;
- padding: 16px;
+ height: calc(100vh - 64px);
+ padding: 14px;
+ overflow: auto;
+ background: #eef1f4;
+}
+
+.work-card,
+.n-card {
+ border-radius: 6px;
+}
+
+.work-card {
+ border-color: #dfe4ec;
+ box-shadow: 0 1px 2px rgba(31, 41, 55, 0.04);
+}
+
+.work-card .n-card-header {
+ min-height: 46px;
+ padding: 10px 14px;
+}
+
+.work-card .n-card__content {
+ padding: 14px;
}
.login-page {
@@ -91,15 +150,13 @@ body {
min-height: 100vh;
place-items: center;
padding: 24px;
- background:
- linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(238, 241, 244, 0.94)),
- #eef1f4;
+ background: #eef1f4;
}
.login-card {
width: min(420px, 100%);
border-radius: 8px;
- box-shadow: 0 18px 48px rgba(21, 38, 62, 0.16);
+ box-shadow: 0 18px 48px rgba(21, 38, 62, 0.14);
}
.login-brand {
@@ -133,22 +190,15 @@ body {
max-height: 360px;
}
-.log-panel {
+.log-panel,
+.script-log {
min-height: 320px;
padding: 12px;
- background: #101418;
+ color: #d7dde7;
+ background: #111820;
border-radius: 6px;
}
-.script-editor textarea,
-.json-editor textarea {
- font-family:
- "Cascadia Code",
- Consolas,
- "Courier New",
- monospace;
-}
-
.section-label {
margin-bottom: 8px;
color: #333b46;
@@ -166,8 +216,294 @@ body {
margin-top: 16px;
}
-.n-card {
- border-radius: 8px;
+.settings-workspace {
+ display: grid;
+ grid-template-columns: minmax(340px, 420px) minmax(0, 1fr);
+ align-items: start;
+ gap: 14px;
+}
+
+.settings-column {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.card-actions {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ margin-top: 4px;
+}
+
+.filter-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ min-height: 34px;
+}
+
+.field-mapping-card {
+ min-width: 0;
+ height: calc(100vh - 92px);
+}
+
+.field-mapping-card .n-card__content {
+ height: calc(100% - 48px);
+ padding: 0 14px 14px;
+}
+
+.field-card-title,
+.script-card-title {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+ font-weight: 700;
+}
+
+.field-search {
+ width: 240px;
+}
+
+.field-mapping-scroll {
+ height: 100%;
+}
+
+.field-mapping-scroll .n-scrollbar-content {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding: 2px 4px 4px 0;
+}
+
+.field-mapping-item {
+ position: relative;
+ min-width: 0;
+ padding: 32px 12px 12px;
+ background: #fff;
+ border: 1px solid #dfe4ec;
+ border-radius: 6px;
+}
+
+.field-mapping-item:hover {
+ box-shadow: 0 2px 8px rgba(31, 41, 55, 0.08);
+}
+
+.field-mapping-number {
+ position: absolute;
+ top: 8px;
+ left: 10px;
+ display: grid;
+ width: 22px;
+ height: 22px;
+ place-items: center;
+ color: #fff;
+ font-size: 12px;
+ font-weight: 700;
+ background: #1f5ea8;
+ border-radius: 4px;
+}
+
+.field-remove-button {
+ position: absolute;
+ top: 6px;
+ right: 8px;
+}
+
+.field-mapping-header {
+ display: grid;
+ grid-template-columns: minmax(180px, 1fr) 160px;
+ gap: 12px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #e8ecf2;
+}
+
+.field-name-control,
+.field-type-control {
+ margin-bottom: 0;
+}
+
+.extract-list {
+ padding-top: 10px;
+}
+
+.extract-list-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 8px;
+ color: #4b5563;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.extract-add-group {
+ width: 300px;
+}
+
+.extract-tree {
+ max-height: 130px;
+ overflow: auto;
+ background: #f6f8fb;
+ border: 1px solid #e1e6ee;
+ border-radius: 5px;
+}
+
+.extract-tree-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ min-height: 32px;
+ padding: 5px 10px;
+ font-size: 13px;
+ border-bottom: 1px solid #e8edf3;
+}
+
+.extract-tree-item:last-child {
+ border-bottom: 0;
+}
+
+.extract-tree-item:hover {
+ background: #edf3fa;
+}
+
+.tree-text {
+ display: inline-flex;
+ align-items: center;
+ min-width: 0;
+ gap: 6px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.tree-text::before {
+ content: "└";
+ flex: 0 0 auto;
+ color: #8b95a4;
+}
+
+.extract-empty {
+ padding: 9px 10px;
+ color: #8b95a4;
+ font-size: 13px;
+ background: #f6f8fb;
+ border: 1px dashed #cfd7e4;
+ border-radius: 5px;
+}
+
+.script-workspace {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 360px;
+ gap: 14px;
+ height: calc(100vh - 92px);
+ min-height: 620px;
+}
+
+.script-editor-pane,
+.script-log-pane {
+ min-width: 0;
+ height: 100%;
+}
+
+.script-editor-pane .n-card__content {
+ display: flex;
+ flex-direction: column;
+ height: calc(100% - 48px);
+ padding: 0;
+}
+
+.script-editor-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ min-height: 38px;
+ padding: 0 12px;
+ color: #596473;
+ font-size: 12px;
+ background: #f6f8fb;
+ border-top: 1px solid #e4e8ef;
+ border-bottom: 1px solid #e4e8ef;
+}
+
+.script-path {
+ overflow: hidden;
+ min-width: 0;
+ font-family:
+ "Cascadia Code",
+ Consolas,
+ "Courier New",
+ monospace;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.script-modified {
+ flex: 0 0 auto;
+}
+
+.monaco-editor-host {
+ flex: 1;
+ min-height: 480px;
+ overflow: hidden;
+}
+
+.script-status-bar {
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ height: 28px;
+ padding: 0 12px;
+ color: #5f6b7a;
+ font-size: 12px;
+ background: #f6f8fb;
+ border-top: 1px solid #e4e8ef;
+}
+
+.script-log-pane .n-card__content {
+ height: calc(100% - 48px);
+}
+
+.script-log {
+ height: 100%;
+ min-height: 0;
+}
+
+@media (max-width: 1180px) {
+ .settings-workspace,
+ .script-workspace {
+ grid-template-columns: 1fr;
+ height: auto;
+ min-height: 0;
+ }
+
+ .field-mapping-card,
+ .script-editor-pane,
+ .script-log-pane {
+ height: auto;
+ }
+
+ .field-mapping-scroll {
+ height: min(680px, calc(100vh - 180px));
+ }
+
+ .script-workspace {
+ min-height: 0;
+ }
+
+ .monaco-editor-host {
+ height: 620px;
+ flex: 0 0 auto;
+ }
+
+ .script-log {
+ height: 360px;
+ }
}
@media (max-width: 720px) {
@@ -176,15 +512,27 @@ body {
}
.content {
- padding: 12px;
+ padding: 10px;
}
- .page-subtitle {
- display: none;
+ .page-title {
+ max-width: 46vw;
}
- .table-footer {
+ .table-footer,
+ .card-actions,
+ .extract-list-header,
+ .script-editor-toolbar {
align-items: flex-start;
flex-direction: column;
}
+
+ .field-mapping-header {
+ grid-template-columns: 1fr;
+ }
+
+ .field-search,
+ .extract-add-group {
+ width: 100%;
+ }
}
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/frontend/src/vite-env.d.ts
@@ -0,0 +1 @@
+///