141 lines
6.0 KiB
Vue
141 lines
6.0 KiB
Vue
<script setup>
|
||
import { ref } from 'vue'
|
||
import {
|
||
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'])
|
||
|
||
const fileInput = ref(null)
|
||
|
||
const triggerImageUpload = () => fileInput.value.click()
|
||
|
||
const handleImageUpload = async (e) => {
|
||
try {
|
||
const images = await readFilesAsDataURL(e.target.files)
|
||
if (images.length > 0) {
|
||
emit('action', 'insertImages', images)
|
||
}
|
||
} catch (error) {
|
||
console.error("图片读取失败", error)
|
||
}
|
||
e.target.value = ''
|
||
}
|
||
|
||
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-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="dividerClass"></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>
|
||
|
||
<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="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>
|
||
<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 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 transition" @click="$emit('action', 'addCol')">
|
||
<Plus class="w-3 h-3" /> 列
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template> |