diff --git a/frontend/src/components/canvas/CanvasStage.vue b/frontend/src/components/canvas/CanvasStage.vue
index 599b1f0..75b2533 100644
--- a/frontend/src/components/canvas/CanvasStage.vue
+++ b/frontend/src/components/canvas/CanvasStage.vue
@@ -633,6 +633,16 @@ function zoomStep(direction) {
Object.assign(vp, zoomAt(vp, cx, cy, scale))
notifyViewport()
}
+// 直接缩放到指定比例(围绕视口中心)
+function zoomTo(scale) {
+ const r = stageEl.value?.getBoundingClientRect()
+ const cx = r ? r.width / 2 : 0
+ const cy = r ? r.height / 2 : 0
+ scale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale))
+ if (scale === vp.scale) return
+ Object.assign(vp, zoomAt(vp, cx, cy, scale))
+ notifyViewport()
+}
function centerOn(x, y) {
const r = stageEl.value?.getBoundingClientRect()
vp.x = (r ? r.width / 2 : 400) - x * vp.scale
@@ -690,7 +700,7 @@ function screenRectToWorld(r) {
}
}
defineExpose({
- setViewport, zoomStep, centerOn, clearSelection, selectedIds, selectAll, syncZTop, vp,
+ setViewport, zoomStep, zoomTo, centerOn, clearSelection, selectedIds, selectAll, syncZTop, vp,
cancelLink, consumeSelectedEdges, hasSelectedEdges, fitToContent, selectIds, bumpNode,
})
diff --git a/frontend/src/views/CanvasView.vue b/frontend/src/views/CanvasView.vue
index 98f3c8e..a4f6719 100644
--- a/frontend/src/views/CanvasView.vue
+++ b/frontend/src/views/CanvasView.vue
@@ -55,6 +55,8 @@ const countOptions = [1, 2, 3, 4].map((n) => ({ value: n, label: `${n} 张` }))
const canvasOptions = computed(() =>
canvases.value.map((c) => ({ value: c.id, label: c.title || '未命名画布' })),
)
+const zoomOptions = [20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 200]
+ .map((p) => ({ value: p, label: `${p}%` }))
const maxRefs = computed(() => Math.min(8, Math.max(
Number(model.value?.max_reference_images || 0), model.value?.image_to_image ? 1 : 0,
)))
@@ -827,7 +829,12 @@ const saveLabel = computed(() => ({
- {{ scalePct }}%
+ stage?.zoomTo(p / 100)"
+ />
@@ -1079,6 +1086,13 @@ const saveLabel = computed(() => ({
.cv-dd--canvas :deep(.field) {
height: 30px;
}
+.cv-dd--zoom {
+ min-width: 74px;
+}
+.cv-dd--zoom :deep(.field) {
+ height: 30px;
+ justify-content: center;
+}
.cv-sep {
width: 2.5px;
height: 20px;
@@ -1091,12 +1105,6 @@ const saveLabel = computed(() => ({
font-size: 12px;
font-weight: 700;
color: var(--fg);
- min-width: 46px;
- text-align: center;
- background: var(--hover);
- border: 1px solid rgb(124 92 252 / 0.35);
- border-radius: 999px;
- padding: 1px 8px;
}
.cv-save {
font-size: 11px;