mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 添加曲线、折线线条
This commit is contained in:
parent
345a9b6600
commit
60622b4811
@ -2,12 +2,26 @@ export interface LinePoolItem {
|
|||||||
path: string;
|
path: string;
|
||||||
style: string;
|
style: string;
|
||||||
points: [string, string];
|
points: [string, string];
|
||||||
|
isBroken?: boolean;
|
||||||
|
isCurve?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LINE_LIST = [
|
export const LINE_LIST = [
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', ''] },
|
{
|
||||||
{ path: 'M0,0 L20,20', style: 'dashed', points: ['', ''] },
|
type: '直线',
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'arrow'] },
|
children: [
|
||||||
{ path: 'M0,0 L20,20', style: 'dashed', points: ['', 'arrow'] },
|
{ path: 'M 0 0 L 20 20', style: 'solid', points: ['', ''] },
|
||||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'dot'] },
|
{ path: 'M 0 0 L 20 20', style: 'dashed', points: ['', ''] },
|
||||||
|
{ path: 'M 0 0 L 20 20', style: 'solid', points: ['', 'arrow'] },
|
||||||
|
{ path: 'M 0 0 L 20 20', style: 'dashed', points: ['', 'arrow'] },
|
||||||
|
{ path: 'M 0 0 L 20 20', style: 'solid', points: ['', 'dot'] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '折线、曲线',
|
||||||
|
children: [
|
||||||
|
{ path: 'M 0 0 L 0 20 L 20 20', style: 'solid', points: ['', 'arrow'], isBroken: true },
|
||||||
|
{ path: 'M 0 0 Q 0 20 20 20', style: 'solid', points: ['', 'arrow'], isCurve: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
@ -3,7 +3,7 @@ import { MutationTypes, useStore } from '@/store'
|
|||||||
import { createRandomCode } from '@/utils/common'
|
import { createRandomCode } from '@/utils/common'
|
||||||
import { getImageSize } from '@/utils/image'
|
import { getImageSize } from '@/utils/image'
|
||||||
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
||||||
import { ChartType, PPTElement, TableCell } from '@/types/slides'
|
import { PPTLineElement, ChartType, PPTElement, TableCell } from '@/types/slides'
|
||||||
import { ShapePoolItem } from '@/configs/shapes'
|
import { ShapePoolItem } from '@/configs/shapes'
|
||||||
import { LinePoolItem } from '@/configs/lines'
|
import { LinePoolItem } from '@/configs/lines'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
@ -177,7 +177,8 @@ export default () => {
|
|||||||
*/
|
*/
|
||||||
const createLineElement = (position: LineElementPosition, data: LinePoolItem) => {
|
const createLineElement = (position: LineElementPosition, data: LinePoolItem) => {
|
||||||
const { left, top, start, end } = position
|
const { left, top, start, end } = position
|
||||||
createElement({
|
|
||||||
|
const newElement: PPTLineElement = {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
id: createRandomCode(),
|
id: createRandomCode(),
|
||||||
left,
|
left,
|
||||||
@ -188,7 +189,10 @@ export default () => {
|
|||||||
color: themeColor.value,
|
color: themeColor.value,
|
||||||
style: data.style,
|
style: data.style,
|
||||||
width: 2,
|
width: 2,
|
||||||
})
|
}
|
||||||
|
if (data.isBroken) newElement.broken = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
|
||||||
|
if (data.isCurve) newElement.curve = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
|
||||||
|
createElement(newElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -44,11 +44,12 @@ export const enum OperateResizeHandlers {
|
|||||||
RIGHT_BOTTOM = 'right-bottom',
|
RIGHT_BOTTOM = 'right-bottom',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OperateLineHandler = 'start' | 'end'
|
export type OperateLineHandler = 'start' | 'end' | 'mid'
|
||||||
|
|
||||||
export const enum OperateLineHandlers {
|
export const enum OperateLineHandlers {
|
||||||
START = 'start',
|
START = 'start',
|
||||||
END = 'end,'
|
END = 'end',
|
||||||
|
MID = 'mid',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AlignmentLineAxis {
|
export interface AlignmentLineAxis {
|
||||||
|
@ -117,6 +117,8 @@ export interface PPTLineElement {
|
|||||||
color: string;
|
color: string;
|
||||||
points: [string, string];
|
points: [string, string];
|
||||||
shadow?: PPTElementShadow;
|
shadow?: PPTElementShadow;
|
||||||
|
broken?: [number, number];
|
||||||
|
curve?: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChartType = 'bar' | 'line' | 'pie'
|
export type ChartType = 'bar' | 'line' | 'pie'
|
||||||
|
@ -51,7 +51,7 @@ export default defineComponent({
|
|||||||
const canvasScale = computed(() => store.state.canvasScale)
|
const canvasScale = computed(() => store.state.canvasScale)
|
||||||
|
|
||||||
const resizeHandlers = computed(() => {
|
const resizeHandlers = computed(() => {
|
||||||
return [
|
const handlers = [
|
||||||
{
|
{
|
||||||
handler: OperateLineHandlers.START,
|
handler: OperateLineHandlers.START,
|
||||||
style: {
|
style: {
|
||||||
@ -67,6 +67,19 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (props.elementInfo.curve || props.elementInfo.broken) {
|
||||||
|
const midHandler = (props.elementInfo.curve || props.elementInfo.broken) as [number, number]
|
||||||
|
|
||||||
|
handlers.push({
|
||||||
|
handler: OperateLineHandlers.MID,
|
||||||
|
style: {
|
||||||
|
left: midHandler[0] * canvasScale.value + 'px',
|
||||||
|
top: midHandler[1] * canvasScale.value + 'px',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return handlers
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -78,6 +78,10 @@ export default (elementList: Ref<PPTElement[]>) => {
|
|||||||
let endX = element.left + element.end[0]
|
let endX = element.left + element.end[0]
|
||||||
let endY = element.top + element.end[1]
|
let endY = element.top + element.end[1]
|
||||||
|
|
||||||
|
const mid = element.broken || element.curve || [0, 0]
|
||||||
|
let midX = element.left + mid[0]
|
||||||
|
let midY = element.top + mid[1]
|
||||||
|
|
||||||
// 拖拽起点或终点的位置
|
// 拖拽起点或终点的位置
|
||||||
// 水平和垂直方向上有吸附
|
// 水平和垂直方向上有吸附
|
||||||
if (command === OperateLineHandlers.START) {
|
if (command === OperateLineHandlers.START) {
|
||||||
@ -96,7 +100,7 @@ export default (elementList: Ref<PPTElement[]>) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (command === OperateLineHandlers.END) {
|
||||||
endX = endX + moveX
|
endX = endX + moveX
|
||||||
endY = endY + moveY
|
endY = endY + moveY
|
||||||
|
|
||||||
@ -112,6 +116,19 @@ export default (elementList: Ref<PPTElement[]>) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
midX = midX + moveX
|
||||||
|
midY = midY + moveY
|
||||||
|
|
||||||
|
if (Math.abs(midX - startX) < sorptionRange) midX = startX
|
||||||
|
if (Math.abs(midY - startY) < sorptionRange) midY = startY
|
||||||
|
if (Math.abs(midX - endX) < sorptionRange) midX = endX
|
||||||
|
if (Math.abs(midY - endY) < sorptionRange) midY = endY
|
||||||
|
if (Math.abs(midX - (startX + endX) / 2) < sorptionRange && Math.abs(midY - (startY + endY) / 2) < sorptionRange) {
|
||||||
|
midX = (startX + endX) / 2
|
||||||
|
midY = (startY + endY) / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 计算更新起点和终点基于自身元素位置的坐标
|
// 计算更新起点和终点基于自身元素位置的坐标
|
||||||
const minX = Math.min(startX, endX)
|
const minX = Math.min(startX, endX)
|
||||||
@ -132,13 +149,22 @@ export default (elementList: Ref<PPTElement[]>) => {
|
|||||||
|
|
||||||
elementList.value = elementList.value.map(el => {
|
elementList.value = elementList.value.map(el => {
|
||||||
if (el.id === element.id) {
|
if (el.id === element.id) {
|
||||||
return {
|
const newEl: PPTLineElement = {
|
||||||
...el,
|
...(el as PPTLineElement),
|
||||||
left: minX,
|
left: minX,
|
||||||
top: minY,
|
top: minY,
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
}
|
}
|
||||||
|
if (command !== OperateLineHandlers.MID) {
|
||||||
|
if (element.broken) newEl.broken = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
|
||||||
|
if (element.curve) newEl.curve = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (element.broken) newEl.broken = [midX - minX, midY - minY]
|
||||||
|
if (element.curve) newEl.curve = [midX - minX, midY - minY]
|
||||||
|
}
|
||||||
|
return newEl
|
||||||
}
|
}
|
||||||
return el
|
return el
|
||||||
})
|
})
|
||||||
|
@ -1,46 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="line-pool">
|
<div class="line-pool">
|
||||||
<div class="line-item" v-for="(line, index) in lineList" :key="index">
|
<div class="category" v-for="item in lineList" :key="item.type">
|
||||||
<div class="line-content" @click="selectLine(line)">
|
<div class="category-name">{{item.type}}</div>
|
||||||
<SvgWrapper
|
<div class="line-list">
|
||||||
overflow="visible"
|
<div class="line-item" v-for="(line, index) in item.children" :key="index">
|
||||||
width="20"
|
<div class="line-content" @click="selectLine(line)">
|
||||||
height="20"
|
<SvgWrapper
|
||||||
>
|
overflow="visible"
|
||||||
<defs>
|
width="20"
|
||||||
<LinePointMarker
|
height="20"
|
||||||
class="line-marker"
|
>
|
||||||
v-if="line.points[0]"
|
<defs>
|
||||||
:id="`preset-line-${index}`"
|
<LinePointMarker
|
||||||
position="start"
|
class="line-marker"
|
||||||
:type="line.points[0]"
|
v-if="line.points[0]"
|
||||||
color="currentColor"
|
:id="`preset-line-${index}`"
|
||||||
:baseSize="2"
|
position="start"
|
||||||
/>
|
:type="line.points[0]"
|
||||||
<LinePointMarker
|
color="currentColor"
|
||||||
class="line-marker"
|
:baseSize="2"
|
||||||
v-if="line.points[1]"
|
/>
|
||||||
:id="`preset-line-${index}`"
|
<LinePointMarker
|
||||||
position="end"
|
class="line-marker"
|
||||||
:type="line.points[1]"
|
v-if="line.points[1]"
|
||||||
color="currentColor"
|
:id="`preset-line-${index}`"
|
||||||
:baseSize="2"
|
position="end"
|
||||||
/>
|
:type="line.points[1]"
|
||||||
</defs>
|
color="currentColor"
|
||||||
<path
|
:baseSize="2"
|
||||||
class="line-path"
|
/>
|
||||||
:d="line.path"
|
</defs>
|
||||||
stroke="currentColor"
|
<path
|
||||||
fill="none"
|
class="line-path"
|
||||||
stroke-width="2"
|
:d="line.path"
|
||||||
:stroke-dasharray="line.style === 'solid' ? '0, 0' : '4, 1'"
|
stroke="currentColor"
|
||||||
stroke-linecap
|
fill="none"
|
||||||
stroke-linejoin
|
stroke-width="2"
|
||||||
stroke-miterlimit
|
:stroke-dasharray="line.style === 'solid' ? '0, 0' : '4, 1'"
|
||||||
:marker-start="line.points[0] ? `url(#${`preset-line-${index}`}-${line.points[0]}-start)` : ''"
|
stroke-linecap
|
||||||
:marker-end="line.points[1] ? `url(#${`preset-line-${index}`}-${line.points[1]}-end)` : ''"
|
stroke-linejoin
|
||||||
></path>
|
stroke-miterlimit
|
||||||
</SvgWrapper>
|
:marker-start="line.points[0] ? `url(#${`preset-line-${index}`}-${line.points[0]}-start)` : ''"
|
||||||
|
:marker-end="line.points[1] ? `url(#${`preset-line-${index}`}-${line.points[1]}-end)` : ''"
|
||||||
|
></path>
|
||||||
|
</SvgWrapper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -74,10 +79,24 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.line-pool {
|
.line-pool {
|
||||||
width: 200px;
|
width: 220px;
|
||||||
margin-bottom: -5px;
|
overflow: auto;
|
||||||
|
margin-bottom: -12px;
|
||||||
|
margin-right: -12px;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
.category-name {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-left: 4px solid #aaa;
|
||||||
|
background-color: #eee;
|
||||||
|
padding: 2px 0 2px 10px;
|
||||||
|
}
|
||||||
|
.line-list {
|
||||||
@include flex-grid-layout();
|
@include flex-grid-layout();
|
||||||
|
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.line-item {
|
.line-item {
|
||||||
@include flex-grid-layout-children(5, 19%);
|
@include flex-grid-layout-children(5, 19%);
|
||||||
@ -86,8 +105,6 @@ export default defineComponent({
|
|||||||
padding-bottom: 19%;
|
padding-bottom: 19%;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.line-content {
|
.line-content {
|
||||||
|
@ -86,6 +86,14 @@ export default defineComponent({
|
|||||||
const path = computed(() => {
|
const path = computed(() => {
|
||||||
const start = props.elementInfo.start.join(',')
|
const start = props.elementInfo.start.join(',')
|
||||||
const end = props.elementInfo.end.join(',')
|
const end = props.elementInfo.end.join(',')
|
||||||
|
if (props.elementInfo.broken) {
|
||||||
|
const mid = props.elementInfo.broken.join(',')
|
||||||
|
return `M${start} L${mid} L${end}`
|
||||||
|
}
|
||||||
|
if (props.elementInfo.curve) {
|
||||||
|
const mid = props.elementInfo.curve.join(',')
|
||||||
|
return `M${start} Q${mid} ${end}`
|
||||||
|
}
|
||||||
return `M${start} L${end}`
|
return `M${start} L${end}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -111,6 +111,14 @@ export default defineComponent({
|
|||||||
const path = computed(() => {
|
const path = computed(() => {
|
||||||
const start = props.elementInfo.start.join(',')
|
const start = props.elementInfo.start.join(',')
|
||||||
const end = props.elementInfo.end.join(',')
|
const end = props.elementInfo.end.join(',')
|
||||||
|
if (props.elementInfo.broken) {
|
||||||
|
const mid = props.elementInfo.broken.join(',')
|
||||||
|
return `M${start} L${mid} L${end}`
|
||||||
|
}
|
||||||
|
if (props.elementInfo.curve) {
|
||||||
|
const mid = props.elementInfo.curve.join(',')
|
||||||
|
return `M${start} Q${mid} ${end}`
|
||||||
|
}
|
||||||
return `M${start} L${end}`
|
return `M${start} L${end}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user