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 {
path: string
style: 'solid' | 'dashed'
style: LineStyleType
points: [LinePoint, LinePoint]
isBroken?: boolean
isBroken2?: boolean

View File

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

View File

@ -2,16 +2,16 @@
<div class="line-style-panel">
<div class="row">
<div style="width: 40%;">线条样式</div>
<Select
style="width: 60%;"
:value="handleLineElement.style"
@update:value="value => updateLine({ style: value as 'solid' | 'dashed' })"
:options="[
{ label: '实线', value: 'solid' },
{ label: '虚线', value: 'dashed' },
{ label: '点线', value: 'dotted' },
]"
/>
<SelectCustom style="width: 60%;">
<template #options>
<div class="option" v-for="item in lineStyleOptions" :key="item" @click="updateLine({ style: item })">
<SVGLine :type="item" />
</div>
</template>
<template #label>
<SVGLine :type="handleLineElement.style" />
</template>
</SelectCustom>
</div>
<div class="row">
<div style="width: 40%;">线条颜色</div>
@ -36,29 +36,29 @@
<div class="row">
<div style="width: 40%;">起点样式</div>
<Select
style="width: 60%;"
:value="handleLineElement.points[0]"
@update:value="value => updateLine({ points: [value as 'arrow' | 'dot', handleLineElement.points[1]] })"
:options="[
{ label: '无', value: '' },
{ label: '箭头', value: 'arrow' },
{ label: '圆点', value: 'dot' },
]"
/>
<SelectCustom style="width: 60%;">
<template #options>
<div class="option" v-for="item in lineMarkerOptions" :key="item" @click="updateLine({ points: [item, handleLineElement.points[1]] })">
<SVGLine :padding="5" :markers="[item, '']" />
</div>
</template>
<template #label>
<SVGLine :padding="5" :markers="[handleLineElement.points[0], '']" />
</template>
</SelectCustom>
</div>
<div class="row">
<div style="width: 40%;">终点样式</div>
<Select
style="width: 60%;"
:value="handleLineElement.points[1]"
@update:value="value => updateLine({ points: [handleLineElement.points[0], value as 'arrow' | 'dot'] })"
:options="[
{ label: '无', value: '' },
{ label: '箭头', value: 'arrow' },
{ label: '圆点', value: 'dot' },
]"
/>
<SelectCustom style="width: 60%;">
<template #options>
<div class="option" v-for="item in lineMarkerOptions" :key="item" @click="updateLine({ points: [handleLineElement.points[0], item] })">
<SVGLine :padding="5" :markers="['', item]" />
</div>
</template>
<template #label>
<SVGLine :padding="5" :markers="['', handleLineElement.points[1]]" />
</template>
</SelectCustom>
</div>
<Divider />
@ -73,19 +73,20 @@
</template>
<script lang="ts" setup>
import type { Ref } from 'vue'
import { type Ref, ref } from 'vue'
import { storeToRefs } from 'pinia'
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 ElementShadow from '../common/ElementShadow.vue'
import SVGLine from '../common/SVGLine.vue'
import Button from '@/components/Button.vue'
import ColorButton from '@/components/ColorButton.vue'
import ColorPicker from '@/components/ColorPicker/index.vue'
import Divider from '@/components/Divider.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'
const slidesStore = useSlidesStore()
@ -95,6 +96,9 @@ const handleLineElement = handleElement as Ref<PPTLineElement>
const { addHistorySnapshot } = useHistorySnapshot()
const lineStyleOptions = ref<LineStyleType[]>(['solid', 'dashed', 'dotted'])
const lineMarkerOptions = ref<LinePoint[]>(['', 'arrow', 'dot'])
const updateLine = (props: Partial<PPTLineElement>) => {
if (!handleElement.value) return
slidesStore.updateElement({ id: handleElement.value.id, props })
@ -135,4 +139,19 @@ const updateLine = (props: Partial<PPTLineElement>) => {
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>

View File

@ -17,16 +17,16 @@
<div class="row">
<div style="width: 40%;">边框样式</div>
<Select
style="width: 60%;"
:value="outline.style || ''"
@update:value="value => updateOutline({ style: value as 'solid' | 'dashed' | 'dotted' })"
:options="[
{ label: '实线边框', value: 'solid' },
{ label: '虚线边框', value: 'dashed' },
{ label: '点线边框', value: 'dotted' },
]"
/>
<SelectCustom style="width: 60%;">
<template #options>
<div class="option" v-for="item in lineStyleOptions" :key="item" @click="updateOutline({ style: item })">
<SVGLine :type="item" />
</div>
</template>
<template #label>
<SVGLine :type="outline.style" />
</template>
</SelectCustom>
</div>
<div class="row">
<div style="width: 40%;">边框颜色</div>
@ -137,11 +137,12 @@
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
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 { WEB_FONTS } from '@/configs/font'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import SVGLine from '../common/SVGLine.vue'
import ColorButton from '@/components/ColorButton.vue'
import TextColorButton from '@/components/TextColorButton.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 Select from '@/components/Select.vue'
import SelectGroup from '@/components/SelectGroup.vue'
import SelectCustom from '@/components/SelectCustom.vue'
import Popover from '@/components/Popover.vue'
const slidesStore = useSlidesStore()
@ -165,6 +167,7 @@ const updateElement = (id: string, props: Partial<PPTElement>) => {
addHistorySnapshot()
}
const lineStyleOptions = ref<LineStyleType[]>(['solid', 'dashed', 'dotted'])
const fontSizeOptions = [
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
@ -257,4 +260,19 @@ const updateFontStyle = (command: string, value: string) => {
.font-size-btn {
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>

View File

@ -177,16 +177,16 @@
<template v-if="moreThemeConfigsVisible">
<div class="row">
<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' },
]"
/>
<SelectCustom style="width: 60%;">
<template #options>
<div class="option" v-for="item in lineStyleOptions" :key="item" @click="updateTheme({ outline: { ...theme.outline, style: item } })">
<SVGLine :type="item" />
</div>
</template>
<template #label>
<SVGLine :type="theme.outline.style" />
</template>
</SelectCustom>
</div>
<div class="row">
<div style="width: 40%;">边框颜色</div>
@ -312,6 +312,7 @@ import type {
SlideTheme,
SlideBackgroundImage,
SlideBackgroundImageSize,
LineStyleType,
} from '@/types/slides'
import { PRESET_THEMES } from '@/configs/theme'
import { WEB_FONTS } from '@/configs/font'
@ -320,6 +321,7 @@ import useSlideTheme from '@/hooks/useSlideTheme'
import { getImageDataURL } from '@/utils/image'
import ThemeStylesExtract from './ThemeStylesExtract.vue'
import SVGLine from './common/SVGLine.vue'
import ColorButton from '@/components/ColorButton.vue'
import FileInput from '@/components/FileInput.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 Select from '@/components/Select.vue'
import Popover from '@/components/Popover.vue'
import SelectCustom from '@/components/SelectCustom.vue'
import NumberInput from '@/components/NumberInput.vue'
import Modal from '@/components/Modal.vue'
import GradientBar from '@/components/GradientBar.vue'
@ -339,6 +342,7 @@ const { slides, currentSlide, viewportRatio, theme } = storeToRefs(slidesStore)
const moreThemeConfigsVisible = ref(false)
const themeStylesExtractVisible = ref(false)
const currentGradientIndex = ref(0)
const lineStyleOptions = ref<LineStyleType[]>(['solid', 'dashed', 'dotted'])
const background = computed(() => {
if (!currentSlide.value.background) {
@ -554,4 +558,19 @@ const updateViewportRatio = (value: number) => {
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>

View File

@ -12,16 +12,16 @@
<template v-if="hasOutline && outline">
<div class="row">
<div style="width: 40%;">边框样式</div>
<Select
style="width: 60%;"
:value="outline.style || ''"
@update:value="value => updateOutline({ style: value as 'dashed' | 'solid' | 'dotted' })"
:options="[
{ label: '实线边框', value: 'solid' },
{ label: '虚线边框', value: 'dashed' },
{ label: '点线边框', value: 'dotted' },
]"
/>
<SelectCustom style="width: 60%;">
<template #options>
<div class="option" v-for="item in lineStyleOptions" :key="item" @click="updateOutline({ style: item })">
<SVGLine :type="item" />
</div>
</template>
<template #label>
<SVGLine :type="outline.style" />
</template>
</SelectCustom>
</div>
<div class="row">
<div style="width: 40%;">边框颜色</div>
@ -51,14 +51,15 @@
import { ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore } from '@/store'
import type { PPTElementOutline } from '@/types/slides'
import type { LineStyleType, PPTElementOutline } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import SVGLine from '../common/SVGLine.vue'
import ColorButton from '@/components/ColorButton.vue'
import ColorPicker from '@/components/ColorPicker/index.vue'
import Switch from '@/components/Switch.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'
withDefaults(defineProps<{
@ -73,6 +74,7 @@ const { handleElement } = storeToRefs(useMainStore())
const outline = ref<PPTElementOutline>()
const hasOutline = ref(false)
const lineStyleOptions = ref<LineStyleType[]>(['solid', 'dashed', 'dotted'])
watch(handleElement, () => {
if (!handleElement.value) return
@ -113,4 +115,19 @@ const toggleOutline = (checked: boolean) => {
.switch-wrapper {
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>

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>
import { computed } from 'vue'
import type { LinePoint } from '@/types/slides'
type NonEmptyLinePoint = Exclude<LinePoint, ''>
const props = defineProps<{
id: string
position: 'start' | 'end'
type: 'dot' | 'arrow'
type: NonEmptyLinePoint
baseSize: number
color?: string
}>()