mirror of
https://github.com/palxiao/poster-design.git
synced 2025-07-15 16:02:19 +08:00
Merge pull request #102 from JeremyYu-cn/feat-upgrade-vue3
Merge: main branch
This commit is contained in:
commit
13be7596cc
10
README.md
10
README.md
@ -13,7 +13,7 @@
|
||||
### 特点
|
||||
|
||||
- 丝滑的页面操作体验,丰富的交互细节,基础功能完善
|
||||
- 采用服务端生成图片,能确保多端出图统一性,支持各种 CSS 特性
|
||||
- 采用服务端生成图片,确保多端出图统一性,支持各种 HTML5 特性
|
||||
- 简易 AI 抠图工具,上传图片一键去除背景
|
||||
- 技术栈:Vue3 、Vite5 、Vuex 、ElementPlus,开发体验畅快
|
||||
- 图片生成:Puppeteer、Express
|
||||
@ -54,13 +54,9 @@ npm run dev
|
||||
|
||||
后端需要自己开发,目前本项目演示 Demo 中的后端接口参考:[接口 API 文档](https://xp.palxp.cn/apidoc/index.html)。
|
||||
|
||||
## 其它
|
||||
## 交流群
|
||||
|
||||
我们尝试沉淀一个高质量内容社区,形成可持续学习的平台,同时解决开发者在项目中遇到的疑难和困惑,帮大家少走一些弯路。
|
||||
|
||||
<img style="width: 380px;" src="https://github.com/palxiao/poster-design/assets/21021314/643dcc8b-ef73-4c76-a78c-a7c377b5f268" />
|
||||
|
||||
也欢迎关注公众号:品味前端,回复“加群”进行交流。
|
||||
关注公众号:品味前端,回复 “加群” 获取二维码,更新公告不错过。
|
||||
|
||||
<img style="width: 380px;" src="https://xp.palxp.cn/images/2024-3-1-1709306365949.png" />
|
||||
|
||||
|
@ -1,16 +1,14 @@
|
||||
@import './color.less';
|
||||
// design index page
|
||||
|
||||
// Color variables (appears count calculates by raw css)
|
||||
@color4: #50555b; // Appears 2 times
|
||||
@color5: #808080; // Appears 2 times
|
||||
@color4: #50555b;
|
||||
@color5: #808080;
|
||||
|
||||
// Width variables (appears count calculates by raw css)
|
||||
@width0: 1180px; // Appears 3 times
|
||||
@width1: 120px; // Appears 2 times
|
||||
@width2: 100%; // Appears 8 times
|
||||
@width0: 1180px;
|
||||
@width1: 120px;
|
||||
@width2: 100%;
|
||||
|
||||
@height2: 54px; // Appears 5 times
|
||||
@height2: 54px;
|
||||
|
||||
.page-design-bg-color {
|
||||
// background-color: #4b678c;
|
||||
|
@ -22,6 +22,10 @@ export default defineComponent({
|
||||
computed: mapGetters(['dSelectWidgets', 'dActiveElement', 'activeMouseEvent', 'showMoveable', 'showRotatable', 'dWidgets', 'updateRect', 'updateSelect', 'guidelines']),
|
||||
watch: {
|
||||
async dActiveElement(val) {
|
||||
setTimeout(async () => {
|
||||
await nextTick()
|
||||
this.checkMouseEvent()
|
||||
}, 10);
|
||||
if (!val.record) {
|
||||
return
|
||||
}
|
||||
@ -50,11 +54,7 @@ export default defineComponent({
|
||||
// // Set Move Auto
|
||||
this.moveable.setState({ target: this._target }, () => {
|
||||
// 当出现mouseevent时进行即刻选中
|
||||
if (this.activeMouseEvent) {
|
||||
this.moveable.dragStart(this.activeMouseEvent)
|
||||
// TODO 使用后销毁mouseevent
|
||||
this.$store.commit('setMouseEvent', null)
|
||||
}
|
||||
this.checkMouseEvent()
|
||||
})
|
||||
// // End
|
||||
this.$store.commit('setShowMoveable', true)
|
||||
@ -84,7 +84,7 @@ export default defineComponent({
|
||||
// TODO: 这里是通过旋转来判断是否可以操作
|
||||
this.moveable.renderDirections = val ? ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'] : []
|
||||
this.moveable.resizable = val
|
||||
this.moveable.scalable = val
|
||||
// this.moveable.scalable = val
|
||||
document.getElementsByClassName('moveable-rotation')[0].style.display = val ? 'block' : 'none'
|
||||
},
|
||||
updateRect(val) {
|
||||
@ -366,7 +366,7 @@ export default defineComponent({
|
||||
this.resizeTempData = null
|
||||
// await this.$nextTick()
|
||||
this.moveable.updateRect()
|
||||
// 临时处理缩放后细线问题
|
||||
// 临时处理缩放后细线问题 https://github.com/palxiao/poster-design/issues/75
|
||||
this.$store.commit('setShowMoveable', false)
|
||||
setTimeout(() => {
|
||||
this.$store.commit('setShowMoveable', true)
|
||||
@ -486,7 +486,7 @@ export default defineComponent({
|
||||
},
|
||||
async created() {
|
||||
await nextTick()
|
||||
const Ele = document.getElementById('page-design')
|
||||
const Ele = document.getElementById('main')
|
||||
// 后续可能加个节流 TODO
|
||||
Ele?.addEventListener('scroll', () => {
|
||||
this.moveable.updateRect()
|
||||
@ -494,6 +494,13 @@ export default defineComponent({
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateWidgetData', 'updateWidgetMultiple', 'pushHistory']),
|
||||
checkMouseEvent() {
|
||||
if (this.activeMouseEvent) {
|
||||
this.moveable.dragStart(this.activeMouseEvent)
|
||||
// 使用后销毁mouseevent
|
||||
this.$store.commit('setMouseEvent', null)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
@ -10,12 +10,12 @@
|
||||
<el-tabs tab-position="left" style="height: 60vh" class="demo-tabs" @tab-change="tabChange">
|
||||
<el-tab-pane label="我的素材">
|
||||
<div class="pic__box">
|
||||
<photo-list :isDone="state.isDone" :listData="state.imgList" @load="load" @select="selectImg" />
|
||||
<photo-list :canDrag="false" :isDone="state.isDone" :listData="state.imgList" @load="load" @select="selectImg" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="照片图库">
|
||||
<div class="pic__box">
|
||||
<photo-list :isDone="state.isPicsDone" :listData="state.recommendImgList" @load="loadPic" @select="selectImg($event, state.recommendImgList)" />
|
||||
<photo-list :canDrag="false" :isDone="state.isPicsDone" :listData="state.recommendImgList" @load="loadPic" @select="selectImg($event, state.recommendImgList)" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
@ -225,7 +225,6 @@ async function drop(e: MouseEvent) {
|
||||
store.commit('setShowMoveable', true) // 恢复选择
|
||||
} else {
|
||||
store.dispatch('addWidget', setting) // 正常加入面板
|
||||
// addWidget(setting) // 正常加入面板
|
||||
}
|
||||
}
|
||||
} else if (type === 'bg') {
|
||||
@ -233,7 +232,6 @@ async function drop(e: MouseEvent) {
|
||||
} else if (type !== 'group') {
|
||||
console.log(setting)
|
||||
store.dispatch('addWidget', setting) // 正常加入面板
|
||||
// addWidget(setting) // 正常加入面板
|
||||
}
|
||||
// 清除临时数据
|
||||
// this.$store.commit('selectItem', {})
|
||||
|
@ -259,7 +259,9 @@ async function autoFixTop() {
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
screenChange
|
||||
screenChange,
|
||||
add,
|
||||
sub
|
||||
})
|
||||
|
||||
</script>
|
||||
@ -267,7 +269,7 @@ defineExpose({
|
||||
<style lang="less" scoped>
|
||||
@color-select: #1b1634;
|
||||
@color1: #ffffff; // 选项板背景
|
||||
@color2: #ffffff; // Appears 3 times
|
||||
@color2: #ffffff;
|
||||
@color3: #666666; // 文字主颜色
|
||||
@color4: #c2c2c2; // 禁用
|
||||
@color5: rgba(0, 0, 0, 0.12); // 高亮选项背景
|
||||
|
@ -157,9 +157,9 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@color0: #ffffff; // Appears 5 times
|
||||
@color1: #999999; // Appears 3 times
|
||||
@color2: rgba(0, 0, 0, 0.05); // Appears 2 times
|
||||
@color0: #ffffff;
|
||||
@color1: #999999;
|
||||
@color2: rgba(0, 0, 0, 0.05);
|
||||
|
||||
.widget-list {
|
||||
width: 100%;
|
||||
|
@ -59,29 +59,13 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
// const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
// watch(
|
||||
// () => state.active,
|
||||
// () => {
|
||||
// let screen = document.getElementById('page-design')
|
||||
// nextTick(() => {
|
||||
// proxy?.updateScreen({
|
||||
// width: screen.offsetWidth,
|
||||
// height: screen.offsetHeight,
|
||||
// })
|
||||
// })
|
||||
// },
|
||||
// )
|
||||
|
||||
defineExpose({
|
||||
clickClassify
|
||||
})
|
||||
// ...mapActions(['updateScreen']),
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// Color variables (appears count calculates by raw css)
|
||||
@color1: #3e4651; // Appears 2 times
|
||||
@color1: #3e4651;
|
||||
|
||||
#widget-panel {
|
||||
transition: all 1s;
|
||||
|
@ -10,13 +10,12 @@
|
||||
<div class="list">
|
||||
<div
|
||||
v-for="(item, i) in state.list" :key="i + 'i'"
|
||||
:style="{ width: item.listWidth + 'px', marginRight: item.gap + 'px' }"
|
||||
:style="{ width: item.listWidth + 'px', marginRight: item.gap + 'px', cursor: canDrag ? 'grab' : 'pointer' }"
|
||||
class="list__img" draggable="false"
|
||||
@mousedown="dragStart($event, i)"
|
||||
@mousemove="mousemove"
|
||||
@mouseup="mouseup"
|
||||
@click.stop="select(i)"
|
||||
@dragstart="dragStart($event, i)"
|
||||
>
|
||||
<edit-model v-if="props.edit" :options="props.edit" :data="{ item, i }">
|
||||
<div v-if="item.isDelect" class="list__mask">已删除</div>
|
||||
@ -49,13 +48,13 @@ type TProps = {
|
||||
edit?: Record<string, any>
|
||||
isDone?: boolean
|
||||
isShort?: boolean
|
||||
canDrag?: boolean
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'load'): void
|
||||
(event: 'select', data: number): void
|
||||
(event: 'drag', data: number): void
|
||||
|
||||
}
|
||||
|
||||
type TState = {
|
||||
@ -65,6 +64,7 @@ type TState = {
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
isShort: false,
|
||||
canDrag: true,
|
||||
listData: () => ([])
|
||||
})
|
||||
const emit = defineEmits<TEmits>()
|
||||
@ -176,6 +176,9 @@ const select = (i: number) => {
|
||||
|
||||
const dragStart = async (e: Event | any, i: number) => {
|
||||
e.preventDefault()
|
||||
if (!props.canDrag) {
|
||||
return
|
||||
}
|
||||
startPoint = { x: e.x, y: e.y }
|
||||
if (!state.list[i].isDelect) {
|
||||
const setImageParams: TItem2DataParam = {
|
||||
@ -242,7 +245,6 @@ defineExpose({
|
||||
&__img {
|
||||
// background: #f1f2f4;
|
||||
display: inline-block;
|
||||
cursor: grab;
|
||||
// margin: 0 6px 2px 0;
|
||||
margin-bottom: 3px;
|
||||
border-radius: 2px;
|
||||
|
@ -173,8 +173,8 @@ function blurInput() {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@color0: #e1e1e1; // Appears 2 times
|
||||
@color1: #d1d1d1; // Appears 2 times
|
||||
@color0: #e1e1e1;
|
||||
@color1: #d1d1d1;
|
||||
|
||||
.number-input {
|
||||
height: 60px;
|
||||
|
@ -133,6 +133,7 @@ function updateRecord(tempScale ?: number) {
|
||||
// TODO DOM Change
|
||||
// this.dActiveElement.scale = this.ratio
|
||||
if (widget.value) {
|
||||
// 缩放原点在左上角,旋转原点在中心
|
||||
widget.value.style.transformOrigin = 'left top' // 设置scale的原点
|
||||
setTransformAttribute(widget.value, 'scale', ratio.value)
|
||||
}
|
||||
@ -180,7 +181,7 @@ function touchend() {
|
||||
setTimeout(() => {
|
||||
if (!widget.value) return
|
||||
widget.value.style.opacity = `${props.params.opacity}`
|
||||
// this.$refs.widget.style.transformOrigin = 'center' // 设置scale的原点
|
||||
// widget.value.style.transformOrigin = 'center' // 设置scale的原点
|
||||
}, 100)
|
||||
|
||||
// const opacity = this.$refs.widget.style.opacity
|
||||
|
@ -304,8 +304,11 @@ function openCropper() {
|
||||
// }
|
||||
|
||||
|
||||
function selectDone(img: TGetImageListResult) {
|
||||
async function selectDone(img: TGetImageListResult) {
|
||||
state.innerElement.imgUrl = img.url
|
||||
const loadImg = await getImage(img.url)
|
||||
state.innerElement.width = loadImg.width * store.getters.dZoom / 100
|
||||
state.innerElement.height = loadImg.height * store.getters.dZoom / 100
|
||||
// this.imgCrop(true)
|
||||
}
|
||||
|
||||
|
@ -70,11 +70,11 @@ export default () => {
|
||||
setting.text = await navigator.clipboard.readText()
|
||||
store.dispatch('addWidget', setting)
|
||||
break
|
||||
}
|
||||
} else resolve()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// 剪贴板内容为空
|
||||
// 剪贴板内容为空, 直接返回
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
@ -6,11 +6,8 @@
|
||||
* @LastEditTime: 2023-09-19 17:29:06
|
||||
*/
|
||||
import store from '@/store'
|
||||
const _this: any = {}
|
||||
_this.dHistoryParams = store.getters.dHistoryParams
|
||||
// _this.dActiveElement = store.getters.dActiveElement
|
||||
// _this.dPage = store.getters.dPage
|
||||
// _this.handleHistory = store.dispatch.ha
|
||||
// const _this: any = {}
|
||||
// _this.dHistoryParams = store.getters.dHistoryParams
|
||||
|
||||
import keyCodeOptions from './methods/keyCodeOptions'
|
||||
import dealWithCtrl from './methods/dealWithCtrl'
|
||||
|
@ -277,10 +277,8 @@ export default {
|
||||
} else {
|
||||
copyElement[i].parent = '-1'
|
||||
}
|
||||
if (!container) {
|
||||
copyElement[i].top += 50
|
||||
copyElement[i].left += 50
|
||||
}
|
||||
copyElement[i].top += 30
|
||||
copyElement[i].left += 30
|
||||
}
|
||||
store.state.dWidgets = store.state.dWidgets.concat(copyElement)
|
||||
store.state.dActiveElement = copyElement[0]
|
||||
|
@ -26,7 +26,6 @@ type TState = {
|
||||
style: StyleValue
|
||||
}
|
||||
|
||||
// mixins: [shortcuts],
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const state = reactive<TState>({
|
||||
@ -44,8 +43,8 @@ onMounted(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// ...mapActions(['initGroupJson', 'setTemplate', 'addGroup']),
|
||||
async function load() {
|
||||
let backgroundImage = ''
|
||||
let loadFlag = false
|
||||
const { id, tempid, tempType: type } = route.query
|
||||
if (id || tempid) {
|
||||
@ -64,6 +63,9 @@ async function load() {
|
||||
store.dispatch('addGroup', content)
|
||||
// addGroup(content)
|
||||
} else {
|
||||
// 移除背景图,作为独立事件
|
||||
backgroundImage = content.page?.backgroundImage
|
||||
backgroundImage && delete content.page.backgroundImage
|
||||
store.commit('setDPage', content.page)
|
||||
if (id) {
|
||||
store.commit('setDWidgets', widgets)
|
||||
@ -106,10 +108,11 @@ async function load() {
|
||||
}
|
||||
} catch (e) {}
|
||||
})
|
||||
// TODO优化: 背景图无法检测是否加载完毕,考虑应该将设置背景作为独立事件
|
||||
if (content.page?.backgroundImage) {
|
||||
const preloadBg = new Preload([content.page.backgroundImage])
|
||||
// 背景图无法检测是否加载完毕,所以单独做判断
|
||||
if (backgroundImage) {
|
||||
const preloadBg = new Preload([backgroundImage])
|
||||
await preloadBg.imgs()
|
||||
store.commit('setDPage', {...content.page, ...{backgroundImage}})
|
||||
}
|
||||
try {
|
||||
fontWithDraw && (await font2style(fontContent, fontData))
|
||||
|
@ -57,7 +57,6 @@ import _config from '../config'
|
||||
import {
|
||||
CSSProperties, computed, nextTick,
|
||||
onBeforeUnmount, onMounted, reactive, ref,
|
||||
getCurrentInstance
|
||||
} from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import RightClickMenu from '@/components/business/right-click-menu/RcMenu.vue'
|
||||
@ -65,7 +64,6 @@ 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'
|
||||
@ -83,16 +81,6 @@ type TState = {
|
||||
showLineGuides: boolean
|
||||
}
|
||||
|
||||
const beforeUnload = function (e: Event): string {
|
||||
const confirmationMessage: string = '系统不会自动保存您未修改的内容';
|
||||
|
||||
(e || window.event).returnValue = (confirmationMessage as any) // Gecko and Trident
|
||||
return confirmationMessage // Gecko and WebKit
|
||||
}
|
||||
|
||||
// mixins: [shortcuts],
|
||||
!_config.isDev && window.addEventListener('beforeunload', beforeUnload)
|
||||
|
||||
const {
|
||||
dActiveElement, dHistoryParams, dCopyElement, dPage, dZoom
|
||||
} = useSetupMapGetters(['dActiveElement', 'dHistoryParams', 'dCopyElement', 'dPage', 'dZoom'])
|
||||
@ -113,9 +101,15 @@ const zoomControlRef = ref<typeof zoomControl | null>(null)
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
|
||||
// const draw = () => {
|
||||
// state.openDraw = true
|
||||
// }
|
||||
const beforeUnload = function (e: Event): any {
|
||||
if (store.getters.dHistoryParams.length > 0) {
|
||||
const confirmationMessage: string = '系统不会自动保存您未修改的内容';
|
||||
(e || window.event).returnValue = (confirmationMessage as any) // Gecko and Trident
|
||||
return confirmationMessage // Gecko and WebKit
|
||||
} else return false
|
||||
}
|
||||
|
||||
!_config.isDev && window.addEventListener('beforeunload', beforeUnload)
|
||||
|
||||
function jump2home() {
|
||||
// const fullPath = window.location.href.split('/')
|
||||
@ -136,32 +130,40 @@ const undoable = computed(() => {
|
||||
const redoable = computed(() => {
|
||||
return !(dHistoryParams.value.index === dHistoryParams.value.length - 1)
|
||||
})
|
||||
// watch: {
|
||||
// $route() {
|
||||
// console.log('change route', this.$route.query)
|
||||
// this.loadData()
|
||||
// },
|
||||
// },
|
||||
|
||||
function zoomSub() {
|
||||
if (!zoomControlRef.value) return
|
||||
zoomControlRef.value.sub()
|
||||
}
|
||||
|
||||
function zoomAdd() {
|
||||
if (!zoomControlRef.value) return
|
||||
zoomControlRef.value.add()
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (!optionsRef.value) return
|
||||
optionsRef.value.save()
|
||||
}
|
||||
|
||||
const { handleKeydowm, handleKeyup, dealCtrl } = shortcuts.methods
|
||||
let checkCtrl: number | undefined
|
||||
const instanceFn = { save, zoomAdd, zoomSub }
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('initGroupJson', JSON.stringify(wGroupSetting))
|
||||
// initGroupJson(JSON.stringify(wGroup.setting))
|
||||
window.addEventListener('scroll', fixTopBarScroll)
|
||||
// window.addEventListener('click', this.clickListener)
|
||||
const instance = getCurrentInstance()
|
||||
document.addEventListener('keydown', handleKeydowm(store, checkCtrl, instance, dealCtrl), false)
|
||||
document.addEventListener('keydown', handleKeydowm(store, checkCtrl, instanceFn, dealCtrl), false)
|
||||
document.addEventListener('keyup', handleKeyup(store, checkCtrl), false)
|
||||
loadData()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('scroll', fixTopBarScroll)
|
||||
const instance = getCurrentInstance()
|
||||
// window.removeEventListener('click', this.clickListener)
|
||||
document.removeEventListener('keydown', handleKeydowm(store, checkCtrl, instance, dealCtrl), false)
|
||||
document.removeEventListener('keydown', handleKeydowm(store, checkCtrl, instanceFn, dealCtrl), false)
|
||||
document.removeEventListener('keyup', handleKeyup(store, checkCtrl), false)
|
||||
document.oncontextmenu = null
|
||||
})
|
||||
@ -196,29 +198,14 @@ function loadData() {
|
||||
})
|
||||
}
|
||||
|
||||
function zoomSub() {
|
||||
if (!zoomControlRef.value) return
|
||||
zoomControlRef.value.sub()
|
||||
}
|
||||
|
||||
function zoomAdd() {
|
||||
if (!zoomControlRef.value) return
|
||||
zoomControlRef.value.add()
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (!optionsRef.value) return
|
||||
optionsRef.value.save()
|
||||
}
|
||||
|
||||
function fixTopBarScroll() {
|
||||
const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
|
||||
state.style.left = `-${scrollLeft}px`
|
||||
}
|
||||
|
||||
function clickListener(e: Event) {
|
||||
console.log('click listener', e)
|
||||
}
|
||||
// function clickListener(e: Event) {
|
||||
// console.log('click listener', e)
|
||||
// }
|
||||
|
||||
function optionsChange({ downloadPercent, downloadText }: { downloadPercent: number, downloadText: string }) {
|
||||
state.downloadPercent = downloadPercent
|
||||
|
Loading…
x
Reference in New Issue
Block a user