feat: moveable 迁移到vue3

This commit is contained in:
zackxizi 2024-04-04 09:46:10 +08:00
parent 50d193c067
commit 2ae7b0fd4c
33 changed files with 5872 additions and 2258 deletions

3
.gitignore vendored
View File

@ -25,3 +25,6 @@ pnpm-debug.log*
*.njsproj
*.sln
*.sw?
**/dist/

View File

@ -1,7 +1,7 @@
// .prettierrc.js 代码美化规则配置
module.exports = {
// 一行最多 n 字符
printWidth: 1000,
printWidth: 200,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格

13
.vscode/settings.json vendored
View File

@ -9,5 +9,16 @@
},
"css.validate": false,
"less.validate": false,
"scss.validate": false
"scss.validate": false,
"[vue]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"cSpell.words": [
"clippable",
"pinchable",
"pinia",
"snappable",
"ungroup",
"warpable"
]
}

2038
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -26,17 +26,18 @@
"fontfaceobserver": "^2.1.0",
"html2canvas": "^1.4.1",
"mitt": "^3.0.1",
"moveable": "^0.26.0",
"moveable-helper": "^0.4.0",
"nanoid": "^3.1.23",
"normalize.css": "^8.0.1",
"pinia": "^2.1.7",
"prepared": "^0.1.1",
"qr-code-styling": "^1.6.0-rc.1",
"selecto": "^1.13.0",
"selecto": "^1.26.3",
"throttle-debounce": "^3.0.1",
"vite-plugin-compression": "^0.5.1",
"vue": "3.4.19",
"vue-router": "^4.0.0-0",
"vue3-moveable": "^0.28.0",
"vue3-selecto": "^1.12.3",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
@ -53,6 +54,7 @@
"eslint-config-alloy": "~4.1.0",
"eslint-plugin-vue": "^7.12.1",
"less": "^4.1.1",
"prettier-eslint": "^16.3.0",
"terser": "^5.28.1",
"typescript": "^5.2.2",
"unplugin-element-plus": "^0.7.1",

3096
screenshot/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -47,7 +47,7 @@ export type TGetFontParam = {
/** 字体item数据 */
export type TGetFontItemData = {
id: number
id: number | string
alias: string
oid: string
value: string

File diff suppressed because it is too large Load Diff

View File

@ -1,54 +0,0 @@
import Selecto from 'selecto'
import Moveable, { getElementInfo } from 'moveable'
// import store from '@/store'
import { useWidgetStore, useControlStore } from '@/store'
export default function(moveable: Moveable) {
const widgetStore = useWidgetStore()
const controlStore = useControlStore()
const selecto = new Selecto({
container: document.getElementById('page-design'),
selectableTargets: ['.layer'],
selectByClick: false,
// 是否从内部目标中选择(default: true)
selectFromInside: false,
// 选择后,是否与所选目标一起选择下一个目标
continueSelect: false,
// Determines which key to continue selecting the next target via keydown and keyup.
toggleContinueSelect: 'shift',
// The container for keydown and keyup events
keyContainer: document.getElementById('page-design'),
// 目标与要选择的拖动区域重叠的速率。(默认:100)
hitRate: 5,
getElementRect: getElementInfo,
})
selecto.on('select', (e) => {
e.added.forEach((el) => {
if (!Array.from(el.classList).includes('layer-lock') && !el.hasAttribute('child')) {
el.classList.add('widget-selected')
widgetStore.selectWidgetsInOut({
uuid: el.getAttribute('data-uuid') || "",
})
// store.dispatch('selectWidgetsInOut', {
// uuid: el.getAttribute('data-uuid'),
// })
}
})
e.removed.forEach((el) => {
el.classList.remove('widget-selected')
widgetStore.selectWidgetsInOut({
uuid: el.getAttribute('data-uuid') || "",
})
// store.dispatch('selectWidgetsInOut', {
// uuid: el.getAttribute('data-uuid'),
// })
})
moveable.renderDirections = [] // ['nw', 'ne', 'sw', 'se'] // []
moveable.rotatable = false
moveable.target = [].slice.call(document.querySelectorAll('.widget-selected'))
}).on("dragStart", e => {
if (controlStore.dSpaceDown) {
e.stop();
}
})
}

View File

@ -4,7 +4,10 @@
<li
v-for="(item, index) in menuListData.list"
:key="index"
:class="{ 'menu-item': true, 'disable-menu': dCopyElement.length === 0 && item.type === 'paste' }"
:class="{
'menu-item': true,
'disable-menu': dCopyElement.length === 0 && item.type === 'paste',
}"
@click.stop="selectMenu(item.type)"
>
{{ item.text }}
@ -15,29 +18,24 @@
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import {
widgetMenu as widget,
pageMenu as page,
menuList as menu,
TMenuItemData, TWidgetItemData,
} from './rcMenuData'
import { widgetMenu as widget, pageMenu as page, menuList as menu, TMenuItemData, TWidgetItemData } from './rcMenuData'
import { getTarget } from '@/common/methods/target'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters';
import { storeToRefs } from 'pinia';
import { useControlStore, useWidgetStore } from '@/store';
import { storeToRefs } from 'pinia'
import { useControlStore, useWidgetStore } from '@/store'
const menuListData = ref<TMenuItemData>({...menu})
const menuListData = ref<TMenuItemData>({ ...menu })
const showMenuBg = ref<boolean>(false)
const widgetMenu = ref<TWidgetItemData[]>({...widget})
const pageMenu = ref<TWidgetItemData[]>({...page})
const widgetMenu = ref<TWidgetItemData[]>({ ...widget })
const pageMenu = ref<TWidgetItemData[]>({ ...page })
const widgetStore = useWidgetStore()
const controlStore = useControlStore()
// const {dActiveElement, dWidgets, dCopyElement} = useSetupMapGetters(['dActiveElement', 'dWidgets', 'dCopyElement'])
const {dActiveElement, dWidgets, dCopyElement} = storeToRefs(widgetStore)
const { dActiveElement, dWidgets, dCopyElement } = storeToRefs(widgetStore)
const { dAltDown } = storeToRefs(useControlStore())
const styleObj = computed(() => {
return {
left: menuListData.value.left + 'px',
@ -64,12 +62,8 @@ async function mouseRightClick(e: MouseEvent) {
if (uuid !== '-1' && !dAltDown.value) {
let widget = dWidgets.value.find((item: any) => item.uuid === uuid)
if (
widget?.parent !== '-1' &&
widget?.parent !== dActiveElement.value?.uuid &&
widget?.parent !== dActiveElement.value?.parent
) {
uuid = widget?.parent || ""
if (widget?.parent !== '-1' && widget?.parent !== dActiveElement.value?.uuid && widget?.parent !== dActiveElement.value?.parent) {
uuid = widget?.parent || ''
}
}
widgetStore.selectWidget({
@ -130,7 +124,7 @@ function selectMenu(type: TWidgetItemData['type']) {
break
case 'index-up':
widgetStore.updateLayerIndex({
uuid: dActiveElement.value?.uuid || "",
uuid: dActiveElement.value?.uuid || '',
value: 1,
isGroup: dActiveElement.value?.isContainer,
})
@ -142,7 +136,7 @@ function selectMenu(type: TWidgetItemData['type']) {
break
case 'index-down':
widgetStore.updateLayerIndex({
uuid: dActiveElement.value?.uuid || "",
uuid: dActiveElement.value?.uuid || '',
value: -1,
isGroup: dActiveElement.value?.isContainer,
})
@ -157,13 +151,21 @@ function selectMenu(type: TWidgetItemData['type']) {
// store.dispatch('deleteWidget')
break
case 'ungroup':
widgetStore.ungroup(dActiveElement.value?.uuid || "")
widgetStore.ungroup(dActiveElement.value?.uuid || '')
// store.dispatch('ungroup', dActiveElement.value.uuid)
break
case 'warpable':
controlStore.setWarpable(true)
break
case 'scalable':
controlStore.setScalable(true)
break
case 'resizable':
controlStore.setResizable(true)
break
}
closeMenu()
}
</script>
<style lang="less" scoped>

View File

@ -2,7 +2,7 @@
* @Author: ShawnPhang
* @Date: 2021-07-30 17:38:50
* @Description:
* @LastEditors: ShawnPhang, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditors: xi_zi
* @Date: 2024-03-04 18:50:00
*/
@ -19,7 +19,7 @@ export const menuList: TMenuItemData = {
}
export type TWidgetItemData = {
type: 'copy' | 'paste' | 'index-up' | 'index-down' | 'del' | 'ungroup'
type: 'copy' | 'paste' | 'index-up' | 'index-down' | 'del' | 'ungroup' | 'warpable' | 'scalable' | 'resizable'
text: string
}
@ -44,6 +44,18 @@ export const widgetMenu: TWidgetItemData[] = [
type: 'del',
text: '删除',
},
{
type: 'warpable',
text: '斜切',
},
{
type: 'scalable',
text: '缩放',
},
{
type: 'resizable',
text: '调整大小',
},
]
export const pageMenu: TWidgetItemData[] = [

View File

@ -1,6 +1,6 @@
<template>
<div id="main">
<div id="page-design" ref="page_design" :style="{ paddingTop: dPaddingTop + 'px', minWidth: (dPage.width * dZoom) / 100 + 120 + 'px' }" >
<div id="page-design" ref="page_design" :style="{ paddingTop: dPaddingTop + 'px', minWidth: (dPage.width * dZoom) / 100 + 120 + 'px' }">
<div
id="out-page"
class="out-page"
@ -42,11 +42,17 @@
<component
:is="layer.type"
v-for="layer in getlayers()"
:id="layer.uuid" :key="layer.uuid"
:id="layer.uuid"
:key="layer.uuid"
:class="['layer', { 'layer-hover': layer.uuid === dHoverUuid || dActiveElement?.parent === layer.uuid, 'layer-no-hover': dActiveElement?.uuid === layer.uuid }]"
:data-title="layer.type" :params="layer"
:parent="dPage" :data-type="layer.type"
:data-title="layer.type"
:params="layer"
:parent="dPage"
:data-type="layer.type"
:data-uuid="layer.uuid"
:style="{
...widgetStyle(layer),
}"
>
<template v-if="layer.isContainer">
<!-- :class="{
@ -58,10 +64,17 @@
<component
:is="widget.type"
v-for="widget in getChilds(layer.uuid)"
:key="widget.uuid" child :class="['layer', { 'layer-no-hover':dActiveElement?.uuid !== widget.parent && dActiveElement?.parent !== widget.parent }]"
:data-title="widget.type" :params="widget"
:parent="layer" :data-type="widget.type"
:key="widget.uuid"
child
:class="['layer', { 'layer-no-hover': dActiveElement?.uuid !== widget.parent && dActiveElement?.parent !== widget.parent }]"
:data-title="widget.type"
:params="widget"
:parent="layer"
:data-type="widget.type"
:data-uuid="widget.uuid"
:style="{
...groupWidgetChildrenStyle(layer, widget),
}"
/>
</template>
</component>
@ -75,7 +88,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from 'vue'
import { CSSProperties, onMounted } from 'vue'
import { getTarget } from '@/common/methods/target'
import setWidgetData from '@/common/methods/DesignFeatures/setWidgetData'
import PointImg from '@/utils/plugins/pointImg'
@ -85,6 +98,8 @@ import { move, moveInit } from '@/mixins/move'
import { useCanvasStore, useControlStore, useGroupStore, useWidgetStore } from '@/store'
import { storeToRefs } from 'pinia'
import { TPageState } from '@/store/design/canvas/d'
import { TdWidgetData } from '@/store/design/widget'
import { getOffsetFromTransform, removeTranslate } from '@/utils/widgets/transferTranslate'
//
type TProps = {
pageDesignCanvasId: string
@ -108,7 +123,6 @@ const { dZoom, dPaddingTop, dScreen } = storeToRefs(canvasStore)
const { dDraging, showRotatable, dAltDown, dSpaceDown } = storeToRefs(controlStore)
const { dWidgets, dActiveElement, dSelectWidgets, dHoverUuid } = storeToRefs(widgetStore)
let _dropIn: string | null = ''
let _srcCache: string | null = ''
@ -146,14 +160,14 @@ onMounted(() => {
}
})
// components: {lineGuides},
// mixins: [moveInit],
// ...mapActions(['updateScreen', 'selectWidget', 'deleteWidget', 'addWidget', 'addGroup']),
// components: {lineGuides},
// mixins: [moveInit],
// ...mapActions(['updateScreen', 'selectWidget', 'deleteWidget', 'addWidget', 'addGroup']),
// getBackground(data) {
// if (data.startsWith('http')) return `url(${data})`
// if (data.startsWith('linear-gradient')) return data
// },
// getBackground(data) {
// if (data.startsWith('http')) return `url(${data})`
// if (data.startsWith('linear-gradient')) return data
// },
async function dropOver(e: MouseEvent) {
if (!dActiveElement.value) return
@ -172,7 +186,7 @@ async function dropOver(e: MouseEvent) {
if (!target) return
const uuid = target.getAttribute('data-uuid')
widgetStore.setDropOver(uuid ?? "-1")
widgetStore.setDropOver(uuid ?? '-1')
// store.dispatch('setDropOver', uuid)
const imgEl = target?.firstElementChild?.firstElementChild as HTMLImageElement
@ -201,7 +215,7 @@ async function drop(e: MouseEvent) {
const dropIn = _dropIn
_dropIn = ''
widgetStore.setDropOver("-1")
widgetStore.setDropOver('-1')
// store.dispatch('setDropOver', '-1')
// store.commit('setShowMoveable', false) //
@ -239,7 +253,7 @@ async function drop(e: MouseEvent) {
})
const half = {
x: parent.width ? (parent.width * dZoom.value) / 100 / 2 : 0,
y: parent.height ? (parent.height * dZoom.value) / 100 / 2 : 0
y: parent.height ? (parent.height * dZoom.value) / 100 / 2 : 0,
}
componentItem.forEach((element) => {
element.left += (lost ? lostX - half.x : e.layerX - half.x) * (100 / dZoom.value)
@ -250,9 +264,9 @@ async function drop(e: MouseEvent) {
// addGroup(item)
}
//
const half = {
const half = {
x: setting.width ? (setting.width * dZoom.value) / 100 / 2 : 0,
y: setting.height ? (setting.height * dZoom.value) / 100 / 2 : 0
y: setting.height ? (setting.height * dZoom.value) / 100 / 2 : 0,
}
// const half = { x: (this.dDragInitData.offsetX * this.dZoom) / 100, y: (this.dDragInitData.offsetY * this.dZoom) / 100 }
setting.left = (lost ? lostX - half.x : e.layerX - half.x) * (100 / dZoom.value)
@ -268,7 +282,7 @@ async function drop(e: MouseEvent) {
// store.commit('setShowMoveable', true) //
controlStore.setShowMoveable(true) //
const widget = dWidgets.value.find((item: {uuid: string}) => item.uuid === uuid)
const widget = dWidgets.value.find((item: { uuid: string }) => item.uuid === uuid)
if (!widget) return
widget.imgUrl = item.value.url
// if (e.target.className.baseVal) {
@ -277,14 +291,13 @@ async function drop(e: MouseEvent) {
// }
} else {
if (dropIn) {
const widget = dWidgets.value.find((item: {uuid: string}) => item.uuid == dropIn)
const widget = dWidgets.value.find((item: { uuid: string }) => item.uuid == dropIn)
if (!widget) return
widget.imgUrl = item.value.url
console.log('加入+', widget)
// store.commit('setShowMoveable', true) //
controlStore.setShowMoveable(true) //
} else {
widgetStore.addWidget(setting as Required<TPageState>)
// store.dispatch('addWidget', setting) //
@ -343,7 +356,7 @@ async function handleSelection(e: MouseEvent) {
if (type) {
let uuid = target.getAttribute('data-uuid')
if (uuid !== '-1' && !dAltDown.value) {
let widget = dWidgets.value.find((item: {uuid: string}) => item.uuid === uuid)
let widget = dWidgets.value.find((item: { uuid: string }) => item.uuid === uuid)
if (!widget || !dActiveElement.value) return
if (widget.parent !== '-1' && widget.parent !== dActiveElement.value.uuid && widget.parent !== dActiveElement.value.parent) {
uuid = widget.parent || null
@ -352,14 +365,18 @@ async function handleSelection(e: MouseEvent) {
//
// this.$store.commit('setMoveable', false)
if (showRotatable.value !== false) {
/* if (showRotatable.value !== false) {
widgetStore.selectWidget({
uuid: uuid ?? " -1",
uuid: uuid ?? ' -1',
})
// store.dispatch('selectWidget', {
// uuid: uuid,
// })
}
} */
widgetStore.selectWidget({
uuid: uuid ?? ' -1',
})
if (uuid !== '-1') {
moveInit.methods.initmovement(e) // mixins
@ -367,7 +384,7 @@ async function handleSelection(e: MouseEvent) {
} else {
//
widgetStore.selectWidget({
uuid: "-1"
uuid: '-1',
})
// store.dispatch('selectWidget', {
// uuid: '-1',
@ -382,22 +399,47 @@ function getlayers() {
function getChilds(uuid: string) {
return dWidgets.value.filter((item) => item.parent === uuid)
}
// getIsActive(uuid) {
// if (this.dSelectWidgets.length > 0) {
// let widget = this.dSelectWidgets.find((item) => item.uuid === uuid)
// if (widget) {
// return true
// }
// return false
// } else {
// return uuid === this.dActiveElement.uuid
// }
// },
// getIsActive(uuid) {
// if (this.dSelectWidgets.length > 0) {
// let widget = this.dSelectWidgets.find((item) => item.uuid === uuid)
// if (widget) {
// return true
// }
// return false
// } else {
// return uuid === this.dActiveElement.uuid
// }
// },
const widgetStyle = (widgetData: TdWidgetData): CSSProperties => {
const { transform, transformOrigin, width, height } = widgetData
const style: CSSProperties = { position: 'absolute' }
if (transform) style.transform = transform
if (transformOrigin) style.transformOrigin = transformOrigin
if (width !== undefined) style.width = `${width}px`
if (height !== undefined) style.height = `${height}px`
return style
}
const groupWidgetChildrenStyle = (groupWidgetData: TdWidgetData, widgetData: TdWidgetData): CSSProperties => {
const { transform, transformOrigin, width, height } = widgetData
const { transform: gTransform } = groupWidgetData
const { x: gX, y: gY } = getOffsetFromTransform(gTransform || '')
const { x, y } = getOffsetFromTransform(transform || '')
const style: CSSProperties = { position: 'absolute' }
if (transform) style.transform = removeTranslate(transform)
if (transformOrigin) style.transformOrigin = transformOrigin
if (width !== undefined) style.width = `${width}px`
if (height !== undefined) style.height = `${height}px`
style.transform = `translate(${x - gX}px, ${y - gY}px) ${style.transform}`
return style
}
</script>
<style lang="less" scoped>
#main {
overflow: auto; position: relative;
overflow: auto;
position: relative;
}
#page-design {
scrollbar-width: none;

View File

@ -8,7 +8,7 @@ export const wGroupSetting = {
height: 0,
left: 0,
top: 0,
transform: '',
transform: 'translate(0,0) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)',
opacity: 1,
parent: '-1',
isContainer: true,

View File

@ -2,22 +2,11 @@
* @Author: ShawnPhang
* @Date: 2021-08-02 09:41:41
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-10-16 00:30:03
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 23:31:12
-->
<template>
<div
ref="widget"
:class="['w-group', { 'layer-lock': props.params?.lock }]"
:style="{
position: 'absolute',
left: (props.params.left || 0) - (props.parent?.left || 0) + 'px',
top: (props.params.top || 0) - (props.parent.top || 0) + 'px',
width: params.width + 'px',
height: params.height + 'px',
opacity: params.opacity,
}"
>
<div ref="widget" :class="['w-group', { 'layer-lock': props.params?.lock }]">
<slot></slot>
</div>
</template>
@ -28,8 +17,8 @@ const NAME = 'w-group'
import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref } from 'vue'
import { setTransformAttribute } from '@/common/methods/handleTransform'
import { useWidgetStore } from '@/store';
import { storeToRefs } from 'pinia';
import { useWidgetStore } from '@/store'
import { storeToRefs } from 'pinia'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters';
export type TParamsData = {
@ -46,13 +35,12 @@ export type TParamsData = {
type TProps = {
params?: Partial<TParamsData>
parent?: Partial<Pick<TParamsData, "top" | "left">>
parent?: Partial<Pick<TParamsData, 'top' | 'left'>>
}
const props = withDefaults(defineProps<TProps>(), {
params: () => ({}),
parent: () => ({})
parent: () => ({}),
})
;
const widgetStore = useWidgetStore()
const widget = ref<HTMLElement | null>(null)
@ -105,7 +93,7 @@ onMounted(async () => {
document.addEventListener('mousedown', touchstart, false)
document.addEventListener('mouseup', touchend, false)
if (props.params?.rotate && widget.value) {
(widget.value.style.transform += `rotate(${props.params.rotate})`)
widget.value.style.transform += `rotate(${props.params.rotate})`
}
})
@ -115,7 +103,7 @@ onBeforeUnmount(() => {
})
// ...mapActions(['updateWidgetData']),
function updateRecord(tempScale ?: number) {
function updateRecord(tempScale?: number) {
if (dActiveElement.value?.uuid === props.params.uuid) {
// clearTimeout(this.timer)
let record = dActiveElement.value?.record
@ -238,14 +226,13 @@ function keySetValue(uuid: string, key: keyof TParamsData, value: number) {
setTimeout(() => {
const widget = dWidgets.value.find((item) => item.uuid === uuid)
if (!widget) return
(widget[key] as Number) = value + Number(props.params[key] || '')
;(widget[key] as Number) = value + Number(props.params[key] || '')
}, 10)
}
// defineExpose({
// setting
// })
</script>
<style lang="less" scoped>

View File

@ -2,8 +2,8 @@
* @Author: ShawnPhang
* @Date: 2021-08-09 11:21:37
* @Description: 组合设置
* @LastEditors: ShawnPhang <site: book.palxp.com>
* @LastEditTime: 2023-06-29 17:57:24
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 15:44:34
-->
<template>
<div id="w-group-style">
@ -18,12 +18,7 @@
</el-collapse-item>
<el-collapse-item title="样式设置" name="2">
<!-- <el-button plain type="primary" class="block-btn" @click="store.dispatch('ungroup', state.innerElement.uuid)">取消组合</el-button> -->
<div
class="ungroup style-item"
@click="widgetStore.ungroup(String(state.innerElement.uuid))"
>
取消组合
</div>
<div class="ungroup style-item" @click="widgetStore.ungroup(String(state.innerElement.uuid))">取消组合</div>
<number-slider v-model="state.innerElement.opacity" class="style-item" label="不透明" :step="0.05" :maxValue="1" @finish="(value) => finish('opacity', value)" />
<br />
<icon-item-select class="style-item" label="" :data="layerIconList" @finish="layerAction" />
@ -52,13 +47,13 @@ import { TUpdateAlignData } from '@/store/design/widget/actions/align'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters'
type TState = {
activeNames: string[],
activeNames: string[]
// defaultValue: 0,
innerElement: typeof wGroupSetting,
tag: boolean,
ingoreKeys: string[],
layerIconList: TIconItemSelectData[],
alignIconList: TIconItemSelectData[],
innerElement: typeof wGroupSetting
tag: boolean
ingoreKeys: string[]
layerIconList: TIconItemSelectData[]
alignIconList: TIconItemSelectData[]
}
const state = reactive<TState>({
@ -86,7 +81,7 @@ watch(
(newValue, oldValue) => {
change()
},
{ deep: true }
{ deep: true },
)
watch(
@ -94,7 +89,7 @@ watch(
(newValue, oldValue) => {
changeValue()
},
{ deep: true }
{ deep: true },
)
function created() {
@ -104,7 +99,7 @@ function created() {
created()
// ...mapActions(['updateWidgetData', 'updateAlign', 'updateLayerIndex', 'ungroup']),
function change() {
state.tag = true
state.innerElement = JSON.parse(JSON.stringify(dActiveElement.value))
@ -121,12 +116,12 @@ function changeValue() {
for (let key in state.innerElement) {
const itemKey = key as keyof typeof wGroupSetting
if (state.ingoreKeys.indexOf(itemKey) !== -1) {
(dActiveElement.value as Record<string, any>)[itemKey] = state.innerElement[itemKey]
;(dActiveElement.value as Record<string, any>)[itemKey] = state.innerElement[itemKey]
} else if (key !== 'setting' && key !== 'record' && state.innerElement[itemKey] !== (dActiveElement.value as Record<string, any>)[itemKey]) {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || "",
key: (key as TUpdateWidgetPayload['key']),
value: (state.innerElement[itemKey] as TUpdateWidgetPayload['value']),
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: state.innerElement[itemKey] as TUpdateWidgetPayload['value'],
})
// store.dispatch("updateWidgetData", {
// uuid: dActiveElement.value?.uuid,
@ -137,12 +132,11 @@ function changeValue() {
}
}
function finish(key: string, value: string | number | number[]) {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || "",
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: value as TUpdateWidgetPayload['value'],
value: value as TUpdateWidgetPayload['value'],
pushHistory: true,
})
// store.dispatch("updateWidgetData", {
@ -155,8 +149,8 @@ function finish(key: string, value: string | number | number[]) {
function layerAction(item: TIconItemSelectData) {
widgetStore.updateLayerIndex({
uuid: dActiveElement.value?.uuid || "",
value: (item.value as TupdateLayerIndexData['value']),
uuid: dActiveElement.value?.uuid || '',
value: item.value as TupdateLayerIndexData['value'],
isGroup: true,
})
// store.dispatch("updateLayerIndex", {
@ -168,8 +162,8 @@ function layerAction(item: TIconItemSelectData) {
function alignAction(item: TIconItemSelectData) {
widgetStore.updateAlign({
align: (item.value as TUpdateAlignData['align']),
uuid: dActiveElement.value?.uuid || "",
align: item.value as TUpdateAlignData['align'],
uuid: dActiveElement.value?.uuid || '',
})
// store.dispatch("updateAlign", {
// align: item.value,

View File

@ -3,30 +3,30 @@
:id="params.uuid"
ref="widgetRef"
:class="['w-image', { 'layer-lock': params.lock }]"
:style="{
position: state.position,
left: params.left - parent.left + 'px',
top: params.top - parent.top + 'px',
width: params.width + 'px',
height: params.height + 'px',
opacity: params.opacity,
}"
>
<div
v-if="cropEdit"
:id="params.uuid + '_ebox'"
:ref="params.uuid + '_ebox'"
:style="state.editBoxStyle"
class="svg__edit__wrap"
>
<img class="edit__model" :src="params.imgUrl" />
</div>
<div :style="{ transform: params.flip ? `rotate${params.flip}(180deg)` : undefined, borderRadius: params.radius + 'px', '-webkit-mask-image': `${params.mask ? `url('${params.mask}')` : 'initial'}` }" :class="['img__box', { mask: params.mask }]">
<div v-if="params.isNinePatch" ref="targetRef" class="target" :style="{ border: `${(params.height * params.sliceData.ratio) / 2}px solid transparent`, borderImage: `url('${params.imgUrl}') ${params.sliceData.left} round` }"></div>
<img v-else ref="targetRef" class="target" style="transform-origin: center" :src="params.imgUrl" />
</div>
<div v-if="isMask" class="drop__mask">
<div putIn="true" :style="{ fontSize: params.width / 12 + 'px' }" class="drop__btn">拖入</div>
<div>
<div v-if="cropEdit" :id="params.uuid + '_ebox'" :ref="params.uuid + '_ebox'" :style="state.editBoxStyle" class="svg__edit__wrap">
<img class="edit__model" :src="params.imgUrl" />
</div>
<div
:style="{
transform: params.flip ? `rotate${params.flip}(180deg)` : undefined,
borderRadius: params.radius + 'px',
'-webkit-mask-image': `${params.mask ? `url('${params.mask}')` : 'initial'}`,
}"
:class="['img__box', { mask: params.mask }]"
>
<div
v-if="params.isNinePatch"
ref="targetRef"
class="target"
:style="{ border: `${(params.height * params.sliceData.ratio) / 2}px solid transparent`, borderImage: `url('${params.imgUrl}') ${params.sliceData.left} round` }"
></div>
<img v-else ref="targetRef" class="target" style="transform-origin: center" :src="params.imgUrl" />
</div>
<div v-if="isMask" class="drop__mask">
<div putIn="true" :style="{ fontSize: params.width / 12 + 'px' }" class="drop__btn">拖入</div>
</div>
</div>
</div>
</template>
@ -37,7 +37,7 @@
import { CSSProperties, StyleValue, computed, nextTick, onBeforeUnmount, onMounted, onUpdated, reactive, ref, watch } from 'vue'
import { getMatrix } from '@/common/methods/handleTransform'
import setting from "./wImageSetting"
import setting from './wImageSetting'
import PointImg from '@/utils/plugins/pointImg'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters'
import { useRoute } from 'vue-router'
@ -53,8 +53,8 @@ type TProps = {
}
type TState = {
position: 'absolute' | 'relative', // 'absolute'relative
editBoxStyle: CSSProperties,
position: 'absolute' | 'relative' // 'absolute'relative
editBoxStyle: CSSProperties
cropWidgetXY: {
x: number
y: number
@ -79,11 +79,10 @@ const state = reactive<TState>({
holdPosition: {
left: 0,
top: 0,
}
},
})
const route = useRoute()
const controlStore = useControlStore()
const widgetStore = useWidgetStore()
const forceStore = useForceStore()
@ -98,9 +97,7 @@ let flipTemp: string | null = null
// dActiveElement, dWidgets, dMouseXY, dDropOverUuid, dCropUuid
// } = useSetupMapGetters(['dActiveElement', 'dWidgets', 'dMouseXY', 'dDropOverUuid', 'dCropUuid'])
const { dZoom } = storeToRefs(useCanvasStore())
const {
dActiveElement, dWidgets, dMouseXY, dDropOverUuid
} = storeToRefs(widgetStore)
const { dActiveElement, dWidgets, dMouseXY, dDropOverUuid } = storeToRefs(widgetStore)
const { dCropUuid } = storeToRefs(controlStore)
// ...mapGetters(['dActiveElement', 'dWidgets', 'dZoom', 'dMouseXY', 'dDropOverUuid', 'dCropUuid']),
@ -129,7 +126,7 @@ watch(
}
fixRotate()
lockOthers(val)
}
},
)
watch(
@ -137,7 +134,7 @@ watch(
async () => {
await nextTick()
updateRecord()
}
},
)
onUpdated(() => {
@ -241,7 +238,7 @@ function updateRecord() {
}
function setTransform(attrName: string, value: string | number) {
const iof = props.params.transform.indexOf(attrName)
/* const iof = props.params.transform.indexOf(attrName)
let setValue = ''
if (iof != -1) {
const index = iof + attrName.length
@ -267,11 +264,11 @@ function setTransform(attrName: string, value: string | number) {
// })
if (props.params.transform && targetRef.value) {
targetRef.value.style.transform = props.params.transform
}
} */
}
function setEditBox(attrName: string, value: string | number) {
const iof = state.editBoxStyle.transform?.indexOf(attrName)
/* const iof = state.editBoxStyle.transform?.indexOf(attrName)
let setValue = ''
if (iof != -1 && iof != undefined) {
const index = iof + attrName.length
@ -283,7 +280,7 @@ function setEditBox(attrName: string, value: string | number) {
} else {
setValue = state.editBoxStyle.transform + ` ${attrName}(${value})`
}
state.editBoxStyle.transform = setValue
state.editBoxStyle.transform = setValue */
}
function updateZoom() {
@ -320,7 +317,7 @@ function lockOthers(isCrop) {
for (const widget of dWidgets.value) {
if (widget.uuid === props.params.uuid) {
widget.lock = false
break;
break
}
}
}

View File

@ -2,8 +2,8 @@
* @Author: ShawnPhang
* @Date: 2021-08-09 11:41:53
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-03-22 16:14:48
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 13:44:42
-->
<template>
<div id="w-image-style">
@ -31,22 +31,20 @@
<container-wrap @change="changeContainer" />
<div class="slide-wrap">
<number-slider v-model="state.innerElement.opacity" style="font-size: 14px" label="不透明" :step="0.05" :maxValue="1" @finish="(value) => finish('opacity', value)" />
<number-slider v-model="state.innerElement.radius" style="font-size: 14px" label="圆角" :maxValue="Math.min(Number(state.innerElement.record?.width), Number(state.innerElement.record?.height))" @finish="(value) => finish('radius', value)" />
<number-slider
v-model="state.innerElement.radius"
style="font-size: 14px"
label="圆角"
:maxValue="Math.min(Number(state.innerElement.record?.width), Number(state.innerElement.record?.height))"
@finish="(value) => finish('radius', value)"
/>
<!-- <number-slider v-model="innerElement.letterSpacing" style="font-size: 14px" label="字距" labelWidth="40px" :step="0.05" :minValue="-50" :maxValue="innerElement.fontSize" @finish="(value) => finish('letterSpacing', value)" />
<number-slider v-model="innerElement.lineHeight" style="font-size: 14px" label="行距" labelWidth="40px" :step="0.05" :minValue="0" :maxValue="2.5" @finish="(value) => finish('lineHeight', value)" /> -->
</div>
</el-collapse-item>
<el-collapse-item v-if="state.innerElement.isNinePatch" title="点九图设置" name="3">
<number-slider
v-model="state.innerElement.sliceData.ratio"
:step="0.01" label="比率" :maxValue="10"
@finish="(value) => finishSliceData('ratio', value)"
/>
<number-slider
v-model="state.innerElement.sliceData.left"
:step="0.5" label="大小"
@finish="(value) => finishSliceData('left', value)"
/>
<number-slider v-model="state.innerElement.sliceData.ratio" :step="0.01" label="比率" :maxValue="10" @finish="(value) => finishSliceData('ratio', value)" />
<number-slider v-model="state.innerElement.sliceData.left" :step="0.5" label="大小" @finish="(value) => finishSliceData('left', value)" />
</el-collapse-item>
<br />
<icon-item-select class="style-item" label="" :data="layerIconList" @finish="layerAction" />
@ -55,11 +53,7 @@
</el-collapse>
<!-- <CropImage ref="crop" @done="cropDone" /> -->
<inner-tool-bar v-show="state.innerElement.cropEdit" :style="toolBarStyle">
<number-slider
v-model="state.innerElement.zoom"
class="inner-bar" label="缩放" labelWidth="40px"
:step="0.01" :minValue="1" :maxValue="3"
/>
<number-slider v-model="state.innerElement.zoom" class="inner-bar" label="缩放" labelWidth="40px" :step="0.01" :minValue="1" :maxValue="3" />
<i style="padding: 0 8px; cursor: pointer" class="icon sd-queren" @click="imgCrop(false)" />
</inner-tool-bar>
<picBox ref="picBoxRef" @select="selectDone" />
@ -134,7 +128,6 @@ const state = reactive<TState>({
const picBoxRef = ref<typeof picBox | null>(null)
const imageCutoutRef = ref<typeof imageCutout | null>(null)
const widgetStore = useWidgetStore()
const forceStore = useForceStore()
const canvasStore = useCanvasStore()
@ -145,10 +138,9 @@ const controlStore = useControlStore()
const { dMoving } = storeToRefs(controlStore)
const { dActiveElement, dWidgets } = storeToRefs(widgetStore)
let lastUuid: string | undefined = undefined
let tag: boolean
let toolBarStyle: { left: string, top: string } | null = null
let toolBarStyle: { left: string; top: string } | null = null
onBeforeUnmount(() => {
imgCrop(false)
@ -166,7 +158,7 @@ watch(
}
lastUuid = newValue.uuid
},
{ deep: true }
{ deep: true },
)
watch(
@ -175,7 +167,7 @@ watch(
changeValue()
cropHandle()
},
{ deep: true }
{ deep: true },
)
function created() {
@ -201,15 +193,12 @@ function changeValue() {
}
for (let key in state.innerElement) {
if (state.ingoreKeys.indexOf(key) !== -1) {
(dActiveElement.value as Record<string, any>)[key] = state.innerElement[(key as keyof TImageSetting)]
} else if (
key !== 'cropEdit' && key !== 'record' &&
state.innerElement[(key as keyof TImageSetting)] !== (dActiveElement.value as Record<string, any>)[key]
) {
;(dActiveElement.value as Record<string, any>)[key] = state.innerElement[key as keyof TImageSetting]
} else if (key !== 'cropEdit' && key !== 'record' && state.innerElement[key as keyof TImageSetting] !== (dActiveElement.value as Record<string, any>)[key]) {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || "",
key: (key as TUpdateWidgetPayload['key']),
value: (state.innerElement[(key as keyof TImageSetting)] as TUpdateWidgetPayload['value']),
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: state.innerElement[key as keyof TImageSetting] as TUpdateWidgetPayload['value'],
})
// store.dispatch('updateWidgetData', {
// uuid: dActiveElement.value.uuid,
@ -240,10 +229,10 @@ function finishSliceData(key: string, value: number | number[]) {
}
}
function finish(key: string = "", value: string | number | (string | number)[] | null = "") {
function finish(key: string = '', value: string | number | (string | number)[] | null = '') {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || "",
key: (key as TUpdateWidgetPayload['key']),
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: value as TUpdateWidgetPayload['value'],
pushHistory: true,
})
@ -258,22 +247,22 @@ function finish(key: string = "", value: string | number | (string | number)[] |
function layerAction(item: TIconItemSelectData) {
if (item.key === 'zIndex') {
widgetStore.updateLayerIndex({
uuid: dActiveElement.value?.uuid || "",
value: (item.value as TupdateLayerIndexData['value']),
uuid: dActiveElement.value?.uuid || '',
value: item.value as TupdateLayerIndexData['value'],
})
// store.dispatch("updateLayerIndex", {
// uuid: dActiveElement.value.uuid,
// value: item.value,
// })
} else {
finish(item.key || "", item.value === dActiveElement.value?.flip ? null : item.value)
finish(item.key || '', item.value === dActiveElement.value?.flip ? null : item.value)
}
}
async function alignAction(item: TIconItemSelectData) {
widgetStore.updateAlign({
align: (item.value as TUpdateAlignData['align']),
uuid: dActiveElement.value?.uuid || "",
align: item.value as TUpdateAlignData['align'],
uuid: dActiveElement.value?.uuid || '',
})
// store.dispatch("updateAlign", {
// align: item.value,
@ -295,7 +284,6 @@ function openCropper() {
// this.innerElement.height = height.toFixed(0)
// }
async function changeContainer(setting: any) {
state.innerElement.mask = setting.svgUrl
// const index = this.dWidgets.findIndex((x) => x.uuid == this.innerElement.uuid)
@ -318,26 +306,25 @@ async function changeContainer(setting: any) {
// this.$store.commit('setShowMoveable', true)
// }
async function selectDone(img: TGetImageListResult) {
state.innerElement.imgUrl = img.url
const loadImg = await getImage(img.url)
state.innerElement.width = loadImg.width * canvasStore.dZoom / 100
state.innerElement.height = loadImg.height * canvasStore.dZoom / 100
state.innerElement.width = (loadImg.width * canvasStore.dZoom) / 100
state.innerElement.height = (loadImg.height * canvasStore.dZoom) / 100
// this.imgCrop(true)
}
function imgCrop(val: boolean) {
async function imgCrop(val: boolean) {
// TODO:
const el = document.getElementById(state.innerElement.uuid || "")
const el = document.getElementById(state.innerElement.uuid || '')
if (!el) return
const { left, top } = el.getBoundingClientRect()
toolBarStyle = { left: left + 'px', top: top + 'px' }
state.innerElement.cropEdit = val
await nextTick()
controlStore.setShowRotatable(!val)
}
function cropHandle() {
controlStore.setCropUuid(state.innerElement.cropEdit ? state.innerElement.uuid : '-1')
// store.commit('setCropUuid', state.innerElement.cropEdit ? state.innerElement.uuid : -1)
@ -352,7 +339,7 @@ function openPicBox() {
//
function openImageCutout() {
fetch(state.innerElement.imgUrl || "")
fetch(state.innerElement.imgUrl || '')
.then((response) => response.blob())
.then((blob) => {
const file = new File([blob], `image_${Math.random()}.jpg`, { type: 'image/jpeg' })
@ -371,7 +358,6 @@ async function cutImageDone(url: string) {
state.innerElement.imgUrl = url
}, 300)
}
</script>
<style lang="less" scoped>

View File

@ -2,32 +2,12 @@
* @Author: ShawnPhang
* @Date: 2022-03-13 15:59:52
* @Description: 二维码
* @LastEditors: ShawnPhang <site: book.palxp.com>
* @LastEditTime: 2023-06-29 15:54:10
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 23:32:14
-->
<template>
<div
:id="`${params.uuid}`"
ref="widgetRef"
:class="['w-qrcode', { 'layer-lock': params.lock }]"
:style="{
position: 'absolute',
left: params.left - parent.left + 'px',
top: params.top - parent.top + 'px',
width: params.width + 'px',
height: params.height + 'px',
opacity: params.opacity,
}"
>
<QRCode
ref="qrcode"
v-bind="state.qrCodeOptions"
:width="width"
:height="width"
class="target"
:image="params.url"
:value="params.value"
/>
<div :id="`${params.uuid}`" ref="widgetRef" :class="['w-qrcode', { 'layer-lock': params.lock }]">
<QRCode ref="qrcode" v-bind="state.qrCodeOptions" :width="width" :height="width" class="target" :image="params.url" :value="params.value" />
</div>
</template>
@ -35,14 +15,13 @@
//
// const NAME = 'w-qrcode'
import QRCode from '@/components/business/qrcode'
import { TWQrcodeSetting } from './wQrcodeSetting';
import { computed, nextTick, onMounted, onUpdated, reactive, ref, watch } from 'vue';
import { TWQrcodeSetting } from './wQrcodeSetting'
import { computed, nextTick, onMounted, onUpdated, reactive, ref, watch } from 'vue'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters';
import { Options } from 'qr-code-styling';
import { useForceStore, useWidgetStore } from '@/store';
import { storeToRefs } from 'pinia';
import { Options } from 'qr-code-styling'
import { useForceStore, useWidgetStore } from '@/store'
import { storeToRefs } from 'pinia'
type TProps = {
params: TWQrcodeSetting & {
@ -59,12 +38,11 @@ type TState = {
qrCodeOptions: Options
}
const forceStore = useForceStore()
const widgetStore = useWidgetStore()
const props = defineProps<TProps>()
const state = reactive<TState>({
qrCodeOptions: {}
qrCodeOptions: {},
})
// const { dActiveElement } = useSetupMapGetters(['dActiveElement'])
const { dActiveElement } = storeToRefs(widgetStore)
@ -76,7 +54,7 @@ watch(
() => {
changeValues()
},
{ immediate: true, deep: true, }
{ immediate: true, deep: true },
)
onUpdated(() => {
@ -88,7 +66,7 @@ onUpdated(() => {
onMounted(async () => {
updateRecord()
await nextTick()
if (widgetRef.value){
if (widgetRef.value) {
props.params.rotate && (widgetRef.value.style.transform += `rotate(${props.params.rotate})`)
}
})

View File

@ -2,30 +2,18 @@
TODO: 重构
-->
<template>
<div
:id="params.uuid"
ref="widgetRef"
class="w-svg"
:style="{
position: state.position,
left: params.left - parent.left + 'px',
top: params.top - parent.top + 'px',
width: params.width + 'px',
height: params.height + 'px',
opacity: params.opacity,
}"
></div>
<div :id="params.uuid" ref="widgetRef" class="w-svg"></div>
</template>
<script lang="ts" setup>
// svg
// const NAME = 'w-svg'
import { useCanvasStore, useForceStore, useWidgetStore } from '@/store';
import { useCanvasStore, useForceStore, useWidgetStore } from '@/store'
import { TWSvgSetting } from './wSvgSetting'
import { CSSProperties, computed, nextTick, onBeforeMount, onMounted, onUpdated, reactive, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { TUpdateWidgetPayload } from '@/store/design/widget/actions/widget';
import { CSSProperties, computed, nextTick, onBeforeMount, onMounted, onUpdated, reactive, ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { TUpdateWidgetPayload } from '@/store/design/widget/actions/widget'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters';
type TProps = {
@ -37,12 +25,12 @@ type TProps = {
}
type TState = {
position: CSSProperties['position'], // 'absolute'relative
editBoxStyle: CSSProperties,
editBoxs: Record<string, any>,
editingKey: string,
cropWidgetXY: Record<string, any>, //
attrRecord: Record<string, any>, //
position: CSSProperties['position'] // 'absolute'relative
editBoxStyle: CSSProperties
editBoxs: Record<string, any>
editingKey: string
cropWidgetXY: Record<string, any> //
attrRecord: Record<string, any> //
svgImg: Record<string, any> | null
}
@ -56,7 +44,7 @@ const state = reactive<TState>({
editingKey: '',
cropWidgetXY: {}, //
attrRecord: {}, //
svgImg: null
svgImg: null,
})
const widgetStore = useWidgetStore()
@ -88,7 +76,7 @@ watch(
() => {
attrsChange()
},
{ immediate: true, deep: true }
{ immediate: true, deep: true },
)
watch(
@ -96,7 +84,7 @@ watch(
async () => {
await nextTick()
updateRecord()
}
},
)
watch(
@ -106,7 +94,7 @@ watch(
state.svgImg.attr({
'xlink:href': props.params.imgUrl,
})
}
},
)
watch(
@ -118,7 +106,7 @@ watch(
} else {
el?.removeEventListener('mousedown', touchstart, false)
}
}
},
)
onUpdated(() => {

View File

@ -1,17 +1,15 @@
<template>
<div
:id="`${params.uuid}`"
ref="widget"
v-loading="state.loading"
v-bind="$attrs"
class="element-inner"
:class="['w-text', { editing: state.editable, 'layer-lock': params.lock }, params.uuid]"
ref="widget"
:style="{
position: 'absolute',
left: params.left - parent.left + 'px',
top: params.top - parent.top + 'px',
width: params.width + 'px',
minWidth: params.fontSize + 'px',
minHeight: params.fontSize * params.lineHeight + 'px',
// minWidth: params.fontSize + 'px',
// minHeight: params.fontSize * params.lineHeight + 'px',
height: params.height + 'px',
width: params.width + 'px',
lineHeight: params.fontSize * params.lineHeight + 'px',
letterSpacing: (params.fontSize * params.letterSpacing) / 100 + 'px',
fontSize: params.fontSize + 'px',
@ -46,12 +44,15 @@
></div>
</template>
<div
ref="editWrap" :style="{ fontFamily: `'${params.fontClass.value}'` }"
class="edit-text" spellcheck="false"
ref="editWrap"
:style="{ fontFamily: `'${params.fontClass.value}'` }"
class="edit-text"
spellcheck="false"
:contenteditable="state.editable ? 'plaintext-only' : false"
@input="writingText($event)"
@blur="writeDone($event)"
v-html="params.text"></div>
v-html="params.text"
></div>
</div>
</template>
@ -65,12 +66,21 @@ import { fontWithDraw } from '@/utils/widgets/loadFontRule'
import getGradientOrImg from './getGradientOrImg'
import { wTextSetting } from './wTextSetting'
import { useForceStore, useHistoryStore, useWidgetStore } from '@/store'
import { addFont } from '@/utils/addFont'
export type TwTextParams = {
rotate?: number
lock?: boolean
width?: number
height?: number
imageTransform?: {
a: number
b: number
c: number
d: number
tx: number
ty: number
}
} & typeof wTextSetting
type TProps = {
@ -92,7 +102,9 @@ const state = reactive({
loadFontDone: '',
})
const widget = ref<HTMLElement | null>(null)
const widgetWrap = ref<HTMLElement | null>(null)
const editWrap = ref<HTMLElement | null>(null)
const editorElRef = ref<HTMLElement | null>(null)
const dActiveElement = computed(() => widgetStore.dActiveElement)
const isDraw = computed(() => route.name === 'Draw' && fontWithDraw)
@ -103,11 +115,6 @@ onUpdated(() => {
onMounted(() => {
updateRecord()
if (!widget.value) return
props.params.transform && (widget.value.style.transform = props.params.transform)
props.params.rotate && (widget.value.style.transform += `translate(0px, 0px) rotate(${props.params.rotate}) scale(1, 1)`)
// store.commit('updateRect')
})
watch(
@ -128,9 +135,7 @@ watch(
return
}
state.loading = !isDraw.value
const loadFont = new window.FontFace(font.value, `url(${font.url})`)
await loadFont.load()
document.fonts.add(loadFont)
await addFont(font.value, font.url)
state.loadFontDone = font.value
state.loading = false
} else {
@ -171,7 +176,7 @@ function updateRecord() {
}
function updateText(e?: Event) {
const value = e && e.target ? (e.target as HTMLElement).innerHTML : props.params.text//.replace(/\n/g, '<br/>')
const value = e && e.target ? (e.target as HTMLElement).innerHTML : props.params.text //.replace(/\n/g, '<br/>')
if (value !== props.params.text) {
widgetStore.updateWidgetData({
uuid: String(props.params.uuid),
@ -212,7 +217,7 @@ function writingText(e?: Event) {
function writeDone(e: Event) {
state.editable = false
setTimeout(() => {
historyStore.pushHistory("文字修改")
historyStore.pushHistory('文字修改')
// store.dispatch('pushHistory', '')
}, 100)
updateText(e)
@ -231,9 +236,9 @@ function dblclickText(_: MouseEvent) {
range.select()
} else {
const range = document.createRange()
range.selectNodeContents(el);
window.getSelection()?.removeAllRanges();
window.getSelection()?.addRange(range);
range.selectNodeContents(el)
window.getSelection()?.removeAllRanges()
window.getSelection()?.addRange(range)
}
}, 100)
}
@ -251,6 +256,23 @@ defineExpose({
</script>
<style lang="less" scoped>
.element-inner {
position: absolute;
// z-index: 1;
// width: 100%;
// height: 100%;
font-size: 0;
white-space: nowrap;
writing-mode: horizontal-tb;
// transform-origin: 0 0;
.element-inner-position {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
.w-text {
// cursor: pointer;
user-select: none;

View File

@ -8,6 +8,7 @@ export type TwTextData = {
left: number
top: number
transform: string
transformOrigin: string
lineHeight: number
letterSpacing: number
fontSize: number

View File

@ -48,8 +48,8 @@ export type TPageState = {
name: string
type: string
uuid: string
left: number
top: number
left?: number
top?: number
/** 画布宽度 */
width: number
/** 画布高度 */

View File

@ -3,8 +3,8 @@
* @Author: Jeremy Yu
* @Date: 2024-03-18 21:00:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-18 21:00:00
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 10:26:38
*/
import { useHistoryStore } from "@/store";
@ -27,8 +27,11 @@ type TControlState = {
dAltDown: boolean
// 是否按下空格键
dSpaceDown: boolean
/** 正在编辑or裁剪的组件id */
/** 正在编辑or裁剪的组件id **/
dCropUuid: string
warpable: boolean, // 是否开启斜切
scalable: boolean, // 是否开启缩放
resizable: boolean, // 是否开启拖拽
}
type TControlAction = {
@ -45,11 +48,29 @@ type TControlAction = {
stopDMove: () => void
/** 设置正在裁剪or编辑的组件 */
setCropUuid: (uuid: string) => void
/**
*
* @param able
* @returns
*/
setWarpable: (able: boolean) => void
/**
*
* @param able
* @returns
*/
setResizable: (able: boolean) => void
/**
*
* @param able
* @returns
*/
setScalable: (able: boolean) => void
setSpaceDown: (uuid: boolean) => void // 设置是否按下空格键
}
/** 全局控制配置 */
const ControlStore = defineStore<"controlStore", TControlState, {}, TControlAction>("controlStore", {
const ControlStore = defineStore<"controlStore", TControlState, {}, TControlAction>("controlStore", {
state: () => ({
dMoving: false, // 是否正在移动组件
dDraging: false, // 是否正在抓取组件
@ -59,6 +80,9 @@ const ControlStore = defineStore<"controlStore", TControlState, {}, TControlAct
showRotatable: true, // 是否显示moveable的旋转按钮
dAltDown: false, // 记录是否按下alt键 / 或ctrl
dCropUuid: '-1', // 正在编辑or裁剪的组件id
warpable: false, // 是否开启斜切
scalable: false, // 是否开启缩放
resizable: true, // 是否开启拖拽
dSpaceDown: false, // 记录是否按下空格键
}),
getters: {},
@ -112,6 +136,31 @@ const ControlStore = defineStore<"controlStore", TControlState, {}, TControlAct
// 设置正在裁剪or编辑的组件
this.dCropUuid = uuid
},
setWarpable(able: boolean) {
this.warpable = able
if (able) {
this.scalable = false
this.resizable = false
} else {
this.resizable = true
}
},
setResizable(able: boolean) {
this.resizable = able
if (able) {
this.warpable = false
this.scalable = false
}
},
setScalable(able: boolean) {
this.scalable = able
if (able) {
this.warpable = false
this.resizable = false
} else {
this.resizable = true
}
},
setSpaceDown(val: boolean) {
this.dSpaceDown = val
}

View File

@ -2,21 +2,21 @@
* @Author: Jeremy Yu
* @Date: 2024-03-28 14:00:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-28 14:00:00
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 23:34:21
*/
import { customAlphabet } from 'nanoid/non-secure'
import { TGroupStore } from '..'
import { useHistoryStore, useCanvasStore, useWidgetStore } from '@/store'
import { TdWidgetData } from '../../widget'
import { getOffsetFromTransform, transferTransformWidget } from '@/utils/widgets/transferTranslate'
const nanoid = customAlphabet('1234567890abcdef', 12)
export function realCombined(store: TGroupStore) {
const widgetStore = useWidgetStore()
const pageStore = useCanvasStore()
const historyStore = useHistoryStore()
const selectWidgets = widgetStore.dSelectWidgets
if (selectWidgets.length > 1) {
const widgets = widgetStore.dWidgets
@ -60,10 +60,12 @@ export function realCombined(store: TGroupStore) {
// index: index,
// widget: widget,
// })
left = Math.min(left, widget.left)
top = Math.min(top, widget.top)
right = Math.max(right, Number(widget.width || widget.record.width) + Number(widget.left))
bottom = Math.max(bottom, Number(widget.height || widget.record.height) + Number(widget.top))
const { x, y } = getOffsetFromTransform(widget.transform ?? '')
left = Math.min(left, x)
top = Math.min(top, y)
const rect = document.getElementById(`${widget.uuid}`)?.getBoundingClientRect()
right = Math.max(right, Number((rect?.width ?? 0) / (pageStore.dZoom / 100) || widget.record.width) + Number(x))
bottom = Math.max(bottom, Number((rect?.height ?? 0) / (pageStore.dZoom / 100) || widget.record.height) + Number(y))
}
// sortWidgets.sort((a, b) => a.index > b.index)
// for (let i = 0; i < sortWidgets.length; ++i) {
@ -71,9 +73,9 @@ export function realCombined(store: TGroupStore) {
// widgets.splice(index, 1)
// widgets.push(sortWidgets[i].widget)
// }
group.left = Number(left)
group.top = Number(top)
// group.left = Number(left)
// group.top = Number(top)
group.transform = `translate(${left}px,${top}px)`
group.width = Number(right - left)
group.height = Number(bottom - top)
widgetStore.dActiveElement = group
@ -92,6 +94,7 @@ export function getCombined(store: TGroupStore): Promise<TdWidgetData> {
const selectWidgets = widgetStore.dSelectWidgets
return new Promise((resolve) => {
if (selectWidgets.length > 1) {
const widgets = widgetStore.dWidgets
const group = JSON.parse(store.dGroupJson)
group.uuid = nanoid()
@ -109,16 +112,17 @@ export function getCombined(store: TGroupStore): Promise<TdWidgetData> {
const uuid = sortWidgets[i].uuid
const index = widgets.findIndex((item: Type.Object) => item.uuid === uuid)
const widget = { ...widgets[index] } // clone
left = Math.min(left, widget.left)
top = Math.min(top, widget.top)
right = Math.max(right, Number(widget.width) + Number(widget.left))
bottom = Math.max(bottom, Number(widget.height) + Number(widget.top))
const { x, y } = getOffsetFromTransform(widget.transform ?? '')
left = Math.min(left, x)
top = Math.min(top, y)
const rect = document.getElementById(`${widget.uuid}`)?.getBoundingClientRect()
right = Math.max(right, Number((rect?.width ?? 0) / (pageStore.dZoom / 100) || widget.record.width) + Number(x))
bottom = Math.max(bottom, Number((rect?.height ?? 0) / (pageStore.dZoom / 100) || widget.record.height) + Number(y))
}
group.left = left
group.top = top
group.width = right - left
group.height = bottom - top
group.transform = `translate(${left}px,${top}px)`
group.width = Number(right - left)
group.height = Number(bottom - top)
resolve(group)
}

View File

@ -2,13 +2,14 @@
* @Author: Jeremy Yu
* @Date: 2024-03-28 21:00:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-28 14:00:00
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 16:02:46
*/
import { useCanvasStore, useHistoryStore } from "@/store"
import { TWidgetStore, TdWidgetData } from ".."
import { customAlphabet } from 'nanoid/non-secure'
import { transferTransformWidgets } from "@/utils/widgets/transferTranslate"
const nanoid = customAlphabet('1234567890abcdef', 12)
@ -16,11 +17,12 @@ export function addGroup(store: TWidgetStore, group: TdWidgetData[]) {
const historyStore = useHistoryStore()
const canvasStore = useCanvasStore()
let parent: TdWidgetData | null = null
group.forEach((item) => {
const tGroup = transferTransformWidgets(group)
tGroup.forEach((item) => {
item.uuid = nanoid() // 重设id
item.type === 'w-group' && (parent = item) // 找出父组件
})
group.forEach((item) => {
tGroup.forEach((item) => {
!item.isContainer && parent && (item.parent = parent.uuid) // 重设父id
item.text && (item.text = decodeURIComponent(item.text))
store.dWidgets.push(item)

View File

@ -10,13 +10,14 @@
import { customAlphabet } from 'nanoid/non-secure'
import { TWidgetStore, TdWidgetData } from '..'
import { useCanvasStore, useHistoryStore } from '@/store'
import { transferTransformWidgets } from '@/utils/widgets/transferTranslate'
const nanoid = customAlphabet('1234567890abcdef', 12)
// TODO: 选择模板
export function setTemplate(store: TWidgetStore, allWidgets: TdWidgetData[]) {
const historyStore = useHistoryStore()
const canvasStore = useCanvasStore()
allWidgets.forEach((item) => {
transferTransformWidgets(allWidgets).forEach((item) => {
Number(item.uuid) < 0 && (item.uuid = nanoid()) // 重设id
item.text && (item.text = decodeURIComponent(item.text))
store.dWidgets.push(item)

View File

@ -2,13 +2,14 @@
* @Author: Jeremy Yu
* @Date: 2024-03-28 21:00:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-28 14:00:00
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 23:23:56
*/
import { useCanvasStore, useHistoryStore } from "@/store"
import { TWidgetStore, TdWidgetData } from ".."
import { customAlphabet } from 'nanoid/non-secure'
import { getOffsetFromTransform, removeTranslate, transferTransformWidget, transferTransformWidgets } from "@/utils/widgets/transferTranslate"
const nanoid = customAlphabet('1234567890abcdef', 12)
type TUpdateWidgetKey = keyof TdWidgetData
@ -37,7 +38,7 @@ export function updateWidgetData(store: TWidgetStore, { uuid, key, value, pushHi
break
case 'left':
case 'top':
if (widget.isContainer) {
/* if (widget.isContainer) {
let dLeft = widget.left - Number(value)
let dTop = widget.top - Number(value)
if (key === 'left') {
@ -54,8 +55,32 @@ export function updateWidgetData(store: TWidgetStore, { uuid, key, value, pushHi
child.top -= dTop
}
}
}
} */
delete widget.left
delete widget.top
break
case 'transform':
if (widget.isContainer) {
const { x: gX1, y: gY1 } = getOffsetFromTransform(value as any)
const { x: gX, y: gY } = getOffsetFromTransform(widget.transform)
const dLeft = gX - gX1
const dTop = gY - gY1
const len = store.dWidgets.length
for (let i = 0; i < len; ++i) {
const child = store.dWidgets[i]
if (child.parent === widget.uuid) {
let cTransform = removeTranslate(child.transform)
const { x, y } = getOffsetFromTransform(child.transform)
cTransform = `translate(${x - dLeft}px, ${y - dTop}px) ${cTransform}`
child.transform = cTransform
}
}
}
// widget.transform = value as string
break;
case 'transformOrigin':
// widget.transformOrigin = value as string
break;
}
(widget[key] as TUpdateWidgetPayload['value']) = value
if (pushHistory) {
@ -73,7 +98,8 @@ export type TUpdateWidgetMultiplePayload = {
uuid: string
data: {
key: TUpdateWidgetKey
value: number
// value: number
value: string | number | boolean | Record<string, any>
}[]
pushHistory?: boolean
}
@ -82,33 +108,34 @@ export type TUpdateWidgetMultiplePayload = {
export function updateWidgetMultiple(store: TWidgetStore, { uuid, data, pushHistory }: TUpdateWidgetMultiplePayload) {
for (const item of data) {
const { key, value } = item
const widget = store.dWidgets.find((item) => item.uuid === uuid)
if (widget && (widget[key] !== value || pushHistory)) {
switch (key) {
case 'left':
case 'top':
if (widget.isContainer) {
let dLeft = widget.left - value
let dTop = widget.top - value
if (key === 'left') {
dTop = 0
}
if (key === 'top') {
dLeft = 0
}
const len = store.dWidgets.length
for (let i = 0; i < len; ++i) {
const child = store.dWidgets[i]
if (child.parent === widget.uuid) {
child.left -= dLeft
child.top -= dTop
}
}
}
break
}
(widget[key] as number | string) = value
}
updateWidgetData(store, { uuid, key, value })
/* const widget = store.dWidgets.find((item) => item.uuid === uuid)
if (widget && (widget[key] !== value || pushHistory)) {
switch (key) {
case 'left':
case 'top':
if (widget.isContainer) {
let dLeft = widget.left - value
let dTop = widget.top - value
if (key === 'left') {
dTop = 0
}
if (key === 'top') {
dLeft = 0
}
const len = store.dWidgets.length
for (let i = 0; i < len; ++i) {
const child = store.dWidgets[i]
if (child.parent === widget.uuid) {
child.left -= dLeft
child.top -= dTop
}
}
}
break
}
(widget[key] as number | string) = value
} */
}
setTimeout(() => {
const historyStore = useHistoryStore()
@ -122,7 +149,7 @@ export function addWidget(store: TWidgetStore, setting: TdWidgetData) {
const historyStore = useHistoryStore()
const canvasStore = useCanvasStore()
setting.uuid = nanoid()
store.dWidgets.push(setting)
store.dWidgets.push(transferTransformWidget(setting))
const len = store.dWidgets.length
// store.state.dActiveElement = store.state.dWidgets[len - 1]
@ -157,7 +184,7 @@ export function deleteWidget(store: TWidgetStore) {
try {
// 清除掉可能存在的选中框
document.getElementById(uuid)?.classList.remove('widget-selected')
} catch (e) {}
} catch (e) { }
}
store.dSelectWidgets = []
} else {
@ -233,7 +260,7 @@ export function setWidgetStyle(state: TWidgetStore, { uuid, key, value, pushHist
}
export function setDWidgets(state: TWidgetStore, e: TdWidgetData[]) {
state.dWidgets = e
state.dWidgets = transferTransformWidgets(e)
}
// 锁定所有图层 / 再次调用时还原图层

View File

@ -2,8 +2,8 @@
* @Author: Jeremy Yu
* @Date: 2024-03-18 21:00:00
* @Description: Store方法export
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-28 14:00:00
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 15:26:46
*/
import { Store, defineStore } from "pinia";
@ -28,6 +28,7 @@ export type TdWidgetData = TPageState & Partial<TCommonItemData> & {
imgUrl?: string
rotate?: string
transform?: string
transformOrigin?: string
sliceData?: Record<string, any>
flip?: boolean
cropEdit?: boolean

30
src/utils/addFont.ts Normal file
View File

@ -0,0 +1,30 @@
/*
* @Description:
* @Author: xi_zi
* @Date: 2024-03-20 14:59:26
* @LastEditTime: 2024-03-20 16:11:12
* @LastEditors: xi_zi
*/
/**
*
*/
const fonts = new Map()
/**
*
* @param family
* @param url
* @returns
*/
export const addFont = async (family: string, url: string) => {
const key = `${family}_${url}`
if (fonts.has(key)) return { loadFont: fonts.get(key), family, url }
const loadFont = new window.FontFace(family, `url(${url})`)
await loadFont.load()
document.fonts.add(loadFont)
fonts.set(key, loadFont)
return { loadFont, family, url }
}

971
src/utils/magic.ts Normal file
View File

@ -0,0 +1,971 @@
export default {
"version": "2.0.0",
"type": "page",
"uuid": -1,
"global": {
"unit": "px",
"name": "弹窗-运营类_9cd1ee.psd",
"backgroundColor": "#ffffffff",
"layout": {
"left": 0,
"top": 0,
"height": 1176,
"width": 840,
"backgroundImage": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/弹窗_运营类_a4c646.png"
}
},
"layouts": [
{
"uuid": "f069f2ba-5e3a-4dd2-b2b0-d6d2d7e4470a",
"imageUrl": "",
"title": "弹窗-运营类",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 840,
"height": 1176,
"left": 0,
"top": 0,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "group",
"elements": [
{
"uuid": "7e156a83-6cea-4eca-94e7-975c4d9548d0",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/bg_2bdb0d.png",
"title": "bg",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 840,
"height": 1175,
"left": 0,
"top": 0,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "7ec68f3b-123b-40fd-a868-2e2d71558799",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/搬家4_14ff9d.png",
"title": "搬家4",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 287,
"height": 56,
"left": 277,
"top": 1095,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "979d4e6a-700a-4fae-b830-18f5d270dd6d",
"imageUrl": "",
"title": "主视觉",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 840,
"height": 1176,
"left": 0,
"top": 0,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "group",
"elements": [
{
"uuid": "b4561c96-bea7-4d3b-b4b7-307a25d8f8eb",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/椭圆_894_156f13.png",
"title": "椭圆 894",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 767,
"height": 41,
"left": 53,
"top": 849,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "cedabc3d-0025-46a6-b4f1-c3db7d7106b3",
"imageUrl": "",
"title": "红包 拷贝",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 840,
"height": 1176,
"left": 0,
"top": 0,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "group",
"elements": [
{
"uuid": "d058c4cb-f02b-49f3-b6a1-2ee5dfe126cb",
"imageUrl": "",
"title": "组 3231 拷贝 2",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 840,
"height": 1176,
"left": 0,
"top": 0,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "group",
"elements": [
{
"uuid": "6738b1b4-3b1f-44fe-9f04-814c3e634280",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/图层_4_a21999.png",
"title": "图层 4",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 703,
"height": 528,
"left": 68,
"top": 345,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "974ae464-0066-4344-845a-398c9324eaa9",
"imageUrl": "",
"title": "底板2",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 840,
"height": 1176,
"left": 0,
"top": 0,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "group",
"elements": [
{
"uuid": "af29f40a-beb0-4af3-94a5-d5f1f9dfa9f2",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/矩形_1066_5f9ff7.png",
"title": "矩形 1066",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 594,
"height": 333,
"left": 128,
"top": 322,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
}
]
}
]
}
]
},
{
"uuid": "76389660-f7a1-4e17-a2b5-eda27a4a90d1",
"imageUrl": "",
"title": "组 3998",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 840,
"height": 1176,
"left": 0,
"top": 0,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "group",
"elements": [
{
"uuid": "064794d0-9f82-47cf-8b40-c2e6c5fbef51",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/形状_1765_f0b506.png",
"title": "形状 1765",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 375,
"height": 68,
"left": 444,
"top": 851,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "3e0e6cef-690e-4562-b541-b86eb11307f9",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/形状_517_93ba60.png",
"title": "形状 517",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 324,
"height": 72,
"left": 8,
"top": 847,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "572a49fd-298a-4687-9ae0-1a2773ed7fa2",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/图层_22_b0a7ae.png",
"title": "图层 22",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 340,
"height": 193,
"left": 469,
"top": 714,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
}
]
},
{
"uuid": "f9664870-5a00-4cf9-82e8-62afeea201c3",
"imageUrl": "",
"title": "组 3182",
"opacity": 148,
"padding": [
0,
0,
0,
0
],
"width": 840,
"height": 1176,
"left": 0,
"top": 0,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "group",
"elements": [
{
"uuid": "f155cb71-11cf-481c-8a2c-ab3522115c09",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/椭圆_672_6b48d5.png",
"title": "椭圆 672",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 4,
"height": 5,
"left": 27,
"top": 465,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "fc9b296c-d124-4ca7-8253-e3ca1b725ce9",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/椭圆_672_拷贝_3_43f79c.png",
"title": "椭圆 672 拷贝 3",
"opacity": 128,
"padding": [
0,
0,
0,
0
],
"width": 5,
"height": 4,
"left": 727,
"top": 527,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "c209f903-5994-4ce9-a3c3-89858a72f367",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/椭圆_672_拷贝_4_d491e3.png",
"title": "椭圆 672 拷贝 4",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 3,
"height": 3,
"left": 729,
"top": 317,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "188e84a5-0524-4765-9ed7-22bfc80c25ac",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/椭圆_672_拷贝_c115d0.png",
"title": "椭圆 672 拷贝",
"opacity": 89,
"padding": [
0,
0,
0,
0
],
"width": 2,
"height": 2,
"left": 12,
"top": 260,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "8ba537f5-5fc5-40f8-95ad-86e84915e91a",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/椭圆_672_拷贝_5_2c7e71.png",
"title": "椭圆 672 拷贝 5",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 4,
"height": 3,
"left": 135,
"top": 249,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "8d33d44c-5891-47ff-b76d-ab8040ee0c0b",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/椭圆_672_拷贝_2_416355.png",
"title": "椭圆 672 拷贝 2",
"opacity": 77,
"padding": [
0,
0,
0,
0
],
"width": 5,
"height": 5,
"left": 60,
"top": 528,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
}
]
},
{
"uuid": "aa703ab3-7678-4b1e-9472-c5e910b43b44",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/图层_516_04e9fe.png",
"title": "图层 516",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 262,
"height": 171,
"left": 49,
"top": 737,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
}
]
},
{
"uuid": "43fb0ac4-48c9-4ec3-8b53-45eaaa1fd6b8",
"imageUrl": "",
"title": "200元",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 459,
"height": 167,
"left": 195,
"top": 410,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "text",
"elements": [
],
"color": "#F92826FF",
"fontFamily": "Alibaba-PuHuiTi-B",
"fontStyle": "normal",
"fontWeight": 400,
"fontSize": 230,
"lineHeight": 60,
"textType": "paint",
"letterSpacing": 0,
"textDecoration": "none",
"orientation": "horizontal",
"textAlign": "center",
"content": "200元",
"contents": [
],
"boundingBox": {
"left": 195,
"top": 410,
"right": 654,
"bottom": 577,
"width": 459,
"height": 167
},
"textEffects": [
],
"shadows": [
]
},
{
"uuid": "6d054275-770d-4405-9ea0-e671eed9e591",
"imageUrl": "",
"title": "现金",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 89,
"height": 42,
"left": 381,
"top": 333,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "text",
"elements": [
],
"color": "#FF9FA0FF",
"fontFamily": "AdobeHeitiStd-Regular",
"fontStyle": "normal",
"fontWeight": 400,
"fontSize": 46,
"lineHeight": 60,
"textType": "paint",
"letterSpacing": 0,
"textDecoration": "none",
"orientation": "horizontal",
"textAlign": "center",
"content": "现金",
"contents": [
],
"boundingBox": {
"left": 381,
"top": 333,
"right": 470,
"bottom": 375,
"width": 89,
"height": 42
},
"textEffects": [
],
"shadows": [
]
},
{
"uuid": "67824131-6231-4f3d-be98-c9c199167b61",
"imageUrl": "/var/folders/g4/6lr6kwj90_z6mc6njkw1rrkm0000gn/T/com.magic-panel.cep/矩形_976_48da88.png",
"title": "矩形 976",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 744,
"height": 167,
"left": 48,
"top": 913,
"transform": "",
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "image",
"elements": [
]
},
{
"uuid": "b8367b42-a1ad-4082-b43d-3dbb580d2dff",
"imageUrl": "",
"title": "去领取",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 194,
"height": 63,
"left": 325,
"top": 954,
"transform": {
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "text",
"elements": [
],
"color": "#FFFFFFFF",
"fontFamily": "SourceHanSansCN-Bold",
"fontStyle": "normal",
"fontWeight": 400,
"fontSize": 66,
"lineHeight": 60,
"textType": "paint",
"letterSpacing": 0,
"textDecoration": "none",
"orientation": "horizontal",
"textAlign": "center",
"content": "去领取",
"contents": [
],
"boundingBox": {
"left": 325,
"top": 954,
"right": 519,
"bottom": 1017,
"width": 194,
"height": 63
},
"textEffects": [
],
"shadows": [
]
},
{
"uuid": "438d2f8b-3d6a-467c-b96c-d072611f0928",
"imageUrl": "",
"title": "赢现金奖励",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 636,
"height": 159,
"left": 97,
"top": 138,
"transform": {
"a": 1.07171211757033,
"b": 0,
"c": 0,
"d": 1.0695627658834,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "text",
"elements": [
],
"color": "#FFFFFFFF",
"fontFamily": "MFYuanHei-Regular",
"fontStyle": "italic",
"fontWeight": 400,
"fontSize": 110.32544708252,
"lineHeight": 120,
"textType": "paint",
"letterSpacing": 0,
"textDecoration": "none",
"orientation": "horizontal",
"textAlign": "left",
"content": "赢现金奖励",
"contents": [
],
"boundingBox": {
"left": 97,
"top": 138,
"right": 733,
"bottom": 297,
"width": 636,
"height": 159
},
"textEffects": [
{
"enable": true,
"shadow": {
"enable": true,
"blur": 22.39407356372,
"offsetX": 3.44029429027805,
"offsetY": -1.45024358655247,
"color": "#0078FFFF"
}
},
{
"enable": true,
"stroke": {
"width": 1.86617279697666,
"color": "#FFFFFFFF",
"enable": true,
"type": "outer"
}
},
{
"enable": true,
"filling": {
"color": "linear-gradient(-183deg, #FFFFFFFF 12%,#FFDD80FF 99%)",
"enable": true,
"type": 2,
"gradient": {
"byLine": 0,
"angle": -183,
"stops": [
{
"color": "#FFFFFFFF",
"offset": 0.12
},
{
"color": "#FFDD80FF",
"offset": 0.99
}
]
}
}
}
],
"shadows": [
]
},
{
"uuid": "4f7dc2ad-c359-42ee-86f5-86f91de4f7d1",
"imageUrl": "",
"title": "接单码邀冷藏新客 ",
"opacity": 255,
"padding": [
0,
0,
0,
0
],
"width": 559,
"height": 108,
"left": 140,
"top": 46,
"transform": {
"a": 0.85062213156219,
"b": 0,
"c": 0,
"d": 0.84891618265722,
"tx": 0,
"ty": 0
},
"hidden": false,
"lock": false,
"metaInfo": null,
"type": "text",
"elements": [
],
"color": "#FFFFFFFF",
"fontFamily": "MFYuanHei-Regular",
"fontStyle": "italic",
"fontWeight": 400,
"fontSize": 74.7969207763672,
"lineHeight": 120,
"textType": "paint",
"letterSpacing": 0,
"textDecoration": "none",
"orientation": "horizontal",
"textAlign": "left",
"content": "接单码邀冷藏新客\r",
"contents": [
],
"boundingBox": {
"left": 140,
"top": 46,
"right": 699,
"bottom": 154,
"width": 559,
"height": 108
},
"textEffects": [
{
"enable": true,
"shadow": {
"enable": true,
"blur": 28.2146432704769,
"offsetX": 4.33448054323216,
"offsetY": -1.82718455994381,
"color": "#0078FFFF"
}
},
{
"enable": true,
"stroke": {
"width": 2.35122027253975,
"color": "#FFFFFFFF",
"enable": true,
"type": "outer"
}
}
],
"shadows": [
]
}
]
}
]
}

View File

@ -0,0 +1,52 @@
/*
* @Description: Matrix &
* @Author: xi_zi
* @Date: 2024-03-20 23:32:07
* @LastEditTime: 2024-03-20 23:52:28
* @LastEditors: xi_zi
*/
/**
* Matrix
* @param matrix
* @param width
* @returns
*/
export const transferMatrixWidth = (width: number, matrix?: { a: number, b: number, c: number, d: number, tx: number, ty: number }) => {
if (matrix?.a === undefined) return width
return width / Math.pow(Math.abs(matrix.a), 1)
}
/**
* Matrix
* @param matrix
* @param height
* @returns
*/
export const transferMatrixHeight = (height: number, matrix?: { a: number, b: number, c: number, d: number, tx: number, ty: number }) => {
if (matrix?.d === undefined) return height
return height / Math.pow(Math.abs(matrix.d), 1)
}
/**
* Matrix
* @param matrix
* @param left
* @returns
*/
export const transferMatrixLeft = (left: number, matrix?: { a: number, b: number, c: number, d: number, tx: number, ty: number }) => {
if (matrix?.a === undefined) return left
return left * Math.pow(Math.abs(matrix.a), 1)
}
/**
* Matrix
* @param matrix
* @param top
* @returns
*/
export const transferMatrixTop = (top: number, matrix?: { a: number, b: number, c: number, d: number, tx: number, ty: number }) => {
if (matrix?.d === undefined) return top
return top * Math.pow(Math.abs(matrix.d), 1)
}

View File

@ -0,0 +1,61 @@
/*
* @Description: transform
* @Author: xi_zi
* @Date: 2024-04-03 15:17:57
* @LastEditTime: 2024-04-03 23:11:53
* @LastEditors: xi_zi
*/
import { TdWidgetData } from "@/store/design/widget";
/**
* top left
* @param widgetData
* @returns
*/
export const transferTransformWidgets = (widgetData: TdWidgetData[]): TdWidgetData[] => {
return widgetData.map(data => transferTransformWidget(data))
}
/**
* top left
* @param data
* @returns
*/
export const transferTransformWidget = (data: TdWidgetData): TdWidgetData => {
const resData = { ...data }
const { left, top, transform, } = resData
console.log(data, 'widgetTransformStyle')
if (left !== undefined || top !== undefined) {
const regex = /translate\([^)]*\)/g
resData.transform = `translate(${left ?? 0}px, ${top ?? 0}px) ${(transform ?? '').replace(regex, '')}`
delete resData.left;
delete resData.top
} else if (transform?.includes('translate')) {
resData.transform = transform
}
console.log(resData, 'widgetTransformStyle==end')
return resData
}
/**
* transform中的translate移除
* @param transform
* @returns
*/
export const removeTranslate = (transform: string = '') => `${(transform ?? '').replace(/translate\([^)]*\)/g, '')}`
/**
* transform中的translate
* @param transform
* @returns
*/
export const getOffsetFromTransform = (transform: string = '') => {
const res = { x: 0, y: 0 }
const regex = /translate\((-?\d+(\.\d+)?)(px)?,\s*(-?\d+(\.\d+)?)(px)?\)/g
transform.replace(regex, (_match, x, _1, _2, y) => {
res.x = parseFloat(x ?? 0)
res.y = parseFloat(y ?? 0)
return ''
});
return res
}

View File

@ -2,9 +2,9 @@
* @Author: ShawnPhang
* @Date: 2023-09-18 17:34:44
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditors: xi_zi
* @LastUpdateContent: Support typescript
* @LastEditTime: 2024-04-03 10:58:42
* @LastEditTime: 2024-04-03 11:11:39
-->
<template>
<div id="page-design-index" ref="pageDesignIndex" class="page-design-bg-color">
@ -39,46 +39,36 @@
<zoom-control ref="zoomControlRef" />
<!-- 右键菜单 -->
<right-click-menu />
<!-- 旋转缩放组件 -->
<Moveable />
<moveable />
<!-- 遮罩百分比进度条 -->
<ProgressLoading
:percent="state.downloadPercent"
:text="state.downloadText"
:msg="state.downloadMsg"
cancelText="取消"
@cancel="downloadCancel"
@done="state.downloadPercent = 0"
/>
<ProgressLoading :percent="state.downloadPercent" :text="state.downloadText" cancelText="取消" @cancel="downloadCancel" @done="state.downloadPercent = 0" />
</div>
</template>
<script lang="ts" setup>
import _config from '../config'
import {
CSSProperties, computed, nextTick,
onBeforeUnmount, onMounted, reactive, ref,
} from 'vue'
import { CSSProperties, computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import RightClickMenu from '@/components/business/right-click-menu/RcMenu.vue'
import Moveable from '@/components/business/moveable/Moveable.vue'
import designBoard from '@/components/modules/layout/designBoard/index.vue'
import zoomControl from '@/components/modules/layout/zoomControl/index.vue'
import lineGuides from '@/components/modules/layout/lineGuides.vue'
import shortcuts from '@/mixins/shortcuts'
// import wGroup from '@/components/modules/widgets/wGroup/wGroup.vue'
import HeaderOptions from './components/HeaderOptions.vue'
import ProgressLoading from '@/components/common/ProgressLoading/download.vue'
import ProgressLoading from '@/components/common/ProgressLoading/index.vue'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters'
import { useRoute } from 'vue-router'
import { wGroupSetting } from '@/components/modules/widgets/wGroup/groupSetting'
import { storeToRefs } from 'pinia'
import { useCanvasStore, useControlStore, useHistoryStore, useWidgetStore, useGroupStore } from '@/store'
import Moveable from '@/components/business/moveable/Moveable.vue'
type TState = {
style: CSSProperties
downloadPercent: number //
downloadText: string
downloadMsg: string | undefined
isContinue: boolean
APP_NAME: string
showLineGuides: boolean
@ -95,7 +85,6 @@ const { dZoom } = storeToRefs(useCanvasStore())
const { dHistoryParams } = storeToRefs(useHistoryStore())
const { dActiveElement, dCopyElement } = storeToRefs(widgetStore)
const state = reactive<TState>({
style: {
left: '0px',
@ -103,7 +92,6 @@ const state = reactive<TState>({
// openDraw: false,
downloadPercent: 0, //
downloadText: '',
downloadMsg: '',
isContinue: true,
APP_NAME: _config.APP_NAME,
showLineGuides: false,
@ -115,8 +103,8 @@ const route = useRoute()
const beforeUnload = function (e: Event): any {
if (dHistoryParams.value.length > 0) {
const confirmationMessage: string = '系统不会自动保存您未修改的内容';
(e || window.event).returnValue = (confirmationMessage as any) // Gecko and Trident
const confirmationMessage: string = '系统不会自动保存您未修改的内容'
;(e || window.event).returnValue = confirmationMessage as any // Gecko and Trident
return confirmationMessage // Gecko and WebKit
} else return false
}
@ -134,9 +122,7 @@ defineExpose({
})
const undoable = computed(() => {
return !(
dHistoryParams.value.index === -1 ||
(dHistoryParams.value.index === 0 && dHistoryParams.value.length === dHistoryParams.value.maxLength))
return !(dHistoryParams.value.index === -1 || (dHistoryParams.value.index === 0 && dHistoryParams.value.length === dHistoryParams.value.maxLength))
})
const redoable = computed(() => {
@ -180,9 +166,9 @@ onBeforeUnmount(() => {
document.removeEventListener('keyup', handleKeyup(controlStore, checkCtrl), false)
document.oncontextmenu = null
})
// ...mapActions(['selectWidget', 'initGroupJson', 'handleHistory']),
// ...mapActions(['selectWidget', 'initGroupJson', 'handleHistory']),
function handleHistory(data: "undo" | "redo") {
function handleHistory(data: 'undo' | 'redo') {
historyStore.handleHistory(data)
// store.dispatch('handleHistory', data)
}
@ -204,7 +190,7 @@ function loadData() {
if (!zoomControlRef.value) return
// await nextTick()
// zoomControlRef.value.screenChange()
// page
widgetStore.selectWidget({ uuid: '-1' })
// store.dispatch('selectWidget', { uuid: '-1' })
@ -220,10 +206,9 @@ function fixTopBarScroll() {
// console.log('click listener', e)
// }
function optionsChange({ downloadPercent, downloadText, downloadMsg }: { downloadPercent: number, downloadText: string, downloadMsg?: string }) {
function optionsChange({ downloadPercent, downloadText }: { downloadPercent: number; downloadText: string }) {
state.downloadPercent = downloadPercent
state.downloadText = downloadText
state.downloadMsg = downloadMsg
}
</script>