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

Feat: Complete the TS transformation of common methods
This commit is contained in:
Jeremy Yu 2024-03-02 14:18:21 +00:00 committed by GitHub
commit 777e125293
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 584 additions and 386 deletions

View File

@ -67,7 +67,15 @@ npm run serve
- [qr-code-styling](https://qr-code-styling.com/): 风格化二维码
- [rembg](https://github.com/danielgatis/rembg): 图片抠图,使用 u2net 预训练模型
或许你在工作中有类似的需求,或许你对开发编辑器感兴趣,也希望这个项目能给到你一些微薄帮助!
### 友情赞助商
[![](https://xp.palxp.cn/images/2024-3-1-1709306907030.png)](https://dooring.vip/)
### 交流群
| 作者微信:备注加群 | 公众号 |
| --- | --- |
| ![](https://xp.palxp.cn/images/2024-3-1-1709306328344.png) | ![](https://xp.palxp.cn/images/2024-3-1-1709306365949.png) |
### `Star`

View File

@ -21,7 +21,12 @@ export const init = (params: Type.Object = {}) => fetch(API.init, params, 'post'
export const getPicList = (params: Type.Object = {}) => fetch(API.getList, params)
export const getToken = (params: Type.Object = {}) => fetch(API.getToken, params)
type TGetTokenParam = {
bucket: string,
name: string
}
export const getToken = (params: TGetTokenParam) => fetch<string>(API.getToken, params)
export const deletePic = (params: Type.Object = {}) => fetch(API.delOne, params, 'post')

View File

@ -43,8 +43,23 @@ type TGetListResult = TCommResResult<{
// 获取素材列表:
export const getList = (params: TGetListParam) => fetch<TGetListResult>('design/material', params)
export type TGetFontParam = {
pageSize?: number
}
/** 字体item数据 */
export type TGetFontItemData = {
id: number
alias: string
oid: string
value: string
preview: string
woff: string
lang: string
}
// 获取字体
export const getFonts = (params: Type.Object = {}) => fetch('design/fonts', params)
export const getFonts = (params: TGetFontParam = {}) => fetch<TPageRequestResult<TGetFontItemData[]>>('design/fonts', params)
export const getFontSub = (params: Type.Object = {}, extra: any = {}) => fetch('design/font_sub', params, 'get', {}, extra)
// 图库列表

View File

@ -2,19 +2,19 @@
* @Author: ShawnPhang
* @Date: 2022-02-22 15:06:14
* @Description:
* @LastEditors: ShawnPhang
* @LastEditTime: 2022-03-07 14:57:51
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-02 11:50:00
*/
import store from '@/store'
export default async function setCompData(item: any) {
const group = typeof item === 'string' ? JSON.parse(item) : JSON.parse(JSON.stringify(item))
let parent: any = {}
export default async function setCompData(item: TCommonItemData[] | string) {
const group: TCommonItemData[] = typeof item === 'string' ? JSON.parse(item) : JSON.parse(JSON.stringify(item))
let parent: Partial<TCommonItemData> = {}
Array.isArray(group) &&
group.forEach((element: any) => {
group.forEach((element) => {
element.type === 'w-group' && (parent = element)
})
const { width: screenWidth, height: screenHeight } = store.getters.dPage
const { width: imgWidth, height: imgHeight } = parent
const { width: imgWidth = 0, height: imgHeight = 0 } = parent
let ratio = 1
// 先限制在画布内,保证不超过边界
if (imgWidth > screenWidth || imgHeight > screenHeight) {
@ -23,7 +23,7 @@ export default async function setCompData(item: any) {
// 根据画布缩放比例再进行一次调整
if (ratio < 1) {
ratio *= store.getters.dZoom / 100
group.forEach((element: any) => {
group.forEach((element) => {
element.fontSize && (element.fontSize *= ratio)
element.width *= ratio
element.height *= ratio

View File

@ -2,8 +2,8 @@
* @Author: ShawnPhang
* @Date: 2021-08-29 20:35:31
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-10-05 16:11:55
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-02 11:50:00
*/
import dayjs from 'dayjs'
import api from '@/api/album'
@ -15,13 +15,13 @@ interface Options {
}
export default {
upload: async (file: File, options: Options, cb?: Function) => {
const win: any = window
upload: async (file: File, options: Options, cb?: IQiniuSubscribeCb) => {
const win = window
let name = ''
const suffix = file.type.split('/')[1] || 'png' // 文件后缀
if (!options.fullPath) {
// const DT: any = await exifGetTime(file) // 照片时间
const DT: any = new Date()
const DT = new Date()
const YM = `${dayjs(DT).format('YYYY')}/${dayjs(DT).format('MM')}/` // 文件时间分类
const keyName = YM + new Date(DT).getTime()
const prePath = options.prePath ? options.prePath + '/' : ''
@ -32,15 +32,15 @@ export default {
useCdnDomain: true, // 使用cdn加速
}
const observable = win.qiniu.upload(file, name, token, {}, exOption)
return new Promise((resolve: Function, reject: Function) => {
return new Promise((resolve: IQiniuSubscribeCb, reject: (err: string) => void) => {
observable.subscribe({
next: (result: any) => {
cb && cb(result) // result.total.percent -> 展示进度
next: (result) => {
cb?.(result) // result.total.percent -> 展示进度
},
error: (e: any) => {
error: (e) => {
reject(e)
},
complete: (result: any) => {
complete: (result) => {
resolve(result)
// cb && cb(result) // result.total.percent -> 展示进度
},

View File

@ -2,12 +2,19 @@
* @Author: ShawnPhang
* @Date: 2022-03-25 13:43:07
* @Description:
* @LastEditors: ShawnPhang
* @LastEditTime: 2022-03-25 14:32:19
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-02 11:50:00
*/
import store from '@/store'
export default function(el: Element | string, cb: Function, altLimit: boolean = true) {
type TAddEventCb = (e: Event) => void
type TAddEventObj = {
attachEvent?: HTMLElement["addEventListener"]
} & HTMLElement
export default function(el: HTMLElement | string, cb: Function, altLimit: boolean = true) {
const box = typeof el === 'string' ? document.getElementById(el) : el
if (!box) return;
addEvent(box, 'mousewheel', (e: any) => {
const ev = e || window.event
const down = ev.wheelDelta ? ev.wheelDelta < 0 : ev.detail > 0
@ -16,10 +23,7 @@ export default function(el: Element | string, cb: Function, altLimit: boolean =
// } else {
// console.log('鼠标滚轮向上++++++++++')
// }
if (altLimit && store.getters.dAltDown) {
ev.preventDefault()
cb(down)
} else if (!altLimit) {
if ((altLimit && store.getters.dAltDown) || !altLimit) {
ev.preventDefault()
cb(down)
}
@ -27,7 +31,7 @@ export default function(el: Element | string, cb: Function, altLimit: boolean =
})
}
function addEvent(obj: any, xEvent: string, fn: Function) {
function addEvent(obj: TAddEventObj, xEvent: keyof HTMLElementEventMap, fn: TAddEventCb) {
if (obj.attachEvent) {
obj.attachEvent('on' + xEvent, fn)
} else {

View File

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

View File

@ -2,11 +2,14 @@
* @Author: ShawnPhang
* @Date: 2021-09-30 15:52:59
* @Description:
* @LastEditors: ShawnPhang <site: book.palxp.com>
* @LastEditTime: 2023-07-12 16:54:51
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-02 11:50:00
*/
export default (src: string, cb: Function) => {
return new Promise((resolve: any) => {
type TCallBack = (progress: number, xhr: XMLHttpRequest) => void
export default (src: string, cb: TCallBack) => {
return new Promise<void>((resolve) => {
// const image = new Image()
// // 解决跨域 Canvas 污染问题
// image.setAttribute('crossOrigin', 'anonymous')
@ -32,10 +35,10 @@ export default (src: string, cb: Function) => {
fetchImageDataFromUrl(src, (progress: number, xhr: XMLHttpRequest) => {
cb(progress, xhr)
}).then((res: any) => {
}).then((res) => {
const reader = new FileReader()
reader.onload = function (event) {
const txt: any = event?.target?.result
const txt = event?.target?.result as string
// image.src = txt
const a = document.createElement('a')
const mE = new MouseEvent('click')
@ -55,17 +58,17 @@ export default (src: string, cb: Function) => {
})
}
function fetchImageDataFromUrl(url: string, cb: Function) {
return new Promise((resolve) => {
function fetchImageDataFromUrl(url: string, cb: TCallBack) {
return new Promise<null>((resolve) => {
const xhr = new XMLHttpRequest()
let totalLength: any = ''
let totalLength: string | number = ''
xhr.open('GET', url)
xhr.responseType = 'blob'
xhr.onreadystatechange = function () {
totalLength = Number(xhr.getResponseHeader('content-length')) // 'cache-control'
}
xhr.onprogress = function (event) {
cb((event.loaded / totalLength) * 100, xhr)
cb((event.loaded / Number(totalLength)) * 100, xhr)
}
xhr.onload = function () {
if (xhr.status < 400) resolve(this.response)

View File

@ -6,11 +6,14 @@
* @LastEditTime: 2023-10-13 01:30:33
*/
// import { isSupportFontFamily, blob2Base64 } from './utils'
import { getFonts } from '@/api/material'
import { TGetFontItemData, getFonts } from '@/api/material'
const nowVersion = '2' // 当前字体文件版本更新,将刷新前端缓存
const fontList: any = []
/** 字体item类型 */
export type TFontItemData = { url: string } & Omit<TGetFontItemData, "woff">
const fontList: TFontItemData[] = []
// const download: any = {}
export const useFontStore = {
list: fontList,
@ -18,7 +21,7 @@ export const useFontStore = {
async init() {
this.list = []
localStorage.getItem('FONTS_VERSION') !== nowVersion && localStorage.removeItem('FONTS')
const localFonts: any = localStorage.getItem('FONTS') ? JSON.parse(localStorage.getItem('FONTS') || '') : []
const localFonts: TFontItemData[] = localStorage.getItem('FONTS') ? JSON.parse(localStorage.getItem('FONTS') || '') : []
if (localFonts.length > 0) {
this.list.push(...localFonts)
}
@ -26,7 +29,7 @@ export const useFontStore = {
if (this.list.length === 0) {
const res = await getFonts({ pageSize: 400 })
this.list.unshift(
...res.list.map((x: any) => {
...res.list.map((x) => {
const { id, alias, oid, value, preview, woff, lang } = x
return { id, oid, value, preview, alias, url: woff, lang }
}),

View File

@ -70,27 +70,27 @@ export function generateFontStyle(name: string, url: string): HTMLStyleElement {
}
// 找到使用到的所有字体
export function filterSkyFonts() {
const fonts: string[] = []
// const textClouds = sky.state.clouds.filter(
// (cloud) => cloud.type === CLOUD_TYPE.text,
// );
const textClouds: any = []
// export function filterSkyFonts() {
// const fonts: string[] = []
// // const textClouds = sky.state.clouds.filter(
// // (cloud) => cloud.type === CLOUD_TYPE.text,
// // );
// const textClouds: any = []
;(textClouds as unknown as CloudText[]).forEach((cloud) => {
// 找到文字组件字体
if (cloud.fontFamily && !fonts.includes(cloud.fontFamily)) {
fonts.push(cloud.fontFamily)
}
// 找到文字组件子级字体
cloud.texts.forEach((text) => {
if (text.fontFamily && !fonts.includes(text.fontFamily)) {
fonts.push(text.fontFamily)
}
})
})
return fonts
}
// ;(textClouds as unknown as CloudText[]).forEach((cloud) => {
// // 找到文字组件字体
// if (cloud.fontFamily && !fonts.includes(cloud.fontFamily)) {
// fonts.push(cloud.fontFamily)
// }
// // 找到文字组件子级字体
// cloud.texts.forEach((text) => {
// if (text.fontFamily && !fonts.includes(text.fontFamily)) {
// fonts.push(text.fontFamily)
// }
// })
// })
// return fonts
// }
export function base642Blob(b64Data: string, contentType = '', sliceSize = 512) {
const byteCharacters = atob(b64Data)

View File

@ -2,17 +2,17 @@
* @Author: ShawnPhang
* @Date: 2022-01-31 10:45:53
* @Description: transform字符串
* @LastEditors: ShawnPhang
* @LastEditTime: 2022-02-18 16:54:13
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-02 11:50:00
*/
export function getTransformAttribute(target: any, attr: string = '') {
export function getTransformAttribute(target: HTMLElement, attr: string = '') {
const tf = target.style.transform
const iof = tf.indexOf(attr)
const half = tf.substring(iof + attr.length + 1)
return half.substring(0, half.indexOf(')'))
}
export function setTransformAttribute(target: any, attr: string, value: string | number = 0) {
export function setTransformAttribute(target: HTMLElement, attr: string, value: string | number = 0) {
const tf = target?.style.transform
if (!tf) {
return
@ -24,10 +24,10 @@ export function setTransformAttribute(target: any, attr: string, value: string |
target.style.transform = FRONT + value + END
}
export function getMatrix(params: any) {
export function getMatrix(params: Record<string, any>) {
const result = []
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
if (Object.hasOwn(params, key)) {
result.push(params[key])
}
}

View File

@ -8,17 +8,17 @@
// TODO: Group类型比较特殊所以需要全量循环并判断是否为group
const arr = ['w-text', 'w-image', 'w-svg', 'w-group', 'w-qrcode']
export function getTarget(currentTarget: any) {
let collector: any[] = []
let groupTarger: any = null
let saveTarger: any = null
export function getTarget(currentTarget: HTMLElement) {
let collector: string[] = []
let groupTarger: HTMLElement | null = null
let saveTarger: HTMLElement | null = null
return new Promise((resolve) => {
function findTarget(target: any) {
function findTarget(target: HTMLElement | null) {
if (!target || target.id === 'page-design') {
if (collector.length > 1) {
resolve(groupTarger)
} else {
resolve(saveTarger || currentTarget)
resolve(saveTarger ?? currentTarget)
}
return
}
@ -37,12 +37,12 @@ export function getTarget(currentTarget: any) {
})
}
export function getFinalTarget(currentTarget: any) {
let collector: any[] = []
let groupTarger: any = null
let saveTarger: any = null
export function getFinalTarget(currentTarget: HTMLElement) {
let collector: string[] = []
// let groupTarger: HTMLElement | null = null
// let saveTarger: HTMLElement | null = null
return new Promise((resolve) => {
function findTarget(target: any) {
function findTarget(target: HTMLElement | null) {
if (!target || target.id === 'page-design') {
resolve(target)
return
@ -51,8 +51,8 @@ export function getFinalTarget(currentTarget: any) {
collector = collector.concat(
t.filter((x) => {
arr.includes(x) && (saveTarger = target)
x === 'w-group' && (groupTarger = target)
// arr.includes(x) && (saveTarger = target)
// x === 'w-group' && (groupTarger = target)
return arr.includes(x)
}),
)

View File

@ -8,7 +8,7 @@
<template>
<el-dialog v-model="dialogVisible" title="裁剪图片" width="80%" :before-close="handleClose" @close="cancel">
<div id="wrap" v-loading="loading" style="height: 50vh">
<img v-show="url" ref="imgBox" style="visibility: hidden" :src="url" />
<img v-show="url" ref="imgBox" style="visibility: hidden" alt="imgBox" :src="url" />
</div>
<template #footer>
<span class="dialog-footer">
@ -84,6 +84,7 @@ export default defineComponent({
cancel()
}, 100)
}
const cancel = () => {
store.commit('setShowMoveable', true)
dialogVisible.value = false

View File

@ -0,0 +1,114 @@
<!--
* @Author: ShawnPhang
* @Date: 2024-03-02 13:32:00
* @Description: 裁剪组件
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-02 13:32:00
-->
<template>
<el-dialog v-model="dialogVisible" title="裁剪图片" width="80%" :before-close="handleClose" @close="cancel">
<div id="wrap" v-loading="state.loading" style="height: 50vh">
<img v-show="state.url" ref="imgBox" style="visibility: hidden" alt="imgBox" :src="state.url" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button :loading="state.loading" plain type="primary" @click="ok">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import api from '@/api'
import { ElDialog } from 'element-plus'
import { ref, defineEmits, reactive, nextTick, toRefs } from 'vue'
import { useStore } from 'vuex'
import 'cropperjs/dist/cropper.css'
import Cropper from 'cropperjs'
type TDoneParams = {
newImg: string,
data: Cropper.Data,
width: string,
height: string
}
type TDoneFunc = (event: "done", data: TDoneParams) => void
type TOpenItem = {
rawImg?: string
imgUrl: string
}
const store = useStore()
const state = reactive({
loading: false,
url: '',
})
const dialogVisible = ref(false)
const imgBox = ref<HTMLImageElement>()
let cropData: Cropper.Data | null
let cropper: Cropper
const emit = defineEmits<TDoneFunc>()
const handleClose = (done: () => void) => {
done()
}
const open = async (item: TOpenItem, data: Cropper.Data) => {
state.loading = true
item.rawImg = item.rawImg ? item.rawImg : item.imgUrl
cropData = data
state.url = item.rawImg
store.commit('setShowMoveable', false)
dialogVisible.value = true
await nextTick()
setEdit()
}
const setEdit = () => {
if (!imgBox || !imgBox.value) return
cropper = new Cropper(imgBox.value, {
// aspectRatio: imgBox.value.width / imgBox.value.height,
dragMode: 'move',
viewMode: 1,
cropBoxMovable: false,
// cropBoxResizable: false,
highlight: false,
background: true,
// crop(event) {
// console.log(event);
// },
})
imgBox.value.addEventListener('ready', function() {
state.loading = false
// if (this.cropper === cropper) {
cropData && cropper.setData(cropData)
// }
})
}
const ok = () => {
state.loading = true
setTimeout(async () => {
// const newImg = cropper.getCroppedCanvas({ maxWidth: 4096, minWidth: 4096 }).toDataURL('image/jpeg', 0.8)
// const { width, height } = cropper.getCropBoxData()
// const { preview_url } = await api.material.uploadBase64({ file: newImg })
// context.emit('done', { newImg: preview_url, data: cropper.getData(), width, height })
cancel()
}, 100)
}
const cancel = () => {
store.commit('setShowMoveable', true)
dialogVisible.value = false
state.url = ''
cropData = null
state.loading = false
cropper.destroy()
}
toRefs(state),
</script>

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-27 15:16:07
* @Description: 素材列表主要用于文字组合列表
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-01-11 17:59:57
* @LastEditTime: 2024-02-29 16:54:28
-->
<template>
<div class="wrap">
@ -241,14 +241,14 @@ export default defineComponent({
background: #f8fafc;
}
&__img {
cursor: pointer;
cursor: grab;
width: 142px;
height: 142px;
padding: 4px;
border-radius: 4px;
}
&__img-thumb {
cursor: pointer;
cursor: grab;
width: 90px;
height: 90px;
background: #f8fafc;

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-27 15:16:07
* @Description: 素材列表
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-11-24 11:14:28
* @LastEditTime: 2024-02-29 16:49:59
-->
<template>
<div class="wrap">
@ -217,14 +217,14 @@ export default defineComponent({
background: #f8fafc;
}
&__img {
cursor: pointer;
cursor: grab;
width: 142px;
height: 142px;
padding: 4px;
border-radius: 4px;
}
&__img-thumb {
cursor: pointer;
cursor: grab;
width: 90px;
height: 90px;
background: #f8fafc;

View File

@ -70,7 +70,7 @@ export default {
{
text: '+ 添加文字',
fontSize: 60,
fontWeight: 'bold',
fontWeight: 'normal',
},
// {
// text: '+ ',
@ -97,8 +97,7 @@ export default {
</script>
<style lang="less" scoped>
// Color variables (appears count calculates by raw css)
@color0: #3b74f1; // Appears 2 times
// @color0: #3b74f1;
#text-list-wrap {
display: flex;

View File

@ -3,7 +3,7 @@
* @Date: 2022-02-23 15:48:52
* @Description: 图片列表组件 Bookshelf Layout
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-12-11 11:12:04
* @LastEditTime: 2024-02-29 16:52:37
-->
<template>
<ul ref="listRef" class="img-list-wrap" :style="{ paddingBottom: isShort ? '15px' : '200px' }" @scroll="scrollEvent($event)">
@ -207,7 +207,7 @@ export default defineComponent({
&__img {
// background: #f1f2f4;
display: inline-block;
cursor: pointer;
cursor: grab;
// margin: 0 6px 2px 0;
margin-bottom: 3px;
border-radius: 2px;

View File

@ -57,7 +57,9 @@ export default defineComponent({
onMounted(() => {
if (props.modelValue) {
state.innerColor = props.modelValue + (props.modelValue.length === 7 ? 'ff' : '')
let fixColor = props.modelValue + (props.modelValue.length === 7 ? 'ff' : '')
// @palxp/color-picker16
state.innerColor = fixColor.toLocaleUpperCase()
}
})
const dropColor = async (e: any) => {

View File

@ -49,15 +49,17 @@
</div>
</template>
<script>
<script lang="ts">
//
const NAME = 'w-text'
import { mapGetters, mapActions } from 'vuex'
import { defineComponent, reactive, toRefs, computed, onUpdated, watch, onMounted, ref } from 'vue'
import { useStore } from 'vuex'
import { useRoute } from 'vue-router'
import { fontWithDraw } from '@/utils/widgets/loadFontRule'
import getGradientOrImg from './getGradientOrImg.ts'
export default {
export default defineComponent({
name: NAME,
setting: {
name: '文本',
@ -97,116 +99,122 @@ export default {
},
},
props: ['params', 'parent'],
data() {
return {
setup(props) {
const store = useStore()
const route = useRoute()
const state = reactive({
loading: false,
editable: false,
loadFontDone: false,
}
},
computed: {
...mapGetters(['dActiveElement']),
isDraw() {
return this.$route.name === 'Draw' && fontWithDraw
},
},
watch: {
params: {
async handler(nval) {
this.updateText()
if (this.loading) {
})
const widget = ref(null)
const editWrap = ref(null)
const dActiveElement = computed(() => store.getters.dActiveElement)
const isDraw = computed(() => route.name === 'Draw' && fontWithDraw)
onUpdated(() => {
updateRecord()
})
onMounted(() => {
updateRecord()
props.params.transform && (widget.value.style.transform = props.params.transform)
props.params.rotate && (widget.value.style.transform += `translate(0px, 0px) rotate(${props.params.rotate}) scale(1, 1)`)
// store.commit('updateRect')
})
watch(
() => props.params,
async (nval) => {
updateText()
if (state.loading) {
return
}
let font = nval.fontClass
const isDone = font.value === this.loadFontDone
const isDone = font.value === state.loadFontDone
if (font.url && !isDone) {
if (font.id && this.isDraw) {
if (font.id && isDraw.value) {
// url
// demobug
this.loading = false
state.loading = false
return
}
this.loading = !this.isDraw
state.loading = !isDraw.value
const loadFont = new window.FontFace(font.value, `url(${font.url})`)
await loadFont.load()
document.fonts.add(loadFont)
this.loadFontDone = font.value
this.loading = false
state.loadFontDone = font.value
state.loading = false
} else {
this.loading = false
state.loading = false
}
},
immediate: true,
deep: true,
},
editable(value) {
this.updateWidgetData({
uuid: this.params.uuid,
key: 'editable',
value,
pushHistory: false,
})
},
},
updated() {
this.updateRecord()
},
async mounted() {
this.updateRecord()
// await this.$nextTick()
this.params.transform && (this.$refs.widget.style.transform = this.params.transform)
this.params.rotate && (this.$refs.widget.style.transform += `translate(0px, 0px) rotate(${this.params.rotate}) scale(1, 1)`)
// this.$store.commit('updateRect')
},
methods: {
...mapActions(['updateWidgetData', 'pushHistory']),
getGradientOrImg,
updateRecord() {
if (this.dActiveElement.uuid === this.params.uuid) {
let record = this.dActiveElement.record
record.width = this.$refs.widget.offsetWidth
record.height = this.$refs.widget.offsetHeight
record.minWidth = this.params.fontSize
record.minHeight = this.params.fontSize * this.params.lineHeight
this.writingText()
{ immediate: true, deep: true },
)
watch(
() => state.editable,
(value) => {
store.dispatch('updateWidgetData', {
uuid: props.params.uuid,
key: 'editable',
value,
pushHistory: false,
})
},
)
function updateRecord() {
if (dActiveElement.value.uuid === props.params.uuid) {
let record = dActiveElement.value.record
record.width = widget.value.offsetWidth
record.height = widget.value.offsetHeight
record.minWidth = props.params.fontSize
record.minHeight = props.params.fontSize * props.params.lineHeight
writingText()
}
},
updateText(e) {
const value = e ? e.target.innerHTML : this.params.text.replace(/\n/g, '<br/>')
// const value = (e ? e.target.innerText : this.params.text).replace(/<br\/>/g, '\r\n').replace(/&nbsp;/g, ' ')
if (value !== this.params.text) {
this.updateWidgetData({
uuid: this.params.uuid,
}
function updateText(e) {
const value = e ? e.target.innerHTML : props.params.text.replace(/\n/g, '<br/>')
// const value = (e ? e.target.innerText : props.params.text).replace(/<br\/>/g, '\r\n').replace(/&nbsp;/g, ' ')
if (value !== props.params.text) {
store.dispatch('updateWidgetData', {
uuid: props.params.uuid,
key: 'text',
value,
pushHistory: false,
})
}
},
writingText(e) {
// this.updateText(e)
}
function writingText(e) {
// updateText(e)
// TODO:
const el = this.$refs.editWrap || this.$refs.widget
this.updateWidgetData({
uuid: this.params.uuid,
const el = editWrap.value || widget.value
store.dispatch('updateWidgetData', {
uuid: props.params.uuid,
key: 'height',
value: el.offsetHeight,
pushHistory: false,
})
this.$store.commit('updateRect')
},
writeDone(e) {
this.editable = false
store.commit('updateRect')
}
function writeDone(e) {
state.editable = false
setTimeout(() => {
this.pushHistory('文字修改')
store.dispatch('pushHistory', '文字修改')
}, 100)
this.updateText(e)
},
dblclickText(e) {
// this.$store.commit('setShowMoveable', false)
this.editable = true
const el = this.$refs.editWrap || this.$refs.widget
updateText(e)
}
function dblclickText(e) {
// store.commit('setShowMoveable', false)
state.editable = true
const el = editWrap.value || widget.value
setTimeout(() => {
el.focus()
if (document.selection) {
@ -220,9 +228,21 @@ export default {
window.getSelection().addRange(range)
}
}, 100)
},
}
return {
...toRefs(state),
getGradientOrImg,
updateRecord,
writingText,
updateText,
writeDone,
dblclickText,
widget,
editWrap,
}
},
}
})
</script>
<style lang="less" scoped>
@ -247,13 +267,4 @@ export default {
width: 100%;
height: 100%;
}
// @font-face {
// font-family: 'FONT-AZPPT';
// font-display: swap;
// src: url('./AZPPT.ttf') format('truetype');
// }
// .FONT-AZPPT {
// font-family: 'FONT-AZPPT';
// }
</style>

View File

@ -34,7 +34,7 @@
<!-- <color-select v-model="innerElement.backgroundColor" label="背景颜色" @finish="(value) => finish('backgroundColor', value)" /> -->
</div>
<div class="line-layout style-item">
<effect-wrap v-model="innerElement.textEffects" :data="innerElement" :degree="innerElement.degree" @select="testEffect" />
<effect-wrap v-model="innerElement.textEffects" :data="innerElement" :degree="innerElement.degree" @select="selectTextEffect" />
</div>
<icon-item-select class="style-item" :data="layerIconList" @finish="layerAction" />
<icon-item-select class="style-item" :data="alignIconList" @finish="alignAction" />
@ -48,13 +48,13 @@
</div>
</template>
<script>
<script lang="ts">
//
const NAME = 'w-text-style'
// import api from '@/api'
// import _config from '@/config'
import { mapGetters, mapActions } from 'vuex'
import { styleIconList1, styleIconList2, alignIconList } from '../../../../assets/data/TextIconsData'
import { defineComponent, reactive, toRefs, computed, watch, nextTick, onMounted } from 'vue'
import { useStore } from 'vuex'
import { useRoute } from 'vue-router'
import { styleIconList1, styleIconList2, alignIconList } from '@/assets/data/TextIconsData'
import layerIconList from '@/assets/data/LayerIconList'
import numberInput from '../../settings/numberInput.vue'
import numberSlider from '../../settings/numberSlider.vue'
@ -66,193 +66,191 @@ import effectWrap from '../../settings/EffectSelect/TextWrap.vue'
import { useFontStore } from '@/common/methods/fonts'
import usePageFontsFilter from './pageFontsFilter.ts'
export default {
export default defineComponent({
name: NAME,
components: { numberInput, colorSelect, iconItemSelect, textInputArea, valueSelect, effectWrap, numberSlider },
data() {
return {
activeNames: [],
innerElement: {},
tag: false,
ingoreKeys: ['left', 'top', 'name', 'width', 'height', 'text', 'color', 'backgroundColor'],
fontSizeList: [12, 14, 24, 26, 28, 30, 36, 48, 60, 72, 96, 108, 120, 140, 180, 200, 250, 300, 400, 500],
fontClassList: [], //
lineHeightList: [1, 1.5, 2],
letterSpacingList: [0, 10, 25, 50, 75, 100, 200],
layerIconList,
styleIconList1,
styleIconList2,
alignIconList,
components: { numberInput, colorSelect, iconItemSelect, textInputArea, valueSelect, effectWrap, numberSlider },
setup() {
const store = useStore()
const route = useRoute()
const state = reactive({
activeNames: [],
innerElement: {},
tag: false,
ingoreKeys: ['left', 'top', 'name', 'width', 'height', 'text', 'color', 'backgroundColor'],
fontSizeList: [12, 14, 24, 26, 28, 30, 36, 48, 60, 72, 96, 108, 120, 140, 180, 200, 250, 300, 400, 500],
fontClassList: [], //
lineHeightList: [1, 1.5, 2],
letterSpacingList: [0, 10, 25, 50, 75, 100, 200],
layerIconList,
styleIconList1,
styleIconList2,
alignIconList,
})
const dActiveElement = computed(() => store.getters.dActiveElement)
const dMoving = computed(() => store.getters.dMoving)
// const isDraw = computed(() => route.name === 'Draw')
watch(() => dActiveElement.value, () => {
change()
}, { deep: true })
watch(() => state.innerElement, () => {
changeValue()
}, { deep: true })
let timer: any = null
onMounted(() => {
change()
setTimeout(() => {
loadFonts()
}, 100)
})
function change() {
if (timer) {
return
}
timer = true
setTimeout(() => {
timer = null
}, 300)
state.tag = true
state.innerElement = JSON.parse(JSON.stringify(dActiveElement.value))
changeStyleIconList()
}
function changeValue() {
if (state.tag) {
state.tag = false
return
}
if (dMoving.value) {
return
}
// TODO
for (let key in state.innerElement) {
if (state.ingoreKeys.indexOf(key) !== -1) {
dActiveElement.value[key] = state.innerElement[key]
} else if (key !== 'setting' && key !== 'record' && state.innerElement[key] !== dActiveElement.value[key]) {
// const pushHistory = !['textEffects', 'transformData', 'fontClass'].includes(key)
store.dispatch('updateWidgetData', {
uuid: dActiveElement.value.uuid,
key,
value: state.innerElement[key],
pushHistory: false,
})
}
}
}
function selectTextEffect({ key, value, style }: any) {
const uuid = dActiveElement.value.uuid
store.commit('setWidgetStyle', { uuid, key, value })
if (style) {
finish('color', style.color || '')
}
}
function loadFonts() {
const localFonts = useFontStore.list
const fontLists = { 当前页面: [], 中文: [], 英文: [] }
for (const font of localFonts) {
const { id, oid, value, url, alias, preview, lang } = font
const item = { id, oid, value, url, alias, preview }
lang === 'zh' ? fontLists['中文'].unshift(item) : fontLists['英文'].unshift(item)
}
fontLists['当前页面'] = usePageFontsFilter()
state.fontClassList = fontLists
}
function finish(key, value) {
store.dispatch('updateWidgetData', {
uuid: dActiveElement.value.uuid,
key,
value,
pushHistory: false,
})
setTimeout(() => {
key === 'fontClass' && (state.fontClassList['当前页面'] = usePageFontsFilter())
}, 300)
}
function layerAction(item) {
store.dispatch('updateLayerIndex', {
uuid: dActiveElement.value.uuid,
value: item.value,
})
}
async function textStyleAction(item) {
let value = item.key === 'textAlign' ? item.value : item.value[item.select ? 1 : 0]
state.innerElement[item.key] = value
// TODO:
item.key === 'writingMode' && relationChange()
await nextTick()
store.commit('updateRect')
}
async function alignAction(item) {
store.dispatch('updateAlign', {
align: item.value,
uuid: dActiveElement.value.uuid,
})
await nextTick()
store.commit('updateRect')
}
function changeStyleIconList() {
for (let i = 0; i < state.styleIconList1.length; ++i) {
let key = state.styleIconList1[i].key
state.styleIconList1[i].select = false
const [unchecked, checked] = state.styleIconList1[i].value
switch (key) {
case 'fontWeight':
case 'textDecoration':
case 'fontStyle':
if (state.innerElement[key] !== unchecked && state.innerElement[key] == checked) {
state.styleIconList1[i].select = !state.styleIconList1[i].select
}
break
case 'writingMode':
if (state.innerElement[key] !== unchecked) {
state.styleIconList1[i].select = true
}
break
}
}
for (let i = 0; i < state.styleIconList2.length; i++) {
let key = state.styleIconList2[i].key
state.styleIconList2[i].select = false
if (key === 'textAlign' && state.innerElement[key] === state.styleIconList2[i].value) {
state.styleIconList2[i].select = true
continue
}
}
}
function relationChange() {
setTimeout(() => {
if (dActiveElement.value.writingMode) {
const w_record = dActiveElement.value.width
state.innerElement.width = dActiveElement.value.height
state.innerElement.height = w_record
}
}, 10)
}
return {
...toRefs(state),
selectTextEffect,
textStyleAction,
finish,
layerAction,
alignAction
}
}
},
computed: {
...mapGetters(['dActiveElement', 'dMoving']),
isDraw() {
return this.$route.name === 'Draw'
},
},
watch: {
dActiveElement: {
handler(newValue, oldValue) {
this.change()
},
deep: true,
},
innerElement: {
handler(newValue, oldValue) {
this.changeValue()
},
deep: true,
},
},
created() {
this.timer = null
this.change()
setTimeout(() => {
this.loadFonts()
}, 100)
},
methods: {
...mapActions(['updateWidgetData', 'updateAlign', 'updateLayerIndex', 'pushHistory']),
testEffect({ key, value, style }) {
console.log('选择回调')
const uuid = this.dActiveElement.uuid
this.$store.commit('setWidgetStyle', { uuid, key, value })
if (style) {
this.finish('color', style.color || '')
}
},
loadFonts() {
// if (!this.isDraw) {
// useFontStore().init()
const localFonts = useFontStore.list
const fontLists = { 当前页面: [], 中文: [], 英文: [] }
for (const font of localFonts) {
const { id, oid, value, url, alias, preview, lang } = font
const item = { id, oid, value, url, alias, preview }
lang === 'zh' ? fontLists['中文'].unshift(item) : fontLists['英文'].unshift(item)
}
fontLists['当前页面'] = usePageFontsFilter()
this.fontClassList = fontLists
// }
// const isDev = process.env.NODE_ENV === 'development'
// if (!isDev) {
// const { list } = await api.material.getFonts() // { name: 'SourceHanSansCN-Normal' }
// this.fontClassList = this.fontClassList.concat(list)
// }
},
change() {
if (this.timer) {
return
}
this.timer = true
setTimeout(() => {
this.timer = null
}, 300)
this.tag = true
this.innerElement = JSON.parse(JSON.stringify(this.dActiveElement))
this.changeStyleIconList()
},
changeValue() {
if (this.tag) {
this.tag = false
return
}
if (this.dMoving) {
return
}
// TODO
for (let key in this.innerElement) {
if (this.ingoreKeys.indexOf(key) !== -1) {
this.dActiveElement[key] = this.innerElement[key]
} else if (key !== 'setting' && key !== 'record' && this.innerElement[key] !== this.dActiveElement[key]) {
// console.log('???', key)
// const pushHistory = !['textEffects', 'transformData', 'fontClass'].includes(key)
this.updateWidgetData({
uuid: this.dActiveElement.uuid,
key,
value: this.innerElement[key],
pushHistory: false,
})
}
}
},
finish(key, value) {
this.updateWidgetData({
uuid: this.dActiveElement.uuid,
key: key,
value: value,
pushHistory: false,
})
setTimeout(() => {
key === 'fontClass' && (this.fontClassList['当前页面'] = usePageFontsFilter())
}, 300)
},
layerAction(item) {
this.updateLayerIndex({
uuid: this.dActiveElement.uuid,
value: item.value,
})
},
async textStyleAction(item) {
let value = item.key === 'textAlign' ? item.value : item.value[item.select ? 1 : 0]
this.innerElement[item.key] = value
// TODO:
item.key === 'writingMode' && this.relationChange()
await this.$nextTick()
this.$store.commit('updateRect')
},
async alignAction(item) {
this.updateAlign({
align: item.value,
uuid: this.dActiveElement.uuid,
})
await this.$nextTick()
this.$store.commit('updateRect')
},
changeStyleIconList() {
for (let i = 0; i < this.styleIconList1.length; ++i) {
let key = this.styleIconList1[i].key
this.styleIconList1[i].select = false
switch (key) {
case 'fontWeight':
case 'fontStyle':
if (this.innerElement[key] !== 'normal') {
this.styleIconList1[i].select = true
}
break
case 'textDecoration':
if (this.innerElement[key] !== this.styleIconList1[i].value[0] && this.innerElement[key] == this.styleIconList1[i].value[1]) {
this.styleIconList1[i].select = !this.styleIconList1[i].select
}
break
case 'writingMode':
if (this.innerElement[key] !== this.styleIconList1[i].value[0]) {
this.styleIconList1[i].select = true
}
break
}
}
for (let i = 0; i < this.styleIconList2.length; i++) {
let key = this.styleIconList2[i].key
this.styleIconList2[i].select = false
if (key === 'textAlign' && this.innerElement[key] === this.styleIconList2[i].value) {
this.styleIconList2[i].select = true
continue
}
}
},
relationChange() {
setTimeout(() => {
if (this.dActiveElement.writingMode) {
const w_record = this.dActiveElement.width
this.innerElement.width = this.dActiveElement.height
this.innerElement.height = w_record
}
}, 10)
},
},
}
})
</script>
<style lang="less" scoped>

41
src/types/global.d.ts vendored
View File

@ -7,13 +7,48 @@ type TCommResResult<T> = {
result: T
}
type TCommonItemData = {
type: string
fontFamily?: string
color?: string
fontSize: number
width?: number
width: number
height: number
left: number
top: number
fontWeight: number
value: TItem2DataParam
}
/** 分页查询公共返回 */
type TPageRequestResult<T> = {
list: T
total: number
}
interface HTMLElementEventMap {
"mousewheel": MouseEvent
}
interface IQiniuSubscribeCb {
(result: { total: { percent: number }}): void
}
interface Window {
qiniu: {
upload: (
file: File,
name: string,
token: string,
exObj: Record<string, any>,
exOption: {
useCdnDomain: boolean
}) => {
subscribe: (cb: {
next: IQiniuSubscribeCb
error: (err: string) => void
complete: IQiniuSubscribeCb
}) => void
}
}
}