TextStylePanel.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <template>
  2. <div class="text-style-panel">
  3. <InputGroup compact class="row">
  4. <Select
  5. style="flex: 3;"
  6. :value="richTextAttrs.fontname"
  7. >
  8. <SelectOption v-for="font in availableFonts" :key="font.en" :value="font.en">
  9. <span :style="{ fontFamily: font.en }">{{font.zh}}</span>
  10. </SelectOption>
  11. </Select>
  12. <Select
  13. style="flex: 2;"
  14. :value="richTextAttrs.fontsize"
  15. >
  16. <SelectOption v-for="fontsize in fontSizeOptions" :key="fontsize" :value="fontsize">
  17. {{fontsize}}
  18. </SelectOption>
  19. </Select>
  20. </InputGroup>
  21. <ButtonGroup class="row">
  22. <Popover trigger="click">
  23. <template #content>
  24. <ColorPicker v-model="richTextAttrs.color" />
  25. </template>
  26. <Button class="text-color-btn" style="flex: 1;">
  27. <FontColorsOutlined />
  28. <div class="text-color-block" :style="{ backgroundColor: richTextAttrs.color }"></div>
  29. </Button>
  30. </Popover>
  31. <Popover trigger="click">
  32. <template #content>
  33. <ColorPicker v-model="richTextAttrs.backcolor" />
  34. </template>
  35. <Button class="text-color-btn" style="flex: 1;">
  36. <HighlightOutlined />
  37. <div class="text-color-block" :style="{ backgroundColor: richTextAttrs.backcolor }"></div>
  38. </Button>
  39. </Popover>
  40. <Popover trigger="click">
  41. <template #content>
  42. <ColorPicker v-model="fill" />
  43. </template>
  44. <Button class="text-color-btn" style="flex: 1;">
  45. <BgColorsOutlined />
  46. <div class="text-color-block" :style="{ backgroundColor: fill }"></div>
  47. </Button>
  48. </Popover>
  49. </ButtonGroup>
  50. <ButtonGroup class="row">
  51. <Button style="flex: 1;" :type="richTextAttrs.bold ? 'primary' : 'default'"><BoldOutlined /></Button>
  52. <Button style="flex: 1;" :type="richTextAttrs.em ? 'primary' : 'default'"><ItalicOutlined /></Button>
  53. <Button style="flex: 1;" :type="richTextAttrs.underline ? 'primary' : 'default'"><UnderlineOutlined /></Button>
  54. <Button style="flex: 1;" :type="richTextAttrs.strikethrough ? 'primary' : 'default'"><StrikethroughOutlined /></Button>
  55. </ButtonGroup>
  56. <ButtonGroup class="row">
  57. <Button style="flex: 1;" :type="richTextAttrs.superscript ? 'primary' : 'default'">上</Button>
  58. <Button style="flex: 1;" :type="richTextAttrs.subscript ? 'primary' : 'default'">下</Button>
  59. <Button style="flex: 1;" :type="richTextAttrs.code ? 'primary' : 'default'">码</Button>
  60. <Button style="flex: 1;" :type="richTextAttrs.blockquote ? 'primary' : 'default'">引</Button>
  61. <Button style="flex: 1;">清</Button>
  62. </ButtonGroup>
  63. <Divider />
  64. <ButtonGroup class="row">
  65. <Button style="flex: 1;" :type="richTextAttrs.align === 'left' || '' ? 'primary' : 'default'"><AlignLeftOutlined /></Button>
  66. <Button style="flex: 1;" :type="richTextAttrs.align === 'center' ? 'primary' : 'default'"><AlignCenterOutlined /></Button>
  67. <Button style="flex: 1;" :type="richTextAttrs.align === 'right' ? 'primary' : 'default'"><AlignRightOutlined /></Button>
  68. </ButtonGroup>
  69. <ButtonGroup class="row">
  70. <Button style="flex: 1;" :type="richTextAttrs.bulletList ? 'primary' : 'default'"><UnorderedListOutlined /></Button>
  71. <Button style="flex: 1;" :type="richTextAttrs.orderedList ? 'primary' : 'default'"><OrderedListOutlined /></Button>
  72. </ButtonGroup>
  73. <Divider />
  74. <div class="row">
  75. <div style="flex: 2;">行间距:</div>
  76. <Select style="flex: 3;" :value="lineHeight" @change="value => updateLineHeight(value)">
  77. <template #suffixIcon><ColumnHeightOutlined /></template>
  78. <SelectOption v-for="item in lineHeightOptions" :key="item" :value="item">{{item}}</SelectOption>
  79. </Select>
  80. </div>
  81. <div class="row">
  82. <div style="flex: 2;">字间距:</div>
  83. <Select style="flex: 3;" :value="wordSpace" @change="value => updateWordSpace(value)">
  84. <template #suffixIcon><ColumnWidthOutlined /></template>
  85. <SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}</SelectOption>
  86. </Select>
  87. </div>
  88. <Divider />
  89. <ElementOutline />
  90. <Divider />
  91. <ElementShadow />
  92. <Divider />
  93. <ElementOpacity />
  94. </div>
  95. </template>
  96. <script lang="ts">
  97. import { computed, defineComponent, onUnmounted, Ref, ref, watch } from 'vue'
  98. import { useStore } from 'vuex'
  99. import { MutationTypes, State } from '@/store'
  100. import { PPTTextElement } from '@/types/slides'
  101. import emitter, { EmitterEvents } from '@/utils/emitter'
  102. import { TextAttrs } from '@/prosemirror/utils'
  103. import useHistorySnapshot from '@/hooks/useHistorySnapshot'
  104. import ElementOpacity from '../common/ElementOpacity.vue'
  105. import ElementOutline from '../common/ElementOutline.vue'
  106. import ElementShadow from '../common/ElementShadow.vue'
  107. import ColorPicker from '@/components/ColorPicker/index.vue'
  108. import { Select, Input, Button, Divider, Popover } from 'ant-design-vue'
  109. import {
  110. FontColorsOutlined,
  111. HighlightOutlined,
  112. BgColorsOutlined,
  113. BoldOutlined,
  114. ItalicOutlined,
  115. UnderlineOutlined,
  116. StrikethroughOutlined,
  117. AlignLeftOutlined,
  118. AlignCenterOutlined,
  119. AlignRightOutlined,
  120. OrderedListOutlined,
  121. UnorderedListOutlined,
  122. ColumnHeightOutlined,
  123. ColumnWidthOutlined,
  124. } from '@ant-design/icons-vue'
  125. export default defineComponent({
  126. name: 'text-style-panel',
  127. components: {
  128. ColorPicker,
  129. Select,
  130. SelectOption: Select.Option,
  131. InputGroup: Input.Group,
  132. Button,
  133. ButtonGroup: Button.Group,
  134. Divider,
  135. Popover,
  136. FontColorsOutlined,
  137. HighlightOutlined,
  138. BgColorsOutlined,
  139. BoldOutlined,
  140. ItalicOutlined,
  141. UnderlineOutlined,
  142. StrikethroughOutlined,
  143. AlignLeftOutlined,
  144. AlignCenterOutlined,
  145. AlignRightOutlined,
  146. OrderedListOutlined,
  147. UnorderedListOutlined,
  148. ColumnHeightOutlined,
  149. ColumnWidthOutlined,
  150. ElementOpacity,
  151. ElementOutline,
  152. ElementShadow,
  153. },
  154. setup() {
  155. const store = useStore<State>()
  156. const handleElement: Ref<PPTTextElement> = computed(() => store.getters.handleElement)
  157. const fill = ref<string>()
  158. const lineHeight = ref<number>()
  159. const wordSpace = ref<number>()
  160. watch(handleElement, () => {
  161. if(!handleElement.value) return
  162. fill.value = handleElement.value.fill || '#000'
  163. lineHeight.value = handleElement.value.lineHeight || 1.5
  164. wordSpace.value = handleElement.value.wordSpace || 0
  165. }, { deep: true, immediate: true })
  166. const richTextAttrs = ref<TextAttrs>({
  167. bold: false,
  168. em: false,
  169. underline: false,
  170. strikethrough: false,
  171. superscript: false,
  172. subscript: false,
  173. code: false,
  174. color: '#000',
  175. backcolor: '#000',
  176. fontsize: '12px',
  177. fontname: '微软雅黑',
  178. align: 'left',
  179. bulletList: false,
  180. orderedList: false,
  181. blockquote: false,
  182. })
  183. const availableFonts = computed(() => store.state.availableFonts)
  184. const fontSizeOptions = [
  185. '12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
  186. '36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '80px',
  187. ]
  188. const lineHeightOptions = [0.5, 1.0, 1.2, 1.5, 1.8, 2.0, 3.0]
  189. const wordSpaceOptions = [0, 1, 2, 3, 4, 5, 8]
  190. const updateRichTextAttrs = (attr: TextAttrs) => richTextAttrs.value = attr
  191. emitter.on(EmitterEvents.UPDATE_TEXT_STATE, attr => updateRichTextAttrs(attr))
  192. onUnmounted(() => {
  193. emitter.off(EmitterEvents.UPDATE_TEXT_STATE, attr => updateRichTextAttrs(attr))
  194. })
  195. const { addHistorySnapshot } = useHistorySnapshot()
  196. const updateLineHeight = (value: number) => {
  197. const props = { lineHeight: value }
  198. store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
  199. addHistorySnapshot()
  200. }
  201. const updateWordSpace = (value: number) => {
  202. const props = { wordSpace: value }
  203. store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
  204. addHistorySnapshot()
  205. }
  206. return {
  207. fill,
  208. lineHeight,
  209. wordSpace,
  210. richTextAttrs,
  211. availableFonts,
  212. fontSizeOptions,
  213. lineHeightOptions,
  214. wordSpaceOptions,
  215. updateLineHeight,
  216. updateWordSpace,
  217. }
  218. },
  219. })
  220. </script>
  221. <style lang="scss" scoped>
  222. .row {
  223. width: 100%;
  224. display: flex;
  225. align-items: center;
  226. margin-bottom: 10px;
  227. }
  228. .text-color-btn {
  229. display: flex;
  230. flex-direction: column;
  231. justify-content: center;
  232. align-items: center;
  233. }
  234. .text-color-block {
  235. width: 16px;
  236. height: 3px;
  237. margin-top: 1px;
  238. }
  239. </style>