Merge pull request #72 from JeremyYu-cn/feat-upgrade-vue3

Feat: Convert views and components to composition API
This commit is contained in:
Jeremy Yu 2024-03-07 16:35:05 +00:00 committed by GitHub
commit db962af514
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1009 additions and 844 deletions

View File

@ -39,6 +39,7 @@
"vuex": "^4.0.0-0"
},
"devDependencies": {
"@types/fontfaceobserver": "^2.1.3",
"@types/node": "^20.11.24",
"@types/throttle-debounce": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^7.1.0",

View File

@ -43,6 +43,7 @@ export const getTempList = (params: IGetTempListParam) => fetch<IGetTempListResu
type TGetTempDetail = {
id: number
type?: number
}
export const getTempDetail = (params: TGetTempDetail) => fetch<{data: string}>('design/temp', params, 'get')

View File

@ -32,12 +32,11 @@ export type TGetListData = {
updated_time: string
url: string
width: number
thumbUrl: string
imgUrl: string
}
type TGetListResult = TCommResResult<{
list: TGetListData
total: number
}>
export type TGetListResult = TPageRequestResult<TGetListData[]>

View File

@ -20,7 +20,7 @@ type TProps = {
title: string
width: number
content: string
position: "bottom" | "auto" | "auto-start" | "auto-end" | "top" | "right" | "left" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "right-start" | "right-end" | "left-start" | "left-end"
position?: "bottom" | "auto" | "auto-start" | "auto-end" | "top" | "right" | "left" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "right-start" | "right-end" | "left-start" | "left-end"
offset?: number
}

View File

@ -24,7 +24,7 @@ type TProps = {
percent: number
text: string
cancelText: string
msg: string
msg?: string
}
type TEmits = {

View File

@ -35,9 +35,9 @@ export type TUploadDoneData = {
type TQiNiuUploadReturn = { hash: string, key: string }
type TProps = {
modelValue: TModelData
options: { bucket: string, prePath: string }
hold: boolean
modelValue?: TModelData
options?: { bucket: string, prePath: string }
hold?: boolean
}
type TEmits = {

View File

@ -39,6 +39,7 @@ const state = reactive({
active: true,
})
const clickClassify = (index: number) => {
console.log('index' ,index)
state.activeWidgetClassify = index
state.active = true
}

View File

@ -17,7 +17,7 @@
<i class="icon sd-AI_zhineng" />
<div class="text"><span>智能抠图</span> <span class="desc">上传图像一键去除背景</span></div>
</div>
<imageCutout ref="imageCutout" />
<imageCutout ref="imageCutoutRef" />
</div>
</template>

View File

@ -52,7 +52,7 @@ import { TUploadDoneData } from '@/components/common/Uploader/index.vue'
import { IGetTempListData } from '@/api/home'
type TProps = {
active: number
active?: number
}
type TState = {
@ -104,7 +104,6 @@ const load = (init?: boolean) => {
} else {
state.imgList = state.imgList.concat(list)
}
console.log('state.imgList', state.imgList)
setTimeout(() => {
loading = false
if (!imgListRef.value) return
@ -133,11 +132,9 @@ const loadDesign = (init: boolean = false) => {
return x
}),
))
console.log('state.designList', state.designList)
setTimeout(() => {
loading = false
if (!listRef.value) return
console.log('listRef.value', listRef.value)
checkHeight(listRef.value, loadDesign)
}, 100)
})

View File

@ -30,7 +30,7 @@ export type TClassHeaderTypeData = {
type TProps = {
types: TClassHeaderTypeData[]
isBack: boolean
isBack?: boolean
}
type TEmits = {

View File

@ -46,9 +46,9 @@ import { IGetTempListData } from '@/api/home';
type TProps = {
listData: IGetTempListData[]
edit: Record<string, any>
isDone: boolean
isShort: boolean
edit?: Record<string, any>
isDone?: boolean
isShort?: boolean
}
type TEmits = {
@ -64,7 +64,8 @@ type TState = {
}
const props = withDefaults(defineProps<TProps>(), {
isShort: false
isShort: false,
listData: () => ([])
})
const emit = defineEmits<TEmits>()
const listRef = ref<HTMLElement | null>(null)

View File

@ -37,7 +37,7 @@ import api from '@/api'
type TProps = {
type?: string
modelValue: string
modelValue?: string
}
type TEmits = {

View File

@ -6,27 +6,27 @@
* @LastEditTime: 2023-06-29 17:53:39
-->
<template>
<el-card class="box-card" shadow="hover" :body-style="{ padding: effectSelect ? '20px' : 0 }">
<el-card class="box-card" shadow="hover" :body-style="{ padding: state.effectSelect ? '20px' : 0 }">
<template #header>
<div class="card-header">
<template v-if="effectSelect">
<component :is="effectSelect" class="demo" />
<template v-if="state.effectSelect">
<component :is="state.effectSelect" class="demo" />
</template>
<div v-show="!effectSelect"></div>
<div v-show="!state.effectSelect"></div>
<span class="title">图片容器</span>
<el-popover :visible="visiable" placement="bottom-end" :width="260" trigger="click">
<el-popover :visible="state.visiable" placement="bottom-end" :width="260" trigger="click">
<div class="box__header">
<el-radio-group v-model="type" size="small">
<el-radio-group v-model="state.type" size="small">
<el-radio-button label="160">形状</el-radio-button>
<el-radio-button label="166">框架</el-radio-button>
</el-radio-group>
</div>
<div class="select__box">
<div class="select__box__select-item" @click="select(null)"></div>
<el-image v-for="(item, i) in list" :key="i + 'l'" class="select__box__select-item" :src="item.thumbUrl" fit="contain" @click="select(item.imgUrl)"></el-image>
<div class="select__box__select-item" @click="select()"></div>
<el-image v-for="(item, i) in state.list" :key="i + 'l'" class="select__box__select-item" :src="item.thumbUrl" fit="contain" @click="select(item.imgUrl)"></el-image>
</div>
<template #reference>
<el-button class="button" link @click="visiable = !visiable">{{ visiable ? '取消' : '选择' }}</el-button>
<el-button class="button" link @click="state.visiable = !state.visiable">{{ state.visiable ? '取消' : '选择' }}</el-button>
</template>
</el-popover>
</div>
@ -38,30 +38,49 @@
</el-card>
</template>
<script lang="ts">
<script lang="ts" setup>
import api from '@/api'
import { defineComponent, toRefs, reactive, watch, onMounted, nextTick } from 'vue'
import { toRefs, reactive, watch, onMounted, nextTick, defineProps, defineEmits, defineExpose } from 'vue'
import { ElRadioGroup, ElRadioButton } from 'element-plus'
import wSvg from '@/components/modules/widgets/wSvg/wSvg.vue'
import { TGetListResult } from '@/api/material';
export default defineComponent({
components: { ElRadioGroup, ElRadioButton },
props: ['modelValue', 'degree'],
emits: ['change'],
setup(props, context) {
const state = reactive({
type TProps = {
modelValue?: string
degree?: number
}
type TEmits = {
(event: 'change', data: Record<string, any>): void
}
type TState = {
effectSelect: string
visiable: boolean
type: string
list: {thumbUrl: string, imgUrl: string}[]
}
const props = defineProps<TProps>()
const emit = defineEmits<TEmits>()
const state = reactive<TState>({
// strength: 20, //
effectSelect: '', //
visiable: false, //
type: '166', //
list: [],
})
const select = (value: string = '') => {
state.visiable = false
const setting = JSON.parse(JSON.stringify(wSvg.setting))
setting.svgUrl = value
context.emit('change', setting)
emit('change', setting)
}
onMounted(async () => {
await nextTick()
state.effectSelect = props?.modelValue || ''
@ -74,22 +93,19 @@ export default defineComponent({
first_id: 2,
second_id: state.type,
})
state.list = res.list.map(({ thumbUrl, imgUrl }: any) => {
state.list = res.list.map(({ thumbUrl, imgUrl }) => {
return { thumbUrl, imgUrl }
})
}
watch(
() => state.type,
(value) => {
getList()
},
)
return {
...toRefs(state),
defineExpose({
select,
}
},
methods: {},
})
</script>

View File

@ -27,7 +27,7 @@
:key="efi + 'effect'"
:style="{
color: ef.filling && ef.filling.enable && ef.filling.type === 0 ? ef.filling.color : 'transparent',
webkitTextStroke: ef.stroke && ef.stroke.enable ? `${ef.stroke.width / coefficient}px ${ef.stroke.color}` : undefined,
webkitTextStroke: ef.stroke && ef.stroke.enable ? `${ef.stroke.width / coefficient}px ${ef.stroke.color}` : '',
textShadow: ef.shadow && ef.shadow.enable ? `${ef.shadow.offsetX / coefficient}px ${ef.shadow.offsetY / coefficient}px ${ef.shadow.blur / coefficient}px ${ef.shadow.color}` : undefined,
backgroundImage: ef.filling && ef.filling.enable ? (ef.filling.type === 0 ? undefined : getGradientOrImg(ef)) : undefined,
webkitBackgroundClip: ef.filling && ef.filling.enable ? (ef.filling.type === 0 ? undefined : 'text') : undefined,
@ -39,52 +39,61 @@
A
</div>
<span class="title">文字特效</span>
<el-popover :visible="visiable" placement="left" :width="220" trigger="click">
<el-popover :visible="state.visiable" placement="left" :width="220" trigger="click">
<div class="select__box">
<div class="select__box__select-item" @click="selectEffect(null)"></div>
<div v-for="(l, li) in list" :key="'list' + li" class="select__box__select-item" @click="selectEffect(l.id)">
<div class="select__box__select-item" @click="selectEffect()"></div>
<div v-for="(l, li) in state.list" :key="'list' + li" class="select__box__select-item" @click="selectEffect(l.id)">
<img :src="l.cover" />
</div>
</div>
<template #reference>
<el-button class="button" link @click="openSet">{{ visiable ? '取消' : '选择' }}</el-button>
<el-button class="button" link @click="openSet">{{ state.visiable ? '取消' : '选择' }}</el-button>
</template>
</el-popover>
</div>
</template>
<!-- filling 描边 stroke 阴影 shadow -->
<div v-show="layers && layers.length > 0" class="text item"><span style="width: 65px">强度</span> <el-slider v-model="strength" show-input :maxValue="100" input-size="small" :show-input-controls="false" @input="strengthChange"> </el-slider></div>
<div v-show="state.layers && state.layers.length > 0" class="text item"><span style="width: 65px">强度</span> <el-slider v-model="state.strength" show-input :maxValue="100" input-size="small" :show-input-controls="false" @input="strengthChange"> </el-slider></div>
<el-collapse-item>
<template #title>
<b>高级编辑</b>
</template>
<div class="line"></div>
<div style="display: flex; justify-content: space-between">
<el-button class="add-layer" size="small" type="primary" link @click="addLayer"> + 新建特效层</el-button> <el-button v-show="layers && layers.length > 0" class="add-layer" size="small" type="primary" link @click="unfold = !unfold">{{ unfold ? '收起' : '展开' }}全部</el-button>
<el-button
class="add-layer" size="small" type="primary" link
@click="addLayer">
+ 新建特效层
</el-button>
<el-button
v-show="state.layers && state.layers.length > 0" class="add-layer" size="small"
type="primary" link @click="state.unfold = !state.unfold">
{{ state.unfold ? '收起' : '展开' }}全部
</el-button>
</div>
<div class="line"></div>
<draggable v-model="layers" handle=".sd-yidong" item-key="uuid" v-bind="dragOptions">
<draggable v-model="state.layers" handle=".sd-yidong" item-key="uuid" v-bind="dragOptions">
<template #item="{ element, index }">
<div class="feature__grab-wrap">
<div class="layer__title">
<i class="icon sd-yidong" /><span style="font-size: 12px"><b>特效层</b> {{ index + 1 }}</span>
<i class="icon sd-delete" @click="removeLayer(index)" />
</div>
<div v-if="element.filling && [0, 2, '0', '2'].includes(element.filling.type)" v-show="unfold" class="feature__item">
<div v-if="element.filling && [0, 2, '0', '2'].includes(element.filling.type)" v-show="state.unfold" class="feature__item">
<el-checkbox v-model="element.filling.enable" label="填充" class="feature__header" />
<color-select v-model="element.filling.color" width="28px" :modes="['纯色', '渐变']" label="" @change="colorChange($event, element.filling)" />
</div>
<div v-if="element.stroke" v-show="unfold" class="feature__item">
<div v-if="element.stroke" v-show="state.unfold" class="feature__item">
<el-checkbox v-model="element.stroke.enable" label="描边" class="feature__header" />
<el-input-number v-model="element.stroke.width" style="width: 65px; margin-right: 0.5rem" :min="0" size="small" controls-position="right" />
<color-select v-model="element.stroke.color" width="28px" label="" @finish="(value) => finish('color', value)" />
</div>
<div v-if="element.offset" v-show="unfold" class="feature__item">
<div v-if="element.offset" v-show="state.unfold" class="feature__item">
<el-checkbox v-model="element.offset.enable" label="偏移" class="feature__header" />
<numberInput v-model="element.offset.x" style="width: 49.5px; margin-right: 2px" prepend="x" type="simple" />
<numberInput v-model="element.offset.y" style="width: 49.5px" prepend="y" type="simple" />
</div>
<div v-if="element.shadow" v-show="unfold" class="feature__item">
<div v-if="element.shadow" v-show="state.unfold" class="feature__item">
<el-checkbox v-model="element.shadow.enable" label="阴影" class="feature__header" />
<numberInput v-model="element.shadow.blur" prepend="blur" :minValue="0" style="width: 30px; margin-right: 2px" type="simple" />
<numberInput v-model="element.shadow.offsetX" prepend="x" style="width: 30px; margin-right: 2px" type="simple" />
@ -98,22 +107,46 @@
</el-card>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, watch, onMounted, nextTick, computed } from 'vue'
<script lang="ts" setup>
import {
reactive, watch, onMounted, nextTick, computed,
defineProps, defineEmits, defineExpose
} from 'vue'
import colorSelect from '../colorSelect.vue'
import { ElInputNumber, ElCheckbox } from 'element-plus'
import numberInput from '../numberInput.vue'
import draggable from 'vuedraggable'
import api from '@/api'
import getGradientOrImg from '../../widgets/wText/getGradientOrImg'
let froze_font_effect_list: any = []
let froze_font_effect_list: Record<string, any>[] = []
export default defineComponent({
components: { colorSelect, ElInputNumber, numberInput, ElCheckbox, draggable },
props: ['modelValue', 'degree', 'data'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const state = reactive({
type TProps = {
modelValue?: Record<string, any>
degree?: string | number
data: Record<string, any>
}
type TEmits = {
(event: 'update:modelValue', data: Record<string, any>[]): void
}
type TState = {
strength: number
visiable: boolean
list: Record<string,any>[]
layers: Record<string, any>[]
draging: boolean
unfold: boolean
}
const props = withDefaults(defineProps<TProps>(), {
modelValue: () => ({}),
data: () => ({})
})
const emit = defineEmits<TEmits>()
const state = reactive<TState>({
strength: 50, //
visiable: false, //
list: [],
@ -121,13 +154,14 @@ export default defineComponent({
draging: false,
unfold: true,
})
const dragOptions = {
animation: 300,
ghostClass: 'ghost',
chosenClass: 'choose',
}
const coefficient = computed(() => Math.round(160 / 27))
let rawData: any = [] //
let rawData: Record<string, any>[] = [] //
onMounted(async () => {
await nextTick()
@ -144,6 +178,7 @@ export default defineComponent({
.reverse()
rawData = JSON.parse(JSON.stringify(state.layers))
})
watch(
() => state.layers,
(v) => {
@ -157,12 +192,12 @@ export default defineComponent({
)
//
const selectEffect = async (id) => {
const selectEffect = async (id?: number) => {
state.visiable = false
if (id) {
const { data } = await api.home.getTempDetail({ id, type: 1 })
state.layers = JSON.parse(data)
.textEffects.map((x) => {
.textEffects.map((x: Record<string, any>) => {
x.uuid = String(Math.random())
return x
})
@ -186,10 +221,10 @@ export default defineComponent({
rawData = JSON.parse(JSON.stringify(state.layers))
}
const finish = () => {}
const finish = (type?: string, value?: string) => {}
const colorChange = (e: any, item: any) => {
const modeStr: any = {
const colorChange = (e: Record<string, any>, item: Record<string, any>) => {
const modeStr: Record<string, number> = {
渐变: 2,
纯色: 0,
}
@ -236,8 +271,8 @@ export default defineComponent({
froze_font_effect_list = list
} else state.list = froze_font_effect_list
}
return {
...toRefs(state),
defineExpose({
selectEffect,
finish,
coefficient,
@ -249,9 +284,6 @@ export default defineComponent({
openSet,
colorChange,
getGradientOrImg,
}
},
methods: {},
})
</script>

View File

@ -46,7 +46,7 @@ export default defineComponent({
setup(props, { emit }) {
const store = useStore()
const state: any = reactive({
innerColor: '#ffffffff',
innerColor: '',
// colorLength: 0,
// hasEyeDrop: 'EyeDropper' in window,
})

View File

@ -14,6 +14,7 @@ _this.dHistoryParams = store.getters.dHistoryParams
import keyCodeOptions from './methods/keyCodeOptions'
import dealWithCtrl from './methods/dealWithCtrl'
import { useStore, Store } from 'vuex'
const ignoreNode = ['INPUT', 'TEXTAREA']
@ -36,7 +37,8 @@ let hadDown = false
const shortcuts = {
methods: {
handleKeydowm(e: any) {
handleKeydowm(store: Store<any>, checkCtrl: number | undefined, instance: any, dealCtrl: (e: any, instance: any) => void) {
return (e: any) => {
const nodeName = e.target.nodeName
if (ignoreNode.indexOf(nodeName) !== -1 || (nodeName === 'DIV' && e.target.contentEditable === 'true')) {
return
@ -56,14 +58,14 @@ const shortcuts = {
// hadDown = false
// }
if (shift || ctrl) {
this.$store.dispatch('updateAltDown', true)
clearInterval(this.checkCtrl)
this.checkCtrl = setInterval(() => {
store.dispatch('updateAltDown', true)
clearInterval(checkCtrl)
checkCtrl = setInterval(() => {
// TODO: 防止组合键导致页面失焦无法操作
if (!document.hasFocus()) {
clearInterval(this.checkCtrl)
clearInterval(checkCtrl)
hadDown = false
this.$store.dispatch('updateAltDown', false)
store.dispatch('updateAltDown', false)
}
}, 500)
}
@ -91,7 +93,7 @@ const shortcuts = {
// }
const withCtrl = e.ctrlKey || e.metaKey
if (withCtrl && !(ctrl || alt || shift)) {
this.dealCtrl(e)
dealCtrl(e, instance)
return
}
// const withAlt = e.altKey
@ -113,17 +115,20 @@ const shortcuts = {
// e.preventDefault()
const f = withShift ? 10 : 1
keyCodeOptions(e, { f })
},
handleKeyup(e) {
console.log(e)
clearInterval(this.checkCtrl)
hadDown = false
if (e.key === 'Alt' || e.key === 'Shift' || e.key === 'Control' || e.key === 'Meta') {
this.$store.dispatch('updateAltDown', false)
}
},
dealCtrl(e: any) {
dealWithCtrl(e, this)
handleKeyup(store: Store<any>, checkCtrl: number | undefined) {
return (e: any) => {
console.log(e)
clearInterval(checkCtrl)
hadDown = false
if (e.key === 'Alt' || e.key === 'Shift' || e.key === 'Control' || e.key === 'Meta') {
store.dispatch('updateAltDown', false)
}
}
},
dealCtrl(e: any, instance: any) {
dealWithCtrl(e, instance)
console.log(e.key, e.keyCode)
},
},

View File

@ -62,3 +62,4 @@ interface MouseEvent {
layerX: number
layerY: number
}

View File

@ -8,9 +8,9 @@
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
import { mapActions, mapGetters } from 'vuex'
<script lang="ts" setup>
import { StyleValue, onMounted, reactive, nextTick } from 'vue'
import { useStore } from 'vuex'
import api from '@/api'
import wGroup from '@/components/modules/widgets/wGroup/wGroup.vue'
import Preload from '@/utils/plugins/preload'
@ -18,57 +18,66 @@ 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'
type TState = {
style: StyleValue
}
export default defineComponent({
components: { designBoard, zoomControl },
// mixins: [shortcuts],
setup() {
const state = reactive({
const store = useStore()
const route = useRoute()
const state = reactive<TState>({
style: {
left: '0px',
},
})
const { dPage } = useSetupMapGetters(['dPage'])
return {
...toRefs(state),
}
},
computed: {
...mapGetters(['dPage']),
},
mounted() {
this.initGroupJson(JSON.stringify(wGroup.setting))
this.$nextTick(() => {
this.load()
onMounted(() => {
store.dispatch('initGroupJson', JSON.stringify(wGroup.setting))
// initGroupJson(JSON.stringify(wGroup.setting))
nextTick(() => {
load()
})
})
},
methods: {
...mapActions(['initGroupJson', 'setTemplate', 'addGroup']),
async load() {
let loadFlag = false
const { id, tempid, tempType: type } = this.$route.query
if (id || tempid) {
const { data, width, height } = await api.home[id ? 'getWorks' : 'getTempDetail']({ id: id || tempid, type })
const content = JSON.parse(data)
const widgets = type == 1 ? content : content.widgets
if (type == 1) {
this.dPage.width = width
this.dPage.height = height
this.dPage.backgroundColor = '#ffffff00'
this.addGroup(content)
// ...mapActions(['initGroupJson', 'setTemplate', 'addGroup']),
async function load() {
let loadFlag = false
const { id, tempid, tempType: type } = route.query
if (id || tempid) {
const postData = {
id: Number(id || tempid),
type: Number(type)
}
const { data, width, height } = await api.home[id ? 'getWorks' : 'getTempDetail'](postData)
const content = JSON.parse(data)
const widgets = Number(type) == 1 ? content : content.widgets
if (Number(type) == 1) {
dPage.value.width = width
dPage.value.height = height
dPage.value.backgroundColor = '#ffffff00'
store.dispatch('addGroup', content)
// addGroup(content)
} else {
this.$store.commit('setDPage', content.page)
id ? this.$store.commit('setDWidgets', widgets) : this.setTemplate(widgets)
store.commit('setDPage', content.page)
if (id) {
store.commit('setDWidgets', widgets)
} else {
store.dispatch('setTemplate', widgets)
}
}
await this.$nextTick()
await nextTick()
const imgsData: any = []
const svgsData: any = []
const fontLoaders: any = []
const fontContent: any = {}
let fontData: any = []
const imgsData: HTMLImageElement[] = []
const svgsData: HTMLImageElement[] = []
const fontLoaders: Promise<void>[] = []
const fontContent: Record<string, string> = {}
let fontData: string[] = []
widgets.forEach((item: any) => {
if (item.fontClass && item.fontClass.value) {
const loader = new FontFaceObserver(item.fontClass.value)
@ -131,9 +140,7 @@ export default defineComponent({
setTimeout(() => {
!loadFlag && (window as any).loadFinishToInject('done')
}, 60000)
},
},
})
}
</script>
<style lang="less" scoped>

View File

@ -8,10 +8,10 @@
-->
<template>
<div id="page-design-index" ref="pageDesignIndex" class="page-design-bg-color">
<div :style="style" class="top-nav">
<div :style="state.style" class="top-nav">
<div class="top-nav-wrap">
<div class="top-left">
<div class="name" @click="jump2home">{{ APP_NAME }}</div>
<div class="name" @click="jump2home">{{ state.APP_NAME }}</div>
<div class="operation">
<div :class="['operation-item', { disable: !undoable }]" @click="undoable ? handleHistory('undo') : ''"><i class="iconfont icon-undo" /></div>
<div :class="['operation-item', { disable: !redoable }]" @click="redoable ? handleHistory('redo') : ''"><i class="iconfont icon-redo" /></div>
@ -20,7 +20,7 @@
<i style="font-size: 20px" class="icon sd-biaochi extra-operation" @click="changeLineGuides" />
</el-tooltip>
</div>
<HeaderOptions ref="options" v-model="isContinue" @change="optionsChange" />
<HeaderOptions ref="optionsRef" v-model="state.isContinue" @change="optionsChange" />
</div>
</div>
<div class="page-design-index-wrap">
@ -34,28 +34,32 @@
<style-panel></style-panel>
</div>
<!-- 标尺 -->
<line-guides :show="showLineGuides" />
<line-guides :show="state.showLineGuides" />
<!-- 缩放控制 -->
<zoom-control ref="zoomControl" />
<zoom-control ref="zoomControlRef" />
<!-- 右键菜单 -->
<right-click-menu />
<!-- 旋转缩放组件 -->
<Moveable />
<!-- 遮罩百分比进度条 -->
<ProgressLoading
:percent="downloadPercent"
:text="downloadText"
:percent="state.downloadPercent"
:text="state.downloadText"
cancelText="取消"
@cancel="downloadCancel"
@done="downloadPercent = 0"
@done="state.downloadPercent = 0"
/>
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import _config from '../config'
import { defineComponent, reactive, toRefs } from 'vue'
import { mapActions, mapGetters } from 'vuex'
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'
import Moveable from '@/components/business/moveable/Moveable.vue'
import designBoard from '@/components/modules/layout/designBoard/index.vue'
@ -66,28 +70,33 @@ 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/index.vue'
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
import { useRoute } from 'vue-router'
const beforeUnload = function (e: any) {
const confirmationMessage = '系统不会自动保存您未修改的内容'
;(e || window.event).returnValue = confirmationMessage // Gecko and Trident
type TState = {
style: CSSProperties
downloadPercent: number //
downloadText: string
isContinue: boolean
APP_NAME: string
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
}
export default defineComponent({
components: {
RightClickMenu,
Moveable,
HeaderOptions,
ProgressLoading,
designBoard,
zoomControl,
lineGuides,
},
// mixins: [shortcuts],
setup() {
!_config.isDev && window.addEventListener('beforeunload', beforeUnload)
const state = reactive({
const {
dActiveElement, dHistoryParams, dCopyElement, dPage, dZoom
} = useSetupMapGetters(['dActiveElement', 'dHistoryParams', 'dCopyElement', 'dPage', 'dZoom'])
const state = reactive<TState>({
style: {
left: '0px',
},
@ -98,93 +107,122 @@ export default defineComponent({
APP_NAME: _config.APP_NAME,
showLineGuides: false,
})
const optionsRef = ref<typeof HeaderOptions | null>(null)
const zoomControlRef = ref<typeof zoomControl | null>(null)
const store = useStore()
const route = useRoute()
// const draw = () => {
// state.openDraw = true
// }
function jump2home() {
// const fullPath = window.location.href.split('/')
// window.open(fullPath[0] + '//' + fullPath[2])
window.open('https://xp.palxp.cn/')
}
return {
...toRefs(state),
defineExpose({
jump2home,
}
},
computed: {
...mapGetters(['dActiveElement', 'dHistoryParams', 'dCopyElement', 'dPage', 'dZoom']),
undoable() {
return !(this.dHistoryParams.index === -1 || (this.dHistoryParams === 0 && this.dHistoryParams.length === this.dHistoryParams.maxLength))
},
redoable() {
return !(this.dHistoryParams.index === this.dHistoryParams.length - 1)
},
},
})
const undoable = computed(() => {
return !(
dHistoryParams.value.index === -1 ||
(dHistoryParams.value === 0 && dHistoryParams.value.length === dHistoryParams.value.maxLength))
})
const redoable = computed(() => {
return !(dHistoryParams.value.index === dHistoryParams.value.length - 1)
})
// watch: {
// $route() {
// console.log('change route', this.$route.query)
// this.loadData()
// },
// },
mounted() {
this.initGroupJson(JSON.stringify(wGroup.setting))
window.addEventListener('scroll', this.fixTopBarScroll)
const { handleKeydowm, handleKeyup, dealCtrl } = shortcuts.methods
let checkCtrl: number | undefined
onMounted(() => {
store.dispatch('initGroupJson', JSON.stringify(wGroup.setting))
// initGroupJson(JSON.stringify(wGroup.setting))
window.addEventListener('scroll', fixTopBarScroll)
// window.addEventListener('click', this.clickListener)
document.addEventListener('keydown', this.handleKeydowm, false)
document.addEventListener('keyup', this.handleKeyup, false)
this.loadData()
},
beforeUnmount() {
window.removeEventListener('scroll', this.fixTopBarScroll)
const instance = getCurrentInstance()
document.addEventListener('keydown', handleKeydowm(store, checkCtrl, instance, 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', this.handleKeydowm, false)
document.removeEventListener('keyup', this.handleKeyup, false)
document.removeEventListener('keydown', handleKeydowm(store, checkCtrl, instance, dealCtrl), false)
document.removeEventListener('keyup', handleKeyup(store, checkCtrl), false)
document.oncontextmenu = null
},
methods: {
...mapActions(['selectWidget', 'initGroupJson', 'handleHistory']),
...shortcuts.methods,
changeLineGuides() {
this.showLineGuides = !this.showLineGuides
},
downloadCancel() {
this.downloadPercent = 0
this.isContinue = false
},
loadData() {
})
// ...mapActions(['selectWidget', 'initGroupJson', 'handleHistory']),
function handleHistory(data: string) {
store.dispatch('handleHistory', data)
}
function changeLineGuides() {
state.showLineGuides = !state.showLineGuides
}
function downloadCancel() {
state.downloadPercent = 0
state.isContinue = false
}
function loadData() {
//
const { id, tempid, tempType } = this.$route.query
;(this.$refs as any).options.load(id, tempid, tempType, async () => {
;(this.$refs as any).zoomControl.screenChange()
await this.$nextTick()
const { id, tempid, tempType } = route.query
if (!optionsRef.value) return
optionsRef.value.load(id, tempid, tempType, async () => {
if (!zoomControlRef.value) return
zoomControlRef.value.screenChange()
await nextTick()
// page
this.selectWidget({
uuid: '-1',
store.dispatch('selectWidget', { uuid: '-1' })
// selectWidget({
// uuid: '-1',
// })
})
})
},
zoomSub() {
;(this.$refs as any).zoomControl.sub()
},
zoomAdd() {
;(this.$refs as any).zoomControl.add()
},
save() {
;(this.$refs as any).options.save()
},
fixTopBarScroll() {
}
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
this.style.left = `-${scrollLeft}px`
},
clickListener(e: Event) {
state.style.left = `-${scrollLeft}px`
}
function clickListener(e: Event) {
console.log('click listener', e)
},
optionsChange({ downloadPercent, downloadText }: any) {
this.downloadPercent = downloadPercent
this.downloadText = downloadText
},
},
})
}
function optionsChange({ downloadPercent, downloadText }: { downloadPercent: number, downloadText: string }) {
state.downloadPercent = downloadPercent
state.downloadText = downloadText
}
</script>
<style lang="less" scoped>

View File

@ -12,10 +12,12 @@
<div class="top-left">
<div class="name" style="font-size: 15px">在线PSD解析</div>
</div>
<div style="flex: 1"><el-button plain type="primary" @click="jump2word">说明文档及PSD规范</el-button></div>
<el-button v-show="isDone" @click="clear">清空模板</el-button>
<div style="flex: 1">
<el-button plain type="primary" @click="jump2word">说明文档及PSD规范</el-button>
</div>
<el-button v-show="state.isDone" @click="clear">清空模板</el-button>
<div class="v-tips">
<HeaderOptions :isDone="isDone" @change="optionsChange" />
<HeaderOptions :isDone="state.isDone" @change="optionsChange" />
</div>
</div>
</div>
@ -23,28 +25,38 @@
<div class="page-design-index-wrap">
<!-- <widget-panel></widget-panel> -->
<design-board class="page-design-wrap" pageDesignCanvasId="page-design-canvas">
<div v-if="isDone" class="shelter" :style="{ width: (dPage.width * dZoom) / 100 + 'px', height: (dPage.height * dZoom) / 100 + 'px' }"></div>
<div v-if="state.isDone" class="shelter" :style="{ width: (dPage.width * dZoom) / 100 + 'px', height: (dPage.height * dZoom) / 100 + 'px' }"></div>
<uploader v-else accept=".psd" :hold="true" :drag="true" class="uploader" @load="selectFile">
<div class="uploader__box"><img style="margin-right: 1rem" src="https://cdn.dancf.com/design/svg/icon_psdimport.37e6f23e.svg" /> 在此拖入或选择PSD文件</div>
<div class="uploader__box">
<img
style="margin-right: 1rem"
src="https://cdn.dancf.com/design/svg/icon_psdimport.37e6f23e.svg"
alt="upload"
/> PSD
</div>
</uploader>
</design-board>
<style-panel v-show="isDone"></style-panel>
<style-panel v-show="state.isDone"></style-panel>
</div>
<!-- 缩放控制 -->
<zoom-control v-if="isDone" ref="zoomControl" />
<zoom-control v-if="state.isDone" ref="zoomControlRef" />
<!-- 右键菜单 -->
<right-click-menu />
<!-- 旋转缩放组件 -->
<Moveable />
<!-- 遮罩百分比进度条 -->
<ProgressLoading :percent="downloadPercent" :text="downloadText" :cancelText="cancelText" :msg="downloadMsg" @cancel="cancel" @done="downloadPercent = 0" />
<ProgressLoading
:percent="state.downloadPercent" :text="state.downloadText"
:cancelText="state.cancelText" :msg="state.downloadMsg"
@cancel="cancel" @done="state.downloadPercent = 0"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, getCurrentInstance, ComponentInternalInstance, onMounted, nextTick } from 'vue'
<script lang="ts" setup>
import { reactive, onMounted, nextTick, onBeforeMount, ref, getCurrentInstance } from 'vue'
import { useRoute } from 'vue-router'
import { mapActions, mapGetters, useStore } from 'vuex'
import { useStore } from 'vuex'
import RightClickMenu from '@/components/business/right-click-menu/RcMenu.vue'
import Moveable from '@/components/business/moveable/Moveable.vue'
import shortcuts from '@/mixins/shortcuts'
@ -54,16 +66,22 @@ import useLoading from '@/common/methods/loading'
import uploader from '@/components/common/Uploader/index.vue'
import designBoard from '@/components/modules/layout/designBoard/index.vue'
import zoomControl from '@/components/modules/layout/zoomControl/index.vue'
import HeaderOptions from './components/UploadTemplate.vue'
import HeaderOptions, { TEmitChangeData } from './components/UploadTemplate.vue'
import ProgressLoading from '@/components/common/ProgressLoading/index.vue'
// import MyWorker from '@/utils/plugins/webWorker'
import { processPSD2Page } from '@/utils/plugins/psd'
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
export default defineComponent({
components: { RightClickMenu, Moveable, uploader, designBoard, zoomControl, HeaderOptions, ProgressLoading },
mixins: [shortcuts],
setup() {
const state = reactive({
type TState = {
isDone: boolean
downloadPercent: number //
downloadText: string
downloadMsg: string
cancelText: string
}
// mixins: [shortcuts],
const state = reactive<TState>({
isDone: true,
downloadPercent: 0, //
downloadText: '',
@ -72,12 +90,18 @@ export default defineComponent({
})
const store = useStore()
const route = useRoute()
const { proxy }: any = getCurrentInstance() as ComponentInternalInstance
let loading: any = null
const { dPage, dZoom } = useSetupMapGetters(['dPage', 'dZoom'])
const zoomControlRef = ref<typeof zoomControl | null>()
let loading: ReturnType<typeof useLoading> | null = null
// const myWorker = new MyWorker('loadPSD')
onMounted(async () => {
await nextTick()
if (zoomControlRef.value){
zoomControlRef.value.screenChange()
}
state.isDone = false
})
@ -86,13 +110,15 @@ export default defineComponent({
link_element.setAttribute('src', '/psd.js')
document.head.appendChild(link_element)
}
async function selectFile(file: any) {
async function selectFile(file: File) {
loading = useLoading()
await loadPSD(file)
loading.close()
state.isDone = true
}
async function loadPSD(file: any) {
async function loadPSD(file: File) {
// const { compositeBuffer, psdFile } = await myWorker.start(file)
const data = await processPSD2Page(file)
@ -111,7 +137,7 @@ export default defineComponent({
const { width, height, background: bg } = data
store.commit('setDPage', Object.assign(store.getters.dPage, { width, height, backgroundColor: bg.color, backgroundImage: bg.image }))
await proxy?.loadDone()
await loadDone()
}, 10)
}
@ -123,56 +149,62 @@ export default defineComponent({
state.isDone = false
}
const optionsChange = ({ downloadPercent, downloadText, downloadMsg = '', cancelText = '' }: any) => {
const optionsChange = ({ downloadPercent, downloadText, downloadMsg = '', cancelText = '' }: TEmitChangeData) => {
typeof downloadPercent === 'number' && (state.downloadPercent = downloadPercent)
state.downloadText = downloadText
state.downloadMsg = downloadMsg
state.cancelText = cancelText
}
const cancel = () => {
state.downloadPercent = 100
window.open(`${window.location.protocol + '//' + window.location.host}/home?id=${route.query.id}`)
}
return {
...toRefs(state),
const {handleKeydowm, handleKeyup, dealCtrl} = shortcuts.methods
// ...mapGetters(['dPage', 'dZoom']),
let checkCtrl: number | undefined
onMounted(() => {
const instance = getCurrentInstance()
document.addEventListener('keydown', handleKeydowm(store, checkCtrl, instance, dealCtrl), false)
document.addEventListener('keyup', handleKeyup(store, checkCtrl), false)
loadJS()
})
onBeforeMount(() => {
const instance = getCurrentInstance()
document.removeEventListener('keydown', handleKeydowm(store, checkCtrl, instance, dealCtrl), false)
document.removeEventListener('keyup', handleKeyup(store, checkCtrl), false)
document.oncontextmenu = null
})
// ...mapActions(['selectWidget']),
async function loadDone() {
await nextTick()
if (!zoomControlRef.value) return
zoomControlRef.value.screenChange()
setTimeout(() => {
store.dispatch('selectWidget', { uuid: '-1' })
// selectWidget({
// uuid: '-1',
// })
// this.$store.commit('setShowMoveable', false)
}, 100)
}
function jump2word() {
window.open('https://xp.palxp.cn/#/articles/1687855172725')
// window.open('https://kdocs.cn/l/clmBsIkhve8d')
}
defineExpose({
loadJS,
selectFile,
clear,
cancel,
optionsChange,
}
},
computed: {
...mapGetters(['dPage', 'dZoom']),
},
async mounted() {
document.addEventListener('keydown', this.handleKeydowm, false)
document.addEventListener('keyup', this.handleKeyup, false)
this.loadJS()
},
beforeUnmount() {
document.removeEventListener('keydown', this.handleKeydowm, false)
document.removeEventListener('keyup', this.handleKeyup, false)
document.oncontextmenu = null
},
methods: {
...mapActions(['selectWidget']),
async loadDone() {
await this.$nextTick()
;(this.$refs as any).zoomControl.screenChange()
setTimeout(() => {
this.selectWidget({
uuid: '-1',
})
// this.$store.commit('setShowMoveable', false)
}, 100)
},
jump2word() {
window.open('https://xp.palxp.cn/#/articles/1687855172725')
// window.open('https://kdocs.cn/l/clmBsIkhve8d')
},
},
})
</script>

View File

@ -13,19 +13,10 @@
</tool-tip>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
<script lang="ts" setup>
import toolTip from '@/components/common/PopoverTip.vue'
export default defineComponent({
components: { toolTip },
setup() {
const content = '本站为个人项目,所使用素材图片等均为网络收集而来,下载之作品仅供学习研究或欣赏目的而使用,无法提供商用授权哦。'
return {
content,
}
},
})
</script>
<style lang="less" scoped>

View File

@ -6,10 +6,10 @@
* @LastEditTime: 2023-12-11 12:40:59
-->
<template>
<div class="top-title"><el-input v-model="title" placeholder="未命名的设计" class="input-wrap" /></div>
<div class="top-title"><el-input v-model="state.title" placeholder="未命名的设计" class="input-wrap" /></div>
<div class="top-icon-wrap">
<template v-if="tempEditing">
<span style="color: #999; font-size: 14px; margin-right: 0.5rem">{{ stateBollean ? '启用' : '停用' }}</span> <el-switch v-model="stateBollean" @change="stateChange" />
<span style="color: #999; font-size: 14px; margin-right: 0.5rem">{{ state.stateBollean ? '启用' : '停用' }}</span> <el-switch v-model="state.stateBollean" @change="stateChange" />
<div class="divide__line">|</div>
<el-button plain type="primary" @click="saveTemp">保存模板</el-button>
<el-button @click="$store.commit('managerEdit', false)">取消</el-button>
@ -18,16 +18,16 @@
<!-- <el-button @click="draw">绘制(测试)</el-button> -->
<el-button size="large" class="primary-btn" :disabled="tempEditing" @click="save(false)">保存</el-button>
<copyRight>
<el-button :loading="loading" size="large" class="primary-btn" :disabled="tempEditing" plain type="primary" @click="download">下载作品</el-button>
<el-button :loading="state.loading" size="large" class="primary-btn" :disabled="tempEditing" plain type="primary" @click="download">下载作品</el-button>
</copyRight>
</div>
<!-- 生成图片组件 -->
<SaveImage ref="canvasImage" />
</template>
<script lang="ts">
<script lang="ts" setup>
import api from '@/api'
import { defineComponent, reactive, toRefs, getCurrentInstance, ComponentInternalInstance } from 'vue'
import { reactive, toRefs, defineEmits, defineProps, ref } from 'vue'
import { mapGetters, mapActions, useStore } from 'vuex'
import { useRoute, useRouter } from 'vue-router'
import _dl from '@/common/methods/download'
@ -38,17 +38,34 @@ import copyRight from './CopyRight.vue'
import _config from '@/config'
import useConfirm from '@/common/methods/confirm'
import wGroup from '@/components/modules/widgets/wGroup/wGroup.vue'
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
export default defineComponent({
components: { copyRight, SaveImage },
props: ['modelValue'],
emits: ['change', 'update:modelValue'],
setup(props, context) {
const { proxy }: any = getCurrentInstance() as ComponentInternalInstance
type TProps = {
modelValue?: boolean
}
type TEmits = {
(event: 'change', data: {downloadPercent: number, downloadText: string}): void
(event: 'update:modelValue', data: boolean): void
}
type TState= {
stateBollean: boolean,
title: string,
loading: boolean,
}
const props = defineProps<TProps>()
const emit = defineEmits<TEmits>()
const route = useRoute()
const router = useRouter()
const store = useStore()
const state = reactive({
const canvasImage = ref<typeof SaveImage | null>(null)
const {
dPage, dWidgets, tempEditing, dHistory, dPageHistory
} = useSetupMapGetters(['dPage', 'dWidgets', 'tempEditing', 'dHistory', 'dPageHistory'])
const state = reactive<TState>({
stateBollean: false,
title: '',
loading: false,
@ -57,43 +74,45 @@ export default defineComponent({
//
async function save(hasCover: boolean = false) {
// Bugs: page proxy?.dPageHistory
if (proxy?.dHistory.length <= 0) {
if (dHistory.value.length <= 0) {
return
}
store.commit('setShowMoveable', false) //
// console.log(proxy?.dPage, proxy?.dWidgets)
const { id, tempid } = route.query
const cover = hasCover ? await proxy?.draw() : undefined
const widgets = proxy.dWidgets // reviseData()
const { id: newId, stat, msg } = await api.home.saveWorks({ cover, id, title: proxy.title || '未命名设计', data: JSON.stringify({ page: proxy.dPage, widgets }), temp_id: tempid, width: proxy.dPage.width, height: proxy.dPage.height })
const cover = hasCover ? await draw() : undefined
const widgets = dWidgets.value // reviseData()
const { id: newId, stat, msg } = await api.home.saveWorks({ cover, id, title: state.title || '未命名设计', data: JSON.stringify({ page: dPage.value, widgets }), temp_id: tempid, width: dPage.value.width, height: dPage.value.height })
stat !== 0 ? useNotification('保存成功', '可在"我的作品"中查看') : useNotification('保存失败', msg, { type: 'error' })
!id && router.push({ path: '/home', query: { id: newId }, replace: true })
store.commit('setShowMoveable', true)
}
//
async function saveTemp() {
const { tempid, tempType: type } = route.query
let res = null
if (type == 1) {
if (Number(type) == 1) {
//
if (proxy.dWidgets[0].type === 'w-group') {
const group = proxy.dWidgets.shift()
if (dWidgets.value[0].type === 'w-group') {
const group = dWidgets.value.shift()
group.record.width = 0
group.record.height = 0
proxy.dWidgets.push(group)
dWidgets.value.push(group)
}
// TODO
if (!proxy.dWidgets.some((x) => x.type === 'w-group')) {
if (!dWidgets.value.some((x: Record<string, any>) => x.type === 'w-group')) {
alert('提交组件必须为组合!')
return
// proxy.dWidgets.push(wGroup.setting)
}
res = await api.home.saveTemp({ id: tempid, type, title: proxy.title || '未命名组件', content: JSON.stringify(proxy.dWidgets), width: proxy.dPage.width, height: proxy.dPage.height })
} else res = await api.home.saveTemp({ id: tempid, title: proxy.title || '未命名模板', content: JSON.stringify({ page: proxy.dPage, widgets: proxy.dWidgets }), width: proxy.dPage.width, height: proxy.dPage.height })
res = await api.home.saveTemp({ id: tempid, type, title: state.title || '未命名组件', content: JSON.stringify(dWidgets.value), width: dPage.value.width, height: dPage.value.height })
} else res = await api.home.saveTemp({ id: tempid, title: state.title || '未命名模板', content: JSON.stringify({ page: dPage.value, widgets: dWidgets.value }), width: dPage.value.width, height: dPage.value.height })
res.stat != 0 && useNotification('保存成功', '模板内容已变更')
}
//
async function stateChange(e: any) {
async function stateChange(e: string | number | boolean) {
const { tempid, tempType: type } = route.query
const { stat } = await api.home.saveTemp({ id: tempid, type, state: e ? 1 : 0 })
stat != 0 && useNotification('保存成功', '模板内容已变更')
@ -103,28 +122,28 @@ export default defineComponent({
return
}
//
if (proxy.title === '自设计模板') {
if (state.title === '自设计模板') {
const isPass = await useConfirm('提示', 'PSD自设计作品暂时保存在Github下载可能失败', 'warning')
if (!isPass) {
return
}
}
state.loading = true
context.emit('update:modelValue', true)
context.emit('change', { downloadPercent: 1, downloadText: '正在处理封面' })
emit('update:modelValue', true)
emit('change', { downloadPercent: 1, downloadText: '正在处理封面' })
await save(true)
setTimeout(async () => {
const { id } = route.query
if (id) {
const { width, height } = proxy.dPage
context.emit('update:modelValue', true)
context.emit('change', { downloadPercent: 1, downloadText: '准备合成图片' })
const { width, height } = dPage.value
emit('update:modelValue', true)
emit('change', { downloadPercent: 1, downloadText: '准备合成图片' })
state.loading = false
let timerCount = 0
const animation = setInterval(() => {
if (props.modelValue && timerCount < 75) {
timerCount += RandomNumber(1, 10)
context.emit('change', { downloadPercent: 1 + timerCount, downloadText: '正在合成图片' })
emit('change', { downloadPercent: 1 + timerCount, downloadText: '正在合成图片' })
} else {
clearInterval(animation)
}
@ -132,12 +151,12 @@ export default defineComponent({
await _dl.downloadImg(api.home.download({ id, width, height }) + '&r=' + Math.random(), (progress: number, xhr: any) => {
if (props.modelValue) {
clearInterval(animation)
progress >= timerCount && context.emit('change', { downloadPercent: Number(progress.toFixed(0)), downloadText: '图片生成中' })
progress >= timerCount && emit('change', { downloadPercent: Number(progress.toFixed(0)), downloadText: '图片生成中' })
} else {
xhr.abort()
}
})
context.emit('change', { downloadPercent: 100, downloadText: '图片下载中' })
emit('change', { downloadPercent: 100, downloadText: '图片下载中' })
}
}, 100)
}
@ -145,21 +164,10 @@ export default defineComponent({
return Math.ceil(Math.random() * (max - min)) + min
}
return {
...toRefs(state),
download,
save,
saveTemp,
stateChange,
}
},
computed: {
...mapGetters(['dPage', 'dWidgets', 'tempEditing', 'dHistory', 'dPageHistory']),
},
methods: {
...mapActions(['pushHistory', 'addGroup']),
async load(id: any, tempId: any, type: any, cb: Function) {
if (this.$route.name !== 'Draw') {
// ...mapActions(['pushHistory', 'addGroup']),
async function load(id: number, tempId: number, type: number, cb: () => void) {
if (route.name !== 'Draw') {
await useFontStore.init() //
}
const apiName = tempId && !id ? 'getTempDetail' : 'getWorks'
@ -170,31 +178,43 @@ export default defineComponent({
const { data: content, title, state, width, height } = await api.home[apiName]({ id: id || tempId, type })
if (content) {
const data = JSON.parse(content)
this.stateBollean = !!state
this.title = title
this.$store.commit('setShowMoveable', false) //
state.stateBollean = !!state
state.title = title
store.commit('setShowMoveable', false) //
// this.$store.commit('setDWidgets', [])
if (type == 1) {
//
this.dPage.width = width
this.dPage.height = height
this.addGroup(data)
dPage.value.width = width
dPage.value.height = height
store.dispatch('addGroup', data)
// addGroup(data)
} else {
this.$store.commit('setDPage', data.page)
id ? this.$store.commit('setDWidgets', data.widgets) : this.$store.dispatch('setTemplate', data.widgets)
store.commit('setDPage', data.page)
id ? store.commit('setDWidgets', data.widgets) : store.dispatch('setTemplate', data.widgets)
}
cb()
this.pushHistory('请求加载load')
store.dispatch('pushHistory', '请求加载load')
// pushHistory('load')
}
},
draw() {
}
function draw() {
return new Promise((resolve) => {
this.$refs.canvasImage.createCover(({ key }) => {
if (!canvasImage.value) resolve('')
else {
canvasImage.value.createCover(({ key }: {key: string}) => {
resolve(_config.IMG_URL + key)
})
}
})
},
},
}
defineExpose({
download,
save,
saveTemp,
stateChange,
load,
})
</script>

View File

@ -11,31 +11,55 @@
<SaveImage ref="canvasImage" />
</template>
<script lang="ts">
<script lang="ts" setup>
import api from '@/api'
import { defineComponent, reactive, toRefs, getCurrentInstance, ComponentInternalInstance } from 'vue'
import { mapGetters, useStore } from 'vuex'
import { reactive, ref } from 'vue'
import { useStore } from 'vuex'
import { useRoute, useRouter } from 'vue-router'
import useNotification from '@/common/methods/notification'
import SaveImage from '@/components/business/save-download/CreateCover.vue'
import { useFontStore } from '@/common/methods/fonts'
import _config from '@/config'
import github from '@/api/github'
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
export default defineComponent({
components: { SaveImage },
props: ['modelValue', 'isDone'],
emits: ['change', 'update:modelValue'],
setup(props, context) {
const { proxy }: any = getCurrentInstance() as ComponentInternalInstance
const route = useRoute()
const router = useRouter()
const store = useStore()
const state: any = reactive({
type TProps = {
modelValue?: string
isDone?: boolean
}
export type TEmitChangeData = {
downloadPercent: number | null
downloadText: string
downloadMsg?: string
cancelText?: string
}
type TEmits = {
(event: 'change', data: TEmitChangeData): void
(event: 'update:modelValue', data: string): void
}
type TState = {
stateBollean: false,
title: '',
loading: false,
}
const { dPage, dWidgets } = useSetupMapGetters(['dPage', 'dWidgets'])
const props = defineProps<TProps>()
const emit = defineEmits<TEmits>()
const route = useRoute()
const router = useRouter()
const store = useStore()
const canvasImage = ref<typeof SaveImage | null>(null)
const state = reactive<TState>({
stateBollean: false,
title: '',
loading: false,
canvasImage: null,
})
useFontStore.init() //
@ -43,28 +67,32 @@ export default defineComponent({
//
const draw = () => {
return new Promise((resolve) => {
state.canvasImage.createCover(({ key }: any) => {
if (!canvasImage.value) {
resolve('')
} else {
canvasImage.value.createCover(({ key }: { key: string }) => {
resolve(_config.IMG_URL + key)
})
}
})
}
let addition = 0 //
let lenCount = 0 //
let lens = 0 //
const queue: any[] = [] //
let widgets: any = []
let page: any = {}
const queue: { type: string, imgUrl: string }[] = [] //
let widgets: { type: string, imgUrl: string }[] = []
let page: Record<string, any> = {}
async function prepare() {
store.commit('setShowMoveable', false) //
addition = 0
lenCount = 0
widgets = proxy.dWidgets
page = proxy.dPage
widgets = dWidgets.value
page = dPage.value
if (page.backgroundImage) {
context.emit('change', { downloadPercent: 1, downloadText: '正在准备上传', downloadMsg: '请等待..' })
emit('change', { downloadPercent: 1, downloadText: '正在准备上传', downloadMsg: '请等待..' })
page.backgroundImage = await github.putPic(page.backgroundImage.split(',')[1])
}
@ -81,11 +109,12 @@ export default defineComponent({
async function uploadImgs() {
if (queue.length > 0) {
const item = queue.pop()
if (!item) return
const url = await github.putPic(item.imgUrl.split(',')[1])
addition += item.imgUrl.length
let downloadPercent: any = (addition / lenCount) * 100
let downloadPercent: number | null = (addition / lenCount) * 100
downloadPercent >= 100 && (downloadPercent = null)
context.emit('change', { downloadPercent, downloadText: '上传资源中', downloadMsg: `已完成:${lens - queue.length} / ${lens}` })
emit('change', { downloadPercent, downloadText: '上传资源中', downloadMsg: `已完成:${lens - queue.length} / ${lens}` })
item.imgUrl = url
uploadImgs()
} else {
@ -94,22 +123,16 @@ export default defineComponent({
}
const uploadTemplate = async () => {
context.emit('change', { downloadPercent: 95, downloadText: '正在处理封面', downloadMsg: '即将结束...' })
emit('change', { downloadPercent: 95, downloadText: '正在处理封面', downloadMsg: '即将结束...' })
const cover = await draw()
const { id, stat, msg } = await api.home.saveWorks({ cover, title: '自设计模板', data: JSON.stringify({ page, widgets }), width: page.width, height: page.height })
stat !== 0 ? useNotification('保存成功', '可在"我的模板"中查看') : useNotification('保存失败', msg, { type: 'error' })
router.push({ path: '/psd', query: { id }, replace: true })
context.emit('change', { downloadPercent: 99.99, downloadText: '上传完成', cancelText: '查看我的作品' }) //
emit('change', { downloadPercent: 99.99, downloadText: '上传完成', cancelText: '查看我的作品' }) //
}
return {
...toRefs(state),
defineExpose({
prepare,
}
},
computed: {
...mapGetters(['dPage', 'dWidgets']),
},
})
</script>