b6fb767928
- 新建 stores/screen.ts(id用 crypto.randomUUID 唯一生成、addScreen/removeScreen/renameScreen actions) - 新建 screen-panel.vue 替换 canvas-area.vue(NTab + NTabs type=card + addable/closable) - 添加屏幕增删的二次确认弹窗(window.$dialog.warning/info) - 双击Tab弹出重命名输入框(沿用删除dialog的交互风格) - 调整 onClose 入参命名为 id,语义与 store 一致 - 包裹 Tab 区域加 user-select: none,防止双击/拖动时文字被选中 - 附带:config-panel.vue 同步 Prettier 格式化(无逻辑改动)
124 lines
3.3 KiB
Vue
124 lines
3.3 KiB
Vue
<script setup lang="ts">
|
||
import { NButton, NIcon, NTabPane, NTabs, NText } from 'naive-ui';
|
||
import { ChevronRightIcon, DatabaseIcon, LayoutGridIcon, SlidersHorizontalIcon, ZapIcon } from 'lucide-vue-next';
|
||
import { ref, type Component } from 'vue';
|
||
import { useConfigPanelStore } from '../stores';
|
||
import { storeToRefs } from 'pinia';
|
||
|
||
interface ControlTabPane {
|
||
name: string;
|
||
tab: string;
|
||
icon: Component;
|
||
}
|
||
|
||
const tabs: ControlTabPane[] = [
|
||
{ name: 'component', tab: '组件', icon: LayoutGridIcon },
|
||
{ name: 'config', tab: '属性', icon: SlidersHorizontalIcon },
|
||
{ name: 'data', tab: '数据', icon: DatabaseIcon },
|
||
{ name: 'interaction', tab: '事件', icon: ZapIcon },
|
||
];
|
||
|
||
const PANEL_WIDTH_EXPANDED = '320px';
|
||
const PANEL_WIDTH_COLLAPSED = '72px';
|
||
const TAB_WIDTH = '72px';
|
||
|
||
const activeTab = ref(tabs[0]?.name ?? '');
|
||
|
||
const configPanelStore = useConfigPanelStore();
|
||
const { collapsed } = storeToRefs(configPanelStore);
|
||
|
||
const expandConfigPanel = () => {
|
||
if (collapsed.value) {
|
||
configPanelStore.toggleCollapsed();
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
:style="{
|
||
width: collapsed ? PANEL_WIDTH_COLLAPSED : PANEL_WIDTH_EXPANDED,
|
||
flexShrink: 0,
|
||
height: '100%',
|
||
display: 'flex',
|
||
justifyContent: 'flex-end',
|
||
overflow: 'hidden',
|
||
transition: 'width 0.2s',
|
||
}"
|
||
>
|
||
<div
|
||
:style="{
|
||
width: PANEL_WIDTH_EXPANDED,
|
||
height: '100%',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
}"
|
||
>
|
||
<div
|
||
:style="{
|
||
height: '42px',
|
||
flexShrink: 0,
|
||
padding: '8px 0',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
}"
|
||
>
|
||
<div
|
||
:style="{
|
||
display: 'grid',
|
||
placeItems: 'center',
|
||
width: '32px',
|
||
marginRight: 'auto',
|
||
}"
|
||
>
|
||
<NButton text @click="configPanelStore.toggleCollapsed()">
|
||
<NIcon :component="ChevronRightIcon" />
|
||
</NButton>
|
||
</div>
|
||
<div
|
||
:style="{
|
||
display: 'grid',
|
||
placeItems: 'center',
|
||
width: TAB_WIDTH,
|
||
}"
|
||
>
|
||
<NText>控制</NText>
|
||
</div>
|
||
</div>
|
||
<div
|
||
:style="{
|
||
flex: 1,
|
||
minHeight: 0,
|
||
overflow: 'hidden',
|
||
}"
|
||
>
|
||
<NTabs
|
||
v-model:value="activeTab"
|
||
:type="'bar'"
|
||
:placement="'right'"
|
||
:size="'small'"
|
||
:tab-style="{
|
||
width: TAB_WIDTH,
|
||
height: '64px',
|
||
}"
|
||
:style="{
|
||
height: '100%',
|
||
'--n-pane-padding-top': '0',
|
||
'--n-tab-gap-vertical': '0',
|
||
}"
|
||
>
|
||
<NTabPane v-for="t in tabs" :key="t.name" :name="t.name" :tab="t.tab" :tab-props="{ onClick: () => expandConfigPanel() }">
|
||
<template #tab>
|
||
<div :style="{ width: '48px', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }">
|
||
<NIcon :size="18" :component="t.icon" />
|
||
<div :style="{ fontSize: '12px' }">{{ t.tab }}</div>
|
||
</div>
|
||
</template>
|
||
<div :style="{ padding: '20px', textAlign: 'center', fontSize: '12px' }">{{ t.tab }}面板(占位)</div>
|
||
</NTabPane>
|
||
</NTabs>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|