feat: 添加曲线、折线线条

This commit is contained in:
pipipi-pikachu 2021-02-27 16:27:38 +08:00
parent 345a9b6600
commit 60622b4811
9 changed files with 153 additions and 60 deletions

View File

@ -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 },
],
},
] ]

View File

@ -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 {

View File

@ -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 {

View File

@ -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'

View File

@ -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 {

View File

@ -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
}) })

View File

@ -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 {

View File

@ -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}`
}) })

View File

@ -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}`
}) })