Browse Source

增加全局字体

黄琪勇 4 months ago
parent
commit
5d5716d9f5

+ 2 - 2
src/assets/styles/prosemirror.scss

@@ -1,7 +1,7 @@
 .ProseMirror, .ProseMirror-static {
   outline: 0;
   border: 0;
-  font-size: 20px;
+  font-size: 40px;
   word-break: break-word;
   white-space: normal;
 
@@ -96,4 +96,4 @@
 
 .ProseMirror-selectednode {
   outline: none !important;
-}
+}

+ 1 - 0
src/mocks/theme.ts

@@ -4,6 +4,7 @@ export const theme: SlideTheme = {
   themeColor: "#5b9bd5",
   fontColor: "#333",
   fontName: "Microsoft Yahei",
+  fontSize: "40px",
   backgroundColor: "#fff",
   shadow: {
     h: 3,

+ 1 - 0
src/types/slides.ts

@@ -765,6 +765,7 @@ export interface SlideTheme {
   themeColor: string
   fontColor: string
   fontName: string
+  fontSize: string
   outline: PPTElementOutline
   shadow: PPTElementShadow
 }

+ 3 - 3
src/utils/database.ts

@@ -13,7 +13,7 @@ export interface Snapshot {
   slides: Slide[]
 }
 
-const databaseNamePrefix = 'PPTist'
+const databaseNamePrefix = 'PPT'
 
 // 删除失效/过期的数据库
 // 应用关闭时(关闭或刷新浏览器),会将其数据库ID记录在 localStorage 中,表示该ID指向的数据库已失效
@@ -28,7 +28,7 @@ export const deleteDiscardedDB = async () => {
   const databaseNames = await Dexie.getDatabaseNames()
   const discardedDBNames = databaseNames.filter(name => {
     if (name.indexOf(databaseNamePrefix) === -1) return false
-    
+
     const [prefix, id, time] = name.split('_')
     if (prefix !== databaseNamePrefix || !id || !time) return true
     if (localStorageDiscardedDBList.includes(id)) return true
@@ -56,4 +56,4 @@ class PPTistDB extends Dexie {
   }
 }
 
-export const db = new PPTistDB()
+export const db = new PPTistDB()

+ 3 - 3
src/utils/prosemirror/utils.ts

@@ -186,7 +186,7 @@ interface DefaultAttrs {
 const _defaultAttrs: DefaultAttrs = {
   color: '#000',
   backcolor: '',
-  fontsize: '20px',
+  fontsize: '40px',
   fontname: '微软雅黑',
   align: 'left',
 }
@@ -250,11 +250,11 @@ export const defaultRichTextAttrs: TextAttrs = {
   code: false,
   color: '#000',
   backcolor: '',
-  fontsize: '20px',
+  fontsize: '40px',
   fontname: '微软雅黑',
   link: '',
   align: 'left',
   bulletList: false,
   orderedList: false,
   blockquote: false,
-}
+}

+ 13 - 13
src/views/Editor/Toolbar/ElementStylePanel/MultiStylePanel.vue

@@ -17,8 +17,8 @@
 
     <div class="row">
       <div style="width: 40%;">边框样式:</div>
-      <Select 
-        style="width: 60%;" 
+      <Select
+        style="width: 60%;"
         :value="outline.style || ''"
         @update:value="value => updateOutline({ style: value as 'solid' | 'dashed' | 'dotted' })"
         :options="[
@@ -42,10 +42,10 @@
     </div>
     <div class="row">
       <div style="width: 40%;">边框粗细:</div>
-      <NumberInput 
+      <NumberInput
         :value="outline.width || 0"
-        @update:value="value => updateOutline({ width: value })" 
-        style="width: 60%;" 
+        @update:value="value => updateOutline({ width: value })"
+        style="width: 60%;"
       />
     </div>
 
@@ -105,7 +105,7 @@
           <IconHighLight />
         </TextColorButton>
       </Popover>
-      <Button 
+      <Button
         class="font-size-btn"
         style="width: 20%;"
         v-tooltip="'增大字号'"
@@ -119,9 +119,9 @@
         @click="updateFontStyle('fontsize-reduce', '2')"
       ><IconFontSize />-</Button>
     </ButtonGroup>
-    <RadioGroup 
-      class="row" 
-      button-style="solid" 
+    <RadioGroup
+      class="row"
+      button-style="solid"
       :value="richTextAttrs.align"
       @update:value="value => updateFontStyle('align', value)"
     >
@@ -166,9 +166,9 @@ const updateElement = (id: string, props: Partial<PPTElement>) => {
 }
 
 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',
+  '24px', '28px', '32px', '36px', '40px', '44px', '48px', '56px', '64px',
+  '72px', '80px', '88px', '96px', '108px', '120px', '132px', '144px', '152px',
+  '160px', '176px', '192px', '208px', '224px', '240px',
 ]
 
 const fill = ref('#fff')
@@ -257,4 +257,4 @@ const updateFontStyle = (command: string, value: string) => {
 .font-size-btn {
   padding: 0;
 }
-</style>
+</style>

+ 31 - 31
src/views/Editor/Toolbar/ElementStylePanel/TableStylePanel.vue

@@ -58,25 +58,25 @@
     </ButtonGroup>
 
     <ButtonGroup class="row">
-      <CheckboxButton 
+      <CheckboxButton
         style="flex: 1;"
         :checked="textAttrs.bold"
         v-tooltip="'加粗'"
         @click="updateTextAttrs({ bold: !textAttrs.bold })"
       ><IconTextBold /></CheckboxButton>
-      <CheckboxButton 
+      <CheckboxButton
         style="flex: 1;"
         :checked="textAttrs.em"
         v-tooltip="'斜体'"
         @click="updateTextAttrs({ em: !textAttrs.em })"
       ><IconTextItalic /></CheckboxButton>
-      <CheckboxButton 
+      <CheckboxButton
         style="flex: 1;"
         :checked="textAttrs.underline"
         v-tooltip="'下划线'"
         @click="updateTextAttrs({ underline: !textAttrs.underline })"
       ><IconTextUnderline /></CheckboxButton>
-      <CheckboxButton 
+      <CheckboxButton
         style="flex: 1;"
         :checked="textAttrs.strikethrough"
         v-tooltip="'删除线'"
@@ -84,9 +84,9 @@
       ><IconStrikethrough /></CheckboxButton>
     </ButtonGroup>
 
-    <RadioGroup 
-      class="row" 
-      button-style="solid" 
+    <RadioGroup
+      class="row"
+      button-style="solid"
       :value="textAttrs.align"
       @update:value="value => updateTextAttrs({ align: value as TextAlign })"
     >
@@ -124,35 +124,35 @@
     <div class="row theme-switch">
       <div style="width: 40%;">启用主题表格:</div>
       <div class="switch-wrapper" style="width: 60%;">
-        <Switch 
-          :value="hasTheme" 
-          @update:value="value => toggleTheme(value)" 
+        <Switch
+          :value="hasTheme"
+          @update:value="value => toggleTheme(value)"
         />
       </div>
     </div>
 
     <template v-if="theme">
       <div class="row">
-        <Checkbox 
-          @update:value="value => updateTheme({ rowHeader: value })" 
-          :value="theme.rowHeader" 
+        <Checkbox
+          @update:value="value => updateTheme({ rowHeader: value })"
+          :value="theme.rowHeader"
           style="flex: 1;"
         >标题行</Checkbox>
-        <Checkbox 
-          @update:value="value => updateTheme({ rowFooter: value })" 
-          :value="theme.rowFooter" 
+        <Checkbox
+          @update:value="value => updateTheme({ rowFooter: value })"
+          :value="theme.rowFooter"
           style="flex: 1;"
         >汇总行</Checkbox>
       </div>
       <div class="row">
-        <Checkbox 
-          @update:value="value => updateTheme({ colHeader: value })" 
-          :value="theme.colHeader" 
+        <Checkbox
+          @update:value="value => updateTheme({ colHeader: value })"
+          :value="theme.colHeader"
           style="flex: 1;"
         >第一列</Checkbox>
-        <Checkbox 
-          @update:value="value => updateTheme({ colFooter: value })" 
-          :value="theme.colFooter" 
+        <Checkbox
+          @update:value="value => updateTheme({ colFooter: value })"
+          :value="theme.colFooter"
           style="flex: 1;"
         >最后一列</Checkbox>
       </div>
@@ -212,7 +212,7 @@ const textAttrs = ref({
   strikethrough: false,
   color: '#000',
   backcolor: '',
-  fontsize: '12px',
+  fontsize: '24px',
   fontname: '微软雅黑',
   align: 'left',
 })
@@ -226,7 +226,7 @@ const minColCount = ref(0)
 
 watch(handleElement, () => {
   if (!handleElement.value || handleElement.value.type !== 'table') return
-  
+
   theme.value = handleElement.value.theme
   hasTheme.value = !!theme.value
 
@@ -251,7 +251,7 @@ const updateTextAttrState = () => {
     colIndex = +selectedCell.split('_')[1]
   }
   const style = handleElement.value.data[rowIndex][colIndex].style
-
+  debugger
   if (!style) {
     textAttrs.value = {
       bold: false,
@@ -260,7 +260,7 @@ const updateTextAttrState = () => {
       strikethrough: false,
       color: '#000',
       backcolor: '',
-      fontsize: '12px',
+      fontsize: '24px',
       fontname: '微软雅黑',
       align: 'left',
     }
@@ -273,7 +273,7 @@ const updateTextAttrState = () => {
       strikethrough: !!style.strikethrough,
       color: style.color || '#000',
       backcolor: style.backcolor || '',
-      fontsize: style.fontsize || '12px',
+      fontsize: style.fontsize || '24px',
       fontname: style.fontname || '微软雅黑',
       align: style.align || 'left',
     }
@@ -344,10 +344,10 @@ const setTableRow = (value: number) => {
   if (value > rowCount) {
     const rowCells: TableCell[] = new Array(colCount.value).fill({ id: nanoid(10), colspan: 1, rowspan: 1, text: '' })
     const newTableCells: TableCell[][] = new Array(value - rowCount).fill(rowCells)
-  
+
     const tableCells: TableCell[][] = JSON.parse(JSON.stringify(_handleElement.data))
     tableCells.push(...newTableCells)
-  
+
     updateElement({ data: tableCells })
   }
   else {
@@ -370,7 +370,7 @@ const setTableCol = (value: number) => {
       item.push(...cells)
       return item
     })
-  
+
     const newColSizeList: number[] = new Array(value - colCount).fill(100)
     colSizeList.push(...newColSizeList)
   }
@@ -419,4 +419,4 @@ const setTableCol = (value: number) => {
     margin: 0 8px;
   }
 }
-</style>
+</style>

+ 87 - 102
src/views/Editor/Toolbar/ElementStylePanel/TextStylePanel.vue

@@ -1,13 +1,9 @@
 <template>
   <div class="text-style-panel">
     <div class="preset-style">
-      <div 
-        class="preset-style-item"
-        v-for="item in presetStyles"
-        :key="item.label"
-        :style="item.style"
-        @click="emitBatchRichTextCommand(item.cmd)"
-      >{{item.label}}</div>
+      <div class="preset-style-item" v-for="item in presetStyles" :key="item.label" :style="item.style" @click="emitBatchRichTextCommand(item.cmd)">
+        {{ item.label }}
+      </div>
     </div>
 
     <Divider />
@@ -15,13 +11,17 @@
     <Divider />
 
     <div class="row">
-      <div style="width: 40%;">行间距:</div>
-      <Select style="width: 60%;"
+      <div style="width: 40%">行间距:</div>
+      <Select
+        style="width: 60%"
         :value="lineHeight || 1"
         @update:value="value => updateLineHeight(value as number)"
-        :options="lineHeightOptions.map(item => ({
-          label: item + '倍', value: item
-        }))"
+        :options="
+          lineHeightOptions.map(item => ({
+            label: item + '倍',
+            value: item
+          }))
+        "
       >
         <template #icon>
           <IconRowHeight />
@@ -29,13 +29,17 @@
       </Select>
     </div>
     <div class="row">
-      <div style="width: 40%;">段间距:</div>
-      <Select style="width: 60%;"
+      <div style="width: 40%">段间距:</div>
+      <Select
+        style="width: 60%"
         :value="paragraphSpace || 0"
         @update:value="value => updateParagraphSpace(value as number)"
-        :options="paragraphSpaceOptions.map(item => ({
-          label: item + 'px', value: item
-        }))"
+        :options="
+          paragraphSpaceOptions.map(item => ({
+            label: item + 'px',
+            value: item
+          }))
+        "
       >
         <template #icon>
           <IconVerticalSpacingBetweenItems />
@@ -43,13 +47,17 @@
       </Select>
     </div>
     <div class="row">
-      <div style="width: 40%;">字间距:</div>
-      <Select style="width: 60%;"
+      <div style="width: 40%">字间距:</div>
+      <Select
+        style="width: 60%"
         :value="wordSpace || 0"
         @update:value="value => updateWordSpace(value as number)"
-        :options="wordSpaceOptions.map(item => ({
-          label: item + 'px', value: item
-        }))"
+        :options="
+          wordSpaceOptions.map(item => ({
+            label: item + 'px',
+            value: item
+          }))
+        "
       >
         <template #icon>
           <IconFullwidth />
@@ -57,13 +65,10 @@
       </Select>
     </div>
     <div class="row">
-      <div style="width: 40%;">文本框填充:</div>
-      <Popover trigger="click" style="width: 60%;">
+      <div style="width: 40%">文本框填充:</div>
+      <Popover trigger="click" style="width: 60%">
         <template #content>
-          <ColorPicker
-            :modelValue="fill"
-            @update:modelValue="value => updateFill(value)"
-          />
+          <ColorPicker :modelValue="fill" @update:modelValue="value => updateFill(value)" />
         </template>
         <ColorButton :color="fill" />
       </Popover>
@@ -79,96 +84,72 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, watch } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useMainStore, useSlidesStore } from '@/store'
-import type { PPTTextElement } from '@/types/slides'
-import emitter, { EmitterEvents, type RichTextAction } from '@/utils/emitter'
-import useHistorySnapshot from '@/hooks/useHistorySnapshot'
-
-import ElementOpacity from '../common/ElementOpacity.vue'
-import ElementOutline from '../common/ElementOutline.vue'
-import ElementShadow from '../common/ElementShadow.vue'
-import RichTextBase from '../common/RichTextBase.vue'
-import ColorButton from '@/components/ColorButton.vue'
-import ColorPicker from '@/components/ColorPicker/index.vue'
-import Divider from '@/components/Divider.vue'
-import Select from '@/components/Select.vue'
-import Popover from '@/components/Popover.vue'
+import { ref, watch } from "vue"
+import { storeToRefs } from "pinia"
+import { useMainStore, useSlidesStore } from "@/store"
+import type { PPTTextElement } from "@/types/slides"
+import emitter, { EmitterEvents, type RichTextAction } from "@/utils/emitter"
+import useHistorySnapshot from "@/hooks/useHistorySnapshot"
+
+import ElementOpacity from "../common/ElementOpacity.vue"
+import ElementOutline from "../common/ElementOutline.vue"
+import ElementShadow from "../common/ElementShadow.vue"
+import RichTextBase from "../common/RichTextBase.vue"
+import ColorButton from "@/components/ColorButton.vue"
+import ColorPicker from "@/components/ColorPicker/index.vue"
+import Divider from "@/components/Divider.vue"
+import Select from "@/components/Select.vue"
+import Popover from "@/components/Popover.vue"
 
 // 注意,存在一个未知原因的BUG,如果文本加粗后文本框高度增加,画布的可视区域定位会出现错误
 // 因此在执行预置样式命令时,将加粗命令放在尽可能靠前的位置,避免字号增大后再加粗
 const presetStyles = [
   {
-    label: '大标题',
+    label: "大标题",
     style: {
-      fontSize: '26px',
-      fontWeight: 700,
+      fontSize: "26px",
+      fontWeight: 700
     },
-    cmd: [
-      { command: 'clear' },
-      { command: 'bold' },
-      { command: 'fontsize', value: '66px' },
-      { command: 'align', value: 'center' },
-    ],
+    cmd: [{ command: "clear" }, { command: "bold" }, { command: "fontsize", value: "132px" }, { command: "align", value: "center" }]
   },
   {
-    label: '小标题',
+    label: "小标题",
     style: {
-      fontSize: '22px',
-      fontWeight: 700,
+      fontSize: "22px",
+      fontWeight: 700
     },
-    cmd: [
-      { command: 'clear' },
-      { command: 'bold' },
-      { command: 'fontsize', value: '40px' },
-      { command: 'align', value: 'center' },
-    ],
+    cmd: [{ command: "clear" }, { command: "bold" }, { command: "fontsize", value: "80px" }, { command: "align", value: "center" }]
   },
   {
-    label: '正文',
+    label: "正文",
     style: {
-      fontSize: '20px',
+      fontSize: "20px"
     },
-    cmd: [
-      { command: 'clear' },
-      { command: 'fontsize', value: '20px' },
-    ],
+    cmd: [{ command: "clear" }, { command: "fontsize", value: "40px" }]
   },
   {
-    label: '正文[小]',
+    label: "正文[小]",
     style: {
-      fontSize: '18px',
+      fontSize: "18px"
     },
-    cmd: [
-      { command: 'clear' },
-      { command: 'fontsize', value: '18px' },
-    ],
+    cmd: [{ command: "clear" }, { command: "fontsize", value: "36px" }]
   },
   {
-    label: '注释 1',
+    label: "注释 1",
     style: {
-      fontSize: '16px',
-      fontStyle: 'italic',
+      fontSize: "16px",
+      fontStyle: "italic"
     },
-    cmd: [
-      { command: 'clear' },
-      { command: 'fontsize', value: '16px' },
-      { command: 'em' },
-    ],
+    cmd: [{ command: "clear" }, { command: "fontsize", value: "32px" }, { command: "em" }]
   },
   {
-    label: '注释 2',
+    label: "注释 2",
     style: {
-      fontSize: '16px',
-      textDecoration: 'underline',
+      fontSize: "16px",
+      textDecoration: "underline"
     },
-    cmd: [
-      { command: 'clear' },
-      { command: 'fontsize', value: '16px' },
-      { command: 'underline' },
-    ],
-  },
+    cmd: [{ command: "clear" }, { command: "fontsize", value: "32px" }, { command: "underline" }]
+  }
 ]
 
 const mainStore = useMainStore()
@@ -182,20 +163,24 @@ const updateElement = (props: Partial<PPTTextElement>) => {
   addHistorySnapshot()
 }
 
-const fill = ref<string>('#000')
+const fill = ref<string>("#000")
 const lineHeight = ref<number>()
 const wordSpace = ref<number>()
 const paragraphSpace = ref<number>()
 
-watch(handleElement, () => {
-  if (!handleElement.value || handleElement.value.type !== 'text') return
+watch(
+  handleElement,
+  () => {
+    if (!handleElement.value || handleElement.value.type !== "text") return
 
-  fill.value = handleElement.value.fill || '#fff'
-  lineHeight.value = handleElement.value.lineHeight || 1.5
-  wordSpace.value = handleElement.value.wordSpace || 0
-  paragraphSpace.value = handleElement.value.paragraphSpace === undefined ? 5 : handleElement.value.paragraphSpace
-  emitter.emit(EmitterEvents.SYNC_RICH_TEXT_ATTRS_TO_STORE)
-}, { deep: true, immediate: true })
+    fill.value = handleElement.value.fill || "#fff"
+    lineHeight.value = handleElement.value.lineHeight || 1.5
+    wordSpace.value = handleElement.value.wordSpace || 0
+    paragraphSpace.value = handleElement.value.paragraphSpace === undefined ? 5 : handleElement.value.paragraphSpace
+    emitter.emit(EmitterEvents.SYNC_RICH_TEXT_ATTRS_TO_STORE)
+  },
+  { deep: true, immediate: true }
+)
 
 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]
@@ -263,8 +248,8 @@ const emitBatchRichTextCommand = (action: RichTextAction[]) => {
   &:nth-child(2n) {
     margin-left: -1px;
   }
-  &:nth-child(n+3) {
+  &:nth-child(n + 3) {
     margin-top: -1px;
   }
 }
-</style>
+</style>

+ 6 - 6
src/views/Editor/Toolbar/SlideAnimationPanel.vue

@@ -1,10 +1,10 @@
 <template>
   <div class="slide-animation-panel">
     <div class="animation-pool">
-      <div 
-        class="animation-item" 
-        :class="{ 'active': currentTurningMode === item.value }" 
-        v-for="item in animations" 
+      <div
+        class="animation-item"
+        :class="{ 'active': currentTurningMode === item.value }"
+        v-for="item in animations"
         :key="item.label"
         @click="updateTurningMode(item.value)"
       >
@@ -95,7 +95,7 @@ const applyAllSlide = () => {
   overflow: hidden;
 
   @mixin elAnimation($animationType) {
-    content: 'PPTist';
+    content: 'PPT';
     width: 100%;
     height: 100%;
     position: absolute;
@@ -247,4 +247,4 @@ const applyAllSlide = () => {
     transform: scale(1);
   }
 }
-</style>
+</style>

+ 148 - 150
src/views/Editor/Toolbar/SlideDesignPanel.vue

@@ -2,48 +2,45 @@
   <div class="slide-design-panel">
     <div class="title">背景填充</div>
     <div class="row">
-      <Select 
-        style="flex: 1;" 
-        :value="background.type" 
+      <Select
+        style="flex: 1"
+        :value="background.type"
         @update:value="value => updateBackgroundType(value as 'gradient' | 'image' | 'solid')"
         :options="[
           { label: '纯色填充', value: 'solid' },
           { label: '图片填充', value: 'image' },
-          { label: '渐变填充', value: 'gradient' },
+          { label: '渐变填充', value: 'gradient' }
         ]"
       />
-      <div style="width: 10px;"></div>
+      <div style="width: 10px"></div>
 
-      <Popover trigger="click" v-if="background.type === 'solid'" style="flex: 1;">
+      <Popover trigger="click" v-if="background.type === 'solid'" style="flex: 1">
         <template #content>
-          <ColorPicker
-            :modelValue="background.color"
-            @update:modelValue="color => updateBackground({ color })"
-          />
+          <ColorPicker :modelValue="background.color" @update:modelValue="color => updateBackground({ color })" />
         </template>
         <ColorButton :color="background.color || '#fff'" />
       </Popover>
 
-      <Select 
-        style="flex: 1;" 
-        :value="background.image?.size || 'cover'" 
+      <Select
+        style="flex: 1"
+        :value="background.image?.size || 'cover'"
         @update:value="value => updateImageBackground({ size: value as SlideBackgroundImageSize })"
         v-else-if="background.type === 'image'"
         :options="[
           { label: '缩放', value: 'contain' },
           { label: '拼贴', value: 'repeat' },
-          { label: '缩放铺满', value: 'cover' },
+          { label: '缩放铺满', value: 'cover' }
         ]"
       />
 
-      <Select 
-        style="flex: 1;" 
-        :value="background.gradient?.type || ''" 
+      <Select
+        style="flex: 1"
+        :value="background.gradient?.type || ''"
         @update:value="value => updateGradientBackground({ type: value as GradientType })"
         v-else
         :options="[
           { label: '线性渐变', value: 'linear' },
-          { label: '径向渐变', value: 'radial' },
+          { label: '径向渐变', value: 'radial' }
         ]"
       />
     </div>
@@ -63,12 +60,12 @@
         <GradientBar
           :value="background.gradient?.colors || []"
           @update:value="value => updateGradientBackground({ colors: value })"
-          @update:index="index => currentGradientIndex = index"
+          @update:index="index => (currentGradientIndex = index)"
         />
       </div>
       <div class="row">
-        <div style="width: 40%;">当前色块:</div>
-        <Popover trigger="click" style="width: 60%;">
+        <div style="width: 40%">当前色块:</div>
+        <Popover trigger="click" style="width: 60%">
           <template #content>
             <ColorPicker
               :modelValue="background.gradient!.colors[currentGradientIndex].color"
@@ -79,36 +76,36 @@
         </Popover>
       </div>
       <div class="row" v-if="background.gradient?.type === 'linear'">
-        <div style="width: 40%;">渐变角度:</div>
+        <div style="width: 40%">渐变角度:</div>
         <Slider
           :min="0"
           :max="360"
           :step="15"
           :value="background.gradient.rotate || 0"
           @update:value="value => updateGradientBackground({ rotate: value as number })"
-          style="width: 60%;"
+          style="width: 60%"
         />
       </div>
     </div>
 
     <div class="row">
-      <Button style="flex: 1;" @click="applyBackgroundAllSlide()">应用背景到全部</Button>
+      <Button style="flex: 1" @click="applyBackgroundAllSlide()">应用背景到全部</Button>
     </div>
 
     <Divider />
 
     <div class="row">
-      <div style="width: 40%;">画布尺寸:</div>
-      <Select 
-        style="width: 60%;" 
-        :value="viewportRatio" 
+      <div style="width: 40%">画布尺寸:</div>
+      <Select
+        style="width: 60%"
+        :value="viewportRatio"
         @update:value="value => updateViewportRatio(value as number)"
         :options="[
           { label: '宽屏 16 : 9', value: 0.5625 },
           { label: '宽屏 16 : 10', value: 0.625 },
           { label: '标准 4 : 3', value: 0.75 },
           { label: '纸张 A3 / A4', value: 0.70710678 },
-          { label: '竖向 A3 / A4', value: 1.41421356 },
+          { label: '竖向 A3 / A4', value: 1.41421356 }
         ]"
       />
     </div>
@@ -124,73 +121,81 @@
       </span>
     </div>
     <div class="row">
-      <div style="width: 40%;">字体:</div>
+      <div style="width: 40%">字体:</div>
       <Select
-        style="width: 60%;"
+        style="width: 60%"
         :value="theme.fontName"
         search
         searchLabel="搜索字体"
         @update:value="value => updateTheme({ fontName: value as string })"
-        :options="[
-          ...availableFonts,
-          ...WEB_FONTS
-        ]"
+        :options="[...availableFonts, ...WEB_FONTS]"
       />
     </div>
     <div class="row">
-      <div style="width: 40%;">字体颜色:</div>
-      <Popover trigger="click" style="width: 60%;">
+      <div style="width: 40%">字号:</div>
+      <Select
+        style="width: 60%"
+        :value="theme.fontSize"
+        search
+        searchLabel="搜索字号"
+        @update:value="value => updateTheme({ fontSize: value as string })"
+        :options="
+          fontSizeOptions.map(item => ({
+            label: item,
+            value: item
+          }))
+        "
+      >
+        <template #icon>
+          <IconAddText />
+        </template>
+      </Select>
+    </div>
+    <div class="row">
+      <div style="width: 40%">字体颜色:</div>
+      <Popover trigger="click" style="width: 60%">
         <template #content>
-          <ColorPicker
-            :modelValue="theme.fontColor"
-            @update:modelValue="value => updateTheme({ fontColor: value })"
-          />
+          <ColorPicker :modelValue="theme.fontColor" @update:modelValue="value => updateTheme({ fontColor: value })" />
         </template>
         <ColorButton :color="theme.fontColor" />
       </Popover>
     </div>
     <div class="row">
-      <div style="width: 40%;">背景颜色:</div>
-      <Popover trigger="click" style="width: 60%;">
+      <div style="width: 40%">背景颜色:</div>
+      <Popover trigger="click" style="width: 60%">
         <template #content>
-          <ColorPicker
-            :modelValue="theme.backgroundColor"
-            @update:modelValue="value => updateTheme({ backgroundColor: value })"
-          />
+          <ColorPicker :modelValue="theme.backgroundColor" @update:modelValue="value => updateTheme({ backgroundColor: value })" />
         </template>
         <ColorButton :color="theme.backgroundColor" />
       </Popover>
     </div>
     <div class="row">
-      <div style="width: 40%;">主题色:</div>
-      <Popover trigger="click" style="width: 60%;">
+      <div style="width: 40%">主题色:</div>
+      <Popover trigger="click" style="width: 60%">
         <template #content>
-          <ColorPicker
-            :modelValue="theme.themeColor"
-            @update:modelValue="value => updateTheme({ themeColor: value })"
-          />
+          <ColorPicker :modelValue="theme.themeColor" @update:modelValue="value => updateTheme({ themeColor: value })" />
         </template>
         <ColorButton :color="theme.themeColor" />
       </Popover>
     </div>
-    
+
     <template v-if="moreThemeConfigsVisible">
       <div class="row">
-        <div style="width: 40%;">边框样式:</div>
-        <Select 
-          style="width: 60%;" 
-          :value="theme.outline.style || ''" 
+        <div style="width: 40%">边框样式:</div>
+        <Select
+          style="width: 60%"
+          :value="theme.outline.style || ''"
           @update:value="value => updateTheme({ outline: { ...theme.outline, style: value as 'dashed' | 'solid' | 'dotted' } })"
           :options="[
             { label: '实线边框', value: 'solid' },
             { label: '虚线边框', value: 'dashed' },
-            { label: '点线边框', value: 'dotted' },
+            { label: '点线边框', value: 'dotted' }
           ]"
         />
       </div>
       <div class="row">
-        <div style="width: 40%;">边框颜色:</div>
-        <Popover trigger="click" style="width: 60%;">
+        <div style="width: 40%">边框颜色:</div>
+        <Popover trigger="click" style="width: 60%">
           <template #content>
             <ColorPicker
               :modelValue="theme.outline.color"
@@ -201,28 +206,28 @@
         </Popover>
       </div>
       <div class="row">
-        <div style="width: 40%;">边框粗细:</div>
-        <NumberInput 
-          :value="theme.outline.width || 0" 
-          @update:value="value => updateTheme({ outline: { ...theme.outline, width: value } })" 
-          style="width: 60%;" 
+        <div style="width: 40%">边框粗细:</div>
+        <NumberInput
+          :value="theme.outline.width || 0"
+          @update:value="value => updateTheme({ outline: { ...theme.outline, width: value } })"
+          style="width: 60%"
         />
       </div>
-      <div class="row" style="height: 30px;">
-        <div style="width: 40%;">水平阴影:</div>
-        <Slider 
-          style="width: 60%;"
-          :min="-10" 
-          :max="10" 
-          :step="1" 
-          :value="theme.shadow.h" 
+      <div class="row" style="height: 30px">
+        <div style="width: 40%">水平阴影:</div>
+        <Slider
+          style="width: 60%"
+          :min="-10"
+          :max="10"
+          :step="1"
+          :value="theme.shadow.h"
           @update:value="value => updateTheme({ shadow: { ...theme.shadow, h: value as number } })"
         />
       </div>
-      <div class="row" style="height: 30px;">
-        <div style="width: 40%;">垂直阴影:</div>
+      <div class="row" style="height: 30px">
+        <div style="width: 40%">垂直阴影:</div>
         <Slider
-          style="width: 60%;"
+          style="width: 60%"
           :min="-10"
           :max="10"
           :step="1"
@@ -230,10 +235,10 @@
           @update:value="value => updateTheme({ shadow: { ...theme.shadow, v: value as number } })"
         />
       </div>
-      <div class="row" style="height: 30px;">
-        <div style="width: 40%;">模糊距离:</div>
+      <div class="row" style="height: 30px">
+        <div style="width: 40%">模糊距离:</div>
         <Slider
-          style="width: 60%;"
+          style="width: 60%"
           :min="1"
           :max="20"
           :step="1"
@@ -242,13 +247,10 @@
         />
       </div>
       <div class="row">
-        <div style="width: 40%;">阴影颜色:</div>
-        <Popover trigger="click" style="width: 60%;">
+        <div style="width: 40%">阴影颜色:</div>
+        <Popover trigger="click" style="width: 60%">
           <template #content>
-            <ColorPicker
-              :modelValue="theme.shadow.color"
-              @update:modelValue="value => updateTheme({ shadow: { ...theme.shadow, color: value } })"
-            />
+            <ColorPicker :modelValue="theme.shadow.color" @update:modelValue="value => updateTheme({ shadow: { ...theme.shadow, color: value } })" />
           </template>
           <ColorButton :color="theme.shadow.color" />
         </Popover>
@@ -256,81 +258,77 @@
     </template>
 
     <div class="row">
-      <Button style="flex: 1;" @click="applyThemeToAllSlides(moreThemeConfigsVisible)">应用主题到全部</Button>
+      <Button style="flex: 1" @click="applyThemeToAllSlides(moreThemeConfigsVisible)">应用主题到全部</Button>
     </div>
 
     <div class="row">
-      <Button style="flex: 1;" @click="themeStylesExtractVisible = true">从幻灯片提取主题</Button>
+      <Button style="flex: 1" @click="themeStylesExtractVisible = true">从幻灯片提取主题</Button>
     </div>
 
     <Divider />
 
     <div class="title">预置主题</div>
     <div class="theme-list">
-      <div 
-        class="theme-item" 
-        v-for="(item, index) in PRESET_THEMES" 
+      <div
+        class="theme-item"
+        v-for="(item, index) in PRESET_THEMES"
         :key="index"
         :style="{
           backgroundColor: item.background,
-          fontFamily: item.fontname,
+          fontFamily: item.fontname
         }"
       >
         <div class="theme-item-content">
           <div class="text" :style="{ color: item.fontColor }">文字 Aa</div>
           <div class="colors">
-            <div class="color-block" v-for="(color, index) in item.colors" :key="index" :style="{ backgroundColor: color}"></div>
+            <div class="color-block" v-for="(color, index) in item.colors" :key="index" :style="{ backgroundColor: color }"></div>
           </div>
 
           <div class="btns">
             <Button type="primary" size="small" @click="applyPresetThemeToSingleSlide(item)">应用</Button>
-            <Button type="primary" size="small" style="margin-top: 3px;" @click="applyPresetThemeToAllSlides(item)">应用全局</Button>
+            <Button type="primary" size="small" style="margin-top: 3px" @click="applyPresetThemeToAllSlides(item)">应用全局</Button>
           </div>
         </div>
       </div>
     </div>
   </div>
 
-  <Modal
-    v-model:visible="themeStylesExtractVisible" 
-    :width="320"
-    @closed="themeStylesExtractVisible = false"
-  >
+  <Modal v-model:visible="themeStylesExtractVisible" :width="320" @closed="themeStylesExtractVisible = false">
     <ThemeStylesExtract @close="themeStylesExtractVisible = false" />
   </Modal>
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useMainStore, useSlidesStore } from '@/store'
-import type { 
+import { computed, ref } from "vue"
+import { storeToRefs } from "pinia"
+import { useMainStore, useSlidesStore } from "@/store"
+import type {
   Gradient,
   GradientType,
   SlideBackground,
   SlideBackgroundType,
   SlideTheme,
   SlideBackgroundImage,
-  SlideBackgroundImageSize,
-} from '@/types/slides'
-import { PRESET_THEMES } from '@/configs/theme'
-import { WEB_FONTS } from '@/configs/font'
-import useHistorySnapshot from '@/hooks/useHistorySnapshot'
-import useSlideTheme from '@/hooks/useSlideTheme'
-import { getImageDataURL } from '@/utils/image'
-
-import ThemeStylesExtract from './ThemeStylesExtract.vue'
-import ColorButton from '@/components/ColorButton.vue'
-import FileInput from '@/components/FileInput.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 Select from '@/components/Select.vue'
-import Popover from '@/components/Popover.vue'
-import NumberInput from '@/components/NumberInput.vue'
-import Modal from '@/components/Modal.vue'
-import GradientBar from '@/components/GradientBar.vue'
+  SlideBackgroundImageSize
+} from "@/types/slides"
+import { PRESET_THEMES } from "@/configs/theme"
+import { WEB_FONTS } from "@/configs/font"
+import useHistorySnapshot from "@/hooks/useHistorySnapshot"
+import useSlideTheme from "@/hooks/useSlideTheme"
+import { getImageDataURL } from "@/utils/image"
+
+import ThemeStylesExtract from "./ThemeStylesExtract.vue"
+import ColorButton from "@/components/ColorButton.vue"
+import FileInput from "@/components/FileInput.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 Select from "@/components/Select.vue"
+import Popover from "@/components/Popover.vue"
+import NumberInput from "@/components/NumberInput.vue"
+import Modal from "@/components/Modal.vue"
+import GradientBar from "@/components/GradientBar.vue"
 
 const slidesStore = useSlidesStore()
 const { availableFonts } = storeToRefs(useMainStore())
@@ -343,53 +341,47 @@ const currentGradientIndex = ref(0)
 const background = computed(() => {
   if (!currentSlide.value.background) {
     return {
-      type: 'solid',
-      value: '#fff',
+      type: "solid",
+      value: "#fff"
     } as SlideBackground
   }
   return currentSlide.value.background
 })
 
 const { addHistorySnapshot } = useHistorySnapshot()
-const {
-  applyPresetThemeToSingleSlide,
-  applyPresetThemeToAllSlides,
-  applyThemeToAllSlides,
-} = useSlideTheme()
+const { applyPresetThemeToSingleSlide, applyPresetThemeToAllSlides, applyThemeToAllSlides } = useSlideTheme()
 
 // 设置背景模式:纯色、图片、渐变色
 const updateBackgroundType = (type: SlideBackgroundType) => {
-  if (type === 'solid') {
+  if (type === "solid") {
     const newBackground: SlideBackground = {
       ...background.value,
-      type: 'solid',
-      color: background.value.color || '#fff',
+      type: "solid",
+      color: background.value.color || "#fff"
     }
     slidesStore.updateSlide({ background: newBackground })
-  }
-  else if (type === 'image') {
+  } else if (type === "image") {
     const newBackground: SlideBackground = {
       ...background.value,
-      type: 'image',
+      type: "image",
       image: background.value.image || {
-        src: '',
-        size: 'cover',
-      },
+        src: "",
+        size: "cover"
+      }
     }
     slidesStore.updateSlide({ background: newBackground })
-  }
-  else {
+  } else {
     const newBackground: SlideBackground = {
       ...background.value,
-      type: 'gradient',
+      type: "gradient",
       gradient: background.value.gradient || {
-        type: 'linear',
+        type: "linear",
         colors: [
-          { pos: 0, color: '#fff' },
-          { pos: 100, color: '#fff' },
+          { pos: 0, color: "#fff" },
+          { pos: 100, color: "#fff" }
         ],
-        rotate: 0,
-      },
+        rotate: 0
+      }
     }
     currentGradientIndex.value = 0
     slidesStore.updateSlide({ background: newBackground })
@@ -432,7 +424,7 @@ const applyBackgroundAllSlide = () => {
   const newSlides = slides.value.map(slide => {
     return {
       ...slide,
-      background: currentSlide.value.background,
+      background: currentSlide.value.background
     }
   })
   slidesStore.setSlides(newSlides)
@@ -448,6 +440,12 @@ const updateTheme = (themeProps: Partial<SlideTheme>) => {
 const updateViewportRatio = (value: number) => {
   slidesStore.setViewportRatio(value)
 }
+
+const fontSizeOptions = [
+  '24px', '28px', '32px', '36px', '40px', '44px', '48px', '56px', '64px',
+  '72px', '80px', '88px', '96px', '108px', '120px', '132px', '144px', '152px',
+  '160px', '176px', '192px', '208px', '224px', '240px',
+]
 </script>
 
 <style lang="scss" scoped>
@@ -549,9 +547,9 @@ const updateViewportRatio = (value: number) => {
     justify-content: center;
     align-items: center;
     display: flex;
-    background-color: rgba($color: #000, $alpha: .25);
+    background-color: rgba($color: #000, $alpha: 0.25);
     opacity: 0;
     transition: opacity $transitionDelay;
   }
 }
-</style>
+</style>

+ 18 - 18
src/views/Editor/Toolbar/common/RichTextBase.vue

@@ -56,7 +56,7 @@
           <IconHighLight />
         </TextColorButton>
       </Popover>
-      <Button 
+      <Button
         class="font-size-btn"
         style="width: 20%;"
         v-tooltip="'增大字号'"
@@ -72,25 +72,25 @@
     </ButtonGroup>
 
     <ButtonGroup class="row">
-      <CheckboxButton 
+      <CheckboxButton
         style="flex: 1;"
         :checked="richTextAttrs.bold"
         v-tooltip="'加粗'"
         @click="emitRichTextCommand('bold')"
       ><IconTextBold /></CheckboxButton>
-      <CheckboxButton 
+      <CheckboxButton
         style="flex: 1;"
         :checked="richTextAttrs.em"
         v-tooltip="'斜体'"
         @click="emitRichTextCommand('em')"
       ><IconTextItalic /></CheckboxButton>
-      <CheckboxButton 
+      <CheckboxButton
         style="flex: 1;"
         :checked="richTextAttrs.underline"
         v-tooltip="'下划线'"
         @click="emitRichTextCommand('underline')"
       ><IconTextUnderline /></CheckboxButton>
-      <CheckboxButton 
+      <CheckboxButton
         style="flex: 1;"
         :checked="richTextAttrs.strikethrough"
         v-tooltip="'删除线'"
@@ -160,9 +160,9 @@
     </ButtonGroup>
     <Divider />
 
-    <RadioGroup 
-      class="row" 
-      button-style="solid" 
+    <RadioGroup
+      class="row"
+      button-style="solid"
       :value="richTextAttrs.align"
       @update:value="value => emitRichTextCommand('align', value)"
     >
@@ -184,9 +184,9 @@
         <Popover trigger="click" v-model:value="bulletListPanelVisible">
           <template #content>
             <div class="list-wrap">
-              <ul class="list" 
-                v-for="item in bulletListStyleTypeOption" 
-                :key="item" 
+              <ul class="list"
+                v-for="item in bulletListStyleTypeOption"
+                :key="item"
                 :style="{ listStyleType: item }"
                 @click="emitRichTextCommand('bulletList', item)"
               >
@@ -209,9 +209,9 @@
         <Popover trigger="click" v-model:value="orderedListPanelVisible">
           <template #content>
             <div class="list-wrap">
-              <ul class="list" 
-                v-for="item in orderedListStyleTypeOption" 
-                :key="item" 
+              <ul class="list"
+                v-for="item in orderedListStyleTypeOption"
+                :key="item"
                 :style="{ listStyleType: item }"
                 @click="emitRichTextCommand('orderedList', item)"
               >
@@ -276,9 +276,9 @@ const { richTextAttrs, availableFonts, textFormatPainter } = storeToRefs(useMain
 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',
+  '24px', '28px', '32px', '36px', '40px', '44px', '48px', '56px', '64px',
+  '72px', '80px', '88px', '96px', '108px', '120px', '132px', '144px', '152px',
+  '160px', '176px', '192px', '208px', '224px', '240px',
 ]
 
 const emitRichTextCommand = (command: string, value?: string) => {
@@ -387,4 +387,4 @@ const removeLink = () => {
 .popover-btn {
   padding: 0 3px;
 }
-</style>
+</style>

+ 1 - 0
src/views/Screen/ScreenElement.vue

@@ -7,6 +7,7 @@
       zIndex: elementIndex,
       color: theme.fontColor,
       fontFamily: theme.fontName,
+      fontSize: theme.fontSize,
       visibility: needWaitAnimation ? 'hidden' : 'visible'
     }"
     :title="elementInfo.link?.target || ''"

+ 3 - 3
src/views/components/element/TableElement/utils.ts

@@ -21,14 +21,14 @@ export const getTextStyle = (style?: TableCellStyle): CSSProperties => {
 
   let textDecoration = `${underline ? 'underline' : ''} ${strikethrough ? 'line-through' : ''}`
   if (textDecoration === ' ') textDecoration = 'none'
-  
+
   return {
     fontWeight: bold ? 'bold' : 'normal',
     fontStyle: em ? 'italic' : 'normal',
     textDecoration,
     color: color || '#000',
     backgroundColor: backcolor || '',
-    fontSize: fontsize || '14px',
+    fontSize: fontsize || '24px',
     fontFamily: fontname || '微软雅黑',
     textAlign: align || 'left',
   }
@@ -36,4 +36,4 @@ export const getTextStyle = (style?: TableCellStyle): CSSProperties => {
 
 export const formatText = (text: string) => {
   return text.replace(/\n/g, '</br>').replace(/ /g, '&nbsp;')
-}
+}