TextStylePanel.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. <template>
  2. <div class="text-style-panel">
  3. <InputGroup compact class="row">
  4. <Select
  5. style="flex: 3;"
  6. :value="richTextAttrs.fontname"
  7. @change="value => emitRichTextCommand('fontname', value)"
  8. >
  9. <template #suffixIcon><IconFontSize /></template>
  10. <SelectOption v-for="font in availableFonts" :key="font.en" :value="font.en">
  11. <span :style="{ fontFamily: font.en }">{{font.zh}}</span>
  12. </SelectOption>
  13. </Select>
  14. <Select
  15. style="flex: 2;"
  16. :value="richTextAttrs.fontsize"
  17. @change="value => emitRichTextCommand('fontsize', value)"
  18. >
  19. <template #suffixIcon><IconAddText /></template>
  20. <SelectOption v-for="fontsize in fontSizeOptions" :key="fontsize" :value="fontsize">
  21. {{fontsize}}
  22. </SelectOption>
  23. </Select>
  24. </InputGroup>
  25. <ButtonGroup class="row">
  26. <Popover trigger="click">
  27. <template #content>
  28. <ColorPicker
  29. :modelValue="richTextAttrs.color"
  30. @update:modelValue="value => emitRichTextCommand('color', value)"
  31. />
  32. </template>
  33. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色">
  34. <Button class="text-color-btn" style="flex: 1;">
  35. <IconText />
  36. <div class="text-color-block" :style="{ backgroundColor: richTextAttrs.color }"></div>
  37. </Button>
  38. </Tooltip>
  39. </Popover>
  40. <Popover trigger="click">
  41. <template #content>
  42. <ColorPicker
  43. :modelValue="richTextAttrs.backcolor"
  44. @update:modelValue="value => emitRichTextCommand('backcolor', value)"
  45. />
  46. </template>
  47. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字高亮">
  48. <Button class="text-color-btn" style="flex: 1;">
  49. <IconBackgroundColor />
  50. <div class="text-color-block" :style="{ backgroundColor: richTextAttrs.backcolor }"></div>
  51. </Button>
  52. </Tooltip>
  53. </Popover>
  54. <Popover trigger="click">
  55. <template #content>
  56. <ColorPicker
  57. :modelValue="fill"
  58. @update:modelValue="value => updateFill(value)"
  59. />
  60. </template>
  61. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文本框填充">
  62. <Button class="text-color-btn" style="flex: 1;">
  63. <IconFill />
  64. <div class="text-color-block" :style="{ backgroundColor: fill }"></div>
  65. </Button>
  66. </Tooltip>
  67. </Popover>
  68. </ButtonGroup>
  69. <CheckboxButtonGroup class="row">
  70. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="加粗">
  71. <CheckboxButton
  72. style="flex: 1;"
  73. :checked="richTextAttrs.bold"
  74. @click="emitRichTextCommand('bold')"
  75. ><IconTextBold /></CheckboxButton>
  76. </Tooltip>
  77. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="斜体">
  78. <CheckboxButton
  79. style="flex: 1;"
  80. :checked="richTextAttrs.em"
  81. @click="emitRichTextCommand('em')"
  82. ><IconTextItalic /></CheckboxButton>
  83. </Tooltip>
  84. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="下划线">
  85. <CheckboxButton
  86. style="flex: 1;"
  87. :checked="richTextAttrs.underline"
  88. @click="emitRichTextCommand('underline')"
  89. ><IconTextUnderline /></CheckboxButton>
  90. </Tooltip>
  91. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="删除线">
  92. <CheckboxButton
  93. style="flex: 1;"
  94. :checked="richTextAttrs.strikethrough"
  95. @click="emitRichTextCommand('strikethrough')"
  96. ><IconStrikethrough /></CheckboxButton>
  97. </Tooltip>
  98. </CheckboxButtonGroup>
  99. <CheckboxButtonGroup class="row">
  100. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="上标">
  101. <CheckboxButton
  102. style="flex: 1;"
  103. :checked="richTextAttrs.superscript"
  104. @click="emitRichTextCommand('superscript')"
  105. ><IconUpOne /></CheckboxButton>
  106. </Tooltip>
  107. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="下标">
  108. <CheckboxButton
  109. style="flex: 1;"
  110. :checked="richTextAttrs.subscript"
  111. @click="emitRichTextCommand('subscript')"
  112. ><IconDownOne /></CheckboxButton>
  113. </Tooltip>
  114. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="行内代码">
  115. <CheckboxButton
  116. style="flex: 1;"
  117. :checked="richTextAttrs.code"
  118. @click="emitRichTextCommand('code')"
  119. ><IconCode /></CheckboxButton>
  120. </Tooltip>
  121. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="引用">
  122. <CheckboxButton
  123. style="flex: 1;"
  124. :checked="richTextAttrs.blockquote"
  125. @click="emitRichTextCommand('blockquote')"
  126. ><IconQuote /></CheckboxButton>
  127. </Tooltip>
  128. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="清除格式">
  129. <CheckboxButton
  130. style="flex: 1;"
  131. @click="emitRichTextCommand('clear')"
  132. ><IconFormat /></CheckboxButton>
  133. </Tooltip>
  134. </CheckboxButtonGroup>
  135. <Divider />
  136. <RadioGroup
  137. class="row"
  138. button-style="solid"
  139. :value="richTextAttrs.align"
  140. @change="e => emitRichTextCommand('align', e.target.value)"
  141. >
  142. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="左对齐">
  143. <RadioButton value="left" style="flex: 1;"><IconAlignTextLeft /></RadioButton>
  144. </Tooltip>
  145. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="居中">
  146. <RadioButton value="center" style="flex: 1;"><IconAlignTextCenter /></RadioButton>
  147. </Tooltip>
  148. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="右对齐">
  149. <RadioButton value="right" style="flex: 1;"><IconAlignTextRight /></RadioButton>
  150. </Tooltip>
  151. </RadioGroup>
  152. <CheckboxButtonGroup class="row">
  153. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="项目符号">
  154. <CheckboxButton
  155. style="flex: 1;"
  156. :checked="richTextAttrs.bulletList"
  157. @click="emitRichTextCommand('bulletList')"
  158. ><IconList /></CheckboxButton>
  159. </Tooltip>
  160. <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="编号">
  161. <CheckboxButton
  162. style="flex: 1;"
  163. :checked="richTextAttrs.orderedList"
  164. @click="emitRichTextCommand('orderedList')"
  165. ><IconOrderedList /></CheckboxButton>
  166. </Tooltip>
  167. </CheckboxButtonGroup>
  168. <Divider />
  169. <div class="row">
  170. <div style="flex: 2;">行间距:</div>
  171. <Select style="flex: 3;" :value="lineHeight" @change="value => updateLineHeight(value)">
  172. <template #suffixIcon><IconRowHeight /></template>
  173. <SelectOption v-for="item in lineHeightOptions" :key="item" :value="item">{{item}}倍</SelectOption>
  174. </Select>
  175. </div>
  176. <div class="row">
  177. <div style="flex: 2;">字间距:</div>
  178. <Select style="flex: 3;" :value="wordSpace" @change="value => updateWordSpace(value)">
  179. <template #suffixIcon><IconFullwidth /></template>
  180. <SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}px</SelectOption>
  181. </Select>
  182. </div>
  183. <Divider />
  184. <ElementOutline />
  185. <Divider />
  186. <ElementShadow />
  187. <Divider />
  188. <ElementOpacity />
  189. </div>
  190. </template>
  191. <script lang="ts">
  192. import { computed, defineComponent, onUnmounted, Ref, ref, watch } from 'vue'
  193. import { useStore } from 'vuex'
  194. import { MutationTypes, State } from '@/store'
  195. import { PPTTextElement } from '@/types/slides'
  196. import emitter, { EmitterEvents } from '@/utils/emitter'
  197. import { TextAttrs } from '@/prosemirror/utils'
  198. import useHistorySnapshot from '@/hooks/useHistorySnapshot'
  199. import ElementOpacity from '../common/ElementOpacity.vue'
  200. import ElementOutline from '../common/ElementOutline.vue'
  201. import ElementShadow from '../common/ElementShadow.vue'
  202. export default defineComponent({
  203. name: 'text-style-panel',
  204. components: {
  205. ElementOpacity,
  206. ElementOutline,
  207. ElementShadow,
  208. },
  209. setup() {
  210. const store = useStore<State>()
  211. const handleElement: Ref<PPTTextElement> = computed(() => store.getters.handleElement)
  212. const fill = ref<string>()
  213. const lineHeight = ref<number>()
  214. const wordSpace = ref<number>()
  215. watch(handleElement, () => {
  216. if(!handleElement.value) return
  217. fill.value = handleElement.value.fill || '#000'
  218. lineHeight.value = handleElement.value.lineHeight || 1.5
  219. wordSpace.value = handleElement.value.wordSpace || 0
  220. }, { deep: true, immediate: true })
  221. const richTextAttrs = ref<TextAttrs>({
  222. bold: false,
  223. em: false,
  224. underline: false,
  225. strikethrough: false,
  226. superscript: false,
  227. subscript: false,
  228. code: false,
  229. color: '#000',
  230. backcolor: '#000',
  231. fontsize: '12px',
  232. fontname: '微软雅黑',
  233. align: 'left',
  234. bulletList: false,
  235. orderedList: false,
  236. blockquote: false,
  237. })
  238. const availableFonts = computed(() => store.state.availableFonts)
  239. const fontSizeOptions = [
  240. '12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
  241. '36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '80px',
  242. ]
  243. const lineHeightOptions = [0.5, 1.0, 1.2, 1.5, 1.8, 2.0, 3.0]
  244. const wordSpaceOptions = [0, 1, 2, 3, 4, 5, 8]
  245. const updateRichTextAttrs = (attr: TextAttrs) => richTextAttrs.value = attr
  246. emitter.on(EmitterEvents.UPDATE_TEXT_STATE, attr => updateRichTextAttrs(attr))
  247. onUnmounted(() => {
  248. emitter.off(EmitterEvents.UPDATE_TEXT_STATE, attr => updateRichTextAttrs(attr))
  249. })
  250. const emitRichTextCommand = (command: string, value?: string) => {
  251. emitter.emit(EmitterEvents.EXEC_TEXT_COMMAND, { command, value })
  252. }
  253. const { addHistorySnapshot } = useHistorySnapshot()
  254. const updateLineHeight = (value: number) => {
  255. const props = { lineHeight: value }
  256. store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
  257. addHistorySnapshot()
  258. }
  259. const updateWordSpace = (value: number) => {
  260. const props = { wordSpace: value }
  261. store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
  262. addHistorySnapshot()
  263. }
  264. const updateFill = (value: string) => {
  265. const props = { fill: value }
  266. store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
  267. addHistorySnapshot()
  268. }
  269. return {
  270. fill,
  271. lineHeight,
  272. wordSpace,
  273. richTextAttrs,
  274. availableFonts,
  275. fontSizeOptions,
  276. lineHeightOptions,
  277. wordSpaceOptions,
  278. updateLineHeight,
  279. updateWordSpace,
  280. updateFill,
  281. emitRichTextCommand,
  282. }
  283. },
  284. })
  285. </script>
  286. <style lang="scss" scoped>
  287. .text-style-panel {
  288. user-select: none;
  289. }
  290. .row {
  291. width: 100%;
  292. display: flex;
  293. align-items: center;
  294. margin-bottom: 10px;
  295. }
  296. .text-color-btn {
  297. display: flex;
  298. flex-direction: column;
  299. justify-content: center;
  300. align-items: center;
  301. }
  302. .text-color-block {
  303. width: 16px;
  304. height: 3px;
  305. margin-top: 1px;
  306. }
  307. </style>