update at 2026-02-13 22:26:53

This commit is contained in:
douboer@gmail.com
2026-02-13 22:26:53 +08:00
parent 2fe45888ba
commit 43107afff1
54 changed files with 2183 additions and 311 deletions

View File

@@ -9,8 +9,14 @@
<div class="toolbar">
<div ref="themeTriggerRef" class="tool-item theme-trigger">
<span class="tool-label">选择主题</span>
<button class="icon-btn" type="button" @click.stop="toggleThemePicker">
<img :src="iconChooseColor" alt="choose-color" />
<button
class="icon-btn"
type="button"
title="选择主题"
aria-label="选择主题"
@click.stop="toggleThemePicker"
>
<img :src="iconChooseColor" alt="choose-color" title="选择主题" />
</button>
<div
@@ -56,8 +62,14 @@
<div class="tool-item">
<span class="tool-label">文件上传</span>
<button class="icon-btn" type="button" @click="openFileDialog">
<img :src="iconUpload" alt="upload" />
<button
class="icon-btn"
type="button"
title="上传文件"
aria-label="上传文件"
@click="openFileDialog"
>
<img :src="iconUpload" alt="upload" title="上传文件" />
</button>
</div>
@@ -79,12 +91,24 @@
</label>
<div class="export-box">
<img :src="iconExport" alt="export" class="export-main" />
<button class="icon-btn export-item" type="button" @click="exportSvg">
<img :src="iconExportSvg" alt="export-svg" />
<img :src="iconExport" alt="export" class="export-main" title="导出" />
<button
class="icon-btn export-item"
type="button"
title="导出 SVG"
aria-label="导出 SVG"
@click="exportSvg"
>
<img :src="iconExportSvg" alt="export-svg" title="导出 SVG" />
</button>
<button class="icon-btn export-item" type="button" @click="exportPng">
<img :src="iconExportPng" alt="export-png" />
<button
class="icon-btn export-item"
type="button"
title="导出 PNG"
aria-label="导出 PNG"
@click="exportPng"
>
<img :src="iconExportPng" alt="export-png" title="导出 PNG" />
</button>
</div>
</div>
@@ -215,21 +239,26 @@
<section class="panel preview-panel">
<div class="preview-head">
<img :src="iconSankeyViewTitle" alt="桑基图预览" class="panel-title-svg panel-title-preview" />
<img
:src="iconSankeyViewTitle"
alt="桑基图预览"
title="桑基图预览"
class="panel-title-svg panel-title-preview"
/>
<div class="preview-controls">
<template v-if="isPhoneViewport">
<label class="compact-control mobile-only">
<img :src="iconGap" alt="间距" class="slider-icon" />
<select v-model.number="nodeGap" class="compact-select">
<template v-if="isNarrowViewport">
<label class="compact-control">
<img :src="iconGap" alt="间距" title="间距" class="slider-icon" />
<select v-model.number="nodeGap" class="compact-select" aria-label="间距">
<option v-for="value in mobileGapOptions" :key="`gap-${value}`" :value="value">
{{ value }}
</option>
</select>
</label>
<label class="compact-control mobile-only">
<img :src="iconPadding" alt="边距" class="slider-icon" />
<select v-model.number="chartPadding" class="compact-select">
<label class="compact-control">
<img :src="iconPadding" alt="边距" title="边距" class="slider-icon" />
<select v-model.number="chartPadding" class="compact-select" aria-label="边距">
<option
v-for="value in mobilePaddingOptions"
:key="`padding-${value}`"
@@ -239,22 +268,10 @@
</option>
</select>
</label>
<label class="compact-control mobile-only">
<span class="compact-label">方向</span>
<select v-model="direction" class="compact-select">
<option
v-for="option in directionOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</label>
</template>
<template v-else>
<label class="slider-label desktop-only">
<img :src="iconGap" alt="间距" class="slider-icon" />
<img :src="iconGap" alt="间距" title="间距" class="slider-icon" />
<div class="slider-track-wrap">
<span class="slider-value" :style="getSliderValueStyle(nodeGap, 0, 30)">{{
nodeGap
@@ -266,11 +283,12 @@
type="range"
min="0"
max="30"
aria-label="间距"
/>
</div>
</label>
<label class="slider-label desktop-only">
<img :src="iconPadding" alt="边距" class="slider-icon" />
<img :src="iconPadding" alt="边距" title="边距" class="slider-icon" />
<div class="slider-track-wrap">
<span class="slider-value" :style="getSliderValueStyle(chartPadding, 0, 80)">
{{ chartPadding }}
@@ -282,9 +300,26 @@
type="range"
min="0"
max="80"
aria-label="边距"
/>
</div>
</label>
</template>
<template v-if="isPhoneViewport">
<label class="compact-control mobile-only">
<span class="compact-label">方向</span>
<select v-model="direction" class="compact-select" aria-label="方向">
<option
v-for="option in directionOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</label>
</template>
<template v-else>
<button class="direction-control desktop-only" type="button" @click="toggleDirection">
<span class="direction-label">方向</span>
<span class="direction-switch" :class="{ on: direction === 'source-to-target' }">
@@ -440,6 +475,7 @@ const buildResult = ref<SankeyBuildResult | null>(null);
const uploadedFileSnapshot = ref<PersistedUploadedFile | null>(null);
const isRestoringWorkspace = ref(false);
const isPhoneViewport = ref(false);
const isNarrowViewport = ref(false);
const mapping = reactive<MappingConfig>({
sourceDataColumn: null,
@@ -900,6 +936,7 @@ function updatePhoneViewportFlag(): void {
}
isPhoneViewport.value = window.matchMedia('(max-width: 640px)').matches;
isNarrowViewport.value = window.matchMedia('(max-width: 1024px)').matches;
}
/**

View File

@@ -548,11 +548,12 @@ body {
.direction-control {
display: inline-flex;
align-items: center;
gap: 6px;
gap: 4px;
border: 0;
background: transparent;
padding: 0;
cursor: pointer;
flex-shrink: 0;
}
.direction-label {
@@ -565,29 +566,31 @@ body {
.direction-switch {
display: inline-flex;
align-items: center;
gap: 2px;
width: 80px;
height: 24px;
padding: 2px;
border-radius: 999px;
gap: 1px;
min-width: 0;
width: auto;
height: 14px;
padding: 1px;
border-radius: 58.333px;
background: var(--fill-3);
justify-content: space-between;
}
.direction-switch-text {
font-size: 14px;
font-size: 8px;
line-height: 1;
padding: 0 4px;
padding: 0 1px;
white-space: nowrap;
color: var(--text-4);
}
.direction-switch-thumb {
width: 20px;
height: 20px;
width: 11.667px;
height: 11.667px;
border-radius: 50%;
background: #fff;
border: 1px solid var(--fill-3);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
border: 0;
box-shadow: none;
}
.direction-switch.on {
@@ -617,15 +620,7 @@ body {
}
.label-position-select {
min-width: 64px;
height: 24px;
border: 1px solid #c9aee0;
border-radius: 2px;
background: #fff;
color: #1d2129;
font-size: 14px;
padding: 0 20px 0 6px;
line-height: 22px;
min-width: 0;
}
.target-align-control {
@@ -643,15 +638,7 @@ body {
}
.target-align-select {
min-width: 88px;
height: 24px;
border: 1px solid #c9aee0;
border-radius: 2px;
background: #fff;
color: #1d2129;
font-size: 14px;
padding: 0 20px 0 6px;
line-height: 22px;
min-width: 0;
}
.compact-control {
@@ -669,15 +656,29 @@ body {
}
.compact-select {
height: 24px;
min-width: 52px;
border: 1px solid #c9aee0;
border-radius: 2px;
min-width: 0;
}
.label-position-select,
.target-align-select,
.compact-select {
width: auto;
inline-size: max-content;
height: 14px;
border: 0.5px solid #c9aee0;
border-radius: 4px;
background: #fff;
color: #1d2129;
font-size: 12px;
padding: 0 16px 0 4px;
line-height: 22px;
line-height: 1;
padding: 0 14px 0 4px;
appearance: none;
-webkit-appearance: none;
background-image: url('../assets/icons/list.svg');
background-repeat: no-repeat;
background-position: right 2px center;
background-size: 10px 8px;
flex-shrink: 0;
}
.example-line {
@@ -859,7 +860,19 @@ body {
}
.upload-area {
display: none;
display: flex;
min-width: 92px;
max-width: none;
flex: 1 1 auto;
min-height: 28px;
padding: 2px 5px;
}
.upload-text {
font-size: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.top-bar {
@@ -870,14 +883,12 @@ body {
.toolbar {
width: 100%;
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
overflow: hidden;
justify-content: flex-start;
gap: 6px;
scrollbar-width: none;
gap: 4px;
}
.toolbar::-webkit-scrollbar {
.tool-item .tool-label {
display: none;
}
@@ -886,6 +897,27 @@ body {
flex-shrink: 0;
}
.tool-item .icon-btn {
width: 28px;
height: 28px;
}
.export-box {
padding: 1px 4px;
gap: 4px;
}
.export-main {
display: block;
width: 12px;
height: 24px;
}
.export-item {
width: 28px;
height: 28px;
}
.content {
gap: 6px;
}
@@ -948,13 +980,13 @@ body {
}
.label-position-select {
min-width: 46px;
min-width: 0;
font-size: 12px;
padding: 0 14px 0 4px;
}
.target-align-select {
min-width: 58px;
min-width: 0;
font-size: 12px;
padding: 0 14px 0 4px;
}