mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
chore: 类型调整,支持 volar
This commit is contained in:
parent
652665a4ae
commit
8f0b272224
@ -67,6 +67,12 @@ module.exports = {
|
||||
'no-console': isProduction ? 'error' : 'warn',
|
||||
'no-debugger': isProduction ? 'error' : 'warn',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/ban-types': ['error', {
|
||||
'extendDefaults': true,
|
||||
'types': {
|
||||
'{}': false,
|
||||
},
|
||||
}],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
@ -73,7 +73,12 @@ import { FORMULA_LIST, SYMBOL_LIST } from '@/configs/latex'
|
||||
import FormulaContent from './FormulaContent.vue'
|
||||
import SymbolContent from './SymbolContent.vue'
|
||||
|
||||
const tabs = [
|
||||
interface Tab {
|
||||
label: string;
|
||||
value: 'symbol' | 'formula';
|
||||
}
|
||||
|
||||
const tabs: Tab[] = [
|
||||
{ label: '常用符号', value: 'symbol' },
|
||||
{ label: '预置公式', value: 'formula' },
|
||||
]
|
||||
@ -93,7 +98,7 @@ export default defineComponent({
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const latex = ref('')
|
||||
const toolbarState = ref('symbol')
|
||||
const toolbarState = ref<'symbol' | 'formula'>('symbol')
|
||||
const textAreaRef = ref<HTMLTextAreaElement>()
|
||||
|
||||
const selectedSymbolKey = ref(SYMBOL_LIST[0].type)
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { ShapePoolItem } from '@/configs/shapes'
|
||||
import { LinePoolItem } from '@/configs/lines'
|
||||
import { ImageClipDataRange } from './slides'
|
||||
|
||||
export const enum ElementOrderCommands {
|
||||
export enum ElementOrderCommands {
|
||||
UP = 'up',
|
||||
DOWN = 'down',
|
||||
TOP = 'top',
|
||||
BOTTOM = 'bottom',
|
||||
}
|
||||
|
||||
export const enum ElementAlignCommands {
|
||||
export enum ElementAlignCommands {
|
||||
TOP = 'top',
|
||||
BOTTOM = 'bottom',
|
||||
LEFT = 'left',
|
||||
@ -62,13 +63,6 @@ export interface MultiSelectRange {
|
||||
maxY: number;
|
||||
}
|
||||
|
||||
export type ImageClipDataRange = [[number, number], [number, number]]
|
||||
|
||||
export interface ImageClipData {
|
||||
range: ImageClipDataRange;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ImageClipedEmitData {
|
||||
range: ImageClipDataRange;
|
||||
position: {
|
||||
|
@ -191,6 +191,8 @@ export interface ImageElementFilters {
|
||||
'opacity'?: string;
|
||||
}
|
||||
|
||||
export type ImageClipDataRange = [[number, number], [number, number]]
|
||||
|
||||
/**
|
||||
* 图片裁剪
|
||||
*
|
||||
@ -199,7 +201,7 @@ export interface ImageElementFilters {
|
||||
* shape: 裁剪形状,见 configs/imageClip.ts CLIPPATHS
|
||||
*/
|
||||
export interface ImageElementClip {
|
||||
range: [[number, number], [number, number]];
|
||||
range: ImageClipDataRange;
|
||||
shape: string;
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@
|
||||
:link="elementInfo.link"
|
||||
:openLinkDialog="openLinkDialog"
|
||||
v-if="isActive && elementInfo.link"
|
||||
@mousedown.stop
|
||||
@mousedown.stop=""
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -6,7 +6,7 @@ import { PPTElement } from '@/types/slides'
|
||||
|
||||
export default (
|
||||
elementList: Ref<PPTElement[]>,
|
||||
moveElement: (e: MouseEvent, element: PPTElement) => void,
|
||||
moveElement: (e: MouseEvent | TouchEvent, element: PPTElement) => void,
|
||||
) => {
|
||||
const mainStore = useMainStore()
|
||||
const { activeElementIdList, activeGroupElementId, handleElementId, editorAreaFocus } = storeToRefs(mainStore)
|
||||
@ -14,7 +14,7 @@ export default (
|
||||
|
||||
// 选中元素
|
||||
// startMove 表示是否需要再选中操作后进入到开始移动的状态
|
||||
const selectElement = (e: MouseEvent, element: PPTElement, startMove = true) => {
|
||||
const selectElement = (e: MouseEvent | TouchEvent, element: PPTElement, startMove = true) => {
|
||||
if (!editorAreaFocus.value) mainStore.setEditorareaFocus(true)
|
||||
|
||||
// 如果目标元素当前未被选中,则将他设为选中状态
|
||||
@ -69,8 +69,8 @@ export default (
|
||||
|
||||
// 如果目标元素已被选中,同时也是当前操作元素,那么当目标元素在该状态下再次被点击时,将被设置为多选元素中的激活成员
|
||||
else if (activeGroupElementId.value !== element.id) {
|
||||
const startPageX = e.pageX
|
||||
const startPageY = e.pageY
|
||||
const startPageX = e instanceof TouchEvent ? e.changedTouches[0].pageX : e.pageX
|
||||
const startPageY = e instanceof TouchEvent ? e.changedTouches[0].pageY : e.pageY
|
||||
|
||||
;(e.target as HTMLElement).onmouseup = (e: MouseEvent) => {
|
||||
const currentPageX = e.pageX
|
||||
|
@ -15,7 +15,7 @@
|
||||
@click="activeTab = tab.key"
|
||||
>{{tab.label}}</div>
|
||||
</div>
|
||||
<template v-for="key in Object.keys(animations)">
|
||||
<template v-for="key in animationTypes">
|
||||
<div :class="['animation-pool', key]" :key="key" v-if="activeTab === key">
|
||||
<div class="pool-type" :key="effect.name" v-for="effect in animations[key]">
|
||||
<div class="type-title">{{effect.name}}:</div>
|
||||
@ -87,7 +87,7 @@
|
||||
:max="3000"
|
||||
:step="500"
|
||||
:value="element.duration"
|
||||
@change="value => updateElementAnimationDuration(element.id, value)"
|
||||
@change="value => updateElementAnimationDuration(element.id, value as number)"
|
||||
style="flex: 5;"
|
||||
/>
|
||||
</div>
|
||||
@ -95,7 +95,7 @@
|
||||
<div style="flex: 3;">触发方式:</div>
|
||||
<Select
|
||||
:value="element.trigger"
|
||||
@change="value => updateElementAnimationTrigger(element.id, value)"
|
||||
@change="value => updateElementAnimationTrigger(element.id, value as 'click' | 'meantime' | 'auto')"
|
||||
style="flex: 5;"
|
||||
>
|
||||
<SelectOption value="click">主动触发</SelectOption>
|
||||
@ -155,6 +155,8 @@ interface TabItem {
|
||||
label: string;
|
||||
}
|
||||
|
||||
const animationTypes: AnimationType[] = ['in', 'out', 'attention']
|
||||
|
||||
export default defineComponent({
|
||||
name: 'element-animation-panel',
|
||||
components: {
|
||||
@ -341,6 +343,7 @@ export default defineComponent({
|
||||
attention: ATTENTION_ANIMATIONS,
|
||||
},
|
||||
prefix: ANIMATION_CLASS_PREFIX,
|
||||
animationTypes,
|
||||
addAnimation,
|
||||
deleteAnimation,
|
||||
handleDragEnd,
|
||||
|
@ -2,12 +2,12 @@
|
||||
<div class="element-positopn-panel">
|
||||
<div class="title">层级:</div>
|
||||
<ButtonGroup class="row">
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, 'top')"><IconSendToBack class="btn-icon" /> 置于顶层</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, 'bottom')"><IconBringToFrontOne class="btn-icon" /> 置于底层</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.TOP)"><IconSendToBack class="btn-icon" /> 置于顶层</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.BOTTOM)"><IconBringToFrontOne class="btn-icon" /> 置于底层</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="row">
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, 'up')"><IconBringToFront class="btn-icon" /> 上移一层</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, 'down')"><IconSentToBack class="btn-icon" /> 下移一层</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.UP)"><IconBringToFront class="btn-icon" /> 上移一层</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.DOWN)"><IconSentToBack class="btn-icon" /> 下移一层</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<Divider />
|
||||
@ -15,24 +15,24 @@
|
||||
<div class="title">对齐:</div>
|
||||
<ButtonGroup class="row">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="左对齐">
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('left')"><IconAlignLeft /></Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.LEFT)"><IconAlignLeft /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="水平居中">
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('horizontal')"><IconAlignVertically /></Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.HORIZONTAL)"><IconAlignVertically /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="右对齐">
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('right')"><IconAlignRight /></Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.RIGHT)"><IconAlignRight /></Button>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="row">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="上对齐">
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('top')"><IconAlignTop /></Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.TOP)"><IconAlignTop /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="垂直居中">
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('vertical')"><IconAlignHorizontally /></Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.VERTICAL)"><IconAlignHorizontally /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="下对齐">
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('bottom')"><IconAlignBottom /></Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.BOTTOM)"><IconAlignBottom /></Button>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
|
||||
@ -43,14 +43,14 @@
|
||||
<InputNumber
|
||||
:step="5"
|
||||
:value="left"
|
||||
@change="value => updateLeft(value)"
|
||||
@change="value => updateLeft(value as number)"
|
||||
style="flex: 4;"
|
||||
/>
|
||||
<div style="flex: 1;"></div>
|
||||
<InputNumber
|
||||
:step="5"
|
||||
:value="top"
|
||||
@change="value => updateTop(value)"
|
||||
@change="value => updateTop(value as number)"
|
||||
style="flex: 4;"
|
||||
/>
|
||||
</div>
|
||||
@ -69,7 +69,7 @@
|
||||
:max="1500"
|
||||
:step="5"
|
||||
:value="width"
|
||||
@change="value => updateWidth(value)"
|
||||
@change="value => updateWidth(value as number)"
|
||||
style="flex: 4;"
|
||||
/>
|
||||
<template v-if="['image', 'shape', 'audio'].includes(handleElement.type)">
|
||||
@ -87,7 +87,7 @@
|
||||
:step="5"
|
||||
:disabled="handleElement.type === 'text'"
|
||||
:value="height"
|
||||
@change="value => updateHeight(value)"
|
||||
@change="value => updateHeight(value as number)"
|
||||
style="flex: 4;"
|
||||
/>
|
||||
</div>
|
||||
@ -123,7 +123,7 @@
|
||||
:max="180"
|
||||
:step="5"
|
||||
:value="rotate"
|
||||
@change="value => updateRotate(value)"
|
||||
@change="value => updateRotate(value as number)"
|
||||
style="flex: 4;"
|
||||
/>
|
||||
</div>
|
||||
@ -132,10 +132,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watch } from 'vue'
|
||||
import { computed, defineComponent, Ref, ref, watch } from 'vue'
|
||||
import { round } from 'lodash'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { PPTElement } from '@/types/slides'
|
||||
import { ElementAlignCommands, ElementOrderCommands } from '@/types/edit'
|
||||
import { MIN_SIZE } from '@/configs/element'
|
||||
import useOrderElement from '@/hooks/useOrderElement'
|
||||
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
||||
@ -230,7 +232,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
handleElement,
|
||||
handleElement: handleElement as Ref<PPTElement>,
|
||||
orderElement,
|
||||
alignElementToCanvas,
|
||||
left,
|
||||
@ -247,6 +249,8 @@ export default defineComponent({
|
||||
updateRotate,
|
||||
updateFixedRatio,
|
||||
updateRotate45,
|
||||
ElementOrderCommands,
|
||||
ElementAlignCommands,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -18,7 +18,7 @@
|
||||
<div class="switch-wrapper" style="flex: 3;">
|
||||
<Switch
|
||||
:checked="handleElement.autoplay"
|
||||
@change="checked => updateAudio({ autoplay: checked })"
|
||||
@change="checked => updateAudio({ autoplay: checked as boolean })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -28,7 +28,7 @@
|
||||
<div class="switch-wrapper" style="flex: 3;">
|
||||
<Switch
|
||||
:checked="handleElement.loop"
|
||||
@change="checked => updateAudio({ loop: checked })"
|
||||
@change="checked => updateAudio({ loop: checked as boolean })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -36,7 +36,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent, Ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { PPTAudioElement } from '@/types/slides'
|
||||
@ -62,7 +62,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
handleElement,
|
||||
handleElement: handleElement as Ref<PPTAudioElement>,
|
||||
updateAudio,
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div style="flex: 2;">图例:</div>
|
||||
<Select style="flex: 3;" :value="legend" @change="value => updateLegend(value)">
|
||||
<Select style="flex: 3;" :value="legend" @change="value => updateLegend(value as '' | 'top' | 'bottom')">
|
||||
<SelectOption value="">不显示</SelectOption>
|
||||
<SelectOption value="top">显示在上方</SelectOption>
|
||||
<SelectOption value="bottom">显示在下方</SelectOption>
|
||||
@ -152,7 +152,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onUnmounted, ref, watch } from 'vue'
|
||||
import { defineComponent, onUnmounted, Ref, ref, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { ChartData, ChartOptions, PPTChartElement } from '@/types/slides'
|
||||
@ -197,7 +197,7 @@ export default defineComponent({
|
||||
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
const fill = ref<string>()
|
||||
const fill = ref<string>('#000')
|
||||
|
||||
const themeColor = ref<string[]>([])
|
||||
const gridColor = ref('')
|
||||
@ -313,7 +313,7 @@ export default defineComponent({
|
||||
chartDataEditorVisible,
|
||||
presetThemesVisible,
|
||||
presetThemeColorHoverIndex,
|
||||
handleElement,
|
||||
handleElement: handleElement as Ref<PPTChartElement>,
|
||||
updateData,
|
||||
fill,
|
||||
updateFill,
|
||||
|
@ -58,7 +58,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { defineComponent, Ref, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { PPTImageElement, SlideBackground } from '@/types/slides'
|
||||
@ -265,7 +265,7 @@ export default defineComponent({
|
||||
clipPanelVisible,
|
||||
shapeClipPathOptions,
|
||||
ratioClipOptions,
|
||||
handleElement,
|
||||
handleElement: handleElement as Ref<PPTImageElement>,
|
||||
clipImage,
|
||||
presetImageClip,
|
||||
replaceImage,
|
||||
|
@ -22,7 +22,7 @@
|
||||
:min="1"
|
||||
:max="3"
|
||||
:value="handleElement.strokeWidth"
|
||||
@change="value => updateLatex({ strokeWidth: value })"
|
||||
@change="value => updateLatex({ strokeWidth: value as number })"
|
||||
style="flex: 3;"
|
||||
/>
|
||||
</div>
|
||||
@ -44,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onUnmounted, ref } from 'vue'
|
||||
import { defineComponent, onUnmounted, Ref, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { PPTLatexElement } from '@/types/slides'
|
||||
@ -92,7 +92,7 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
handleElement,
|
||||
handleElement: handleElement as Ref<PPTLatexElement>,
|
||||
latexEditorVisible,
|
||||
updateLatex,
|
||||
updateLatexData,
|
||||
|
@ -5,7 +5,7 @@
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="handleElement.style"
|
||||
@change="value => updateLine({ style: value })"
|
||||
@change="value => updateLine({ style: value as 'solid' | 'dashed' })"
|
||||
>
|
||||
<SelectOption value="solid">实线</SelectOption>
|
||||
<SelectOption value="dashed">虚线</SelectOption>
|
||||
@ -27,7 +27,7 @@
|
||||
<div style="flex: 2;">线条宽度:</div>
|
||||
<InputNumber
|
||||
:value="handleElement.width"
|
||||
@change="value => updateLine({ width: value })"
|
||||
@change="value => updateLine({ width: value as number })"
|
||||
style="flex: 3;"
|
||||
/>
|
||||
</div>
|
||||
@ -37,7 +37,7 @@
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="handleElement.points[0]"
|
||||
@change="value => updateLine({ points: [value, handleElement.points[1]] })"
|
||||
@change="value => updateLine({ points: [value as 'arrow' | 'dot', handleElement.points[1]] })"
|
||||
>
|
||||
<SelectOption value="">无</SelectOption>
|
||||
<SelectOption value="arrow">箭头</SelectOption>
|
||||
@ -49,7 +49,7 @@
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="handleElement.points[1]"
|
||||
@change="value => updateLine({ points: [handleElement.points[0], value] })"
|
||||
@change="value => updateLine({ points: [handleElement.points[0], value as 'arrow' | 'dot'] })"
|
||||
>
|
||||
<SelectOption value="">无</SelectOption>
|
||||
<SelectOption value="arrow">箭头</SelectOption>
|
||||
@ -63,7 +63,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent, Ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { PPTLineElement } from '@/types/slides'
|
||||
@ -91,7 +91,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
handleElement,
|
||||
handleElement: handleElement as Ref<PPTLineElement>,
|
||||
updateLine,
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="outline.style"
|
||||
@change="value => updateOutline({ style: value })"
|
||||
@change="value => updateOutline({ style: value as 'solid' | 'dashed' })"
|
||||
>
|
||||
<SelectOption value="solid">实线边框</SelectOption>
|
||||
<SelectOption value="dashed">虚线边框</SelectOption>
|
||||
@ -35,14 +35,14 @@
|
||||
@update:modelValue="value => updateOutline({ color: value })"
|
||||
/>
|
||||
</template>
|
||||
<ColorButton :color="outline.color" style="flex: 3;" />
|
||||
<ColorButton :color="outline.color || '#000'" style="flex: 3;" />
|
||||
</Popover>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="flex: 2;">边框粗细:</div>
|
||||
<InputNumber
|
||||
:value="outline.width"
|
||||
@change="value => updateOutline({ width: value })"
|
||||
@change="value => updateOutline({ width: value as number })"
|
||||
style="flex: 3;"
|
||||
/>
|
||||
</div>
|
||||
@ -53,7 +53,7 @@
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="richTextAttrs.fontname"
|
||||
@change="value => updateFontStyle('fontname', value)"
|
||||
@change="value => updateFontStyle('fontname', value as string)"
|
||||
>
|
||||
<template #suffixIcon><IconFontSize /></template>
|
||||
<SelectOptGroup label="系统字体">
|
||||
@ -70,7 +70,7 @@
|
||||
<Select
|
||||
style="flex: 2;"
|
||||
:value="richTextAttrs.fontsize"
|
||||
@change="value => updateFontStyle('fontsize', value)"
|
||||
@change="value => updateFontStyle('fontsize', value as string)"
|
||||
>
|
||||
<template #suffixIcon><IconAddText /></template>
|
||||
<SelectOption v-for="fontsize in fontSizeOptions" :key="fontsize" :value="fontsize">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<Select
|
||||
style="flex: 10;"
|
||||
:value="fillType"
|
||||
@change="value => updateFillType(value)"
|
||||
@change="value => updateFillType(value as 'fill' | 'gradient')"
|
||||
>
|
||||
<SelectOption value="fill">纯色填充</SelectOption>
|
||||
<SelectOption value="gradient">渐变填充</SelectOption>
|
||||
@ -22,7 +22,7 @@
|
||||
<Select
|
||||
style="flex: 10;"
|
||||
:value="gradient.type"
|
||||
@change="value => updateGradient({ type: value })"
|
||||
@change="value => updateGradient({ type: value as 'linear' | 'radial' })"
|
||||
v-else
|
||||
>
|
||||
<SelectOption value="linear">线性渐变</SelectOption>
|
||||
@ -63,7 +63,7 @@
|
||||
:max="360"
|
||||
:step="15"
|
||||
:value="gradient.rotate"
|
||||
@change="value => updateGradient({ rotate: value })"
|
||||
@change="value => updateGradient({ rotate: value as number })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -76,7 +76,7 @@
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="richTextAttrs.fontname"
|
||||
@change="value => emitRichTextCommand('fontname', value)"
|
||||
@change="value => emitRichTextCommand('fontname', value as string)"
|
||||
>
|
||||
<template #suffixIcon><IconFontSize /></template>
|
||||
<SelectOptGroup label="系统字体">
|
||||
@ -93,7 +93,7 @@
|
||||
<Select
|
||||
style="flex: 2;"
|
||||
:value="richTextAttrs.fontsize"
|
||||
@change="value => emitRichTextCommand('fontsize', value)"
|
||||
@change="value => emitRichTextCommand('fontsize', value as string)"
|
||||
>
|
||||
<template #suffixIcon><IconAddText /></template>
|
||||
<SelectOption v-for="fontsize in fontSizeOptions" :key="fontsize" :value="fontsize">
|
||||
@ -223,7 +223,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from 'vue'
|
||||
import { defineComponent, Ref, ref, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { PPTShapeElement, ShapeGradient, ShapeText } from '@/types/slides'
|
||||
@ -253,8 +253,12 @@ export default defineComponent({
|
||||
const slidesStore = useSlidesStore()
|
||||
const { handleElement, handleElementId, richTextAttrs, availableFonts } = storeToRefs(mainStore)
|
||||
|
||||
const fill = ref<string>()
|
||||
const gradient = ref<ShapeGradient>()
|
||||
const fill = ref<string>('#000')
|
||||
const gradient = ref<ShapeGradient>({
|
||||
type: 'linear',
|
||||
rotate: 0,
|
||||
color: ['#fff', '#fff'],
|
||||
})
|
||||
const fillType = ref('fill')
|
||||
const textAlign = ref('middle')
|
||||
|
||||
@ -327,7 +331,7 @@ export default defineComponent({
|
||||
availableFonts,
|
||||
fontSizeOptions,
|
||||
webFonts,
|
||||
handleElement,
|
||||
handleElement: handleElement as Ref<PPTShapeElement>,
|
||||
emitRichTextCommand,
|
||||
updateFillType,
|
||||
updateFill,
|
||||
|
@ -4,7 +4,7 @@
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="textAttrs.fontname"
|
||||
@change="value => updateTextAttrs({ fontname: value })"
|
||||
@change="value => updateTextAttrs({ fontname: value as string })"
|
||||
>
|
||||
<template #suffixIcon><IconFontSize /></template>
|
||||
<SelectOptGroup label="系统字体">
|
||||
@ -21,7 +21,7 @@
|
||||
<Select
|
||||
style="flex: 2;"
|
||||
:value="textAttrs.fontsize"
|
||||
@change="value => updateTextAttrs({ fontsize: value })"
|
||||
@change="value => updateTextAttrs({ fontsize: value as string })"
|
||||
>
|
||||
<template #suffixIcon><IconAddText /></template>
|
||||
<SelectOption v-for="fontsize in fontSizeOptions" :key="fontsize" :value="fontsize">
|
||||
@ -139,12 +139,12 @@
|
||||
<div class="switch-wrapper" style="flex: 3;">
|
||||
<Switch
|
||||
:checked="hasTheme"
|
||||
@change="checked => toggleTheme(checked)"
|
||||
@change="checked => toggleTheme(checked as boolean)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="hasTheme">
|
||||
<template v-if="theme">
|
||||
<div class="row">
|
||||
<Checkbox
|
||||
@change="e => updateTheme({ rowHeader: e.target.checked })"
|
||||
@ -400,7 +400,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
handleElement,
|
||||
availableFonts,
|
||||
fontSizeOptions,
|
||||
textAttrs,
|
||||
|
@ -16,7 +16,7 @@
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="richTextAttrs.fontname"
|
||||
@change="value => emitRichTextCommand('fontname', value)"
|
||||
@change="value => emitRichTextCommand('fontname', value as string)"
|
||||
>
|
||||
<template #suffixIcon><IconFontSize /></template>
|
||||
<SelectOptGroup label="系统字体">
|
||||
@ -33,7 +33,7 @@
|
||||
<Select
|
||||
style="flex: 2;"
|
||||
:value="richTextAttrs.fontsize"
|
||||
@change="value => emitRichTextCommand('fontsize', value)"
|
||||
@change="value => emitRichTextCommand('fontsize', value as string)"
|
||||
>
|
||||
<template #suffixIcon><IconAddText /></template>
|
||||
<SelectOption v-for="fontsize in fontSizeOptions" :key="fontsize" :value="fontsize">
|
||||
@ -222,28 +222,28 @@
|
||||
|
||||
<div class="row">
|
||||
<div style="flex: 2;">行间距:</div>
|
||||
<Select style="flex: 3;" :value="lineHeight" @change="value => updateLineHeight(value)">
|
||||
<Select style="flex: 3;" :value="lineHeight" @change="value => updateLineHeight(value as number)">
|
||||
<template #suffixIcon><IconRowHeight /></template>
|
||||
<SelectOption v-for="item in lineHeightOptions" :key="item" :value="item">{{item}}倍</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="flex: 2;">段间距:</div>
|
||||
<Select style="flex: 3;" :value="paragraphSpace" @change="value => updateParagraphSpace(value)">
|
||||
<Select style="flex: 3;" :value="paragraphSpace" @change="value => updateParagraphSpace(value as number)">
|
||||
<template #suffixIcon><IconVerticalSpacingBetweenItems /></template>
|
||||
<SelectOption v-for="item in paragraphSpaceOptions" :key="item" :value="item">{{item}}px</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="flex: 2;">字间距:</div>
|
||||
<Select style="flex: 3;" :value="wordSpace" @change="value => updateWordSpace(value)">
|
||||
<Select style="flex: 3;" :value="wordSpace" @change="value => updateWordSpace(value as number)">
|
||||
<template #suffixIcon><IconFullwidth /></template>
|
||||
<SelectOption v-for="item in wordSpaceOptions" :key="item" :value="item">{{item}}px</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="flex: 2;">首行缩进:</div>
|
||||
<Select style="flex: 3;" :value="textIndent" @change="value => updateTextIndent(value)">
|
||||
<Select style="flex: 3;" :value="textIndent" @change="value => updateTextIndent(value as number)">
|
||||
<template #suffixIcon><IconIndentRight /></template>
|
||||
<SelectOption v-for="item in textIndentOptions" :key="item" :value="item">{{item}}px</SelectOption>
|
||||
</Select>
|
||||
@ -379,7 +379,7 @@ export default defineComponent({
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
const fill = ref<string>()
|
||||
const fill = ref<string>('#000')
|
||||
const lineHeight = ref<number>()
|
||||
const wordSpace = ref<number>()
|
||||
const textIndent = ref<number>()
|
||||
@ -450,7 +450,7 @@ export default defineComponent({
|
||||
link.value = richTextAttrs.value.link
|
||||
linkPopoverVisible.value = true
|
||||
}
|
||||
const updateLink = (link: string) => {
|
||||
const updateLink = (link?: string) => {
|
||||
if (link) {
|
||||
const linkRegExp = /^(https?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/
|
||||
if (!linkRegExp.test(link)) return message.error('不是正确的网页链接地址')
|
||||
|
@ -2,24 +2,24 @@
|
||||
<div class="multi-position-panel">
|
||||
<ButtonGroup class="row">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="左对齐">
|
||||
<Button style="flex: 1;" @click="alignElement('left')"><IconAlignLeft /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement(ElementAlignCommands.LEFT)"><IconAlignLeft /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="水平居中">
|
||||
<Button style="flex: 1;" @click="alignElement('horizontal')"><IconAlignHorizontally /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement(ElementAlignCommands.HORIZONTAL)"><IconAlignHorizontally /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="右对齐">
|
||||
<Button style="flex: 1;" @click="alignElement('right')"><IconAlignRight /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement(ElementAlignCommands.RIGHT)"><IconAlignRight /></Button>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="row">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="上对齐">
|
||||
<Button style="flex: 1;" @click="alignElement('top')"><IconAlignTop /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement(ElementAlignCommands.TOP)"><IconAlignTop /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="垂直居中">
|
||||
<Button style="flex: 1;" @click="alignElement('vertical')"><IconAlignVertically /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement(ElementAlignCommands.VERTICAL)"><IconAlignVertically /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="下对齐">
|
||||
<Button style="flex: 1;" @click="alignElement('bottom')"><IconAlignBottom /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement(ElementAlignCommands.BOTTOM)"><IconAlignBottom /></Button>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="row" v-if="displayItemCount > 2">
|
||||
@ -68,6 +68,7 @@ export default defineComponent({
|
||||
uniformHorizontalDisplay,
|
||||
uniformVerticalDisplay,
|
||||
alignElement,
|
||||
ElementAlignCommands,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -5,7 +5,7 @@
|
||||
<Select
|
||||
style="flex: 10;"
|
||||
:value="background.type"
|
||||
@change="value => updateBackgroundType(value)"
|
||||
@change="value => updateBackgroundType(value as 'gradient' | 'image' | 'solid')"
|
||||
>
|
||||
<SelectOption value="solid">纯色填充</SelectOption>
|
||||
<SelectOption value="image">图片填充</SelectOption>
|
||||
@ -26,7 +26,7 @@
|
||||
<Select
|
||||
style="flex: 10;"
|
||||
:value="background.imageSize || 'cover'"
|
||||
@change="value => updateBackground({ imageSize: value })"
|
||||
@change="value => updateBackground({ imageSize: value as 'repeat' | 'cover' | 'contain' })"
|
||||
v-else-if="background.type === 'image'"
|
||||
>
|
||||
<SelectOption value="contain">缩放</SelectOption>
|
||||
@ -37,7 +37,7 @@
|
||||
<Select
|
||||
style="flex: 10;"
|
||||
:value="background.gradientType"
|
||||
@change="value => updateBackground({ gradientType: value })"
|
||||
@change="value => updateBackground({ gradientType: value as 'linear' | 'radial' })"
|
||||
v-else
|
||||
>
|
||||
<SelectOption value="linear">线性渐变</SelectOption>
|
||||
@ -61,11 +61,11 @@
|
||||
<Popover trigger="click">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="background.gradientColor[0]"
|
||||
@update:modelValue="value => updateBackground({ gradientColor: [value, background.gradientColor[1]] })"
|
||||
:modelValue="background.gradientColor![0]"
|
||||
@update:modelValue="value => updateBackground({ gradientColor: [value, background.gradientColor![1]] })"
|
||||
/>
|
||||
</template>
|
||||
<ColorButton :color="background.gradientColor[0]" style="flex: 3;" />
|
||||
<ColorButton :color="background.gradientColor![0]" style="flex: 3;" />
|
||||
</Popover>
|
||||
</div>
|
||||
<div class="row">
|
||||
@ -73,11 +73,11 @@
|
||||
<Popover trigger="click">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="background.gradientColor[1]"
|
||||
@update:modelValue="value => updateBackground({ gradientColor: [background.gradientColor[0], value] })"
|
||||
:modelValue="background.gradientColor![1]"
|
||||
@update:modelValue="value => updateBackground({ gradientColor: [background.gradientColor![0], value] })"
|
||||
/>
|
||||
</template>
|
||||
<ColorButton :color="background.gradientColor[1]" style="flex: 3;" />
|
||||
<ColorButton :color="background.gradientColor![1]" style="flex: 3;" />
|
||||
</Popover>
|
||||
</div>
|
||||
<div class="row" v-if="background.gradientType === 'linear'">
|
||||
@ -88,7 +88,7 @@
|
||||
:max="360"
|
||||
:step="15"
|
||||
:value="background.gradientRotate"
|
||||
@change="value => updateBackground({ gradientRotate: value })"
|
||||
@change="value => updateBackground({ gradientRotate: value as number })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -99,7 +99,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div style="flex: 2;">画布尺寸:</div>
|
||||
<Select style="flex: 3;" :value="viewportRatio" @change="value => updateViewportRatio(value)">
|
||||
<Select style="flex: 3;" :value="viewportRatio" @change="value => updateViewportRatio(value as number)">
|
||||
<SelectOption :value="0.5625">宽屏 16 : 9</SelectOption>
|
||||
<SelectOption :value="0.625">宽屏 16 :10</SelectOption>
|
||||
<SelectOption :value="0.75">标准 4 :3</SelectOption>
|
||||
@ -114,7 +114,7 @@
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="theme.fontName"
|
||||
@change="value => updateTheme({ fontName: value })"
|
||||
@change="value => updateTheme({ fontName: value as string })"
|
||||
>
|
||||
<SelectOptGroup label="系统字体">
|
||||
<SelectOption v-for="font in availableFonts" :key="font.value" :value="font.value">
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div class="switch-wrapper" style="flex: 3;">
|
||||
<Switch
|
||||
:checked="hasFilters"
|
||||
@change="checked => toggleFilters(checked)"
|
||||
@change="checked => toggleFilters(checked as boolean)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -18,7 +18,7 @@
|
||||
:min="0"
|
||||
:step="filter.step"
|
||||
:value="filter.value"
|
||||
@change="value => updateFilter(filter, value)"
|
||||
@change="value => updateFilter(filter, value as number)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,7 +8,7 @@
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
:value="opacity"
|
||||
@change="value => updateOpacity(value)"
|
||||
@change="value => updateOpacity(value as number)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,17 +5,17 @@
|
||||
<div class="switch-wrapper" style="flex: 3;">
|
||||
<Switch
|
||||
:checked="hasOutline"
|
||||
@change="checked => toggleOutline(checked)"
|
||||
@change="checked => toggleOutline(checked as boolean)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="hasOutline">
|
||||
<template v-if="hasOutline && outline">
|
||||
<div class="row">
|
||||
<div style="flex: 2;">边框样式:</div>
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="outline.style"
|
||||
@change="value => updateOutline({ style: value })"
|
||||
@change="value => updateOutline({ style: value as 'dashed' | 'solid' })"
|
||||
>
|
||||
<SelectOption value="solid">实线边框</SelectOption>
|
||||
<SelectOption value="dashed">虚线边框</SelectOption>
|
||||
@ -30,14 +30,14 @@
|
||||
@update:modelValue="value => updateOutline({ color: value })"
|
||||
/>
|
||||
</template>
|
||||
<ColorButton :color="outline.color" style="flex: 3;" />
|
||||
<ColorButton :color="outline.color || '#000'" style="flex: 3;" />
|
||||
</Popover>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="flex: 2;">边框粗细:</div>
|
||||
<InputNumber
|
||||
:value="outline.width"
|
||||
@change="value => updateOutline({ width: value })"
|
||||
@change="value => updateOutline({ width: value as number })"
|
||||
style="flex: 3;"
|
||||
/>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="row">
|
||||
<div style="flex: 2;">启用阴影:</div>
|
||||
<div class="switch-wrapper" style="flex: 3;">
|
||||
<Switch :checked="hasShadow" @change="checked => toggleShadow(checked)" />
|
||||
<Switch :checked="hasShadow" @change="checked => toggleShadow(checked as boolean)" />
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="hasShadow && shadow">
|
||||
@ -15,7 +15,7 @@
|
||||
:max="10"
|
||||
:step="1"
|
||||
:value="shadow.h"
|
||||
@change="value => updateShadow({ h: value })"
|
||||
@change="value => updateShadow({ h: value as number })"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
@ -26,7 +26,7 @@
|
||||
:max="10"
|
||||
:step="1"
|
||||
:value="shadow.v"
|
||||
@change="value => updateShadow({ v: value })"
|
||||
@change="value => updateShadow({ v: value as number })"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
@ -37,7 +37,7 @@
|
||||
:max="20"
|
||||
:step="1"
|
||||
:value="shadow.blur"
|
||||
@change="value => updateShadow({ blur: value })"
|
||||
@change="value => updateShadow({ blur: value as number })"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
@ -96,23 +96,23 @@
|
||||
<Divider style="margin: 20px 0;" />
|
||||
|
||||
<ButtonGroup class="row">
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, 'top')"><IconSendToBack class="icon" /> 置顶</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, 'bottom')"><IconBringToFrontOne class="icon" /> 置底</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, 'up')"><IconBringToFront class="icon" /> 上移</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, 'down')"><IconSentToBack class="icon" /> 下移</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.TOP)"><IconSendToBack class="icon" /> 置顶</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.BOTTOM)"><IconBringToFrontOne class="icon" /> 置底</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.UP)"><IconBringToFront class="icon" /> 上移</Button>
|
||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.DOWN)"><IconSentToBack class="icon" /> 下移</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<Divider style="margin: 20px 0;" />
|
||||
|
||||
<ButtonGroup class="row">
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('left')"><IconAlignLeft class="icon" /> 左对齐</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('horizontal')"><IconAlignVertically class="icon" /> 水平居中</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('right')"><IconAlignRight class="icon" /> 右对齐</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.LEFT)"><IconAlignLeft class="icon" /> 左对齐</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.HORIZONTAL)"><IconAlignVertically class="icon" /> 水平居中</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.RIGHT)"><IconAlignRight class="icon" /> 右对齐</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="row">
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('top')"><IconAlignTop class="icon" /> 上对齐</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('vertical')"><IconAlignHorizontally class="icon" /> 垂直居中</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas('bottom')"><IconAlignBottom class="icon" /> 下对齐</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.TOP)"><IconAlignTop class="icon" /> 上对齐</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.VERTICAL)"><IconAlignHorizontally class="icon" /> 垂直居中</Button>
|
||||
<Button style="flex: 1;" @click="alignElementToCanvas(ElementAlignCommands.BOTTOM)"><IconAlignBottom class="icon" /> 下对齐</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
@ -120,10 +120,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { defineComponent, Ref, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { PPTElement, TableCell } from '@/types/slides'
|
||||
import { ElementAlignCommands, ElementOrderCommands } from '@/types/edit'
|
||||
import emitter, { EmitterEvents } from '@/utils/emitter'
|
||||
import useOrderElement from '@/hooks/useOrderElement'
|
||||
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
||||
@ -215,7 +216,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
handleElement,
|
||||
handleElement: handleElement as Ref<PPTElement>,
|
||||
tabs,
|
||||
activeTab,
|
||||
richTextAttrs,
|
||||
@ -227,6 +228,8 @@ export default defineComponent({
|
||||
emitRichTextCommand,
|
||||
updateFontColor,
|
||||
updateFill,
|
||||
ElementOrderCommands,
|
||||
ElementAlignCommands,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -40,7 +40,7 @@ export default defineComponent({
|
||||
required: true,
|
||||
},
|
||||
selectElement: {
|
||||
type: Function as PropType<(e: MouseEvent | TouchEvent, element: PPTElement, canMove?: boolean) => void>,
|
||||
type: Function as PropType<(e: TouchEvent, element: PPTElement, canMove?: boolean) => void>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
@ -5,10 +5,10 @@
|
||||
top: elementInfo.top * canvasScale + 'px',
|
||||
left: elementInfo.left * canvasScale + 'px',
|
||||
transform: `rotate(${rotate}deg)`,
|
||||
transformOrigin: `${elementInfo.width * canvasScale / 2}px ${height * canvasScale / 2}px`,
|
||||
transformOrigin: `${elementInfo.width * canvasScale / 2}px ${elementInfo.height * canvasScale / 2}px`,
|
||||
}"
|
||||
>
|
||||
<template v-if="isSelected && elementInfo.type !== 'line'">
|
||||
<template v-if="isSelected">
|
||||
<BorderLine
|
||||
class="operate-border-line"
|
||||
v-for="line in borderLines"
|
||||
@ -32,7 +32,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue'
|
||||
import { PPTElement, PPTLineElement } from '@/types/slides'
|
||||
import { getElementRange } from '@/utils/element'
|
||||
import useCommonOperate from '@/views/Editor/Canvas/hooks/useCommonOperate'
|
||||
import { OperateResizeHandlers } from '@/types/edit'
|
||||
|
||||
@ -47,7 +46,7 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
type: Object as PropType<PPTElement>,
|
||||
type: Object as PropType<Exclude<PPTElement, PPTLineElement>>,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
@ -65,23 +64,9 @@ export default defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
const rotate = computed(() => 'rotate' in props.elementInfo ? props.elementInfo.rotate : 0)
|
||||
const width = computed(() => {
|
||||
if (props.elementInfo.type === 'line') {
|
||||
const { minX, maxX } = getElementRange(props.elementInfo)
|
||||
return maxX - minX
|
||||
}
|
||||
return props.elementInfo.width
|
||||
})
|
||||
const height = computed(() => {
|
||||
if (props.elementInfo.type === 'line') {
|
||||
const { minY, maxY } = getElementRange(props.elementInfo)
|
||||
return maxY - minY
|
||||
}
|
||||
return props.elementInfo.height
|
||||
})
|
||||
|
||||
const scaleWidth = computed(() => width.value * props.canvasScale)
|
||||
const scaleHeight = computed(() => height.value * props.canvasScale)
|
||||
const scaleWidth = computed(() => props.elementInfo.width * props.canvasScale)
|
||||
const scaleHeight = computed(() => props.elementInfo.height * props.canvasScale)
|
||||
const {
|
||||
borderLines,
|
||||
resizeHandlers,
|
||||
@ -90,8 +75,6 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
rotate,
|
||||
width,
|
||||
height,
|
||||
borderLines,
|
||||
resizeHandlers: props.elementInfo.type === 'text' || props.elementInfo.type === 'table' ? textElementResizeHandlers : resizeHandlers,
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
<ButtonGroup class="row">
|
||||
<Button style="flex: 1;" @click="insertTextElement()"><IconFontSize class="icon" /> 文字</Button>
|
||||
<FileInput @change="files => insertImageElement(files)">
|
||||
<Button style="flex: 1;" @click="insertImageElement()"><IconPicture class="icon" /> 图片</Button>
|
||||
<Button style="flex: 1;"><IconPicture class="icon" /> 图片</Button>
|
||||
</FileInput>
|
||||
<Button style="flex: 1;" @click="insertShapeElement('square')"><IconSquare class="icon" /> 矩形</Button>
|
||||
<Button style="flex: 1;" @click="insertShapeElement('round')"><IconRound class="icon" /> 圆形</Button>
|
||||
|
@ -13,14 +13,15 @@
|
||||
:length="line.length"
|
||||
:canvasScale="canvasScale"
|
||||
/>
|
||||
<MobileOperate
|
||||
v-for="element in elementList"
|
||||
:key="element.id"
|
||||
:elementInfo="element"
|
||||
:isSelected="activeElementIdList.includes(element.id)"
|
||||
:canvasScale="canvasScale"
|
||||
:scaleElement="scaleElement"
|
||||
/>
|
||||
<template v-for="element in elementList" :key="element.id">
|
||||
<MobileOperate
|
||||
v-if="element.type !== 'line'"
|
||||
:elementInfo="element"
|
||||
:isSelected="activeElementIdList.includes(element.id)"
|
||||
:canvasScale="canvasScale"
|
||||
:scaleElement="scaleElement"
|
||||
/>
|
||||
</template>
|
||||
<div class="viewport" :style="{ transform: `scale(${canvasScale})` }">
|
||||
<MobileEditableElement
|
||||
v-for="(element, index) in elementList"
|
||||
|
@ -34,8 +34,8 @@
|
||||
</div>
|
||||
<div
|
||||
class="volume-bar-wrap"
|
||||
@mousedown="$event => handleMousedownVolumeBar($event)"
|
||||
@touchstart="$event => handleMousedownVolumeBar($event)"
|
||||
@mousedown="handleMousedownVolumeBar()"
|
||||
@touchstart="handleMousedownVolumeBar()"
|
||||
@click="$event => handleClickVolumeBar($event)"
|
||||
>
|
||||
<div class="volume-bar" ref="volumeBarRef">
|
||||
@ -54,8 +54,8 @@
|
||||
<div
|
||||
class="bar-wrap"
|
||||
ref="playBarWrap"
|
||||
@mousedown="$event => handleMousedownPlayBar($event)"
|
||||
@touchstart="$event => handleMousedownPlayBar($event)"
|
||||
@mousedown="handleMousedownPlayBar()"
|
||||
@touchstart="handleMousedownPlayBar()"
|
||||
@mousemove="$event => handleMousemovePlayBar($event)"
|
||||
@mouseenter="playBarTimeVisible = true"
|
||||
@mouseleave="playBarTimeVisible = false"
|
||||
|
@ -32,7 +32,7 @@
|
||||
:src="elementInfo.src"
|
||||
:loop="elementInfo.loop"
|
||||
:scale="canvasScale"
|
||||
@mousedown.stop
|
||||
@mousedown.stop=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -98,7 +98,7 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
const handleSelectElement = (e: MouseEvent | TouchEvent) => {
|
||||
if (props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
|
@ -72,7 +72,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
const handleSelectElement = (e: MouseEvent | TouchEvent) => {
|
||||
if (props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
>
|
||||
<div
|
||||
:class="['clip-point', point, rotateClassName]"
|
||||
v-for="point in ['left-top', 'right-top', 'left-bottom', 'right-bottom']"
|
||||
v-for="point in cornerPoint"
|
||||
:key="point"
|
||||
@mousedown.stop="$event => scaleClipRange($event, point)"
|
||||
>
|
||||
@ -49,7 +49,7 @@
|
||||
</div>
|
||||
<div
|
||||
:class="['clip-point', point, rotateClassName]"
|
||||
v-for="point in ['top', 'bottom', 'left', 'right']"
|
||||
v-for="point in edgePoints"
|
||||
:key="point"
|
||||
@mousedown.stop="$event => scaleClipRange($event, point)"
|
||||
>
|
||||
@ -70,7 +70,8 @@ import { computed, defineComponent, onMounted, onUnmounted, PropType, ref } from
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useKeyboardStore } from '@/store'
|
||||
import { KEYS } from '@/configs/hotkey'
|
||||
import { ImageClipData, ImageClipDataRange, ImageClipedEmitData, OperateResizeHandlers } from '@/types/edit'
|
||||
import { ImageClipedEmitData, OperateResizeHandlers } from '@/types/edit'
|
||||
import { ImageClipDataRange, ImageElementClip } from '@/types/slides'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'image-clip-handler',
|
||||
@ -81,7 +82,7 @@ export default defineComponent({
|
||||
required: true,
|
||||
},
|
||||
clipData: {
|
||||
type: Object as PropType<ImageClipData>,
|
||||
type: Object as PropType<ImageElementClip>,
|
||||
},
|
||||
clipPath: {
|
||||
type: String,
|
||||
@ -527,12 +528,27 @@ export default defineComponent({
|
||||
return prefix + 0
|
||||
})
|
||||
|
||||
const cornerPoint = [
|
||||
OperateResizeHandlers.LEFT_TOP,
|
||||
OperateResizeHandlers.RIGHT_TOP,
|
||||
OperateResizeHandlers.LEFT_BOTTOM,
|
||||
OperateResizeHandlers.RIGHT_BOTTOM,
|
||||
]
|
||||
const edgePoints = [
|
||||
OperateResizeHandlers.TOP,
|
||||
OperateResizeHandlers.BOTTOM,
|
||||
OperateResizeHandlers.LEFT,
|
||||
OperateResizeHandlers.RIGHT,
|
||||
]
|
||||
|
||||
return {
|
||||
clipWrapperPositionStyle,
|
||||
bottomImgPositionStyle,
|
||||
topImgWrapperPositionStyle,
|
||||
topImgPositionStyle,
|
||||
rotateClassName,
|
||||
edgePoints,
|
||||
cornerPoint,
|
||||
handleClip,
|
||||
moveClipRange,
|
||||
scaleClipRange,
|
||||
|
@ -10,7 +10,6 @@
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin
|
||||
fill="transparent"
|
||||
:cx="width / 2"
|
||||
:cy="height / 2"
|
||||
|
@ -10,7 +10,6 @@
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin
|
||||
fill="transparent"
|
||||
:d="createPath(width, height)"
|
||||
:stroke="outlineColor"
|
||||
|
@ -10,7 +10,6 @@
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin
|
||||
fill="transparent"
|
||||
:rx="radius"
|
||||
:ry="radius"
|
||||
|
@ -115,7 +115,7 @@ export default defineComponent({
|
||||
const filters = computed(() => props.elementInfo.filters)
|
||||
const { filter } = useFilter(filters)
|
||||
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
const handleSelectElement = (e: MouseEvent | TouchEvent) => {
|
||||
if (props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
props.selectElement(e, props.elementInfo)
|
||||
|
@ -63,7 +63,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
const handleSelectElement = (e: MouseEvent | TouchEvent) => {
|
||||
if (props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
|
@ -39,9 +39,6 @@
|
||||
:stroke-width="elementInfo.width"
|
||||
:stroke-dasharray="lineDashArray"
|
||||
fill="none"
|
||||
stroke-linecap
|
||||
stroke-linejoin
|
||||
stroke-miterlimit
|
||||
:marker-start="elementInfo.points[0] ? `url(#${elementInfo.id}-${elementInfo.points[0]}-start)` : ''"
|
||||
:marker-end="elementInfo.points[1] ? `url(#${elementInfo.id}-${elementInfo.points[1]}-end)` : ''"
|
||||
></path>
|
||||
|
@ -43,9 +43,6 @@
|
||||
:stroke-width="elementInfo.width"
|
||||
:stroke-dasharray="lineDashArray"
|
||||
fill="none"
|
||||
stroke-linecap
|
||||
stroke-linejoin
|
||||
stroke-miterlimit
|
||||
:marker-start="elementInfo.points[0] ? `url(#${elementInfo.id}-${elementInfo.points[0]}-start)` : ''"
|
||||
:marker-end="elementInfo.points[1] ? `url(#${elementInfo.id}-${elementInfo.points[1]}-end)` : ''"
|
||||
></path>
|
||||
@ -90,7 +87,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
const handleSelectElement = (e: MouseEvent | TouchEvent) => {
|
||||
if (props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
|
@ -43,7 +43,6 @@
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin=""
|
||||
:d="elementInfo.path"
|
||||
:fill="elementInfo.gradient ? `url(#base-gradient-${elementInfo.id})` : elementInfo.fill"
|
||||
:stroke="outlineColor"
|
||||
|
@ -49,7 +49,6 @@
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin=""
|
||||
:d="elementInfo.path"
|
||||
:fill="elementInfo.gradient ? `url(#editabel-gradient-${elementInfo.id})` : elementInfo.fill"
|
||||
:stroke="outlineColor"
|
||||
@ -119,7 +118,7 @@ export default defineComponent({
|
||||
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
const handleSelectElement = (e: MouseEvent, canMove = true) => {
|
||||
const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
|
||||
if (props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
|
@ -2,10 +2,9 @@
|
||||
<div
|
||||
class="custom-textarea"
|
||||
ref="textareaRef"
|
||||
:contenteditable="contenteditable"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@input="$event => handleInput($event)"
|
||||
@input="handleInput()"
|
||||
v-html="text"
|
||||
></div>
|
||||
</template>
|
||||
@ -102,5 +101,7 @@ export default defineComponent({
|
||||
.custom-textarea {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
-webkit-user-modify: read-write-plaintext-only;
|
||||
-moz-user-modify: read-write-plaintext-only;
|
||||
}
|
||||
</style>
|
@ -47,13 +47,12 @@
|
||||
v-show="!hideCells.includes(`${rowIndex}_${colIndex}`)"
|
||||
@mousedown="$event => handleCellMousedown($event, rowIndex, colIndex)"
|
||||
@mouseenter="handleCellMouseenter(rowIndex, colIndex)"
|
||||
v-contextmenu="el => contextmenus(el)"
|
||||
v-contextmenu="(el: HTMLElement) => contextmenus(el)"
|
||||
>
|
||||
<CustomTextarea
|
||||
v-if="activedCell === `${rowIndex}_${colIndex}`"
|
||||
class="cell-text"
|
||||
:class="{ 'active': activedCell === `${rowIndex}_${colIndex}` }"
|
||||
contenteditable="plaintext-only"
|
||||
:value="cell.text"
|
||||
@updateValue="value => handleInput(value, rowIndex, colIndex)"
|
||||
@insertExcelData="value => insertExcelData(value, rowIndex, colIndex)"
|
||||
|
@ -81,7 +81,7 @@ export default defineComponent({
|
||||
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
const handleSelectElement = (e: MouseEvent | TouchEvent) => {
|
||||
if (props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { CSSProperties } from 'vue'
|
||||
import { TableCellStyle } from '@/types/slides'
|
||||
|
||||
/**
|
||||
* 计算单元格文本样式
|
||||
* @param style 单元格文本样式原数据
|
||||
*/
|
||||
export const getTextStyle = (style?: TableCellStyle) => {
|
||||
export const getTextStyle = (style?: TableCellStyle): CSSProperties => {
|
||||
if (!style) return {}
|
||||
const {
|
||||
bold,
|
||||
|
@ -30,10 +30,7 @@
|
||||
/>
|
||||
<div
|
||||
class="text ProseMirror-static"
|
||||
:style="{
|
||||
'--textIndent': `${elementInfo.textIndent || 0}px`,
|
||||
'--paragraphSpace': `${elementInfo.paragraphSpace === undefined ? 5 : elementInfo.paragraphSpace}px`,
|
||||
}"
|
||||
:style="cssVar"
|
||||
v-html="elementInfo.content"
|
||||
></div>
|
||||
</div>
|
||||
@ -42,7 +39,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue'
|
||||
import { defineComponent, PropType, computed, StyleValue } from 'vue'
|
||||
import { PPTTextElement } from '@/types/slides'
|
||||
import ElementOutline from '@/views/components/element/ElementOutline.vue'
|
||||
|
||||
@ -63,8 +60,14 @@ export default defineComponent({
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
const cssVar = computed(() => ({
|
||||
'--textIndent': `${props.elementInfo.textIndent || 0}px`,
|
||||
'--paragraphSpace': `${props.elementInfo.paragraphSpace === undefined ? 5 : props.elementInfo.paragraphSpace}px`,
|
||||
} as StyleValue))
|
||||
|
||||
return {
|
||||
shadowStyle,
|
||||
cssVar,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -101,7 +101,7 @@ export default defineComponent({
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
const handleSelectElement = (e: MouseEvent, canMove = true) => {
|
||||
const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
|
||||
if (props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
|
@ -53,8 +53,8 @@
|
||||
</div>
|
||||
<div
|
||||
class="volume-bar-wrap"
|
||||
@mousedown="$event => handleMousedownVolumeBar($event)"
|
||||
@touchstart="$event => handleMousedownVolumeBar($event)"
|
||||
@mousedown="handleMousedownVolumeBar()"
|
||||
@touchstart="handleMousedownVolumeBar()"
|
||||
@click="$event => handleClickVolumeBar($event)"
|
||||
>
|
||||
<div class="volume-bar" ref="volumeBarRef">
|
||||
@ -94,8 +94,8 @@
|
||||
<div
|
||||
class="bar-wrap"
|
||||
ref="playBarWrap"
|
||||
@mousedown="$event => handleMousedownPlayBar($event)"
|
||||
@touchstart="$event => handleMousedownPlayBar($event)"
|
||||
@mousedown="handleMousedownPlayBar()"
|
||||
@touchstart="handleMousedownPlayBar()"
|
||||
@mousemove="$event => handleMousemovePlayBar($event)"
|
||||
@mouseenter="playBarTimeVisible = true"
|
||||
@mouseleave="playBarTimeVisible = false"
|
||||
|
@ -67,7 +67,7 @@ export default defineComponent({
|
||||
setup(props) {
|
||||
const { canvasScale } = storeToRefs(useMainStore())
|
||||
|
||||
const handleSelectElement = (e: MouseEvent, canMove = true) => {
|
||||
const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
|
||||
if (props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user