mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
perf: 富文本编辑优化
This commit is contained in:
parent
ae4656bdce
commit
5a9f232330
@ -452,7 +452,7 @@ export interface TableCellStyle {
|
||||
backcolor?: string
|
||||
fontsize?: string
|
||||
fontname?: string
|
||||
align?: 'left' | 'center' | 'right'
|
||||
align?: 'left' | 'center' | 'right' | 'justify'
|
||||
}
|
||||
|
||||
|
||||
|
@ -123,6 +123,7 @@
|
||||
<RadioButton value="left" style="flex: 1;" v-tooltip="'左对齐'"><IconAlignTextLeft /></RadioButton>
|
||||
<RadioButton value="center" style="flex: 1;" v-tooltip="'居中'"><IconAlignTextCenter /></RadioButton>
|
||||
<RadioButton value="right" style="flex: 1;" v-tooltip="'右对齐'"><IconAlignTextRight /></RadioButton>
|
||||
<RadioButton value="justify" style="flex: 1;" v-tooltip="'两端对齐'"><IconAlignTextBoth /></RadioButton>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -95,127 +95,8 @@
|
||||
<Divider />
|
||||
|
||||
<template v-if="handleShapeElement.text?.content">
|
||||
<SelectGroup class="row">
|
||||
<Select
|
||||
class="font-select"
|
||||
style="width: 60%;"
|
||||
:value="richTextAttrs.fontname"
|
||||
@update:value="value => emitRichTextCommand('fontname', value as string)"
|
||||
:options="[
|
||||
...availableFonts,
|
||||
...WEB_FONTS
|
||||
]"
|
||||
>
|
||||
<template #icon>
|
||||
<IconFontSize />
|
||||
</template>
|
||||
</Select>
|
||||
<Select
|
||||
style="width: 40%;"
|
||||
:value="richTextAttrs.fontsize"
|
||||
@update:value="value => emitRichTextCommand('fontsize', value as string)"
|
||||
:options="fontSizeOptions.map(item => ({
|
||||
label: item, value: item
|
||||
}))"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddText />
|
||||
</template>
|
||||
</Select>
|
||||
</SelectGroup>
|
||||
|
||||
<ButtonGroup class="row" passive>
|
||||
<Popover trigger="click" style="width: 30%;">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="richTextAttrs.color"
|
||||
@update:modelValue="value => emitRichTextCommand('color', value)"
|
||||
/>
|
||||
</template>
|
||||
<TextColorButton first v-tooltip="'文字颜色'" :color="richTextAttrs.color">
|
||||
<IconText />
|
||||
</TextColorButton>
|
||||
</Popover>
|
||||
<Popover trigger="click" style="width: 30%;">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="richTextAttrs.backcolor"
|
||||
@update:modelValue="value => emitRichTextCommand('backcolor', value)"
|
||||
/>
|
||||
</template>
|
||||
<TextColorButton v-tooltip="'文字高亮'" :color="richTextAttrs.backcolor">
|
||||
<IconHighLight />
|
||||
</TextColorButton>
|
||||
</Popover>
|
||||
<Button
|
||||
class="font-size-btn"
|
||||
style="width: 20%;"
|
||||
v-tooltip="'增大字号'"
|
||||
@click="emitRichTextCommand('fontsize-add')"
|
||||
><IconFontSize />+</Button>
|
||||
<Button
|
||||
last
|
||||
class="font-size-btn"
|
||||
style="width: 20%;"
|
||||
v-tooltip="'减小字号'"
|
||||
@click="emitRichTextCommand('fontsize-reduce')"
|
||||
><IconFontSize />-</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup class="row">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.bold"
|
||||
v-tooltip="'加粗'"
|
||||
@click="emitRichTextCommand('bold')"
|
||||
><IconTextBold /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.em"
|
||||
v-tooltip="'斜体'"
|
||||
@click="emitRichTextCommand('em')"
|
||||
><IconTextItalic /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.underline"
|
||||
v-tooltip="'下划线'"
|
||||
@click="emitRichTextCommand('underline')"
|
||||
><IconTextUnderline /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.strikethrough"
|
||||
v-tooltip="'删除线'"
|
||||
@click="emitRichTextCommand('strikethrough')"
|
||||
><IconStrikethrough /></CheckboxButton>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup class="row">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
v-tooltip="'清除格式'"
|
||||
@click="emitRichTextCommand('clear')"
|
||||
><IconFormat /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="!!textFormatPainter"
|
||||
v-tooltip="'格式刷(双击连续使用)'"
|
||||
@click="toggleTextFormatPainter()"
|
||||
@dblclick="toggleTextFormatPainter(true)"
|
||||
><IconFormatBrush /></CheckboxButton>
|
||||
</ButtonGroup>
|
||||
|
||||
<Divider />
|
||||
|
||||
<RadioGroup
|
||||
class="row"
|
||||
button-style="solid"
|
||||
:value="richTextAttrs.align"
|
||||
@update:value="value => emitRichTextCommand('align', value)"
|
||||
>
|
||||
<RadioButton value="left" v-tooltip="'左对齐'" style="flex: 1;"><IconAlignTextLeft /></RadioButton>
|
||||
<RadioButton value="center" v-tooltip="'居中'" style="flex: 1;"><IconAlignTextCenter /></RadioButton>
|
||||
<RadioButton value="right" v-tooltip="'右对齐'" style="flex: 1;"><IconAlignTextRight /></RadioButton>
|
||||
</RadioGroup>
|
||||
<RichTextBase />
|
||||
<Divider />
|
||||
|
||||
<RadioGroup
|
||||
class="row"
|
||||
@ -228,7 +109,7 @@
|
||||
<RadioButton value="bottom" v-tooltip="'底对齐'" style="flex: 1;"><IconAlignTextBottomOne /></RadioButton>
|
||||
</RadioGroup>
|
||||
|
||||
<Divider />
|
||||
<Divider />
|
||||
</template>
|
||||
|
||||
<ElementOutline />
|
||||
@ -255,11 +136,8 @@ import { type Ref, ref, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import type { PPTShapeElement, ShapeGradient, ShapeText } from '@/types/slides'
|
||||
import { WEB_FONTS } from '@/configs/font'
|
||||
import { type ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from '@/configs/shapes'
|
||||
import emitter, { EmitterEvents } from '@/utils/emitter'
|
||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
import useTextFormatPainter from '@/hooks/useTextFormatPainter'
|
||||
import useShapeFormatPainter from '@/hooks/useShapeFormatPainter'
|
||||
|
||||
import ElementOpacity from '../common/ElementOpacity.vue'
|
||||
@ -267,23 +145,20 @@ import ElementOutline from '../common/ElementOutline.vue'
|
||||
import ElementShadow from '../common/ElementShadow.vue'
|
||||
import ElementFlip from '../common/ElementFlip.vue'
|
||||
import ColorButton from '../common/ColorButton.vue'
|
||||
import TextColorButton from '../common/TextColorButton.vue'
|
||||
import RichTextBase from '../common/RichTextBase.vue'
|
||||
import ShapeItemThumbnail from '@/views/Editor/CanvasTool/ShapeItemThumbnail.vue'
|
||||
import CheckboxButton from '@/components/CheckboxButton.vue'
|
||||
import ColorPicker from '@/components/ColorPicker/index.vue'
|
||||
import Divider from '@/components/Divider.vue'
|
||||
import Slider from '@/components/Slider.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import ButtonGroup from '@/components/ButtonGroup.vue'
|
||||
import RadioButton from '@/components/RadioButton.vue'
|
||||
import RadioGroup from '@/components/RadioGroup.vue'
|
||||
import Select from '@/components/Select.vue'
|
||||
import SelectGroup from '@/components/SelectGroup.vue'
|
||||
import Popover from '@/components/Popover.vue'
|
||||
|
||||
const mainStore = useMainStore()
|
||||
const slidesStore = useSlidesStore()
|
||||
const { handleElement, handleElementId, richTextAttrs, availableFonts, textFormatPainter, shapeFormatPainter } = storeToRefs(mainStore)
|
||||
const { handleElement, handleElementId, shapeFormatPainter } = storeToRefs(mainStore)
|
||||
|
||||
const handleShapeElement = handleElement as Ref<PPTShapeElement>
|
||||
|
||||
@ -306,7 +181,6 @@ watch(handleElement, () => {
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
const { toggleTextFormatPainter } = useTextFormatPainter()
|
||||
const { toggleShapeFormatPainter } = useShapeFormatPainter()
|
||||
|
||||
const updateElement = (props: Partial<PPTShapeElement>) => {
|
||||
@ -373,16 +247,6 @@ const updateTextAlign = (align: 'top' | 'middle' | 'bottom') => {
|
||||
const _text = _handleElement.text || defaultText
|
||||
updateElement({ text: { ..._text, align } })
|
||||
}
|
||||
|
||||
const fontSizeOptions = [
|
||||
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
||||
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
|
||||
'80px', '88px', '96px', '104px', '112px', '120px',
|
||||
]
|
||||
|
||||
const emitRichTextCommand = (command: string, value?: string) => {
|
||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -84,11 +84,12 @@
|
||||
class="row"
|
||||
button-style="solid"
|
||||
:value="textAttrs.align"
|
||||
@update:value="value => updateTextAttrs({ align: value as 'left' | 'center' | 'right' })"
|
||||
@update:value="value => updateTextAttrs({ align: value as 'left' | 'center' | 'right' | 'justify' })"
|
||||
>
|
||||
<RadioButton value="left" v-tooltip="'左对齐'" style="flex: 1;"><IconAlignTextLeft /></RadioButton>
|
||||
<RadioButton value="center" v-tooltip="'居中'" style="flex: 1;"><IconAlignTextCenter /></RadioButton>
|
||||
<RadioButton value="right" v-tooltip="'右对齐'" style="flex: 1;"><IconAlignTextRight /></RadioButton>
|
||||
<RadioButton value="justify" v-tooltip="'两端对齐'" style="flex: 1;"><IconAlignTextBoth /></RadioButton>
|
||||
</RadioGroup>
|
||||
|
||||
<Divider />
|
||||
|
@ -11,250 +11,7 @@
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<SelectGroup class="row">
|
||||
<Select
|
||||
class="font-select"
|
||||
style="width: 60%;"
|
||||
:value="richTextAttrs.fontname"
|
||||
@update:value="value => emitRichTextCommand('fontname', value as string)"
|
||||
:options="[
|
||||
...availableFonts,
|
||||
...WEB_FONTS
|
||||
]"
|
||||
>
|
||||
<template #icon>
|
||||
<IconFontSize />
|
||||
</template>
|
||||
</Select>
|
||||
<Select
|
||||
style="width: 40%;"
|
||||
:value="richTextAttrs.fontsize"
|
||||
@update:value="value => emitRichTextCommand('fontsize', value as string)"
|
||||
:options="fontSizeOptions.map(item => ({
|
||||
label: item, value: item
|
||||
}))"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddText />
|
||||
</template>
|
||||
</Select>
|
||||
</SelectGroup>
|
||||
|
||||
<ButtonGroup class="row" passive>
|
||||
<Popover trigger="click" style="width: 30%;">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="richTextAttrs.color"
|
||||
@update:modelValue="value => emitRichTextCommand('color', value)"
|
||||
/>
|
||||
</template>
|
||||
<TextColorButton first v-tooltip="'文字颜色'" :color="richTextAttrs.color">
|
||||
<IconText />
|
||||
</TextColorButton>
|
||||
</Popover>
|
||||
<Popover trigger="click" style="width: 30%;">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="richTextAttrs.backcolor"
|
||||
@update:modelValue="value => emitRichTextCommand('backcolor', value)"
|
||||
/>
|
||||
</template>
|
||||
<TextColorButton v-tooltip="'文字高亮'" :color="richTextAttrs.backcolor">
|
||||
<IconHighLight />
|
||||
</TextColorButton>
|
||||
</Popover>
|
||||
<Button
|
||||
class="font-size-btn"
|
||||
style="width: 20%;"
|
||||
v-tooltip="'增大字号'"
|
||||
@click="emitRichTextCommand('fontsize-add')"
|
||||
><IconFontSize />+</Button>
|
||||
<Button
|
||||
last
|
||||
class="font-size-btn"
|
||||
style="width: 20%;"
|
||||
v-tooltip="'减小字号'"
|
||||
@click="emitRichTextCommand('fontsize-reduce')"
|
||||
><IconFontSize />-</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup class="row">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.bold"
|
||||
v-tooltip="'加粗'"
|
||||
@click="emitRichTextCommand('bold')"
|
||||
><IconTextBold /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.em"
|
||||
v-tooltip="'斜体'"
|
||||
@click="emitRichTextCommand('em')"
|
||||
><IconTextItalic /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.underline"
|
||||
v-tooltip="'下划线'"
|
||||
@click="emitRichTextCommand('underline')"
|
||||
><IconTextUnderline /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.strikethrough"
|
||||
v-tooltip="'删除线'"
|
||||
@click="emitRichTextCommand('strikethrough')"
|
||||
><IconStrikethrough /></CheckboxButton>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup class="row">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.superscript"
|
||||
v-tooltip="'上标'"
|
||||
@click="emitRichTextCommand('superscript')"
|
||||
>A²</CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.subscript"
|
||||
v-tooltip="'下标'"
|
||||
@click="emitRichTextCommand('subscript')"
|
||||
>A₂</CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.code"
|
||||
v-tooltip="'行内代码'"
|
||||
@click="emitRichTextCommand('code')"
|
||||
><IconCode /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.blockquote"
|
||||
v-tooltip="'引用'"
|
||||
@click="emitRichTextCommand('blockquote')"
|
||||
><IconQuote /></CheckboxButton>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup class="row" passive>
|
||||
<CheckboxButton
|
||||
first
|
||||
style="flex: 1;"
|
||||
v-tooltip="'清除格式'"
|
||||
@click="emitRichTextCommand('clear')"
|
||||
><IconFormat /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="!!textFormatPainter"
|
||||
v-tooltip="'格式刷(双击连续使用)'"
|
||||
@click="toggleTextFormatPainter()"
|
||||
@dblclick="toggleTextFormatPainter(true)"
|
||||
><IconFormatBrush /></CheckboxButton>
|
||||
<Popover placement="bottom-end" trigger="click" v-model:value="linkPopoverVisible" style="width: 33.33%;">
|
||||
<template #content>
|
||||
<div class="link-popover">
|
||||
<Input v-model:value="link" placeholder="请输入超链接" />
|
||||
<div class="btns">
|
||||
<Button size="small" :disabled="!richTextAttrs.link" @click="updateLink()" style="margin-right: 5px;">移除</Button>
|
||||
<Button size="small" type="primary" @click="updateLink(link)">确认</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<CheckboxButton
|
||||
last
|
||||
style="width: 100%;"
|
||||
:checked="!!richTextAttrs.link"
|
||||
v-tooltip="'超链接'"
|
||||
@click="openLinkPopover()"
|
||||
><IconLinkOne /></CheckboxButton>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
|
||||
<Divider />
|
||||
|
||||
<RadioGroup
|
||||
class="row"
|
||||
button-style="solid"
|
||||
:value="richTextAttrs.align"
|
||||
@update:value="value => emitRichTextCommand('align', value)"
|
||||
>
|
||||
<RadioButton value="left" v-tooltip="'左对齐'" style="flex: 1;"><IconAlignTextLeft /></RadioButton>
|
||||
<RadioButton value="center" v-tooltip="'居中'" style="flex: 1;"><IconAlignTextCenter /></RadioButton>
|
||||
<RadioButton value="right" v-tooltip="'右对齐'" style="flex: 1;"><IconAlignTextRight /></RadioButton>
|
||||
<RadioButton value="justify" v-tooltip="'两端对齐'" style="flex: 1;"><IconAlignTextBoth /></RadioButton>
|
||||
</RadioGroup>
|
||||
|
||||
<div class="row" passive>
|
||||
<ButtonGroup style="flex: 1;">
|
||||
<Button
|
||||
first
|
||||
:type="richTextAttrs.bulletList ? 'primary' : 'default'"
|
||||
style="flex: 1;"
|
||||
v-tooltip="'项目符号'"
|
||||
@click="emitRichTextCommand('bulletList')"
|
||||
><IconList /></Button>
|
||||
<Popover trigger="click" v-model:value="bulletListPanelVisible">
|
||||
<template #content>
|
||||
<div class="list-wrap">
|
||||
<ul class="list"
|
||||
v-for="item in bulletListStyleTypeOption"
|
||||
:key="item"
|
||||
:style="{ listStyleType: item }"
|
||||
@click="emitRichTextCommand('bulletList', item)"
|
||||
>
|
||||
<li class="list-item" v-for="key in 3" :key="key"><span></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<Button last class="popover-btn"><IconDown /></Button>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
<div style="width: 10px;"></div>
|
||||
<ButtonGroup style="flex: 1;" passive>
|
||||
<Button
|
||||
first
|
||||
:type="richTextAttrs.orderedList ? 'primary' : 'default'"
|
||||
style="flex: 1;"
|
||||
v-tooltip="'编号'"
|
||||
@click="emitRichTextCommand('orderedList')"
|
||||
><IconOrderedList /></Button>
|
||||
<Popover trigger="click" v-model:value="orderedListPanelVisible">
|
||||
<template #content>
|
||||
<div class="list-wrap">
|
||||
<ul class="list"
|
||||
v-for="item in orderedListStyleTypeOption"
|
||||
:key="item"
|
||||
:style="{ listStyleType: item }"
|
||||
@click="emitRichTextCommand('orderedList', item)"
|
||||
>
|
||||
<li class="list-item" v-for="key in 3" :key="key"><span></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<Button last class="popover-btn"><IconDown /></Button>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<ButtonGroup style="flex: 1;" passive>
|
||||
<Button first style="flex: 1;" v-tooltip="'减小段落缩进'" @click="emitRichTextCommand('indent', '-1')"><IconIndentLeft /></Button>
|
||||
<Popover trigger="click" v-model:value="indentLeftPanelVisible">
|
||||
<template #content>
|
||||
<PopoverMenuItem @click="emitRichTextCommand('textIndent', '-1')">减小首行缩进</PopoverMenuItem>
|
||||
</template>
|
||||
<Button last class="popover-btn"><IconDown /></Button>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
<div style="width: 10px;"></div>
|
||||
<ButtonGroup style="flex: 1;" passive>
|
||||
<Button first style="flex: 1;" v-tooltip="'增大段落缩进'" @click="emitRichTextCommand('indent', '+1')"><IconIndentRight /></Button>
|
||||
<Popover trigger="click" v-model:value="indentRightPanelVisible">
|
||||
<template #content>
|
||||
<PopoverMenuItem @click="emitRichTextCommand('textIndent', '+1')">增大首行缩进</PopoverMenuItem>
|
||||
</template>
|
||||
<Button last class="popover-btn"><IconDown /></Button>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<RichTextBase />
|
||||
<Divider />
|
||||
|
||||
<div class="row">
|
||||
@ -327,28 +84,17 @@ import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import type { PPTTextElement } from '@/types/slides'
|
||||
import emitter, { EmitterEvents, type RichTextAction } from '@/utils/emitter'
|
||||
import { WEB_FONTS } from '@/configs/font'
|
||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
import useTextFormatPainter from '@/hooks/useTextFormatPainter'
|
||||
import message from '@/utils/message'
|
||||
|
||||
import ElementOpacity from '../common/ElementOpacity.vue'
|
||||
import ElementOutline from '../common/ElementOutline.vue'
|
||||
import ElementShadow from '../common/ElementShadow.vue'
|
||||
import ColorButton from '../common/ColorButton.vue'
|
||||
import TextColorButton from '../common/TextColorButton.vue'
|
||||
import CheckboxButton from '@/components/CheckboxButton.vue'
|
||||
import RichTextBase from '../common/RichTextBase.vue'
|
||||
import ColorPicker from '@/components/ColorPicker/index.vue'
|
||||
import Divider from '@/components/Divider.vue'
|
||||
import Input from '@/components/Input.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import ButtonGroup from '@/components/ButtonGroup.vue'
|
||||
import RadioButton from '@/components/RadioButton.vue'
|
||||
import RadioGroup from '@/components/RadioGroup.vue'
|
||||
import Select from '@/components/Select.vue'
|
||||
import SelectGroup from '@/components/SelectGroup.vue'
|
||||
import Popover from '@/components/Popover.vue'
|
||||
import PopoverMenuItem from '@/components/PopoverMenuItem.vue'
|
||||
|
||||
// 注意,存在一个未知原因的BUG,如果文本加粗后文本框高度增加,画布的可视区域定位会出现错误
|
||||
// 因此在执行预置样式命令时,将加粗命令放在尽可能靠前的位置,避免字号增大后再加粗
|
||||
@ -427,24 +173,15 @@ const presetStyles = [
|
||||
|
||||
const mainStore = useMainStore()
|
||||
const slidesStore = useSlidesStore()
|
||||
const { handleElement, handleElementId, richTextAttrs, availableFonts, textFormatPainter } = storeToRefs(mainStore)
|
||||
const { handleElement, handleElementId } = storeToRefs(mainStore)
|
||||
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
const { toggleTextFormatPainter } = useTextFormatPainter()
|
||||
|
||||
const updateElement = (props: Partial<PPTTextElement>) => {
|
||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
const bulletListPanelVisible = ref(false)
|
||||
const orderedListPanelVisible = ref(false)
|
||||
const indentLeftPanelVisible = ref(false)
|
||||
const indentRightPanelVisible = ref(false)
|
||||
|
||||
const bulletListStyleTypeOption = ref(['disc', 'circle', 'square'])
|
||||
const orderedListStyleTypeOption = ref(['decimal', 'lower-roman', 'upper-roman', 'lower-alpha', 'upper-alpha', 'lower-greek'])
|
||||
|
||||
const fill = ref<string>('#000')
|
||||
const lineHeight = ref<number>()
|
||||
const wordSpace = ref<number>()
|
||||
@ -460,11 +197,6 @@ watch(handleElement, () => {
|
||||
emitter.emit(EmitterEvents.SYNC_RICH_TEXT_ATTRS_TO_STORE)
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
const fontSizeOptions = [
|
||||
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
||||
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
|
||||
'80px', '88px', '96px', '104px', '112px', '120px',
|
||||
]
|
||||
const lineHeightOptions = [0.9, 1.0, 1.15, 1.2, 1.4, 1.5, 1.8, 2.0, 2.5, 3.0]
|
||||
const wordSpaceOptions = [0, 1, 2, 3, 4, 5, 6, 8, 10]
|
||||
const paragraphSpaceOptions = [0, 5, 10, 15, 20, 25, 30, 40, 50, 80]
|
||||
@ -489,32 +221,10 @@ const updateFill = (value: string) => {
|
||||
updateElement({ fill: value })
|
||||
}
|
||||
|
||||
// 发射富文本设置命令
|
||||
const emitRichTextCommand = (command: string, value?: string) => {
|
||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })
|
||||
}
|
||||
|
||||
// 发射富文本设置命令(批量)
|
||||
// 发送富文本设置命令(批量)
|
||||
const emitBatchRichTextCommand = (action: RichTextAction[]) => {
|
||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action })
|
||||
}
|
||||
|
||||
// 设置富文本超链接
|
||||
const link = ref('')
|
||||
const linkPopoverVisible = ref(false)
|
||||
|
||||
watch(richTextAttrs, () => linkPopoverVisible.value = false)
|
||||
|
||||
const openLinkPopover = () => {
|
||||
link.value = richTextAttrs.value.link
|
||||
}
|
||||
const updateLink = (link?: string) => {
|
||||
const linkRegExp = /^(https?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/
|
||||
if (!link || !linkRegExp.test(link)) return message.error('不是正确的网页链接地址')
|
||||
|
||||
emitRichTextCommand('link', link)
|
||||
linkPopoverVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -557,67 +267,4 @@ const updateLink = (link?: string) => {
|
||||
margin-top: -1px;
|
||||
}
|
||||
}
|
||||
.font-size-btn {
|
||||
padding: 0;
|
||||
}
|
||||
.link-popover {
|
||||
width: 240px;
|
||||
|
||||
.btns {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
width: 176px;
|
||||
color: #666;
|
||||
padding: 8px;
|
||||
margin: -12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
}
|
||||
.list {
|
||||
background-color: $lightGray;
|
||||
padding: 4px 4px 4px 20px;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:nth-child(3n)) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:nth-child(4),
|
||||
&:nth-child(5),
|
||||
&:nth-child(6) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $themeColor;
|
||||
|
||||
span {
|
||||
background-color: $themeColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
.list-item {
|
||||
width: 24px;
|
||||
height: 12px;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
top: -5px;
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
.popover-btn {
|
||||
padding: 0 3px;
|
||||
}
|
||||
</style>
|
386
src/views/Editor/Toolbar/common/RichTextBase.vue
Normal file
386
src/views/Editor/Toolbar/common/RichTextBase.vue
Normal file
@ -0,0 +1,386 @@
|
||||
<template>
|
||||
<div class="rich-text-base">
|
||||
<SelectGroup class="row">
|
||||
<Select
|
||||
class="font-select"
|
||||
style="width: 60%;"
|
||||
:value="richTextAttrs.fontname"
|
||||
@update:value="value => emitRichTextCommand('fontname', value as string)"
|
||||
:options="[
|
||||
...availableFonts,
|
||||
...WEB_FONTS
|
||||
]"
|
||||
>
|
||||
<template #icon>
|
||||
<IconFontSize />
|
||||
</template>
|
||||
</Select>
|
||||
<Select
|
||||
style="width: 40%;"
|
||||
:value="richTextAttrs.fontsize"
|
||||
@update:value="value => emitRichTextCommand('fontsize', value as string)"
|
||||
:options="fontSizeOptions.map(item => ({
|
||||
label: item, value: item
|
||||
}))"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddText />
|
||||
</template>
|
||||
</Select>
|
||||
</SelectGroup>
|
||||
|
||||
<ButtonGroup class="row" passive>
|
||||
<Popover trigger="click" style="width: 30%;">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="richTextAttrs.color"
|
||||
@update:modelValue="value => emitRichTextCommand('color', value)"
|
||||
/>
|
||||
</template>
|
||||
<TextColorButton first v-tooltip="'文字颜色'" :color="richTextAttrs.color">
|
||||
<IconText />
|
||||
</TextColorButton>
|
||||
</Popover>
|
||||
<Popover trigger="click" style="width: 30%;">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="richTextAttrs.backcolor"
|
||||
@update:modelValue="value => emitRichTextCommand('backcolor', value)"
|
||||
/>
|
||||
</template>
|
||||
<TextColorButton v-tooltip="'文字高亮'" :color="richTextAttrs.backcolor">
|
||||
<IconHighLight />
|
||||
</TextColorButton>
|
||||
</Popover>
|
||||
<Button
|
||||
class="font-size-btn"
|
||||
style="width: 20%;"
|
||||
v-tooltip="'增大字号'"
|
||||
@click="emitRichTextCommand('fontsize-add')"
|
||||
><IconFontSize />+</Button>
|
||||
<Button
|
||||
last
|
||||
class="font-size-btn"
|
||||
style="width: 20%;"
|
||||
v-tooltip="'减小字号'"
|
||||
@click="emitRichTextCommand('fontsize-reduce')"
|
||||
><IconFontSize />-</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup class="row">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.bold"
|
||||
v-tooltip="'加粗'"
|
||||
@click="emitRichTextCommand('bold')"
|
||||
><IconTextBold /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.em"
|
||||
v-tooltip="'斜体'"
|
||||
@click="emitRichTextCommand('em')"
|
||||
><IconTextItalic /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.underline"
|
||||
v-tooltip="'下划线'"
|
||||
@click="emitRichTextCommand('underline')"
|
||||
><IconTextUnderline /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.strikethrough"
|
||||
v-tooltip="'删除线'"
|
||||
@click="emitRichTextCommand('strikethrough')"
|
||||
><IconStrikethrough /></CheckboxButton>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup class="row">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.superscript"
|
||||
v-tooltip="'上标'"
|
||||
@click="emitRichTextCommand('superscript')"
|
||||
>A²</CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.subscript"
|
||||
v-tooltip="'下标'"
|
||||
@click="emitRichTextCommand('subscript')"
|
||||
>A₂</CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.code"
|
||||
v-tooltip="'行内代码'"
|
||||
@click="emitRichTextCommand('code')"
|
||||
><IconCode /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="richTextAttrs.blockquote"
|
||||
v-tooltip="'引用'"
|
||||
@click="emitRichTextCommand('blockquote')"
|
||||
><IconQuote /></CheckboxButton>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup class="row" passive>
|
||||
<CheckboxButton
|
||||
first
|
||||
style="flex: 1;"
|
||||
v-tooltip="'清除格式'"
|
||||
@click="emitRichTextCommand('clear')"
|
||||
><IconFormat /></CheckboxButton>
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="!!textFormatPainter"
|
||||
v-tooltip="'格式刷(双击连续使用)'"
|
||||
@click="toggleTextFormatPainter()"
|
||||
@dblclick="toggleTextFormatPainter(true)"
|
||||
><IconFormatBrush /></CheckboxButton>
|
||||
<Popover placement="bottom-end" trigger="click" v-model:value="linkPopoverVisible" style="width: 33.33%;">
|
||||
<template #content>
|
||||
<div class="link-popover">
|
||||
<Input v-model:value="link" placeholder="请输入超链接" />
|
||||
<div class="btns">
|
||||
<Button size="small" :disabled="!richTextAttrs.link" @click="removeLink()" style="margin-right: 5px;">移除</Button>
|
||||
<Button size="small" type="primary" @click="updateLink(link)">确认</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<CheckboxButton
|
||||
last
|
||||
style="width: 100%;"
|
||||
:checked="!!richTextAttrs.link"
|
||||
v-tooltip="'超链接'"
|
||||
@click="openLinkPopover()"
|
||||
><IconLinkOne /></CheckboxButton>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
<Divider />
|
||||
|
||||
<RadioGroup
|
||||
class="row"
|
||||
button-style="solid"
|
||||
:value="richTextAttrs.align"
|
||||
@update:value="value => emitRichTextCommand('align', value)"
|
||||
>
|
||||
<RadioButton value="left" v-tooltip="'左对齐'" style="flex: 1;"><IconAlignTextLeft /></RadioButton>
|
||||
<RadioButton value="center" v-tooltip="'居中'" style="flex: 1;"><IconAlignTextCenter /></RadioButton>
|
||||
<RadioButton value="right" v-tooltip="'右对齐'" style="flex: 1;"><IconAlignTextRight /></RadioButton>
|
||||
<RadioButton value="justify" v-tooltip="'两端对齐'" style="flex: 1;"><IconAlignTextBoth /></RadioButton>
|
||||
</RadioGroup>
|
||||
|
||||
<div class="row" passive>
|
||||
<ButtonGroup style="flex: 1;">
|
||||
<Button
|
||||
first
|
||||
:type="richTextAttrs.bulletList ? 'primary' : 'default'"
|
||||
style="flex: 1;"
|
||||
v-tooltip="'项目符号'"
|
||||
@click="emitRichTextCommand('bulletList')"
|
||||
><IconList /></Button>
|
||||
<Popover trigger="click" v-model:value="bulletListPanelVisible">
|
||||
<template #content>
|
||||
<div class="list-wrap">
|
||||
<ul class="list"
|
||||
v-for="item in bulletListStyleTypeOption"
|
||||
:key="item"
|
||||
:style="{ listStyleType: item }"
|
||||
@click="emitRichTextCommand('bulletList', item)"
|
||||
>
|
||||
<li class="list-item" v-for="key in 3" :key="key"><span></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<Button last class="popover-btn"><IconDown /></Button>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
<div style="width: 10px;"></div>
|
||||
<ButtonGroup style="flex: 1;" passive>
|
||||
<Button
|
||||
first
|
||||
:type="richTextAttrs.orderedList ? 'primary' : 'default'"
|
||||
style="flex: 1;"
|
||||
v-tooltip="'编号'"
|
||||
@click="emitRichTextCommand('orderedList')"
|
||||
><IconOrderedList /></Button>
|
||||
<Popover trigger="click" v-model:value="orderedListPanelVisible">
|
||||
<template #content>
|
||||
<div class="list-wrap">
|
||||
<ul class="list"
|
||||
v-for="item in orderedListStyleTypeOption"
|
||||
:key="item"
|
||||
:style="{ listStyleType: item }"
|
||||
@click="emitRichTextCommand('orderedList', item)"
|
||||
>
|
||||
<li class="list-item" v-for="key in 3" :key="key"><span></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<Button last class="popover-btn"><IconDown /></Button>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<ButtonGroup style="flex: 1;" passive>
|
||||
<Button first style="flex: 1;" v-tooltip="'减小段落缩进'" @click="emitRichTextCommand('indent', '-1')"><IconIndentLeft /></Button>
|
||||
<Popover trigger="click" v-model:value="indentLeftPanelVisible">
|
||||
<template #content>
|
||||
<PopoverMenuItem @click="emitRichTextCommand('textIndent', '-1')">减小首行缩进</PopoverMenuItem>
|
||||
</template>
|
||||
<Button last class="popover-btn"><IconDown /></Button>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
<div style="width: 10px;"></div>
|
||||
<ButtonGroup style="flex: 1;" passive>
|
||||
<Button first style="flex: 1;" v-tooltip="'增大段落缩进'" @click="emitRichTextCommand('indent', '+1')"><IconIndentRight /></Button>
|
||||
<Popover trigger="click" v-model:value="indentRightPanelVisible">
|
||||
<template #content>
|
||||
<PopoverMenuItem @click="emitRichTextCommand('textIndent', '+1')">增大首行缩进</PopoverMenuItem>
|
||||
</template>
|
||||
<Button last class="popover-btn"><IconDown /></Button>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore } from '@/store'
|
||||
import emitter, { EmitterEvents } from '@/utils/emitter'
|
||||
import { WEB_FONTS } from '@/configs/font'
|
||||
import useTextFormatPainter from '@/hooks/useTextFormatPainter'
|
||||
import message from '@/utils/message'
|
||||
|
||||
import TextColorButton from '../common/TextColorButton.vue'
|
||||
import CheckboxButton from '@/components/CheckboxButton.vue'
|
||||
import ColorPicker from '@/components/ColorPicker/index.vue'
|
||||
import Input from '@/components/Input.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import ButtonGroup from '@/components/ButtonGroup.vue'
|
||||
import Select from '@/components/Select.vue'
|
||||
import SelectGroup from '@/components/SelectGroup.vue'
|
||||
import Divider from '@/components/Divider.vue'
|
||||
import Popover from '@/components/Popover.vue'
|
||||
import RadioButton from '@/components/RadioButton.vue'
|
||||
import RadioGroup from '@/components/RadioGroup.vue'
|
||||
import PopoverMenuItem from '@/components/PopoverMenuItem.vue'
|
||||
|
||||
const { richTextAttrs, availableFonts, textFormatPainter } = storeToRefs(useMainStore())
|
||||
|
||||
const { toggleTextFormatPainter } = useTextFormatPainter()
|
||||
|
||||
const fontSizeOptions = [
|
||||
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
||||
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
|
||||
'80px', '88px', '96px', '104px', '112px', '120px',
|
||||
]
|
||||
|
||||
const emitRichTextCommand = (command: string, value?: string) => {
|
||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })
|
||||
}
|
||||
|
||||
const bulletListPanelVisible = ref(false)
|
||||
const orderedListPanelVisible = ref(false)
|
||||
const indentLeftPanelVisible = ref(false)
|
||||
const indentRightPanelVisible = ref(false)
|
||||
|
||||
const bulletListStyleTypeOption = ref(['disc', 'circle', 'square'])
|
||||
const orderedListStyleTypeOption = ref(['decimal', 'lower-roman', 'upper-roman', 'lower-alpha', 'upper-alpha', 'lower-greek'])
|
||||
|
||||
const link = ref('')
|
||||
const linkPopoverVisible = ref(false)
|
||||
|
||||
watch(richTextAttrs, () => linkPopoverVisible.value = false)
|
||||
|
||||
const openLinkPopover = () => {
|
||||
link.value = richTextAttrs.value.link
|
||||
}
|
||||
const updateLink = (link?: string) => {
|
||||
const linkRegExp = /^(https?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/
|
||||
if (!link || !linkRegExp.test(link)) return message.error('不是正确的网页链接地址')
|
||||
|
||||
emitRichTextCommand('link', link)
|
||||
linkPopoverVisible.value = false
|
||||
}
|
||||
|
||||
const removeLink = () => {
|
||||
emitRichTextCommand('link')
|
||||
linkPopoverVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rich-text-base {
|
||||
user-select: none;
|
||||
}
|
||||
.row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.font-size-btn {
|
||||
padding: 0;
|
||||
}
|
||||
.link-popover {
|
||||
width: 240px;
|
||||
|
||||
.btns {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
.list-wrap {
|
||||
width: 176px;
|
||||
color: #666;
|
||||
padding: 8px;
|
||||
margin: -12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
}
|
||||
.list {
|
||||
background-color: $lightGray;
|
||||
padding: 4px 4px 4px 20px;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:nth-child(3n)) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:nth-child(4),
|
||||
&:nth-child(5),
|
||||
&:nth-child(6) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $themeColor;
|
||||
|
||||
span {
|
||||
background-color: $themeColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
.list-item {
|
||||
width: 24px;
|
||||
height: 12px;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
top: -5px;
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
.popover-btn {
|
||||
padding: 0 3px;
|
||||
}
|
||||
</style>
|
Loading…
x
Reference in New Issue
Block a user