perf: 线条、边框样式配置项优化

This commit is contained in:
pipipi-pikachu 2024-12-12 22:23:07 +08:00
parent c767934b69
commit bc6a51e5c7
9 changed files with 338 additions and 71 deletions

View File

@ -0,0 +1,120 @@
<template>
<div class="select-wrap" v-if="disabled">
<div class="select disabled" ref="selectRef">
<div class="selector"><slot name="label"></slot></div>
<div class="icon">
<slot name="icon">
<IconDown :size="14" />
</slot>
</div>
</div>
</div>
<Popover
class="select-wrap"
trigger="click"
v-model:value="popoverVisible"
placement="bottom"
:contentStyle="{
padding: 0,
boxShadow: '0 6px 16px 0 rgba(0, 0, 0, 0.08)',
}"
v-else
>
<template #content>
<div class="options" :style="{ width: width + 2 + 'px' }" @click="popoverVisible = false">
<slot name="options"></slot>
</div>
</template>
<div class="select" ref="selectRef">
<div class="selector"><slot name="label"></slot></div>
<div class="icon">
<slot name="icon">
<IconDown :size="14" />
</slot>
</div>
</div>
</Popover>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue'
import Popover from './Popover.vue'
withDefaults(defineProps<{
disabled?: boolean
}>(), {
disabled: false,
})
const popoverVisible = ref(false)
const selectRef = ref<HTMLElement>()
const width = ref(0)
const updateWidth = () => {
if (!selectRef.value) return
width.value = selectRef.value.clientWidth
}
const resizeObserver = new ResizeObserver(updateWidth)
onMounted(() => {
if (!selectRef.value) return
resizeObserver.observe(selectRef.value)
})
onUnmounted(() => {
if (!selectRef.value) return
resizeObserver.unobserve(selectRef.value)
})
</script>
<style lang="scss" scoped>
.select {
width: 100%;
height: 32px;
padding-right: 32px;
border-radius: $borderRadius;
transition: border-color .25s;
font-size: 13px;
user-select: none;
background-color: #fff;
border: 1px solid #d9d9d9;
position: relative;
cursor: pointer;
&:not(.disabled):hover {
border-color: $themeColor;
}
&.disabled {
background-color: #f5f5f5;
border-color: #dcdcdc;
color: #b7b7b7;
cursor: default;
}
.selector {
min-width: 50px;
height: 30px;
line-height: 30px;
padding-left: 10px;
@include ellipsis-oneline();
}
}
.options {
max-height: 260px;
padding: 5px;
overflow: auto;
text-align: left;
font-size: 13px;
user-select: none;
}
.icon {
width: 32px;
height: 30px;
color: #bfbfbf;
position: absolute;
top: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -1,9 +1,9 @@
import type { LinePoint } from '@/types/slides' import type { LinePoint, LineStyleType } from '@/types/slides'
export interface LinePoolItem { export interface LinePoolItem {
path: string path: string
style: 'solid' | 'dashed' style: LineStyleType
points: [LinePoint, LinePoint] points: [LinePoint, LinePoint]
isBroken?: boolean isBroken?: boolean
isBroken2?: boolean isBroken2?: boolean

View File

@ -52,6 +52,8 @@ export interface Gradient {
rotate: number rotate: number
} }
export type LineStyleType = 'solid' | 'dashed' | 'dotted'
/** /**
* *
* *
@ -80,7 +82,7 @@ export interface PPTElementShadow {
* color?: 边框颜色 * color?: 边框颜色
*/ */
export interface PPTElementOutline { export interface PPTElementOutline {
style?: 'dashed' | 'solid' | 'dotted' style?: LineStyleType
width?: number width?: number
color?: string color?: string
} }
@ -390,7 +392,7 @@ export interface PPTLineElement extends Omit<PPTBaseElement, 'height' | 'rotate'
type: 'line' type: 'line'
start: [number, number] start: [number, number]
end: [number, number] end: [number, number]
style: 'solid' | 'dashed' | 'dotted' style: LineStyleType
color: string color: string
points: [LinePoint, LinePoint] points: [LinePoint, LinePoint]
shadow?: PPTElementShadow shadow?: PPTElementShadow

View File

@ -2,16 +2,16 @@
<div class="line-style-panel"> <div class="line-style-panel">
<div class="row"> <div class="row">
<div style="width: 40%;">线条样式</div> <div style="width: 40%;">线条样式</div>
<Select <SelectCustom style="width: 60%;">
style="width: 60%;" <template #options>
:value="handleLineElement.style" <div class="option" v-for="item in lineStyleOptions" :key="item" @click="updateLine({ style: item })">
@update:value="value => updateLine({ style: value as 'solid' | 'dashed' })" <SVGLine :type="item" />
:options="[ </div>
{ label: '实线', value: 'solid' }, </template>
{ label: '虚线', value: 'dashed' }, <template #label>
{ label: '点线', value: 'dotted' }, <SVGLine :type="handleLineElement.style" />
]" </template>
/> </SelectCustom>
</div> </div>
<div class="row"> <div class="row">
<div style="width: 40%;">线条颜色</div> <div style="width: 40%;">线条颜色</div>
@ -36,29 +36,29 @@
<div class="row"> <div class="row">
<div style="width: 40%;">起点样式</div> <div style="width: 40%;">起点样式</div>
<Select <SelectCustom style="width: 60%;">
style="width: 60%;" <template #options>
:value="handleLineElement.points[0]" <div class="option" v-for="item in lineMarkerOptions" :key="item" @click="updateLine({ points: [item, handleLineElement.points[1]] })">
@update:value="value => updateLine({ points: [value as 'arrow' | 'dot', handleLineElement.points[1]] })" <SVGLine :padding="5" :markers="[item, '']" />
:options="[ </div>
{ label: '无', value: '' }, </template>
{ label: '箭头', value: 'arrow' }, <template #label>
{ label: '圆点', value: 'dot' }, <SVGLine :padding="5" :markers="[handleLineElement.points[0], '']" />
]" </template>
/> </SelectCustom>
</div> </div>
<div class="row"> <div class="row">
<div style="width: 40%;">终点样式</div> <div style="width: 40%;">终点样式</div>
<Select <SelectCustom style="width: 60%;">
style="width: 60%;" <template #options>
:value="handleLineElement.points[1]" <div class="option" v-for="item in lineMarkerOptions" :key="item" @click="updateLine({ points: [handleLineElement.points[0], item] })">
@update:value="value => updateLine({ points: [handleLineElement.points[0], value as 'arrow' | 'dot'] })" <SVGLine :padding="5" :markers="['', item]" />
:options="[ </div>
{ label: '无', value: '' }, </template>
{ label: '箭头', value: 'arrow' }, <template #label>
{ label: '圆点', value: 'dot' }, <SVGLine :padding="5" :markers="['', handleLineElement.points[1]]" />
]" </template>
/> </SelectCustom>
</div> </div>
<Divider /> <Divider />
@ -73,19 +73,20 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Ref } from 'vue' import { type Ref, ref } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore } from '@/store' import { useMainStore, useSlidesStore } from '@/store'
import type { PPTLineElement } from '@/types/slides' import type { LinePoint, LineStyleType, PPTLineElement } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import ElementShadow from '../common/ElementShadow.vue' import ElementShadow from '../common/ElementShadow.vue'
import SVGLine from '../common/SVGLine.vue'
import Button from '@/components/Button.vue' import Button from '@/components/Button.vue'
import ColorButton from '@/components/ColorButton.vue' import ColorButton from '@/components/ColorButton.vue'
import ColorPicker from '@/components/ColorPicker/index.vue' import ColorPicker from '@/components/ColorPicker/index.vue'
import Divider from '@/components/Divider.vue' import Divider from '@/components/Divider.vue'
import NumberInput from '@/components/NumberInput.vue' import NumberInput from '@/components/NumberInput.vue'
import Select from '@/components/Select.vue' import SelectCustom from '@/components/SelectCustom.vue'
import Popover from '@/components/Popover.vue' import Popover from '@/components/Popover.vue'
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
@ -95,6 +96,9 @@ const handleLineElement = handleElement as Ref<PPTLineElement>
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
const lineStyleOptions = ref<LineStyleType[]>(['solid', 'dashed', 'dotted'])
const lineMarkerOptions = ref<LinePoint[]>(['', 'arrow', 'dot'])
const updateLine = (props: Partial<PPTLineElement>) => { const updateLine = (props: Partial<PPTLineElement>) => {
if (!handleElement.value) return if (!handleElement.value) return
slidesStore.updateElement({ id: handleElement.value.id, props }) slidesStore.updateElement({ id: handleElement.value.id, props })
@ -135,4 +139,19 @@ const updateLine = (props: Partial<PPTLineElement>) => {
margin-top: 10px; margin-top: 10px;
} }
} }
.option {
height: 32px;
padding: 0 5px;
border-radius: $borderRadius;
&:not(.selected):hover {
background-color: rgba($color: $themeColor, $alpha: .05);
cursor: pointer;
}
&.selected {
color: $themeColor;
font-weight: 700;
}
}
</style> </style>

View File

@ -17,16 +17,16 @@
<div class="row"> <div class="row">
<div style="width: 40%;">边框样式</div> <div style="width: 40%;">边框样式</div>
<Select <SelectCustom style="width: 60%;">
style="width: 60%;" <template #options>
:value="outline.style || ''" <div class="option" v-for="item in lineStyleOptions" :key="item" @click="updateOutline({ style: item })">
@update:value="value => updateOutline({ style: value as 'solid' | 'dashed' | 'dotted' })" <SVGLine :type="item" />
:options="[ </div>
{ label: '实线边框', value: 'solid' }, </template>
{ label: '虚线边框', value: 'dashed' }, <template #label>
{ label: '点线边框', value: 'dotted' }, <SVGLine :type="outline.style" />
]" </template>
/> </SelectCustom>
</div> </div>
<div class="row"> <div class="row">
<div style="width: 40%;">边框颜色</div> <div style="width: 40%;">边框颜色</div>
@ -137,11 +137,12 @@
import { ref } from 'vue' import { ref } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore } from '@/store' import { useMainStore, useSlidesStore } from '@/store'
import type { PPTElement, PPTElementOutline, TableCell } from '@/types/slides' import type { LineStyleType, PPTElement, PPTElementOutline, TableCell } from '@/types/slides'
import emitter, { EmitterEvents } from '@/utils/emitter' import emitter, { EmitterEvents } from '@/utils/emitter'
import { WEB_FONTS } from '@/configs/font' import { WEB_FONTS } from '@/configs/font'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import SVGLine from '../common/SVGLine.vue'
import ColorButton from '@/components/ColorButton.vue' import ColorButton from '@/components/ColorButton.vue'
import TextColorButton from '@/components/TextColorButton.vue' import TextColorButton from '@/components/TextColorButton.vue'
import ColorPicker from '@/components/ColorPicker/index.vue' import ColorPicker from '@/components/ColorPicker/index.vue'
@ -153,6 +154,7 @@ import RadioGroup from '@/components/RadioGroup.vue'
import NumberInput from '@/components/NumberInput.vue' import NumberInput from '@/components/NumberInput.vue'
import Select from '@/components/Select.vue' import Select from '@/components/Select.vue'
import SelectGroup from '@/components/SelectGroup.vue' import SelectGroup from '@/components/SelectGroup.vue'
import SelectCustom from '@/components/SelectCustom.vue'
import Popover from '@/components/Popover.vue' import Popover from '@/components/Popover.vue'
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
@ -165,6 +167,7 @@ const updateElement = (id: string, props: Partial<PPTElement>) => {
addHistorySnapshot() addHistorySnapshot()
} }
const lineStyleOptions = ref<LineStyleType[]>(['solid', 'dashed', 'dotted'])
const fontSizeOptions = [ const fontSizeOptions = [
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px', '12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px', '36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
@ -257,4 +260,19 @@ const updateFontStyle = (command: string, value: string) => {
.font-size-btn { .font-size-btn {
padding: 0; padding: 0;
} }
.option {
height: 32px;
padding: 0 5px;
border-radius: $borderRadius;
&:not(.selected):hover {
background-color: rgba($color: $themeColor, $alpha: .05);
cursor: pointer;
}
&.selected {
color: $themeColor;
font-weight: 700;
}
}
</style> </style>

View File

@ -177,16 +177,16 @@
<template v-if="moreThemeConfigsVisible"> <template v-if="moreThemeConfigsVisible">
<div class="row"> <div class="row">
<div style="width: 40%;">边框样式</div> <div style="width: 40%;">边框样式</div>
<Select <SelectCustom style="width: 60%;">
style="width: 60%;" <template #options>
:value="theme.outline.style || ''" <div class="option" v-for="item in lineStyleOptions" :key="item" @click="updateTheme({ outline: { ...theme.outline, style: item } })">
@update:value="value => updateTheme({ outline: { ...theme.outline, style: value as 'dashed' | 'solid' | 'dotted' } })" <SVGLine :type="item" />
:options="[ </div>
{ label: '实线边框', value: 'solid' }, </template>
{ label: '虚线边框', value: 'dashed' }, <template #label>
{ label: '点线边框', value: 'dotted' }, <SVGLine :type="theme.outline.style" />
]" </template>
/> </SelectCustom>
</div> </div>
<div class="row"> <div class="row">
<div style="width: 40%;">边框颜色</div> <div style="width: 40%;">边框颜色</div>
@ -312,6 +312,7 @@ import type {
SlideTheme, SlideTheme,
SlideBackgroundImage, SlideBackgroundImage,
SlideBackgroundImageSize, SlideBackgroundImageSize,
LineStyleType,
} from '@/types/slides' } from '@/types/slides'
import { PRESET_THEMES } from '@/configs/theme' import { PRESET_THEMES } from '@/configs/theme'
import { WEB_FONTS } from '@/configs/font' import { WEB_FONTS } from '@/configs/font'
@ -320,6 +321,7 @@ import useSlideTheme from '@/hooks/useSlideTheme'
import { getImageDataURL } from '@/utils/image' import { getImageDataURL } from '@/utils/image'
import ThemeStylesExtract from './ThemeStylesExtract.vue' import ThemeStylesExtract from './ThemeStylesExtract.vue'
import SVGLine from './common/SVGLine.vue'
import ColorButton from '@/components/ColorButton.vue' import ColorButton from '@/components/ColorButton.vue'
import FileInput from '@/components/FileInput.vue' import FileInput from '@/components/FileInput.vue'
import ColorPicker from '@/components/ColorPicker/index.vue' import ColorPicker from '@/components/ColorPicker/index.vue'
@ -328,6 +330,7 @@ import Slider from '@/components/Slider.vue'
import Button from '@/components/Button.vue' import Button from '@/components/Button.vue'
import Select from '@/components/Select.vue' import Select from '@/components/Select.vue'
import Popover from '@/components/Popover.vue' import Popover from '@/components/Popover.vue'
import SelectCustom from '@/components/SelectCustom.vue'
import NumberInput from '@/components/NumberInput.vue' import NumberInput from '@/components/NumberInput.vue'
import Modal from '@/components/Modal.vue' import Modal from '@/components/Modal.vue'
import GradientBar from '@/components/GradientBar.vue' import GradientBar from '@/components/GradientBar.vue'
@ -339,6 +342,7 @@ const { slides, currentSlide, viewportRatio, theme } = storeToRefs(slidesStore)
const moreThemeConfigsVisible = ref(false) const moreThemeConfigsVisible = ref(false)
const themeStylesExtractVisible = ref(false) const themeStylesExtractVisible = ref(false)
const currentGradientIndex = ref(0) const currentGradientIndex = ref(0)
const lineStyleOptions = ref<LineStyleType[]>(['solid', 'dashed', 'dotted'])
const background = computed(() => { const background = computed(() => {
if (!currentSlide.value.background) { if (!currentSlide.value.background) {
@ -554,4 +558,19 @@ const updateViewportRatio = (value: number) => {
transition: opacity $transitionDelay; transition: opacity $transitionDelay;
} }
} }
.option {
height: 32px;
padding: 0 5px;
border-radius: $borderRadius;
&:not(.selected):hover {
background-color: rgba($color: $themeColor, $alpha: .05);
cursor: pointer;
}
&.selected {
color: $themeColor;
font-weight: 700;
}
}
</style> </style>

View File

@ -12,16 +12,16 @@
<template v-if="hasOutline && outline"> <template v-if="hasOutline && outline">
<div class="row"> <div class="row">
<div style="width: 40%;">边框样式</div> <div style="width: 40%;">边框样式</div>
<Select <SelectCustom style="width: 60%;">
style="width: 60%;" <template #options>
:value="outline.style || ''" <div class="option" v-for="item in lineStyleOptions" :key="item" @click="updateOutline({ style: item })">
@update:value="value => updateOutline({ style: value as 'dashed' | 'solid' | 'dotted' })" <SVGLine :type="item" />
:options="[ </div>
{ label: '实线边框', value: 'solid' }, </template>
{ label: '虚线边框', value: 'dashed' }, <template #label>
{ label: '点线边框', value: 'dotted' }, <SVGLine :type="outline.style" />
]" </template>
/> </SelectCustom>
</div> </div>
<div class="row"> <div class="row">
<div style="width: 40%;">边框颜色</div> <div style="width: 40%;">边框颜色</div>
@ -51,14 +51,15 @@
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore } from '@/store' import { useMainStore, useSlidesStore } from '@/store'
import type { PPTElementOutline } from '@/types/slides' import type { LineStyleType, PPTElementOutline } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import SVGLine from '../common/SVGLine.vue'
import ColorButton from '@/components/ColorButton.vue' import ColorButton from '@/components/ColorButton.vue'
import ColorPicker from '@/components/ColorPicker/index.vue' import ColorPicker from '@/components/ColorPicker/index.vue'
import Switch from '@/components/Switch.vue' import Switch from '@/components/Switch.vue'
import NumberInput from '@/components/NumberInput.vue' import NumberInput from '@/components/NumberInput.vue'
import Select from '@/components/Select.vue' import SelectCustom from '@/components/SelectCustom.vue'
import Popover from '@/components/Popover.vue' import Popover from '@/components/Popover.vue'
withDefaults(defineProps<{ withDefaults(defineProps<{
@ -73,6 +74,7 @@ const { handleElement } = storeToRefs(useMainStore())
const outline = ref<PPTElementOutline>() const outline = ref<PPTElementOutline>()
const hasOutline = ref(false) const hasOutline = ref(false)
const lineStyleOptions = ref<LineStyleType[]>(['solid', 'dashed', 'dotted'])
watch(handleElement, () => { watch(handleElement, () => {
if (!handleElement.value) return if (!handleElement.value) return
@ -113,4 +115,19 @@ const toggleOutline = (checked: boolean) => {
.switch-wrapper { .switch-wrapper {
text-align: right; text-align: right;
} }
.option {
height: 32px;
padding: 0 5px;
border-radius: $borderRadius;
&:not(.selected):hover {
background-color: rgba($color: $themeColor, $alpha: .05);
cursor: pointer;
}
&.selected {
color: $themeColor;
font-weight: 700;
}
}
</style> </style>

View File

@ -0,0 +1,69 @@
<template>
<svg width="100%" height="100%" viewBox="0 0 100 10">
<defs>
<LinePointMarker
v-if="markers && markers[0]"
:id="id"
position="start"
:type="markers[0]"
:color="color"
:baseSize="width"
/>
<LinePointMarker
v-if="markers && markers[1]"
:id="id"
position="end"
:type="markers[1]"
:color="color"
:baseSize="width"
/>
</defs>
<line
:x1="padding"
:y1="5"
:x2="100 - padding"
:y2="5"
:stroke="color"
:stroke-width="width"
:stroke-dasharray="lineDashArray"
:marker-start="markers && markers[0] ? `url(#${id}-${markers[0]}-start)` : ''"
:marker-end="markers && markers[1] ? `url(#${id}-${markers[1]}-end)` : ''"
/>
</svg>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import { nanoid } from 'nanoid'
import type { LinePoint, LineStyleType } from '@/types/slides'
import LinePointMarker from '@/views/components/element/LineElement/LinePointMarker.vue'
const props = withDefaults(defineProps<{
width?: number
color?: string
markers?: [LinePoint, LinePoint]
type?: LineStyleType
padding?: number
}>(), {
width: 2,
color: '#333',
padding: 0
})
const id = ref('')
onMounted(() => {
id.value = nanoid()
})
const lineDashArray = computed(() => {
const size = props.width
if (props.type === 'dashed') return size <= 8 ? `${size * 5} ${size * 2.5}` : `${size * 5} ${size * 1.5}`
if (props.type === 'dotted') return size <= 8 ? `${size * 1.8} ${size * 1.6}` : `${size * 1.5} ${size * 1.2}`
return '0 0'
})
</script>
<style lang="scss" scoped>
</style>

View File

@ -18,11 +18,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import type { LinePoint } from '@/types/slides'
type NonEmptyLinePoint = Exclude<LinePoint, ''>
const props = defineProps<{ const props = defineProps<{
id: string id: string
position: 'start' | 'end' position: 'start' | 'end'
type: 'dot' | 'arrow' type: NonEmptyLinePoint
baseSize: number baseSize: number
color?: string color?: string
}>() }>()