feat: add watermark & canvas size modification

This commit is contained in:
ShawnPhang 2024-04-10 18:11:42 +08:00
parent 2859efd0e7
commit f6903eee7c
31 changed files with 640 additions and 160 deletions

View File

@ -0,0 +1,75 @@
/*
* @Author: ShawnPhang
* @Date: 2024-04-07 17:49:06
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-10 00:37:33
*/
export default [
{
name: '手机海报',
width: 1242,
height: 2208,
icon: 'sd-shouji'
},
{
name: '横版海报',
width: 900,
height: 500,
icon: 'sd-wangye'
},
{
name: '公众号首图',
width: 900,
height: 383,
icon: 'sd-weixin'
},
{
name: '公众号次图',
width: 500,
height: 500,
icon: 'sd-weixin'
},
{
name: '小红书配图',
width: 1242,
height: 1660,
icon: 'sd-shouji'
},
{
name: '商品主图',
width: 800,
height: 800,
icon: 'sd-wangye'
},
{
name: '电商详情页',
width: 750,
height: 1000,
icon: 'sd-wangye'
},
{
name: '电商竖版海报',
width: 750,
height: 950,
icon: 'sd-shouji'
},
{
name: '电商横版海报',
width: 750,
height: 390,
icon: 'sd-wangye'
},
{
name: '小程序封面',
width: 520,
height: 416,
icon: 'sd-weixin'
},
{
name: '壁纸 / PPT(16:9)',
width: 1920,
height: 1080,
icon: 'sd-wangye'
}
]

View File

@ -1,8 +1,8 @@
// element UI fix
.el-collapse-item__header {
padding: 0;
font-size: 14px;
color: #666666;
font-size: 14px !important;
color: #666666 !important;
user-select: none;
}
.el-collapse-item__wrap {

View File

@ -61,6 +61,9 @@
// font-size: 18px;
color: #333333;
}
.iconfont {
font-size: 14px;
}
.text {
font-weight: 600;
margin-left: .4rem;

View File

@ -2,17 +2,17 @@
* @Author: ShawnPhang
* @Date: 2022-02-03 16:30:18
* @Description: Type: success / info / warning / error
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-02 11:50:00
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-08 18:55:12
*/
import { ElMessageBox, messageType } from 'element-plus'
export default (title: string = '提示', message: string = '', type: messageType = 'success') => {
export default (title: string = '提示', message: string = '', type: messageType = 'success', extra?: any) => {
return new Promise((resolve: Function) => {
ElMessageBox.confirm(message, title, {
ElMessageBox.confirm(message, title, Object.assign({
confirmButtonText: '确定',
cancelButtonText: '取消',
type,
})
}, extra))
.then(() => {
resolve(true)
})

View File

@ -0,0 +1,112 @@
<!--
* @Author: ShawnPhang
* @Date: 2024-04-09 11:24:57
* @Description: 创建/编辑画布尺寸
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-10 17:29:15
-->
<template>
<div>
<el-dialog v-model="dialogVisible" center destroy-on-close :align-center="false" :title="params ? '编辑尺寸' : '新建空白设计'" width="380" draggable>
<!-- <el-divider content-position="left">自定义尺寸</el-divider> -->
<el-checkbox v-if="params" v-model="isAdaptive" label="自动调整元素大小位置" size="large" />
<sizeEditor :params="page" :class="params ? 'editor-mode' : 'add-mode'">
<el-button @click="finish" plain size="large" type="primary">{{ params ? '应用' : '创建' }}</el-button>
</sizeEditor>
<el-divider content-position="left">使用推荐尺寸</el-divider>
<ul class="pre-list">
<li @click="applySize(s)" class="item" v-for="(s, si) in sizes" :key="'s' + si">
<i :class="['icon', s.icon]" /> {{ s.name }} <span class="info">{{ s.width }} × {{ s.height }} px</span>
</li>
</ul>
<!-- <template #footer>
<el-button type="primary" @click="dialogVisible = false"> Confirm </el-button>
</template> -->
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, Ref } from 'vue'
import { ElCheckbox } from 'element-plus'
import { useRouter } from 'vue-router'
import sizeEditor from './sizeEditor.vue'
import sizes from '@/assets/data/PageSizeData'
import { useWidgetStore } from '@/store';
const router = useRouter()
const widgetStore = useWidgetStore()
const props = withDefaults(
defineProps<{
params?: any
}>(),
{
params: undefined,
},
)
const dialogVisible: Ref<boolean> = ref(false)
const isAdaptive: Ref<boolean> = ref(true)
const page: any = ref({ width: 100, height: 100 })
const applySize = ({ width, height }: any) => {
page.value.width = width
page.value.height = height
}
const open = () => {
if (props.params) {
page.value.width = props.params.width
page.value.height = props.params.height
}
dialogVisible.value = true
}
function finish() {
const { width, height } = page.value
if (props.params) {
const lastPageData = JSON.parse(JSON.stringify(props.params))
props.params.width = width
props.params.height = height
isAdaptive.value && widgetStore.autoResizeAll(lastPageData)
} else window.open(router.resolve(`/home?mode=create&w_h=${width}*${height}`).href, '_blank')
}
defineExpose({
open,
})
</script>
<style lang="less" scoped>
:deep(.el-dialog__header) {
padding-bottom: 7px !important;
}
.editor-mode {
padding: 0 0 0.5rem 0;
}
.add-mode {
padding: 1rem 0 0.5rem 0;
}
.pre-list {
margin: 1rem 0;
height: 245px;
overflow-y: scroll;
.item {
padding: 10px 8px;
border-radius: 8px;
cursor: pointer;
font-size: 15px;
color: #333;
.icon {
margin-right: 0.2rem;
}
.info {
margin-left: 0.4rem;
font-size: 12px;
color: #b4b8bf;
}
}
.item:hover {
background-color: #f6f7f9;
}
}
</style>

View File

@ -0,0 +1,2 @@
import v from './createDesign.vue'
export default v

View File

@ -0,0 +1,101 @@
<!--
* @Author: ShawnPhang
* @Date: 2024-04-08 21:33:59
* @Description: 尺寸编辑
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-09 15:55:33
-->
<template>
<div class="position-size">
<number-input v-model="params.width" label="宽" :maxValue="5000" />
<el-tooltip :show-after="300" :hide-after="0" effect="dark" :content="lockRatio ? '锁定宽高比' : '自由改变'" placement="top">
<i @click="changeRatio" :class="['icon', lockRatio ? 'sd-db' : 'sd-fdb']" />
</el-tooltip>
<number-input v-model="params.height" label="高" :maxValue="5000" />
<slot />
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import numberInput from '@/components/modules/settings/numberInput.vue'
type TProps = {
params: any
}
const props = withDefaults(defineProps<TProps>(), {
params: {},
})
const lockRatio = ref(false) //
let scale = 0,
temp = { width: 0, height: 0 }
watch(
() => props.params.width,
(val, old) => {
if (scale && isNumber(val) && isNumber(old)) {
temp.width === 0 && (temp.width = props.params.width)
setChange()
}
},
)
watch(
() => props.params.height,
(val, old) => {
if (scale && isNumber(val) && isNumber(old)) {
temp.height === 0 && (temp.height = props.params.height)
setChange()
}
},
)
//
function changeRatio() {
lockRatio.value = !lockRatio.value
if (lockRatio.value) {
scale = props.params.width / props.params.height
} else scale = 0
}
//
let timer: any = null
function setChange() {
clearTimeout(timer)
timer = setTimeout(() => {
temp.width > 0 && temp.height === 0 && (props.params.height = props.params.width / scale)
temp.height > 0 && temp.width === 0 && (props.params.width = props.params.height * scale)
if (temp.width > 0 && temp.height > 0) {
temp = { width: 0, height: 0 }
}
}, 300)
}
function isNumber(val: any) {
return typeof val === 'number'
}
</script>
<style lang="less" scoped>
.position-size {
display: flex;
align-items: center;
// justify-content: space-between;
width: 100%;
.number-input {
flex: 0.2;
}
.icon {
cursor: pointer;
font-size: 19px;
}
.sd-fdb,
.sd-db {
color: #999999;
margin: 0 6px 0 -4px;
}
.sd-db {
color: #333333;
}
}
</style>

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-04 11:46:39
* @Description: 原版movable插件
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-11-14 11:41:23
* @LastEditTime: 2024-04-06 14:56:35
-->
<template>
<div id="empty" class="moveable__remove-item zk-moveable-style"></div>
@ -275,7 +275,9 @@ onMounted(() => {
triggerAblesSimultaneously: true,
}
moveable = new Moveable(document.body, moveableOptions)
// moveable = new Moveable(document.body, moveableOptions)
const containerEl = document.querySelector('#main') as HTMLElement | SVGElement;
moveable = new Moveable(containerEl, moveableOptions)
const helper = new MoveableHelper()
@ -578,12 +580,6 @@ onMounted(() => {
key: 'left',
value: item.left,
})
// store.dispatch("updateWidgetData", {
// uuid: key,
// key: 'left',
// value: item.left,
// })
widgetStore.updateWidgetData({
uuid: key,
key: 'top',

View File

@ -1,9 +1,9 @@
/*
* @Author: ShawnPhang
* @Date: 2021-07-13 22:51:29
* @Description: require.context自动引用
* @LastEditors: ShawnPhang
* @LastEditTime: 2022-04-08 10:28:47
* @Description: Widgetspanel中所有组件将会自动全局引入
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-09 22:31:08
*/
import { App } from "vue"

View File

@ -0,0 +1,34 @@
<!--
* @Author: ShawnPhang
* @Date: 2024-04-08 19:19:17
* @Description: 水印组件封装
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-08 21:30:12
-->
<template>
<slot v-if="isDrawPage" />
<el-watermark v-else :style="props.customStyle" :gap="[140, 120]" :content="watermark">
<slot />
</el-watermark>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ElWatermark } from 'element-plus'
import { storeToRefs } from 'pinia'
import { useBaseStore } from '@/store'
import { useRoute } from 'vue-router'
const route = useRoute()
type TProps = {
customStyle: any
}
const props = withDefaults(defineProps<TProps>(), {
customStyle: {}
})
const isDrawPage = computed(() => route.name === 'Draw')
const baseStore = useBaseStore()
const { watermark } = storeToRefs(baseStore)
</script>

View File

@ -0,0 +1,133 @@
<!--
* @Author: ShawnPhang
* @Date: 2024-04-06 15:17:03
* @Description: 画布尺寸操作柄
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-08 12:28:15
-->
<template>
<div v-show="show" class="page-resize" :style="{ width: Math.floor(pw) + 'px', height: Math.floor(ph) + 'px' }">
<div @mousedown="handlemousedown($event, 'ns', true)" class="resize__bar resize__bar-top"></div>
<div @mousedown="handlemousedown($event, 'ew')" class="resize__bar resize__bar-right"></div>
<div @mousedown="handlemousedown($event, 'ns')" class="resize__bar resize__bar-bottom"></div>
<div @mousedown="handlemousedown($event, 'ew', true)" class="resize__bar resize__bar-left"></div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useWidgetStore, useCanvasStore } from '@/store'
import { storeToRefs } from 'pinia'
const widgetStore = useWidgetStore()
const canvasStore = useCanvasStore()
const props = defineProps<{
width: number
height: number
}>()
type Direction = 'ns' | 'ew' // |
const { dActiveElement } = storeToRefs(widgetStore)
const show = computed(() => dActiveElement.value?.uuid === '-1')
let initData = { x: 0, y: 0, w: 0, h: 0 }
let moveDir = ''
let moveRev: any = undefined
const handlemousedown = (e: any, dir: Direction, isReverse?: boolean) => {
moveDir = dir
moveRev = isReverse
e.stopPropagation()
e.preventDefault()
initData = { x: e.pageX, y: e.pageY, w: canvasStore.dPage.width, h: canvasStore.dPage.height }
document.addEventListener('mousemove', handlemousemove, true)
document.addEventListener('mouseup', stopMove, true)
}
const stopMove = () => {
document.removeEventListener('mousemove', handlemousemove, true)
document.removeEventListener('mouseup', stopMove, true)
}
function handlemousemove(e: any) {
const { x, y, w, h } = initData
if (moveDir === 'ew') {
const dx = e.pageX - x
const moveX = Math.floor((dx * 100) / canvasStore.dZoom)
const result = moveRev ? w - moveX * 2 : w + moveX * 2
result <= 5000 && result > 0 && (canvasStore.dPage.width = result)
} else {
const dy = e.pageY - y
const moveY = Math.floor((dy * 100) / canvasStore.dZoom)
const result = moveRev ? h - moveY * 2 : h + moveY * 2
result <= 5000 && result > 0 && (canvasStore.dPage.height = result)
}
}
const pw = computed(() => props.width)
const ph = computed(() => props.height)
</script>
<style lang="less" scoped>
@bar-color: rgba(0, 0, 0, 0.2);
.page-resize {
pointer-events: none;
position: absolute;
z-index: 10;
.resize__bar {
pointer-events: auto;
position: absolute;
&-left {
left: -14px;
}
&-right {
right: -14px;
}
&-left,
&-right {
position: reactive;
cursor: ew-resize;
width: 10px;
height: 24px;
top: 50%;
transform: translateY(-12px);
}
&-left:after,
&-right:after {
position: absolute;
content: '';
left: 3px;
width: 4px;
height: 24px;
border-radius: 12px;
background: @bar-color;
}
&-top {
top: -14px;
}
&-bottom {
bottom: -12px;
}
&-top,
&-bottom {
cursor: ns-resize;
width: 24px;
height: 10px;
left: 50%;
transform: translateX(-12px);
}
&-top:after,
&-bottom:after {
position: absolute;
content: '';
top: 4px;
width: 24px;
height: 4px;
border-radius: 12px;
background: @bar-color;
}
}
}
</style>

View File

@ -10,7 +10,9 @@
opacity: 1 - (dZoom < 100 ? dPage.tag : 0),
}"
>
<slot />
<slot />
<resize-page :width="(dPage.width * dZoom) / 100" :height="(dPage.height * dZoom) / 100" />
<watermark :customStyle="{ height: (dPage.height * dZoom) / 100 + 'px'}">
<div
:id="pageDesignCanvasId"
class="design-canvas"
@ -33,12 +35,6 @@
@mouseup="drop($event)"
>
<!-- <grid-size /> -->
<!-- :class="{
layer: true,
'layer-active': getIsActive(layer.uuid),
'layer-hover': layer.uuid === dHoverUuid || dActiveElement.parent === layer.uuid,
}" -->
<component
:is="layer.type"
v-for="layer in getlayers()"
@ -69,6 +65,7 @@
<!-- <ref-line v-if="dSelectWidgets.length === 0" /> -->
<!-- <size-control v-if="dSelectWidgets.length === 0" /> -->
</div>
</watermark>
</div>
</div>
</div>
@ -82,9 +79,11 @@ import PointImg from '@/utils/plugins/pointImg'
import getComponentsData from '@/common/methods/DesignFeatures/setComponents'
import { debounce } from 'throttle-debounce'
import { move, moveInit } from '@/mixins/move'
import { useCanvasStore, useControlStore, useGroupStore, useWidgetStore } from '@/store'
import { useCanvasStore, useControlStore, useWidgetStore } from '@/store'
import { storeToRefs } from 'pinia'
import { TPageState } from '@/store/design/canvas/d'
import resizePage from './comps/resize.vue'
import watermark from './comps/pageWatermark.vue'
//
type TProps = {
pageDesignCanvasId: string

View File

@ -8,10 +8,9 @@
</div>
<el-collapse v-else v-model="state.activeNames">
<el-collapse-item title="画布尺寸" name="1">
<div class="position-size">
<number-input v-model="state.innerElement.width" label="宽" :maxValue="5000" @finish="(value) => finish('width', value)" />
<number-input v-model="state.innerElement.height" label="高" :maxValue="5000" @finish="(value) => finish('height', value)" />
</div>
<sizeEditor :params="state.innerElement">
<i @click="openSizeEdit" class="icon sd-edit"></i>
</sizeEditor>
</el-collapse-item>
<el-collapse-item title="背景设置" name="2">
<el-button style="width: 100%; margin: 0 0 1rem 0;" type="primary" link @click="state.showBgLib = true">在背景库中选择</el-button>
@ -49,29 +48,27 @@
<el-button v-show="state.mode === '图片' && state.innerElement.backgroundImage" class="btn-wrap" @click="shiftOut">将背景分离为图层</el-button>
</el-collapse-item>
</el-collapse>
<createDesign ref="sizeEditRef" :params="state.innerElement" />
</div>
</template>
<script lang="ts" setup>
//
// const NAME = 'page-style'
import { nextTick, onMounted, reactive, watch } from 'vue'
import numberInput from '../settings/numberInput.vue'
import colorSelect, { colorChangeData } from '../settings/colorSelect.vue'
import { nextTick, onMounted, reactive, watch, ref, Ref } from 'vue'
import colorSelect, { colorChangeData } from '@/components/modules/settings/colorSelect.vue'
import uploader, { TUploadDoneData } from '@/components/common/Uploader/index.vue'
import api from '@/api'
import _dl from '@/common/methods/download'
// import ColorPipette from '@/utils/plugins/color-pipette'
import Tabs from '@palxp/color-picker/comps/Tabs.vue'
import TabPanel from '@palxp/color-picker/comps/TabPanel.vue'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters'
import { useCanvasStore, useWidgetStore } from '@/store'
import { TPageState } from '@/store/design/canvas/d'
import { storeToRefs } from 'pinia'
import { Delete as iconDelete, Download as iconDownload } from '@element-plus/icons-vue'
import wImageSetting from '@/components/modules/widgets/wImage/wImageSetting'
// import setImageData from '@/common/methods/DesignFeatures/setImage'
import sizeEditor from '@/components/business/create-design/sizeEditor.vue'
import createDesign from '@/components/business/create-design'
type TState = {
activeNames: string[]
@ -84,7 +81,6 @@ type TState = {
showBgLib: boolean
}
const pageStore = useCanvasStore()
const widgetStore = useWidgetStore()
const state = reactive<TState>({
@ -97,6 +93,7 @@ const state = reactive<TState>({
modes: ['颜色', '图片'],
showBgLib: false
})
const sizeEditRef: Ref<typeof createDesign | null> = ref(null)
// const { dActiveElement } = useSetupMapGetters(['dActiveElement'])
const { dActiveElement } = storeToRefs(widgetStore)
let _localTempBG: string | null = null
@ -164,8 +161,6 @@ function changeValue() {
}
function finish(key: keyof TPageState, value: string | number) {
console.log('111');
pageStore.updatePageData({
key: key,
value: value,
@ -202,30 +197,32 @@ async function shiftOut() {
setting.width = state.innerElement.width
setting.height = state.innerElement.height
setting.imgUrl = state.innerElement.backgroundImage
// store.dispatch('addWidget', setting)
setting.uuid = `bg-${(new Date()).getTime()}`
widgetStore.dWidgets.unshift(setting)
widgetStore.selectWidget({
uuid: widgetStore.dWidgets[0].uuid,
})
// store.dispatch('selectWidget', {
// uuid: store.getters.dWidgets[0].uuid,
// })
deleteBg()
}
//
function openSizeEdit() {
sizeEditRef.value?.open()
}
</script>
<style lang="less" scoped>
#page-style {
height: 100%;
width: 100%;
}
.position-size {
display: flex;
// justify-content: space-between;
width: 100%;
.number-input {
flex: 0.25;
.sd-edit {
cursor: pointer;
color: #666666;
font-size: 22px;
}
.sd-edit:hover {
color: #333333;
transform: scale(1.2);
}
}
.select {

View File

@ -68,38 +68,12 @@ function alignAction(item: TIconItemSelectData) {
uuid: element.uuid,
group,
})
// store.dispatch('updateAlign', {
// align: item.value,
// uuid: element.uuid,
// group,
// })
});
historyStore.pushHistory()
// store.dispatch('pushHistory')
// pushHistory()
})
// store.dispatch('getCombined').then((group) => {
// sWidgets.forEach((element: Record<string, any>) => {
// store.dispatch('updateAlign', {
// align: item.value,
// uuid: element.uuid,
// group,
// })
// // updateAlign({
// // align: item.value,
// // uuid: element.uuid,
// // group,
// // })
// });
// store.dispatch('pushHistory')
// // pushHistory()
// })
}
function layerChange(newLayer: TdWidgetData[]) {
widgetStore.setDWidgets(newLayer.toReversed())
// store.commit('setDWidgets', newLayer.toReversed())
// store.commit('setShowMoveable', false)
controlStore.setShowMoveable(false)
}

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-27 15:16:07
* @Description: 背景图
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-03-11 01:42:36
* @LastEditTime: 2024-04-09 22:29:51
-->
<template>
<div class="wrap">
@ -50,13 +50,10 @@ type TState = {
bgList: TGetImageListResult[]
showList: boolean
colors: string[]
}
const { model } = defineProps<TProps>()
const pageStore = useCanvasStore()
const widgetStore = useWidgetStore()

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-27 15:16:07
* @Description: 素材列表主要用于文字组合列表
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-02-29 16:54:28
* @LastEditTime: 2024-04-09 22:30:10
-->
<template>
<div class="wrap">
@ -55,7 +55,6 @@
<script lang="ts" setup>
import { reactive, onMounted } from 'vue'
import api from '@/api'
import getComponentsData from '@/common/methods/DesignFeatures/setComponents'
import DragHelper from '@/common/hooks/dragHelper'
import setItem2Data from '@/common/methods/DesignFeatures/setImage'

View File

@ -118,13 +118,14 @@ function checkHeight() {
isLess && load()
}
let hideReplacePrompt: any = localStorage.getItem('hide_replace_prompt')
async function selectItem(item: IGetTempListData) {
controlStore.setShowMoveable(false) //
if (dHistoryParams.value.length > 0) {
const isPass = await useConfirm('提示', '使用模板后,当前页面将会被替换,是否继续', 'warning')
if (!isPass) {
return false
if (!hideReplacePrompt && dHistoryParams.value.length > 0) {
const doNotPrompt = await useConfirm('添加到作品', '模板内容将替换页面内容', 'warning', {confirmButtonText: '知道了',cancelButtonText: '不再提示'})
if (!doNotPrompt) {
localStorage.setItem('hide_replace_prompt', '1')
hideReplacePrompt = true
}
}
userStore.managerEdit(false)

View File

@ -2,14 +2,14 @@
* @Author: ShawnPhang
* @Date: 2021-07-29 18:31:27
* @Description:
* @LastEditors: ShawnPhang
* @LastEditTime: 2022-04-07 23:26:51
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-10 07:36:58
-->
<template>
<div class="icon-item-select">
<span v-if="label" class="label">{{ label }}</span>
<ul class="list btn__bar flex">
<el-tooltip v-for="(item, index) in data" :key="index" class="item" effect="dark" :content="item.tip" placement="top" :auto-close="400">
<el-tooltip v-for="(item, index) in data" :key="index" class="item" effect="dark" :content="item.tip" placement="top" :show-after="300" >
<li :class="{ 'list-item': true, active: item.select }" @click="selectItem(item)">
<i :class="`${item.extraIcon ? 'icon' : 'iconfont'} ${item.icon}`"></i>
</li>

View File

@ -1,3 +1,10 @@
/*
* @Author: ShawnPhang
* @Date: 2024-04-05 07:31:45
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-10 00:34:21
*/
// const prefix = import.meta.env
const prefix = process.env
@ -16,7 +23,7 @@ export default {
IMG_URL: 'https://store.palxp.cn/', // 七牛云资源地址
// ICONFONT_URL: '//at.alicdn.com/t/font_3223711_74mlzj4jdue.css',
ICONFONT_URL: '//at.alicdn.com/t/font_2717063_ypy8vprc3b.css?display=swap',
ICONFONT_EXTRA: '//at.alicdn.com/t/c/font_3228074_8r5ffak8d5q.css',
ICONFONT_EXTRA: '//at.alicdn.com/t/c/font_3228074_ljv2tbkwgqp.css',
QINIUYUN_PLUGIN: 'https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/qiniu-js/2.5.5/qiniu.min.js',
supportSubFont: true, // 是否开启服务端字体压缩
}

View File

@ -2,13 +2,12 @@
* @Author: ShawnPhang
* @Date: 2022-03-03 14:13:16
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-02-26 17:54:00
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-08 18:19:35
*/
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// import store from './store'
import utils from './utils'
import 'normalize.css/normalize.css'
import '@/assets/styles/index.less'

View File

@ -2,8 +2,8 @@
* @Author: Jeremy Yu
* @Date: 2024-03-17 15:00:00
* @Description: Base全局状态管理
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-18 21:00:00
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-08 17:00:12
*/
import { Store, defineStore } from 'pinia'
@ -13,7 +13,7 @@ import { Store, defineStore } from 'pinia'
type TStoreBaseState = {
loading: boolean | null
scroll: boolean
watermark: string | string[]
/** fonts */
fonts: string[]
}
@ -21,13 +21,14 @@ type TStoreBaseState = {
type TUserAction = {
hideLoading: () => void
setFonts: (list: string[]) => void
changeWatermark: (e: string[] | string) => void
}
/** Base全局状态管理 */
const useBaseStore = defineStore<'base', TStoreBaseState, {}, TUserAction>('base', {
state: () => ({
loading: null,
scroll: true,
watermark: ['迅排设计', 'poster-design'],
fonts: [], // 缓存字体列表
}),
actions: {
@ -40,6 +41,9 @@ const useBaseStore = defineStore<'base', TStoreBaseState, {}, TUserAction>('base
setFonts(list: string[]) {
this.fonts = list
},
changeWatermark(wm: any) {
this.watermark = wm
}
}
})

View File

@ -4,7 +4,7 @@
* @Date: 2024-03-18 21:00:00
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-05 14:52:06
* @LastEditTime: 2024-04-08 21:23:38
*/
import { Store, defineStore } from 'pinia'

View File

@ -2,13 +2,13 @@
* @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: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-10 18:01:14
*/
import { useCanvasStore, useControlStore } from "@/store"
import { TWidgetStore } from ".."
import { updateGroupSize } from "."
import { useCanvasStore, useControlStore } from '@/store'
import { TWidgetStore } from '..'
import { updateGroupSize } from '.'
export type TInitResize = {
startX: number
@ -19,6 +19,18 @@ export type TInitResize = {
height: number
}
export type TSize = {
width: number
height: number
}
export type TdResizePayload = {
x: number
y: number
/** 方向 */
dirs: 'top' | 'left' | 'bottom' | 'right'
}
/** 设置 resize 操作的初始值 */
export function initDResize(store: TWidgetStore, payload: TInitResize) {
const mouseXY = store.dMouseXY
@ -32,24 +44,14 @@ export function initDResize(store: TWidgetStore, payload: TInitResize) {
resizeWH.height = payload.height
}
export type TdResizePayload = {
x: number
y: number
/** 方向 */
dirs: "top" | "left" | "bottom" | "right"
}
/** 更新组件宽高 */
export function dResize(store: TWidgetStore, { x, y, dirs }: TdResizePayload) {
const pageStore = useCanvasStore()
const canvasStore = useCanvasStore()
const controlStore = useControlStore()
controlStore.setdResizeing(true)
// store.state.dResizeing = true
const page = pageStore.dPage
const page = canvasStore.dPage
const target = store.dActiveElement
const mouseXY = store.dMouseXY
const widgetXY = store.dActiveWidgetXY
@ -105,22 +107,39 @@ export function dResize(store: TWidgetStore, { x, y, dirs }: TdResizePayload) {
}
if (parent.uuid !== '-1') {
updateGroupSize(store, parent.uuid)
// store.dispatch('updateGroupSize', parent.uuid)
}
canvasStore.reChangeCanvas()
// store.dispatch('reChangeCanvas')
}
export type TResize = {
width: number
height: number
}
export function resize(state: TWidgetStore, data: TResize) {
const { width, height } = data
const target = state.dActiveElement
export function resize(store: TWidgetStore, size: TSize) {
const { width, height } = size
const target = store.dActiveElement
if (!target) return target
target.width = width
target.height = height
}
/** 自适应适配所有元素 */
export function autoResizeAll(store: TWidgetStore, lastPageSize: TSize) {
if (!lastPageSize) return
const canvasStore = useCanvasStore()
const { width: lastWidth, height: lastHeight } = lastPageSize
const { width: pageWidth, height: pageHeight } = canvasStore.dPage
const originWHRatio = lastWidth / lastHeight // 原始比例
const WHRatio = pageWidth / pageHeight // 当前比例
const changeFn = originWHRatio > WHRatio ? 'max' : 'min'
const degree = [pageWidth / lastWidth, pageHeight / lastHeight]
const ratio = Math[changeFn](...degree)
const pageDiff = (pageWidth - lastWidth) / 2
for (const widget of store.dWidgets) {
const originWidth = widget.width
let diff = 0
if (widget.type === 'w-text') {
widget.fontSize && (widget.fontSize *= ratio)
} else widget.height *= ratio
widget.width *= ratio
diff = (originWidth - widget.width) / 2
widget.left = widget.left + diff + pageDiff
widget.top *= degree[1]
}
}

View File

@ -44,8 +44,6 @@ export function selectWidget(store: TWidgetStore, { uuid }: TSelectWidgetData) {
return
}
store.dSelectWidgets = []
console.log("uuid", uuid);
if (uuid === '-1') {
store.dActiveElement = pageStore.dPage
const pageHistory = historyStore.dPageHistory

View File

@ -2,14 +2,14 @@
* @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: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-10 08:17:16
*/
import { Store, defineStore } from "pinia";
import { TInidDMovePayload, TMovePayload, dMove, initDMove, setDropOver, setMouseEvent, setdActiveElement, updateGroupSize, updateHoverUuid } from "./actions";
import { TPageState } from "@/store/design/canvas/d";
import { TInitResize, TResize, TdResizePayload, dResize, initDResize, resize } from "./actions/resize";
import { TInitResize, TSize, TdResizePayload, dResize, initDResize, resize, autoResizeAll } from "./actions/resize";
import { TUpdateWidgetMultiplePayload, TUpdateWidgetPayload, TsetWidgetStyleData, addWidget, deleteWidget, setDWidgets, setWidgetStyle, updateWidgetData, updateWidgetMultiple, lockWidgets } from "./actions/widget";
import { addGroup } from "./actions/group";
import { setTemplate } from "./actions/template";
@ -113,12 +113,13 @@ type TAction = {
/** 设置拖拽时在哪个图层 */
setDropOver: (uuid: string) => void
setSelectItem: (data: TselectItem) => void
resize: (data: TResize) => void
resize: (data: TSize) => void
setWidgetStyle: (data: TsetWidgetStyleData) => void
setDWidgets: (data: TdWidgetData[]) => void
lockWidgets: () => void
setMouseEvent: (e: MouseEvent | null) => void
setdActiveElement: (data: TdWidgetData) => void
autoResizeAll: (data: TSize) => void
}
const WidgetStore = defineStore<"widgetStore", TWidgetState, TGetter, TAction>("widgetStore", {
@ -180,6 +181,7 @@ const WidgetStore = defineStore<"widgetStore", TWidgetState, TGetter, TAction>("
lockWidgets() { lockWidgets(this) },
setMouseEvent(event) { setMouseEvent(this, event) },
setdActiveElement(data) { setdActiveElement(this, data) },
autoResizeAll(data) { autoResizeAll(this, data) }
}
})

View File

@ -1,16 +1,16 @@
/*
* @Author: ShawnPhang
* @Date: 2021-07-14 11:43:13
* @Description:
* @LastEditors: ShawnPhang
* @LastEditTime: 2021-08-10 17:39:01
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-08 18:23:15
*/
// import { Button, Field, Divider, NavBar, Toast, Popup } from 'vant'
import coms from '@/components/modules'
import pageStyle from '@/components/modules/layout/designBoard/pageStyle.vue'
import { App } from 'vue'
export default (Vue: App) => {
coms(Vue)
// Vue.component(Button.name, Button)
Vue.component('page-style', pageStyle) // 背景属性已不在 modules/widgets 中,单独注册
// Vue.use(Field).use(Divider).use(NavBar).use(Toast).use(Popup)
}

View File

@ -11,13 +11,11 @@
<script lang="ts" setup>
import { StyleValue, onMounted, reactive, nextTick } from 'vue'
import api from '@/api'
// import wGroup from '@/components/modules/widgets/wGroup/wGroup.vue'
import Preload from '@/utils/plugins/preload'
import FontFaceObserver from 'fontfaceobserver'
import { fontWithDraw, font2style } from '@/utils/widgets/loadFontRule'
import designBoard from '@/components/modules/layout/designBoard/index.vue'
import zoomControl from '@/components/modules/layout/zoomControl/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'
@ -50,7 +48,7 @@ onMounted(() => {
async function load() {
let backgroundImage = ''
let loadFlag = false
const { id, tempid, tempType: type } = route.query
const { id, tempid, tempType: type = 0 } = route.query
if (id || tempid) {
const postData = {
id: Number(id || tempid),

View File

@ -4,7 +4,7 @@
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastUpdateContent: Support typescript
* @LastEditTime: 2024-04-05 05:34:43
* @LastEditTime: 2024-04-10 07:16:48
-->
<template>
<div id="page-design-index" ref="pageDesignIndex" class="page-design-bg-color">
@ -59,6 +59,8 @@
/>
<!-- 漫游导航 -->
<Tour ref="tourRef" :steps="[ref1, ref2, ref3, ref4]" />
<!-- 创建设计 -->
<createDesign ref="createDesignRef" />
</div>
</template>
@ -66,7 +68,7 @@
import _config from '../config'
import {
CSSProperties, computed, nextTick,
onBeforeUnmount, onMounted, reactive, ref,
onBeforeUnmount, onMounted, reactive, ref, Ref
} from 'vue'
import RightClickMenu from '@/components/business/right-click-menu/RcMenu.vue'
import Moveable from '@/components/business/moveable/Moveable.vue'
@ -74,18 +76,16 @@ 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 Folder from './components/Folder.vue'
import Helper from './components/Helper.vue'
import ProgressLoading from '@/components/common/ProgressLoading/download.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 type { ButtonInstance } from 'element-plus'
import Tour from './components/Tour.vue'
import createDesign from '@/components/business/create-design'
const ref1 = ref<ButtonInstance>()
const ref2 = ref<ButtonInstance>()
@ -111,7 +111,6 @@ const groupStore = useGroupStore()
const { dPage } = storeToRefs(useCanvasStore())
const { dZoom } = storeToRefs(useCanvasStore())
const { dHistoryParams } = storeToRefs(useHistoryStore())
// const { dActiveElement, dCopyElement } = storeToRefs(widgetStore)
const state = reactive<TState>({
style: {
@ -128,7 +127,7 @@ const state = reactive<TState>({
const optionsRef = ref<typeof HeaderOptions | null>(null)
const zoomControlRef = ref<typeof zoomControl | null>(null)
const controlStore = useControlStore()
const route = useRoute()
const createDesignRef: Ref<typeof createDesign | null> = ref(null)
const beforeUnload = function (e: Event): any {
if (dHistoryParams.value.length > 0) {
@ -211,16 +210,13 @@ function downloadCancel() {
function loadData() {
//
const { id, tempid, tempType } = route.query
if (!optionsRef.value) return
optionsRef.value.load(id, tempid, tempType, async () => {
optionsRef.value.load(async () => {
if (!zoomControlRef.value) return
// await nextTick()
// zoomControlRef.value.screenChange()
// page
widgetStore.selectWidget({ uuid: '-1' })
// store.dispatch('selectWidget', { uuid: '-1' })
})
}
@ -246,7 +242,10 @@ const fns: any = {
download: () => {
optionsRef.value?.download()
},
changeLineGuides
changeLineGuides,
newDesign: () => {
createDesignRef.value?.open()
}
}
const dealWith = (fnName: string, params?: any) => {
fns[fnName](params)

View File

@ -3,7 +3,7 @@
* @Date: 2024-04-03 19:15:21
* @Description: 文件
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-05 06:01:20
* @LastEditTime: 2024-04-10 07:16:00
-->
<template>
<el-dropdown trigger="click" size="large" placement="bottom-start">
@ -12,7 +12,7 @@
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item><div class="item">创建设计</div></el-dropdown-item>
<el-dropdown-item><div @click="$emit('select', 'newDesign')" class="item">创建设计</div></el-dropdown-item>
<el-dropdown-item @click="openPSD">导入文件</el-dropdown-item>
<el-dropdown-item @click="$emit('select', 'save')" divided>保存</el-dropdown-item>
<el-dropdown-item @click="$emit('select', 'download')">导出文件</el-dropdown-item>
@ -25,8 +25,8 @@
</template>
<script setup lang="ts">
// import { ref } from 'vue'
import { useRouter} from 'vue-router'
// import { ref, Ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElDropdown, ElDropdownItem, ElDropdownMenu } from 'element-plus'
const router = useRouter()

View File

@ -3,7 +3,7 @@
* @Date: 2022-01-12 11:26:53
* @Description: 顶部操作按钮组
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-05 05:37:53
* @LastEditTime: 2024-04-09 23:39:24
-->
<template>
<div class="top-title"><el-input v-model="state.title" placeholder="未命名的设计" class="input-wrap" /></div>
@ -16,6 +16,7 @@
<!-- <el-button @click="$store.commit('managerEdit', false)">取消</el-button> -->
<div class="divide__line">|</div>
</template>
<watermark-option style="margin-right: .5rem;" />
<!-- <el-button @click="draw">绘制(测试)</el-button> -->
<!-- <copyRight> -->
<slot />
@ -39,6 +40,7 @@ import _config from '@/config'
import useConfirm from '@/common/methods/confirm'
import { useControlStore, useHistoryStore, useCanvasStore, useUserStore, useWidgetStore } from '@/store/index'
import { storeToRefs } from 'pinia'
import watermarkOption from './Watermark.vue'
type TProps = {
modelValue?: boolean
@ -51,6 +53,7 @@ type TEmits = {
type TState= {
stateBollean: boolean,
wmBollean: boolean,
title: string,
loading: boolean,
}
@ -80,6 +83,7 @@ const { dHistory, dPageHistory } = storeToRefs(useHistoryStore())
const state = reactive<TState>({
stateBollean: false,
wmBollean: false,
title: '',
loading: false,
})
@ -91,7 +95,6 @@ async function save(hasCover: boolean = false) {
return
}
// store.commit('setShowMoveable', false) //
controlStore.setShowMoveable(false) //
// console.log(proxy?.dPage, proxy?.dWidgets)
@ -189,11 +192,17 @@ async function saveTemp() {
// ...mapActions(['pushHistory', 'addGroup']),
async function load(id: number, tempId: number, type: number, cb: () => void) {
async function load(cb: () => void) {
const { id, tempid: tempId, tempType: type, w_h } = route.query
if (route.name !== 'Draw') {
await useFontStore.init() //
}
const apiName = tempId && !id ? 'getTempDetail' : 'getWorks'
if (w_h) {
const wh: any = w_h.toString().split('*')
wh[0] && (dPage.value.width = wh[0])
wh[1] && (dPage.value.height = wh[1])
}
if (!id && !tempId) {
cb()
return

View File

@ -0,0 +1,22 @@
<!--
* @Author: ShawnPhang
* @Date: 2024-04-08 16:50:04
* @Description: 画布加水印
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-08 18:00:37
-->
<template>
<el-switch v-model="wmBollean" @change="wmChange" size="large" inline-prompt style="--el-switch-off-color: #9999999e" active-text="移除水印" inactive-text="官方水印" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useBaseStore } from '@/store'
const baseStore = useBaseStore()
const wmBollean = ref(false)
function wmChange(isRemove: string | number | boolean) {
baseStore.changeWatermark(isRemove ? '' : ['迅排设计', 'poster-design'])
}
</script>