|
<template> |
|
<div class="rich-text-base"> |
|
<SelectGroup class="row"> |
|
<Select |
|
style="width: 60%;" |
|
:value="richTextAttrs.fontname" |
|
search |
|
searchLabel="搜索字体" |
|
@update:value="value => emitRichTextCommand('fontname', value as string)" |
|
:options="FONTS" |
|
> |
|
<template #icon> |
|
<IconFontSize /> |
|
</template> |
|
</Select> |
|
<Select |
|
style="width: 40%;" |
|
:value="richTextAttrs.fontsize" |
|
search |
|
searchLabel="搜索字号" |
|
@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 { FONTS } from '@/configs/font' |
|
import useTextFormatPainter from '@/hooks/useTextFormatPainter' |
|
import message from '@/utils/message' |
|
|
|
import TextColorButton from '@/components/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, 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: -3px; |
|
|
|
span { |
|
width: 100%; |
|
height: 2px; |
|
display: inline-block; |
|
position: absolute; |
|
top: 8px; |
|
background-color: #666; |
|
} |
|
} |
|
.popover-btn { |
|
padding: 0 3px; |
|
} |
|
</style> |