请求头
@@ -191,6 +191,10 @@ const transportOptions = MCP_TRANSPORT_OPTIONS.map(value => ({ value, label: val
const pullPolicyOptions = MCP_PULL_POLICY_OPTIONS.map(value => ({ value, label: { always: '总是拉取', missing: '缺失才拉取', never: '从不拉取' }[value]! }))
const negotiationOptions = MCP_NEGOTIATION_OPTIONS.map(value => ({ value, label: { legacy: '旧版(固定版本)', auto: '自动协商', modern: '现代(仅新版本)' }[value]! }))
+// 含 "//" 的占位文本必须经常量绑定:vue-tsc 3.3.12+ 模板 codegen 会把组件 props 里的 "//"
+// 误改写为块注释导致虚拟文件解析失败(language-tools#6264),直接写在模板里会让全文件报 TS2304
+const MCP_SERVER_URL_PLACEHOLDER = 'https://example.com/mcp'
+
function mcpSection(): JsonObject {
const settings = store.document.value?.settings
return isObject(settings) && isObject(settings.mcp) ? settings.mcp : {}
diff --git a/frontend/src/components/llm-config/ProvidersPanel.vue b/frontend/src/components/llm-config/ProvidersPanel.vue
index 9cda1c82..ad745a8a 100644
--- a/frontend/src/components/llm-config/ProvidersPanel.vue
+++ b/frontend/src/components/llm-config/ProvidersPanel.vue
@@ -5,27 +5,40 @@
渠道列表 ({{ providers.length }})
添加渠道
-
@@ -358,16 +371,15 @@ onMounted(() => { if (!store.environment.value) void store.loadEnvironment() })
display: grid;
grid-template-columns: 248px minmax(0, 1fr);
gap: var(--qq-gap-md);
- height: 100%;
- min-height: 0;
- overflow: hidden;
+ align-items: start;
}
.providers-panel__list {
+ position: sticky;
+ top: var(--qq-gap-lg);
display: flex;
flex-direction: column;
- height: 100%;
- min-height: 0;
+ max-height: calc(100vh - var(--qq-gap-lg) * 2);
background: var(--qq-surface);
border: 1px solid var(--qq-border);
border-radius: var(--qq-radius-card);
@@ -387,6 +399,10 @@ onMounted(() => { if (!store.environment.value) void store.loadEnvironment() })
flex-shrink: 0;
}
+.providers-panel__mobile-picker {
+ display: none;
+}
+
.providers-panel__list-scroll {
display: flex;
flex-direction: column;
@@ -446,10 +462,7 @@ onMounted(() => { if (!store.environment.value) void store.loadEnvironment() })
display: flex;
flex-direction: column;
gap: var(--qq-gap-sm);
- height: 100%;
- min-height: 0;
min-width: 0;
- overflow: hidden;
}
.provider-overview-bar {
@@ -515,17 +528,12 @@ onMounted(() => { if (!store.environment.value) void store.loadEnvironment() })
display: flex;
flex-direction: column;
gap: var(--qq-gap-md);
- flex: 1;
- min-height: 0;
- overflow-y: auto;
- padding-right: 4px;
padding-bottom: var(--qq-gap-md);
}
.providers-panel__placeholder {
display: grid;
place-items: center;
- height: 100%;
min-height: 320px;
background: var(--qq-surface);
border: 1px solid var(--qq-border);
@@ -771,17 +779,19 @@ onMounted(() => { if (!store.environment.value) void store.loadEnvironment() })
@media (max-width: 900px) {
.providers-panel {
grid-template-columns: 1fr;
- height: auto;
- overflow: visible;
}
.providers-panel__list {
- height: auto;
- max-height: 220px;
+ position: static;
+ max-height: none;
+ }
+ .providers-panel__mobile-picker {
+ display: block;
+ }
+ .providers-panel__mobile-picker select {
+ width: 100%;
}
- .providers-panel__detail,
- .providers-panel__detail-scroll {
- height: auto;
- overflow: visible;
+ .providers-panel__list-scroll {
+ display: none;
}
.form-grid { grid-template-columns: 1fr; }
}
diff --git a/frontend/src/components/skills/PresetSyncPanel.vue b/frontend/src/components/skills/PresetSyncPanel.vue
index a1167b29..87f9860d 100644
--- a/frontend/src/components/skills/PresetSyncPanel.vue
+++ b/frontend/src/components/skills/PresetSyncPanel.vue
@@ -154,6 +154,13 @@ defineExpose({ reload })
display: flex;
align-items: center;
gap: var(--qq-gap-xs);
+ flex-wrap: wrap;
+}
+
+/* 标题不换行:窄屏下由按钮组折行,避免「预置同/步」词中断行 */
+.preset-head .section-title {
+ flex-shrink: 0;
+ white-space: nowrap;
}
.preset-spacer { flex: 1; }
diff --git a/frontend/src/components/skills/SkillFileEditor.vue b/frontend/src/components/skills/SkillFileEditor.vue
index 2079be52..471618f1 100644
--- a/frontend/src/components/skills/SkillFileEditor.vue
+++ b/frontend/src/components/skills/SkillFileEditor.vue
@@ -180,8 +180,6 @@ defineExpose({ confirmLeave, openPath, reset })
display: flex;
flex-direction: column;
gap: var(--qq-gap-sm);
- min-height: 0;
- flex: 1;
}
.file-table-wrap {
@@ -284,9 +282,8 @@ defineExpose({ confirmLeave, openPath, reset })
}
.file-editor {
- flex: 1;
width: 100%;
- min-height: 300px;
+ min-height: 55vh;
padding: var(--qq-gap-md);
border: 1px solid var(--qq-border);
border-radius: var(--qq-radius-card);
diff --git a/frontend/src/components/ui/EChart.vue b/frontend/src/components/ui/EChart.vue
index eb664e8b..25bb742e 100644
--- a/frontend/src/components/ui/EChart.vue
+++ b/frontend/src/components/ui/EChart.vue
@@ -6,7 +6,10 @@
/**
* 通用 ECharts 容器组件。
* - echarts 通过动态 import 加载,独立异步 chunk,不拖慢首屏;
- * - option 变化时整体重建 setOption(notMerge);主题切换时由父级重建 option 传入;
+ * - option 变化时整体重建 setOption(notMerge);父级 option 必须由 computed
+ * 产出新引用(本组件不做 deep watch),主题切换时由父级重建 option 传入;
+ * - cursorAt 是轻量增量通道:仅按 id 合并 cursor 系列的 markLine.data
+ * (样式沿用整树 option 中的定义),供播放/擦洗类高频移动使用;
* - ResizeObserver 自适应容器宽度,卸载时自动 dispose。
* - plotClick 开启后额外透传"绘图区空白点击"的 x 轴数值(zr click →
* convertFromPixel),供时间轴擦洗类交互使用;系列点击不受影响。
@@ -19,6 +22,8 @@ const props = withDefaults(defineProps<{
option: ECOption
height?: number
plotClick?: boolean
+ /** cursor 竖线的 x 轴位置;undefined = 不启用增量 cursor 通道,null = 隐藏 */
+ cursorAt?: number | null
}>(), {
height: 260,
plotClick: false,
@@ -33,9 +38,22 @@ const el = ref
(null)
let chart: echartsApi.ECharts | null = null
let resizeObserver: ResizeObserver | null = null
+/** 仅更新 cursor markLine 的数据位;未指定 id 的 option(无 cursor 系列)不受影响 */
+function applyCursor() {
+ if (!chart || props.cursorAt === undefined) return
+ chart.setOption({
+ series: [{
+ id: 'cursor',
+ markLine: { data: props.cursorAt != null ? [{ xAxis: props.cursorAt }] : [] },
+ }],
+ })
+}
+
function render() {
if (!chart || !props.option) return
chart.setOption(props.option, { notMerge: true })
+ // 整树重建会冲掉 cursor 增量,重建后按当前 cursorAt 回补
+ applyCursor()
}
onMounted(async () => {
@@ -59,7 +77,8 @@ onMounted(async () => {
resizeObserver.observe(el.value)
})
-watch(() => props.option, render, { deep: true })
+watch(() => props.option, render)
+watch(() => props.cursorAt, applyCursor)
onBeforeUnmount(() => {
resizeObserver?.disconnect()
diff --git a/frontend/src/components/ui/UiDatePicker.vue b/frontend/src/components/ui/UiDatePicker.vue
index 7ca84769..144aec41 100644
--- a/frontend/src/components/ui/UiDatePicker.vue
+++ b/frontend/src/components/ui/UiDatePicker.vue
@@ -35,6 +35,7 @@ import { VueDatePicker } from '@vuepic/vue-datepicker'
import { zhCN } from 'date-fns/locale'
import { format } from 'date-fns'
import { useTheme } from '../../composables/useTheme'
+import '../../styles/datepicker.css'
const props = withDefaults(defineProps<{
/** '' 或模式对应格式的字符串 */
diff --git a/frontend/src/components/ui/UiIcon.vue b/frontend/src/components/ui/UiIcon.vue
index f3004a1a..a689b82d 100644
--- a/frontend/src/components/ui/UiIcon.vue
+++ b/frontend/src/components/ui/UiIcon.vue
@@ -9,23 +9,23 @@
diff --git a/frontend/src/views/CronDashboardView.vue b/frontend/src/views/CronDashboardView.vue
index 5f000345..4674a6bf 100644
--- a/frontend/src/views/CronDashboardView.vue
+++ b/frontend/src/views/CronDashboardView.vue
@@ -6,8 +6,8 @@
-
- 每 30 秒自动刷新,快照时间 {{ snapshotText }}快照超过 5 分钟未更新,bot 进程可能未运行
+
+ 每 30 秒自动刷新,快照时间 {{ snapshotText }}快照超过 5 分钟未更新,bot 进程可能未运行