rename
This commit is contained in:
parent
fa1e7f13d3
commit
b715809a34
|
@ -1,14 +1,5 @@
|
|||
const DEFAULT_COLOR = '#41464b'
|
||||
|
||||
export enum ElementTypes {
|
||||
TEXT = '文本',
|
||||
IMAGE = '图片',
|
||||
SHAPE = '形状',
|
||||
LINE = '线条',
|
||||
CHART = '图表',
|
||||
TABLE = '表格',
|
||||
}
|
||||
|
||||
export const DEFAULT_TEXT = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
|
@ -23,17 +14,17 @@ export const DEFAULT_TEXT = {
|
|||
export const DEFAULT_IMAGE = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
lockRatio: true,
|
||||
fixedRatio: true,
|
||||
}
|
||||
|
||||
export const DEFAULT_SHAPE = {
|
||||
fill: DEFAULT_COLOR,
|
||||
lockRatio: false,
|
||||
fixedRatio: false,
|
||||
}
|
||||
|
||||
export const DEFAULT_LINE = {
|
||||
style: 'solid',
|
||||
marker: ['', ''],
|
||||
points: ['', ''],
|
||||
width: 4,
|
||||
color: DEFAULT_COLOR,
|
||||
}
|
||||
|
@ -48,8 +39,9 @@ export const DEFAULT_CHART = {
|
|||
export const DEFAULT_TABLE = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
isLock: false,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 2,
|
||||
borderColor: DEFAULT_COLOR,
|
||||
outline: {
|
||||
width: 2,
|
||||
style: 'solid',
|
||||
color: DEFAULT_COLOR
|
||||
},
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
export const LINES = [
|
||||
{ path: 'M0,0 L20,20', style: 'solid', marker: ['', ''] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', marker: ['', 'arrow'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', marker: ['arrow', 'arrow'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', marker: ['', 'cusp'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', marker: ['cusp', 'cusp'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', marker: ['', 'dot'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', marker: ['dot', 'dot'] },
|
||||
{ path: 'M0,0 L20,20', style: 'dashed', marker: ['', ''] },
|
||||
{ path: 'M0,0 L20,20', style: 'dashed', marker: ['', 'arrow'] },
|
||||
{ path: 'M0,0 L20,20', style: 'dashed', marker: ['arrow', 'arrow'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', ''] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'arrow'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['arrow', 'arrow'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'cusp'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['cusp', 'cusp'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['', 'dot'] },
|
||||
{ path: 'M0,0 L20,20', style: 'solid', points: ['dot', 'dot'] },
|
||||
{ path: 'M0,0 L20,20', style: 'dashed', points: ['', ''] },
|
||||
{ path: 'M0,0 L20,20', style: 'dashed', points: ['', 'arrow'] },
|
||||
{ path: 'M0,0 L20,20', style: 'dashed', points: ['arrow', 'arrow'] },
|
||||
]
|
|
@ -20,7 +20,7 @@ export default () => {
|
|||
|
||||
const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
|
||||
for(const element of newElementList) {
|
||||
if(!activeElementIdList.value.includes(element.elId)) continue
|
||||
if(!activeElementIdList.value.includes(element.id)) continue
|
||||
|
||||
if(command === ElementAlignCommands.TOP) {
|
||||
const offsetY = minY - 0
|
||||
|
|
|
@ -22,16 +22,16 @@ export default () => {
|
|||
|
||||
const combineElementList: PPTElement[] = []
|
||||
for(const element of newElementList) {
|
||||
if(activeElementIdList.value.includes(element.elId)) {
|
||||
if(activeElementIdList.value.includes(element.id)) {
|
||||
element.groupId = groupId
|
||||
combineElementList.push(element)
|
||||
}
|
||||
}
|
||||
|
||||
// 注意,组合元素的层级应该是连续的,所以需要获取该组元素中最顶层的元素,将组内其他成员从原位置移动到最顶层的元素的下面
|
||||
const combineElementMaxIndex = newElementList.findIndex(_element => _element.elId === combineElementList[combineElementList.length - 1].elId)
|
||||
const combineElementIdList = combineElementList.map(_element => _element.elId)
|
||||
newElementList = newElementList.filter(_element => !combineElementIdList.includes(_element.elId))
|
||||
const combineElementMaxIndex = newElementList.findIndex(_element => _element.id === combineElementList[combineElementList.length - 1].id)
|
||||
const combineElementIdList = combineElementList.map(_element => _element.id)
|
||||
newElementList = newElementList.filter(_element => !combineElementIdList.includes(_element.id))
|
||||
|
||||
const insertIndex = combineElementMaxIndex - combineElementList.length + 1
|
||||
newElementList.splice(insertIndex, 0, ...combineElementList)
|
||||
|
@ -48,7 +48,7 @@ export default () => {
|
|||
|
||||
const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
|
||||
for(const element of newElementList) {
|
||||
if(activeElementIdList.value.includes(element.elId) && element.groupId) delete element.groupId
|
||||
if(activeElementIdList.value.includes(element.id) && element.groupId) delete element.groupId
|
||||
}
|
||||
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
|
||||
addHistorySnapshot()
|
||||
|
|
|
@ -35,12 +35,12 @@ export default () => {
|
|||
|
||||
const createElement = (element: PPTElement) => {
|
||||
store.commit(MutationTypes.ADD_ELEMENT, element)
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [element.elId])
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [element.id])
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
const createImageElement = (imgUrl: string) => {
|
||||
getImageSize(imgUrl).then(({ width, height }) => {
|
||||
const createImageElement = (src: string) => {
|
||||
getImageSize(src).then(({ width, height }) => {
|
||||
const scale = width / height
|
||||
|
||||
if(scale < VIEWPORT_ASPECT_RATIO && width > VIEWPORT_SIZE) {
|
||||
|
@ -55,8 +55,8 @@ export default () => {
|
|||
createElement({
|
||||
...DEFAULT_IMAGE,
|
||||
type: 'image',
|
||||
elId: createRandomCode(),
|
||||
imgUrl,
|
||||
id: createRandomCode(),
|
||||
src,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
|
@ -67,7 +67,7 @@ export default () => {
|
|||
createElement({
|
||||
...DEFAULT_CHART,
|
||||
type: 'chart',
|
||||
elId: createRandomCode(),
|
||||
id: createRandomCode(),
|
||||
chartType,
|
||||
data,
|
||||
})
|
||||
|
@ -87,7 +87,7 @@ export default () => {
|
|||
createElement({
|
||||
...DEFAULT_TABLE,
|
||||
type: 'table',
|
||||
elId: createRandomCode(),
|
||||
id: createRandomCode(),
|
||||
width: colCount * DEFAULT_CELL_WIDTH + DEFAULT_BORDER_WIDTH,
|
||||
height: rowCount * DEFAULT_CELL_HEIGHT + DEFAULT_BORDER_WIDTH,
|
||||
colSizes,
|
||||
|
@ -101,7 +101,7 @@ export default () => {
|
|||
createElement({
|
||||
...DEFAULT_TEXT,
|
||||
type: 'text',
|
||||
elId: createRandomCode(),
|
||||
id: createRandomCode(),
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
|
@ -114,7 +114,7 @@ export default () => {
|
|||
createElement({
|
||||
...DEFAULT_SHAPE,
|
||||
type: 'shape',
|
||||
elId: createRandomCode(),
|
||||
id: createRandomCode(),
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
|
@ -123,17 +123,17 @@ export default () => {
|
|||
})
|
||||
}
|
||||
|
||||
const createLineElement = (position: LineElementPosition, marker: [string, string], lineType: string) => {
|
||||
const createLineElement = (position: LineElementPosition, points: [string, string], lineType: string) => {
|
||||
const { left, top, start, end } = position
|
||||
createElement({
|
||||
...DEFAULT_LINE,
|
||||
type: 'line',
|
||||
elId: createRandomCode(),
|
||||
id: createRandomCode(),
|
||||
left,
|
||||
top,
|
||||
start,
|
||||
end,
|
||||
marker,
|
||||
points,
|
||||
lineType,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ export default () => {
|
|||
|
||||
const deleteElement = () => {
|
||||
if(!activeElementIdList.value.length) return
|
||||
const newElementList = currentSlide.value.elements.filter(el => !activeElementIdList.value.includes(el.elId))
|
||||
const newElementList = currentSlide.value.elements.filter(el => !activeElementIdList.value.includes(el.id))
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
|
||||
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
|
||||
addHistorySnapshot()
|
||||
|
|
|
@ -15,7 +15,7 @@ export default () => {
|
|||
const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
|
||||
|
||||
for(const element of newElementList) {
|
||||
if(activeElementIdList.value.includes(element.elId)) element.isLock = true
|
||||
if(activeElementIdList.value.includes(element.id)) element.lock = true
|
||||
}
|
||||
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
|
||||
addHistorySnapshot()
|
||||
|
@ -26,14 +26,14 @@ export default () => {
|
|||
|
||||
if(handleElement.groupId) {
|
||||
for(const element of newElementList) {
|
||||
if(element.groupId === handleElement.groupId) element.isLock = false
|
||||
if(element.groupId === handleElement.groupId) element.lock = false
|
||||
}
|
||||
return newElementList
|
||||
}
|
||||
|
||||
for(const element of newElementList) {
|
||||
if(element.elId === handleElement.elId) {
|
||||
element.isLock = false
|
||||
if(element.id === handleElement.id) {
|
||||
element.lock = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ export default () => {
|
|||
|
||||
const moveElement = (command: string) => {
|
||||
const newElementList = currentSlide.value.elements.map(el => {
|
||||
if(activeElementIdList.value.includes(el.elId)) {
|
||||
if(activeElementIdList.value.includes(el.id)) {
|
||||
let { left, top } = el
|
||||
switch(command) {
|
||||
case KEYS.LEFT:
|
||||
|
|
|
@ -13,8 +13,8 @@ export default () => {
|
|||
|
||||
// 获取组合元素层级范围(组合成员中的最大层级和最小层级)
|
||||
const getCombineElementIndexRange = (elementList: PPTElement[], combineElementList: PPTElement[]) => {
|
||||
const minIndex = elementList.findIndex(_element => _element.elId === combineElementList[0].elId)
|
||||
const maxIndex = elementList.findIndex(_element => _element.elId === combineElementList[combineElementList.length - 1].elId)
|
||||
const minIndex = elementList.findIndex(_element => _element.id === combineElementList[0].id)
|
||||
const maxIndex = elementList.findIndex(_element => _element.id === combineElementList[combineElementList.length - 1].id)
|
||||
return { minIndex, maxIndex }
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ export default () => {
|
|||
else {
|
||||
|
||||
// 元素在元素列表中的层级
|
||||
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
|
||||
const elementIndex = elementList.findIndex(item => item.id === element.id)
|
||||
|
||||
// 无法移动(已经处在顶层)
|
||||
if(elementIndex === elementList.length - 1) return null
|
||||
|
@ -96,7 +96,7 @@ export default () => {
|
|||
}
|
||||
|
||||
else {
|
||||
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
|
||||
const elementIndex = elementList.findIndex(item => item.id === element.id)
|
||||
if(elementIndex === 0) return null
|
||||
const prevElement = copyOfElementList[elementIndex - 1]
|
||||
const movedElement = copyOfElementList.splice(elementIndex, 1)[0]
|
||||
|
@ -133,7 +133,7 @@ export default () => {
|
|||
else {
|
||||
|
||||
// 元素在元素列表中的层级
|
||||
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
|
||||
const elementIndex = elementList.findIndex(item => item.id === element.id)
|
||||
|
||||
// 无法移动(已经处在顶层)
|
||||
if(elementIndex === elementList.length - 1) return null
|
||||
|
@ -159,7 +159,7 @@ export default () => {
|
|||
}
|
||||
|
||||
else {
|
||||
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
|
||||
const elementIndex = elementList.findIndex(item => item.id === element.id)
|
||||
if(elementIndex === 0) return null
|
||||
copyOfElementList.splice(elementIndex, 1)
|
||||
copyOfElementList.unshift(element)
|
||||
|
|
|
@ -25,14 +25,14 @@ export default () => {
|
|||
if(groupId && !groupIdMap[groupId]) {
|
||||
groupIdMap[groupId] = createRandomCode()
|
||||
}
|
||||
elIdMap[element.elId] = createRandomCode()
|
||||
elIdMap[element.id] = createRandomCode()
|
||||
}
|
||||
const currentSlideElementIdList = currentSlide.value.elements.map(el => el.elId)
|
||||
const currentSlideElementIdList = currentSlide.value.elements.map(el => el.id)
|
||||
|
||||
for(const element of elements) {
|
||||
const inCurrentSlide = currentSlideElementIdList.includes(element.elId)
|
||||
const inCurrentSlide = currentSlideElementIdList.includes(element.id)
|
||||
|
||||
element.elId = elIdMap[element.elId]
|
||||
element.id = elIdMap[element.id]
|
||||
|
||||
if(inCurrentSlide) {
|
||||
element.left = element.left + 10
|
||||
|
|
|
@ -8,8 +8,8 @@ export default () => {
|
|||
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
|
||||
|
||||
const selectAllElement = () => {
|
||||
const unlockedElements = currentSlide.value.elements.filter(el => !el.isLock)
|
||||
const newActiveElementIdList = unlockedElements.map(el => el.elId)
|
||||
const unlockedElements = currentSlide.value.elements.filter(el => !el.lock)
|
||||
const newActiveElementIdList = unlockedElements.map(el => el.id)
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, newActiveElementIdList)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Ref, computed } from 'vue'
|
||||
import { SlideBackground } from '@/types/slides'
|
||||
|
||||
export default (background: Ref<[string, string] | undefined>) => {
|
||||
export default (background: Ref<SlideBackground | undefined>) => {
|
||||
const backgroundStyle = computed(() => {
|
||||
if(!background.value) return { backgroundColor: '#fff' }
|
||||
|
||||
const [type, value] = background.value
|
||||
const { type, value } = background.value
|
||||
if(type === 'solid') return { backgroundColor: value }
|
||||
else if(type === 'image') return { backgroundImage: `url(${value}` }
|
||||
|
||||
|
|
|
@ -3,66 +3,71 @@ import { Slide } from '@/types/slides'
|
|||
export const slides: Slide[] = [
|
||||
{
|
||||
id: 'xxx1',
|
||||
background: ['solid', '#fff'],
|
||||
background: {
|
||||
type: 'solid',
|
||||
value: '#fff',
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
elId: 'xxx1',
|
||||
id: 'xxx1',
|
||||
type: 'text',
|
||||
left: 190,
|
||||
top: 50,
|
||||
width: 320,
|
||||
height: 104,
|
||||
rotate: 0,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 4,
|
||||
borderColor: '#5b7d89',
|
||||
fill: 'rgba(220,220,220,0.8)',
|
||||
shadow: '1px 1px 3px rgba(10,10,10,.5)',
|
||||
fill: 'rgba(220, 220, 220, 0.8)',
|
||||
shadow: {
|
||||
h: 1,
|
||||
v: 1,
|
||||
blur: 3,
|
||||
color: 'rgba(10, 10, 10, .5)'
|
||||
},
|
||||
opacity: 1,
|
||||
lineHeight: 1.5,
|
||||
segmentSpacing: 10,
|
||||
isLock: false,
|
||||
lock: false,
|
||||
content: '<div style=\'text-align: center;\'><span style=\'font-size: 28px;\'><span style=\'color: rgb(232, 107, 153); font-weight: bold;\'>一段测试文字</span>,字号固定为<span style=\'font-weight: bold; font-style: italic; text-decoration-line: underline;\'>28px</span></span></div>',
|
||||
},
|
||||
{
|
||||
elId: 'xxx3',
|
||||
id: 'xxx3',
|
||||
type: 'image',
|
||||
left: 80,
|
||||
top: 250,
|
||||
width: 180,
|
||||
height: 180,
|
||||
rotate: 0,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 4,
|
||||
borderColor: 'rgba(10,10,10,1)',
|
||||
filter: '',
|
||||
outline: {
|
||||
width: 4,
|
||||
style: 'solid',
|
||||
color: '#333'
|
||||
},
|
||||
clip: {
|
||||
range: [[30, 0], [100, 70]],
|
||||
shape: 'ellipse'
|
||||
},
|
||||
lockRatio: false,
|
||||
isLock: false,
|
||||
imgUrl: 'https://img.lessonplan.cn/IMG/Show/ppt/3ab74e91-c34f-499d-9711-166e423d4dd6/1573622467064v2-7aa3ce420052983d91c6d01b47a7441d_hd.jpg',
|
||||
fixedRatio: false,
|
||||
lock: false,
|
||||
src: 'https://img.lessonplan.cn/IMG/Show/ppt/3ab74e91-c34f-499d-9711-166e423d4dd6/1573622467064v2-7aa3ce420052983d91c6d01b47a7441d_hd.jpg',
|
||||
},
|
||||
{
|
||||
elId: 'xxx2',
|
||||
id: 'xxx2',
|
||||
type: 'image',
|
||||
left: 750,
|
||||
top: 320,
|
||||
width: 150,
|
||||
height: 150,
|
||||
rotate: 0,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 6,
|
||||
borderColor: 'rgba(10,10,10,1)',
|
||||
filter: '',
|
||||
outline: {
|
||||
width: 6,
|
||||
style: 'solid',
|
||||
color: '#333'
|
||||
},
|
||||
clip: {
|
||||
range: [[0, 0], [100, 100]],
|
||||
shape: 'roundRect'
|
||||
},
|
||||
lockRatio: true,
|
||||
isLock: false,
|
||||
imgUrl: 'https://img.lessonplan.cn/IMG/Show/ppt/3ab74e91-c34f-499d-9711-166e423d4dd6/62d9adb3-e7a6-4dc4-a352-095cffb49f08/b1be1a2f-f893-47d3-a8a3-eac7d04d395f/1596159381259v2-b2c69096d25ae16bf6ca09e30add3e65_hd.jpg',
|
||||
fixedRatio: true,
|
||||
lock: false,
|
||||
src: 'https://img.lessonplan.cn/IMG/Show/ppt/3ab74e91-c34f-499d-9711-166e423d4dd6/62d9adb3-e7a6-4dc4-a352-095cffb49f08/b1be1a2f-f893-47d3-a8a3-eac7d04d395f/1596159381259v2-b2c69096d25ae16bf6ca09e30add3e65_hd.jpg',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -70,7 +75,7 @@ export const slides: Slide[] = [
|
|||
id: 'sajd172',
|
||||
elements: [
|
||||
{
|
||||
elId: 'yyx1',
|
||||
id: 'yyx1',
|
||||
type: 'text',
|
||||
left: 590,
|
||||
top: 90,
|
||||
|
@ -78,9 +83,7 @@ export const slides: Slide[] = [
|
|||
height: 188,
|
||||
rotate: 0,
|
||||
opacity: 1,
|
||||
lineHeight: 1.5,
|
||||
segmentSpacing: 10,
|
||||
isLock: false,
|
||||
lock: false,
|
||||
content: '<div>😀 😐 😶 😜 🔔 ⭐ ⚡ 🔥 👍 💡 🔰 🎀 🎁 🥇 🏅 🏆 🎈 🎉 💎 🚧 ⛔ 📢 ⌛ ⏰ 🕒 🧩 🎵 📎 🔒 🔑 ⛳ 📌 📍 💬 📅 📈 📋 📜 📁 📱 💻 💾 🌏 🚚 🚡 🚢💧 🌐 🧭 💰 💳 🛒</div>',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -13,20 +13,20 @@ export const getters: GetterTree<State, State> = {
|
|||
if(!animations) return null
|
||||
|
||||
const els = currentSlide.elements
|
||||
const elIds = els.map(el => el.elId)
|
||||
const elIds = els.map(el => el.id)
|
||||
return animations.filter(animation => elIds.includes(animation.elId))
|
||||
},
|
||||
|
||||
activeElementList(state) {
|
||||
const currentSlide = state.slides[state.slideIndex]
|
||||
if(!currentSlide || !currentSlide.elements) return []
|
||||
return currentSlide.elements.filter(element => state.activeElementIdList.includes(element.elId))
|
||||
return currentSlide.elements.filter(element => state.activeElementIdList.includes(element.id))
|
||||
},
|
||||
|
||||
handleElement(state) {
|
||||
const currentSlide = state.slides[state.slideIndex]
|
||||
if(!currentSlide || !currentSlide.elements) return null
|
||||
return currentSlide.elements.find(element => state.handleElementId === element.elId) || null
|
||||
return currentSlide.elements.find(element => state.handleElementId === element.id) || null
|
||||
},
|
||||
|
||||
canUndo(state) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { FONT_NAMES } from '@/configs/fontName'
|
|||
import { isSupportFontFamily } from '@/utils/fontFamily'
|
||||
|
||||
interface UpdateElementData {
|
||||
elId: string | string[];
|
||||
id: string | string[];
|
||||
props: Partial<PPTElement>;
|
||||
}
|
||||
|
||||
|
@ -91,13 +91,13 @@ export const mutations: MutationTree<State> = {
|
|||
},
|
||||
|
||||
[MutationTypes.UPDATE_ELEMENT](state, data: UpdateElementData) {
|
||||
const { elId, props } = data
|
||||
const elIdList = typeof elId === 'string' ? [elId] : elId
|
||||
const { id, props } = data
|
||||
const elIdList = typeof id === 'string' ? [id] : id
|
||||
|
||||
const slideIndex = state.slideIndex
|
||||
const slide = state.slides[slideIndex]
|
||||
const elements = slide.elements.map(el => {
|
||||
return elIdList.includes(el.elId) ? { ...el, ...props } : el
|
||||
return elIdList.includes(el.id) ? { ...el, ...props } : el
|
||||
})
|
||||
state.slides[slideIndex].elements = (elements as PPTElement[])
|
||||
},
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
export type ElementType = 'text' | 'image' | 'shape' | 'line' | 'chart' | 'table'
|
||||
export interface PPTElementShadow {
|
||||
h: number;
|
||||
v: number;
|
||||
blur: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export enum ElementTypes {
|
||||
TEXT = 'text',
|
||||
|
@ -10,58 +15,68 @@ export enum ElementTypes {
|
|||
}
|
||||
|
||||
export interface PPTElementBaseProps {
|
||||
elId: string;
|
||||
id: string;
|
||||
left: number;
|
||||
top: number;
|
||||
isLock?: boolean;
|
||||
lock?: boolean;
|
||||
groupId?: string;
|
||||
}
|
||||
|
||||
export interface PPTElementSizeProps {
|
||||
export interface PPTElementOutline {
|
||||
style?: 'dashed' | 'solid';
|
||||
width?: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface PPTTextElement extends PPTElementBaseProps {
|
||||
type: 'text';
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface PPTElementBorderProps {
|
||||
borderStyle?: string;
|
||||
borderWidth?: number;
|
||||
borderColor?: string;
|
||||
}
|
||||
|
||||
export interface PPTTextElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
||||
type: 'text';
|
||||
content: string;
|
||||
rotate?: number;
|
||||
outline?: PPTElementOutline;
|
||||
fill?: string;
|
||||
opacity?: number;
|
||||
lineHeight?: number;
|
||||
segmentSpacing?: number;
|
||||
letterSpacing?: number;
|
||||
shadow?: string;
|
||||
shadow?: PPTElementShadow;
|
||||
}
|
||||
|
||||
export interface PPTImageElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
||||
export interface ImageElementFilters {
|
||||
'blur': string;
|
||||
'brightness': string;
|
||||
'contrast': string;
|
||||
'grayscale': string;
|
||||
'saturate': string;
|
||||
'hue-rotate': string;
|
||||
'opacity': string;
|
||||
}
|
||||
export interface PPTImageElement extends PPTElementBaseProps {
|
||||
type: 'image';
|
||||
lockRatio: boolean;
|
||||
imgUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
fixedRatio: boolean;
|
||||
src: string;
|
||||
rotate?: number;
|
||||
filter?: string;
|
||||
outline?: PPTElementOutline;
|
||||
filters?: ImageElementFilters;
|
||||
clip?: {
|
||||
range: [[number, number], [number, number]];
|
||||
shape: 'rect' | 'roundRect' | 'ellipse' | 'triangle' | 'pentagon' | 'rhombus' | 'star';
|
||||
};
|
||||
flip?: { x?: number; y?: number };
|
||||
shadow?: string;
|
||||
shadow?: PPTElementShadow;
|
||||
}
|
||||
|
||||
export interface PPTShapeElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
||||
export interface PPTShapeElement extends PPTElementBaseProps {
|
||||
type: 'shape';
|
||||
width: number;
|
||||
height: number;
|
||||
svgCode: string;
|
||||
lockRatio: boolean;
|
||||
fixedRatio: boolean;
|
||||
fill: string;
|
||||
rotate?: number;
|
||||
outline?: PPTElementOutline;
|
||||
opacity?: number;
|
||||
shadow?: string;
|
||||
shadow?: PPTElementShadow;
|
||||
}
|
||||
|
||||
export interface PPTLineElement extends PPTElementBaseProps {
|
||||
|
@ -71,14 +86,17 @@ export interface PPTLineElement extends PPTElementBaseProps {
|
|||
width: number;
|
||||
style: string;
|
||||
color: string;
|
||||
marker: [string, string];
|
||||
points: [string, string];
|
||||
lineType: string;
|
||||
}
|
||||
|
||||
export interface PPTChartElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
|
||||
export interface PPTChartElement extends PPTElementBaseProps {
|
||||
type: 'chart';
|
||||
width: number;
|
||||
height: number;
|
||||
chartType: string;
|
||||
data: string;
|
||||
outline?: PPTElementOutline;
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
|
@ -88,8 +106,10 @@ export interface TableElementCell {
|
|||
content: string;
|
||||
bgColor: string;
|
||||
}
|
||||
export interface PPTTableElement extends PPTElementBaseProps, PPTElementSizeProps {
|
||||
export interface PPTTableElement extends PPTElementBaseProps {
|
||||
type: 'table';
|
||||
width: number;
|
||||
height: number;
|
||||
borderTheme?: string;
|
||||
theme?: string;
|
||||
rowSizes: number[];
|
||||
|
@ -97,12 +117,7 @@ export interface PPTTableElement extends PPTElementBaseProps, PPTElementSizeProp
|
|||
data: TableElementCell[][];
|
||||
}
|
||||
|
||||
export type PPTElement = PPTTextElement |
|
||||
PPTImageElement |
|
||||
PPTShapeElement |
|
||||
PPTLineElement |
|
||||
PPTChartElement |
|
||||
PPTTableElement
|
||||
export type PPTElement = PPTTextElement | PPTImageElement | PPTShapeElement | PPTLineElement | PPTChartElement | PPTTableElement
|
||||
|
||||
export interface PPTAnimation {
|
||||
elId: string;
|
||||
|
@ -110,9 +125,14 @@ export interface PPTAnimation {
|
|||
duration: number;
|
||||
}
|
||||
|
||||
export interface SlideBackground {
|
||||
type: 'solid' | 'image';
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Slide {
|
||||
id: string;
|
||||
elements: PPTElement[];
|
||||
background?: [string, string];
|
||||
background?: SlideBackground;
|
||||
animations?: PPTAnimation[];
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { PPTElement } from '@/types/slides'
|
||||
import { ElementTypes, PPTElement } from '@/types/slides'
|
||||
|
||||
// 获取矩形旋转后在画布中的位置范围
|
||||
interface RotatedElementData {
|
||||
|
@ -46,7 +46,7 @@ export const getRectRotatedRange = (element: RotatedElementData) => {
|
|||
export const getElementRange = (element: PPTElement) => {
|
||||
let minX, maxX, minY, maxY
|
||||
|
||||
if(element.type === 'line') {
|
||||
if(element.type === ElementTypes.LINE) {
|
||||
minX = element.left
|
||||
maxX = element.left + Math.max(element.start[0], element.end[0])
|
||||
minY = element.top
|
||||
|
|
|
@ -4,10 +4,10 @@ interface ImageSize {
|
|||
}
|
||||
|
||||
// 获取图片的原始宽高
|
||||
export const getImageSize = (imgUrl: string): Promise<ImageSize> => {
|
||||
export const getImageSize = (src: string): Promise<ImageSize> => {
|
||||
return new Promise(resolve => {
|
||||
const img = document.createElement('img')
|
||||
img.src = imgUrl
|
||||
img.src = src
|
||||
img.style.opacity = '0'
|
||||
document.body.appendChild(img)
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ export default defineComponent({
|
|||
const store = useStore<State>()
|
||||
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
const localActiveElementList = computed(() => props.elementList.filter(el => activeElementIdList.value.includes(el.elId)))
|
||||
const localActiveElementList = computed(() => props.elementList.filter(el => activeElementIdList.value.includes(el.id)))
|
||||
|
||||
const range = reactive({
|
||||
minX: 0,
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
import { Ref, computed, defineComponent } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { State } from '@/store'
|
||||
import { SlideBackground } from '@/types/slides'
|
||||
import GridLines from './GridLines.vue'
|
||||
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
|
||||
|
||||
|
@ -25,7 +26,7 @@ export default defineComponent({
|
|||
setup() {
|
||||
const store = useStore<State>()
|
||||
const showGridLines = computed(() => store.state.showGridLines)
|
||||
const background: Ref<[string, string] | undefined> = computed(() => store.getters.currentSlide.background)
|
||||
const background: Ref<SlideBackground | undefined> = computed(() => store.getters.currentSlide.background)
|
||||
|
||||
const { backgroundStyle } = useSlideBackgroundStyle(background)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ export default (
|
|||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
const dragElement = (e: MouseEvent, element: PPTElement) => {
|
||||
if(!activeElementIdList.value.includes(element.elId)) return
|
||||
if(!activeElementIdList.value.includes(element.id)) return
|
||||
let isMouseDown = true
|
||||
|
||||
// 可视范围宽高,用于边缘对齐吸附
|
||||
|
@ -27,7 +27,7 @@ export default (
|
|||
const edgeHeight = VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO
|
||||
|
||||
const originElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList.value))
|
||||
const originActiveElementList = originElementList.filter(el => activeElementIdList.value.includes(el.elId))
|
||||
const originActiveElementList = originElementList.filter(el => activeElementIdList.value.includes(el.id))
|
||||
|
||||
const sorptionRange = 3
|
||||
const elOriginLeft = element.left
|
||||
|
@ -40,7 +40,7 @@ export default (
|
|||
|
||||
let isMisoperation: boolean | null = null
|
||||
|
||||
const isActiveGroupElement = element.elId === activeGroupElementId.value
|
||||
const isActiveGroupElement = element.id === activeGroupElementId.value
|
||||
|
||||
// 收集对齐参考线
|
||||
// 包括页面内出被操作元素以外的所有元素在页面内水平和垂直方向的范围和中心位置、页面边界和水平和垂直的中心位置
|
||||
|
@ -50,8 +50,8 @@ export default (
|
|||
// 元素在页面内水平和垂直方向的范围和中心位置(需要特殊计算线条和被旋转的元素)
|
||||
for(const el of elementList.value) {
|
||||
if(el.type === ElementTypes.LINE) continue
|
||||
if(isActiveGroupElement && el.elId === element.elId) continue
|
||||
if(!isActiveGroupElement && activeElementIdList.value.includes(el.elId)) continue
|
||||
if(isActiveGroupElement && el.id === element.id) continue
|
||||
if(!isActiveGroupElement && activeElementIdList.value.includes(el.id)) continue
|
||||
|
||||
let left, top, width, height
|
||||
if('rotate' in el && el.rotate) {
|
||||
|
@ -145,7 +145,7 @@ export default (
|
|||
targetMinY = yRange[0]
|
||||
targetMaxY = yRange[1]
|
||||
}
|
||||
else if(element.type === 'line') {
|
||||
else if(element.type === ElementTypes.LINE) {
|
||||
targetMinX = targetLeft
|
||||
targetMaxX = targetLeft + Math.max(element.start[0], element.end[0])
|
||||
targetMinY = targetTop
|
||||
|
@ -179,7 +179,7 @@ export default (
|
|||
rightValues.push(xRange[1])
|
||||
bottomValues.push(yRange[1])
|
||||
}
|
||||
else if(element.type === 'line') {
|
||||
else if(element.type === ElementTypes.LINE) {
|
||||
leftValues.push(left)
|
||||
topValues.push(top)
|
||||
rightValues.push(left + Math.max(element.start[0], element.end[0]))
|
||||
|
@ -265,19 +265,19 @@ export default (
|
|||
// 非多选,或者当前操作的元素时激活的组合元素
|
||||
if(activeElementIdList.value.length === 1 || isActiveGroupElement) {
|
||||
elementList.value = elementList.value.map(el => {
|
||||
return el.elId === element.elId ? { ...el, left: targetLeft, top: targetTop } : el
|
||||
return el.id === element.id ? { ...el, left: targetLeft, top: targetTop } : el
|
||||
})
|
||||
}
|
||||
|
||||
// 修改元素位置,如果需要修改位置的元素不是被操作的元素(例如多选下的操作)
|
||||
// 那么其他非操作元素要移动的位置通过操作元素的移动偏移量计算
|
||||
else {
|
||||
const handleElement = elementList.value.find(el => el.elId === element.elId)
|
||||
const handleElement = elementList.value.find(el => el.id === element.id)
|
||||
if(!handleElement) return
|
||||
|
||||
elementList.value = elementList.value.map(el => {
|
||||
if(activeElementIdList.value.includes(el.elId)) {
|
||||
if(el.elId === element.elId) {
|
||||
if(activeElementIdList.value.includes(el.id)) {
|
||||
if(el.id === element.id) {
|
||||
return {
|
||||
...el,
|
||||
left: targetLeft,
|
||||
|
|
|
@ -110,19 +110,19 @@ export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | n
|
|||
}
|
||||
|
||||
// 被锁定的元素除外
|
||||
if(isInclude && !element.isLock) inRangeElementList.push(element)
|
||||
if(isInclude && !element.lock) inRangeElementList.push(element)
|
||||
}
|
||||
|
||||
// 对于组合元素成员,必须所有成员都在选择范围中才算被选中
|
||||
inRangeElementList = inRangeElementList.filter(inRangeElement => {
|
||||
if(inRangeElement.groupId) {
|
||||
const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.elId)
|
||||
const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.id)
|
||||
const groupElementList = elementList.value.filter(element => element.groupId === inRangeElement.groupId)
|
||||
return groupElementList.every(groupElement => inRangeElementIdList.includes(groupElement.elId))
|
||||
return groupElementList.every(groupElement => inRangeElementIdList.includes(groupElement.id))
|
||||
}
|
||||
return true
|
||||
})
|
||||
const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.elId)
|
||||
const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.id)
|
||||
if(inRangeElementIdList.length) store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, inRangeElementIdList)
|
||||
|
||||
mouseSelectionState.isShow = false
|
||||
|
|
|
@ -59,7 +59,7 @@ export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | n
|
|||
else if( angle < 0 && Math.abs(angle + 180) <= sorptionRange ) angle -= (angle + 180)
|
||||
|
||||
// 修改元素角度
|
||||
elementList.value = elementList.value.map(el => element.elId === el.elId ? { ...el, rotate: angle } : el)
|
||||
elementList.value = elementList.value.map(el => element.id === el.id ? { ...el, rotate: angle } : el)
|
||||
}
|
||||
|
||||
document.onmouseup = () => {
|
||||
|
|
|
@ -102,8 +102,8 @@ export default (
|
|||
const elOriginWidth = element.width
|
||||
const elOriginHeight = element.height
|
||||
|
||||
const isLockRatio = ctrlOrShiftKeyActive.value || ('lockRatio' in element && element.lockRatio)
|
||||
const lockRatio = elOriginWidth / elOriginHeight
|
||||
const fixedRatio = ctrlOrShiftKeyActive.value || ('fixedRatio' in element && element.fixedRatio)
|
||||
const aspectRatio = elOriginWidth / elOriginHeight
|
||||
|
||||
const elRotate = ('rotate' in element && element.rotate) ? element.rotate : 0
|
||||
const rotateRadian = Math.PI * elRotate / 180
|
||||
|
@ -133,13 +133,13 @@ export default (
|
|||
else {
|
||||
const edgeWidth = VIEWPORT_SIZE
|
||||
const edgeHeight = VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO
|
||||
const isActiveGroupElement = element.elId === activeGroupElementId.value
|
||||
const isActiveGroupElement = element.id === activeGroupElementId.value
|
||||
|
||||
for(const el of elementList.value) {
|
||||
if('rotate' in el && el.rotate) continue
|
||||
if(el.type === ElementTypes.LINE) continue
|
||||
if(isActiveGroupElement && el.elId === element.elId) continue
|
||||
if(!isActiveGroupElement && activeElementIdList.value.includes(el.elId)) continue
|
||||
if(isActiveGroupElement && el.id === element.id) continue
|
||||
if(!isActiveGroupElement && activeElementIdList.value.includes(el.id)) continue
|
||||
|
||||
const left = el.left
|
||||
const top = el.top
|
||||
|
@ -236,9 +236,9 @@ export default (
|
|||
let revisedY = (Math.cos(rotateRadian) * y - Math.sin(rotateRadian) * x) / canvasScale.value
|
||||
|
||||
// 锁定宽高比例
|
||||
if(isLockRatio) {
|
||||
if(command === OperatePoints.RIGHT_BOTTOM || command === OperatePoints.LEFT_TOP) revisedY = revisedX / lockRatio
|
||||
if(command === OperatePoints.LEFT_BOTTOM || command === OperatePoints.RIGHT_TOP) revisedY = -revisedX / lockRatio
|
||||
if(fixedRatio) {
|
||||
if(command === OperatePoints.RIGHT_BOTTOM || command === OperatePoints.LEFT_TOP) revisedY = revisedX / aspectRatio
|
||||
if(command === OperatePoints.LEFT_BOTTOM || command === OperatePoints.RIGHT_TOP) revisedY = -revisedX / aspectRatio
|
||||
}
|
||||
|
||||
// 根据不同的操作点分别计算元素缩放后的大小和位置
|
||||
|
@ -297,18 +297,18 @@ export default (
|
|||
let moveX = x / canvasScale.value
|
||||
let moveY = y / canvasScale.value
|
||||
|
||||
if(isLockRatio) {
|
||||
if(command === OperatePoints.RIGHT_BOTTOM || command === OperatePoints.LEFT_TOP) moveY = moveX / lockRatio
|
||||
if(command === OperatePoints.LEFT_BOTTOM || command === OperatePoints.RIGHT_TOP) moveY = -moveX / lockRatio
|
||||
if(fixedRatio) {
|
||||
if(command === OperatePoints.RIGHT_BOTTOM || command === OperatePoints.LEFT_TOP) moveY = moveX / aspectRatio
|
||||
if(command === OperatePoints.LEFT_BOTTOM || command === OperatePoints.RIGHT_TOP) moveY = -moveX / aspectRatio
|
||||
}
|
||||
|
||||
if(command === OperatePoints.RIGHT_BOTTOM) {
|
||||
const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + elOriginWidth + moveX, elOriginTop + elOriginHeight + moveY)
|
||||
moveX = moveX - offsetX
|
||||
moveY = moveY - offsetY
|
||||
if(isLockRatio) {
|
||||
if(offsetY) moveX = moveY * lockRatio
|
||||
else moveY = moveX / lockRatio
|
||||
if(fixedRatio) {
|
||||
if(offsetY) moveX = moveY * aspectRatio
|
||||
else moveY = moveX / aspectRatio
|
||||
}
|
||||
width = getSizeWithinRange(elOriginWidth + moveX)
|
||||
height = getSizeWithinRange(elOriginHeight + moveY)
|
||||
|
@ -317,9 +317,9 @@ export default (
|
|||
const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + moveX, elOriginTop + elOriginHeight + moveY)
|
||||
moveX = moveX - offsetX
|
||||
moveY = moveY - offsetY
|
||||
if(isLockRatio) {
|
||||
if(offsetY) moveX = -moveY * lockRatio
|
||||
else moveY = -moveX / lockRatio
|
||||
if(fixedRatio) {
|
||||
if(offsetY) moveX = -moveY * aspectRatio
|
||||
else moveY = -moveX / aspectRatio
|
||||
}
|
||||
width = getSizeWithinRange(elOriginWidth - moveX)
|
||||
height = getSizeWithinRange(elOriginHeight + moveY)
|
||||
|
@ -329,9 +329,9 @@ export default (
|
|||
const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + moveX, elOriginTop + moveY)
|
||||
moveX = moveX - offsetX
|
||||
moveY = moveY - offsetY
|
||||
if(isLockRatio) {
|
||||
if(offsetY) moveX = moveY * lockRatio
|
||||
else moveY = moveX / lockRatio
|
||||
if(fixedRatio) {
|
||||
if(offsetY) moveX = moveY * aspectRatio
|
||||
else moveY = moveX / aspectRatio
|
||||
}
|
||||
width = getSizeWithinRange(elOriginWidth - moveX)
|
||||
height = getSizeWithinRange(elOriginHeight - moveY)
|
||||
|
@ -342,9 +342,9 @@ export default (
|
|||
const { offsetX, offsetY } = alignedAdsorption(elOriginLeft + elOriginWidth + moveX, elOriginTop + moveY)
|
||||
moveX = moveX - offsetX
|
||||
moveY = moveY - offsetY
|
||||
if(isLockRatio) {
|
||||
if(offsetY) moveX = -moveY * lockRatio
|
||||
else moveY = -moveX / lockRatio
|
||||
if(fixedRatio) {
|
||||
if(offsetY) moveX = -moveY * aspectRatio
|
||||
else moveY = -moveX / aspectRatio
|
||||
}
|
||||
width = getSizeWithinRange(elOriginWidth + moveX)
|
||||
height = getSizeWithinRange(elOriginHeight - moveY)
|
||||
|
@ -374,7 +374,7 @@ export default (
|
|||
}
|
||||
}
|
||||
|
||||
elementList.value = elementList.value.map(el => element.elId === el.elId ? { ...el, left, top, width, height } : el)
|
||||
elementList.value = elementList.value.map(el => element.id === el.id ? { ...el, left, top, width, height } : el)
|
||||
}
|
||||
|
||||
document.onmouseup = e => {
|
||||
|
@ -396,7 +396,7 @@ export default (
|
|||
const { minX, maxX, minY, maxY } = range
|
||||
const operateWidth = maxX - minX
|
||||
const operateHeight = maxY - minY
|
||||
const lockRatio = operateWidth / operateHeight
|
||||
const aspectRatio = operateWidth / operateHeight
|
||||
|
||||
const startPageX = e.pageX
|
||||
const startPageY = e.pageY
|
||||
|
@ -415,8 +415,8 @@ export default (
|
|||
|
||||
// 锁定宽高比例
|
||||
if(ctrlOrShiftKeyActive.value) {
|
||||
if(command === OperatePoints.RIGHT_BOTTOM || command === OperatePoints.LEFT_TOP) y = x / lockRatio
|
||||
if(command === OperatePoints.LEFT_BOTTOM || command === OperatePoints.RIGHT_TOP) y = -x / lockRatio
|
||||
if(command === OperatePoints.RIGHT_BOTTOM || command === OperatePoints.LEFT_TOP) y = x / aspectRatio
|
||||
if(command === OperatePoints.LEFT_BOTTOM || command === OperatePoints.RIGHT_TOP) y = -x / aspectRatio
|
||||
}
|
||||
|
||||
// 获取鼠标缩放时当前所有激活元素的范围
|
||||
|
@ -468,8 +468,8 @@ export default (
|
|||
// 根据上面计算的比例,修改所有被激活元素的位置大小
|
||||
// 宽高通过乘以对应的比例得到,位置通过将被操作元素在所有元素整体中的相对位置乘以对应比例获得
|
||||
elementList.value = elementList.value.map(el => {
|
||||
if((el.type === ElementTypes.IMAGE || el.type === ElementTypes.SHAPE) && activeElementIdList.value.includes(el.elId)) {
|
||||
const originElement = originElementList.find(originEl => originEl.elId === el.elId) as PPTImageElement | PPTShapeElement
|
||||
if((el.type === ElementTypes.IMAGE || el.type === ElementTypes.SHAPE) && activeElementIdList.value.includes(el.id)) {
|
||||
const originElement = originElementList.find(originEl => originEl.id === el.id) as PPTImageElement | PPTShapeElement
|
||||
return {
|
||||
...el,
|
||||
width: originElement.width * widthScale,
|
||||
|
|
|
@ -19,25 +19,25 @@ export default (
|
|||
if(!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
|
||||
|
||||
// 如果被点击的元素处于未激活状态,则将他设置为激活元素(单选),或者加入到激活元素中(多选)
|
||||
if(!activeElementIdList.value.includes(element.elId)) {
|
||||
if(!activeElementIdList.value.includes(element.id)) {
|
||||
let newActiveIdList: string[] = []
|
||||
|
||||
if(ctrlOrShiftKeyActive.value) {
|
||||
newActiveIdList = [...activeElementIdList.value, element.elId]
|
||||
newActiveIdList = [...activeElementIdList.value, element.id]
|
||||
}
|
||||
else newActiveIdList = [element.elId]
|
||||
else newActiveIdList = [element.id]
|
||||
|
||||
// 同时如果该元素是分组成员,需要将和他同组的元素一起激活
|
||||
if(element.groupId) {
|
||||
const groupMembersId: string[] = []
|
||||
elementList.value.forEach((el: PPTElement) => {
|
||||
if(el.groupId === element.groupId) groupMembersId.push(el.elId)
|
||||
if(el.groupId === element.groupId) groupMembersId.push(el.id)
|
||||
})
|
||||
newActiveIdList = [...newActiveIdList, ...groupMembersId]
|
||||
}
|
||||
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, uniq(newActiveIdList))
|
||||
store.commit(MutationTypes.SET_HANDLE_ELEMENT_ID, element.elId)
|
||||
store.commit(MutationTypes.SET_HANDLE_ELEMENT_ID, element.id)
|
||||
}
|
||||
|
||||
// 如果被点击的元素已激活,且按下了多选按钮,则取消其激活状态(除非该元素或分组是最后的一个激活元素)
|
||||
|
@ -48,12 +48,12 @@ export default (
|
|||
if(element.groupId) {
|
||||
const groupMembersId: string[] = []
|
||||
elementList.value.forEach((el: PPTElement) => {
|
||||
if(el.groupId === element.groupId) groupMembersId.push(el.elId)
|
||||
if(el.groupId === element.groupId) groupMembersId.push(el.id)
|
||||
})
|
||||
newActiveIdList = activeElementIdList.value.filter(elId => !groupMembersId.includes(elId))
|
||||
newActiveIdList = activeElementIdList.value.filter(id => !groupMembersId.includes(id))
|
||||
}
|
||||
else {
|
||||
newActiveIdList = activeElementIdList.value.filter(elId => elId !== element.elId)
|
||||
newActiveIdList = activeElementIdList.value.filter(id => id !== element.id)
|
||||
}
|
||||
|
||||
if(newActiveIdList.length > 0) {
|
||||
|
@ -62,11 +62,11 @@ export default (
|
|||
}
|
||||
|
||||
// 如果被点击的元素已激活,且没有按下多选按钮,且该元素不是当前操作元素,则将其设置为当前操作元素
|
||||
else if(handleElementId.value !== element.elId) {
|
||||
store.commit(MutationTypes.SET_HANDLE_ELEMENT_ID, element.elId)
|
||||
else if(handleElementId.value !== element.id) {
|
||||
store.commit(MutationTypes.SET_HANDLE_ELEMENT_ID, element.id)
|
||||
}
|
||||
|
||||
else if(activeGroupElementId.value !== element.elId && element.groupId) {
|
||||
else if(activeGroupElementId.value !== element.id && element.groupId) {
|
||||
const startPageX = e.pageX
|
||||
const startPageY = e.pageY
|
||||
|
||||
|
@ -75,7 +75,7 @@ export default (
|
|||
const currentPageY = e.pageY
|
||||
|
||||
if(startPageX === currentPageX && startPageY === currentPageY) {
|
||||
activeGroupElementId.value = element.elId
|
||||
activeGroupElementId.value = element.id
|
||||
;(e.target as HTMLElement).onmouseup = null
|
||||
}
|
||||
}
|
||||
|
@ -85,8 +85,8 @@ export default (
|
|||
}
|
||||
|
||||
const selectAllElement = () => {
|
||||
const unlockedElements = elementList.value.filter(el => !el.isLock)
|
||||
const newActiveElementIdList = unlockedElements.map(el => el.elId)
|
||||
const unlockedElements = elementList.value.filter(el => !el.lock)
|
||||
const newActiveElementIdList = unlockedElements.map(el => el.id)
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, newActiveElementIdList)
|
||||
}
|
||||
|
||||
|
|
|
@ -42,12 +42,12 @@
|
|||
|
||||
<EditableElement
|
||||
v-for="(element, index) in elementList"
|
||||
:key="element.elId"
|
||||
:key="element.id"
|
||||
:elementInfo="element"
|
||||
:elementIndex="index + 1"
|
||||
:isActive="activeElementIdList.includes(element.elId)"
|
||||
:isHandleEl="element.elId === handleElementId"
|
||||
:isActiveGroupElement="activeGroupElementId === element.elId"
|
||||
:isActive="activeElementIdList.includes(element.id)"
|
||||
:isHandleEl="element.id === handleElementId"
|
||||
:isActiveGroupElement="activeGroupElementId === element.id"
|
||||
:isMultiSelect="activeElementIdList.length > 1"
|
||||
:selectElement="selectElement"
|
||||
:rotateElement="rotateElement"
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<div class="background" :style="{ ...backgroundStyle }"></div>
|
||||
<BaseElement
|
||||
v-for="(element, index) in slide.elements"
|
||||
:key="element.elId"
|
||||
:key="element.id"
|
||||
:elementInfo="element"
|
||||
:elementIndex="index + 1"
|
||||
/>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div
|
||||
class="editable-element"
|
||||
ref="elementRef"
|
||||
:id="'editable-element-' + elementInfo.elId"
|
||||
:id="'editable-element-' + elementInfo.id"
|
||||
:style="{ zIndex: elementIndex }"
|
||||
>
|
||||
<component
|
||||
|
@ -105,7 +105,7 @@ export default defineComponent({
|
|||
const { copyElement, cutElement } = useCopyAndPasteElement()
|
||||
|
||||
const contextmenus = (): ContextmenuItem[] => {
|
||||
if(props.elementInfo.isLock) {
|
||||
if(props.elementInfo.lock) {
|
||||
return [{
|
||||
text: '解锁',
|
||||
icon: 'icon-unlock',
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
<template>
|
||||
<SvgWrapper
|
||||
class="element-border"
|
||||
overflow="visible"
|
||||
:width="width"
|
||||
:height="height"
|
||||
>
|
||||
<path
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin
|
||||
fill="transparent"
|
||||
:d="`M0,0 L${width},0 L${width},${height} L0,${height} Z`"
|
||||
:stroke="borderColor"
|
||||
:stroke-width="borderWidth"
|
||||
:stroke-dasharray="borderStyle === 'dashed' ? '12 9' : '0 0'"
|
||||
></path>
|
||||
</SvgWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
|
||||
export default {
|
||||
name: 'element-border',
|
||||
components: {
|
||||
SvgWrapper,
|
||||
},
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
},
|
||||
borderWidth: {
|
||||
type: Number,
|
||||
},
|
||||
borderStyle: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
svg {
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<SvgWrapper
|
||||
class="element-outline"
|
||||
overflow="visible"
|
||||
:width="width"
|
||||
:height="height"
|
||||
>
|
||||
<path
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin
|
||||
fill="transparent"
|
||||
:d="`M0,0 L${width},0 L${width},${height} L0,${height} Z`"
|
||||
:stroke="outlineColor"
|
||||
:stroke-width="outlineWidth"
|
||||
:stroke-dasharray="outlineStyle === 'dashed' ? '12 9' : '0 0'"
|
||||
></path>
|
||||
</SvgWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, toRef } from 'vue'
|
||||
import { PPTElementOutline } from '@/types/slides'
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
import useElementOutline from '@/views/_common/_element/hooks/useElementOutline'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'element-outline',
|
||||
components: {
|
||||
SvgWrapper,
|
||||
},
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
outline: {
|
||||
type: Object as PropType<PPTElementOutline>
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const {
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
} = useElementOutline(toRef(props, 'outline'))
|
||||
|
||||
return {
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
svg {
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
|
@ -12,40 +12,34 @@
|
|||
<div
|
||||
class="element-content"
|
||||
:style="{
|
||||
filter: elementInfo.shadow ? `drop-shadow(${elementInfo.shadow})` : '',
|
||||
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
|
||||
transform: flip,
|
||||
}"
|
||||
>
|
||||
<ImageRectBorder
|
||||
<ImageRectOutline
|
||||
v-if="clipShape.type === 'rect'"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:radius="clipShape.radius"
|
||||
:borderColor="elementInfo.borderColor"
|
||||
:borderWidth="elementInfo.borderWidth"
|
||||
:borderStyle="elementInfo.borderStyle"
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
<ImageEllipseBorder
|
||||
<ImageEllipseOutline
|
||||
v-else-if="clipShape.type === 'ellipse'"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:borderColor="elementInfo.borderColor"
|
||||
:borderWidth="elementInfo.borderWidth"
|
||||
:borderStyle="elementInfo.borderStyle"
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
<ImagePolygonBorder
|
||||
<ImagePolygonOutline
|
||||
v-else-if="clipShape.type === 'polygon'"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:createPath="clipShape.createPath"
|
||||
:borderColor="elementInfo.borderColor"
|
||||
:borderWidth="elementInfo.borderWidth"
|
||||
:borderStyle="elementInfo.borderStyle"
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
|
||||
<div class="img-wrapper" :style="{ clipPath: clipShape.style }">
|
||||
<img
|
||||
:src="elementInfo.imgUrl"
|
||||
:src="elementInfo.src"
|
||||
:draggable="false"
|
||||
:style="{
|
||||
top: imgPosition.top,
|
||||
|
@ -67,16 +61,18 @@ import { computed, defineComponent, PropType } from 'vue'
|
|||
import { PPTImageElement } from '@/types/slides'
|
||||
import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
|
||||
|
||||
import ImageRectBorder from './ImageRectBorder.vue'
|
||||
import ImageEllipseBorder from './ImageEllipseBorder.vue'
|
||||
import ImagePolygonBorder from './ImagePolygonBorder.vue'
|
||||
import ImageRectOutline from './ImageRectOutline.vue'
|
||||
import ImageEllipseOutline from './ImageEllipseOutline.vue'
|
||||
import ImagePolygonOutline from './ImagePolygonOutline.vue'
|
||||
|
||||
import useElementShadow from '@/views/_common/_element/hooks/useElementShadow'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'base-element-image',
|
||||
components: {
|
||||
ImageRectBorder,
|
||||
ImageEllipseBorder,
|
||||
ImagePolygonBorder,
|
||||
ImageRectOutline,
|
||||
ImageEllipseOutline,
|
||||
ImagePolygonOutline,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
|
@ -118,10 +114,10 @@ export default defineComponent({
|
|||
})
|
||||
|
||||
const filter = computed(() => {
|
||||
if(!props.elementInfo.filter) return ''
|
||||
if(!props.elementInfo.filters) return ''
|
||||
let filter = ''
|
||||
for(const key of Object.keys(props.elementInfo.filter)) {
|
||||
filter += `${key}(${props.elementInfo.filter[key]}) `
|
||||
for(const key of Object.keys(props.elementInfo.filters)) {
|
||||
filter += `${key}(${props.elementInfo.filters[key]}) `
|
||||
}
|
||||
return filter
|
||||
})
|
||||
|
@ -135,11 +131,15 @@ export default defineComponent({
|
|||
return ''
|
||||
})
|
||||
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
return {
|
||||
imgPosition,
|
||||
clipShape,
|
||||
filter,
|
||||
flip,
|
||||
shadowStyle,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
>
|
||||
<img
|
||||
class="bottom-img"
|
||||
:src="imgUrl"
|
||||
:src="src"
|
||||
:draggable="false"
|
||||
alt=""
|
||||
:style="bottomImgPositionStyle"
|
||||
|
@ -21,7 +21,7 @@
|
|||
>
|
||||
<img
|
||||
class="top-img"
|
||||
:src="imgUrl"
|
||||
:src="src"
|
||||
:draggable="false"
|
||||
alt=""
|
||||
:style="topImgPositionStyle"
|
||||
|
@ -78,7 +78,7 @@ export default defineComponent({
|
|||
SvgWrapper,
|
||||
},
|
||||
props: {
|
||||
imgUrl: {
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
<template>
|
||||
<SvgWrapper
|
||||
class="image-ellipse-border"
|
||||
overflow="visible"
|
||||
:width="width"
|
||||
:height="height"
|
||||
>
|
||||
<ellipse
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin
|
||||
fill="transparent"
|
||||
:cx="width / 2"
|
||||
:cy="height / 2"
|
||||
:rx="width / 2"
|
||||
:ry="height / 2"
|
||||
:stroke="borderColor"
|
||||
:stroke-width="borderWidth"
|
||||
:stroke-dasharray="borderStyle === 'dashed' ? '12 9' : '0 0'"
|
||||
></ellipse>
|
||||
</SvgWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
|
||||
export default {
|
||||
name: 'image-ellipse-border',
|
||||
components: {
|
||||
SvgWrapper,
|
||||
},
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
borderWidth: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
borderStyle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
svg {
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<SvgWrapper
|
||||
class="image-ellipse-outline"
|
||||
overflow="visible"
|
||||
:width="width"
|
||||
:height="height"
|
||||
>
|
||||
<ellipse
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin
|
||||
fill="transparent"
|
||||
:cx="width / 2"
|
||||
:cy="height / 2"
|
||||
:rx="width / 2"
|
||||
:ry="height / 2"
|
||||
:stroke="outlineColor"
|
||||
:stroke-width="outlineWidth"
|
||||
:stroke-dasharray="outlineStyle === 'dashed' ? '12 9' : '0 0'"
|
||||
></ellipse>
|
||||
</SvgWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, toRef } from 'vue'
|
||||
import { PPTElementOutline } from '@/types/slides'
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
import useElementOutline from '@/views/_common/_element/hooks/useElementOutline'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'image-ellipse-outline',
|
||||
components: {
|
||||
SvgWrapper,
|
||||
},
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
outline: {
|
||||
type: Object as PropType<PPTElementOutline>
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const {
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
} = useElementOutline(toRef(props, 'outline'))
|
||||
|
||||
return {
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
svg {
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,67 +0,0 @@
|
|||
<template>
|
||||
<SvgWrapper
|
||||
class="image-polygon-border"
|
||||
overflow="visible"
|
||||
:width="width"
|
||||
:height="height"
|
||||
>
|
||||
<path
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin
|
||||
fill="transparent"
|
||||
:d="createPath(width, height)"
|
||||
:stroke="borderColor"
|
||||
:stroke-width="borderWidth"
|
||||
:stroke-dasharray="borderStyle === 'dashed' ? '12 9' : '0 0'"
|
||||
></path>
|
||||
</SvgWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
|
||||
export default {
|
||||
name: 'image-polygon-border',
|
||||
components: {
|
||||
SvgWrapper,
|
||||
},
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
borderWidth: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
borderStyle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
createPath: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
svg {
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<SvgWrapper
|
||||
class="image-polygon-outline"
|
||||
overflow="visible"
|
||||
:width="width"
|
||||
:height="height"
|
||||
>
|
||||
<path
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin
|
||||
fill="transparent"
|
||||
:d="createPath(width, height)"
|
||||
:stroke="outlineColor"
|
||||
:stroke-width="outlineWidth"
|
||||
:stroke-dasharray="outlineStyle === 'dashed' ? '12 9' : '0 0'"
|
||||
></path>
|
||||
</SvgWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, toRef } from 'vue'
|
||||
import { PPTElementOutline } from '@/types/slides'
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
import useElementOutline from '@/views/_common/_element/hooks/useElementOutline'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'image-polygon-outline',
|
||||
components: {
|
||||
SvgWrapper,
|
||||
},
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
outline: {
|
||||
type: Object as PropType<PPTElementOutline>
|
||||
},
|
||||
createPath: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const {
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
} = useElementOutline(toRef(props, 'outline'))
|
||||
|
||||
return {
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
svg {
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<SvgWrapper
|
||||
class="image-rect-border"
|
||||
class="image-rect-outline"
|
||||
overflow="visible"
|
||||
:width="width"
|
||||
:height="height"
|
||||
|
@ -15,18 +15,21 @@
|
|||
:ry="radius"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:stroke="borderColor"
|
||||
:stroke-width="borderWidth"
|
||||
:stroke-dasharray="borderStyle === 'dashed' ? '12 9' : '0 0'"
|
||||
:stroke="outlineColor"
|
||||
:stroke-width="outlineWidth"
|
||||
:stroke-dasharray="outlineStyle === 'dashed' ? '12 9' : '0 0'"
|
||||
></rect>
|
||||
</SvgWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, toRef } from 'vue'
|
||||
import { PPTElementOutline } from '@/types/slides'
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
import useElementOutline from '@/views/_common/_element/hooks/useElementOutline'
|
||||
|
||||
export default {
|
||||
name: 'image-rect-border',
|
||||
export default defineComponent({
|
||||
name: 'image-rect-outline',
|
||||
components: {
|
||||
SvgWrapper,
|
||||
},
|
||||
|
@ -39,24 +42,28 @@ export default {
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
borderWidth: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
borderStyle: {
|
||||
type: String,
|
||||
default: '',
|
||||
outline: {
|
||||
type: Object as PropType<PPTElementOutline>
|
||||
},
|
||||
radius: {
|
||||
type: String,
|
||||
default: '0',
|
||||
},
|
||||
},
|
||||
}
|
||||
setup(props) {
|
||||
const {
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
} = useElementOutline(toRef(props, 'outline'))
|
||||
|
||||
return {
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="editable-element image"
|
||||
:class="{ 'lock': elementInfo.isLock }"
|
||||
:class="{ 'lock': elementInfo.lock }"
|
||||
:style="{
|
||||
top: elementInfo.top + 'px',
|
||||
left: elementInfo.left + 'px',
|
||||
|
@ -13,7 +13,7 @@
|
|||
>
|
||||
<ImageClip
|
||||
v-if="isCliping"
|
||||
:imgUrl="elementInfo.imgUrl"
|
||||
:src="elementInfo.src"
|
||||
:clipData="elementInfo.clip"
|
||||
:canvasScale="canvasScale"
|
||||
:width="elementInfo.width"
|
||||
|
@ -29,40 +29,34 @@
|
|||
v-if="!isCliping"
|
||||
v-contextmenu="contextmenus"
|
||||
:style="{
|
||||
filter: elementInfo.shadow ? `drop-shadow(${elementInfo.shadow})` : '',
|
||||
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
|
||||
transform: flip,
|
||||
}"
|
||||
>
|
||||
<ImageRectBorder
|
||||
<ImageRectOutline
|
||||
v-if="clipShape.type === 'rect'"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:radius="clipShape.radius"
|
||||
:borderColor="elementInfo.borderColor"
|
||||
:borderWidth="elementInfo.borderWidth"
|
||||
:borderStyle="elementInfo.borderStyle"
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
<ImageEllipseBorder
|
||||
<ImageEllipseOutline
|
||||
v-else-if="clipShape.type === 'ellipse'"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:borderColor="elementInfo.borderColor"
|
||||
:borderWidth="elementInfo.borderWidth"
|
||||
:borderStyle="elementInfo.borderStyle"
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
<ImagePolygonBorder
|
||||
<ImagePolygonOutline
|
||||
v-else-if="clipShape.type === 'polygon'"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:outline="elementInfo.outline"
|
||||
:createPath="clipShape.createPath"
|
||||
:borderColor="elementInfo.borderColor"
|
||||
:borderWidth="elementInfo.borderWidth"
|
||||
:borderStyle="elementInfo.borderStyle"
|
||||
/>
|
||||
|
||||
<div class="img-wrapper" :style="{clipPath: clipShape.style}">
|
||||
<img
|
||||
:src="elementInfo.imgUrl"
|
||||
:src="elementInfo.src"
|
||||
:draggable="false"
|
||||
:style="{
|
||||
top: imgPosition.top,
|
||||
|
@ -93,7 +87,7 @@
|
|||
:type="line.type"
|
||||
:style="line.style"
|
||||
/>
|
||||
<template v-if="!elementInfo.isLock && (isActiveGroupElement || !isMultiSelect)">
|
||||
<template v-if="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)">
|
||||
<ResizablePoint
|
||||
class="el-resizable-point"
|
||||
v-for="point in resizablePoints"
|
||||
|
@ -119,7 +113,7 @@ import { computed, defineComponent, ref, PropType } from 'vue'
|
|||
|
||||
import { PPTImageElement } from '@/types/slides'
|
||||
import { ElementScaleHandler } from '@/types/edit'
|
||||
import useCommonOperate from '@/views/_common/_element/useCommonOperate'
|
||||
import useCommonOperate from '@/views/_common/_element/hooks/useCommonOperate'
|
||||
|
||||
import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
|
||||
|
||||
|
@ -129,9 +123,11 @@ import BorderLine from '@/views/_common/_operate/BorderLine.vue'
|
|||
import AnimationIndex from '@/views/_common/_operate/AnimationIndex.vue'
|
||||
|
||||
import ImageClip, { ClipedEmitData } from './ImageClipHandler.vue'
|
||||
import ImageRectBorder from './ImageRectBorder.vue'
|
||||
import ImageEllipseBorder from './ImageEllipseBorder.vue'
|
||||
import ImagePolygonBorder from './ImagePolygonBorder.vue'
|
||||
import ImageRectOutline from './ImageRectOutline.vue'
|
||||
import ImageEllipseOutline from './ImageEllipseOutline.vue'
|
||||
import ImagePolygonOutline from './ImagePolygonOutline.vue'
|
||||
|
||||
import useElementShadow from '@/views/_common/_element/hooks/useElementShadow'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editable-element-image',
|
||||
|
@ -141,9 +137,9 @@ export default defineComponent({
|
|||
BorderLine,
|
||||
AnimationIndex,
|
||||
ImageClip,
|
||||
ImageRectBorder,
|
||||
ImageEllipseBorder,
|
||||
ImagePolygonBorder,
|
||||
ImageRectOutline,
|
||||
ImageEllipseOutline,
|
||||
ImagePolygonOutline,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
|
@ -198,7 +194,7 @@ export default defineComponent({
|
|||
|
||||
const { resizablePoints, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||
|
||||
const isCliping = computed(() => clipingImageElId.value === props.elementInfo.elId)
|
||||
const isCliping = computed(() => clipingImageElId.value === props.elementInfo.id)
|
||||
|
||||
const imgPosition = computed(() => {
|
||||
if(!props.elementInfo || !props.elementInfo.clip) {
|
||||
|
@ -233,10 +229,10 @@ export default defineComponent({
|
|||
})
|
||||
|
||||
const filter = computed(() => {
|
||||
if(!props.elementInfo.filter) return ''
|
||||
if(!props.elementInfo.filters) return ''
|
||||
let filter = ''
|
||||
for(const key of Object.keys(props.elementInfo.filter)) {
|
||||
filter += `${key}(${props.elementInfo.filter[key]}) `
|
||||
for(const key of Object.keys(props.elementInfo.filters)) {
|
||||
filter += `${key}(${props.elementInfo.filters[key]}) `
|
||||
}
|
||||
return filter
|
||||
})
|
||||
|
@ -250,8 +246,11 @@ export default defineComponent({
|
|||
return ''
|
||||
})
|
||||
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
if(isCliping.value || props.elementInfo.isLock) return
|
||||
if(isCliping.value || props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
props.selectElement(e, props.elementInfo)
|
||||
}
|
||||
|
@ -283,6 +282,7 @@ export default defineComponent({
|
|||
borderLines,
|
||||
filter,
|
||||
flip,
|
||||
shadowStyle,
|
||||
handleSelectElement,
|
||||
clip,
|
||||
}
|
||||
|
|
|
@ -12,17 +12,13 @@
|
|||
:style="{
|
||||
backgroundColor: elementInfo.fill,
|
||||
opacity: elementInfo.opacity,
|
||||
textShadow: elementInfo.shadow,
|
||||
lineHeight: elementInfo.lineHeight,
|
||||
letterSpacing: (elementInfo.letterSpacing || 0) + 'px',
|
||||
textShadow: shadowStyle,
|
||||
}"
|
||||
>
|
||||
<ElementBorder
|
||||
<ElementOutline
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:borderColor="elementInfo.borderColor"
|
||||
:borderWidth="elementInfo.borderWidth"
|
||||
:borderStyle="elementInfo.borderStyle"
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
<div class="text-content"
|
||||
v-html="elementInfo.content"
|
||||
|
@ -32,14 +28,16 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import { defineComponent, PropType, computed } from 'vue'
|
||||
import { PPTTextElement } from '@/types/slides'
|
||||
import ElementBorder from '@/views/_common/_element/ElementBorder.vue'
|
||||
import ElementOutline from '@/views/_common/_element/ElementOutline.vue'
|
||||
|
||||
import useElementShadow from '@/views/_common/_element/hooks/useElementShadow'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'base-element-text',
|
||||
components: {
|
||||
ElementBorder,
|
||||
ElementOutline,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
|
@ -47,6 +45,14 @@ export default defineComponent({
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
return {
|
||||
shadowStyle,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -58,6 +64,7 @@ export default defineComponent({
|
|||
.element-content {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
line-height: 1.5;
|
||||
|
||||
.text-content {
|
||||
position: relative;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="editable-element text"
|
||||
:class="{ 'lock': elementInfo.isLock }"
|
||||
:class="{ 'lock': elementInfo.lock }"
|
||||
:style="{
|
||||
top: elementInfo.top + 'px',
|
||||
left: elementInfo.left + 'px',
|
||||
|
@ -14,22 +14,18 @@
|
|||
:style="{
|
||||
backgroundColor: elementInfo.fill,
|
||||
opacity: elementInfo.opacity,
|
||||
textShadow: elementInfo.shadow,
|
||||
lineHeight: elementInfo.lineHeight,
|
||||
letterSpacing: (elementInfo.letterSpacing || 0) + 'px',
|
||||
textShadow: shadowStyle,
|
||||
}"
|
||||
v-contextmenu="contextmenus"
|
||||
>
|
||||
<ElementBorder
|
||||
<ElementOutline
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:borderColor="elementInfo.borderColor"
|
||||
:borderWidth="elementInfo.borderWidth"
|
||||
:borderStyle="elementInfo.borderStyle"
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
<div class="text-content"
|
||||
v-html="elementInfo.content"
|
||||
:contenteditable="isActive && !elementInfo.isLock"
|
||||
:contenteditable="isActive && !elementInfo.lock"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
|
@ -52,7 +48,7 @@
|
|||
:isWide="true"
|
||||
@mousedown="handleSelectElement($event)"
|
||||
/>
|
||||
<template v-if="!elementInfo.isLock && (isActiveGroupElement || !isMultiSelect)">
|
||||
<template v-if="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)">
|
||||
<ResizablePoint class="el-resizable-point"
|
||||
v-for="point in resizablePoints"
|
||||
:key="point.type"
|
||||
|
@ -77,18 +73,20 @@ import { computed, defineComponent, PropType } from 'vue'
|
|||
|
||||
import { PPTTextElement } from '@/types/slides'
|
||||
import { ElementScaleHandler } from '@/types/edit'
|
||||
import useCommonOperate from '@/views/_common/_element/useCommonOperate'
|
||||
import useCommonOperate from '@/views/_common/_element/hooks/useCommonOperate'
|
||||
|
||||
import ElementBorder from '@/views/_common/_element/ElementBorder.vue'
|
||||
import ElementOutline from '@/views/_common/_element/ElementOutline.vue'
|
||||
import RotateHandler from '@/views/_common/_operate/RotateHandler.vue'
|
||||
import ResizablePoint from '@/views/_common/_operate/ResizablePoint.vue'
|
||||
import BorderLine from '@/views/_common/_operate/BorderLine.vue'
|
||||
import AnimationIndex from '@/views/_common/_operate/AnimationIndex.vue'
|
||||
|
||||
import useElementShadow from '@/views/_common/_element/hooks/useElementShadow'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editable-element-text',
|
||||
components: {
|
||||
ElementBorder,
|
||||
ElementOutline,
|
||||
RotateHandler,
|
||||
ResizablePoint,
|
||||
BorderLine,
|
||||
|
@ -146,17 +144,21 @@ export default defineComponent({
|
|||
const { resizablePoints, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||
|
||||
const handleSelectElement = (e: MouseEvent, canMove = true) => {
|
||||
if(props.elementInfo.isLock) return
|
||||
if(props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
props.selectElement(e, props.elementInfo, canMove)
|
||||
}
|
||||
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
return {
|
||||
scaleWidth,
|
||||
resizablePoints,
|
||||
borderLines,
|
||||
handleSelectElement,
|
||||
shadowStyle,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -182,6 +184,7 @@ export default defineComponent({
|
|||
.element-content {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
line-height: 1.5;
|
||||
|
||||
.text-content {
|
||||
position: relative;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { computed, Ref } from 'vue'
|
||||
import { PPTElementOutline } from '@/types/slides'
|
||||
|
||||
export default (outline: Ref<PPTElementOutline | undefined>) => {
|
||||
const outlineWidth = computed(() => (outline.value && outline.value.width !== undefined) ? outline.value.width : 0)
|
||||
const outlineStyle = computed(() => (outline.value && outline.value.style !== undefined) ? outline.value.style : 'solid')
|
||||
const outlineColor = computed(() => (outline.value && outline.value.color !== undefined) ? outline.value.color : '#41464b')
|
||||
|
||||
return {
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { Ref } from 'vue'
|
||||
import { PPTElementShadow } from '@/types/slides'
|
||||
|
||||
export default (shadow: Ref<PPTElementShadow | undefined>) => {
|
||||
let shadowStyle = ''
|
||||
if(shadow.value) {
|
||||
const { h, v, blur, color } = shadow.value
|
||||
shadowStyle = `${h} ${v} ${blur} ${color}`
|
||||
}
|
||||
|
||||
return {
|
||||
shadowStyle,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue