update at 2026-02-13 22:26:53
This commit is contained in:
99
src/App.vue
99
src/App.vue
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
120
src/styles.css
120
src/styles.css
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user