mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 形状支持自定义绘制线条(#234)
This commit is contained in:
parent
23fc06462b
commit
40778513d9
@ -109,6 +109,7 @@ npm run serve
|
||||
- 设置为背景图
|
||||
#### 形状
|
||||
- 绘制任意多边形
|
||||
- 绘制任意线条(未封闭形状模拟)
|
||||
- 替换形状
|
||||
- 填充色
|
||||
- 边框
|
||||
|
@ -92,7 +92,7 @@ defineExpose({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.message {
|
||||
max-width: 500px;
|
||||
max-width: 600px;
|
||||
|
||||
& + & {
|
||||
margin-top: 15px;
|
||||
|
@ -86,6 +86,7 @@ export const HOTKEY_DOC = [
|
||||
{ label: '创建水平 / 垂直线条', value: '按住 Ctrl 或 Shift' },
|
||||
{ label: '切换焦点元素', value: 'Tab' },
|
||||
{ label: '确认图片裁剪', value: 'Enter' },
|
||||
{ label: '完成自定义形状绘制', value: 'Enter' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -213,7 +213,7 @@ export default () => {
|
||||
* @param position 位置大小信息
|
||||
* @param data 形状路径信息
|
||||
*/
|
||||
const createShapeElement = (position: CommonElementPosition, data: ShapePoolItem) => {
|
||||
const createShapeElement = (position: CommonElementPosition, data: ShapePoolItem, supplement: Partial<PPTShapeElement> = {}) => {
|
||||
const { left, top, width, height } = position
|
||||
const newElement: PPTShapeElement = {
|
||||
type: 'shape',
|
||||
@ -227,6 +227,7 @@ export default () => {
|
||||
fill: theme.value.themeColor,
|
||||
fixedRatio: false,
|
||||
rotate: 0,
|
||||
...supplement,
|
||||
}
|
||||
if (data.special) newElement.special = true
|
||||
if (data.pathFormula) {
|
||||
|
@ -83,6 +83,8 @@ export interface CreateCustomShapeData {
|
||||
end: [number, number]
|
||||
path: string
|
||||
viewBox: [number, number]
|
||||
fill?: string
|
||||
outline?: PPTElementOutline
|
||||
}
|
||||
|
||||
export interface CreatingTextElement {
|
||||
|
@ -20,7 +20,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useKeyboardStore, useMainStore } from '@/store'
|
||||
import { useKeyboardStore, useMainStore, useSlidesStore } from '@/store'
|
||||
import type { CreateCustomShapeData } from '@/types/edit'
|
||||
import { KEYS } from '@/configs/hotkey'
|
||||
import message from '@/utils/message'
|
||||
@ -30,8 +30,10 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
const mainStore = useMainStore()
|
||||
const { ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())
|
||||
const { theme } = storeToRefs(useSlidesStore())
|
||||
|
||||
const shapeCanvasRef = ref<HTMLElement>()
|
||||
const isMouseDown = ref(false)
|
||||
const offset = ref({
|
||||
x: 0,
|
||||
y: 0,
|
||||
@ -42,14 +44,16 @@ onMounted(() => {
|
||||
offset.value = { x, y }
|
||||
})
|
||||
|
||||
const mousePosition = ref<[number, number]>()
|
||||
const mousePosition = ref<[number, number] | null>(null)
|
||||
const points = ref<[number, number][]>([])
|
||||
const closed = ref(false)
|
||||
|
||||
const getPoint = (e: MouseEvent) => {
|
||||
const getPoint = (e: MouseEvent, custom = false) => {
|
||||
let pageX = e.pageX - offset.value.x
|
||||
let pageY = e.pageY - offset.value.y
|
||||
|
||||
if (custom) return { pageX, pageY }
|
||||
|
||||
if (ctrlOrShiftKeyActive.value && points.value.length) {
|
||||
const [lastPointX, lastPointY] = points.value[points.value.length - 1]
|
||||
if (Math.abs(lastPointX - pageX) - Math.abs(lastPointY - pageY) > 0) {
|
||||
@ -61,6 +65,13 @@ const getPoint = (e: MouseEvent) => {
|
||||
}
|
||||
|
||||
const updateMousePosition = (e: MouseEvent) => {
|
||||
if (isMouseDown.value) {
|
||||
const { pageX, pageY } = getPoint(e, true)
|
||||
points.value.push([pageX, pageY])
|
||||
mousePosition.value = null
|
||||
return
|
||||
}
|
||||
|
||||
const { pageX, pageY } = getPoint(e)
|
||||
mousePosition.value = [pageX, pageY]
|
||||
|
||||
@ -87,10 +98,7 @@ const path = computed(() => {
|
||||
return d
|
||||
})
|
||||
|
||||
const addPoint = (e: MouseEvent) => {
|
||||
const { pageX, pageY } = getPoint(e)
|
||||
|
||||
if (closed.value) {
|
||||
const getCreateData = (close = true) => {
|
||||
const xList = points.value.map(item => item[0])
|
||||
const yList = points.value.map(item => item[1])
|
||||
const minX = Math.min(...xList)
|
||||
@ -101,34 +109,65 @@ const addPoint = (e: MouseEvent) => {
|
||||
const formatedPoints = points.value.map(point => {
|
||||
return [point[0] - minX, point[1] - minY]
|
||||
})
|
||||
|
||||
let path = ''
|
||||
for (let i = 0; i < formatedPoints.length; i++) {
|
||||
const point = formatedPoints[i]
|
||||
if (i === 0) path += `M ${point[0]} ${point[1]} `
|
||||
else path += `L ${point[0]} ${point[1]} `
|
||||
}
|
||||
path += 'Z'
|
||||
if (close) path += 'Z'
|
||||
|
||||
emit('created', {
|
||||
start: [minX + offset.value.x, minY + offset.value.y],
|
||||
end: [maxX + offset.value.x, maxY + offset.value.y],
|
||||
const start: [number, number] = [minX + offset.value.x, minY + offset.value.y]
|
||||
const end: [number, number] = [maxX + offset.value.x, maxY + offset.value.y]
|
||||
const viewBox: [number, number] = [maxX - minX, maxY - minY]
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
path,
|
||||
viewBox: [maxX - minX, maxY - minY],
|
||||
})
|
||||
viewBox,
|
||||
}
|
||||
}
|
||||
|
||||
const addPoint = (e: MouseEvent) => {
|
||||
const { pageX, pageY } = getPoint(e)
|
||||
isMouseDown.value = true
|
||||
|
||||
if (closed.value) emit('created', getCreateData())
|
||||
else points.value.push([pageX, pageY])
|
||||
|
||||
document.onmouseup = () => {
|
||||
isMouseDown.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
mainStore.setCreatingCustomShapeState(false)
|
||||
}
|
||||
|
||||
const create = () => {
|
||||
emit('created', {
|
||||
...getCreateData(false),
|
||||
fill: 'rgba(0, 0, 0, 0)',
|
||||
outline: {
|
||||
width: 2,
|
||||
color: theme.value.themeColor,
|
||||
style: 'solid',
|
||||
},
|
||||
})
|
||||
close()
|
||||
}
|
||||
|
||||
const keydownListener = (e: KeyboardEvent) => {
|
||||
const key = e.key.toUpperCase()
|
||||
if (key === KEYS.ESC) close()
|
||||
if (key === KEYS.ENTER) create()
|
||||
}
|
||||
onMounted(() => {
|
||||
message.success('点击开始绘制任意多边形,首尾闭合完成绘制,按 ESC 键或鼠标右键关闭')
|
||||
message.success('点击绘制任意形状,首尾闭合完成绘制,按 ESC 键或鼠标右键取消,按 ENTER 键提前完成', {
|
||||
duration: 5000,
|
||||
})
|
||||
document.addEventListener('keydown', keydownListener)
|
||||
})
|
||||
onUnmounted(() => document.removeEventListener('keydown', keydownListener))
|
||||
|
@ -102,7 +102,7 @@ import { throttle } from 'lodash'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'
|
||||
import type { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||
import type { PPTElement } from '@/types/slides'
|
||||
import type { PPTElement, PPTShapeElement } from '@/types/slides'
|
||||
import type { AlignmentLineProps, CreateCustomShapeData } from '@/types/edit'
|
||||
import { injectKeySlideScale } from '@/types/injectKey'
|
||||
import { removeAllRanges } from '@/utils/selection'
|
||||
@ -277,7 +277,12 @@ const insertCustomShape = (data: CreateCustomShapeData) => {
|
||||
viewBox,
|
||||
} = data
|
||||
const position = formatCreateSelection({ start, end })
|
||||
position && createShapeElement(position, { path, viewBox })
|
||||
if (position) {
|
||||
const supplement: Partial<PPTShapeElement> = {}
|
||||
if (data.fill) supplement.fill = data.fill
|
||||
if (data.outline) supplement.outline = data.outline
|
||||
createShapeElement(position, { path, viewBox }, supplement)
|
||||
}
|
||||
|
||||
mainStore.setCreatingCustomShapeState(false)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user