cardbuddy 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/cli.js +14 -6
- package/dist/config.js +13 -2
- package/dist/preview-editor/assets/index-B4VZgNSK.css +1 -0
- package/dist/preview-editor/assets/index-jl4YJzKv.js +180 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/server-core/_vendor/finmall-env.mjs +4 -4
- package/dist/server-core/ai-chat.js +3710 -0
- package/dist/server-core/cardFiles.js +95 -2
- package/dist/server-core/cardImage.js +14 -0
- package/dist/server-core/cardSave.js +2 -0
- package/dist/server-core/cardValidate.js +3 -0
- package/dist/server-core/index.js +3 -0
- package/dist/server-core/sliceUpload.js +8 -1
- package/dist/vendored/App.vue +269 -79
- package/dist/vendored/api.js +4 -4
- package/dist/vendored/assets/svgIcons/icons.svg +28 -0
- package/dist/vendored/components/AiSettings.vue +392 -0
- package/dist/vendored/components/CardGallery.vue +51 -5
- package/dist/vendored/components/ChatComposer.vue +413 -0
- package/dist/vendored/components/ChatMessage.vue +383 -0
- package/dist/vendored/components/ChatPanel.vue +937 -0
- package/dist/vendored/components/DebugPanel.vue +30 -33
- package/dist/vendored/components/Dropdown.vue +4 -2
- package/dist/vendored/components/MarkdownBody.vue +64 -0
- package/dist/vendored/components/NewCardDialog.vue +132 -0
- package/dist/vendored/components/RemotePreview.vue +1 -1
- package/dist/vendored/main.js +1 -0
- package/dist/vendored/runtime/adapter.js +1 -1
- package/dist/vendored/runtime/ai-api.js +165 -0
- package/dist/vendored/runtime/markdown.js +140 -0
- package/dist/vendored/skills/.manifest.json +1101 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +249 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/check_topology.py +678 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/derive_data_json.py +81 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.py +105 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +624 -0
- package/dist/vendored/skills/finmall-card-sync/scripts/api_batch_sync.mjs +900 -0
- package/dist/vendored/skills/finmall-card-sync/scripts/slice_upload.mjs +699 -0
- package/dist/vendored/skills/shared/finmall-config.mjs +124 -0
- package/dist/vendored/styles/ai-chat.css +1850 -0
- package/dist/vendored/styles/app.css +107 -3
- package/dist/vendored/styles/card-gallery.css +45 -7
- package/dist/vendored/styles/node-inspector.css +11 -0
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-C_UKS0NF.css +0 -1
- package/dist/preview-editor/assets/index-D1iIJ4sl.js +0 -81
package/dist/vendored/App.vue
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
右侧调试面板。
|
|
4
4
|
-->
|
|
5
5
|
<template>
|
|
6
|
-
<div class="app" :class="{ 'app--chrome-off': chromeOff }" :style="{ '--preview-width': previewWidth + 'px', '--api-panel-width': apiPanelWidth + 'px' }">
|
|
6
|
+
<div class="app" :class="{ 'app--chrome-off': chromeOff }" :style="{ '--preview-width': previewWidth + 'px', '--api-panel-width': apiPanelWidth + 'px', '--debug-min': debugMinWidth + 'px' }">
|
|
7
7
|
<header class="app__bar" v-show="!chromeOff">
|
|
8
8
|
<strong class="app__logo">
|
|
9
9
|
<img class="app__logo-icon" :src="logoUrl" alt="" />
|
|
@@ -35,6 +35,15 @@
|
|
|
35
35
|
>
|
|
36
36
|
<svg class="app__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-grid" /></svg>
|
|
37
37
|
</button>
|
|
38
|
+
<!-- 新增空白卡片:紧邻网格预览的图标按钮;弹窗收 ID(必填、本地重名前置拦截)+ 描述(选填),创建成功切换到新卡 -->
|
|
39
|
+
<button
|
|
40
|
+
class="app__btn app__btn--icon"
|
|
41
|
+
type="button"
|
|
42
|
+
@click="openNewCard"
|
|
43
|
+
title="新增空白卡片"
|
|
44
|
+
>
|
|
45
|
+
<svg class="app__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-folder-plus" /></svg>
|
|
46
|
+
</button>
|
|
38
47
|
<div
|
|
39
48
|
ref="validateBadgeEl"
|
|
40
49
|
class="app__badge"
|
|
@@ -233,10 +242,30 @@
|
|
|
233
242
|
</Transition>
|
|
234
243
|
</div>
|
|
235
244
|
</div>
|
|
236
|
-
<!-- revision 依赖锚点:setData 自增后驱动本组件重渲染 -->
|
|
237
245
|
<span style="display: none">{{ rt.revision }}</span>
|
|
238
246
|
</header>
|
|
239
247
|
|
|
248
|
+
<!-- AI 开发助手悬浮入口:右下角圆形按钮(尺寸 / 配色走规范 Token,图标取自 sprite) -->
|
|
249
|
+
<button
|
|
250
|
+
class="ai-fab"
|
|
251
|
+
type="button"
|
|
252
|
+
:class="{ 'ai-fab--active': aiChatOpen }"
|
|
253
|
+
:title="aiChatOpen ? 'AI 开发助手(已打开)' : 'AI 开发助手'"
|
|
254
|
+
:aria-expanded="aiChatOpen ? 'true' : 'false'"
|
|
255
|
+
data-testid="ai-fab"
|
|
256
|
+
@click="aiChatOpen = !aiChatOpen"
|
|
257
|
+
>
|
|
258
|
+
<svg class="ai-fab__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#icon-sparkles" /></svg>
|
|
259
|
+
</button>
|
|
260
|
+
|
|
261
|
+
<!-- AI 开发助手对话面板:右侧抽屉,宽度可拖(320px ~ 视口 50%,默认 33%),对话记录按卡片落盘 -->
|
|
262
|
+
<ChatPanel
|
|
263
|
+
:visible="aiChatOpen"
|
|
264
|
+
:card-id="selectedId"
|
|
265
|
+
:card-name="selectedCardName"
|
|
266
|
+
@close="aiChatOpen = false"
|
|
267
|
+
/>
|
|
268
|
+
|
|
240
269
|
<!-- 部署态但未配置本地开发服务:顶部提示条(chrome=off 截图模式不显示) -->
|
|
241
270
|
<div v-if="apiBaseResolved === null && !chromeOff" class="app__nosvc-banner">
|
|
242
271
|
<template v-if="devSvcBlockReason === 'disabled'">
|
|
@@ -358,7 +387,7 @@
|
|
|
358
387
|
v-show="!chromeOff"
|
|
359
388
|
class="splitter"
|
|
360
389
|
:class="{ 'is-dragging': dragging }"
|
|
361
|
-
@
|
|
390
|
+
@pointerdown.prevent="startDrag"
|
|
362
391
|
title="拖拽调整预览区域宽度"
|
|
363
392
|
></div>
|
|
364
393
|
|
|
@@ -369,7 +398,7 @@
|
|
|
369
398
|
<div class="workside" v-show="!chromeOff">
|
|
370
399
|
<div id="app-workside-bar" class="workside__bar"></div>
|
|
371
400
|
<div id="app-workside-panels" class="workside__panels"></div>
|
|
372
|
-
<div class="workside__cols">
|
|
401
|
+
<div class="workside__cols" ref="colsEl">
|
|
373
402
|
<!-- 拖拽分隔条:置于两栏容器内(定位上下文),只贯穿列区高度,不穿过底部弹出面板与状态栏 -->
|
|
374
403
|
<!-- 中右分隔条:调试面板 | 接口调用面板 接缝,拖动调整右栏宽度。
|
|
375
404
|
约束:中栏最小宽度 ≥ 屏宽 1/3,右栏最小宽度 ≥ 320px(见 startApiDrag) -->
|
|
@@ -377,7 +406,7 @@
|
|
|
377
406
|
v-show="!chromeOff && apiPanelOpen"
|
|
378
407
|
class="splitter splitter--api"
|
|
379
408
|
:class="{ 'is-dragging': apiDragging }"
|
|
380
|
-
@
|
|
409
|
+
@pointerdown.prevent="startApiDrag"
|
|
381
410
|
title="拖拽调整接口调用面板宽度"
|
|
382
411
|
></div>
|
|
383
412
|
<DebugPanel
|
|
@@ -504,6 +533,14 @@
|
|
|
504
533
|
:selected-id="selectedId"
|
|
505
534
|
@select="onSelect"
|
|
506
535
|
@close="closeGallery"
|
|
536
|
+
@delete="onGalleryDelete"
|
|
537
|
+
/>
|
|
538
|
+
<!-- 新增空白卡片弹窗:创建成功后关弹窗 + 刷列表 + 切换到新卡;existing-ids 供 ID 重名前置判定 -->
|
|
539
|
+
<NewCardDialog
|
|
540
|
+
:visible="newCardOpen"
|
|
541
|
+
:existing-ids="cardIds"
|
|
542
|
+
@close="newCardOpen = false"
|
|
543
|
+
@created="onNewCardCreated"
|
|
507
544
|
/>
|
|
508
545
|
<!-- App 级弱提示:顶栏异步操作(远端保存 / 同步)完成反馈,浮于 App 外壳顶部居中 -->
|
|
509
546
|
<AppToast />
|
|
@@ -537,8 +574,10 @@ import RemotePreview from '@/components/RemotePreview.vue'
|
|
|
537
574
|
import AppToast from '@/components/AppToast.vue'
|
|
538
575
|
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
|
539
576
|
import CardGallery from '@/components/CardGallery.vue'
|
|
577
|
+
import NewCardDialog from '@/components/NewCardDialog.vue'
|
|
540
578
|
import ComponentPalette from '@/components/ComponentPalette.vue'
|
|
541
579
|
import Switch from '@/components/Switch.vue'
|
|
580
|
+
import ChatPanel from '@/components/ChatPanel.vue'
|
|
542
581
|
import { showAppToast } from '@/runtime/app-toast.js'
|
|
543
582
|
import { clearAllAlerts } from '@/runtime/alert-card.js'
|
|
544
583
|
import { installJsApiMock, clearDeviceUI } from '@/runtime/jsapi-mock.js'
|
|
@@ -563,9 +602,16 @@ const cardRuntime = createCardRuntime({}, adapter)
|
|
|
563
602
|
// 直接驱动 rt.setRootScope —— 与 DebugPanel「变量 → 配置变量 mock」Raw「应用」同源。生产构建不暴露。
|
|
564
603
|
if (typeof window !== 'undefined' && import.meta.env.DEV) window.__rt = cardRuntime
|
|
565
604
|
|
|
605
|
+
// 三栏(预览列 | 调试面板 | 接口调用面板)布局约束的单一来源:
|
|
606
|
+
// 拖拽夹取与 computed 都从这里取值,避免「两套口径」在极限位置互相打架导致接缝跳动 / 整行溢出。
|
|
607
|
+
const PREVIEW_MIN = 188 // 预览列硬下限(= 最小机型预设宽)
|
|
608
|
+
const API_MIN = 320 // 接口调用面板常规下限(空间不足时按 apiPanelBounds 优雅退让,不整行溢出)
|
|
609
|
+
const DEBUG_MIN_FLOOR = 200 // 中栏(调试面板)硬下限;软目标为屏宽 1/3
|
|
610
|
+
const API_ABS_MIN = 160 // 极窄窗口下面板的绝对下限,保证不撑破容器
|
|
611
|
+
|
|
566
612
|
export default {
|
|
567
613
|
name: 'App',
|
|
568
|
-
components: { RenderTree, DebugPanel, ApiCallPanel, CardSelect, AlertCardLayer, DeviceOverlay, PickerOverlay, DatePickerOverlay, SelectionOverlay, HoverGuides, RemotePreview, AppToast, ConfirmDialog, CardGallery, ComponentPalette, Switch },
|
|
614
|
+
components: { RenderTree, DebugPanel, ApiCallPanel, CardSelect, AlertCardLayer, DeviceOverlay, PickerOverlay, DatePickerOverlay, SelectionOverlay, HoverGuides, RemotePreview, AppToast, ConfirmDialog, CardGallery, NewCardDialog, ComponentPalette, Switch, ChatPanel },
|
|
569
615
|
provide() {
|
|
570
616
|
return {
|
|
571
617
|
adapter,
|
|
@@ -583,7 +629,9 @@ export default {
|
|
|
583
629
|
appVersion: __APP_VERSION__,
|
|
584
630
|
cardList: [], // 实时从 GET /__cards 拉取,不缓存启动快照
|
|
585
631
|
cardCount: 0,
|
|
586
|
-
galleryOpen: false,
|
|
632
|
+
galleryOpen: false,
|
|
633
|
+
// 新增空白卡片弹窗开关(顶栏图标按钮触发)
|
|
634
|
+
newCardOpen: false, // 卡片网格预览弹窗开关
|
|
587
635
|
selectedId: '',
|
|
588
636
|
// 外部(IDE / 其他工具)改了当前卡、且平台无未保存改动时,自动重载后顶部短暂提示
|
|
589
637
|
// (非阻塞,区别于有未保存改动时的阻塞确认框)。
|
|
@@ -697,15 +745,22 @@ export default {
|
|
|
697
745
|
{ key: 188, value: '188' },
|
|
698
746
|
],
|
|
699
747
|
stageWidth: typeof window !== 'undefined' ? window.innerWidth : 1280, // 舞台实际宽度(ResizeObserver 实测后更新)
|
|
748
|
+
colsWidth: 0, // 中右两栏容器(.workside__cols)实测宽度:右栏宽度夹取的单一口径
|
|
700
749
|
stageHeight: typeof window !== 'undefined' ? window.innerHeight - 52 : 748, // 舞台实际高度(contain 缩放的“高度上限”基准)
|
|
701
750
|
dragging: false, // 是否正在拖拽分隔条
|
|
702
751
|
apiDragging: false, // 是否正在拖拽「接口调用」面板分隔条
|
|
703
752
|
_stageObserver: null,
|
|
704
|
-
_dragMove: null,
|
|
705
|
-
_dragUp: null,
|
|
706
753
|
// —— 本地开发服务(前后端分离)配置入口 ——
|
|
707
754
|
// devServiceForm:设置面板双向绑定的草稿;启用后前端所有 /__xxx 请求改走该地址。
|
|
708
755
|
devServiceOpen: false,
|
|
756
|
+
// AI 面板开合跨刷新保持:dev server 重启 / 误刷新后不必重新找入口
|
|
757
|
+
aiChatOpen: (() => {
|
|
758
|
+
try {
|
|
759
|
+
return sessionStorage.getItem('cardbuddy:ai-chat-open') === '1'
|
|
760
|
+
} catch {
|
|
761
|
+
return false
|
|
762
|
+
}
|
|
763
|
+
})(),
|
|
709
764
|
devServiceForm: loadDevServiceConfig(),
|
|
710
765
|
// apiBaseResolved:当前生效的 API 基址;null 表示部署态未配置(必须用本地服务才能编辑)。
|
|
711
766
|
apiBaseResolved: resolveApiBase(),
|
|
@@ -725,6 +780,10 @@ export default {
|
|
|
725
780
|
currentBg() {
|
|
726
781
|
return this.bgOptions.find((b) => b.key === this.canvasBg) || this.bgOptions[0]
|
|
727
782
|
},
|
|
783
|
+
/** 本地已有卡片 ID(GET /__cards 实时扫描 src/cards 的结果):新增空白卡片时做重名前置判定 */
|
|
784
|
+
cardIds() {
|
|
785
|
+
return this.cardList.map((c) => c && c.id).filter(Boolean)
|
|
786
|
+
},
|
|
728
787
|
// 撤销可用:存在可回退的上一版本;重做可用:存在被撤销后的版本
|
|
729
788
|
canUndo() {
|
|
730
789
|
return this.past.length > 0
|
|
@@ -825,27 +884,58 @@ export default {
|
|
|
825
884
|
return ''
|
|
826
885
|
},
|
|
827
886
|
// 预览列宽度(像素):chromeOff 时占满整屏(截图/真机模式,非“宽度调整”场景);
|
|
828
|
-
// 否则使用 _previewWidthPx
|
|
887
|
+
// 否则使用 _previewWidthPx 并夹到 previewBounds(拖拽与渲染共用同一口径,杜绝两套夹取打架)
|
|
829
888
|
previewWidth() {
|
|
830
889
|
if (this.chromeOff) return this.stageWidth
|
|
831
|
-
const
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
890
|
+
const { min, max } = this.previewBounds
|
|
891
|
+
const w = this._previewWidthPx || PREVIEW_MIN
|
|
892
|
+
return Math.max(min, Math.min(max, w))
|
|
893
|
+
},
|
|
894
|
+
/** 视口宽(响应式:随 stageWidth / colsWidth 的实测更新一起重算) */
|
|
895
|
+
viewportWidth() {
|
|
896
|
+
// 依赖 stageWidth 以便窗口缩放时重算;取值仍用 innerWidth(vw 口径与 CSS 一致)
|
|
897
|
+
void this.stageWidth
|
|
898
|
+
return typeof window !== 'undefined' ? window.innerWidth : 1280
|
|
899
|
+
},
|
|
900
|
+
/** 中栏软目标宽:屏宽 1/3,且不低于硬下限(下发给 CSS `.debug` 的 min-width) */
|
|
901
|
+
debugSoftWidth() {
|
|
902
|
+
return Math.max(DEBUG_MIN_FLOOR, Math.floor(this.viewportWidth / 3))
|
|
903
|
+
},
|
|
904
|
+
/** 预览列宽度区间:[188, min(舞台宽/2, 舞台宽 - 右栏下限 - 中栏软目标)]。
|
|
905
|
+
* 只依赖舞台宽与视口宽(不反向依赖中右两栏实测),杜绝循环夹取。 */
|
|
906
|
+
previewBounds() {
|
|
907
|
+
const stage = this.stageWidth || 0
|
|
908
|
+
const byHalf = Math.floor(stage / 2)
|
|
909
|
+
const byWorkspace = stage - API_MIN - this.debugSoftWidth
|
|
910
|
+
return { min: PREVIEW_MIN, max: Math.max(PREVIEW_MIN, Math.min(byHalf, byWorkspace)) }
|
|
911
|
+
},
|
|
912
|
+
/** 中栏(调试面板)实际最小宽:软目标(屏宽 1/3)优先,空间不足时退让到硬下限 */
|
|
913
|
+
debugMinWidth() {
|
|
914
|
+
const cols = this.colsWidth || Math.max(0, (this.stageWidth || 0) - this.previewWidth)
|
|
915
|
+
return Math.max(DEBUG_MIN_FLOOR, Math.min(this.debugSoftWidth, Math.floor(cols) - API_MIN))
|
|
916
|
+
},
|
|
917
|
+
/** 右栏(接口调用面板)宽度区间:常规 [320, 两栏容器宽 - 中栏最小宽];
|
|
918
|
+
* 容器实在放不下 320 时(窄窗口 + 预览列已占位)整体退让到剩余空间,绝不撑破容器
|
|
919
|
+
* —— 撑破会让面板被推出视口、分隔条接缝与面板边缘脱节,表现为「拖到极限就跳」。 */
|
|
920
|
+
apiPanelBounds() {
|
|
921
|
+
const cols = this.colsWidth || Math.max(0, (this.stageWidth || 0) - this.previewWidth)
|
|
922
|
+
const max = Math.max(API_ABS_MIN, Math.floor(cols) - this.debugMinWidth)
|
|
923
|
+
return { min: Math.min(API_MIN, max), max }
|
|
836
924
|
},
|
|
837
925
|
// 画布实际宽度(列宽,整数):canvas-bar__size 实时显示用,拖拽分隔条时随之变化
|
|
838
926
|
canvasW() {
|
|
839
927
|
return Math.round(this.previewWidth || 375)
|
|
840
928
|
},
|
|
841
|
-
|
|
842
|
-
|
|
929
|
+
/** 当前卡片名(AI 开发助手头部展示;列表未加载完时回落 id) */
|
|
930
|
+
selectedCardName() {
|
|
931
|
+
const hit = this.cardList.find((c) => c.id === this.selectedId)
|
|
932
|
+
return (hit && hit.name) || this.selectedId || ''
|
|
933
|
+
},
|
|
934
|
+
// 右侧「接口调用」面板宽度(像素):夹到 apiPanelBounds,保证中栏不小于屏宽 1/3、右栏不小于 320px
|
|
843
935
|
apiPanelWidth() {
|
|
844
|
-
const
|
|
845
|
-
const midMin = Math.floor((typeof window !== 'undefined' ? window.innerWidth : 1280) / 3)
|
|
846
|
-
const maxW = Math.max(minW, Math.floor((this.stageWidth || 0) - this.previewWidth - midMin))
|
|
936
|
+
const { min, max } = this.apiPanelBounds
|
|
847
937
|
const w = this._apiPanelWidthPx || 360
|
|
848
|
-
return Math.max(
|
|
938
|
+
return Math.max(min, Math.min(max, w))
|
|
849
939
|
},
|
|
850
940
|
// 缩放外框占满整列(宽高交由 CSS flex 填满;画布不再做 transform 缩放,直接按实际尺寸布局)
|
|
851
941
|
frameStyle() {
|
|
@@ -861,6 +951,13 @@ export default {
|
|
|
861
951
|
clearHoverEl()
|
|
862
952
|
}
|
|
863
953
|
},
|
|
954
|
+
aiChatOpen(val) {
|
|
955
|
+
try {
|
|
956
|
+
sessionStorage.setItem('cardbuddy:ai-chat-open', val ? '1' : '0')
|
|
957
|
+
} catch {
|
|
958
|
+
/* sessionStorage 不可用时忽略 */
|
|
959
|
+
}
|
|
960
|
+
},
|
|
864
961
|
// 主题切换:把 'dark' / 'light' 写到 <html data-theme>,驱动 CSS 变量整体换肤
|
|
865
962
|
theme(val) {
|
|
866
963
|
document.documentElement.dataset.theme = val
|
|
@@ -877,8 +974,6 @@ export default {
|
|
|
877
974
|
beforeUnmount() {
|
|
878
975
|
if (this.viewportObserver) this.viewportObserver.disconnect()
|
|
879
976
|
if (this._stageObserver) this._stageObserver.disconnect()
|
|
880
|
-
if (this._dragMove) document.removeEventListener('mousemove', this._dragMove)
|
|
881
|
-
if (this._dragUp) document.removeEventListener('mouseup', this._dragUp)
|
|
882
977
|
if (this.saveTimer) clearTimeout(this.saveTimer)
|
|
883
978
|
if (this.syncTimer) clearTimeout(this.syncTimer)
|
|
884
979
|
if (this.saveRemoteTimer) clearTimeout(this.saveRemoteTimer)
|
|
@@ -897,11 +992,17 @@ export default {
|
|
|
897
992
|
this.loadApiPanelWidth()
|
|
898
993
|
this._stageObserver = new ResizeObserver((entries) => {
|
|
899
994
|
for (const entry of entries) {
|
|
995
|
+
// 舞台与两栏容器分别实测:右栏夹取要用 colsWidth(不含预览列),不能拿舞台宽反推
|
|
996
|
+
if (entry.target === this.$refs.colsEl) {
|
|
997
|
+
this.colsWidth = Math.round(entry.contentRect.width)
|
|
998
|
+
continue
|
|
999
|
+
}
|
|
900
1000
|
this.stageWidth = Math.round(entry.contentRect.width)
|
|
901
1001
|
this.stageHeight = Math.round(entry.contentRect.height)
|
|
902
1002
|
}
|
|
903
1003
|
})
|
|
904
1004
|
if (this.$refs.stageEl) this._stageObserver.observe(this.$refs.stageEl)
|
|
1005
|
+
if (this.$refs.colsEl) this._stageObserver.observe(this.$refs.colsEl)
|
|
905
1006
|
// 全局键盘快捷键:复制/粘贴/副本/删除/撤销/重做/取消选中(见 onKeydown)
|
|
906
1007
|
window.addEventListener('keydown', this.onKeydown)
|
|
907
1008
|
},
|
|
@@ -1023,32 +1124,84 @@ export default {
|
|
|
1023
1124
|
showAppToast('本地开发服务配置已保存', 'ok')
|
|
1024
1125
|
this.fetchCardList()
|
|
1025
1126
|
},
|
|
1026
|
-
// ——
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1127
|
+
// —— 列宽拖拽(左:预览列 / 右:接口调用面板)——
|
|
1128
|
+
/**
|
|
1129
|
+
* 分隔条拖拽统一入口:Pointer Events + 指针捕获。
|
|
1130
|
+
* 曾经左条绑 mousedown、处理函数却按 pointer 写(ev.pointerId 为 undefined → 捕获失败,
|
|
1131
|
+
* pointermove 只在 6px 命中区内触发),表现为「左侧分隔条拖不动」;两条统一走这里。
|
|
1132
|
+
* 捕获后即使松手点在视口外,pointerup / pointercancel 仍回送本元素,不会残留拖拽态。
|
|
1133
|
+
* @param {PointerEvent} ev
|
|
1134
|
+
* @param {(e: PointerEvent) => void} onMove 每次移动的夹取写入
|
|
1135
|
+
* @param {() => void} onEnd 收尾(清光标 / 持久化)
|
|
1136
|
+
*/
|
|
1137
|
+
beginColumnDrag(ev, onMove, onEnd) {
|
|
1138
|
+
const el = ev && ev.currentTarget
|
|
1139
|
+
if (!el) return
|
|
1140
|
+
const pointerId = ev.pointerId
|
|
1141
|
+
try {
|
|
1142
|
+
el.setPointerCapture(pointerId)
|
|
1143
|
+
} catch {
|
|
1144
|
+
/* 不支持捕获时退化为元素级监听 */
|
|
1041
1145
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1146
|
+
const finish = () => {
|
|
1147
|
+
el.removeEventListener('pointermove', onMove)
|
|
1148
|
+
el.removeEventListener('pointerup', finish)
|
|
1149
|
+
el.removeEventListener('pointercancel', finish)
|
|
1150
|
+
window.removeEventListener('blur', finish)
|
|
1151
|
+
try {
|
|
1152
|
+
if (el.hasPointerCapture?.(pointerId)) el.releasePointerCapture(pointerId)
|
|
1153
|
+
} catch {
|
|
1154
|
+
/* 已释放 */
|
|
1155
|
+
}
|
|
1044
1156
|
document.body.style.cursor = ''
|
|
1045
1157
|
document.body.style.userSelect = ''
|
|
1046
|
-
|
|
1047
|
-
document.removeEventListener('mouseup', this._dragUp)
|
|
1048
|
-
this.savePreviewWidth()
|
|
1158
|
+
onEnd()
|
|
1049
1159
|
}
|
|
1050
|
-
document.
|
|
1051
|
-
document.
|
|
1160
|
+
document.body.style.cursor = 'col-resize'
|
|
1161
|
+
document.body.style.userSelect = 'none'
|
|
1162
|
+
el.addEventListener('pointermove', onMove)
|
|
1163
|
+
el.addEventListener('pointerup', finish)
|
|
1164
|
+
el.addEventListener('pointercancel', finish)
|
|
1165
|
+
window.addEventListener('blur', finish) // 窗口失焦兜底:切走 / 最小化不卡死拖拽态
|
|
1166
|
+
},
|
|
1167
|
+
// 左分隔条:拖动改变预览列宽度(写像素,previewWidth / canvasW 自动联动),松手持久化
|
|
1168
|
+
startDrag(ev) {
|
|
1169
|
+
if (this.chromeOff) return
|
|
1170
|
+
const stage = this.$refs.stageEl
|
|
1171
|
+
if (!stage) return
|
|
1172
|
+
const baseLeft = stage.getBoundingClientRect().left
|
|
1173
|
+
this.dragging = true
|
|
1174
|
+
this.beginColumnDrag(
|
|
1175
|
+
ev,
|
|
1176
|
+
(e) => {
|
|
1177
|
+
const { min, max } = this.previewBounds
|
|
1178
|
+
this._previewWidthPx = Math.max(min, Math.min(max, Math.round(e.clientX - baseLeft)))
|
|
1179
|
+
},
|
|
1180
|
+
() => {
|
|
1181
|
+
this.dragging = false
|
|
1182
|
+
this.savePreviewWidth()
|
|
1183
|
+
}
|
|
1184
|
+
)
|
|
1185
|
+
},
|
|
1186
|
+
// 右分隔条:拖动改变「接口调用」面板宽度(向左加宽 / 向右收窄),松手持久化。
|
|
1187
|
+
// 基准取两栏容器右缘(= 面板右缘),夹取用 apiPanelBounds,与渲染口径完全一致 → 极限处不再跳动
|
|
1188
|
+
startApiDrag(ev) {
|
|
1189
|
+
if (this.chromeOff || !this.apiPanelOpen) return
|
|
1190
|
+
const cols = this.$refs.colsEl
|
|
1191
|
+
if (!cols) return
|
|
1192
|
+
const baseRight = cols.getBoundingClientRect().right
|
|
1193
|
+
this.apiDragging = true
|
|
1194
|
+
this.beginColumnDrag(
|
|
1195
|
+
ev,
|
|
1196
|
+
(e) => {
|
|
1197
|
+
const { min, max } = this.apiPanelBounds
|
|
1198
|
+
this._apiPanelWidthPx = Math.max(min, Math.min(max, Math.round(baseRight - e.clientX)))
|
|
1199
|
+
},
|
|
1200
|
+
() => {
|
|
1201
|
+
this.apiDragging = false
|
|
1202
|
+
this.saveApiPanelWidth()
|
|
1203
|
+
}
|
|
1204
|
+
)
|
|
1052
1205
|
},
|
|
1053
1206
|
// 读取上次保存的预览列宽度(像素);首次打开(无记录)时保持默认 372px
|
|
1054
1207
|
loadPreviewWidth() {
|
|
@@ -1069,40 +1222,10 @@ export default {
|
|
|
1069
1222
|
// 预设尺寸切换:375 / 188 一键设定预览列宽度;高度随列高动态保持一致(由 CSS flex 自动填充)
|
|
1070
1223
|
setPresetWidth(w) {
|
|
1071
1224
|
if (this.chromeOff) return
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
const maxW = Math.max(minW, Math.floor(total / 2)) // 最大不得超过可视宽度的一半
|
|
1075
|
-
this._previewWidthPx = Math.max(minW, Math.min(maxW, w))
|
|
1225
|
+
const { min, max } = this.previewBounds
|
|
1226
|
+
this._previewWidthPx = Math.max(min, Math.min(max, w))
|
|
1076
1227
|
this.savePreviewWidth()
|
|
1077
1228
|
},
|
|
1078
|
-
// —— 接口调用面板宽度调节 ——
|
|
1079
|
-
// 拖拽分隔条:拖动改变右栏宽度(向左拖加宽、向右拖收窄),松手持久化。
|
|
1080
|
-
// 约束:右栏 [320px, 舞台宽 - 预览列宽 - 中栏最小宽(屏宽 1/3)],与 apiPanelWidth computed 一致
|
|
1081
|
-
startApiDrag() {
|
|
1082
|
-
if (this.chromeOff || !this.apiPanelOpen) return
|
|
1083
|
-
this.apiDragging = true
|
|
1084
|
-
document.body.style.cursor = 'col-resize'
|
|
1085
|
-
document.body.style.userSelect = 'none'
|
|
1086
|
-
const stage = this.$refs.stageEl
|
|
1087
|
-
this._apiDragMove = (ev) => {
|
|
1088
|
-
const rect = stage.getBoundingClientRect()
|
|
1089
|
-
const minW = 320 // 右栏最小宽度
|
|
1090
|
-
const midMin = Math.floor(window.innerWidth / 3) // 中栏最小宽度:屏宽 1/3
|
|
1091
|
-
const maxW = Math.max(minW, Math.floor(rect.width - this.previewWidth - midMin))
|
|
1092
|
-
const w = Math.max(minW, Math.min(maxW, rect.right - ev.clientX))
|
|
1093
|
-
this._apiPanelWidthPx = w
|
|
1094
|
-
}
|
|
1095
|
-
this._apiDragUp = () => {
|
|
1096
|
-
this.apiDragging = false
|
|
1097
|
-
document.body.style.cursor = ''
|
|
1098
|
-
document.body.style.userSelect = ''
|
|
1099
|
-
document.removeEventListener('mousemove', this._apiDragMove)
|
|
1100
|
-
document.removeEventListener('mouseup', this._apiDragUp)
|
|
1101
|
-
this.saveApiPanelWidth()
|
|
1102
|
-
}
|
|
1103
|
-
document.addEventListener('mousemove', this._apiDragMove)
|
|
1104
|
-
document.addEventListener('mouseup', this._apiDragUp)
|
|
1105
|
-
},
|
|
1106
1229
|
// 读取上次保存的接口调用面板宽度(像素);首次打开(无记录)时保持默认 360px
|
|
1107
1230
|
loadApiPanelWidth() {
|
|
1108
1231
|
try {
|
|
@@ -1125,20 +1248,35 @@ export default {
|
|
|
1125
1248
|
},
|
|
1126
1249
|
onSelect(id) {
|
|
1127
1250
|
this.loadingCard = true
|
|
1251
|
+
// 乐观置位:下拉立刻跟手。持久化交给 loadCard —— 载入成功才记,
|
|
1252
|
+
// 避免把一张打不开的卡记成「上次选中」,下次进页面直接白屏
|
|
1253
|
+
this.selectedId = id
|
|
1254
|
+
this.loadCard(id, { clearAll: true })
|
|
1255
|
+
},
|
|
1256
|
+
/**
|
|
1257
|
+
* 置为「当前卡」并持久化(下次打开页面自动恢复,沿用 cardbuddy: 前缀约定)。
|
|
1258
|
+
* 顶栏下拉、网格高亮、校验徽标、RenderTree 的 key、导入元数据的过期判定全读 selectedId,
|
|
1259
|
+
* 所以由 loadCard 统一置位:任何调用点(下拉选中、网格点卡、新增空白卡、删除后切下一张)
|
|
1260
|
+
* 都不会再出现「画布切过去了、下拉还停在旧卡」的错位。
|
|
1261
|
+
*/
|
|
1262
|
+
selectCard(id) {
|
|
1128
1263
|
this.selectedId = id
|
|
1129
|
-
// 持久化当前选中卡片,下次打开页面自动恢复(沿用 cardbuddy: 前缀约定)
|
|
1130
1264
|
try {
|
|
1131
1265
|
localStorage.setItem('cardbuddy:selected-card', id)
|
|
1132
1266
|
} catch (e) {
|
|
1133
1267
|
/* localStorage 不可用时忽略 */
|
|
1134
1268
|
}
|
|
1135
|
-
this.loadCard(id, { clearAll: true })
|
|
1136
1269
|
},
|
|
1137
1270
|
/** 打开卡片网格预览弹窗 */
|
|
1138
1271
|
openGallery() {
|
|
1139
1272
|
if (!this.cardList.length) return
|
|
1140
1273
|
this.galleryOpen = true
|
|
1141
1274
|
},
|
|
1275
|
+
/** 打开「新增空白卡片」弹窗:先刷一次列表,重名判定才拿得到最新本地卡片(别的窗口 / 同步远程可能刚建过卡) */
|
|
1276
|
+
openNewCard() {
|
|
1277
|
+
this.newCardOpen = true
|
|
1278
|
+
this.fetchCardList()
|
|
1279
|
+
},
|
|
1142
1280
|
/** 关闭卡片网格预览弹窗 */
|
|
1143
1281
|
closeGallery() {
|
|
1144
1282
|
this.galleryOpen = false
|
|
@@ -1327,6 +1465,9 @@ export default {
|
|
|
1327
1465
|
this.rt.pushError('loadCard', 'schema 读取失败')
|
|
1328
1466
|
return
|
|
1329
1467
|
}
|
|
1468
|
+
// 载入成功即认定这张卡为当前卡:必须在 fetchImportMeta / refreshValidateBadge 之前,
|
|
1469
|
+
// 二者都以 selectedId 作「是否已切卡」的判据,晚置位会把新卡的结果当过期丢掉
|
|
1470
|
+
this.selectCard(id)
|
|
1330
1471
|
// 切换卡片:清全部错误;刷新/重建预览:只清绑定错误,保留事件错误
|
|
1331
1472
|
if (opts.clearAll) {
|
|
1332
1473
|
this.rt.clearErrors()
|
|
@@ -1740,6 +1881,16 @@ export default {
|
|
|
1740
1881
|
clearHoverEl()
|
|
1741
1882
|
this.scheduleCommit()
|
|
1742
1883
|
},
|
|
1884
|
+
/** 页面是否存在非空文本选区:有则视为「文本复制/粘贴」意图,交还原生行为 */
|
|
1885
|
+
hasTextSelection() {
|
|
1886
|
+
if (typeof window === 'undefined' || !window.getSelection) return false
|
|
1887
|
+
const sel = window.getSelection()
|
|
1888
|
+
return !!sel && !sel.isCollapsed && sel.toString().trim() !== ''
|
|
1889
|
+
},
|
|
1890
|
+
/** 是否存在可用于节点操作的选区(选中了非根节点):节点复制/粘贴/副本的前提 */
|
|
1891
|
+
hasNodeSelection() {
|
|
1892
|
+
return !!this.selectedNodeId && this.selectedNodeId !== '_root'
|
|
1893
|
+
},
|
|
1743
1894
|
/** 判断按键事件焦点是否在可编辑元素内(输入框/文本域/可选文本),此时不劫持快捷键 */
|
|
1744
1895
|
isEditableTarget(e) {
|
|
1745
1896
|
const t = e.target
|
|
@@ -1755,6 +1906,11 @@ export default {
|
|
|
1755
1906
|
* Ctrl/⌘+Z 撤销 · Ctrl/⌘+Shift+Z / Ctrl+Y 重做 · Esc 取消选中。
|
|
1756
1907
|
* 焦点在输入框/JSON 编辑器等可编辑元素内时,仅放行 Esc,其余交还原生行为
|
|
1757
1908
|
* (避免误删节点或劫持文本撤销/复制)。
|
|
1909
|
+
* 节点复制/粘贴与系统文本复制/粘贴共存判据(按优先级):
|
|
1910
|
+
* 1) 焦点可编辑 → 一律原生(文本撤销/复制/粘贴);
|
|
1911
|
+
* 2) 存在非空文本选区 → 一律原生(用户意图是文本);
|
|
1912
|
+
* 3) 节点类操作还要求已选中非根节点(粘贴另要求节点剪贴板非空),
|
|
1913
|
+
* 否则不 preventDefault,把按键还给浏览器(如 ⌘D 书签)。
|
|
1758
1914
|
*/
|
|
1759
1915
|
onKeydown(e) {
|
|
1760
1916
|
if (this.chromeOff || !this.preview) return
|
|
@@ -1764,18 +1920,21 @@ export default {
|
|
|
1764
1920
|
|
|
1765
1921
|
if (ctrl && (k === 'c' || k === 'C')) {
|
|
1766
1922
|
if (editable) return
|
|
1923
|
+
if (this.hasTextSelection() || !this.hasNodeSelection()) return
|
|
1767
1924
|
this.copySelectedNode()
|
|
1768
1925
|
e.preventDefault()
|
|
1769
1926
|
return
|
|
1770
1927
|
}
|
|
1771
1928
|
if (ctrl && (k === 'v' || k === 'V')) {
|
|
1772
1929
|
if (editable) return
|
|
1930
|
+
if (this.hasTextSelection() || !this.hasNodeSelection() || !getClipboardNode()) return
|
|
1773
1931
|
this.pasteNode()
|
|
1774
1932
|
e.preventDefault()
|
|
1775
1933
|
return
|
|
1776
1934
|
}
|
|
1777
1935
|
if (ctrl && (k === 'd' || k === 'D')) {
|
|
1778
1936
|
if (editable) return
|
|
1937
|
+
if (this.hasTextSelection() || !this.hasNodeSelection()) return
|
|
1779
1938
|
this.duplicateNode()
|
|
1780
1939
|
e.preventDefault()
|
|
1781
1940
|
return
|
|
@@ -2569,6 +2728,37 @@ export default {
|
|
|
2569
2728
|
console.error('[加载卡片列表失败]', e)
|
|
2570
2729
|
}
|
|
2571
2730
|
},
|
|
2731
|
+
/**
|
|
2732
|
+
* 网格预览删除卡片:格子删除钮 → 警告确认 → 这里真删(DELETE /__card/<id> 递归删本地目录)。
|
|
2733
|
+
* 删完刷列表;若删的正是当前卡,切到剩余首卡,避免工作台指向已不存在的目录(保存会 404)。
|
|
2734
|
+
*/
|
|
2735
|
+
async onGalleryDelete(id) {
|
|
2736
|
+
try {
|
|
2737
|
+
const res = await apiFetch(`/__card/${encodeURIComponent(id)}`, { method: 'DELETE' })
|
|
2738
|
+
const r = await res.json().catch(() => null)
|
|
2739
|
+
if (!res.ok) throw new Error((r && (r.message || r.error)) || `HTTP ${res.status}`)
|
|
2740
|
+
showAppToast(`已删除本地卡片 ${id}`, 'ok')
|
|
2741
|
+
await this.fetchCardList()
|
|
2742
|
+
if (this.selectedId === id) {
|
|
2743
|
+
const next = this.cardList[0]
|
|
2744
|
+
if (next) await this.loadCard(next.id)
|
|
2745
|
+
else {
|
|
2746
|
+
this.selectedId = ''
|
|
2747
|
+
this.currentSchema = null
|
|
2748
|
+
this.preview = null
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
} catch (e) {
|
|
2752
|
+
showAppToast(`删除卡片失败:${e.message}`, 'err')
|
|
2753
|
+
}
|
|
2754
|
+
},
|
|
2755
|
+
/** 新增空白卡片创建成功:关弹窗 → 刷列表 → 切换到新卡(空白卡无封面,网格显示占位图) */
|
|
2756
|
+
async onNewCardCreated(id) {
|
|
2757
|
+
this.newCardOpen = false
|
|
2758
|
+
showAppToast(`已创建空白卡片 ${id}`, 'ok')
|
|
2759
|
+
await this.fetchCardList()
|
|
2760
|
+
await this.loadCard(id)
|
|
2761
|
+
},
|
|
2572
2762
|
/** 刷新当前卡片:从磁盘重新读取 schema/data 并强制重挂载 */
|
|
2573
2763
|
async onRefresh() {
|
|
2574
2764
|
const id = this.selectedId
|
package/dist/vendored/api.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 设计目标(前后端分离方案 Phase 2):
|
|
5
5
|
* - 开发期(npm run dev,由 tools/dev.mjs 同起 cardbuddy):apiBase 默认
|
|
6
|
-
* http://127.0.0.1:<FINMALL_CARD_PORT>(.env.
|
|
6
|
+
* http://127.0.0.1:<FINMALL_CARD_PORT>(.env.cb,默认 8787),与部署态一致。
|
|
7
7
|
* 若页面不是经回环地址打开(局域网 IP / 其它本机 IP),则退化为同源 '',
|
|
8
8
|
* 由 Vite dev server 代理到 8787,避开浏览器私有网络访问(PNA)拦截。
|
|
9
9
|
* - 部署态(vite build 静态产物):默认读 localStorage 的「本地开发服务」配置;
|
|
@@ -28,8 +28,8 @@ const PREVIEW_API_BASE =
|
|
|
28
28
|
? window.__CARD_DEV_PREVIEW_BASE__ || window.__CARD_DEV_OFFLINE_BASE__ || ''
|
|
29
29
|
: ''
|
|
30
30
|
|
|
31
|
-
// 本地开发服务默认端口:与 .env.
|
|
32
|
-
// 开发期 .env.
|
|
31
|
+
// 本地开发服务默认端口:与 .env.cb 的 FINMALL_CARD_PORT 保持一致(默认 8787)。
|
|
32
|
+
// 开发期 .env.cb 经 vite envPrefix 暴露为 import.meta.env.FINMALL_CARD_PORT,
|
|
33
33
|
// 前端据此连接;该值同时由启动器注入 cardbuddy 子进程(DSL_DEV_PORT)使其监听同一端口。
|
|
34
34
|
const CARD_DEV_PORT = Number(import.meta.env.FINMALL_CARD_PORT || 8787)
|
|
35
35
|
|
|
@@ -85,7 +85,7 @@ export function saveDevServiceConfig(cfg) {
|
|
|
85
85
|
* - 显式覆盖(VITE_API_BASE,手动 / 部署态注入)→ 该 URL
|
|
86
86
|
* - 用户已在设置启用并填了地址 → 该地址(去尾斜杠)
|
|
87
87
|
* - Vite dev server(import.meta.env.DEV 且无覆盖)→ 默认本地开发服务
|
|
88
|
-
* http://127.0.0.1:<FINMALL_CARD_PORT>(.env.
|
|
88
|
+
* http://127.0.0.1:<FINMALL_CARD_PORT>(.env.cb,默认 8787)
|
|
89
89
|
* (前后端分离:Vite 不再托管卡片后端,改由 tools/dev.mjs 同起的 cardbuddy 提供)
|
|
90
90
|
* 例外:页面经非回环主机名打开时返回 ''(同源),改由 Vite dev server 代理到 8787,
|
|
91
91
|
* 绕开浏览器私有网络访问(PNA)拦截,详见 isNonLoopbackDevHost 注释
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<symbol id="icon-save" viewBox="0 0 24 24"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></symbol>
|
|
9
9
|
<symbol id="icon-copy" viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></symbol>
|
|
10
10
|
<symbol id="icon-loading" viewBox="0 0 24 24"><line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line></symbol>
|
|
11
|
+
<symbol id="icon-activity" viewBox="0 0 24 24"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></symbol>
|
|
11
12
|
<symbol id="icon-cloud-check" viewBox="0 0 24 24"><path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path><polyline points="9 15 11 17 14 13"></polyline></symbol>
|
|
12
13
|
<symbol id="icon-cloud-alert" viewBox="0 0 24 24"><path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path><line x1="12" y1="13" x2="12" y2="16"></line><line x1="12" y1="18" x2="12.01" y2="18"></line></symbol>
|
|
13
14
|
<symbol id="icon-cloud" viewBox="0 0 24 24"><path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path></symbol>
|
|
@@ -27,6 +28,33 @@
|
|
|
27
28
|
<!-- 连接(link):本地开发服务「连接前后端」语义图标,呼应主题【连接】 -->
|
|
28
29
|
<symbol id="icon-link" viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></symbol>
|
|
29
30
|
|
|
31
|
+
<!-- AI 开发助手(sparkles):悬浮入口 / 面板标题 / 思考态,与主题「智能辅助」呼应 -->
|
|
32
|
+
<symbol id="icon-sparkles" viewBox="0 0 24 24"><path d="M12 3l1.9 4.9L18.8 9.8l-4.9 1.9L12 16.6l-1.9-4.9L5.2 9.8l4.9-1.9L12 3z"></path><path d="M18 15.5l.9 2.2 2.2.9-2.2.9-.9 2.2-.9-2.2-2.2-.9 2.2-.9.9-2.2z"></path></symbol>
|
|
33
|
+
<!-- 终止生成(实心方块):流式进行中的「停止」按钮 -->
|
|
34
|
+
<symbol id="icon-stop" viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="2" fill="currentColor" stroke="none"></rect></symbol>
|
|
35
|
+
<!-- 发送(arrow-up):输入框发送态 -->
|
|
36
|
+
<symbol id="icon-send" viewBox="0 0 24 24"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></symbol>
|
|
37
|
+
<!-- 附件(paperclip):本地文件 / 图片挂载入口 -->
|
|
38
|
+
<symbol id="icon-paperclip" viewBox="0 0 24 24"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></symbol>
|
|
39
|
+
<!-- 文件夹加号:挂载本地文件夹(如 Sketch MeaXure 标注导出目录) -->
|
|
40
|
+
<symbol id="icon-folder-plus" viewBox="0 0 24 24"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path><line x1="12" y1="11" x2="12" y2="17"></line><line x1="9" y1="14" x2="15" y2="14"></line></symbol>
|
|
41
|
+
<!-- 图片(image):图片类附件缩略占位 -->
|
|
42
|
+
<symbol id="icon-image" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></symbol>
|
|
43
|
+
<!-- 文件(file-text):文本类附件占位 -->
|
|
44
|
+
<symbol id="icon-file-text" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line></symbol>
|
|
45
|
+
<!-- 关闭(x):面板 / 弹层关闭,替代文本 ✕ -->
|
|
46
|
+
<symbol id="icon-x" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></symbol>
|
|
47
|
+
<!-- 工具调用(wrench):assistant 消息里的工具执行行 -->
|
|
48
|
+
<symbol id="icon-eye" viewBox="0 0 24 24"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></symbol>
|
|
49
|
+
<symbol id="icon-eye-off" viewBox="0 0 24 24"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"></path><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"></path><path d="M14.12 14.12a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></symbol>
|
|
50
|
+
<symbol id="icon-wrench" viewBox="0 0 24 24"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></symbol>
|
|
51
|
+
<!-- 回到底部(arrow-down):滚动脱离底部时的悬浮按钮 -->
|
|
52
|
+
<symbol id="icon-arrow-down" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></symbol>
|
|
53
|
+
<!-- 帮助(help-circle):表单字段的「?」详情入口,点开看说明浮层 -->
|
|
54
|
+
<symbol id="icon-help" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></symbol>
|
|
55
|
+
<!-- 展开箭头(chevron-right):折叠行展开态,CSS 旋转 90° 即为收起态 -->
|
|
56
|
+
<symbol id="icon-chevron-right" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"></polyline></symbol>
|
|
57
|
+
|
|
30
58
|
<!-- ===== 16x16 小图标(下拉 / 卡片选择 / 设置器) ===== -->
|
|
31
59
|
<symbol id="icon-chevron" viewBox="0 0 16 16"><path d="M4 6l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path></symbol>
|
|
32
60
|
<symbol id="icon-check" viewBox="0 0 16 16"><path d="M3.5 8.5l3 3 6-7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"></path></symbol>
|