优化交互

This commit is contained in:
李琦
2026-01-22 17:00:24 +08:00
parent 2c2ab1e4f0
commit 0a38130cd9
17 changed files with 2617 additions and 600 deletions

View File

@@ -1,9 +1,14 @@
<script setup>
import { ref } from 'vue'
import {
Undo2, Redo2, Bold, Italic, Underline,
AlignLeft, AlignCenter, AlignRight, Image as ImageIcon, Plus
Undo2, Redo2, PaintRoller,
Bold, Italic, Underline, Strikethrough,
AlignLeft, AlignCenter, AlignRight,
Image as ImageIcon, Plus,
Type, Palette, ChevronDown
} from 'lucide-vue-next'
import { readFilesAsDataURL } from '@/utils/file'
import ColorPicker from './ColorPicker.vue'
const props = defineProps(['currentStyle'])
const emit = defineEmits(['action'])
@@ -12,81 +17,123 @@ const fileInput = ref(null)
const triggerImageUpload = () => fileInput.value.click()
// 支持多图上传的处理逻辑
const handleImageUpload = async (e) => {
const files = Array.from(e.target.files)
if (files.length === 0) return
// 并发读取所有图片
const readers = files.map(file => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (evt) => resolve(evt.target.result)
reader.onerror = reject
reader.readAsDataURL(file)
})
})
try {
const images = await Promise.all(readers)
// 触发批量插入事件
emit('action', 'insertImages', images)
const images = await readFilesAsDataURL(e.target.files)
if (images.length > 0) {
emit('action', 'insertImages', images)
}
} catch (error) {
console.error("图片读取失败", error)
}
// 重置 input 以便能重复上传同一文件
e.target.value = ''
}
const btnClass = "p-1.5 rounded hover:bg-gray-200 transition text-gray-600 flex items-center justify-center cursor-pointer"
const handleColorChange = (type, color) => {
emit('action', 'setStyle', { [type]: color })
}
const handleFontChange = (e) => emit('action', 'setStyle', { fontFamily: e.target.value })
const handleFontSizeChange = (e) => emit('action', 'setStyle', { fontSize: e.target.value })
const btnClass = "p-1.5 rounded hover:bg-gray-200 transition text-gray-700 flex items-center justify-center cursor-pointer relative"
const activeClass = "bg-gray-300 text-black"
const dividerClass = "w-px h-5 bg-gray-300 mx-1 self-center"
</script>
<template>
<div class="h-10 bg-[#fbfbfb] border-b border-gray-300 flex items-center px-2 gap-2 select-none shrink-0">
<div class="flex gap-1 pr-2 border-r border-gray-300">
<button :class="btnClass" title="撤销"><Undo2 class="w-4 h-4" /></button>
<button :class="btnClass" title="重做"><Redo2 class="w-4 h-4" /></button>
<div class="h-10 bg-[#fbfbfb] border-b border-gray-300 flex items-center px-3 gap-1 select-none shrink-0 overflow-x-auto whitespace-nowrap z-40">
<!-- 历史 -->
<div class="flex gap-0.5">
<button :class="btnClass" title="撤销 (Ctrl+Z)" @click="$emit('action', 'undo')"><Undo2 class="w-4 h-4" /></button>
<button :class="btnClass" title="重做 (Ctrl+Y)" @click="$emit('action', 'redo')"><Redo2 class="w-4 h-4" /></button>
</div>
<div class="flex gap-1 pr-2 border-r border-gray-300">
<button :class="[btnClass, currentStyle.bold ? activeClass : '']" @click="$emit('action', 'toggleStyle', 'bold')">
<Bold class="w-4 h-4" />
</button>
<button :class="[btnClass, currentStyle.italic ? activeClass : '']" @click="$emit('action', 'toggleStyle', 'italic')">
<Italic class="w-4 h-4" />
</button>
<button :class="[btnClass, currentStyle.underline ? activeClass : '']" @click="$emit('action', 'toggleStyle', 'underline')">
<Underline class="w-4 h-4" />
</button>
<div :class="dividerClass"></div>
<div class="w-px h-4 bg-gray-300 mx-1 self-center"></div>
<!-- 字体与字号 -->
<div class="flex gap-1 items-center">
<div class="relative group">
<select
class="appearance-none bg-transparent hover:bg-gray-200 pl-2 pr-6 py-1 rounded text-xs text-gray-700 outline-none cursor-pointer w-24 border border-transparent hover:border-gray-300 transition-colors"
:value="currentStyle.fontFamily || 'Arial'"
@change="handleFontChange"
>
<option value="Arial">Arial</option>
<option value="Helvetica">Helvetica</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Verdana">Verdana</option>
</select>
<ChevronDown class="w-3 h-3 absolute right-1 top-1.5 text-gray-500 pointer-events-none" />
</div>
<button :class="[btnClass, currentStyle.align === 'left' ? activeClass : '']" @click="$emit('action', 'setAlign', 'left')">
<AlignLeft class="w-4 h-4" />
</button>
<button :class="[btnClass, currentStyle.align === 'center' ? activeClass : '']" @click="$emit('action', 'setAlign', 'center')">
<AlignCenter class="w-4 h-4" />
</button>
<button :class="[btnClass, currentStyle.align === 'right' ? activeClass : '']" @click="$emit('action', 'setAlign', 'right')">
<AlignRight class="w-4 h-4" />
</button>
<div class="relative group">
<select
class="appearance-none bg-transparent hover:bg-gray-200 pl-2 pr-5 py-1 rounded text-xs text-gray-700 outline-none cursor-pointer w-14 border border-transparent hover:border-gray-300 transition-colors"
:value="currentStyle.fontSize || 11"
@change="handleFontSizeChange"
>
<option v-for="size in [9,10,11,12,14,18,24,36]" :key="size" :value="size">{{ size }}</option>
</select>
<ChevronDown class="w-3 h-3 absolute right-1 top-1.5 text-gray-500 pointer-events-none" />
</div>
</div>
<div class="flex gap-1 pr-2 border-r border-gray-300">
<div :class="dividerClass"></div>
<!-- 样式 -->
<div class="flex gap-0.5">
<button :class="[btnClass, currentStyle.bold ? activeClass : '']" @click="$emit('action', 'toggleStyle', 'bold')"><Bold class="w-4 h-4" /></button>
<button :class="[btnClass, currentStyle.italic ? activeClass : '']" @click="$emit('action', 'toggleStyle', 'italic')"><Italic class="w-4 h-4" /></button>
<button :class="[btnClass, currentStyle.underline ? activeClass : '']" @click="$emit('action', 'toggleStyle', 'underline')"><Underline class="w-4 h-4" /></button>
<button :class="[btnClass, currentStyle.strike ? activeClass : '']" @click="$emit('action', 'toggleStyle', 'strike')"><Strikethrough class="w-4 h-4" /></button>
</div>
<div :class="dividerClass"></div>
<!-- 颜色选择 (使用新组件) -->
<div class="flex gap-0.5">
<ColorPicker
type="text"
:icon-component="Type"
:color="currentStyle.color"
@change="(c) => handleColorChange('color', c)"
/>
<ColorPicker
type="fill"
:icon-component="Palette"
:color="currentStyle.bg"
@change="(c) => handleColorChange('bg', c)"
/>
</div>
<div :class="dividerClass"></div>
<!-- 对齐方式 (修复状态回显:确保 Cell.vue 中使用了 flex justify 来实现对齐,store 中存储的是 'left'/'center'/'right') -->
<div class="flex gap-0.5">
<button :class="[btnClass, (!currentStyle.align || currentStyle.align === 'left') ? activeClass : '']" @click="$emit('action', 'setAlign', 'left')"><AlignLeft class="w-4 h-4" /></button>
<button :class="[btnClass, currentStyle.align === 'center' ? activeClass : '']" @click="$emit('action', 'setAlign', 'center')"><AlignCenter class="w-4 h-4" /></button>
<button :class="[btnClass, currentStyle.align === 'right' ? activeClass : '']" @click="$emit('action', 'setAlign', 'right')"><AlignRight class="w-4 h-4" /></button>
</div>
<div :class="dividerClass"></div>
<!-- 插入 -->
<div class="flex gap-0.5">
<button :class="btnClass" title="插入图片" @click="triggerImageUpload">
<ImageIcon class="w-4 h-4" />
</button>
<!-- 关键修改:添加 multiple 属性支持多选 -->
<input type="file" ref="fileInput" class="hidden" accept="image/*" multiple @change="handleImageUpload">
</div>
<div :class="dividerClass"></div>
<div class="flex gap-1">
<button class="flex items-center gap-1 px-2 py-1 hover:bg-gray-200 rounded text-xs text-gray-700" @click="$emit('action', 'addRow')">
<button class="flex items-center gap-1 px-2 py-1 hover:bg-gray-200 rounded text-xs text-gray-700 transition" @click="$emit('action', 'addRow')">
<Plus class="w-3 h-3" /> 行
</button>
<button class="flex items-center gap-1 px-2 py-1 hover:bg-gray-200 rounded text-xs text-gray-700" @click="$emit('action', 'addCol')">
<button class="flex items-center gap-1 px-2 py-1 hover:bg-gray-200 rounded text-xs text-gray-700 transition" @click="$emit('action', 'addCol')">
<Plus class="w-3 h-3" /> 列
</button>
</div>