mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
perf: UI 优化
This commit is contained in:
parent
c190eb3e88
commit
f704f4ad70
@ -5,6 +5,8 @@
|
|||||||
:style="{ backgroundImage: `url(${handleElement.src})` }"
|
:style="{ backgroundImage: `url(${handleElement.src})` }"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
<ElementFlip />
|
||||||
|
|
||||||
<ButtonGroup class="row">
|
<ButtonGroup class="row">
|
||||||
<Button style="flex: 5;" @click="clipImage()"><IconTailoring class="btn-icon" /> 裁剪图片</Button>
|
<Button style="flex: 5;" @click="clipImage()"><IconTailoring class="btn-icon" /> 裁剪图片</Button>
|
||||||
<Popover trigger="click" v-model:visible="clipPanelVisible">
|
<Popover trigger="click" v-model:visible="clipPanelVisible">
|
||||||
@ -39,27 +41,8 @@
|
|||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
<Popover trigger="click">
|
<Divider />
|
||||||
<template #content>
|
<ElementFilter />
|
||||||
<div class="filter">
|
|
||||||
<div class="filter-item" v-for="filter in filterOptions" :key="filter.key">
|
|
||||||
<div class="name">{{filter.label}}</div>
|
|
||||||
<Slider
|
|
||||||
class="filter-slider"
|
|
||||||
:max="filter.max"
|
|
||||||
:min="filter.min"
|
|
||||||
:step="filter.step"
|
|
||||||
:value="filter.value"
|
|
||||||
@change="value => updateFilter(filter, value)"
|
|
||||||
/>
|
|
||||||
<div class="value">{{filter.value}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<Button class="full-width-btn"><IconColorFilter class="btn-icon" /> 设置滤镜</Button>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<ElementFlip />
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<ElementOutline />
|
<ElementOutline />
|
||||||
<Divider />
|
<Divider />
|
||||||
@ -75,7 +58,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, watch } from 'vue'
|
import { defineComponent, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTImageElement, SlideBackground } from '@/types/slides'
|
import { PPTImageElement, SlideBackground } from '@/types/slides'
|
||||||
@ -86,26 +69,7 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
import ElementOutline from '../common/ElementOutline.vue'
|
import ElementOutline from '../common/ElementOutline.vue'
|
||||||
import ElementShadow from '../common/ElementShadow.vue'
|
import ElementShadow from '../common/ElementShadow.vue'
|
||||||
import ElementFlip from '../common/ElementFlip.vue'
|
import ElementFlip from '../common/ElementFlip.vue'
|
||||||
|
import ElementFilter from '../common/ElementFilter.vue'
|
||||||
interface FilterOption {
|
|
||||||
label: string;
|
|
||||||
key: string;
|
|
||||||
default: number;
|
|
||||||
value: number;
|
|
||||||
unit: string;
|
|
||||||
max: number;
|
|
||||||
step: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultFilters: FilterOption[] = [
|
|
||||||
{ label: '模糊', key: 'blur', default: 0, value: 0, unit: 'px', max: 10, step: 1 },
|
|
||||||
{ label: '亮度', key: 'brightness', default: 100, value: 100, unit: '%', max: 200, step: 5 },
|
|
||||||
{ label: '对比度', key: 'contrast', default: 100, value: 100, unit: '%', max: 200, step: 5 },
|
|
||||||
{ label: '灰度', key: 'grayscale', default: 0, value: 0, unit: '%', max: 100, step: 5 },
|
|
||||||
{ label: '饱和度', key: 'saturate', default: 100, value: 100, unit: '%', max: 200, step: 5 },
|
|
||||||
{ label: '色相', key: 'hue-rotate', default: 0, value: 0, unit: 'deg', max: 360, step: 10 },
|
|
||||||
{ label: '不透明度', key: 'opacity', default: 100, value: 100, unit: '%', max: 100, step: 5 },
|
|
||||||
]
|
|
||||||
|
|
||||||
const shapeClipPathOptions = CLIPPATHS
|
const shapeClipPathOptions = CLIPPATHS
|
||||||
const ratioClipOptions = [
|
const ratioClipOptions = [
|
||||||
@ -147,6 +111,7 @@ export default defineComponent({
|
|||||||
ElementOutline,
|
ElementOutline,
|
||||||
ElementShadow,
|
ElementShadow,
|
||||||
ElementFlip,
|
ElementFlip,
|
||||||
|
ElementFilter,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const mainStore = useMainStore()
|
const mainStore = useMainStore()
|
||||||
@ -156,33 +121,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
const clipPanelVisible = ref(false)
|
const clipPanelVisible = ref(false)
|
||||||
|
|
||||||
const filterOptions = ref<FilterOption[]>(JSON.parse(JSON.stringify(defaultFilters)))
|
|
||||||
|
|
||||||
watch(handleElement, () => {
|
|
||||||
if (!handleElement.value || handleElement.value.type !== 'image') return
|
|
||||||
|
|
||||||
const filters = handleElement.value.filters
|
|
||||||
if (filters) {
|
|
||||||
filterOptions.value = defaultFilters.map(item => {
|
|
||||||
if (filters[item.key] !== undefined) return { ...item, value: parseInt(filters[item.key]) }
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else filterOptions.value = JSON.parse(JSON.stringify(defaultFilters))
|
|
||||||
}, { deep: true, immediate: true })
|
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
// 设置滤镜
|
|
||||||
const updateFilter = (filter: FilterOption, value: number) => {
|
|
||||||
const _handleElement = handleElement.value as PPTImageElement
|
|
||||||
|
|
||||||
const originFilters = _handleElement.filters || {}
|
|
||||||
const filters = { ...originFilters, [filter.key]: `${value}${filter.unit}` }
|
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props: { filters } })
|
|
||||||
addHistorySnapshot()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开自由裁剪
|
// 打开自由裁剪
|
||||||
const clipImage = () => {
|
const clipImage = () => {
|
||||||
mainStore.setClipingImageElementId(handleElementId.value)
|
mainStore.setClipingImageElementId(handleElementId.value)
|
||||||
@ -325,9 +265,7 @@ export default defineComponent({
|
|||||||
clipPanelVisible,
|
clipPanelVisible,
|
||||||
shapeClipPathOptions,
|
shapeClipPathOptions,
|
||||||
ratioClipOptions,
|
ratioClipOptions,
|
||||||
filterOptions,
|
|
||||||
handleElement,
|
handleElement,
|
||||||
updateFilter,
|
|
||||||
clipImage,
|
clipImage,
|
||||||
presetImageClip,
|
presetImageClip,
|
||||||
replaceImage,
|
replaceImage,
|
||||||
@ -364,29 +302,6 @@ export default defineComponent({
|
|||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
|
||||||
width: 280px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.filter-item {
|
|
||||||
padding: 8px 5px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.name {
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
.filter-slider {
|
|
||||||
flex: 1;
|
|
||||||
margin: 0 6px;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
width: 40px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.clip {
|
.clip {
|
||||||
width: 260px;
|
width: 260px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
141
src/views/Editor/Toolbar/common/ElementFilter.vue
Normal file
141
src/views/Editor/Toolbar/common/ElementFilter.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<div class="element-filter">
|
||||||
|
<div class="row">
|
||||||
|
<div style="flex: 2;">启用滤镜:</div>
|
||||||
|
<div class="switch-wrapper" style="flex: 3;">
|
||||||
|
<Switch
|
||||||
|
:checked="hasFilters"
|
||||||
|
@change="checked => toggleFilters(checked)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="filter" v-if="hasFilters">
|
||||||
|
<div class="filter-item" v-for="filter in filterOptions" :key="filter.key">
|
||||||
|
<div class="name">{{filter.label}}</div>
|
||||||
|
<Slider
|
||||||
|
class="filter-slider"
|
||||||
|
:max="filter.max"
|
||||||
|
:min="filter.min"
|
||||||
|
:step="filter.step"
|
||||||
|
:value="filter.value"
|
||||||
|
@change="value => updateFilter(filter, value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref, watch } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
|
import { PPTImageElement } from '@/types/slides'
|
||||||
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
|
interface FilterOption {
|
||||||
|
label: string;
|
||||||
|
key: string;
|
||||||
|
default: number;
|
||||||
|
value: number;
|
||||||
|
unit: string;
|
||||||
|
max: number;
|
||||||
|
step: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultFilters: FilterOption[] = [
|
||||||
|
{ label: '模糊', key: 'blur', default: 0, value: 0, unit: 'px', max: 10, step: 1 },
|
||||||
|
{ label: '亮度', key: 'brightness', default: 100, value: 100, unit: '%', max: 200, step: 5 },
|
||||||
|
{ label: '对比度', key: 'contrast', default: 100, value: 100, unit: '%', max: 200, step: 5 },
|
||||||
|
{ label: '灰度', key: 'grayscale', default: 0, value: 0, unit: '%', max: 100, step: 5 },
|
||||||
|
{ label: '饱和度', key: 'saturate', default: 100, value: 100, unit: '%', max: 200, step: 5 },
|
||||||
|
{ label: '色相', key: 'hue-rotate', default: 0, value: 0, unit: 'deg', max: 360, step: 10 },
|
||||||
|
{ label: '不透明度', key: 'opacity', default: 100, value: 100, unit: '%', max: 100, step: 5 },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'element-filter',
|
||||||
|
setup() {
|
||||||
|
const slidesStore = useSlidesStore()
|
||||||
|
const { handleElement, handleElementId } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
|
const filterOptions = ref<FilterOption[]>(JSON.parse(JSON.stringify(defaultFilters)))
|
||||||
|
const hasFilters = ref(false)
|
||||||
|
|
||||||
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
|
watch(handleElement, () => {
|
||||||
|
if (!handleElement.value || handleElement.value.type !== 'image') return
|
||||||
|
|
||||||
|
const filters = handleElement.value.filters
|
||||||
|
if (filters) {
|
||||||
|
filterOptions.value = defaultFilters.map(item => {
|
||||||
|
if (filters[item.key] !== undefined) return { ...item, value: parseInt(filters[item.key]) }
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
hasFilters.value = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
filterOptions.value = JSON.parse(JSON.stringify(defaultFilters))
|
||||||
|
hasFilters.value = false
|
||||||
|
}
|
||||||
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
|
// 设置滤镜
|
||||||
|
const updateFilter = (filter: FilterOption, value: number) => {
|
||||||
|
const _handleElement = handleElement.value as PPTImageElement
|
||||||
|
|
||||||
|
const originFilters = _handleElement.filters || {}
|
||||||
|
const filters = { ...originFilters, [filter.key]: `${value}${filter.unit}` }
|
||||||
|
slidesStore.updateElement({ id: handleElementId.value, props: { filters } })
|
||||||
|
addHistorySnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFilters = (checked: boolean) => {
|
||||||
|
if (!handleElement.value) return
|
||||||
|
if (checked) {
|
||||||
|
slidesStore.updateElement({ id: handleElement.value.id, props: { filters: {} } })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'filters' })
|
||||||
|
}
|
||||||
|
addHistorySnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filterOptions,
|
||||||
|
hasFilters,
|
||||||
|
toggleFilters,
|
||||||
|
updateFilter,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.switch-wrapper {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.filter {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.filter-item {
|
||||||
|
padding: 8px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
.filter-slider {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user