Files
cover-studio/src/components/ToolPanels.vue
T
im0o 3a3798e37b feat: 接入 iconify 图标库,右栏改成 Figma 式收起分组
图标统一走 unplugin-icons + hugeicons,构建期内联,出口收在 src/ui/icons.ts。
顺带修掉 TopBar 那个 undo 图标缺弧命令的 path。

右栏下半四组默认全收,收起态只有一行 32px;顶栏导出按钮改成先展开
「导出与模板」再滚,否则滚到底只看得到一行标题。

属性区去掉分组标题,改成 Figma 那种「字母/图标 + 无边框输入框」两列密排,
新增 W/H 宽高比锁(比例在开锁那一刻定格,不每次编辑现算)。

删掉 Sidebar.vue 和 panels/CanvasPanel.vue:上一轮改 Figma 布局之后
它们已无人 import,--sidebar-w 同批遗留。
2026-09-05 17:51:43 +08:00

181 lines
5.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, reactive, watch } from "vue";
import { useEditor } from "@/editor/useEditor";
import SourcePanel from "./panels/SourcePanel.vue";
import FilterPanel from "./panels/FilterPanel.vue";
import TextPanel from "./panels/TextPanel.vue";
import ExportPanel from "./panels/ExportPanel.vue";
import {
IconImage,
IconFilter,
IconText,
IconExport,
IconPlus,
IconChevronDown,
} from "@/ui/icons";
import { toolGroupRequest, type ToolGroupId } from "@/ui/toolGroups";
/**
* 右栏下半:背景 / 滤镜 / 文字 / 导出与模板 四个工具面板的容器。
*
* 版式照 Figma 的设计面板:每组平时只是**一行 32px 的标题**(图标 + 名字 + 右侧
* 加号),点开才展开内容。**默认四组全收**——这四个面板加起来一千多行控件,
* 常驻展开的话右栏一屏塞不下,找任何一个都得先滚。
*
* 自动展开只发生在一种情况:选中的对象类型正好对应某一组(选图片 → 滤镜,
* 选文字 → 文字)。这是「我要改这个」的明确信号,替用户省一次点击;反过来
* 不自动收起,收起是用户自己的事。
*
* 面板组件用 v-show 而不是 v-if 隐藏,保持挂载,它们内部 watch activeObject
* 的状态才能一直跟着画布走。滤镜和文字组在条件不满足时显示一行提示。
*
* 别处要把用户送到某个面板,走 `@/ui/toolGroups` 的 `openToolGroup(id)`。
*/
const { selectionKind } = useEditor();
const GROUPS = [
{ id: "bg", label: "背景", icon: IconImage, scope: "" },
{ id: "filter", label: "滤镜", icon: IconFilter, scope: "图片" },
{ id: "text", label: "文字", icon: IconText, scope: "文字" },
{ id: "export", label: "导出与模板", icon: IconExport, scope: "" },
] as const;
/** 每组是否展开。纯 UI 状态;组的内容是否可用看 selectionKind。 */
const open = reactive<Record<ToolGroupId, boolean>>({
bg: false,
filter: false,
text: false,
export: false,
});
const isImage = computed(() => selectionKind.value === "image");
const isText = computed(() => selectionKind.value === "text");
watch(selectionKind, (kind) => {
if (kind === "image") open.filter = true;
else if (kind === "text") open.text = true;
});
/** 别处(顶栏的导出按钮)点名要展开某一组。 */
watch(toolGroupRequest, (req) => {
if (req) open[req.id] = true;
});
function toggle(id: ToolGroupId) {
open[id] = !open[id];
}
</script>
<template>
<div class="tool-panels">
<section v-for="g in GROUPS" :key="g.id" class="sec" :class="{ open: open[g.id] }">
<button type="button" class="head" :aria-expanded="open[g.id]" @click="toggle(g.id)">
<component :is="g.icon" class="head-icon" />
<span class="title">{{ g.label }}</span>
<span v-if="g.scope && !open[g.id]" class="scope">{{ g.scope }}</span>
<IconChevronDown v-if="open[g.id]" class="mark" />
<IconPlus v-else class="mark" />
</button>
<div v-show="open[g.id]" class="body">
<template v-if="g.id === 'bg'">
<SourcePanel />
</template>
<template v-else-if="g.id === 'filter'">
<FilterPanel v-show="isImage" />
<p v-if="!isImage" class="hint">选中画布上的一张图片,这里会列出可叠加的滤镜。</p>
</template>
<template v-else-if="g.id === 'text'">
<TextPanel v-show="isText" />
<p v-if="!isText" class="hint">用文字工具(T)新建,或选中已有文字,再到这里调字体样式。</p>
</template>
<template v-else>
<ExportPanel />
</template>
</div>
</section>
</div>
</template>
<style scoped>
.tool-panels {
padding-bottom: 18px;
}
.sec {
border-top: 1px solid var(--line);
}
/* 标题行:整行可点,覆盖全局 button 外观。收起态就是这一行的全部高度。 */
.head {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
height: 32px;
padding: 0 10px;
border: none;
border-radius: 0;
background: transparent;
color: var(--fg-dim);
cursor: pointer;
text-align: left;
}
.head:hover {
background: var(--bg3);
color: var(--fg);
}
.head:focus-visible {
outline: 1px solid var(--accent);
outline-offset: -1px;
}
.sec.open .head {
color: var(--fg);
}
.head-icon {
font-size: 15px;
}
.title {
font-size: 12px;
font-weight: 500;
}
/* 收起时在右边提示这组作用在什么上;展开后收掉,让位给内容。 */
.scope {
margin-left: auto;
font-size: 10px;
color: var(--fg-dim);
opacity: 0.7;
white-space: nowrap;
}
/* 展开/收起的记号:收起是加号,展开是朝下的角标(和 Figma 一致)。 */
.mark {
margin-left: auto;
font-size: 14px;
color: var(--fg-dim);
}
.scope + .mark {
margin-left: 6px;
}
.head:hover .mark {
color: var(--fg);
}
.body {
padding: 2px 10px 12px;
}
.hint {
margin: 2px 0 0;
color: var(--fg-dim);
font-size: 11px;
line-height: 1.7;
}
</style>