mirror of
https://github.com/palxiao/poster-design.git
synced 2025-07-15 16:02:19 +08:00
Merge pull request #79 from palxiao/feature-vue3
Merge part vue3 & TS modified
This commit is contained in:
commit
d65519f181
@ -13,6 +13,7 @@ module.exports = {
|
||||
// 自定义你的规则
|
||||
'vue/component-tags-order': ['off'],
|
||||
'vue/no-multiple-template-root': ['off'],
|
||||
'max-params': ['off'],
|
||||
// 'no-undef': 'off', // 禁止使用未定义的变量,会把TS声明视为变量,暂时关闭
|
||||
},
|
||||
parserOptions: {
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,6 +15,7 @@ screenshot/_apidoc/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
yarn.lock*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -5,7 +5,7 @@
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"eslint.validate": ["javascript", "javascriptreact", "vue", "typescript", "typescriptreact"],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"css.validate": false,
|
||||
"less.validate": false,
|
||||
|
34376
package-lock.json
generated
34376
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@ -17,6 +17,7 @@
|
||||
"@palxp/color-picker": "^1.5.5",
|
||||
"@palxp/image-extraction": "^1.2.4",
|
||||
"@scena/guides": "^0.18.1",
|
||||
"@types/cropperjs": "^1.3.0",
|
||||
"@webtoon/psd": "^0.4.0",
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.6.5",
|
||||
@ -31,39 +32,30 @@
|
||||
"qr-code-styling": "^1.6.0-rc.1",
|
||||
"selecto": "^1.13.0",
|
||||
"throttle-debounce": "^3.0.1",
|
||||
"vue": "^3.0.0",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vue": "3.4.19",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.3.1",
|
||||
"@types/fontfaceobserver": "^2.1.3",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/throttle-debounce": "^2.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.3",
|
||||
"@typescript-eslint/parser": "^4.28.3",
|
||||
"@vitejs/plugin-vue": "^1.2.4",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.1.4",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"autoprefixer": "^10.3.1",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"esbuild-loader": "^2.13.1",
|
||||
"eslint": "^7.29.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-alloy": "~4.1.0",
|
||||
"eslint-plugin-vue": "^7.12.1",
|
||||
"less": "^4.1.1",
|
||||
"typescript": "~4.1.5",
|
||||
"terser": "^5.28.1",
|
||||
"typescript": "^5.2.2",
|
||||
"unplugin-element-plus": "^0.7.1",
|
||||
"vite": "^2.4.1",
|
||||
"vite-plugin-compression": "^0.3.0",
|
||||
"vue-cli-plugin-norm": "~1.2.2",
|
||||
"vue-eslint-parser": "^7.6.0",
|
||||
"vue-tsc": "^0.2.0"
|
||||
"vite": "^5.1.4",
|
||||
"vue-tsc": "^1.8.27"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
@ -2,23 +2,32 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-27 14:42:15
|
||||
* @Description: AI相关接口
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-13 00:07:19
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-03 19:00:00
|
||||
*/
|
||||
import fetch from '@/utils/axios'
|
||||
|
||||
export type TCommonUploadCb = (up: number, dp: number) => void
|
||||
|
||||
type TUploadProgressCbData = {
|
||||
loaded: number
|
||||
total: number
|
||||
}
|
||||
|
||||
export type TUploadErrorResult = {type: "application/json"}
|
||||
|
||||
// 上传接口
|
||||
export const upload = (file: File, cb: Function) => {
|
||||
export const upload = (file: File, cb: TCommonUploadCb) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const extra = {
|
||||
responseType: 'blob',
|
||||
onUploadProgress: (progress: any) => {
|
||||
onUploadProgress: (progress: TUploadProgressCbData) => {
|
||||
cb(Math.floor((progress.loaded / progress.total) * 100), 0)
|
||||
},
|
||||
onDownloadProgress: (progress: any) => {
|
||||
onDownloadProgress: (progress: TUploadProgressCbData) => {
|
||||
cb(100, Math.floor((progress.loaded / progress.total) * 100))
|
||||
},
|
||||
}
|
||||
return fetch('https://res.palxp.cn/ai/upload', formData, 'post', {}, extra)
|
||||
return fetch<MediaSource | TUploadErrorResult>('https://res.palxp.cn/ai/upload', formData, 'post', {}, extra)
|
||||
}
|
||||
|
@ -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')
|
||||
|
||||
|
@ -11,10 +11,57 @@ import _config from '@/config'
|
||||
// const screenshot_url = window.location.protocol + '//' + window.location.host + '/draw'
|
||||
export const download = (params: Type.Object = {}) => `${_config.SCREEN_URL}/api/screenshots?id=${params.id}&width=${params.width}&height=${params.height}`
|
||||
|
||||
type IGetTempListParam = {
|
||||
search: string
|
||||
page: number
|
||||
pageSize: number
|
||||
cate: number | string
|
||||
}
|
||||
export type IGetTempListData = {
|
||||
cover: string
|
||||
height: number
|
||||
id: number
|
||||
state: number
|
||||
title: string
|
||||
width: number
|
||||
isDelect: boolean
|
||||
fail: boolean
|
||||
top: number
|
||||
left: number
|
||||
data?: string
|
||||
listWidth?: number
|
||||
gap?: number
|
||||
thumb?: string
|
||||
url: string
|
||||
model?: string
|
||||
color?: string
|
||||
}
|
||||
type IGetTempListResult = TPageRequestResult<IGetTempListData[]>
|
||||
|
||||
// 获取模板列表
|
||||
export const getTempList = (params: Type.Object = {}) => fetch('design/list', params, 'get')
|
||||
export const getTempDetail = (params: Type.Object = {}) => fetch('design/temp', params, 'get')
|
||||
export const getCategories = (params: Type.Object = {}) => fetch('design/cate', params, 'get')
|
||||
export const getTempList = (params: IGetTempListParam) => fetch<IGetTempListResult>('design/list', params, 'get')
|
||||
|
||||
type TGetTempDetail = {
|
||||
id: number
|
||||
type?: number
|
||||
}
|
||||
|
||||
export const getTempDetail = (params: TGetTempDetail) => fetch<{data: string}>('design/temp', params, 'get')
|
||||
|
||||
type TGetCategoriesParams = {
|
||||
type?: number
|
||||
}
|
||||
export type TGetCategoriesData = {
|
||||
id: number
|
||||
name: string
|
||||
pid: number
|
||||
type: number
|
||||
}
|
||||
type TgetCategoriesResult = TCommResResult<TGetCategoriesData>
|
||||
|
||||
export const getCategories = (params: TGetCategoriesParams) => fetch<TgetCategoriesResult[]>('design/cate', params, 'get')
|
||||
|
||||
|
||||
// 保存模板
|
||||
export const saveTemp = (params: Type.Object = {}) => fetch('design/edit', params, 'post')
|
||||
// export const delTemp = (params: Type.Object = {}) => fetch('/api/template/temp_del', params)
|
||||
@ -33,5 +80,12 @@ export const saveMyTemp = (params: Type.Object = {}) => fetch('design/user/temp'
|
||||
// 获取作品
|
||||
export const getWorks = (params: Type.Object = {}) => fetch('design/poster', params, 'get')
|
||||
|
||||
type TGetMyDesignParams = {
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
type TGetMyDesignResult = TPageRequestResult<IGetTempListData[]>
|
||||
|
||||
// 作品列表
|
||||
export const getMyDesign = (params: Type.Object = {}) => fetch('design/my', params, 'get')
|
||||
export const getMyDesign = (params: TGetMyDesignParams) => fetch<TGetMyDesignResult>('design/my', params, 'get')
|
||||
|
@ -6,22 +6,82 @@
|
||||
* @LastEditTime: 2023-12-11 11:40:47
|
||||
*/
|
||||
import fetch from '@/utils/axios'
|
||||
import { IGetTempListData } from './home'
|
||||
|
||||
// 获取素材分类:
|
||||
export const getKinds = (params: Type.Object = {}) => fetch('design/cate', params)
|
||||
|
||||
type TGetListParam = {
|
||||
first_id?: number
|
||||
second_id?: string
|
||||
cate?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export type TGetListData = {
|
||||
category: number
|
||||
created_time: string
|
||||
height: number
|
||||
id: number
|
||||
model: string
|
||||
original: string
|
||||
state: number
|
||||
thumb: string
|
||||
title: string
|
||||
type: string
|
||||
updated_time: string
|
||||
url: string
|
||||
width: number
|
||||
thumbUrl: string
|
||||
imgUrl: string
|
||||
}
|
||||
|
||||
export type TGetListResult = TPageRequestResult<TGetListData[]>
|
||||
|
||||
|
||||
|
||||
// 获取素材列表:
|
||||
export const getList = (params: Type.Object = {}) => fetch('design/material', params)
|
||||
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)
|
||||
|
||||
type TGetImageListParams = {
|
||||
page?: number
|
||||
cate?: number
|
||||
}
|
||||
|
||||
export type TGetImageListResult = {
|
||||
created_time: string
|
||||
height: number
|
||||
width: number
|
||||
url: string
|
||||
user_id: number
|
||||
id: string
|
||||
thumb: string
|
||||
} & IGetTempListData
|
||||
|
||||
// 图库列表
|
||||
export const getImagesList = (params: Type.Object = {}) => fetch('design/imgs', params, 'get')
|
||||
export const getImagesList = (params: TGetImageListParams) => fetch<TPageRequestResult<TGetImageListResult[]>>('design/imgs', params, 'get')
|
||||
|
||||
// 我的上传列表
|
||||
export const getMyPhoto = (params: Type.Object = {}) => fetch('design/user/image', params)
|
||||
export const getMyPhoto = (params: TGetImageListParams) => fetch<TPageRequestResult<TGetImageListResult[]>>('design/user/image', params)
|
||||
export const deleteMyPhoto = (params: Type.Object = {}) => fetch('design/user/image/del', params, 'post')
|
||||
export const deleteMyWorks = (params: Type.Object = {}) => fetch('design/poster/del', params, 'post')
|
||||
|
||||
|
@ -2,9 +2,17 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-12 11:08:57
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-19 17:35:44
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-03-01 20:55:51
|
||||
*/
|
||||
|
||||
export type AlignListData = {
|
||||
key: string
|
||||
icon: string
|
||||
tip: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
key: 'align',
|
||||
@ -42,4 +50,4 @@ export default [
|
||||
tip: '下对齐',
|
||||
value: 'bottom',
|
||||
},
|
||||
]
|
||||
] as AlignListData[]
|
||||
|
@ -2,9 +2,17 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-04-15 10:51:50
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-04-15 10:51:51
|
||||
* @LastEditors: ShawnPhang, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-03-01 20:55:51
|
||||
*/
|
||||
|
||||
export type LayerIconList = {
|
||||
key: string
|
||||
icon: string
|
||||
tip: string
|
||||
value: number
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
key: 'zIndex',
|
||||
@ -18,4 +26,4 @@ export default [
|
||||
tip: '下一层',
|
||||
value: -1,
|
||||
},
|
||||
]
|
||||
] as LayerIconList[]
|
||||
|
@ -2,9 +2,21 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-03-16 11:38:48
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-03-23 16:00:11
|
||||
* @LastEditors: ShawnPhang, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-03-01 20:55:51
|
||||
*/
|
||||
|
||||
export type QrCodeLocalizationData = {
|
||||
dotColorTypes: {
|
||||
key: string
|
||||
value: string
|
||||
}[]
|
||||
dotTypes: {
|
||||
key: string
|
||||
value: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export default {
|
||||
dotColorTypes: [
|
||||
{
|
||||
@ -42,4 +54,4 @@ export default {
|
||||
value: '特殊风格',
|
||||
},
|
||||
],
|
||||
}
|
||||
} as QrCodeLocalizationData
|
||||
|
@ -2,10 +2,20 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-02 18:27:27
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-02-25 10:30:38
|
||||
* @LastEditors: ShawnPhang, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-03-01 20:55:51
|
||||
*/
|
||||
|
||||
import { AlignListData } from "./AlignListData"
|
||||
|
||||
export type TStyleIconData = {
|
||||
key: string
|
||||
icon: string
|
||||
tip: string
|
||||
value: string[]
|
||||
select: boolean
|
||||
}
|
||||
|
||||
export const styleIconList1 = [
|
||||
{
|
||||
key: 'fontWeight',
|
||||
@ -42,7 +52,16 @@ export const styleIconList1 = [
|
||||
value: ['horizontal-tb', 'vertical-rl'], // tb-rl
|
||||
select: false,
|
||||
},
|
||||
]
|
||||
] as TStyleIconData[]
|
||||
|
||||
export type TStyleIconData2 = {
|
||||
key: string
|
||||
icon: string
|
||||
tip: string
|
||||
value: string
|
||||
select: boolean
|
||||
}
|
||||
|
||||
export const styleIconList2 = [
|
||||
{
|
||||
key: 'textAlign',
|
||||
@ -72,7 +91,7 @@ export const styleIconList2 = [
|
||||
value: 'justify',
|
||||
select: false,
|
||||
},
|
||||
]
|
||||
] as TStyleIconData2[]
|
||||
|
||||
export const alignIconList = [
|
||||
{
|
||||
@ -111,4 +130,4 @@ export const alignIconList = [
|
||||
tip: '下对齐',
|
||||
value: 'bottom',
|
||||
},
|
||||
]
|
||||
] as AlignListData[]
|
||||
|
@ -2,9 +2,20 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-07-17 11:20:22
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-24 17:07:44
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-03-01 20:55:51
|
||||
*/
|
||||
|
||||
import { StyleValue } from "vue"
|
||||
|
||||
export type TWidgetClassifyData = {
|
||||
name: string
|
||||
icon: string
|
||||
show: boolean
|
||||
component: string
|
||||
style?: StyleValue
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
name: '模板',
|
||||
@ -49,4 +60,4 @@ export default [
|
||||
show: false,
|
||||
component: 'user-wrap',
|
||||
},
|
||||
]
|
||||
] as TWidgetClassifyData[]
|
||||
|
@ -5,17 +5,34 @@
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-22 18:11:15
|
||||
*/
|
||||
|
||||
import store from '@/store'
|
||||
export default class dragHelper {
|
||||
private cloneEl: any = null
|
||||
|
||||
type TInitial = {
|
||||
offsetX: number
|
||||
offsetY: number
|
||||
pageX: number
|
||||
pageY: number
|
||||
width: number
|
||||
height: number
|
||||
finallySize: number
|
||||
flag: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
type TQueueFunction = () => void
|
||||
|
||||
export default class DragHelper {
|
||||
private cloneEl?: HTMLElement | null
|
||||
private dragging: boolean = false
|
||||
private initial: any = {}
|
||||
private queue: any = []
|
||||
private initial: Partial<TInitial> = {}
|
||||
private queue: TQueueFunction[] = []
|
||||
|
||||
constructor() {
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
if (this.dragging && this.cloneEl) {
|
||||
const { offsetX, offsetY, width, height } = this.initial
|
||||
const { width, height } = this.initial as TInitial
|
||||
// this.moveFlutter(e.pageX - offsetX, e.pageY - offsetY, this.distance(e))
|
||||
this.moveFlutter(e.pageX - width / 2, e.pageY - height / 2, this.distance(e))
|
||||
} else {
|
||||
@ -23,10 +40,18 @@ export default class dragHelper {
|
||||
}
|
||||
})
|
||||
// 鼠标抬起
|
||||
window.addEventListener('mouseup', (e: any) => {
|
||||
;(window as any).document.getElementById('app').classList.remove('drag_active')
|
||||
const cl = e.target.classList
|
||||
if (e.target?.id === 'page-design-canvas' || cl.contains('target') || cl.contains('drop__mask') || cl.contains('edit-text')) {
|
||||
window.addEventListener('mouseup', (e) => {
|
||||
const el = window.document.getElementById('app')
|
||||
if (!el || !e.target) return
|
||||
el.classList.remove('drag_active')
|
||||
const target = e.target as HTMLElement
|
||||
const cl = target.classList
|
||||
if (
|
||||
target.id === 'page-design-canvas' ||
|
||||
cl.contains('target') ||
|
||||
cl.contains('drop__mask') ||
|
||||
cl.contains('edit-text')
|
||||
) {
|
||||
setTimeout(() => {
|
||||
this.finish(true)
|
||||
}, 10)
|
||||
@ -44,29 +69,39 @@ export default class dragHelper {
|
||||
/**
|
||||
* 拖动开始 mousedown
|
||||
*/
|
||||
public start(e: any, finallySize: any) {
|
||||
public start(e: MouseEvent, finallySize: number) {
|
||||
if (!this.cloneEl) {
|
||||
store.commit('setDraging', true)
|
||||
;(window as any).document.getElementById('app').classList.add('drag_active') // 整个鼠标全局变成抓取
|
||||
const app = window.document.getElementById('app')
|
||||
if (!app || !e) return
|
||||
app.classList.add('drag_active') // 整个鼠标全局变成抓取
|
||||
const target = e.target as HTMLElement
|
||||
// 选中了元素
|
||||
this.cloneEl = e.target.cloneNode(true)
|
||||
this.cloneEl = (target.cloneNode(true) as HTMLElement)
|
||||
this.cloneEl.classList.add('flutter')
|
||||
// 初始化数据
|
||||
this.init(e, e.target, finallySize || e.target.offsetWidth, Math.random())
|
||||
this.init(e, target, finallySize || target.offsetWidth, Math.random())
|
||||
// 加载原图
|
||||
// simulate(cloneEl.src, initial.flag)
|
||||
this.cloneEl.style.width = e.target.offsetWidth
|
||||
this.cloneEl.style.width = `${target.offsetWidth}`
|
||||
// e.target.parentElement.parentElement.appendChild(this.cloneEl)
|
||||
;(window as any).document.getElementById('widget-panel').appendChild(this.cloneEl)
|
||||
const widgetPanel = window.document.getElementById('widget-panel')
|
||||
if (!widgetPanel) return
|
||||
widgetPanel.appendChild(this.cloneEl)
|
||||
this.dragging = true
|
||||
e.target.classList.add('hide') // 放在最后
|
||||
target.classList.add('hide') // 放在最后
|
||||
this.queue.push(() => {
|
||||
e.target.classList.remove('hide')
|
||||
target.classList.remove('hide')
|
||||
})
|
||||
}
|
||||
}
|
||||
// 开始拖动初始化
|
||||
private init({ offsetX, offsetY, pageX, pageY, x, y }: any, { offsetWidth: width, offsetHeight: height }: any, finallySize: number, flag: any) {
|
||||
private init(
|
||||
{ offsetX, offsetY, pageX, pageY, x, y }: MouseEvent,
|
||||
{ offsetWidth: width, offsetHeight: height }: HTMLElement,
|
||||
finallySize: number,
|
||||
flag: number
|
||||
) {
|
||||
this.initial = { offsetX, offsetY, pageX, pageY, width, height, finallySize, flag, x, y }
|
||||
// store.commit('setDragInitData', { offsetX: 0, offsetY: 0 })
|
||||
this.moveFlutter(pageX - offsetX, pageY - offsetY, 0, 0.3)
|
||||
@ -76,17 +111,23 @@ export default class dragHelper {
|
||||
}
|
||||
// 改变漂浮元素(合并多个操作)
|
||||
private moveFlutter(x: number, y: number, d = 0, lazy = 0) {
|
||||
const { width, height, finallySize } = this.initial
|
||||
let scale: any = null
|
||||
const { width, height, finallySize } = this.initial as TInitial
|
||||
let scale: string | null = null
|
||||
if (d) {
|
||||
if (width > finallySize) {
|
||||
scale = d ? (width - d >= finallySize ? `transform: scale(${(width - d) / width});` : null) : null
|
||||
} else scale = d ? (width + d <= finallySize ? `transform: scale(${(width + d) / width})` : null) : null
|
||||
scale = width - d >= finallySize ? `transform: scale(${(width - d) / width});` : null
|
||||
} else {
|
||||
scale = width + d <= finallySize ? `transform: scale(${(width + d) / width})` : null
|
||||
}
|
||||
}
|
||||
|
||||
const options = [`left: ${x}px`, `top: ${y}px`, `width: ${width}px`, `height: ${height}px`]
|
||||
scale && options.push(scale)
|
||||
options.push(`transition: all ${lazy}s`)
|
||||
this.changeStyle(options)
|
||||
}
|
||||
private changeStyle(arr: any) {
|
||||
private changeStyle(arr: string[]) {
|
||||
if (!this.cloneEl) return
|
||||
const original = this.cloneEl.style.cssText.split(';')
|
||||
original.pop()
|
||||
this.cloneEl.style.cssText = original.concat(arr).join(';') + ';'
|
||||
@ -99,16 +140,14 @@ export default class dragHelper {
|
||||
this.dragging = false
|
||||
store.commit('setDraging', false)
|
||||
store.commit('selectItem', {})
|
||||
if (!this.cloneEl) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
const { pageX, offsetX, pageY, offsetY } = this.initial
|
||||
const { pageX, offsetX, pageY, offsetY } = this.initial as TInitial
|
||||
this.changeStyle([`left: ${pageX - offsetX}px`, `top: ${pageY - offsetY}px`, 'transform: scale(1)', 'transition: all 0.3s'])
|
||||
}
|
||||
setTimeout(
|
||||
() => {
|
||||
this.queue.length && this.queue.shift()()
|
||||
this.queue.length && (this.queue.shift() as TQueueFunction)()
|
||||
this.cloneEl && this.cloneEl.remove()
|
||||
this.cloneEl = null
|
||||
},
|
||||
@ -116,8 +155,8 @@ export default class dragHelper {
|
||||
)
|
||||
}
|
||||
// 计算两点之间距离
|
||||
private distance({ pageX, pageY }: any) {
|
||||
const { pageX: x, pageY: y } = this.initial
|
||||
private distance({ pageX, pageY }: { pageX: number, pageY: number }) {
|
||||
const { pageX: x, pageY: y } = this.initial as TInitial
|
||||
return Math.hypot(pageX - x, pageY - y)
|
||||
}
|
||||
}
|
||||
|
13
src/common/hooks/mapGetters.ts
Normal file
13
src/common/hooks/mapGetters.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ComputedRef, computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
|
||||
export function useSetupMapGetters<T extends string>(strList: T[]) {
|
||||
const mapData: Partial<{[x in T]: ComputedRef}> = {}
|
||||
const getters = useStore().getters
|
||||
|
||||
strList.forEach(val => {
|
||||
mapData[val] = computed(() => getters[val])
|
||||
})
|
||||
|
||||
return mapData as {[x in T]: ComputedRef}
|
||||
}
|
@ -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
|
||||
|
@ -2,17 +2,33 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-22 15:06:14
|
||||
* @Description: 设置图片类型元素
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-11 17:36:44
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-03-01 20:55:51
|
||||
*/
|
||||
import store from '@/store'
|
||||
import { getImage } from '../getImgDetail'
|
||||
export default async function setItem2Data(item: any) {
|
||||
|
||||
export type TItem2DataParam = {
|
||||
id?: string | number
|
||||
width: number
|
||||
height: number
|
||||
url: string
|
||||
model?: string
|
||||
canvasWidth?: number
|
||||
}
|
||||
|
||||
export type TItem2DataResult = {
|
||||
width: number
|
||||
height: number
|
||||
canvasWidth: number
|
||||
}
|
||||
|
||||
export default async function setItem2Data(item: TItem2DataParam): Promise<Required<TItem2DataParam>> {
|
||||
const cloneItem = JSON.parse(JSON.stringify(item))
|
||||
const { width: screenWidth, height: screenHeight } = store.getters.dPage
|
||||
let { width: imgWidth, height: imgHeight } = item
|
||||
if (!imgWidth || !imgHeight) {
|
||||
const actual: any = await getImage(item.url)
|
||||
const actual = await getImage(item.url)
|
||||
cloneItem.width = imgWidth = actual.width
|
||||
cloneItem.height = imgHeight = actual.height
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ import setImageData from '@/common/methods/DesignFeatures/setImage'
|
||||
import wText from '@/components/modules/widgets/wText/wText.vue'
|
||||
import wImage from '@/components/modules/widgets/wImage/wImage.vue'
|
||||
import wSvg from '@/components/modules/widgets/wSvg/wSvg.vue'
|
||||
export default async function(type: string, item: any, data: any) {
|
||||
|
||||
export default async function(type: string, item: TCommonItemData, data: Record<string, any>) {
|
||||
let setting = data
|
||||
if (type === 'text') {
|
||||
!item.fontFamily && !item.color ? (setting = JSON.parse(JSON.stringify(wText.setting))) : (setting = item)
|
||||
|
@ -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 | Blob, 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 -> 展示进度
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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: '确定',
|
||||
|
@ -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)
|
||||
|
@ -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 }
|
||||
}),
|
||||
|
@ -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)
|
||||
|
@ -5,7 +5,7 @@
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-09 10:42:54
|
||||
*/
|
||||
export const getImage = (imgItem: string | File) => {
|
||||
export const getImage = (imgItem: string | File): Promise<HTMLImageElement> => {
|
||||
// 创建对象
|
||||
const img = new Image()
|
||||
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
@ -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): Promise<HTMLElement | null> {
|
||||
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)
|
||||
}),
|
||||
)
|
||||
|
@ -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
|
114
src/components/business/cropper/CropImage/index.vue
Normal file
114
src/components/business/cropper/CropImage/index.vue
Normal 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>
|
@ -1,222 +0,0 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-07-11 23:50:22
|
||||
* @Description: 抠图组件
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-09 00:42:48
|
||||
-->
|
||||
<template>
|
||||
<el-dialog v-model="show" title="AI 智能抠图" align-center width="650" @close="handleClose">
|
||||
<uploader v-if="!rawImage" :hold="true" :drag="true" :multiple="true" class="uploader" @load="selectFile">
|
||||
<div class="uploader__box">
|
||||
<upload-filled style="width: 64px; height: 64px" />
|
||||
<div class="el-upload__text">在此拖入或选择<em>上传图片</em></div>
|
||||
</div>
|
||||
<div class="el-upload__tip">服务器带宽过低,为了更好的体验,请上传 2M 内的图片</div>
|
||||
</uploader>
|
||||
<el-progress v-if="!cutImage && progressText" :percentage="progress">
|
||||
<el-button text>
|
||||
{{ progressText }} <span v-show="progress">{{ progress }}%</span>
|
||||
</el-button>
|
||||
</el-progress>
|
||||
<div class="content">
|
||||
<div v-show="rawImage" v-loading="!cutImage" :style="{ width: offsetWidth ? offsetWidth + 'px' : '100%' }" class="scan-effect transparent-bg">
|
||||
<img ref="raw" :style="{ 'clip-path': 'inset(0 0 0 ' + percent + '%)' }" :src="rawImage" alt="" />
|
||||
<img v-show="cutImage" :src="cutImage" alt="结果图像" @mousemove="mousemove" />
|
||||
<div v-show="cutImage" :style="{ left: percent + '%' }" class="scan-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button v-show="rawImage && toolModel" @click="clear">清空重选</el-button>
|
||||
<el-button v-show="cutImage" type="primary" plain @click="edit">进入编辑模式</el-button>
|
||||
<el-button v-show="cutImage && toolModel" type="primary" plain @click="download"> 下载 </el-button>
|
||||
<el-button v-show="cutImage && !toolModel" v-loading="loading" type="primary" plain @click="cutDone"> {{ loading ? '上传中..' : '完成抠图' }} </el-button>
|
||||
</span>
|
||||
</template>
|
||||
<ImageExtraction ref="matting" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, nextTick } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { ElProgress } from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import uploader from '@/components/common/Uploader/index.vue'
|
||||
import _dl from '@/common/methods/download'
|
||||
import api from '@/api'
|
||||
import Qiniu from '@/common/methods/QiNiu'
|
||||
import _config from '@/config'
|
||||
import { getImage } from '@/common/methods/getImgDetail'
|
||||
import ImageExtraction from './ImageExtraction.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { uploader, UploadFilled, ElProgress, ImageExtraction },
|
||||
emits: ['done'],
|
||||
setup(props, { emit }) {
|
||||
const store = useStore()
|
||||
const state: any = reactive({
|
||||
show: false,
|
||||
rawImage: '',
|
||||
cutImage: '',
|
||||
raw: null,
|
||||
offsetWidth: 0,
|
||||
percent: 0,
|
||||
progress: 0,
|
||||
progressText: '',
|
||||
toolModel: true,
|
||||
loading: false,
|
||||
matting: null,
|
||||
})
|
||||
let fileName: string = 'unknow'
|
||||
let isRuning: boolean = false
|
||||
|
||||
const selectFile = async (file: File) => {
|
||||
if (file.size > 1024 * 1024 * 2) {
|
||||
alert('上传图片超出限制')
|
||||
return false
|
||||
}
|
||||
// 显示选择的图片
|
||||
state.raw.addEventListener('load', () => {
|
||||
state.offsetWidth = state.raw.offsetWidth
|
||||
})
|
||||
state.rawImage = URL.createObjectURL(file)
|
||||
fileName = file.name
|
||||
// 返回抠图结果
|
||||
const result: any = await api.ai.upload(file, (up: number, dp: number) => {
|
||||
if (dp) {
|
||||
state.progressText = dp === 100 ? '' : '导入中..'
|
||||
state.progress = dp
|
||||
} else {
|
||||
state.progressText = up < 100 ? '上传中..' : '正在处理,请稍候..'
|
||||
state.progress = up < 100 ? up : 0
|
||||
}
|
||||
})
|
||||
if (result.type !== 'application/json') {
|
||||
const resultImage = URL.createObjectURL(result)
|
||||
state.rawImage && (state.cutImage = resultImage)
|
||||
requestAnimationFrame(run)
|
||||
} else alert('服务器繁忙,请稍等下重新尝试~')
|
||||
}
|
||||
|
||||
const open = (file: File) => {
|
||||
state.loading = false
|
||||
state.show = true
|
||||
store.commit('setShowMoveable', false)
|
||||
nextTick(() => {
|
||||
if (file) {
|
||||
selectFile(file)
|
||||
state.toolModel = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
store.commit('setShowMoveable', true)
|
||||
}
|
||||
|
||||
const mousemove = (e: MouseEvent) => {
|
||||
!isRuning && (state.percent = (e.offsetX / (e.target as any).width) * 100)
|
||||
}
|
||||
|
||||
const download = () => {
|
||||
_dl.downloadBase64File(state.cutImage, fileName)
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
URL.revokeObjectURL(state.rawImage)
|
||||
state.rawImage = ''
|
||||
// URL.revokeObjectURL(state.cutImage)
|
||||
state.cutImage = ''
|
||||
state.percent = 0
|
||||
state.offsetWidth = 0
|
||||
}
|
||||
|
||||
const run = () => {
|
||||
state.percent += 1
|
||||
isRuning = true
|
||||
state.percent < 100 ? requestAnimationFrame(run) : (isRuning = false)
|
||||
}
|
||||
|
||||
const cutDone = async () => {
|
||||
state.loading = true
|
||||
const response = await fetch(state.cutImage)
|
||||
const buffer = await response.arrayBuffer()
|
||||
const file = new File([buffer], `cut_image_${Math.random()}.png`)
|
||||
// upload
|
||||
const qnOptions = { bucket: 'xp-design', prePath: 'user' }
|
||||
const result = await Qiniu.upload(file, qnOptions)
|
||||
const { width, height } = await getImage(file)
|
||||
const url = _config.IMG_URL + result.key
|
||||
await api.material.addMyPhoto({ width, height, url })
|
||||
emit('done', url)
|
||||
state.show = false
|
||||
handleClose()
|
||||
}
|
||||
|
||||
const edit = () => {
|
||||
state.matting.open(state.rawImage, state.cutImage, (base64: any) => {
|
||||
state.cutImage = base64
|
||||
state.percent = 0
|
||||
requestAnimationFrame(run)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
clear,
|
||||
download,
|
||||
mousemove,
|
||||
selectFile,
|
||||
open,
|
||||
handleClose,
|
||||
...toRefs(state),
|
||||
cutDone,
|
||||
edit,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.uploader {
|
||||
&__box {
|
||||
color: #333333;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.scan-effect {
|
||||
position: relative;
|
||||
height: 50vh;
|
||||
overflow: hidden;
|
||||
img {
|
||||
// width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.scan-line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 1.5px;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
// background-image: linear-gradient(to top, transparent, rgba(255, 255, 255, 0.7), transparent);
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
201
src/components/business/image-cutout/ImageCutout/index.vue
Normal file
201
src/components/business/image-cutout/ImageCutout/index.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2024-03-03 19:00:00
|
||||
* @Description: 裁剪组件
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-03 19:00:00
|
||||
-->
|
||||
<template>
|
||||
<el-dialog v-model="state.show" title="AI 智能抠图" align-center width="650" @close="handleClose">
|
||||
<uploader v-if="!state.rawImage" :hold="true" :drag="true" :multiple="true" class="uploader" @load="handleUploaderLoad">
|
||||
<div class="uploader__box">
|
||||
<upload-filled style="width: 64px; height: 64px" />
|
||||
<div class="el-upload__text">在此拖入或选择<em>上传图片</em></div>
|
||||
</div>
|
||||
<div class="el-upload__tip">服务器带宽过低,为了更好的体验,请上传 2M 内的图片</div>
|
||||
</uploader>
|
||||
<el-progress v-if="!state.cutImage && state.progressText" :percentage="state.progress">
|
||||
<el-button text>
|
||||
{{ state.progressText }} <span v-show="state.progress">{{ state.progress }}%</span>
|
||||
</el-button>
|
||||
</el-progress>
|
||||
<div class="content">
|
||||
<div v-show="state.rawImage" v-loading="!state.cutImage" :style="{ width: state.offsetWidth ? state.offsetWidth + 'px' : '100%' }" class="scan-effect transparent-bg">
|
||||
<img ref="raw" :style="{ 'clip-path': 'inset(0 0 0 ' + state.percent + '%)' }" :src="state.rawImage" alt="" />
|
||||
<img v-show="state.cutImage" :src="state.cutImage" alt="结果图像" @mousemove="mousemove" />
|
||||
<div v-show="state.cutImage" :style="{ left: state.percent + '%' }" class="scan-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button v-show="state.rawImage && state.toolModel" @click="clear">清空重选</el-button>
|
||||
<el-button v-show="state.cutImage" type="primary" plain @click="edit">进入编辑模式</el-button>
|
||||
<el-button v-show="state.cutImage && state.toolModel" type="primary" plain @click="download"> 下载 </el-button>
|
||||
<el-button v-show="state.cutImage && !state.toolModel" v-loading="state.loading" type="primary" plain @click="cutDone"> {{ state.loading ? '上传中..' : '完成抠图' }} </el-button>
|
||||
</span>
|
||||
</template>
|
||||
<ImageExtraction ref="matting" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, nextTick, defineEmits, ref } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { ElProgress } from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import uploader from '@/components/common/Uploader/index.vue'
|
||||
import _dl from '@/common/methods/download'
|
||||
import ImageExtraction from '../ImageExtraction/index.vue'
|
||||
import { selectImageFile, uploadCutPhotoToCloud } from './method'
|
||||
|
||||
export type TImageCutoutState = {
|
||||
show: boolean;
|
||||
rawImage: string;
|
||||
cutImage: string;
|
||||
offsetWidth: number;
|
||||
percent: number;
|
||||
progress: number;
|
||||
progressText: string;
|
||||
toolModel: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const store = useStore()
|
||||
const state = reactive<TImageCutoutState>({
|
||||
show: false,
|
||||
rawImage: '',
|
||||
cutImage: '',
|
||||
offsetWidth: 0,
|
||||
percent: 0,
|
||||
progress: 0,
|
||||
progressText: '',
|
||||
toolModel: true,
|
||||
loading: false,
|
||||
})
|
||||
|
||||
let fileName: string = 'unknow'
|
||||
let isRuning: boolean = false
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: "done", data: string): void
|
||||
}>()
|
||||
|
||||
const raw = ref(null)
|
||||
const matting = ref<typeof ImageExtraction | null>(null)
|
||||
|
||||
const open = (file: File) => {
|
||||
state.loading = false
|
||||
state.show = true
|
||||
store.commit('setShowMoveable', false)
|
||||
nextTick(() => {
|
||||
if (file) {
|
||||
handleUploaderLoad(file)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
|
||||
const handleUploaderLoad = (file: File) => {
|
||||
selectImageFile(state as TImageCutoutState, raw, file, (result, name) => {
|
||||
fileName = name
|
||||
const resultImage = URL.createObjectURL(result)
|
||||
state.rawImage && (state.cutImage = resultImage)
|
||||
requestAnimationFrame(run)
|
||||
|
||||
})
|
||||
state.toolModel = false
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
store.commit('setShowMoveable', true)
|
||||
}
|
||||
|
||||
const mousemove = (e: MouseEvent) => {
|
||||
!isRuning && (state.percent = (e.offsetX / (e.target as any).width) * 100)
|
||||
}
|
||||
|
||||
const download = () => {
|
||||
_dl.downloadBase64File(state.cutImage, fileName)
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
URL.revokeObjectURL(state.rawImage)
|
||||
state.rawImage = ''
|
||||
// URL.revokeObjectURL(state.cutImage)
|
||||
state.cutImage = ''
|
||||
state.percent = 0
|
||||
state.offsetWidth = 0
|
||||
}
|
||||
|
||||
const run = () => {
|
||||
state.percent += 1
|
||||
isRuning = true
|
||||
state.percent < 100 ? requestAnimationFrame(run) : (isRuning = false)
|
||||
}
|
||||
|
||||
const cutDone = async () => {
|
||||
state.loading = true
|
||||
const url = await uploadCutPhotoToCloud(state.cutImage)
|
||||
emits('done', url)
|
||||
state.show = false
|
||||
handleClose()
|
||||
}
|
||||
|
||||
const edit = () => {
|
||||
if (!matting.value) return
|
||||
matting.value.open(state.rawImage, state.cutImage, (base64: string) => {
|
||||
state.cutImage = base64
|
||||
state.percent = 0
|
||||
requestAnimationFrame(run)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.uploader {
|
||||
&__box {
|
||||
color: #333333;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.scan-effect {
|
||||
position: relative;
|
||||
height: 50vh;
|
||||
overflow: hidden;
|
||||
img {
|
||||
// width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.scan-line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 1.5px;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
// background-image: linear-gradient(to top, transparent, rgba(255, 255, 255, 0.7), transparent);
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
68
src/components/business/image-cutout/ImageCutout/method.ts
Normal file
68
src/components/business/image-cutout/ImageCutout/method.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* @Author: Jeremy Yu
|
||||
* @Date: 2024-03-03 19:00:00
|
||||
* @Description: 裁剪组件公共方法
|
||||
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-03 19:00:00
|
||||
*/
|
||||
|
||||
import Qiniu from '@/common/methods/QiNiu'
|
||||
import { TCommonUploadCb, TUploadErrorResult } from "@/api/ai"
|
||||
import { TImageCutoutState } from "./index.vue"
|
||||
import api from "@/api"
|
||||
import { getImage } from '@/common/methods/getImgDetail'
|
||||
import _config from '@/config'
|
||||
import { Ref } from 'vue'
|
||||
|
||||
/** 选择图片 */
|
||||
export const selectImageFile = async (
|
||||
state: TImageCutoutState,
|
||||
raw: Ref<HTMLElement | null>,
|
||||
file: File,
|
||||
successCb?: (result: MediaSource, fileName: string) => void,
|
||||
uploadCb?: TCommonUploadCb,
|
||||
) => {
|
||||
if (file.size > 1024 * 1024 * 2) {
|
||||
alert('上传图片超出限制')
|
||||
return false
|
||||
}
|
||||
if (!raw.value) return
|
||||
// 显示选择的图片
|
||||
raw.value.addEventListener('load', () => {
|
||||
state.offsetWidth = (raw.value as HTMLElement).offsetWidth
|
||||
})
|
||||
state.rawImage = URL.createObjectURL(file)
|
||||
|
||||
// 返回抠图结果
|
||||
const result = await api.ai.upload(file, (up: number, dp: number) => {
|
||||
uploadCb && uploadCb(up, dp)
|
||||
if (dp) {
|
||||
state.progressText = dp === 100 ? '' : '导入中..'
|
||||
state.progress = dp
|
||||
} else {
|
||||
state.progressText = up < 100 ? '上传中..' : '正在处理,请稍候..'
|
||||
state.progress = up < 100 ? up : 0
|
||||
}
|
||||
})
|
||||
if (typeof result == 'object' && (result as TUploadErrorResult).type !== 'application/json') {
|
||||
successCb && successCb(result as MediaSource, file.name)
|
||||
} else alert('服务器繁忙,请稍等下重新尝试~')
|
||||
}
|
||||
|
||||
export async function uploadCutPhotoToCloud(cutImage: string) {
|
||||
try {
|
||||
const response = await fetch(cutImage)
|
||||
const buffer = await response.arrayBuffer()
|
||||
const file = new File([buffer], `cut_image_${Math.random()}.png`)
|
||||
// upload
|
||||
const qnOptions = { bucket: 'xp-design', prePath: 'user' }
|
||||
const result = await Qiniu.upload(file, qnOptions)
|
||||
const { width, height } = await getImage(file)
|
||||
const url = _config.IMG_URL + result.key
|
||||
await api.material.addMyPhoto({ width, height, url })
|
||||
return url
|
||||
} catch(e) {
|
||||
console.error(`upload cut file error: msg: ${e}`)
|
||||
return ''
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-08 14:15:17
|
||||
* @Description: 手动抠图 - 修补擦除
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-09 01:28:11
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="show" align-center width="90%" @close="showMatting = false">
|
||||
<template #header>
|
||||
<div class="tool-wrap">
|
||||
<el-button type="primary" plain @click="done">确认应用</el-button>
|
||||
<el-radio-group v-model="isErasing" style="margin-left: 35px">
|
||||
<el-radio :label="false" size="large"> <b>修补画笔</b> <i class="icon sd-xiubu" /></el-radio>
|
||||
<el-radio :label="true" size="large"> <b>擦除画笔</b> <i class="icon sd-cachu" /></el-radio>
|
||||
</el-radio-group>
|
||||
<number-slider v-model="radius" class="slider-wrap" label="画笔尺寸" :showInput="false" labelWidth="90px" :maxValue="constants.RADIUS_SLIDER_MAX" :minValue="constants.RADIUS_SLIDER_MIN" :step="constants.RADIUS_SLIDER_STEP" />
|
||||
<number-slider v-model="hardness" class="slider-wrap" label="画笔硬度" :showInput="false" labelWidth="90px" :maxValue="constants.HARDNESS_SLIDER_MAX" :minValue="constants.HARDNESS_SLIDER_MIN" :step="constants.HARDNESS_SLIDER_STEP" />
|
||||
</div>
|
||||
</template>
|
||||
<matting v-if="showMatting" :hasHeader="false" @register="mattingStart" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, nextTick } from 'vue'
|
||||
import matting, { MattingType } from '@palxp/image-extraction'
|
||||
import { ElRadioGroup, ElRadio } from 'element-plus'
|
||||
import numberSlider from '@/components/modules/settings/numberSlider.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { matting, ElRadioGroup, ElRadio, numberSlider },
|
||||
setup() {
|
||||
const state: any = reactive({
|
||||
show: false,
|
||||
showMatting: false,
|
||||
isErasing: false,
|
||||
radius: 0, // 画笔尺寸
|
||||
brushSize: '', // 画笔尺寸:计算属性,显示值
|
||||
hardness: 0, // 画笔硬度
|
||||
hardnessText: '', // 画笔硬度:计算属性,显示值
|
||||
constants: {},
|
||||
})
|
||||
|
||||
const params: any = { raw: '', result: '' }
|
||||
let matting: MattingType | any = {}
|
||||
let callback: any = null // 传回自动抠图的回调
|
||||
|
||||
const mattingStart: any = (mattingOptions: MattingType) => {
|
||||
mattingOptions.initLoadImages(params.raw, params.result)
|
||||
state.isErasing = mattingOptions.isErasing
|
||||
state.radius = mattingOptions.radius
|
||||
state.hardness = mattingOptions.hardness
|
||||
state.constants = mattingOptions.constants
|
||||
matting = mattingOptions
|
||||
}
|
||||
|
||||
const open = async (raw: any, result: any, cb: any) => {
|
||||
state.show = true
|
||||
params.raw = raw
|
||||
params.result = result
|
||||
await nextTick()
|
||||
setTimeout(() => {
|
||||
state.showMatting = true
|
||||
}, 300)
|
||||
callback = cb
|
||||
}
|
||||
|
||||
const done = () => {
|
||||
state.show = false
|
||||
callback(matting.getResult())
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
open,
|
||||
done,
|
||||
mattingStart,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
:deep(.el-dialog__header) {
|
||||
padding: 10px 35px;
|
||||
// var(--el-dialog-padding-primary)
|
||||
}
|
||||
.tool-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
// .tool-left {
|
||||
// display: inline-flex;
|
||||
// flex: 1;
|
||||
// }
|
||||
.slider-wrap {
|
||||
margin-left: 35px;
|
||||
width: 240px;
|
||||
}
|
||||
</style>
|
136
src/components/business/image-cutout/ImageExtraction/index.vue
Normal file
136
src/components/business/image-cutout/ImageExtraction/index.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-08 14:15:17
|
||||
* @Description: 手动抠图 - 修补擦除
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-04 09:50:00
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="state.show" align-center width="90%" @close="state.showMatting = false">
|
||||
<template #header>
|
||||
<div class="tool-wrap">
|
||||
<el-button type="primary" plain @click="done">确认应用</el-button>
|
||||
<el-radio-group v-model="state.isErasing" style="margin-left: 35px">
|
||||
<el-radio :value="false" size="large"> <b>修补画笔</b> <i class="icon sd-xiubu" /></el-radio>
|
||||
<el-radio :value="true" size="large"> <b>擦除画笔</b> <i class="icon sd-cachu" /></el-radio>
|
||||
</el-radio-group>
|
||||
<number-slider
|
||||
v-model="state.radius" class="slider-wrap"
|
||||
label="画笔尺寸" :showInput="false"
|
||||
labelWidth="90px"
|
||||
:maxValue="state.constants?.RADIUS_SLIDER_MAX" :minValue="state.constants?.RADIUS_SLIDER_MIN"
|
||||
:step="state.constants?.RADIUS_SLIDER_STEP"
|
||||
/>
|
||||
<number-slider
|
||||
v-model="state.hardness" class="slider-wrap"
|
||||
label="画笔硬度" :showInput="false"
|
||||
labelWidth="90px"
|
||||
:maxValue="state.constants?.HARDNESS_SLIDER_MAX" :minValue="state.constants?.HARDNESS_SLIDER_MIN"
|
||||
:step="state.constants?.HARDNESS_SLIDER_STEP"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<matting v-if="state.showMatting" :hasHeader="false" @register="mattingStart" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, toRefs, nextTick, DefineComponent } from 'vue'
|
||||
import matting, { MattingType } from '@palxp/image-extraction'
|
||||
import { ElRadioGroup, ElRadio } from 'element-plus'
|
||||
import numberSlider from '@/components/modules/settings/numberSlider.vue'
|
||||
|
||||
type TState = {
|
||||
show: boolean;
|
||||
showMatting: boolean;
|
||||
isErasing: boolean;
|
||||
radius: number | string;
|
||||
brushSize: string;
|
||||
hardness: number | string;
|
||||
hardnessText: string;
|
||||
constants: MattingType['constants'] | null;
|
||||
}
|
||||
|
||||
type TParams = {
|
||||
raw: string
|
||||
result: string
|
||||
}
|
||||
|
||||
type TCallback = ((base64: string) => void) | null
|
||||
|
||||
const props: TParams = {
|
||||
raw: '',
|
||||
result: ''
|
||||
}
|
||||
|
||||
let callback: TCallback = null // 传回自动抠图的回调
|
||||
|
||||
const state = reactive<TState>({
|
||||
show: false,
|
||||
showMatting: false,
|
||||
isErasing: false,
|
||||
radius: 0, // 画笔尺寸
|
||||
brushSize: '', // 画笔尺寸:计算属性,显示值
|
||||
hardness: 0, // 画笔硬度
|
||||
hardnessText: '', // 画笔硬度:计算属性,显示值
|
||||
constants: null,
|
||||
})
|
||||
|
||||
let mattingParam: MattingType | null
|
||||
|
||||
const mattingStart = (mattingOptions: MattingType) => {
|
||||
mattingOptions.initLoadImages(props.raw, props.result)
|
||||
state.isErasing = mattingOptions.isErasing
|
||||
state.radius = mattingOptions.radius
|
||||
state.hardness = mattingOptions.hardness
|
||||
state.constants = mattingOptions.constants
|
||||
mattingParam = mattingOptions
|
||||
}
|
||||
|
||||
const open = async (raw: string, result: string, cb: TCallback) => {
|
||||
state.show = true
|
||||
props.raw = raw
|
||||
props.result = result
|
||||
await nextTick()
|
||||
setTimeout(() => {
|
||||
state.showMatting = true
|
||||
}, 300)
|
||||
callback = cb
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
|
||||
const done = () => {
|
||||
state.show = false
|
||||
callback && callback(mattingParam?.getResult())
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
:deep(.el-dialog__header) {
|
||||
padding: 10px 35px;
|
||||
// var(--el-dialog-padding-primary)
|
||||
}
|
||||
.tool-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
// .tool-left {
|
||||
// display: inline-flex;
|
||||
// flex: 1;
|
||||
// }
|
||||
.slider-wrap {
|
||||
margin-left: 35px;
|
||||
width: 240px;
|
||||
}
|
||||
</style>
|
||||
|
@ -5,5 +5,5 @@
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-12 00:05:48
|
||||
*/
|
||||
import index from './ImageCutout.vue'
|
||||
import index from './ImageCutout/index.vue'
|
||||
export default index
|
||||
|
@ -2,20 +2,20 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-10-08 10:07:19
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-05 00:04:51
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-04 18:10:00
|
||||
-->
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="选择图片" @close="close">
|
||||
<el-dialog v-model="state.dialogVisible" title="选择图片" @close="close">
|
||||
<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="isDone" :listData="imgList" @load="load" @select="selectImg" />
|
||||
<photo-list :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="isPicsDone" :listData="recommendImgList" @load="loadPic" @select="selectImg($event, recommendImgList)" />
|
||||
<photo-list :isDone="state.isPicsDone" :listData="state.recommendImgList" @load="loadPic" @select="selectImg($event, state.recommendImgList)" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
@ -30,29 +30,39 @@
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, defineEmits, defineExpose } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { ElTabPane, ElTabs } from 'element-plus'
|
||||
import { ElTabPane, ElTabs, TabPaneName } from 'element-plus'
|
||||
import api from '@/api'
|
||||
import { TGetImageListResult } from '@/api/material'
|
||||
|
||||
export default defineComponent({
|
||||
components: { [ElTabPane.name]: ElTabPane, [ElTabs.name]: ElTabs },
|
||||
emits: ['select'],
|
||||
setup(props, context) {
|
||||
const store = useStore()
|
||||
const state: any = reactive({
|
||||
type TEmits = (event: 'select', data: TGetImageListResult) => void
|
||||
|
||||
type TState = {
|
||||
dialogVisible: boolean;
|
||||
imgList: TGetImageListResult[];
|
||||
recommendImgList: TGetImageListResult[];
|
||||
isDone: boolean;
|
||||
isPicsDone: boolean;
|
||||
}
|
||||
|
||||
const emits = defineEmits<TEmits>()
|
||||
|
||||
const store = useStore()
|
||||
const state = reactive<TState>({
|
||||
dialogVisible: false,
|
||||
imgList: [],
|
||||
recommendImgList: [],
|
||||
isDone: false,
|
||||
isPicsDone: false,
|
||||
})
|
||||
})
|
||||
|
||||
let loading = false
|
||||
let page = 0
|
||||
let picPage = 0
|
||||
const load = (init?: boolean) => {
|
||||
let loading = false
|
||||
let page = 0
|
||||
let picPage = 0
|
||||
|
||||
const load = async (init?: boolean) => {
|
||||
if (init) {
|
||||
state.imgList = []
|
||||
page = 0
|
||||
@ -63,14 +73,15 @@ export default defineComponent({
|
||||
}
|
||||
loading = true
|
||||
page += 1
|
||||
api.material.getMyPhoto({ page }).then(({ list }: any) => {
|
||||
api.material.getMyPhoto({ page }).then(({ list }) => {
|
||||
list.length <= 0 ? (state.isDone = true) : (state.imgList = state.imgList.concat(list))
|
||||
setTimeout(() => {
|
||||
loading = false
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
const loadPic = (init?: boolean) => {
|
||||
}
|
||||
|
||||
const loadPic = (init?: boolean) => {
|
||||
if (state.isPicsDone || loading) {
|
||||
return
|
||||
}
|
||||
@ -79,47 +90,41 @@ export default defineComponent({
|
||||
}
|
||||
loading = true
|
||||
picPage += 1
|
||||
api.material.getImagesList({ page: picPage }).then(({ list }: any) => {
|
||||
api.material.getImagesList({ page: picPage }).then(({ list }) => {
|
||||
list.length <= 0 ? (state.isPicsDone = true) : (state.recommendImgList = state.recommendImgList.concat(list))
|
||||
setTimeout(() => {
|
||||
loading = false
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
const open = () => {
|
||||
state.dialogVisible = true
|
||||
load()
|
||||
store.commit('setShowMoveable', false)
|
||||
}
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
const close = () => {
|
||||
store.commit('setShowMoveable', true)
|
||||
}
|
||||
}
|
||||
|
||||
const selectImg = (index: number, list: any) => {
|
||||
const item: any = list ? list[index] : state.imgList[index]
|
||||
context.emit('select', item)
|
||||
const selectImg = (index: number, list: TGetImageListResult[]) => {
|
||||
const item: TGetImageListResult = list ? list[index] : state.imgList[index]
|
||||
// context.emit('select', item)
|
||||
emits('select', item)
|
||||
state.dialogVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
const tabChange = (index: any) => {
|
||||
const tabChange = (index: TabPaneName) => {
|
||||
if (index == 1) {
|
||||
loadPic(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
open,
|
||||
close,
|
||||
load,
|
||||
loadPic,
|
||||
selectImg,
|
||||
tabChange,
|
||||
}
|
||||
},
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -2,108 +2,63 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-03-16 09:15:52
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-31 15:43:10
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-04 18:50:00
|
||||
-->
|
||||
<template>
|
||||
<div ref="qrCodeDom" class="qrcode__wrap"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, watch, nextTick } from 'vue'
|
||||
import QRCodeStyling, { DrawType, TypeNumber, Mode, ErrorCorrectionLevel, DotType, CornerSquareType, CornerDotType, Extension } from 'qr-code-styling'
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch, nextTick, defineProps } from 'vue'
|
||||
import QRCodeStyling, {DotType, Options } from 'qr-code-styling'
|
||||
import { debounce } from 'throttle-debounce'
|
||||
import { generateOption } from './method'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
width: {
|
||||
default: 300,
|
||||
},
|
||||
height: {
|
||||
default: 300,
|
||||
},
|
||||
image: {},
|
||||
value: {},
|
||||
export type TQrcodeProps = {
|
||||
width?: number
|
||||
height?: number
|
||||
image?: string
|
||||
value?: string
|
||||
dotsOptions: {
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
let options = {}
|
||||
watch(
|
||||
color: string,
|
||||
type: DotType,
|
||||
}
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TQrcodeProps>(), {
|
||||
width: 300,
|
||||
height: 300,
|
||||
dotsOptions: () => ({
|
||||
color: '#41b583',
|
||||
type: 'rounded',
|
||||
})
|
||||
})
|
||||
|
||||
let options: Options = {}
|
||||
watch(
|
||||
() => [props.width, props.height, props.dotsOptions],
|
||||
() => {
|
||||
render()
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
const render = debounce(300, false, async () => {
|
||||
options = {
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
type: 'canvas' as DrawType, // canvas svg
|
||||
data: props.value,
|
||||
image: props.image, // /favicon.svg
|
||||
margin: 0,
|
||||
qrOptions: {
|
||||
typeNumber: 3 as TypeNumber,
|
||||
mode: 'Byte' as Mode,
|
||||
errorCorrectionLevel: 'M' as ErrorCorrectionLevel,
|
||||
},
|
||||
imageOptions: {
|
||||
hideBackgroundDots: true,
|
||||
imageSize: 0.4,
|
||||
margin: 6,
|
||||
crossOrigin: 'anonymous',
|
||||
},
|
||||
backgroundOptions: {
|
||||
color: '#ffffff',
|
||||
},
|
||||
dotsOptions: {
|
||||
color: '#41b583',
|
||||
type: 'rounded' as DotType,
|
||||
...props.dotsOptions,
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
color: props.dotsOptions.color,
|
||||
type: '',
|
||||
// type: 'extra-rounded' as CornerSquareType,
|
||||
// gradient: {
|
||||
// type: 'linear', // 'radial'
|
||||
// rotation: 180,
|
||||
// colorStops: [{ offset: 0, color: '#25456e' }, { offset: 1, color: '#4267b2' }]
|
||||
// },
|
||||
},
|
||||
cornersDotOptions: {
|
||||
color: props.dotsOptions.color,
|
||||
type: 'square' as CornerDotType,
|
||||
// gradient: {
|
||||
// type: 'linear', // 'radial'
|
||||
// rotation: 180,
|
||||
// colorStops: [{ offset: 0, color: '#00266e' }, { offset: 1, color: '#4060b3' }]
|
||||
// },
|
||||
},
|
||||
}
|
||||
const render = debounce(300, false, async () => {
|
||||
options = generateOption(props)
|
||||
if (props.value) {
|
||||
qrCode.update(options)
|
||||
options && qrCode.update(options)
|
||||
await nextTick()
|
||||
qrCodeDom.value.firstChild.style = 'width: 100%;' // 强制其适应缩放
|
||||
if (!qrCodeDom?.value?.firstChild) return
|
||||
(qrCodeDom.value.firstChild as HTMLElement).setAttribute('style', "width: 100%;") // 强制其适应缩放
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const qrCode = new QRCodeStyling(options)
|
||||
const qrCodeDom = ref<HTMLElement>()
|
||||
const qrCode = new QRCodeStyling(options)
|
||||
const qrCodeDom = ref<HTMLElement>()
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
render()
|
||||
qrCode.append(qrCodeDom.value)
|
||||
})
|
||||
|
||||
return {
|
||||
qrCodeDom,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
60
src/components/business/qrcode/method.ts
Normal file
60
src/components/business/qrcode/method.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* @Author: Jeremy Yu
|
||||
* @Date: 2024-03-04 18:10:00
|
||||
* @Description:
|
||||
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-04 18:10:00
|
||||
*/
|
||||
import { CornerDotType, Options } from "qr-code-styling"
|
||||
import { TQrcodeProps } from "./index.vue"
|
||||
|
||||
/** 生成二维码数据 */
|
||||
export function generateOption(props: TQrcodeProps): Options {
|
||||
return {
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
type: 'canvas', // canvas svg
|
||||
data: props.value,
|
||||
image: props.image, // /favicon.svg
|
||||
margin: 0,
|
||||
qrOptions: {
|
||||
typeNumber: 3,
|
||||
mode: 'Byte',
|
||||
errorCorrectionLevel: 'M',
|
||||
},
|
||||
imageOptions: {
|
||||
hideBackgroundDots: true,
|
||||
imageSize: 0.4,
|
||||
margin: 6,
|
||||
crossOrigin: 'anonymous',
|
||||
},
|
||||
backgroundOptions: {
|
||||
color: '#ffffff',
|
||||
},
|
||||
dotsOptions: {
|
||||
// color: '#41b583',
|
||||
// type: 'rounded' as DotType,
|
||||
...props.dotsOptions,
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
color: props.dotsOptions.color,
|
||||
// type: '',
|
||||
// type: 'extra-rounded' as CornerSquareType,
|
||||
// gradient: {
|
||||
// type: 'linear', // 'radial'
|
||||
// rotation: 180,
|
||||
// colorStops: [{ offset: 0, color: '#25456e' }, { offset: 1, color: '#4267b2' }]
|
||||
// },
|
||||
},
|
||||
cornersDotOptions: {
|
||||
color: props.dotsOptions.color,
|
||||
type: 'square' as CornerDotType,
|
||||
// gradient: {
|
||||
// type: 'linear', // 'radial'
|
||||
// rotation: 180,
|
||||
// colorStops: [{ offset: 0, color: '#00266e' }, { offset: 1, color: '#4060b3' }]
|
||||
// },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +1,87 @@
|
||||
<template>
|
||||
<div v-show="showMenuBg" id="menu-bg" class="menu-bg" @click="closeMenu">
|
||||
<ul ref="menuList" class="menu-list" :style="styleObj">
|
||||
<li v-for="(item, index) in menuList.list" :key="index" :class="{ 'menu-item': true, 'disable-menu': dCopyElement.length === 0 && item.type === 'paste' }" @click.stop="selectMenu(item.type)">
|
||||
<li v-for="(item, index) in menuListData.list" :key="index" :class="{ 'menu-item': true, 'disable-menu': dCopyElement.length === 0 && item.type === 'paste' }" @click.stop="selectMenu(item.type)">
|
||||
{{ item.text }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { widgetMenu, pageMenu, menuList } from './rcMenuData'
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import {
|
||||
widgetMenu as widget,
|
||||
pageMenu as page,
|
||||
menuList as menu,
|
||||
TMenuItemData, TWidgetItemData,
|
||||
} from './rcMenuData'
|
||||
import { getTarget } from '@/common/methods/target'
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {},
|
||||
data() {
|
||||
const store = useStore()
|
||||
const menuListData = ref<TMenuItemData>({...menu})
|
||||
const showMenuBg = ref<boolean>(false)
|
||||
const widgetMenu = ref<TWidgetItemData[]>({...widget})
|
||||
const pageMenu = ref<TWidgetItemData[]>({...page})
|
||||
|
||||
const {dActiveElement, dAltDown, dWidgets, dCopyElement} = useSetupMapGetters(['dActiveElement', 'dAltDown', 'dWidgets', 'dCopyElement'])
|
||||
|
||||
const styleObj = computed(() => {
|
||||
return {
|
||||
menuList,
|
||||
showMenuBg: false,
|
||||
widgetMenu,
|
||||
pageMenu,
|
||||
left: menuListData.value.left + 'px',
|
||||
top: menuListData.value.top + 'px',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dActiveElement', 'dAltDown', 'dWidgets', 'dCopyElement']),
|
||||
styleObj() {
|
||||
return {
|
||||
left: this.menuList.left + 'px',
|
||||
top: this.menuList.top + 'px',
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.oncontextmenu = this.mouseRightClick
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['selectWidget', 'copyWidget', 'pasteWidget', 'updateLayerIndex', 'deleteWidget', 'ungroup']),
|
||||
async mouseRightClick(e: any) {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
document.oncontextmenu = mouseRightClick
|
||||
})
|
||||
async function mouseRightClick(e: MouseEvent) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (this.showMenuBg) {
|
||||
this.showMenuBg = false
|
||||
if (showMenuBg.value) {
|
||||
showMenuBg.value = false
|
||||
return
|
||||
}
|
||||
// let target = e.target
|
||||
let target = await getTarget(e.target)
|
||||
|
||||
if (!e.target) return
|
||||
let target = await getTarget(e.target as HTMLElement)
|
||||
if (!target) return
|
||||
let type = target.getAttribute('data-type')
|
||||
if (type) {
|
||||
let uuid = target.getAttribute('data-uuid') // 设置选中元素
|
||||
|
||||
if (uuid !== '-1' && !this.dAltDown) {
|
||||
let widget = this.dWidgets.find((item: any) => item.uuid === uuid)
|
||||
if (widget.parent !== '-1' && widget.parent !== this.dActiveElement.uuid && widget.parent !== this.dActiveElement.parent) {
|
||||
if (uuid !== '-1' && !dAltDown) {
|
||||
let widget = dWidgets.value.find((item: any) => item.uuid === uuid)
|
||||
if (
|
||||
widget.parent !== '-1' &&
|
||||
widget.parent !== dActiveElement.value.uuid &&
|
||||
widget.parent !== dActiveElement.value.parent
|
||||
) {
|
||||
uuid = widget.parent
|
||||
}
|
||||
}
|
||||
this.selectWidget({
|
||||
uuid: uuid || '-1',
|
||||
store.dispatch('selectWidget', {
|
||||
uuid: uuid ?? '-1',
|
||||
})
|
||||
this.showMenu(e)
|
||||
showMenu(e)
|
||||
}
|
||||
},
|
||||
showMenu(e: any) {
|
||||
let isPage = this.dActiveElement.uuid === '-1'
|
||||
this.menuList.list = isPage ? this.pageMenu : this.widgetMenu
|
||||
if (this.dActiveElement.isContainer) {
|
||||
let ungroup = [
|
||||
}
|
||||
|
||||
function showMenu(e: MouseEvent) {
|
||||
let isPage = dActiveElement.value.uuid === '-1'
|
||||
menuListData.value.list = isPage ? pageMenu.value : widgetMenu.value
|
||||
if (dActiveElement.value.isContainer) {
|
||||
let ungroup: TWidgetItemData[] = [
|
||||
{
|
||||
type: 'ungroup',
|
||||
text: '取消组合',
|
||||
},
|
||||
]
|
||||
this.menuList.list = ungroup.concat(this.menuList.list)
|
||||
menuListData.value.list = ungroup.concat(menuListData.value.list)
|
||||
}
|
||||
this.showMenuBg = true
|
||||
showMenuBg.value = true
|
||||
// document.getElementById('menu-bg').addEventListener('click', this.closeMenu, false)
|
||||
let mx = e.pageX
|
||||
let my = e.pageY
|
||||
@ -84,53 +89,54 @@ export default defineComponent({
|
||||
if (mx + listWidth > window.innerWidth) {
|
||||
mx -= listWidth
|
||||
}
|
||||
let listHeight = (14 + 10) * this.menuList.list.length + 10
|
||||
let listHeight = (14 + 10) * menuListData.value.list.length + 10
|
||||
if (my + listHeight > window.innerHeight) {
|
||||
my -= listHeight
|
||||
}
|
||||
this.menuList.left = mx
|
||||
this.menuList.top = my
|
||||
},
|
||||
closeMenu() {
|
||||
this.showMenuBg = false
|
||||
// document.getElementById('menu-bg').removeEventListener('click', this.closeMenu, false)
|
||||
},
|
||||
selectMenu(type) {
|
||||
menuListData.value.left = mx
|
||||
menuListData.value.top = my
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
showMenuBg.value = false
|
||||
}
|
||||
|
||||
/** 点击菜单触发事件 */
|
||||
function selectMenu(type: TWidgetItemData['type']) {
|
||||
switch (type) {
|
||||
case 'copy':
|
||||
this.copyWidget()
|
||||
store.dispatch('copyWidget')
|
||||
break
|
||||
case 'paste':
|
||||
if (this.dCopyElement.length === 0) {
|
||||
if (dCopyElement.value.length === 0) {
|
||||
return
|
||||
}
|
||||
this.pasteWidget()
|
||||
store.dispatch('pasteWidget')
|
||||
break
|
||||
case 'index-up':
|
||||
this.updateLayerIndex({
|
||||
uuid: this.dActiveElement.uuid,
|
||||
store.dispatch('updateLayerIndex', {
|
||||
uuid: dActiveElement.value.uuid,
|
||||
value: 1,
|
||||
isGroup: this.dActiveElement.isContainer,
|
||||
isGroup: dActiveElement.value.isContainer,
|
||||
})
|
||||
break
|
||||
case 'index-down':
|
||||
this.updateLayerIndex({
|
||||
uuid: this.dActiveElement.uuid,
|
||||
store.dispatch('updateLayerIndex', {
|
||||
uuid: dActiveElement.value.uuid,
|
||||
value: -1,
|
||||
isGroup: this.dActiveElement.isContainer,
|
||||
isGroup: dActiveElement.value.isContainer,
|
||||
})
|
||||
break
|
||||
case 'del':
|
||||
this.deleteWidget()
|
||||
store.dispatch('deleteWidget')
|
||||
break
|
||||
case 'ungroup':
|
||||
this.ungroup(this.dActiveElement.uuid)
|
||||
store.dispatch('ungroup', dActiveElement.value.uuid)
|
||||
break
|
||||
}
|
||||
this.closeMenu()
|
||||
},
|
||||
},
|
||||
})
|
||||
closeMenu()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -2,15 +2,28 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-07-30 17:38:50
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2021-07-30 18:15:22
|
||||
* @LastEditors: ShawnPhang, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-04 18:50:00
|
||||
*/
|
||||
export const menuList: any = {
|
||||
|
||||
export type TMenuItemData = {
|
||||
left: number
|
||||
top: number
|
||||
list: TWidgetItemData[]
|
||||
}
|
||||
|
||||
export const menuList: TMenuItemData = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
list: [],
|
||||
}
|
||||
export const widgetMenu = [
|
||||
|
||||
export type TWidgetItemData = {
|
||||
type: 'copy' | 'paste' | 'index-up' | 'index-down' | 'del' | 'ungroup'
|
||||
text: string
|
||||
}
|
||||
|
||||
export const widgetMenu: TWidgetItemData[] = [
|
||||
{
|
||||
type: 'copy',
|
||||
text: '复制',
|
||||
@ -33,7 +46,7 @@ export const widgetMenu = [
|
||||
},
|
||||
]
|
||||
|
||||
export const pageMenu = [
|
||||
export const pageMenu: TWidgetItemData[] = [
|
||||
{
|
||||
type: 'paste',
|
||||
text: '粘贴',
|
||||
|
@ -2,66 +2,63 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-01 11:12:17
|
||||
* @Description: 前端出图 - 用于封面
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-13 17:36:36
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-04 18:50:00
|
||||
-->
|
||||
<template>
|
||||
<div id="cover-wrap"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, watch, getCurrentInstance, ComponentInternalInstance } from 'vue'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
<script lang="ts" setup>
|
||||
import { useStore } from 'vuex'
|
||||
import html2canvas from 'html2canvas'
|
||||
import api from '@/api'
|
||||
import Qiniu from '@/common/methods/QiNiu'
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
|
||||
|
||||
export default defineComponent({
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, context) {
|
||||
const { proxy }: any = getCurrentInstance() as ComponentInternalInstance
|
||||
const store = useStore();
|
||||
|
||||
async function createCover(cb: any) {
|
||||
const nowZoom = proxy?.dZoom
|
||||
const { dZoom } = useSetupMapGetters(['dZoom'])
|
||||
|
||||
|
||||
// props: ['modelValue'],
|
||||
// emits: ['update:modelValue'],
|
||||
|
||||
async function createCover(cb: any) {
|
||||
const nowZoom = dZoom.value
|
||||
// 取消选中元素
|
||||
proxy?.selectWidget({
|
||||
store.dispatch('selectWidget', {
|
||||
uuid: '-1',
|
||||
})
|
||||
proxy?.updateZoom(100)
|
||||
store.dispatch('updateZoom', 100)
|
||||
|
||||
const opts = {
|
||||
useCORS: true, // 跨域图片
|
||||
scale: 0.2,
|
||||
}
|
||||
setTimeout(async () => {
|
||||
const clonePage: HTMLElement = document.getElementById('page-design-canvas').cloneNode(true)
|
||||
const clonePage = document.getElementById('page-design-canvas')?.cloneNode(true) as HTMLElement
|
||||
if (!clonePage) return
|
||||
clonePage.setAttribute('id', 'clone-page')
|
||||
document.body.appendChild(clonePage)
|
||||
html2canvas(document.getElementById('clone-page'), opts).then((canvas: any) => {
|
||||
html2canvas(clonePage, opts).then((canvas) => {
|
||||
canvas.toBlob(
|
||||
async (blobObj: Blob) => {
|
||||
const result: any = await Qiniu.upload(blobObj, { bucket: 'xp-design', prePath: 'cover/user' })
|
||||
async (blobObj) => {
|
||||
if (blobObj) {
|
||||
const result = await Qiniu.upload(blobObj, { bucket: 'xp-design', prePath: 'cover/user' })
|
||||
cb(result)
|
||||
}
|
||||
},
|
||||
'image/jpeg',
|
||||
0.15,
|
||||
)
|
||||
proxy?.updateZoom(nowZoom)
|
||||
store.dispatch('updateZoom', nowZoom)
|
||||
clonePage.remove()
|
||||
})
|
||||
}, 10)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
createCover,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dZoom']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['selectWidget', 'updateZoom']),
|
||||
},
|
||||
defineExpose({
|
||||
createCover
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -13,28 +13,21 @@
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
title: {
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
default: 0,
|
||||
},
|
||||
content: {
|
||||
default: '',
|
||||
},
|
||||
// top/top-start/top-end/bottom/bottom-start/bottom-end/left/left-start/left-end/right/right-start/right-end
|
||||
position: {
|
||||
default: 'bottom',
|
||||
},
|
||||
// offset: {
|
||||
// default: 0,
|
||||
// },
|
||||
},
|
||||
setup() {},
|
||||
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"
|
||||
offset?: number
|
||||
}
|
||||
|
||||
const { title, width, content, position } = withDefaults(defineProps<TProps>(), {
|
||||
title: '',
|
||||
width: 0,
|
||||
content: '',
|
||||
position: 'bottom'
|
||||
})
|
||||
</script>
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-12-28 09:29:42
|
||||
* @Description: 百分比进度条
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-13 23:05:29
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-05 10:50:00
|
||||
-->
|
||||
<template>
|
||||
<div v-if="percent" class="mask">
|
||||
@ -16,33 +16,45 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { watch, defineProps, defineEmits } from 'vue'
|
||||
import { ElProgress } from 'element-plus'
|
||||
|
||||
export default defineComponent({
|
||||
components: { ElProgress },
|
||||
props: ['percent', 'text', 'cancelText', 'msg'],
|
||||
emits: ['done', 'cancel'],
|
||||
setup(props, context) {
|
||||
watch(
|
||||
() => props.percent,
|
||||
type TProps = {
|
||||
percent: number
|
||||
text: string
|
||||
cancelText: string
|
||||
msg?: string
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'done'): void
|
||||
(event: 'cancel'): void
|
||||
}
|
||||
|
||||
const {percent, text, cancelText, msg} = defineProps<TProps>()
|
||||
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
watch(
|
||||
() => percent,
|
||||
(num) => {
|
||||
if (num >= 100) {
|
||||
setTimeout(() => {
|
||||
context.emit('done')
|
||||
emit('done')
|
||||
}, 1000)
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
const cancel = () => {
|
||||
context.emit('cancel')
|
||||
}
|
||||
const cancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
return { cancel }
|
||||
},
|
||||
defineExpose({
|
||||
cancel
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-29 18:17:13
|
||||
* @Description: 二次封装上传组件
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-05 15:46:02
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-05 10:50:00
|
||||
-->
|
||||
<template>
|
||||
<el-upload action="" accept="image/*" :http-request="upload" :show-file-list="false" multiple>
|
||||
@ -13,39 +13,57 @@
|
||||
</el-upload>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, nextTick } from 'vue'
|
||||
import { ElUpload } from 'element-plus'
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, nextTick, defineProps, withDefaults, defineEmits } from 'vue'
|
||||
import { ElUpload, UploadRequestOptions } from 'element-plus'
|
||||
import Qiniu from '@/common/methods/QiNiu'
|
||||
import { getImage } from '@/common/methods/getImgDetail'
|
||||
import _config from '@/config'
|
||||
import useNotification from '@/common/methods/notification'
|
||||
|
||||
export default defineComponent({
|
||||
components: { ElUpload },
|
||||
props: {
|
||||
modelValue: {},
|
||||
options: {
|
||||
default: () => {
|
||||
return { bucket: 'xp-design', prePath: 'user' }
|
||||
},
|
||||
},
|
||||
hold: {
|
||||
default: false, // 是否阻止上传操作,仅做文件选择
|
||||
},
|
||||
},
|
||||
emits: ['done', 'update:modelValue', 'load'],
|
||||
setup(props, context) {
|
||||
let uploading: boolean = false // 上传状态Flag
|
||||
let timer: any = null
|
||||
type TModelData = {
|
||||
num?: string | number
|
||||
ratio?: string
|
||||
}
|
||||
|
||||
let uploadList: any[] = [] // 上传队列
|
||||
let index: number = 0 // 当前上传的脚标
|
||||
let count: number = 0 // 当前上传总数
|
||||
export type TUploadDoneData = {
|
||||
width: number
|
||||
height: number
|
||||
url: string
|
||||
}
|
||||
|
||||
let tempSimpleRes: any = null // 单个文件上传时返回
|
||||
type TQiNiuUploadReturn = { hash: string, key: string }
|
||||
|
||||
onMounted(async () => {
|
||||
type TProps = {
|
||||
modelValue?: TModelData
|
||||
options?: { bucket: string, prePath: string }
|
||||
hold?: boolean
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'done', data: TUploadDoneData): void
|
||||
(event: 'update:modelValue', data: TModelData): void
|
||||
(event: 'load', data: File): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
modelValue: () => ({}),
|
||||
options: () => ({ bucket: 'xp-design', prePath: 'user' }),
|
||||
hold: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
let uploading: boolean = false // 上传状态Flag
|
||||
let timer: number
|
||||
|
||||
let uploadList: File[] = [] // 上传队列
|
||||
let index: number = 0 // 当前上传的脚标
|
||||
let count: number = 0 // 当前上传总数
|
||||
|
||||
let tempSimpleRes: TQiNiuUploadReturn | null // 单个文件上传时返回
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
setTimeout(() => {
|
||||
// 加载七牛上传插件
|
||||
@ -53,11 +71,11 @@ export default defineComponent({
|
||||
link_element.setAttribute('src', _config.QINIUYUN_PLUGIN)
|
||||
document.head.appendChild(link_element)
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
|
||||
const upload = ({ file }: any) => {
|
||||
const upload = async ({ file }: UploadRequestOptions) => {
|
||||
if (props.hold) {
|
||||
context.emit('load', file)
|
||||
emit('load', file)
|
||||
return
|
||||
}
|
||||
uploadList.push(file)
|
||||
@ -65,18 +83,20 @@ export default defineComponent({
|
||||
count++
|
||||
updatePercent(null)
|
||||
uploadQueue()
|
||||
}
|
||||
// 上传队列
|
||||
const uploadQueue = async () => {
|
||||
}
|
||||
|
||||
// 上传队列
|
||||
const uploadQueue = async () => {
|
||||
if (!uploading) {
|
||||
uploading = true
|
||||
const file = uploadList[0]
|
||||
if (file) {
|
||||
if (file.size <= 1024 * 1024) {
|
||||
tempSimpleRes = await qiNiuUpload(file) // 队列有文件,执行上传
|
||||
const { width, height }: any = await getImage(file)
|
||||
console.log("tempSimpleRes", tempSimpleRes)
|
||||
const { width, height } = await getImage(file)
|
||||
useNotification('上传成功', '公共测试账户,上传请注意保护隐私哦!', { position: 'bottom-left' })
|
||||
context.emit('done', { width, height, url: _config.IMG_URL + tempSimpleRes.key }) // 单个文件进行响应
|
||||
emit('done', { width, height, url: _config.IMG_URL + tempSimpleRes?.key }) // 单个文件进行响应
|
||||
} else useNotification('爱护小水管', '请上传小于 1M 的图片哦!', { type: 'error', position: 'bottom-left' })
|
||||
uploading = false
|
||||
handleRemove() // 移除已上传文件
|
||||
@ -91,37 +111,37 @@ export default defineComponent({
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
}
|
||||
const qiNiuUpload = async (file: File) => {
|
||||
}
|
||||
|
||||
const qiNiuUpload = async (file: File): Promise<null | TQiNiuUploadReturn> => {
|
||||
updatePercent(0)
|
||||
return new Promise(async (resolve: Function) => {
|
||||
return new Promise(async (resolve) => {
|
||||
if (props.hold) {
|
||||
context.emit('load', file)
|
||||
resolve()
|
||||
emit('load', file)
|
||||
resolve(null)
|
||||
} else {
|
||||
const result: any = await Qiniu.upload(file, props.options, (res: Type.Object) => {
|
||||
const result = await Qiniu.upload(file, props.options, (res: Type.Object) => {
|
||||
updatePercent(res.total.percent)
|
||||
})
|
||||
resolve(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 更新视图
|
||||
const updatePercent = (p?: number | null) => {
|
||||
}
|
||||
|
||||
// 更新视图
|
||||
const updatePercent = (p?: number | null) => {
|
||||
const num = typeof p === 'number' ? String(p) : p
|
||||
const percent = { ...props.modelValue }
|
||||
percent.num = num ? Number(num).toFixed(0) : percent.num
|
||||
percent.ratio = count ? `${index} / ${count}` : ''
|
||||
context.emit('update:modelValue', percent)
|
||||
}
|
||||
const handleRemove = () => {
|
||||
emit('update:modelValue', percent)
|
||||
}
|
||||
const handleRemove = () => {
|
||||
uploadList.length > 0 && uploadList.splice(0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
defineExpose({
|
||||
upload,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -5,6 +5,8 @@
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-04-08 10:28:47
|
||||
*/
|
||||
import { App } from "vue"
|
||||
|
||||
function capitalizeFirstLetter(string: string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
||||
}
|
||||
@ -15,9 +17,10 @@ const exclude = ['settings', 'layout']
|
||||
const regex = RegExp('.*^(?!.*?(' + exclude.join('|') + ')).*\\.vue$')
|
||||
|
||||
// const requireComponent = require.context('.', true, /\.vue$/) // 找到components文件夹下以.vue命名的文件
|
||||
const requireComponent = import.meta.globEager('./**/*.vue')
|
||||
|
||||
function guide(Vue: Type.Object) {
|
||||
const requireComponent = import.meta.glob('./**/*.vue', { eager: true })
|
||||
|
||||
function guide(Vue: App) {
|
||||
for (const fileName in requireComponent) {
|
||||
if (regex.test(fileName)) {
|
||||
const componentConfig = requireComponent[fileName]
|
||||
|
@ -1,304 +0,0 @@
|
||||
<template>
|
||||
<div id="page-design" ref="page-design" :style="{ paddingTop: dPaddingTop + 'px' }">
|
||||
<div
|
||||
id="out-page"
|
||||
class="out-page"
|
||||
:style="{
|
||||
width: (dPage.width * dZoom) / 100 + 120 + 'px',
|
||||
height: (dPage.height * dZoom) / 100 + 120 + 'px',
|
||||
opacity: 1 - (dZoom < 100 ? dPage.tag : 0),
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
<div
|
||||
:id="pageDesignCanvasId"
|
||||
class="design-canvas"
|
||||
:data-type="dPage.type"
|
||||
:data-uuid="dPage.uuid"
|
||||
:style="{
|
||||
width: dPage.width + 'px',
|
||||
height: dPage.height + 'px',
|
||||
transform: 'scale(' + dZoom / 100 + ')',
|
||||
transformOrigin: (dZoom >= 100 ? 'center' : 'left') + ' top',
|
||||
backgroundColor: dPage.backgroundColor,
|
||||
backgroundImage: `url(${dPage?.backgroundImage})`,
|
||||
backgroundSize: dPage?.backgroundTransform?.x ? 'auto' : 'cover',
|
||||
backgroundPositionX: (dPage?.backgroundTransform?.x || 0) + 'px',
|
||||
backgroundPositionY: (dPage?.backgroundTransform?.y || 0) + 'px',
|
||||
opacity: dPage.opacity + (dZoom < 100 ? dPage.tag : 0),
|
||||
}"
|
||||
@mousemove="dropOver($event)"
|
||||
@drop="drop($event)"
|
||||
@mouseup="drop($event)"
|
||||
>
|
||||
<!-- <grid-size /> -->
|
||||
|
||||
<!-- :class="{
|
||||
layer: true,
|
||||
'layer-active': getIsActive(layer.uuid),
|
||||
'layer-hover': layer.uuid === dHoverUuid || dActiveElement.parent === layer.uuid,
|
||||
}" -->
|
||||
<component :is="layer.type" v-for="layer in getlayers()" :id="layer.uuid" :key="layer.uuid" :class="['layer', { 'layer-hover': layer.uuid === dHoverUuid || dActiveElement.parent === layer.uuid, 'layer-no-hover': dActiveElement.uuid === layer.uuid }]" :data-title="layer.type" :params="layer" :parent="dPage" :data-type="layer.type" :data-uuid="layer.uuid">
|
||||
<template v-if="layer.isContainer">
|
||||
<!-- :class="{
|
||||
layer: true,
|
||||
'layer-active': getIsActive(widget.uuid),
|
||||
'layer-no-hover': dActiveElement.uuid !== widget.parent && dActiveElement.parent !== widget.parent,
|
||||
'layer-hover': widget.uuid === dHoverUuid,
|
||||
}" -->
|
||||
<component :is="widget.type" v-for="widget in getChilds(layer.uuid)" :key="widget.uuid" child :class="['layer', { 'layer-no-hover': dActiveElement.uuid !== widget.parent && dActiveElement.parent !== widget.parent }]" :data-title="widget.type" :params="widget" :parent="layer" :data-type="widget.type" :data-uuid="widget.uuid" />
|
||||
</template>
|
||||
</component>
|
||||
|
||||
<!-- <ref-line v-if="dSelectWidgets.length === 0" /> -->
|
||||
<!-- <size-control v-if="dSelectWidgets.length === 0" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { getTarget } from '@/common/methods/target'
|
||||
|
||||
import setWidgetData from '@/common/methods/DesignFeatures/setWidgetData'
|
||||
import PointImg from '@/utils/plugins/pointImg'
|
||||
import getComponentsData from '@/common/methods/DesignFeatures/setComponents'
|
||||
import { debounce } from 'throttle-debounce'
|
||||
|
||||
// 页面设计组件
|
||||
const NAME = 'page-design'
|
||||
|
||||
import { move, moveInit } from '@/mixins/move'
|
||||
|
||||
export default defineComponent({
|
||||
name: NAME,
|
||||
// components: {lineGuides},
|
||||
mixins: [moveInit],
|
||||
props: ['pageDesignCanvasId'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dPaddingTop', 'dPage', 'dZoom', 'dScreen', 'dWidgets', 'dActiveElement', 'dHoverUuid', 'dSelectWidgets', 'dAltDown', 'dDraging', 'showRotatable']),
|
||||
},
|
||||
mounted() {
|
||||
this.getScreen()
|
||||
document.getElementById('page-design').addEventListener('mousedown', this.handleSelection, false)
|
||||
document.getElementById('page-design').addEventListener('mousemove', debounce(100, false, this.handleMouseMove), false)
|
||||
},
|
||||
beforeUnmount() {},
|
||||
methods: {
|
||||
...mapActions(['updateScreen', 'selectWidget', 'deleteWidget', 'addWidget', 'addGroup']),
|
||||
// getBackground(data) {
|
||||
// if (data.startsWith('http')) return `url(${data})`
|
||||
// if (data.startsWith('linear-gradient')) return data
|
||||
// },
|
||||
async dropOver(e) {
|
||||
if (this.dActiveElement.editable || this.dActiveElement.lock) {
|
||||
return false
|
||||
}
|
||||
e.preventDefault()
|
||||
let { data, type } = this.$store.getters.selectItem
|
||||
if (type !== 'image') {
|
||||
return
|
||||
}
|
||||
const target = await getTarget(e.target)
|
||||
const uuid = target.getAttribute('data-uuid')
|
||||
this.$store.dispatch('setDropOver', uuid)
|
||||
if (e.target.getAttribute('putIn')) {
|
||||
this._dropIn = uuid
|
||||
const imgUrl = data.value.thumb || data.value.url
|
||||
!this._srcCache && (this._srcCache = target.firstElementChild.firstElementChild.src)
|
||||
target.firstElementChild.firstElementChild.src = imgUrl
|
||||
} else {
|
||||
this._srcCache && (target.firstElementChild.firstElementChild.src = this._srcCache)
|
||||
this._srcCache = ''
|
||||
this._dropIn = ''
|
||||
}
|
||||
},
|
||||
async drop(e) {
|
||||
if (!this.dDraging) {
|
||||
return
|
||||
}
|
||||
this.$store.commit('setDraging', false)
|
||||
const dropIn = this._dropIn
|
||||
this._dropIn = ''
|
||||
this.$store.dispatch('setDropOver', '-1')
|
||||
this.$store.commit('setShowMoveable', false) // 清理上一次的选择
|
||||
let lost = e.target.className !== 'design-canvas' // className === 'design-canvas' , id: "page-design-canvas"
|
||||
// e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let { data: item, type } = JSON.parse(JSON.stringify(this.$store.getters.selectItem))
|
||||
// 清除临时数据
|
||||
this.$store.commit('selectItem', {})
|
||||
let setting = {}
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
// 处理数据
|
||||
setting = await setWidgetData(type, item, setting)
|
||||
// 绝对坐标
|
||||
const lostX = e.x - document.getElementById('page-design-canvas').getBoundingClientRect().left
|
||||
const lostY = e.y - document.getElementById('page-design-canvas').getBoundingClientRect().top
|
||||
// 放置组合
|
||||
if (type === 'group') {
|
||||
let parent = {}
|
||||
item = await getComponentsData(item)
|
||||
item.forEach((element) => {
|
||||
if (element.type === 'w-group') {
|
||||
parent.width = element.width
|
||||
parent.height = element.height
|
||||
}
|
||||
})
|
||||
const half = { x: parent.width ? (parent.width * this.$store.getters.dZoom) / 100 / 2 : 0, y: parent.height ? (parent.height * this.$store.getters.dZoom) / 100 / 2 : 0 }
|
||||
item.forEach((element) => {
|
||||
element.left += (lost ? lostX - half.x : e.layerX - half.x) * (100 / this.$store.getters.dZoom)
|
||||
element.top += (lost ? lostY - half.y : e.layerY - half.y) * (100 / this.$store.getters.dZoom)
|
||||
})
|
||||
this.addGroup(item)
|
||||
}
|
||||
// 设置坐标
|
||||
const half = { x: setting.width ? (setting.width * this.$store.getters.dZoom) / 100 / 2 : 0, y: setting.height ? (setting.height * this.$store.getters.dZoom) / 100 / 2 : 0 }
|
||||
// const half = { x: (this.dDragInitData.offsetX * this.dZoom) / 100, y: (this.dDragInitData.offsetY * this.dZoom) / 100 }
|
||||
setting.left = (lost ? lostX - half.x : e.layerX - half.x) * (100 / this.$store.getters.dZoom)
|
||||
setting.top = (lost ? lostY - half.y : e.layerY - half.y) * (100 / this.$store.getters.dZoom)
|
||||
if (lost && type === 'image') {
|
||||
// 如果不从画布加入,且不是图片类型,则判断是否加入到svg中
|
||||
const target = await getTarget(e.target)
|
||||
const targetType = target.getAttribute('data-type')
|
||||
const uuid = target.getAttribute('data-uuid')
|
||||
if (targetType === 'w-mask') {
|
||||
// 容器
|
||||
this.$store.commit('setShowMoveable', true) // 恢复选择
|
||||
const widget = this.dWidgets.find((item) => item.uuid === uuid)
|
||||
widget.imgUrl = item.value.url
|
||||
// if (e.target.className.baseVal) {
|
||||
// !widget.imgs && (widget.imgs = {})
|
||||
// widget.imgs[`${e.target.className.baseVal}`] = item.value.url
|
||||
// }
|
||||
} else {
|
||||
if (dropIn) {
|
||||
const widget = this.dWidgets.find((item) => item.uuid == dropIn)
|
||||
widget.imgUrl = item.value.url
|
||||
console.log('加入+', widget)
|
||||
this.$store.commit('setShowMoveable', true) // 恢复选择
|
||||
} else {
|
||||
this.addWidget(setting) // 正常加入面板
|
||||
}
|
||||
}
|
||||
} else if (type === 'bg') {
|
||||
console.log('背景图片放置')
|
||||
} else if (type !== 'group') {
|
||||
console.log(setting)
|
||||
this.addWidget(setting) // 正常加入面板
|
||||
}
|
||||
// 清除临时数据
|
||||
// this.$store.commit('selectItem', {})
|
||||
},
|
||||
getScreen() {
|
||||
let screen = this.$refs['page-design']
|
||||
this.updateScreen({
|
||||
width: screen.offsetWidth,
|
||||
height: screen.offsetHeight,
|
||||
})
|
||||
},
|
||||
async handleMouseMove(e) {
|
||||
const pImg = new PointImg(e.target)
|
||||
const { rgba } = pImg.getColorXY(e.offsetX, e.offsetY)
|
||||
if (rgba && rgba === 'rgba(0,0,0,0)') {
|
||||
console.log('解析点位颜色: ', rgba)
|
||||
let target = await getTarget(e.target)
|
||||
target.style.pointerEvents = 'none'
|
||||
setTimeout(() => {
|
||||
target.style.pointerEvents = 'auto'
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
async handleSelection(e) {
|
||||
if (e.which === 3) {
|
||||
return
|
||||
}
|
||||
|
||||
let target = await getTarget(e.target)
|
||||
let type = target.getAttribute('data-type')
|
||||
|
||||
if (type) {
|
||||
let uuid = target.getAttribute('data-uuid')
|
||||
if (uuid !== '-1' && !this.dAltDown) {
|
||||
let widget = this.dWidgets.find((item) => item.uuid === uuid)
|
||||
if (widget.parent !== '-1' && widget.parent !== this.dActiveElement.uuid && widget.parent !== this.dActiveElement.parent) {
|
||||
uuid = widget.parent
|
||||
}
|
||||
}
|
||||
|
||||
// 设置选中元素
|
||||
// this.$store.commit('setMoveable', false)
|
||||
if (this.showRotatable !== false) {
|
||||
this.selectWidget({
|
||||
uuid: uuid,
|
||||
})
|
||||
}
|
||||
|
||||
if (uuid !== '-1') {
|
||||
this.initmovement && this.initmovement(e) // 参见 mixins
|
||||
}
|
||||
} else {
|
||||
// 取消选中元素
|
||||
this.selectWidget({
|
||||
uuid: '-1',
|
||||
})
|
||||
}
|
||||
},
|
||||
getlayers() {
|
||||
return this.dWidgets.filter((item) => item.parent === this.dPage.uuid)
|
||||
},
|
||||
getChilds(uuid) {
|
||||
return this.dWidgets.filter((item) => item.parent === uuid)
|
||||
},
|
||||
// getIsActive(uuid) {
|
||||
// if (this.dSelectWidgets.length > 0) {
|
||||
// let widget = this.dSelectWidgets.find((item) => item.uuid === uuid)
|
||||
// if (widget) {
|
||||
// return true
|
||||
// }
|
||||
// return false
|
||||
// } else {
|
||||
// return uuid === this.dActiveElement.uuid
|
||||
// }
|
||||
// },
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#page-design {
|
||||
height: 100%;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.out-page {
|
||||
margin: 0 auto;
|
||||
padding: 60px;
|
||||
position: relative;
|
||||
.design-canvas {
|
||||
// transition: all 0.3s;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
box-shadow: 1px 1px 10px 3px rgba(0, 0, 0, 0.1);
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
// z-index: -9999;
|
||||
// overflow: hidden;
|
||||
// overflow: auto;
|
||||
}
|
||||
// .design-canvas ::-webkit-scrollbar {
|
||||
// display: none; /* Chrome Safari */
|
||||
// }
|
||||
}
|
||||
}
|
||||
</style>
|
362
src/components/modules/layout/designBoard/index.vue
Normal file
362
src/components/modules/layout/designBoard/index.vue
Normal file
@ -0,0 +1,362 @@
|
||||
<template>
|
||||
<div id="page-design" ref="page_design" :style="{ paddingTop: dPaddingTop + 'px' }">
|
||||
<div
|
||||
id="out-page"
|
||||
class="out-page"
|
||||
:style="{
|
||||
width: (dPage.width * dZoom) / 100 + 120 + 'px',
|
||||
height: (dPage.height * dZoom) / 100 + 120 + 'px',
|
||||
opacity: 1 - (dZoom < 100 ? dPage.tag : 0),
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
<div
|
||||
:id="pageDesignCanvasId"
|
||||
class="design-canvas"
|
||||
:data-type="dPage.type"
|
||||
:data-uuid="dPage.uuid"
|
||||
:style="{
|
||||
width: dPage.width + 'px',
|
||||
height: dPage.height + 'px',
|
||||
transform: 'scale(' + dZoom / 100 + ')',
|
||||
transformOrigin: (dZoom >= 100 ? 'center' : 'left') + ' top',
|
||||
backgroundColor: dPage.backgroundColor,
|
||||
backgroundImage: `url(${dPage?.backgroundImage})`,
|
||||
backgroundSize: dPage?.backgroundTransform?.x ? 'auto' : 'cover',
|
||||
backgroundPositionX: (dPage?.backgroundTransform?.x || 0) + 'px',
|
||||
backgroundPositionY: (dPage?.backgroundTransform?.y || 0) + 'px',
|
||||
opacity: dPage.opacity + (dZoom < 100 ? dPage.tag : 0),
|
||||
}"
|
||||
@mousemove="dropOver($event)"
|
||||
@drop="drop($event)"
|
||||
@mouseup="drop($event)"
|
||||
>
|
||||
<!-- <grid-size /> -->
|
||||
|
||||
<!-- :class="{
|
||||
layer: true,
|
||||
'layer-active': getIsActive(layer.uuid),
|
||||
'layer-hover': layer.uuid === dHoverUuid || dActiveElement.parent === layer.uuid,
|
||||
}" -->
|
||||
<component :is="layer.type" v-for="layer in getlayers()" :id="layer.uuid" :key="layer.uuid" :class="['layer', { 'layer-hover': layer.uuid === dHoverUuid || dActiveElement.parent === layer.uuid, 'layer-no-hover': dActiveElement.uuid === layer.uuid }]" :data-title="layer.type" :params="layer" :parent="dPage" :data-type="layer.type" :data-uuid="layer.uuid">
|
||||
<template v-if="layer.isContainer">
|
||||
<!-- :class="{
|
||||
layer: true,
|
||||
'layer-active': getIsActive(widget.uuid),
|
||||
'layer-no-hover': dActiveElement.uuid !== widget.parent && dActiveElement.parent !== widget.parent,
|
||||
'layer-hover': widget.uuid === dHoverUuid,
|
||||
}" -->
|
||||
<component :is="widget.type" v-for="widget in getChilds(layer.uuid)" :key="widget.uuid" child :class="['layer', { 'layer-no-hover': dActiveElement.uuid !== widget.parent && dActiveElement.parent !== widget.parent }]" :data-title="widget.type" :params="widget" :parent="layer" :data-type="widget.type" :data-uuid="widget.uuid" />
|
||||
</template>
|
||||
</component>
|
||||
|
||||
<!-- <ref-line v-if="dSelectWidgets.length === 0" /> -->
|
||||
<!-- <size-control v-if="dSelectWidgets.length === 0" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, defineProps, onMounted, ref } from 'vue'
|
||||
import { mapGetters, mapActions, useStore } from 'vuex'
|
||||
import { getTarget } from '@/common/methods/target'
|
||||
|
||||
import setWidgetData from '@/common/methods/DesignFeatures/setWidgetData'
|
||||
import PointImg from '@/utils/plugins/pointImg'
|
||||
import getComponentsData from '@/common/methods/DesignFeatures/setComponents'
|
||||
import { debounce } from 'throttle-debounce'
|
||||
import { move, moveInit } from '@/mixins/move'
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
|
||||
// 页面设计组件
|
||||
type TProps = {
|
||||
pageDesignCanvasId: string
|
||||
}
|
||||
|
||||
type TParentData = {
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
type TSetting = {
|
||||
width?: number
|
||||
height?: number
|
||||
top?: number
|
||||
left?: number
|
||||
}
|
||||
|
||||
const store = useStore()
|
||||
const { pageDesignCanvasId } = defineProps<TProps>()
|
||||
const {
|
||||
dPaddingTop, dPage, dZoom, dScreen, dWidgets,
|
||||
dActiveElement, dSelectWidgets, dAltDown, dDraging,
|
||||
dHoverUuid, showRotatable
|
||||
} = useSetupMapGetters(['dPaddingTop', 'dPage', 'dZoom', 'dScreen', 'dWidgets', 'dActiveElement', 'dHoverUuid', 'dSelectWidgets', 'dAltDown', 'dDraging', 'showRotatable'])
|
||||
|
||||
let _dropIn: string | null = ''
|
||||
let _srcCache: string | null = ''
|
||||
|
||||
onMounted(() => {
|
||||
getScreen()
|
||||
const pageDesignEl = document.getElementById('page-design')
|
||||
if (!pageDesignEl) return
|
||||
pageDesignEl.addEventListener('mousedown', handleSelection, false)
|
||||
pageDesignEl.addEventListener('mousemove', debounce(100, false, handleMouseMove), false)
|
||||
})
|
||||
|
||||
// components: {lineGuides},
|
||||
// mixins: [moveInit],
|
||||
// ...mapActions(['updateScreen', 'selectWidget', 'deleteWidget', 'addWidget', 'addGroup']),
|
||||
|
||||
// getBackground(data) {
|
||||
// if (data.startsWith('http')) return `url(${data})`
|
||||
// if (data.startsWith('linear-gradient')) return data
|
||||
// },
|
||||
|
||||
async function dropOver(e: MouseEvent) {
|
||||
if (dActiveElement.value.editable || dActiveElement.value.lock) {
|
||||
return false
|
||||
}
|
||||
e.preventDefault()
|
||||
let { data, type } = store.getters.selectItem
|
||||
if (type !== 'image') {
|
||||
return
|
||||
}
|
||||
if (!e || !e.target) return
|
||||
const eventTarget = e.target as HTMLElement
|
||||
const target = await getTarget(eventTarget)
|
||||
if (!target) return
|
||||
const uuid = target.getAttribute('data-uuid')
|
||||
store.dispatch('setDropOver', uuid)
|
||||
|
||||
const imgEl = target?.firstElementChild?.firstElementChild as HTMLImageElement
|
||||
if (eventTarget.getAttribute('putIn')) {
|
||||
_dropIn = uuid
|
||||
const imgUrl = data.value.thumb || data.value.url
|
||||
!_srcCache && (_srcCache = imgEl.src)
|
||||
imgEl.src = imgUrl
|
||||
} else {
|
||||
_srcCache && (imgEl.src = _srcCache)
|
||||
_srcCache = ''
|
||||
_dropIn = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function drop(e: MouseEvent) {
|
||||
if (!dDraging.value) {
|
||||
return
|
||||
}
|
||||
if (!e || !e.target) return
|
||||
const eventTarget = e.target as HTMLElement
|
||||
store.commit('setDraging', false)
|
||||
const dropIn = _dropIn
|
||||
_dropIn = ''
|
||||
store.dispatch('setDropOver', '-1')
|
||||
store.commit('setShowMoveable', false) // 清理上一次的选择
|
||||
let lost = eventTarget.className !== 'design-canvas' // className === 'design-canvas' , id: "page-design-canvas"
|
||||
// e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let { data: item, type } = JSON.parse(JSON.stringify(store.getters.selectItem))
|
||||
// 清除临时数据
|
||||
store.commit('selectItem', {})
|
||||
let setting: TSetting = {}
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
// 处理数据
|
||||
setting = await setWidgetData(type, item, setting)
|
||||
// 绝对坐标
|
||||
const canvasEl = document.getElementById('page-design-canvas')
|
||||
if (!canvasEl) return
|
||||
const lostX = e.x - canvasEl.getBoundingClientRect().left
|
||||
const lostY = e.y - canvasEl.getBoundingClientRect().top
|
||||
// 放置组合
|
||||
if (type === 'group') {
|
||||
let parent: TParentData = {}
|
||||
const componentItem = await getComponentsData(item)
|
||||
// item = await getComponentsData(item)
|
||||
componentItem.forEach((element) => {
|
||||
if (element.type === 'w-group') {
|
||||
parent.width = element.width
|
||||
parent.height = element.height
|
||||
}
|
||||
})
|
||||
const half = {
|
||||
x: parent.width ? (parent.width * store.getters.dZoom) / 100 / 2 : 0,
|
||||
y: parent.height ? (parent.height * store.getters.dZoom) / 100 / 2 : 0
|
||||
}
|
||||
componentItem.forEach((element) => {
|
||||
element.left += (lost ? lostX - half.x : e.layerX - half.x) * (100 / store.getters.dZoom)
|
||||
element.top += (lost ? lostY - half.y : e.layerY - half.y) * (100 / store.getters.dZoom)
|
||||
})
|
||||
store.dispatch('addGroup', componentItem)
|
||||
// addGroup(item)
|
||||
}
|
||||
// 设置坐标
|
||||
const half = {
|
||||
x: setting.width ? (setting.width * store.getters.dZoom) / 100 / 2 : 0,
|
||||
y: setting.height ? (setting.height * store.getters.dZoom) / 100 / 2 : 0
|
||||
}
|
||||
// const half = { x: (this.dDragInitData.offsetX * this.dZoom) / 100, y: (this.dDragInitData.offsetY * this.dZoom) / 100 }
|
||||
setting.left = (lost ? lostX - half.x : e.layerX - half.x) * (100 / store.getters.dZoom)
|
||||
setting.top = (lost ? lostY - half.y : e.layerY - half.y) * (100 / store.getters.dZoom)
|
||||
if (lost && type === 'image') {
|
||||
// 如果不从画布加入,且不是图片类型,则判断是否加入到svg中
|
||||
const target = await getTarget(eventTarget)
|
||||
if (!target) return
|
||||
const targetType = target.getAttribute('data-type')
|
||||
const uuid = target.getAttribute('data-uuid')
|
||||
if (targetType === 'w-mask') {
|
||||
// 容器
|
||||
store.commit('setShowMoveable', true) // 恢复选择
|
||||
const widget = dWidgets.value.find((item: {uuid: string}) => item.uuid === uuid)
|
||||
widget.imgUrl = item.value.url
|
||||
// if (e.target.className.baseVal) {
|
||||
// !widget.imgs && (widget.imgs = {})
|
||||
// widget.imgs[`${e.target.className.baseVal}`] = item.value.url
|
||||
// }
|
||||
} else {
|
||||
if (dropIn) {
|
||||
const widget = dWidgets.value.find((item: {uuid: string}) => item.uuid == dropIn)
|
||||
widget.imgUrl = item.value.url
|
||||
console.log('加入+', widget)
|
||||
store.commit('setShowMoveable', true) // 恢复选择
|
||||
} else {
|
||||
store.dispatch('addWidget', setting) // 正常加入面板
|
||||
// addWidget(setting) // 正常加入面板
|
||||
}
|
||||
}
|
||||
} else if (type === 'bg') {
|
||||
console.log('背景图片放置')
|
||||
} else if (type !== 'group') {
|
||||
console.log(setting)
|
||||
store.dispatch('addWidget', setting) // 正常加入面板
|
||||
// addWidget(setting) // 正常加入面板
|
||||
}
|
||||
// 清除临时数据
|
||||
// this.$store.commit('selectItem', {})
|
||||
}
|
||||
|
||||
function getScreen() {
|
||||
const pageDesignEl = document.getElementById('page-design')
|
||||
if (!pageDesignEl) return
|
||||
store.dispatch('updateScreen', {
|
||||
width: pageDesignEl.offsetWidth,
|
||||
height: pageDesignEl.offsetHeight,
|
||||
})
|
||||
// updateScreen({
|
||||
// width: pageDesignEl.offsetWidth,
|
||||
// height: pageDesignEl.offsetHeight,
|
||||
// })
|
||||
}
|
||||
|
||||
async function handleMouseMove(e: MouseEvent) {
|
||||
if (!e || !e.target) return
|
||||
const imageTarget = e.target as HTMLImageElement
|
||||
const pImg = new PointImg(imageTarget)
|
||||
const { rgba } = pImg.getColorXY(e.offsetX, e.offsetY)
|
||||
if (rgba && rgba === 'rgba(0,0,0,0)') {
|
||||
console.log('解析点位颜色: ', rgba)
|
||||
let target = await getTarget(imageTarget)
|
||||
if (!target) return
|
||||
target.style.pointerEvents = 'none'
|
||||
setTimeout(() => {
|
||||
if (!target) return
|
||||
target.style.pointerEvents = 'auto'
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSelection(e: MouseEvent) {
|
||||
if (e.which === 3) {
|
||||
return
|
||||
}
|
||||
if (!e || !e.target) return
|
||||
let target = await getTarget(e.target as HTMLElement)
|
||||
if (!target) return
|
||||
let type = target.getAttribute('data-type')
|
||||
|
||||
if (type) {
|
||||
let uuid = target.getAttribute('data-uuid')
|
||||
if (uuid !== '-1' && !dAltDown.value) {
|
||||
let widget = dWidgets.value.find((item: {uuid: string}) => item.uuid === uuid)
|
||||
if (widget.parent !== '-1' && widget.parent !== dActiveElement.value.uuid && widget.parent !== dActiveElement.value.parent) {
|
||||
uuid = widget.parent
|
||||
}
|
||||
}
|
||||
|
||||
// 设置选中元素
|
||||
// this.$store.commit('setMoveable', false)
|
||||
if (showRotatable.value !== false) {
|
||||
store.dispatch('selectWidget', {
|
||||
uuid: uuid,
|
||||
})
|
||||
// selectWidget({
|
||||
// uuid: uuid,
|
||||
// })
|
||||
}
|
||||
|
||||
if (uuid !== '-1') {
|
||||
moveInit.methods.initmovement(e) // 参见 mixins
|
||||
}
|
||||
} else {
|
||||
// 取消选中元素
|
||||
store.dispatch('selectWidget', {
|
||||
uuid: '-1',
|
||||
})
|
||||
// selectWidget({
|
||||
// uuid: '-1',
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
function getlayers() {
|
||||
return dWidgets.value.filter((item: { parent: string }) => item.parent === dPage.value.uuid)
|
||||
}
|
||||
|
||||
function getChilds(uuid: string) {
|
||||
return dWidgets.value.filter((item: { parent: string }) => item.parent === uuid)
|
||||
}
|
||||
// getIsActive(uuid) {
|
||||
// if (this.dSelectWidgets.length > 0) {
|
||||
// let widget = this.dSelectWidgets.find((item) => item.uuid === uuid)
|
||||
// if (widget) {
|
||||
// return true
|
||||
// }
|
||||
// return false
|
||||
// } else {
|
||||
// return uuid === this.dActiveElement.uuid
|
||||
// }
|
||||
// },
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#page-design {
|
||||
height: 100%;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.out-page {
|
||||
margin: 0 auto;
|
||||
padding: 60px;
|
||||
position: relative;
|
||||
.design-canvas {
|
||||
// transition: all 0.3s;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
box-shadow: 1px 1px 10px 3px rgba(0, 0, 0, 0.1);
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
// z-index: -9999;
|
||||
// overflow: hidden;
|
||||
// overflow: auto;
|
||||
}
|
||||
// .design-canvas ::-webkit-scrollbar {
|
||||
// display: none; /* Chrome Safari */
|
||||
// }
|
||||
}
|
||||
}
|
||||
</style>
|
@ -9,64 +9,86 @@
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { watch, defineProps } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import Guides from '@scena/guides'
|
||||
import Guides, { GuideOptions } from '@scena/guides'
|
||||
|
||||
export default defineComponent({
|
||||
props: ['show'],
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const container = 'page-design' // page-design out-page
|
||||
let guidesTop: any = null
|
||||
let guidesLeft: any = null
|
||||
type TProps = {
|
||||
show: boolean
|
||||
}
|
||||
|
||||
watch(
|
||||
type TSameParams = {
|
||||
backgroundColor: string,
|
||||
lineColor: string
|
||||
textColor: string
|
||||
// direction: 'start',
|
||||
// height: 30,
|
||||
displayDragPos: boolean,
|
||||
dragPosFormat: (v: string | number) => string,
|
||||
}
|
||||
|
||||
type TGuidesData = Guides & GuideOptions
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
show: false
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
const container = 'page-design' // page-design out-page
|
||||
let guidesTop: TGuidesData | null = null
|
||||
let guidesLeft: TGuidesData | null = null
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(open) => {
|
||||
open ? render() : destroy()
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => store.getters.dZoom,
|
||||
() => {
|
||||
changeScroll()
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
// onMounted(() => {
|
||||
// // let scrollX = 0
|
||||
// // let scrollY = 0
|
||||
// // window.addEventListener('resize', () => {
|
||||
// // guides.resize()
|
||||
// // })
|
||||
// // window.addEventListener('wheel', (e) => {
|
||||
// // scrollX += e.deltaX
|
||||
// // scrollY += e.deltaY
|
||||
// // guides.scrollGuides(scrollY)
|
||||
// // guides.scroll(scrollX)
|
||||
// // })
|
||||
// })
|
||||
// onMounted(() => {
|
||||
// // let scrollX = 0
|
||||
// // let scrollY = 0
|
||||
// // window.addEventListener('resize', () => {
|
||||
// // guides.resize()
|
||||
// // })
|
||||
// // window.addEventListener('wheel', (e) => {
|
||||
// // scrollX += e.deltaX
|
||||
// // scrollY += e.deltaY
|
||||
// // guides.scrollGuides(scrollY)
|
||||
// // guides.scroll(scrollX)
|
||||
// // })
|
||||
// })
|
||||
|
||||
function destroy() {
|
||||
guidesTop.destroy()
|
||||
guidesLeft.destroy()
|
||||
function destroy() {
|
||||
guidesTop?.destroy()
|
||||
guidesLeft?.destroy()
|
||||
guidesTop = null
|
||||
guidesLeft = null
|
||||
}
|
||||
function render() {
|
||||
const sameParams: any = {
|
||||
}
|
||||
|
||||
function render() {
|
||||
const sameParams: TSameParams = {
|
||||
backgroundColor: '#f9f9fa',
|
||||
lineColor: '#bec2c7',
|
||||
textColor: '#999999',
|
||||
// direction: 'start',
|
||||
// height: 30,
|
||||
displayDragPos: true,
|
||||
dragPosFormat: (v: any) => v + 'px',
|
||||
dragPosFormat: (v) => v + 'px',
|
||||
}
|
||||
guidesTop = new Guides(document.getElementById(container), {
|
||||
|
||||
const containerEl = document.getElementById(container)
|
||||
if (!containerEl) return
|
||||
|
||||
guidesTop = new Guides(containerEl, {
|
||||
...sameParams,
|
||||
type: 'horizontal',
|
||||
className: 'my-horizontal',
|
||||
@ -77,7 +99,7 @@ export default defineComponent({
|
||||
// store.commit('updateGuidelines', { horizontalGuidelines: e.guides.map((x) => x + top) })
|
||||
})
|
||||
|
||||
guidesLeft = new Guides(document.getElementById(container), {
|
||||
guidesLeft = new Guides(containerEl, {
|
||||
...sameParams,
|
||||
type: 'vertical',
|
||||
className: 'my-vertical',
|
||||
@ -87,8 +109,9 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
changeScroll()
|
||||
}
|
||||
function changeScroll() {
|
||||
}
|
||||
|
||||
function changeScroll() {
|
||||
if (guidesTop && guidesLeft) {
|
||||
const zoom = store.getters.dZoom / 100
|
||||
guidesTop.zoom = zoom
|
||||
@ -98,18 +121,18 @@ export default defineComponent({
|
||||
guidesLeft.unit = Math.floor(1 / zoom) * 50
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (guidesTop && guidesLeft) {
|
||||
const el = document.getElementById('out-page')
|
||||
const left = 60 + (el?.offsetLeft || 0)
|
||||
const top = 30 + (el?.offsetTop || 0)
|
||||
const left = 60 + (el?.offsetLeft ?? 0)
|
||||
const top = 30 + (el?.offsetTop ?? 0)
|
||||
guidesTop.scroll(-left / zoom)
|
||||
guidesTop.scrollGuides(-top / zoom)
|
||||
guidesLeft.scroll(-top / zoom)
|
||||
guidesLeft.scrollGuides(-(left - 30) / zoom)
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
@ -1,10 +1,11 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-03 17:50:21
|
||||
* @Description: 旧大小控制组件,已交由moveable控制
|
||||
* @Description: 旧大小控制组件,已交由moveable控制 (已不使用)
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2021-08-09 11:13:09
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div v-if="dActiveElement.record && dActiveElement.uuid !== '-1'" id="size-control">
|
||||
<!-- 上左 -->
|
||||
@ -99,73 +100,74 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
// import { mapGetters, mapActions } from 'vuex'
|
||||
|
||||
// 组件大小控制器
|
||||
const NAME = 'size-control'
|
||||
// // 组件大小控制器
|
||||
// const NAME = 'size-control'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
data() {
|
||||
return {
|
||||
dirs: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dActiveElement', 'dWidgets']),
|
||||
left() {
|
||||
return parseInt(this.dActiveElement.left)
|
||||
},
|
||||
top() {
|
||||
return parseInt(this.dActiveElement.top)
|
||||
},
|
||||
width() {
|
||||
return parseInt(this.dActiveElement.record.width)
|
||||
},
|
||||
height() {
|
||||
return parseInt(this.dActiveElement.record.height)
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
...mapActions(['dResize', 'initDResize', 'dResize', 'stopDResize']),
|
||||
handlemousedown(e, dirs) {
|
||||
e.stopPropagation()
|
||||
this.dirs = dirs.split('-')
|
||||
this.initDResize({
|
||||
startX: e.pageX,
|
||||
startY: e.pageY,
|
||||
originX: this.dActiveElement.left,
|
||||
originY: this.dActiveElement.top,
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
})
|
||||
// export default {
|
||||
// name: NAME,
|
||||
// data() {
|
||||
// return {
|
||||
// dirs: [],
|
||||
// }
|
||||
// },
|
||||
// computed: {
|
||||
// ...mapGetters(['dActiveElement', 'dWidgets']),
|
||||
// left() {
|
||||
// return parseInt(this.dActiveElement.left)
|
||||
// },
|
||||
// top() {
|
||||
// return parseInt(this.dActiveElement.top)
|
||||
// },
|
||||
// width() {
|
||||
// return parseInt(this.dActiveElement.record.width)
|
||||
// },
|
||||
// height() {
|
||||
// return parseInt(this.dActiveElement.record.height)
|
||||
// },
|
||||
// },
|
||||
// watch: {},
|
||||
// methods: {
|
||||
// ...mapActions(['dResize', 'initDResize', 'dResize', 'stopDResize']),
|
||||
// handlemousedown(e, dirs) {
|
||||
// e.stopPropagation()
|
||||
// this.dirs = dirs.split('-')
|
||||
// this.initDResize({
|
||||
// startX: e.pageX,
|
||||
// startY: e.pageY,
|
||||
// originX: this.dActiveElement.left,
|
||||
// originY: this.dActiveElement.top,
|
||||
// width: this.width,
|
||||
// height: this.height,
|
||||
// })
|
||||
|
||||
document.addEventListener('mousemove', this.handlemousemove, true)
|
||||
document.addEventListener('mouseup', this.handlemouseup, true)
|
||||
},
|
||||
// document.addEventListener('mousemove', this.handlemousemove, true)
|
||||
// document.addEventListener('mouseup', this.handlemouseup, true)
|
||||
// },
|
||||
|
||||
handlemousemove(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
// handlemousemove(e) {
|
||||
// e.stopPropagation()
|
||||
// e.preventDefault()
|
||||
|
||||
this.dResize({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
dirs: this.dirs,
|
||||
})
|
||||
},
|
||||
// this.dResize({
|
||||
// x: e.pageX,
|
||||
// y: e.pageY,
|
||||
// dirs: this.dirs,
|
||||
// })
|
||||
// },
|
||||
|
||||
handlemouseup() {
|
||||
document.removeEventListener('mousemove', this.handlemousemove, true)
|
||||
document.removeEventListener('mouseup', this.handlemouseup, true)
|
||||
this.stopDResize()
|
||||
},
|
||||
},
|
||||
}
|
||||
// handlemouseup() {
|
||||
// document.removeEventListener('mousemove', this.handlemousemove, true)
|
||||
// document.removeEventListener('mouseup', this.handlemouseup, true)
|
||||
// this.stopDResize()
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
/*
|
||||
#size-control {
|
||||
position: absolute;
|
||||
.square {
|
||||
@ -178,4 +180,5 @@ export default {
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
*/
|
||||
</style>
|
||||
|
@ -1,396 +0,0 @@
|
||||
<template>
|
||||
<div id="zoom-control">
|
||||
<ul v-show="show" class="zoom-selecter">
|
||||
<li v-for="(item, index) in zoomList" :key="index" :class="['zoom-item', { 'zoom-item-active': activezoomIndex === index }]" @click.stop="selectItem(index)">
|
||||
<!-- <i v-if="item.icon" :class="['iconfont', item.icon]"></i> -->
|
||||
<span>{{ item.text }}</span>
|
||||
<i v-if="activezoomIndex === index" class="iconfont icon-selected"></i>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="!hideControl" class="zoom-control-wrap">
|
||||
<div :class="['zoom-icon radius-left', { disable: activezoomIndex === 0 }]" @click.stop="activezoomIndex > 0 ? sub() : ''">
|
||||
<i class="iconfont icon-sub"></i>
|
||||
</div>
|
||||
<div :class="['zoom-text', { 'zoom-text-active': show }]" @click.stop="show = !show">{{ zoom.text }}</div>
|
||||
<div :class="['zoom-icon radius-right', { disable: otherIndex === otherList.length - 1 }]" @click.stop="otherIndex < otherList.length - 1 ? add() : ''">
|
||||
<i class="iconfont icon-add"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import addMouseWheel from '@/common/methods/addMouseWheel'
|
||||
|
||||
// 组件大小控制器
|
||||
const NAME = 'zoom-control'
|
||||
let holder = null
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
data() {
|
||||
return {
|
||||
hideControl: false,
|
||||
activezoomIndex: 0,
|
||||
zoomList: [
|
||||
{
|
||||
text: '25%',
|
||||
value: 25,
|
||||
},
|
||||
{
|
||||
text: '50%',
|
||||
value: 50,
|
||||
},
|
||||
{
|
||||
text: '75%',
|
||||
value: 75,
|
||||
},
|
||||
{
|
||||
text: '100%',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
text: '125%',
|
||||
value: 125,
|
||||
},
|
||||
{
|
||||
text: '150%',
|
||||
value: 150,
|
||||
},
|
||||
{
|
||||
text: '200%',
|
||||
value: 200,
|
||||
},
|
||||
{
|
||||
text: '最佳尺寸',
|
||||
value: -1,
|
||||
// icon: 'icon-best-size',
|
||||
},
|
||||
],
|
||||
show: false,
|
||||
zoom: {
|
||||
value: 0,
|
||||
text: 0,
|
||||
},
|
||||
otherList: [
|
||||
{
|
||||
text: '250%',
|
||||
value: 250,
|
||||
},
|
||||
{
|
||||
text: '300%',
|
||||
value: 300,
|
||||
},
|
||||
{
|
||||
text: '350%',
|
||||
value: 350,
|
||||
},
|
||||
{
|
||||
text: '400%',
|
||||
value: 400,
|
||||
},
|
||||
{
|
||||
text: '450%',
|
||||
value: 450,
|
||||
},
|
||||
{
|
||||
text: '500%',
|
||||
value: 500,
|
||||
},
|
||||
],
|
||||
otherIndex: -1,
|
||||
bestZoom: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dPage', 'dScreen', 'zoomScreenChange', 'dZoom']),
|
||||
},
|
||||
watch: {
|
||||
activezoomIndex(value) {
|
||||
if (value < 0 || value > this.zoomList.length - 1) {
|
||||
return
|
||||
}
|
||||
this.zoom = JSON.parse(JSON.stringify(this.zoomList[value]))
|
||||
},
|
||||
otherIndex(value) {
|
||||
if (value < 0 || value > this.otherList.length - 1) {
|
||||
return
|
||||
}
|
||||
this.zoom = JSON.parse(JSON.stringify(this.otherList[value]))
|
||||
},
|
||||
zoom(value) {
|
||||
let realValue = value.value
|
||||
if (realValue === -1) {
|
||||
realValue = this.calcZoom()
|
||||
}
|
||||
this.updateZoom(realValue)
|
||||
this.autoFixTop()
|
||||
},
|
||||
dScreen: {
|
||||
handler() {
|
||||
this.screenChange()
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
zoomScreenChange() {
|
||||
this.activezoomIndex = this.zoomList.length - 1
|
||||
this.screenChange()
|
||||
},
|
||||
dPage: {
|
||||
handler(val) {
|
||||
this.screenChange()
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.$nextTick()
|
||||
window.addEventListener('click', this.close)
|
||||
if (this.$route.path === '/draw') {
|
||||
this.activezoomIndex = 3
|
||||
this.hideControl = true
|
||||
} else {
|
||||
this.activezoomIndex = this.zoomList.length - 1
|
||||
}
|
||||
// 添加滚轮监听
|
||||
addMouseWheel('page-design', (isDown) => {
|
||||
this.mousewheelZoom(isDown)
|
||||
})
|
||||
// 添加窗口大小监听
|
||||
window.addEventListener('resize', (event) => {
|
||||
this.changeScreen()
|
||||
})
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('click', this.close)
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateZoom', 'updateScreen']),
|
||||
changeScreen() {
|
||||
clearTimeout(holder)
|
||||
holder = setTimeout(() => {
|
||||
const screen = document.getElementById('page-design')
|
||||
this.updateScreen({
|
||||
width: screen.offsetWidth,
|
||||
height: screen.offsetHeight,
|
||||
})
|
||||
}, 300)
|
||||
},
|
||||
screenChange() {
|
||||
// 弹性尺寸即时修改
|
||||
if (this.activezoomIndex === this.zoomList.length - 1) {
|
||||
this.updateZoom(this.calcZoom())
|
||||
this.autoFixTop()
|
||||
}
|
||||
},
|
||||
selectItem(index) {
|
||||
this.activezoomIndex = index
|
||||
this.otherIndex = -1
|
||||
this.show = false
|
||||
},
|
||||
close(e) {
|
||||
this.show = false
|
||||
},
|
||||
add() {
|
||||
this.curAction = 'add'
|
||||
this.show = false
|
||||
if (this.activezoomIndex === this.zoomList.length - 2 || this.activezoomIndex === this.zoomList.length - 1) {
|
||||
this.activezoomIndex = this.zoomList.length
|
||||
// this.otherIndex += 1
|
||||
if (this.bestZoom) {
|
||||
this.nearZoom(true)
|
||||
} else {
|
||||
this.otherIndex += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
if (this.activezoomIndex != this.zoomList.length) {
|
||||
this.activezoomIndex++
|
||||
return
|
||||
}
|
||||
if (this.otherIndex < this.otherList.length - 1) {
|
||||
this.otherIndex++
|
||||
}
|
||||
},
|
||||
sub() {
|
||||
this.curAction = null
|
||||
this.show = false
|
||||
if (this.otherIndex === 0) {
|
||||
this.otherIndex = -1
|
||||
this.activezoomIndex = this.zoomList.length - 2
|
||||
return
|
||||
}
|
||||
if (this.otherIndex != -1) {
|
||||
this.otherIndex--
|
||||
return
|
||||
}
|
||||
if (this.activezoomIndex === this.zoomList.length - 1) {
|
||||
if (this.bestZoom) {
|
||||
this.nearZoom()
|
||||
} else {
|
||||
this.activezoomIndex = this.zoomList.length - 2
|
||||
}
|
||||
return
|
||||
}
|
||||
if (this.activezoomIndex != 0) {
|
||||
this.activezoomIndex--
|
||||
}
|
||||
},
|
||||
mousewheelZoom(down) {
|
||||
const value = Number(this.dZoom.toFixed(0))
|
||||
if (down && value <= 1) return
|
||||
this.updateZoom(down ? value - 1 : value + 1)
|
||||
this.zoom.text = value + '%'
|
||||
this.autoFixTop()
|
||||
},
|
||||
nearZoom(add) {
|
||||
for (let i = 0; i < this.zoomList.length; i++) {
|
||||
this.activezoomIndex = i
|
||||
if (this.zoomList[i].value > this.bestZoom) {
|
||||
if (add) break
|
||||
} else if (this.zoomList[i].value < this.bestZoom) {
|
||||
if (!add) break
|
||||
}
|
||||
}
|
||||
this.bestZoom = 0
|
||||
},
|
||||
calcZoom() {
|
||||
let widthZoom = ((this.dScreen.width - 142) * 100) / this.dPage.width
|
||||
let heightZoom = ((this.dScreen.height - 122) * 100) / this.dPage.height
|
||||
|
||||
this.bestZoom = Math.min(widthZoom, heightZoom)
|
||||
return this.bestZoom
|
||||
},
|
||||
async autoFixTop() {
|
||||
await this.$nextTick()
|
||||
const presetPadding = 60
|
||||
const el = document.getElementById('out-page')
|
||||
// const clientHeight = document.body.clientHeight - 54
|
||||
const parentHeight = el.offsetParent.offsetHeight - 54
|
||||
let padding = (parentHeight - el.offsetHeight) / 2
|
||||
if (typeof this.curAction === 'undefined') {
|
||||
padding += presetPadding / 2
|
||||
}
|
||||
this.curAction === 'add' && (padding -= presetPadding)
|
||||
this.$store.commit('updatePaddingTop', padding > 0 ? padding : 0)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@color-select: #1b1634;
|
||||
@color1: #ffffff; // 选项板背景
|
||||
@color2: #ffffff; // Appears 3 times
|
||||
@color3: #666666; // 文字主颜色
|
||||
@color4: #c2c2c2; // 禁用
|
||||
@color5: rgba(0, 0, 0, 0.12); // 高亮选项背景
|
||||
@z-border-color: #e6e6e6;
|
||||
|
||||
#zoom-control {
|
||||
bottom: 20px;
|
||||
position: absolute;
|
||||
right: 302px;
|
||||
z-index: 1000;
|
||||
.zoom-control-wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
.radius-left {
|
||||
border-bottom-left-radius: 50%;
|
||||
border-top-left-radius: 50%;
|
||||
border-block-end: 1px solid @z-border-color;
|
||||
border-block-start: 1px solid @z-border-color;
|
||||
}
|
||||
.radius-right {
|
||||
border-bottom-right-radius: 50%;
|
||||
border-top-right-radius: 50%;
|
||||
border-block-end: 1px solid @z-border-color;
|
||||
border-block-start: 1px solid @z-border-color;
|
||||
}
|
||||
.zoom-icon {
|
||||
align-items: center;
|
||||
background-color: @color2;
|
||||
color: @color3;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
&:hover {
|
||||
background-color: @color1;
|
||||
color: @color-select;
|
||||
}
|
||||
}
|
||||
.disable {
|
||||
color: @color4;
|
||||
&:hover {
|
||||
background-color: @color2;
|
||||
color: @color4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.zoom-text {
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
background-color: @color2;
|
||||
color: @color3;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
border-block-end: 1px solid @z-border-color;
|
||||
border-block-start: 1px solid @z-border-color;
|
||||
&:hover {
|
||||
background-color: @color1;
|
||||
color: @color-select;
|
||||
}
|
||||
}
|
||||
}
|
||||
.zoom-selecter {
|
||||
background-color: @color1;
|
||||
color: @color3;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
transform: translateY(-100%);
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
&:after {
|
||||
bottom: -8px;
|
||||
content: '';
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.zoom-item {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 34px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
i {
|
||||
margin-right: 10px;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
&:hover {
|
||||
background-color: @color5;
|
||||
color: @color-select;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// #zoom-control-active {
|
||||
// background-color: @color1;
|
||||
// background-color: @color5;
|
||||
// color: @color-select;
|
||||
// color: @color-select;
|
||||
// }
|
||||
</style>
|
69
src/components/modules/layout/zoomControl/data.ts
Normal file
69
src/components/modules/layout/zoomControl/data.ts
Normal file
@ -0,0 +1,69 @@
|
||||
|
||||
export type TZoomData = {
|
||||
text: string
|
||||
value: number
|
||||
}
|
||||
|
||||
export const ZoomList: TZoomData[] = [
|
||||
{
|
||||
text: '25%',
|
||||
value: 25,
|
||||
},
|
||||
{
|
||||
text: '50%',
|
||||
value: 50,
|
||||
},
|
||||
{
|
||||
text: '75%',
|
||||
value: 75,
|
||||
},
|
||||
{
|
||||
text: '100%',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
text: '125%',
|
||||
value: 125,
|
||||
},
|
||||
{
|
||||
text: '150%',
|
||||
value: 150,
|
||||
},
|
||||
{
|
||||
text: '200%',
|
||||
value: 200,
|
||||
},
|
||||
{
|
||||
text: '最佳尺寸',
|
||||
value: -1,
|
||||
// icon: 'icon-best-size',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
export const OtherList: TZoomData[] = [
|
||||
{
|
||||
text: '250%',
|
||||
value: 250,
|
||||
},
|
||||
{
|
||||
text: '300%',
|
||||
value: 300,
|
||||
},
|
||||
{
|
||||
text: '350%',
|
||||
value: 350,
|
||||
},
|
||||
{
|
||||
text: '400%',
|
||||
value: 400,
|
||||
},
|
||||
{
|
||||
text: '450%',
|
||||
value: 450,
|
||||
},
|
||||
{
|
||||
text: '500%',
|
||||
value: 500,
|
||||
},
|
||||
]
|
382
src/components/modules/layout/zoomControl/index.vue
Normal file
382
src/components/modules/layout/zoomControl/index.vue
Normal file
@ -0,0 +1,382 @@
|
||||
<template>
|
||||
<div id="zoom-control">
|
||||
<ul v-show="show" class="zoom-selecter">
|
||||
<li v-for="(item, index) in zoomList" :key="index" :class="['zoom-item', { 'zoom-item-active': activezoomIndex === index }]" @click.stop="selectItem(index)">
|
||||
<!-- <i v-if="item.icon" :class="['iconfont', item.icon]"></i> -->
|
||||
<span>{{ item.text }}</span>
|
||||
<i v-if="activezoomIndex === index" class="iconfont icon-selected"></i>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="!hideControl" class="zoom-control-wrap">
|
||||
<div :class="['zoom-icon radius-left', { disable: activezoomIndex === 0 }]" @click.stop="activezoomIndex > 0 ? sub() : ''">
|
||||
<i class="iconfont icon-sub"></i>
|
||||
</div>
|
||||
<div :class="['zoom-text', { 'zoom-text-active': show }]" @click.stop="show = !show">{{ zoom.text }}</div>
|
||||
<div :class="['zoom-icon radius-right', { disable: otherIndex === otherList.length - 1 }]" @click.stop="otherIndex < otherList.length - 1 ? add() : ''">
|
||||
<i class="iconfont icon-add"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useStore } from 'vuex'
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import addMouseWheel from '@/common/methods/addMouseWheel'
|
||||
import { OtherList, TZoomData, ZoomList } from './data';
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
// 组件大小控制器
|
||||
let holder: number | undefined
|
||||
|
||||
const hideControl = ref(false)
|
||||
const activezoomIndex = ref(0)
|
||||
const zoomList = ref<TZoomData[]>(ZoomList)
|
||||
const show = ref(false)
|
||||
const zoom = ref<TZoomData>({
|
||||
value: 0,
|
||||
text: '',
|
||||
})
|
||||
const otherList = ref<TZoomData[]>(OtherList)
|
||||
const otherIndex = ref(-1)
|
||||
const bestZoom = ref(0)
|
||||
const curAction = ref('')
|
||||
|
||||
const { dPage, dScreen, zoomScreenChange, dZoom } = useSetupMapGetters(['dPage', 'dScreen', 'zoomScreenChange', 'dZoom'])
|
||||
|
||||
watch(
|
||||
activezoomIndex,
|
||||
(data) => {
|
||||
if (data < 0 || data > zoomList.value.length - 1) {
|
||||
return
|
||||
}
|
||||
zoom.value = JSON.parse(JSON.stringify(zoomList.value[data]))
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
otherIndex,
|
||||
(data) => {
|
||||
if (data < 0 || data > otherList.value.length - 1) {
|
||||
return
|
||||
}
|
||||
zoom.value = JSON.parse(JSON.stringify(otherList.value[data]))
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
zoom,
|
||||
(data) => {
|
||||
let realValue = data.value
|
||||
if (realValue === -1) {
|
||||
realValue = calcZoom()
|
||||
}
|
||||
store.dispatch('updateZoom', realValue)
|
||||
// updateZoom(realValue)
|
||||
autoFixTop()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
dScreen,
|
||||
() => {
|
||||
screenChange()
|
||||
},
|
||||
{ deep: true, }
|
||||
)
|
||||
|
||||
watch(
|
||||
zoomScreenChange,
|
||||
() => {
|
||||
activezoomIndex.value = zoomList.value.length - 1
|
||||
screenChange()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
dPage,
|
||||
() => {
|
||||
screenChange()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
window.addEventListener('click', close)
|
||||
if (route.path === '/draw') {
|
||||
activezoomIndex.value = 3
|
||||
hideControl.value = true
|
||||
} else {
|
||||
activezoomIndex.value = zoomList.value.length - 1
|
||||
}
|
||||
// 添加滚轮监听
|
||||
addMouseWheel('page-design', (isDown: boolean) => {
|
||||
mousewheelZoom(isDown)
|
||||
})
|
||||
// 添加窗口大小监听
|
||||
window.addEventListener('resize', (event) => {
|
||||
changeScreen()
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', close)
|
||||
})
|
||||
|
||||
// ...mapActions(['updateZoom', 'updateScreen']),
|
||||
function changeScreen() {
|
||||
clearTimeout(holder)
|
||||
holder = setTimeout(() => {
|
||||
const screen = document.getElementById('page-design')
|
||||
if (!screen) return
|
||||
store.dispatch('updateScreen', {
|
||||
width: screen.offsetWidth,
|
||||
height: screen.offsetHeight,
|
||||
})
|
||||
// updateScreen({
|
||||
// width: screen.offsetWidth,
|
||||
// height: screen.offsetHeight,
|
||||
// })
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function screenChange() {
|
||||
// 弹性尺寸即时修改
|
||||
if (activezoomIndex.value === zoomList.value.length - 1) {
|
||||
store.dispatch('updateZoom', calcZoom())
|
||||
// this.updateZoom(this.calcZoom())
|
||||
autoFixTop()
|
||||
}
|
||||
}
|
||||
|
||||
function selectItem(index: number) {
|
||||
activezoomIndex.value = index
|
||||
otherIndex.value = -1
|
||||
show.value = false
|
||||
}
|
||||
|
||||
function close(_: MouseEvent) {
|
||||
show.value = false
|
||||
}
|
||||
|
||||
function add() {
|
||||
curAction.value = 'add'
|
||||
show.value = false
|
||||
if (
|
||||
activezoomIndex.value === zoomList.value.length - 2 ||
|
||||
activezoomIndex.value === zoomList.value.length - 1
|
||||
) {
|
||||
activezoomIndex.value = zoomList.value.length
|
||||
// this.otherIndex += 1
|
||||
if (bestZoom.value) {
|
||||
nearZoom(true)
|
||||
} else {
|
||||
otherIndex.value += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
if (activezoomIndex.value != zoomList.value.length) {
|
||||
activezoomIndex.value++
|
||||
return
|
||||
}
|
||||
if (otherIndex.value < otherList.value.length - 1) {
|
||||
otherIndex.value++
|
||||
}
|
||||
}
|
||||
|
||||
function sub() {
|
||||
curAction.value = ''
|
||||
show.value = false
|
||||
if (otherIndex.value === 0) {
|
||||
otherIndex.value = -1
|
||||
activezoomIndex.value = zoomList.value.length - 2
|
||||
return
|
||||
}
|
||||
if (otherIndex.value != -1) {
|
||||
otherIndex.value--
|
||||
return
|
||||
}
|
||||
if (activezoomIndex.value === zoomList.value.length - 1) {
|
||||
if (bestZoom) {
|
||||
nearZoom()
|
||||
} else {
|
||||
activezoomIndex.value = zoomList.value.length - 2
|
||||
}
|
||||
return
|
||||
}
|
||||
if (activezoomIndex.value != 0) {
|
||||
activezoomIndex.value--
|
||||
}
|
||||
}
|
||||
|
||||
function mousewheelZoom(down: boolean) {
|
||||
const value = Number(dZoom.value.toFixed(0))
|
||||
if (down && value <= 1) return
|
||||
store.dispatch('updateZoom', down ? value - 1 : value + 1)
|
||||
// updateZoom(down ? value - 1 : value + 1)
|
||||
zoom.value.text = (value + '%') as any
|
||||
autoFixTop()
|
||||
}
|
||||
|
||||
function nearZoom(add?: boolean) {
|
||||
for (let i = 0; i < zoomList.value.length; i++) {
|
||||
activezoomIndex.value = i
|
||||
if (zoomList.value[i].value > bestZoom.value) {
|
||||
if (add) break
|
||||
} else if (zoomList.value[i].value < bestZoom.value) {
|
||||
if (!add) break
|
||||
}
|
||||
}
|
||||
bestZoom.value = 0
|
||||
}
|
||||
|
||||
function calcZoom() {
|
||||
let widthZoom = ((dScreen.value.width - 142) * 100) / dPage.value.width
|
||||
let heightZoom = ((dScreen.value.height - 122) * 100) / dPage.value.height
|
||||
|
||||
bestZoom.value = Math.min(widthZoom, heightZoom)
|
||||
return bestZoom.value
|
||||
}
|
||||
|
||||
async function autoFixTop() {
|
||||
await nextTick()
|
||||
const presetPadding = 60
|
||||
const el = document.getElementById('out-page')
|
||||
if (!el) return
|
||||
// const clientHeight = document.body.clientHeight - 54
|
||||
|
||||
const parentHeight = (el.offsetParent as HTMLElement).offsetHeight - 54
|
||||
let padding = (parentHeight - el.offsetHeight) / 2
|
||||
if (typeof curAction.value === 'undefined') {
|
||||
padding += presetPadding / 2
|
||||
}
|
||||
curAction.value === 'add' && (padding -= presetPadding)
|
||||
store.commit('updatePaddingTop', padding > 0 ? padding : 0)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
screenChange
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@color-select: #1b1634;
|
||||
@color1: #ffffff; // 选项板背景
|
||||
@color2: #ffffff; // Appears 3 times
|
||||
@color3: #666666; // 文字主颜色
|
||||
@color4: #c2c2c2; // 禁用
|
||||
@color5: rgba(0, 0, 0, 0.12); // 高亮选项背景
|
||||
@z-border-color: #e6e6e6;
|
||||
|
||||
#zoom-control {
|
||||
bottom: 20px;
|
||||
position: absolute;
|
||||
right: 302px;
|
||||
z-index: 1000;
|
||||
.zoom-control-wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
.radius-left {
|
||||
border-bottom-left-radius: 50%;
|
||||
border-top-left-radius: 50%;
|
||||
border-block-end: 1px solid @z-border-color;
|
||||
border-block-start: 1px solid @z-border-color;
|
||||
}
|
||||
.radius-right {
|
||||
border-bottom-right-radius: 50%;
|
||||
border-top-right-radius: 50%;
|
||||
border-block-end: 1px solid @z-border-color;
|
||||
border-block-start: 1px solid @z-border-color;
|
||||
}
|
||||
.zoom-icon {
|
||||
align-items: center;
|
||||
background-color: @color2;
|
||||
color: @color3;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
&:hover {
|
||||
background-color: @color1;
|
||||
color: @color-select;
|
||||
}
|
||||
}
|
||||
.disable {
|
||||
color: @color4;
|
||||
&:hover {
|
||||
background-color: @color2;
|
||||
color: @color4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.zoom-text {
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
background-color: @color2;
|
||||
color: @color3;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
border-block-end: 1px solid @z-border-color;
|
||||
border-block-start: 1px solid @z-border-color;
|
||||
&:hover {
|
||||
background-color: @color1;
|
||||
color: @color-select;
|
||||
}
|
||||
}
|
||||
}
|
||||
.zoom-selecter {
|
||||
background-color: @color1;
|
||||
color: @color3;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
transform: translateY(-100%);
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
&:after {
|
||||
bottom: -8px;
|
||||
content: '';
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.zoom-item {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 34px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
i {
|
||||
margin-right: 10px;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
&:hover {
|
||||
background-color: @color5;
|
||||
color: @color-select;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// #zoom-control-active {
|
||||
// background-color: @color1;
|
||||
// background-color: @color5;
|
||||
// color: @color-select;
|
||||
// color: @color-select;
|
||||
// }
|
||||
</style>
|
@ -6,8 +6,8 @@
|
||||
</div>
|
||||
<div v-show="activeTab === 0" class="style-wrap">
|
||||
<div v-show="showGroupCombined" style="padding: 2rem 0">
|
||||
<el-button plain type="primary" class="gounp__btn" @click="realCombined">成组</el-button>
|
||||
<icon-item-select label="" :data="alignIconList" @finish="alignAction" />
|
||||
<el-button plain type="primary" class="gounp__btn" @click="handleCombine">成组</el-button>
|
||||
<icon-item-select label="" :data="iconList" @finish="alignAction" />
|
||||
</div>
|
||||
<component :is="dActiveElement.type + '-style'" v-show="!showGroupCombined" v-if="dActiveElement.type" />
|
||||
</div>
|
||||
@ -17,58 +17,64 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
// 样式设置面板
|
||||
const NAME = 'style-panel'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import alignIconList from '@/assets/data/AlignListData'
|
||||
// const NAME = 'style-panel'
|
||||
import { useStore } from 'vuex'
|
||||
import alignIconList, { AlignListData } from '@/assets/data/AlignListData'
|
||||
import iconItemSelect from '../settings/iconItemSelect.vue'
|
||||
import { ref, watch } from 'vue';
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters';
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
components: { iconItemSelect },
|
||||
data() {
|
||||
return {
|
||||
activeTab: 0,
|
||||
alignIconList,
|
||||
showGroupCombined: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dActiveElement', 'dWidgets', 'dSelectWidgets']),
|
||||
},
|
||||
watch: {
|
||||
dSelectWidgets: {
|
||||
handler(items) {
|
||||
const store = useStore();
|
||||
|
||||
const activeTab = ref(0)
|
||||
const iconList = ref<AlignListData[]>(alignIconList)
|
||||
const showGroupCombined = ref(false)
|
||||
|
||||
const { dActiveElement, dWidgets, dSelectWidgets } = useSetupMapGetters(['dActiveElement', 'dWidgets', 'dSelectWidgets'])
|
||||
|
||||
watch(
|
||||
dSelectWidgets,
|
||||
(items) => {
|
||||
setTimeout(() => {
|
||||
this.showGroupCombined = items.length > 1
|
||||
showGroupCombined.value = items.length > 1
|
||||
}, 100)
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['selectWidget', 'updateAlign', 'updateHoverUuid', 'getCombined', 'realCombined', 'ungroup', 'pushHistory']),
|
||||
alignAction(item) {
|
||||
const sWidgets = JSON.parse(JSON.stringify(this.dSelectWidgets))
|
||||
this.getCombined().then((group) => {
|
||||
for (let i = 0; i < sWidgets.length; i++) {
|
||||
const element = sWidgets[i]
|
||||
this.updateAlign({
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
function handleCombine() {
|
||||
store.dispatch('realCombined')
|
||||
}
|
||||
|
||||
// ...mapActions(['selectWidget', 'updateAlign', 'updateHoverUuid', 'getCombined', 'realCombined', 'ungroup', 'pushHistory']),
|
||||
function alignAction(item: AlignListData) {
|
||||
const sWidgets = JSON.parse(JSON.stringify(dSelectWidgets.value))
|
||||
store.dispatch('getCombined').then((group) => {
|
||||
sWidgets.forEach((element: Record<string, any>) => {
|
||||
store.dispatch('updateAlign', {
|
||||
align: item.value,
|
||||
uuid: element.uuid,
|
||||
group,
|
||||
})
|
||||
}
|
||||
this.pushHistory()
|
||||
// updateAlign({
|
||||
// align: item.value,
|
||||
// uuid: element.uuid,
|
||||
// group,
|
||||
// })
|
||||
});
|
||||
store.dispatch('pushHistory')
|
||||
// pushHistory()
|
||||
})
|
||||
},
|
||||
layerChange(newLayer) {
|
||||
this.$store.commit('setDWidgets', newLayer.reverse())
|
||||
this.$store.commit('setShowMoveable', false)
|
||||
},
|
||||
},
|
||||
}
|
||||
function layerChange(newLayer: Record<string, any>[]) {
|
||||
store.commit('setDWidgets', newLayer.toReversed())
|
||||
store.commit('setShowMoveable', false)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
24
src/components/modules/panel/types/wrap.d.ts
vendored
Normal file
24
src/components/modules/panel/types/wrap.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
type TCommonImgListData = {
|
||||
isDelect: boolean
|
||||
cover: string
|
||||
fail: boolean
|
||||
top: number
|
||||
left: number
|
||||
width: number
|
||||
height: number
|
||||
title: string
|
||||
}
|
||||
|
||||
type TCommonPhotoListData = {
|
||||
listWidth: number
|
||||
gap: number
|
||||
thumb?: string
|
||||
url: string
|
||||
color: string
|
||||
isDelect: boolean
|
||||
width: number
|
||||
height: number
|
||||
model: string
|
||||
}
|
@ -2,87 +2,81 @@
|
||||
<div id="widget-panel">
|
||||
<div class="widget-classify">
|
||||
<ul class="classify-wrap">
|
||||
<li v-for="(item, index) in widgetClassifyList" :key="index" :class="['classify-item', { 'active-classify-item': activeWidgetClassify === index }]" @click="clickClassify(index)">
|
||||
<li v-for="(item, index) in state.widgetClassifyList" :key="index" :class="['classify-item', { 'active-classify-item': state.activeWidgetClassify === index }]" @click="clickClassify(index)">
|
||||
<div class="icon-box"><i :class="['iconfont', 'icon', item.icon]" :style="item.style" /></div>
|
||||
<p>{{ item.name }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
<a href="https://github.com/palxiao/poster-design" target="_blank" class="github"><img src="https://fe-doc.palxp.cn/images/github.svg" alt="Github" title="Github" /> 源码</a>
|
||||
</div>
|
||||
<div v-show="active" class="widget-wrap">
|
||||
<div v-show="state.active" class="widget-wrap">
|
||||
<keep-alive>
|
||||
<component :is="widgetClassifyList[activeWidgetClassify].component" />
|
||||
<component :is="state.widgetClassifyList[state.activeWidgetClassify].component" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
<!-- <div v-show="active" class="side-wrap"><div class="pack__up" @click="active = false"><</div></div> -->
|
||||
<div v-show="active" class="side-wrap">
|
||||
<div v-show="state.active" class="side-wrap">
|
||||
<!-- <el-tooltip effect="dark" content="收起侧边栏" placement="right"> -->
|
||||
<div class="pack__up" @click="active = false"></div>
|
||||
<div class="pack__up" @click="state.active = false"></div>
|
||||
<!-- </el-tooltip> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
// 组件面板
|
||||
const NAME = 'widget-panel'
|
||||
import widgetClassifyListData from '@/assets/data/WidgetClassifyList.ts'
|
||||
import { reactive, toRefs, onMounted, watch, nextTick, getCurrentInstance, ComponentInternalInstance } from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
// const NAME = 'widget-panel'
|
||||
import widgetClassifyListData from '@/assets/data/WidgetClassifyList'
|
||||
import { reactive, onMounted, watch, nextTick, } from 'vue'
|
||||
// import { useStore } from 'vuex'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
setup() {
|
||||
// const store = useStore()
|
||||
const route = useRoute()
|
||||
const state = reactive({
|
||||
// const store = useStore()
|
||||
const route = useRoute()
|
||||
const state = reactive({
|
||||
widgetClassifyList: widgetClassifyListData,
|
||||
activeWidgetClassify: 0,
|
||||
active: true,
|
||||
})
|
||||
const clickClassify = (index: number) => {
|
||||
})
|
||||
const clickClassify = (index: number) => {
|
||||
console.log('index' ,index)
|
||||
state.activeWidgetClassify = index
|
||||
state.active = true
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
const { koutu } = route.query
|
||||
koutu && (state.activeWidgetClassify = 4)
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => state.activeWidgetClassify,
|
||||
(index) => {
|
||||
if (index >= 0 && index < state.widgetClassifyList.length) {
|
||||
state.widgetClassifyList[index].show = true
|
||||
}
|
||||
},
|
||||
)
|
||||
// const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
// watch(
|
||||
// () => state.active,
|
||||
// () => {
|
||||
// let screen = document.getElementById('page-design')
|
||||
// nextTick(() => {
|
||||
// proxy?.updateScreen({
|
||||
// width: screen.offsetWidth,
|
||||
// height: screen.offsetHeight,
|
||||
// })
|
||||
// })
|
||||
// },
|
||||
// )
|
||||
)
|
||||
|
||||
return {
|
||||
clickClassify,
|
||||
...toRefs(state),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateScreen']),
|
||||
},
|
||||
}
|
||||
// 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>
|
||||
|
@ -8,42 +8,64 @@
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<div class="color__box" :style="modelStyle.color">
|
||||
<div v-for="c in colors" :key="c" :style="{ background: c }" class="color__item" @click="setBGcolor(c)"></div>
|
||||
<div v-for="c in state.colors" :key="c" :style="{ background: c }" class="color__item" @click="setBGcolor(c)"></div>
|
||||
</div>
|
||||
<ul v-if="showList" v-infinite-scroll="loadData" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
|
||||
<ul v-if="state.showList" v-infinite-scroll="loadData" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
|
||||
<div class="list" :style="modelStyle.list">
|
||||
<imageTip v-for="(item, i) in bgList" :key="i + 'i'" :detail="item">
|
||||
<imageTip v-for="(item, i) in state.bgList" :key="i + 'i'" :detail="item">
|
||||
<el-image class="list__img" :src="item.thumb" fit="cover" lazy loading="lazy" @click.stop="selectItem(item)" @dragstart="dragStart($event, item)"></el-image>
|
||||
</imageTip>
|
||||
</div>
|
||||
<div v-show="loading" class="loading"><i class="el-icon-loading"></i> 拼命加载中</div>
|
||||
<div v-show="loadDone" class="loading">全部加载完毕</div>
|
||||
<div v-show="state.loading" class="loading"><i class="el-icon-loading"></i> 拼命加载中</div>
|
||||
<div v-show="state.loadDone" class="loading">全部加载完毕</div>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, computed } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, computed, defineProps, defineExpose } from 'vue'
|
||||
import api from '@/api'
|
||||
import { mapActions, useStore } from 'vuex'
|
||||
import { useStore } from 'vuex'
|
||||
import { ElImage } from 'element-plus'
|
||||
import { TGetImageListResult } from '@/api/material';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
model: {
|
||||
default: 'widgetPanel'
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const state = reactive({
|
||||
type TCommonPanelData = {
|
||||
color: string
|
||||
list: string
|
||||
}
|
||||
|
||||
type TTmpModalData = {
|
||||
widgetPanel: TCommonPanelData
|
||||
stylePanel: TCommonPanelData
|
||||
}
|
||||
|
||||
type TProps = {
|
||||
model: 'widgetPanel'
|
||||
}
|
||||
|
||||
type TState = {
|
||||
loading: boolean
|
||||
loadDone: boolean
|
||||
bgList: TGetImageListResult[]
|
||||
showList: boolean
|
||||
colors: string[]
|
||||
|
||||
}
|
||||
|
||||
const { model } = defineProps<TProps>()
|
||||
|
||||
|
||||
const store = useStore()
|
||||
const state = reactive<TState>({
|
||||
loading: false,
|
||||
loadDone: false,
|
||||
bgList: [],
|
||||
showList: true,
|
||||
colors: ['#000000ff', '#999999ff', '#CCCCCCff', '#FFFFFFff', '#E65353ff', '#FFD835ff', '#70BC59ff', '#607AF4ff', '#976BEEff'],
|
||||
})
|
||||
// 临时用于改变样式
|
||||
const models: any = {
|
||||
})
|
||||
|
||||
// 临时用于改变样式
|
||||
const models: TTmpModalData = {
|
||||
widgetPanel: {
|
||||
color: 'padding: 1.2rem 1rem',
|
||||
list: 'grid-template-columns: auto auto auto;padding: 0 1rem;',
|
||||
@ -52,19 +74,20 @@ export default defineComponent({
|
||||
color: 'padding: 1.2rem 0;',
|
||||
list: 'grid-template-columns: repeat(3, 76px);',
|
||||
}
|
||||
}
|
||||
const modelStyle = computed(() => models[props.model])
|
||||
}
|
||||
|
||||
const pageOptions = { page: 0, pageSize: 20 }
|
||||
const modelStyle = computed(() => models[model])
|
||||
|
||||
const loadData = () => {
|
||||
const pageOptions = { page: 0, pageSize: 20 }
|
||||
|
||||
const loadData = () => {
|
||||
if (state.loading) {
|
||||
return
|
||||
}
|
||||
load()
|
||||
}
|
||||
}
|
||||
|
||||
const load = async (init: boolean = false) => {
|
||||
const load = async (init: boolean = false) => {
|
||||
if (state.loadDone) {
|
||||
return
|
||||
}
|
||||
@ -76,7 +99,7 @@ export default defineComponent({
|
||||
pageOptions.page = 1
|
||||
}
|
||||
|
||||
await api.material.getImagesList({ cate: 16, page: pageOptions.page }).then(({ list }: any) => {
|
||||
await api.material.getImagesList({ cate: 16, page: pageOptions.page }).then(({ list }) => {
|
||||
if (list.length > 0) {
|
||||
state.bgList.push(...list)
|
||||
} else {
|
||||
@ -87,9 +110,9 @@ export default defineComponent({
|
||||
setTimeout(() => {
|
||||
state.loading = false
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
function setBGcolor(color: string) {
|
||||
function setBGcolor(color: string) {
|
||||
store.dispatch('updatePageData', {
|
||||
key: 'backgroundImage',
|
||||
value: '',
|
||||
@ -102,38 +125,49 @@ export default defineComponent({
|
||||
store.dispatch('selectWidget', {
|
||||
uuid: '-1',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
load,
|
||||
setBGcolor,
|
||||
loadData,
|
||||
modelStyle
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['selectWidget', 'updatePageData']),
|
||||
async selectItem(item: any) {
|
||||
// ...mapActions(['selectWidget', 'updatePageData']),
|
||||
async function selectItem(item: TGetImageListResult) {
|
||||
// this.$store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
this.updatePageData({
|
||||
store.dispatch('updatePageData', {
|
||||
key: 'backgroundTransform',
|
||||
value: {},
|
||||
})
|
||||
this.updatePageData({
|
||||
store.dispatch('updatePageData', {
|
||||
key: 'backgroundImage',
|
||||
value: item.url,
|
||||
pushHistory: true,
|
||||
})
|
||||
this.selectWidget({
|
||||
store.dispatch('selectWidget', {
|
||||
uuid: '-1',
|
||||
})
|
||||
},
|
||||
dragStart(e: any, item: any) {
|
||||
this.$store.commit('selectItem', { data: {}, type: 'bg' })
|
||||
},
|
||||
},
|
||||
// this.updatePageData({
|
||||
// key: 'backgroundTransform',
|
||||
// value: {},
|
||||
// })
|
||||
// this.updatePageData({
|
||||
// key: 'backgroundImage',
|
||||
// value: item.url,
|
||||
// pushHistory: true,
|
||||
// })
|
||||
// this.selectWidget({
|
||||
// uuid: '-1',
|
||||
// })
|
||||
}
|
||||
|
||||
function dragStart(_: MouseEvent, _item: TGetImageListResult) {
|
||||
store.commit('selectItem', { data: {}, type: 'bg' })
|
||||
}
|
||||
|
||||
|
||||
defineExpose({
|
||||
load,
|
||||
setBGcolor,
|
||||
loadData,
|
||||
modelStyle
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -212,9 +212,12 @@ export default defineComponent({
|
||||
.list {
|
||||
width: 100%;
|
||||
padding: 3.1rem 0 0 1rem;
|
||||
gap: 0px !important;
|
||||
&__item {
|
||||
overflow: hidden;
|
||||
background: #f8fafc;
|
||||
margin-bottom: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
&__img {
|
||||
cursor: grab;
|
||||
|
@ -2,58 +2,77 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-27 15:16:07
|
||||
* @Description: 模板列表
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-22 09:55:59
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<search-header v-model="searchKeyword" @change="cateChange" />
|
||||
<el-divider v-show="title" style="margin-top: 1.7rem" content-position="left">
|
||||
<span style="font-weight: bold">{{ title }}</span>
|
||||
<search-header v-model="state.searchKeyword" @change="cateChange" />
|
||||
<el-divider v-show="state.title" style="margin-top: 1.7rem" content-position="left">
|
||||
<span style="font-weight: bold">{{ state.title }}</span>
|
||||
</el-divider>
|
||||
|
||||
<ul ref="listRef" v-infinite-scroll="load" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
|
||||
<img-water-fall :listData="list" @select="selectItem" />
|
||||
<div v-show="loading" class="loading"><i class="el-icon-loading"></i> 拼命加载中</div>
|
||||
<div v-show="loadDone" class="loading">全部加载完毕</div>
|
||||
<img-water-fall :listData="state.list" @select="selectItem" />
|
||||
<div v-show="state.loading" class="loading"><i class="el-icon-loading"></i> 拼命加载中</div>
|
||||
<div v-show="state.loadDone" class="loading">全部加载完毕</div>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, onMounted, ref } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, defineExpose } from 'vue'
|
||||
import api from '@/api'
|
||||
import { mapActions, mapGetters, useStore } from 'vuex'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import { LocationQueryValue, useRoute, useRouter } from 'vue-router'
|
||||
// import chooseType from './components/chooseType.vue'
|
||||
// import editModel from './components/editModel.vue'
|
||||
import searchHeader from './components/searchHeader.vue'
|
||||
import useConfirm from '@/common/methods/confirm'
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
|
||||
import imgWaterFall from './components/imgWaterFall.vue'
|
||||
import { IGetTempListData } from '@/api/home'
|
||||
|
||||
export default defineComponent({
|
||||
components: { searchHeader },
|
||||
setup() {
|
||||
const listRef = ref(null)
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
const state: any = reactive({
|
||||
type TState = {
|
||||
loading: boolean
|
||||
loadDone: boolean
|
||||
list: IGetTempListData[]
|
||||
title: string
|
||||
searchKeyword: string
|
||||
}
|
||||
|
||||
type TPageOptions = {
|
||||
page: number,
|
||||
pageSize: number,
|
||||
cate: number | string
|
||||
state?: string
|
||||
}
|
||||
|
||||
const listRef = ref<HTMLElement | null>(null)
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const state = reactive<TState>({
|
||||
loading: false,
|
||||
loadDone: false,
|
||||
list: [],
|
||||
title: '推荐模板',
|
||||
searchKeyword: '',
|
||||
})
|
||||
const pageOptions: any = { page: 0, pageSize: 20, cate: 1 }
|
||||
const { cate, edit } = route.query
|
||||
cate && (pageOptions.cate = cate)
|
||||
edit && store.commit('managerEdit', true)
|
||||
})
|
||||
|
||||
// onMounted(async () => {})
|
||||
const { tempEditing, dHistoryParams } = useSetupMapGetters(['tempEditing', 'dHistoryParams'])
|
||||
|
||||
const load = async (init: boolean = false, stat?: string) => {
|
||||
const pageOptions: TPageOptions = { page: 0, pageSize: 20, cate: 1 }
|
||||
const { cate, edit } = route.query
|
||||
cate && (pageOptions.cate = (cate as LocationQueryValue) || 1)
|
||||
edit && store.commit('managerEdit', true)
|
||||
|
||||
// onMounted(async () => {})
|
||||
|
||||
const load = async (init: boolean = false, stat?: string) => {
|
||||
stat && (pageOptions.state = stat)
|
||||
|
||||
if (init) {
|
||||
if (init && listRef.value) {
|
||||
listRef.value.scrollTop = 0
|
||||
state.list = []
|
||||
pageOptions.page = 0
|
||||
@ -69,50 +88,38 @@ export default defineComponent({
|
||||
const res = await api.home.getTempList({ search: state.searchKeyword, ...pageOptions })
|
||||
res.list.length <= 0 && (state.loadDone = true)
|
||||
state.list = state.list.concat(res.list)
|
||||
|
||||
setTimeout(() => {
|
||||
state.loading = false
|
||||
checkHeight()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
function cateChange(type: any) {
|
||||
function cateChange(type: any) {
|
||||
state.title = type.name
|
||||
const init = pageOptions.cate != type.id
|
||||
pageOptions.cate = type.id
|
||||
load(init, pageOptions.stat)
|
||||
}
|
||||
load(init, pageOptions.state)
|
||||
}
|
||||
|
||||
function checkHeight() {
|
||||
function checkHeight() {
|
||||
if (!listRef.value) return
|
||||
// 检查高度是否占满,否则继续请求下一页
|
||||
const isLess = listRef.value.offsetHeight > listRef.value.firstElementChild.offsetHeight
|
||||
const isLess = listRef.value.offsetHeight > (listRef.value.firstElementChild as HTMLElement)?.offsetHeight
|
||||
isLess && load()
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
load,
|
||||
cateChange,
|
||||
listRef,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['tempEditing', 'dHistoryParams']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['selectWidget', 'updatePageData', 'setTemplate', 'pushHistory']),
|
||||
async selectItem(item: any) {
|
||||
this.$store.commit('setShowMoveable', false) // 清理掉上一次的选择框
|
||||
if (this.dHistoryParams.length > 0) {
|
||||
}
|
||||
// ...mapActions(['selectWidget', 'updatePageData', 'setTemplate', 'pushHistory']),
|
||||
async function selectItem(item: IGetTempListData) {
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择框
|
||||
if (dHistoryParams.value.length > 0) {
|
||||
const isPass = await useConfirm('提示', '使用模板后,当前页面将会被替换,是否继续', 'warning')
|
||||
if (!isPass) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
this.$store.commit('managerEdit', false)
|
||||
this.$store.commit('setDWidgets', [])
|
||||
store.commit('managerEdit', false)
|
||||
store.commit('setDWidgets', [])
|
||||
|
||||
this.setTempId(item.id)
|
||||
setTempId(item.id)
|
||||
|
||||
let result = null
|
||||
if (!item.data) {
|
||||
@ -124,15 +131,19 @@ export default defineComponent({
|
||||
const { page, widgets } = result
|
||||
console.log(widgets)
|
||||
|
||||
this.$store.commit('setDPage', page)
|
||||
this.setTemplate(widgets)
|
||||
store.commit('setDPage', page)
|
||||
store.dispatch('setTemplate', widgets)
|
||||
// setTemplate(widgets)
|
||||
setTimeout(() => {
|
||||
this.$store.commit('zoomScreenChange')
|
||||
store.commit('zoomScreenChange')
|
||||
}, 300)
|
||||
this.selectWidget({
|
||||
uuid: '-1',
|
||||
store.dispatch('selectWidget', {
|
||||
uuid: '-1'
|
||||
})
|
||||
},
|
||||
// selectWidget({
|
||||
// uuid: '-1',
|
||||
// })
|
||||
}
|
||||
// action({ name, value }: any, item: any, index: number) {
|
||||
// switch (name) {
|
||||
// case 'edit':
|
||||
@ -158,12 +169,17 @@ export default defineComponent({
|
||||
// setTempStat({ id }: any, stat: string) {
|
||||
// api.home.setTempStat({ id, stat })
|
||||
// },
|
||||
setTempId(tempId: number | string) {
|
||||
const { id } = this.$route.query
|
||||
this.$router.push({ path: '/home', query: { tempid: tempId, id }, replace: true })
|
||||
},
|
||||
},
|
||||
function setTempId(tempId: number | string) {
|
||||
const { id } = route.query
|
||||
router.push({ path: '/home', query: { tempid: tempId, id }, replace: true })
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
load,
|
||||
cateChange,
|
||||
listRef,
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -22,21 +22,23 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
const NAME = 'text-list-wrap'
|
||||
<script lang="ts" setup>
|
||||
// const NAME = 'text-list-wrap'
|
||||
|
||||
import wText from '../../widgets/wText/wText.vue'
|
||||
|
||||
import { mapActions, useStore } from 'vuex'
|
||||
import { getCurrentInstance, ComponentInternalInstance } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
setup() {
|
||||
const store: any = useStore()
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
type TBasicTextData = {
|
||||
text: string
|
||||
fontSize: number
|
||||
fontWeight: string
|
||||
}
|
||||
|
||||
const selectBasicText = (item: any) => {
|
||||
const store = useStore()
|
||||
|
||||
|
||||
const selectBasicText = (item: TBasicTextData) => {
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
let setting = JSON.parse(JSON.stringify(wText.setting))
|
||||
setting.text = '双击编辑文字' // item.text
|
||||
@ -46,22 +48,16 @@ export default {
|
||||
const { width: pW, height: pH } = store.getters.dPage
|
||||
setting.left = pW / 2 - item.fontSize * 3
|
||||
setting.top = pH / 2 - item.fontSize / 2
|
||||
;(proxy as any).addWidget(setting)
|
||||
}
|
||||
store.dispatch('addWidget', setting)
|
||||
// addWidget(setting)
|
||||
}
|
||||
|
||||
const dragStart = (e: Element, item: any) => {
|
||||
const dragStart = (_: MouseEvent, item: any) => {
|
||||
store.commit('setDraging', true)
|
||||
store.commit('selectItem', { data: { value: item }, type: 'text' })
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selectBasicText,
|
||||
dragStart,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
basicTextList: [
|
||||
const basicTextList: TBasicTextData[] = [
|
||||
// {
|
||||
// text: '大标题',
|
||||
// fontSize: 96,
|
||||
@ -87,13 +83,13 @@ export default {
|
||||
// fontSize: 28,
|
||||
// fontWeight: 'normal',
|
||||
// },
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['addWidget']),
|
||||
},
|
||||
}
|
||||
]
|
||||
defineExpose({
|
||||
selectBasicText,
|
||||
dragStart,
|
||||
})
|
||||
|
||||
// ...mapActions(['addWidget'])
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -17,58 +17,59 @@
|
||||
<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>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
// 图片列表
|
||||
const NAME = 'tool-list-wrap'
|
||||
// const NAME = 'tool-list-wrap'
|
||||
// import api from '@/api'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useRoute } from 'vue-router'
|
||||
import wQrcode from '../../widgets/wQrcode/wQrcode.vue'
|
||||
import imageCutout from '@/components/business/image-cutout'
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
components: { imageCutout },
|
||||
data() {
|
||||
return {
|
||||
loadDone: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dPage']),
|
||||
},
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
|
||||
mounted() {
|
||||
const loadDone = ref(false)
|
||||
const imageCutoutRef = ref<typeof imageCutout | null>(null)
|
||||
const { dPage } = useSetupMapGetters(['dPage'])
|
||||
|
||||
onMounted(() => {
|
||||
// this.getDataList()
|
||||
setTimeout(() => {
|
||||
const { koutu } = this.$route.query
|
||||
koutu && this.openImageCutout()
|
||||
const { koutu } = route.query
|
||||
koutu && openImageCutout()
|
||||
}, 300)
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['addWidget']),
|
||||
async getDataList() {
|
||||
if (this.loadDone || this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.page += 1
|
||||
},
|
||||
addQrcode() {
|
||||
this.$store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
})
|
||||
|
||||
// ...mapActions(['addWidget'])
|
||||
|
||||
// async function getDataList() {
|
||||
// if (loadDone || loading) {
|
||||
// return
|
||||
// }
|
||||
// loading = true
|
||||
// page += 1
|
||||
// }
|
||||
|
||||
function addQrcode() {
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
let setting = JSON.parse(JSON.stringify(wQrcode.setting))
|
||||
const { width: pW, height: pH } = this.dPage
|
||||
const { width: pW, height: pH } = dPage.value
|
||||
setting.left = pW / 2 - setting.width / 2
|
||||
setting.top = pH / 2 - setting.height / 2
|
||||
this.addWidget(setting)
|
||||
},
|
||||
openImageCutout() {
|
||||
this.$refs.imageCutout.open()
|
||||
},
|
||||
},
|
||||
store.dispatch('addWidget', setting)
|
||||
// addWidget(setting)
|
||||
}
|
||||
|
||||
function openImageCutout() {
|
||||
if (!imageCutoutRef.value) return
|
||||
imageCutoutRef.value.open()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -7,62 +7,86 @@
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<el-tabs v-model="tabActiveName" :stretch="true" class="tabs" @tab-change="tabChange">
|
||||
<el-tabs v-model="state.tabActiveName" :stretch="true" class="tabs" @tab-change="tabChange">
|
||||
<el-tab-pane label="资源管理" name="pics"> </el-tab-pane>
|
||||
<el-tab-pane label="我的作品" name="design"> </el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-show="tabActiveName === 'pics'">
|
||||
<uploader v-model="percent" class="upload" @done="uploadDone">
|
||||
<div v-show="state.tabActiveName === 'pics'">
|
||||
<uploader v-model="state.percent" class="upload" @done="uploadDone">
|
||||
<el-button class="upload-btn" plain>上传图片 <i class="iconfont icon-upload" /></el-button>
|
||||
</uploader>
|
||||
<el-button class="upload-btn upload-psd" plain type="primary" @click="openPSD">上传 PSD 模板</el-button>
|
||||
<div style="margin: 1rem; height: 100vh">
|
||||
<photo-list ref="imgListRef" :edit="editOptions.photo" :isDone="isDone" :listData="imgList" @load="load" @drag="dragStart" @select="selectImg" />
|
||||
<photo-list
|
||||
ref="imgListRef"
|
||||
:edit="state.editOptions.photo" :isDone="state.isDone"
|
||||
:listData="state.imgList"
|
||||
@load="load" @drag="dragStart" @select="selectImg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="tabActiveName === 'design'" class="wrap">
|
||||
<div v-show="state.tabActiveName === 'design'" class="wrap">
|
||||
<ul ref="listRef" v-infinite-scroll="loadDesign" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
|
||||
<img-water-fall :edit="editOptions.works" :listData="designList" @select="selectDesign" />
|
||||
<img-water-fall :edit="state.editOptions.works" :listData="state.designList" @select="selectDesign" />
|
||||
<!-- <div v-show="loading" class="loading"><i class="el-icon-loading"></i>拼命加载中..</div> -->
|
||||
<div v-show="isDone" class="loading">全部加载完毕</div>
|
||||
<div v-show="state.isDone" class="loading">全部加载完毕</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, watch, nextTick, ref, onMounted } from 'vue'
|
||||
import { ElTabPane, ElTabs } from 'element-plus'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, toRefs, watch, nextTick, ref, onMounted, defineProps, defineExpose } from 'vue'
|
||||
import { ElTabPane, ElTabs, TabPaneName } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import uploader from '@/components/common/Uploader'
|
||||
import api from '@/api'
|
||||
import wImage from '../../widgets/wImage/wImage.vue'
|
||||
import setImageData from '@/common/methods/DesignFeatures/setImage'
|
||||
import setImageData, { TItem2DataParam } from '@/common/methods/DesignFeatures/setImage'
|
||||
import useConfirm from '@/common/methods/confirm'
|
||||
import { TGetImageListResult } from '@/api/material'
|
||||
import photoList from './components/photoList.vue'
|
||||
import imgWaterFall from './components/imgWaterFall.vue'
|
||||
import { TUploadDoneData } from '@/components/common/Uploader/index.vue'
|
||||
import { IGetTempListData } from '@/api/home'
|
||||
|
||||
export default defineComponent({
|
||||
components: { uploader, ElTabPane, ElTabs },
|
||||
props: ['active'],
|
||||
setup(props) {
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const state: any = reactive({
|
||||
type TProps = {
|
||||
active?: number
|
||||
}
|
||||
|
||||
type TState = {
|
||||
prePath: string,
|
||||
percent: { num: number }, // 当前上传进度
|
||||
imgList: IGetTempListData[],
|
||||
designList: IGetTempListData[],
|
||||
isDone: boolean,
|
||||
editOptions: Record<string, any>,
|
||||
tabActiveName: string,
|
||||
}
|
||||
|
||||
const props = defineProps<TProps>()
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const listRef = ref<HTMLElement | null>(null)
|
||||
const imgListRef = ref<typeof photoList | null>(null)
|
||||
|
||||
const state = reactive<TState>({
|
||||
prePath: 'user',
|
||||
percent: { num: 0 }, // 当前上传进度
|
||||
imgList: [],
|
||||
designList: [],
|
||||
isDone: false,
|
||||
editOptions: [],
|
||||
listRef: null,
|
||||
imgListRef: null,
|
||||
tabActiveName: '',
|
||||
})
|
||||
let loading = false
|
||||
let page = 0
|
||||
let listPage = 0
|
||||
})
|
||||
|
||||
const load = (init: boolean) => {
|
||||
let loading = false
|
||||
let page = 0
|
||||
let listPage = 0
|
||||
|
||||
const load = (init?: boolean) => {
|
||||
if (init) {
|
||||
state.imgList = []
|
||||
page = 0
|
||||
@ -73,15 +97,22 @@ export default defineComponent({
|
||||
}
|
||||
loading = true
|
||||
page += 1
|
||||
api.material.getMyPhoto({ page }).then(({ list }: any) => {
|
||||
list.length <= 0 ? (state.isDone = true) : (state.imgList = state.imgList.concat(list))
|
||||
api.material.getMyPhoto({ page }).then(({ list }) => {
|
||||
|
||||
if (list.length <= 0) {
|
||||
state.isDone = true
|
||||
} else {
|
||||
state.imgList = state.imgList.concat(list)
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading = false
|
||||
checkHeight(state.imgListRef.getRef(), load)
|
||||
if (!imgListRef.value) return
|
||||
checkHeight(imgListRef.value.getRef(), load)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
const loadDesign = (init: boolean = false) => {
|
||||
}
|
||||
|
||||
const loadDesign = (init: boolean = false) => {
|
||||
if (init) {
|
||||
state.designList = []
|
||||
listPage = 0
|
||||
@ -92,40 +123,43 @@ export default defineComponent({
|
||||
}
|
||||
loading = true
|
||||
listPage += 1
|
||||
api.home.getMyDesign({ page: listPage, pageSize: 10 }).then(({ list }: any) => {
|
||||
api.home.getMyDesign({ page: listPage, pageSize: 10 }).then(({ list }) => {
|
||||
list.length <= 0
|
||||
? (state.isDone = true)
|
||||
: (state.designList = state.designList.concat(
|
||||
list.map((x: any) => {
|
||||
list.map((x) => {
|
||||
x.cover = x.cover + '?r=' + Math.random()
|
||||
return x
|
||||
}),
|
||||
))
|
||||
setTimeout(() => {
|
||||
loading = false
|
||||
checkHeight(state.listRef, loadDesign)
|
||||
if (!listRef.value) return
|
||||
checkHeight(listRef.value, loadDesign)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function checkHeight(el: any, loadFn: Function) {
|
||||
function checkHeight(el: HTMLElement, loadFn: Function) {
|
||||
// 检查高度是否占满,否则继续请求下一页
|
||||
const isLess = el.offsetHeight > el.firstElementChild.offsetHeight
|
||||
if (el.offsetHeight && el.firstElementChild) {
|
||||
const isLess = el.offsetHeight > (el.firstElementChild as HTMLElement).offsetHeight
|
||||
isLess && loadFn()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
load(true)
|
||||
nextTick(() => {
|
||||
state.tabActiveName = 'pics'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const selectImg = async (index: number) => {
|
||||
const item: any = state.imgList[index]
|
||||
const selectImg = async (index: number) => {
|
||||
const item = state.imgList[index]
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
let setting = JSON.parse(JSON.stringify(wImage.setting))
|
||||
const img: any = await setImageData(item)
|
||||
const img = await setImageData(item)
|
||||
setting.width = img.width
|
||||
setting.height = img.height // parseInt(100 / item.value.ratio, 10)
|
||||
setting.imgUrl = item.url
|
||||
@ -133,8 +167,14 @@ export default defineComponent({
|
||||
setting.left = pW / 2 - img.width / 2
|
||||
setting.top = pH / 2 - img.height / 2
|
||||
store.dispatch('addWidget', setting)
|
||||
}
|
||||
const deleteImg = async ({ i, item }: any) => {
|
||||
}
|
||||
|
||||
type controlImgParam = {
|
||||
i: number
|
||||
item: Required<TItem2DataParam>
|
||||
}
|
||||
|
||||
const deleteImg = async ({ i, item }: controlImgParam) => {
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择框
|
||||
const isPass = await useConfirm('警告', '删除后不可找回,已引用资源将会失效,请谨慎操作', 'warning')
|
||||
if (!isPass) {
|
||||
@ -143,9 +183,10 @@ export default defineComponent({
|
||||
const arr = item.url.split('/')
|
||||
let key = arr.splice(3, arr.length - 1).join('/')
|
||||
api.material.deleteMyPhoto({ id: item.id, key })
|
||||
state.imgListRef.delItem(i) // 通知标记
|
||||
}
|
||||
const deleteWorks = async ({ i, item }: any) => {
|
||||
if (!imgListRef.value) return
|
||||
imgListRef.value.delItem(i) // 通知标记
|
||||
}
|
||||
const deleteWorks = async ({ i, item }: controlImgParam) => {
|
||||
const isPass = await useConfirm('警告', '删除后不可找回,请确认操作', 'warning')
|
||||
if (isPass) {
|
||||
await api.material.deleteMyWorks({ id: item.id })
|
||||
@ -154,48 +195,50 @@ export default defineComponent({
|
||||
loadDesign(true)
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
state.editOptions = {
|
||||
}
|
||||
|
||||
state.editOptions = {
|
||||
photo: [
|
||||
{
|
||||
name: '删除',
|
||||
fn: deleteImg,
|
||||
},
|
||||
],works: [
|
||||
],
|
||||
works: [
|
||||
{
|
||||
name: '删除',
|
||||
fn: deleteWorks,
|
||||
},
|
||||
]
|
||||
}
|
||||
const dragStart = (index: number) => {
|
||||
}
|
||||
|
||||
const dragStart = (index: number) => {
|
||||
const item = state.imgList[index]
|
||||
store.commit('selectItem', { data: { value: item }, type: 'image' })
|
||||
}
|
||||
const uploadDone = async (res: any) => {
|
||||
}
|
||||
const uploadDone = async (res: TUploadDoneData) => {
|
||||
await api.material.addMyPhoto(res)
|
||||
state.imgList = []
|
||||
load(true)
|
||||
}
|
||||
}
|
||||
|
||||
const tabChange = (tabName: string) => {
|
||||
const tabChange = (tabName: TabPaneName) => {
|
||||
if (tabName === 'design') {
|
||||
loadDesign(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectDesign = async (item: any) => {
|
||||
const selectDesign = async (item: IGetTempListData) => {
|
||||
// const { id }: any = state.designList[index]
|
||||
const { id }: any = item
|
||||
const { id } = item
|
||||
window.open(`${window.location.protocol + '//' + window.location.host}/home?id=${id}`)
|
||||
}
|
||||
}
|
||||
|
||||
const openPSD = () => {
|
||||
const openPSD = () => {
|
||||
window.open(router.resolve('/psd').href, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
defineExpose({
|
||||
selectDesign,
|
||||
loadDesign,
|
||||
load,
|
||||
@ -205,8 +248,6 @@ export default defineComponent({
|
||||
dragStart,
|
||||
tabChange,
|
||||
openPSD,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-04 02:04:04
|
||||
* @Description: 列表分类头部
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 02:30:59
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<div v-if="!isBack" class="content__wrap">
|
||||
@ -21,22 +21,34 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
props: ['types', 'isBack'],
|
||||
emits: ['select', 'back'],
|
||||
setup(props, { emit }) {
|
||||
const select = (item: any) => {
|
||||
export type TClassHeaderTypeData = {
|
||||
name: string
|
||||
}
|
||||
|
||||
type TProps = {
|
||||
types: TClassHeaderTypeData[]
|
||||
isBack?: boolean
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'select', data: string[]): void
|
||||
(event: 'back'): void
|
||||
}
|
||||
|
||||
const { types, isBack } = defineProps<TProps>()
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const select = (item: any) => {
|
||||
emit('select', item)
|
||||
}
|
||||
const back = () => {
|
||||
}
|
||||
const back = () => {
|
||||
emit('back')
|
||||
}
|
||||
return { select, back }
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ select, back })
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-04 19:12:40
|
||||
* @Description: 图片描述ToolTip
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 22:51:06
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<el-tooltip :disabled="!detail.author" :offset="1" effect="light" placement="bottom-start" :hide-after="0" :enterable="false" raw-content>
|
||||
@ -17,15 +17,18 @@
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
export type TImageTipDetailData = {
|
||||
author: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type Tprops = {
|
||||
detail: TImageTipDetailData
|
||||
}
|
||||
|
||||
const { detail } = defineProps<Tprops>()
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
detail: {},
|
||||
},
|
||||
setup() {
|
||||
return {}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
@ -2,14 +2,18 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-12-16 16:20:16
|
||||
* @Description: 瀑布流组件
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-12-11 11:45:24
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<div ref="imgWaterFall" :style="{ height: countHeight + 'px' }" class="img-water-fall">
|
||||
<div ref="imgWaterFall" :style="{ height: state.countHeight + 'px' }" class="img-water-fall">
|
||||
<!-- backgroundImage: `url(${item.cover})` -->
|
||||
<div v-for="(item, i) in list" :key="i + 'iwf'" :style="{ top: item.top + 'px', left: item.left + 'px', width: width + 'px', height: item.height + 'px' }" class="img-box" @click.stop="selectItem(item, i)">
|
||||
<edit-model v-if="edit" :options="edit" :data="{ item, i }">
|
||||
<div
|
||||
v-for="(item, i) in state.list" :key="i + 'iwf'"
|
||||
:style="{ top: item.top + 'px', left: item.left + 'px', width: state.width + 'px', height: item.height + 'px' }"
|
||||
class="img-box" @click.stop="selectItem(item, i)"
|
||||
>
|
||||
<edit-model v-if="edit" :options="props.edit" :data="{ item, i }">
|
||||
{{ item.isDelect }}
|
||||
<div v-if="item.isDelect" class="list__mask">已删除</div>
|
||||
<el-image v-if="!item.fail" class="img" :src="item.cover" lazy loading="lazy" @error="loadError(item)" />
|
||||
@ -20,32 +24,41 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const NAME = 'img-water-fall'
|
||||
import { defineComponent, toRefs, reactive, watch } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
// const NAME = 'img-water-fall'
|
||||
import { IGetTempListData } from '@/api/home';
|
||||
import { reactive, watch, defineProps, defineExpose, defineEmits } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: NAME,
|
||||
props: {
|
||||
listData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
edit: {}
|
||||
},
|
||||
emits: ['select', 'load'],
|
||||
setup(props, { emit }) {
|
||||
const state = reactive({
|
||||
type TProps = {
|
||||
listData: IGetTempListData[]
|
||||
edit?: Record<string, any>
|
||||
}
|
||||
|
||||
type TState = {
|
||||
width: number
|
||||
countHeight: number
|
||||
list: IGetTempListData[]
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'select', data: IGetTempListData): void
|
||||
(event: 'load'): void
|
||||
}
|
||||
|
||||
const props = defineProps<TProps>()
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const state = reactive<TState>({
|
||||
width: 146, // 图片的宽度
|
||||
list: [],
|
||||
countHeight: 0,
|
||||
})
|
||||
})
|
||||
|
||||
const columnHeights = [] // 列的高度
|
||||
const columnNums = 2 // 总共有多少列
|
||||
const gap = 7 // 图片之间的间隔
|
||||
const columnHeights: number[] = [] // 列的高度
|
||||
const columnNums = 2 // 总共有多少列
|
||||
const gap = 7 // 图片之间的间隔
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => props.listData,
|
||||
() => {
|
||||
columnHeights.length = 0
|
||||
@ -71,25 +84,22 @@ export default defineComponent({
|
||||
state.countHeight = Math.max(...columnHeights)
|
||||
state.list = cloneList
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
const load = () => {
|
||||
const load = () => {
|
||||
emit('load')
|
||||
}
|
||||
const selectItem = (value, index) => {
|
||||
}
|
||||
const selectItem = (value: IGetTempListData, index: number) => {
|
||||
emit('select', value)
|
||||
}
|
||||
const loadError = (item) => {
|
||||
}
|
||||
const loadError = (item: IGetTempListData) => {
|
||||
item.fail = true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
defineExpose({
|
||||
load,
|
||||
selectItem,
|
||||
loadError,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -2,14 +2,23 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-23 15:48:52
|
||||
* @Description: 图片列表组件 Bookshelf Layout
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-02-29 16:52:37
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<ul ref="listRef" class="img-list-wrap" :style="{ paddingBottom: isShort ? '15px' : '200px' }" @scroll="scrollEvent($event)">
|
||||
<ul ref="listRef" class="img-list-wrap" :style="{ paddingBottom: props.isShort ? '15px' : '200px' }" @scroll="scrollEvent($event)">
|
||||
<div class="list">
|
||||
<div v-for="(item, i) in list" :key="i + 'i'" :style="{ width: item.listWidth + 'px', marginRight: item.gap + 'px' }" 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="edit" :options="edit" :data="{ item, i }">
|
||||
<div
|
||||
v-for="(item, i) in state.list" :key="i + 'i'"
|
||||
:style="{ width: item.listWidth + 'px', marginRight: item.gap + 'px' }"
|
||||
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>
|
||||
<el-image class="img transparent-bg" :src="item.thumb || item.url" :style="{ height: getInnerHeight(item) + 'px' }" lazy loading="lazy" />
|
||||
</edit-model>
|
||||
@ -24,66 +33,82 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isDone" v-show="loading" class="loading"><i class="el-icon-loading" /> 拼命加载中</div>
|
||||
<div v-if="!props.isDone" v-show="state.loading" class="loading"><i class="el-icon-loading" /> 拼命加载中</div>
|
||||
<div v-else class="loading">全部加载完毕</div>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, watch, nextTick } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, watch, nextTick, defineProps, defineExpose, defineEmits, ref } from 'vue'
|
||||
import DragHelper from '@/common/hooks/dragHelper'
|
||||
import setImageData from '@/common/methods/DesignFeatures/setImage'
|
||||
import setImageData, { TItem2DataParam } from '@/common/methods/DesignFeatures/setImage'
|
||||
import { IGetTempListData } from '@/api/home';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
listData: {},
|
||||
edit: {},
|
||||
isDone: {},
|
||||
isShort: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['load', 'drag', 'select'],
|
||||
setup(props, context) {
|
||||
const state: any = reactive({
|
||||
type TProps = {
|
||||
listData: IGetTempListData[]
|
||||
edit?: Record<string, any>
|
||||
isDone?: boolean
|
||||
isShort?: boolean
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'load'): void
|
||||
(event: 'select', data: number): void
|
||||
(event: 'drag', data: number): void
|
||||
|
||||
}
|
||||
|
||||
type TState = {
|
||||
loading: boolean
|
||||
list: IGetTempListData[]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
isShort: false,
|
||||
listData: () => ([])
|
||||
})
|
||||
const emit = defineEmits<TEmits>()
|
||||
const listRef = ref<HTMLElement | null>(null)
|
||||
const state = reactive<TState>({
|
||||
loading: true,
|
||||
list: [],
|
||||
listRef: null,
|
||||
})
|
||||
})
|
||||
|
||||
const dragHelper = new DragHelper()
|
||||
let isDrag = false
|
||||
let startPoint = { x: 99999, y: 99999 }
|
||||
const mouseup = (e: any) => {
|
||||
const dragHelper = new DragHelper()
|
||||
let isDrag = false
|
||||
let startPoint = { x: 99999, y: 99999 }
|
||||
|
||||
const mouseup = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
setTimeout(() => {
|
||||
isDrag = false
|
||||
startPoint = { x: 99999, y: 99999 }
|
||||
}, 10)
|
||||
}
|
||||
const mousemove = (e: any) => {
|
||||
}
|
||||
|
||||
const mousemove = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
if (e.x - startPoint.x > 2 || e.y - startPoint.y > 2) {
|
||||
isDrag = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => props.listData,
|
||||
async (newList: any, oldList: any) => {
|
||||
async (newList: IGetTempListData[], oldList: IGetTempListData[]) => {
|
||||
!oldList && (oldList = [])
|
||||
if (newList.length <= 0) {
|
||||
state.list.length = 0
|
||||
return
|
||||
}
|
||||
let list = newList.filter((v: any) => !newList.includes(v) || !oldList.includes(v)) // difference
|
||||
let list = newList.filter((v: IGetTempListData) => !newList.includes(v) || !oldList.includes(v)) // difference
|
||||
list = JSON.parse(JSON.stringify(list))
|
||||
const marginRight = 6 // 间距
|
||||
const limitWidth = (await getFatherWidth()) - marginRight
|
||||
const standardHeight = 280 // 高度阈值
|
||||
const neatArr: any = [] // 整理后的数组
|
||||
function factory(cutArr: any) {
|
||||
return new Promise((resolve) => {
|
||||
const neatArr: IGetTempListData[][] = [] // 整理后的数组
|
||||
function factory(cutArr: IGetTempListData[]) {
|
||||
return new Promise<{ height: number, list: IGetTempListData[] }>((resolve) => {
|
||||
const lineup = list.shift()
|
||||
if (!lineup) {
|
||||
resolve({ height: calculate(cutArr), list: cutArr })
|
||||
@ -98,7 +123,7 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
}
|
||||
function calculate(cutArr: any) {
|
||||
function calculate(cutArr: IGetTempListData[]) {
|
||||
let cumulate = 0
|
||||
for (const iterator of cutArr) {
|
||||
const { width, height } = iterator
|
||||
@ -110,9 +135,9 @@ export default defineComponent({
|
||||
if (list.length <= 0) {
|
||||
return
|
||||
}
|
||||
const { list: newList, height }: any = await factory([list.shift()])
|
||||
const { list: newList, height } = await factory([(list.shift() as IGetTempListData)])
|
||||
neatArr.push(
|
||||
newList.map((x: any, index) => {
|
||||
newList.map((x: IGetTempListData, index: number) => {
|
||||
x.listWidth = (x.width / x.height) * height
|
||||
x.gap = index !== newList.length - 1 ? marginRight : 0
|
||||
return x
|
||||
@ -126,61 +151,66 @@ export default defineComponent({
|
||||
state.list = state.list.concat(neatArr.flat(1))
|
||||
state.loading = false
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
async function getFatherWidth() {
|
||||
async function getFatherWidth() {
|
||||
await nextTick()
|
||||
const dom = state.listRef
|
||||
const father = dom.parentElement || dom.parentNode
|
||||
return father.offsetWidth
|
||||
}
|
||||
if (!listRef.value) return 0
|
||||
const father = listRef.value.parentElement ?? listRef.value.parentNode
|
||||
if (!father) return 0
|
||||
return (father as HTMLElement).offsetWidth
|
||||
}
|
||||
|
||||
function getRef() {
|
||||
function getRef() {
|
||||
// 用于在组件外调用内部ref
|
||||
return state.listRef
|
||||
}
|
||||
return listRef
|
||||
}
|
||||
|
||||
const load = () => {
|
||||
const load = () => {
|
||||
state.loading = true
|
||||
context.emit('load')
|
||||
}
|
||||
const select = (i: number) => {
|
||||
!isDrag && !state.list[i].isDelect && context.emit('select', i)
|
||||
}
|
||||
const dragStart = async (e: Event | any, i: number) => {
|
||||
emit('load')
|
||||
}
|
||||
const select = (i: number) => {
|
||||
!isDrag && !state.list[i].isDelect && emit('select', i)
|
||||
}
|
||||
|
||||
const dragStart = async (e: Event | any, i: number) => {
|
||||
e.preventDefault()
|
||||
startPoint = { x: e.x, y: e.y }
|
||||
if (!state.list[i].isDelect) {
|
||||
const img = await setImageData(state.list[i])
|
||||
const setImageParams: TItem2DataParam = {
|
||||
width: state.list[i].width,
|
||||
height: state.list[i].height,
|
||||
url: state.list[i].url || '',
|
||||
model: state.list[i].model
|
||||
}
|
||||
const img = await setImageData(setImageParams)
|
||||
dragHelper.start(e, img.canvasWidth)
|
||||
context.emit('drag', i)
|
||||
emit('drag', i)
|
||||
}
|
||||
}
|
||||
function delItem(i: number) {
|
||||
}
|
||||
function delItem(i: number) {
|
||||
state.list[i].isDelect = true
|
||||
}
|
||||
}
|
||||
|
||||
const scrollEvent = (e: any) => {
|
||||
const scrollEvent = (e: any) => {
|
||||
if (e.target.scrollTop + e.target.offsetHeight + 200 >= e.target.scrollHeight) {
|
||||
load()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getInnerHeight = ({ height, listWidth, width }: any) => (height * listWidth) / width
|
||||
const getInnerHeight = ({ height, listWidth, width }: any) => (height * listWidth) / width
|
||||
|
||||
return {
|
||||
defineExpose({
|
||||
load,
|
||||
dragStart,
|
||||
select,
|
||||
...toRefs(state),
|
||||
delItem,
|
||||
scrollEvent,
|
||||
getRef,
|
||||
mouseup,
|
||||
mousemove,
|
||||
getInnerHeight,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-01-27 11:05:48
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 01:53:10
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @Date: 2024-03-06 21:16:00
|
||||
-->
|
||||
<template>
|
||||
<div class="search__wrap">
|
||||
@ -11,66 +11,85 @@
|
||||
<div class="search__type"><i class="iconfont icon-ego-caidan" /></div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="type in materialCates" :key="type.id" @click="action('change', type, type.id)">
|
||||
<span :class="['cate__text', { 'cate--select': +currentIndex === type.id }]">{{ type.name }}</span>
|
||||
<el-dropdown-item
|
||||
v-for="type in state.materialCates" :key="type.id"
|
||||
@click="action('change', type, type.id)"
|
||||
>
|
||||
<span :class="['cate__text', { 'cate--select': + state.currentIndex === type.id }]">{{ type.name }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<span v-else style="width: 1rem"></span>
|
||||
|
||||
<el-input v-model="searchValue" size="large" placeholder="输入关键词搜索" class="input-with-select">
|
||||
<el-input v-model="state.searchValue" size="large" placeholder="输入关键词搜索" class="input-with-select">
|
||||
<template #append>
|
||||
<el-button><i class="iconfont icon-search"></i></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, watch } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, toRefs, watch, defineProps, defineEmits, defineExpose } from 'vue'
|
||||
import { ElDropdown, ElDropdownItem, ElDropdownMenu } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import api from '@/api'
|
||||
|
||||
export default defineComponent({
|
||||
components: { ElDropdown, ElDropdownItem, ElDropdownMenu },
|
||||
props: ['type', 'modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, context) {
|
||||
const route = useRoute()
|
||||
const state: any = reactive({
|
||||
type TProps = {
|
||||
type?: string
|
||||
modelValue?: string
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'update:modelValue', data: string): void
|
||||
(event: 'change', data: TMaterialCatesData): void
|
||||
}
|
||||
|
||||
type TMaterialCatesData = {id: string | number, name: string}
|
||||
|
||||
type TState = {
|
||||
searchValue: string
|
||||
materialCates: TMaterialCatesData[]
|
||||
currentIndex: number | string
|
||||
}
|
||||
|
||||
const props = defineProps<TProps>()
|
||||
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const route = useRoute()
|
||||
const state = reactive<TState>({
|
||||
searchValue: '',
|
||||
materialCates: [],
|
||||
currentIndex: 1,
|
||||
})
|
||||
})
|
||||
|
||||
if (props.type != 'none') {
|
||||
if (props.type != 'none') {
|
||||
api.home.getCategories({ type: 1 }).then((list: any) => {
|
||||
list.unshift({ id: 0, name: '全部' })
|
||||
state.materialCates = list
|
||||
const { cate } = route.query
|
||||
cate && (state.currentIndex = cate)
|
||||
cate && (state.currentIndex = cate as string)
|
||||
cate && action('change', state.materialCates[Number(cate)], Number(cate))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => state.searchValue,
|
||||
() => {
|
||||
context.emit('update:modelValue', state.searchValue)
|
||||
emit('update:modelValue', state.searchValue)
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
function action(fn: string, type: any, currentIndex: number | string) {
|
||||
function action(fn: 'change', type: TMaterialCatesData, currentIndex: number | string) {
|
||||
currentIndex && (state.currentIndex = currentIndex)
|
||||
context.emit(fn, type)
|
||||
}
|
||||
return {
|
||||
...toRefs(state),
|
||||
action,
|
||||
}
|
||||
},
|
||||
emit(fn, type)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
action
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -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,58 +38,74 @@
|
||||
</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 = '') => {
|
||||
})
|
||||
|
||||
const select = (value: string = '') => {
|
||||
state.visiable = false
|
||||
const setting = JSON.parse(JSON.stringify(wSvg.setting))
|
||||
setting.svgUrl = value
|
||||
context.emit('change', setting)
|
||||
}
|
||||
onMounted(async () => {
|
||||
emit('change', setting)
|
||||
}
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
state.effectSelect = props?.modelValue || ''
|
||||
// state.strength = props?.degree || state.strength
|
||||
getList()
|
||||
})
|
||||
})
|
||||
|
||||
async function getList() {
|
||||
async function getList() {
|
||||
const res = await api.material.getList({
|
||||
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(
|
||||
}
|
||||
|
||||
watch(
|
||||
() => state.type,
|
||||
(value) => {
|
||||
getList()
|
||||
},
|
||||
)
|
||||
return {
|
||||
...toRefs(state),
|
||||
)
|
||||
defineExpose({
|
||||
select,
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -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,38 +107,63 @@
|
||||
</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: [],
|
||||
layers: [],
|
||||
draging: false,
|
||||
unfold: true,
|
||||
})
|
||||
const dragOptions = {
|
||||
})
|
||||
|
||||
const dragOptions = {
|
||||
animation: 300,
|
||||
ghostClass: 'ghost',
|
||||
chosenClass: 'choose',
|
||||
}
|
||||
const coefficient = computed(() => Math.round(160 / 27))
|
||||
let rawData: any = [] // 初始化记录数据,用于强度修改
|
||||
}
|
||||
const coefficient = computed(() => Math.round(160 / 27))
|
||||
let rawData: Record<string, any>[] = [] // 初始化记录数据,用于强度修改
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
// console.log(props.data)
|
||||
if (!props.data.textEffects) {
|
||||
@ -143,8 +177,9 @@ export default defineComponent({
|
||||
})
|
||||
.reverse()
|
||||
rawData = JSON.parse(JSON.stringify(state.layers))
|
||||
})
|
||||
watch(
|
||||
})
|
||||
|
||||
watch(
|
||||
() => state.layers,
|
||||
(v) => {
|
||||
const newEffect = v.map((x) => {
|
||||
@ -154,42 +189,42 @@ export default defineComponent({
|
||||
emit('update:modelValue', newEffect.reverse())
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
)
|
||||
|
||||
// 选中加载特效预设
|
||||
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
|
||||
})
|
||||
.reverse()
|
||||
} else state.layers = []
|
||||
}
|
||||
}
|
||||
|
||||
// 删除效果层
|
||||
const removeLayer = (i: number) => {
|
||||
// 删除效果层
|
||||
const removeLayer = (i: number) => {
|
||||
state.layers.splice(i, 1)
|
||||
rawData = JSON.parse(JSON.stringify(state.layers))
|
||||
}
|
||||
}
|
||||
|
||||
// 添加效果层
|
||||
const addLayer = () => {
|
||||
// 添加效果层
|
||||
const addLayer = () => {
|
||||
const filling = { enable: false, type: 0, color: '#000000ff' }
|
||||
const stroke = { enable: false, width: 0, color: '#000000ff', type: 'outer' }
|
||||
const offset = { enable: false, x: 0, y: 0 }
|
||||
const shadow = { enable: false, color: '#000000ff', offsetX: 0, offsetY: 0, blur: 0, opacity: 0 }
|
||||
state.layers.unshift({ filling, stroke, shadow, offset, uuid: String(Math.random()) })
|
||||
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,
|
||||
}
|
||||
@ -200,7 +235,7 @@ export default defineComponent({
|
||||
setTimeout(() => {
|
||||
item.type = modeStr[e.mode] || 0
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
// const onMove = ({ relatedContext, draggedContext }: any) => {
|
||||
// const relatedElement = relatedContext.element
|
||||
@ -223,8 +258,8 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
// 打开特效字体集
|
||||
const openSet = async () => {
|
||||
// 打开特效字体集
|
||||
const openSet = async () => {
|
||||
state.visiable = !state.visiable
|
||||
if (froze_font_effect_list.length <= 0) {
|
||||
const { list } = await api.home.getCompList({
|
||||
@ -235,9 +270,9 @@ export default defineComponent({
|
||||
state.list = list
|
||||
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>
|
||||
|
||||
|
@ -6,67 +6,75 @@
|
||||
<div class="content">
|
||||
<el-popover placement="left-end" trigger="click" width="auto" @after-enter="enter" @before-leave="hide">
|
||||
<!-- eslint-disable-next-line vue/no-v-model-argument -->
|
||||
<color-picker v-model:value="innerColor" :modes="modes" @change="colorChange" @nativePick="dropColor" />
|
||||
<color-picker v-model:value="state.innerColor" :modes="modes" @change="colorChange" @nativePick="dropColor" />
|
||||
<template #reference>
|
||||
<div class="color__bar" :style="{ background: innerColor }"></div>
|
||||
<div class="color__bar" :style="{ background: state.innerColor }"></div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
const NAME = 'color-select'
|
||||
import { defineComponent, toRefs, reactive, computed, onMounted, watch } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
// const NAME = 'color-select'
|
||||
import {reactive, onMounted, watch } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
// import { debounce } from 'throttle-debounce'
|
||||
// import { toolTip } from '@/common/methods/helper'
|
||||
// import colorPicker from '@/utils/plugins/color-picker/index.vue'
|
||||
import colorPicker from '@palxp/color-picker'
|
||||
|
||||
export default defineComponent({
|
||||
name: NAME,
|
||||
components: { colorPicker },
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
label: {
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
default: '100%',
|
||||
},
|
||||
modes: {
|
||||
default: () => ['纯色'],
|
||||
},
|
||||
},
|
||||
emits: ['finish', 'update:modelValue', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const store = useStore()
|
||||
const state: any = reactive({
|
||||
innerColor: '#ffffffff',
|
||||
type TProps = {
|
||||
label?: string
|
||||
modelValue?: string
|
||||
width?: string
|
||||
modes?: string[]
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'finish', data: string): void
|
||||
(event: 'update:modelValue', data: string): void
|
||||
(event: 'change', data: string): void
|
||||
}
|
||||
|
||||
type TState = {
|
||||
innerColor: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
label: '',
|
||||
modelValue: '',
|
||||
width: '100%',
|
||||
modes: () => (['纯色'])
|
||||
})
|
||||
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const store = useStore()
|
||||
const state = reactive<TState>({
|
||||
innerColor: '',
|
||||
// colorLength: 0,
|
||||
// hasEyeDrop: 'EyeDropper' in window,
|
||||
})
|
||||
let first = true
|
||||
})
|
||||
let first = true
|
||||
|
||||
// const dColorHistory = computed(() => {
|
||||
// return store.getters.dColorHistory
|
||||
// })
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
if (props.modelValue) {
|
||||
let fixColor = props.modelValue + (props.modelValue.length === 7 ? 'ff' : '')
|
||||
// 当前@palxp/color-picker对部分小写16进制颜色处理有异常,统一转为大写
|
||||
state.innerColor = fixColor.toLocaleUpperCase()
|
||||
}
|
||||
})
|
||||
const dropColor = async (e: any) => {
|
||||
console.log('取色: ', e)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
const dropColor = async (color: string) => {
|
||||
console.log('取色: ', color)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => state.innerColor,
|
||||
(value) => {
|
||||
activeChange(value)
|
||||
@ -76,48 +84,51 @@ export default defineComponent({
|
||||
}
|
||||
// addHistory(value)
|
||||
},
|
||||
)
|
||||
watch(
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
val !== state.innerColor && (state.innerColor = val)
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
const updateValue = (value: any) => {
|
||||
const updateValue = (value: any) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
const activeChange = (value: any) => {
|
||||
}
|
||||
|
||||
const activeChange = (value: any) => {
|
||||
updateValue(value)
|
||||
}
|
||||
const onChange = () => {
|
||||
}
|
||||
|
||||
const onChange = () => {
|
||||
emit('finish', state.innerColor)
|
||||
}
|
||||
// const addHistory = debounce(300, false, async (value) => {
|
||||
// store.dispatch('pushColorToHistory', value)
|
||||
// })
|
||||
// const colorChange = debounce(150, false, async (e) => {
|
||||
// state.innerColor = e + (e.length === 7 ? 'ff' : '')
|
||||
// })
|
||||
}
|
||||
|
||||
const inputBlur = (color: string) => {
|
||||
// const addHistory = debounce(300, false, async (value) => {
|
||||
// store.dispatch('pushColorToHistory', value)
|
||||
// })
|
||||
// const colorChange = debounce(150, false, async (e) => {
|
||||
// state.innerColor = e + (e.length === 7 ? 'ff' : '')
|
||||
// })
|
||||
|
||||
const inputBlur = (color: string) => {
|
||||
state.innerColor = color
|
||||
}
|
||||
}
|
||||
|
||||
const enter = () => {
|
||||
const enter = () => {
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择框
|
||||
}
|
||||
}
|
||||
|
||||
const hide = () => {
|
||||
const hide = () => {
|
||||
store.commit('setShowMoveable', true) // 恢复上一次的选择框
|
||||
}
|
||||
}
|
||||
|
||||
const colorChange = (e) => {
|
||||
emit('change', e)
|
||||
}
|
||||
const colorChange = (color: string) => {
|
||||
emit('change', color)
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
defineExpose({
|
||||
// dColorHistory,
|
||||
activeChange,
|
||||
onChange,
|
||||
@ -126,8 +137,6 @@ export default defineComponent({
|
||||
enter,
|
||||
hide,
|
||||
colorChange,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -18,34 +18,40 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
// 图标按钮选择组件
|
||||
const NAME = 'icon-item-select'
|
||||
// const NAME = 'icon-item-select'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
props: {
|
||||
label: {
|
||||
default: '',
|
||||
},
|
||||
data: {
|
||||
required: true,
|
||||
type: Array,
|
||||
},
|
||||
},
|
||||
emits: ['finish'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
selectItem(item) {
|
||||
type TPropData = {
|
||||
select: boolean,
|
||||
extraIcon: boolean,
|
||||
tip: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
type TProps = {
|
||||
label?: string
|
||||
data: TPropData[]
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'finish', data: TPropData): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
label: ''
|
||||
})
|
||||
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
|
||||
function selectItem(item: TPropData) {
|
||||
if (typeof item.select !== 'undefined') {
|
||||
item.select = !item.select
|
||||
}
|
||||
this.$emit('finish', item)
|
||||
},
|
||||
},
|
||||
emit('finish', item)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -13,11 +13,26 @@
|
||||
</div> -->
|
||||
<div v-if="type === 'simple'">
|
||||
<span class="prepend">{{ prepend }}</span>
|
||||
<input :class="{ 'small-input': true, disable: !editable }" type="text" :value="modelValue" :readonly="editable ? false : 'readonly'" @input="updateValue($event.target.value)" @focus="focusInput" @blur="blurInput" @keyup="verifyNumber" @keydown="(e) => opNumber(e)" />
|
||||
<input
|
||||
:class="{ 'small-input': true, disable: !editable }" type="text" :value="modelValue"
|
||||
:readonly="!props.editable"
|
||||
@input="updateValue($event && $event.target ? ($event.target as HTMLInputElement).value : '')"
|
||||
@focus="focusInput"
|
||||
@blur="blurInput"
|
||||
@keyup="verifyNumber"
|
||||
@keydown="(e) => opNumber(e)" />
|
||||
</div>
|
||||
<div v-else class="number-input2">
|
||||
<div class="input-wrap" @click="edit">
|
||||
<input :class="{ 'real-input': true, disable: !editable }" type="text" :value="modelValue" :readonly="editable ? false : 'readonly'" @input="updateValue($event.target.value)" @focus="focusInput" @blur="blurInput" @keyup="verifyNumber" @keydown="(e) => opNumber(e)" />
|
||||
<div class="input-wrap">
|
||||
<input
|
||||
:class="{ 'real-input': true, disable: !editable }"
|
||||
type="text" :value="modelValue" :readonly="!props.editable"
|
||||
@input="updateValue($event && $event.target ? ($event.target as HTMLInputElement).value : '')"
|
||||
@focus="focusInput"
|
||||
@blur="blurInput"
|
||||
@keyup="verifyNumber"
|
||||
@keydown="(e) => opNumber(e)"
|
||||
/>
|
||||
</div>
|
||||
<span style="color: rgba(0, 0, 0, 0.45)">{{ label }}</span>
|
||||
<!-- <div :class="{ 'input-wrap': true, active: inputBorder }">
|
||||
@ -30,90 +45,95 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
// 数字输入组件
|
||||
const NAME = 'number-input'
|
||||
// const NAME = 'number-input'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
props: {
|
||||
label: {
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
default: '',
|
||||
},
|
||||
editable: {
|
||||
default: true,
|
||||
},
|
||||
step: {
|
||||
default: 1,
|
||||
},
|
||||
maxValue: {},
|
||||
minValue: {},
|
||||
type: {},
|
||||
prepend: {},
|
||||
},
|
||||
emits: ['finish', 'update:modelValue'],
|
||||
data() {
|
||||
return {
|
||||
inputBorder: false,
|
||||
tagText: '',
|
||||
showEdit: false,
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
watch: {
|
||||
modelValue() {
|
||||
this.fixedNum()
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fixedNum()
|
||||
},
|
||||
methods: {
|
||||
fixedNum() {
|
||||
type TProps = {
|
||||
label?: string
|
||||
modelValue?: string | number
|
||||
editable?: boolean
|
||||
step?: number
|
||||
maxValue?: string | number
|
||||
minValue?: string | number
|
||||
type?: string
|
||||
prepend?: string
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'finish', data: number | string): void
|
||||
(event: 'update:modelValue', data: number | string): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
label: '',
|
||||
modelValue: '',
|
||||
editable: true,
|
||||
step: 1
|
||||
})
|
||||
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const inputBorder = ref<boolean>(false)
|
||||
const tagText = ref<string | number>('')
|
||||
const showEdit = ref<boolean>(false)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => fixedNum()
|
||||
)
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
fixedNum()
|
||||
})
|
||||
|
||||
function fixedNum() {
|
||||
// 小数点过长时
|
||||
const decimal = String(this.modelValue).split('.')[1]
|
||||
const decimal = String(props.modelValue).split('.')[1]
|
||||
if (decimal && decimal.length > 2) {
|
||||
setTimeout(() => {
|
||||
this.updateValue(Number(this.modelValue).toFixed(2))
|
||||
updateValue(Number(props.modelValue).toFixed(2))
|
||||
}, 10)
|
||||
}
|
||||
// 限定数字范围
|
||||
if (this.maxValue && this.modelValue > this.maxValue) {
|
||||
if (props.maxValue && props.modelValue > props.maxValue) {
|
||||
setTimeout(() => {
|
||||
this.updateValue(Number(this.maxValue))
|
||||
updateValue(Number(props.maxValue))
|
||||
}, 10)
|
||||
} else if (typeof this.minValue === 'number' && this.modelValue < this.minValue) {
|
||||
} else if (typeof props.minValue === 'number' && Number(props.modelValue) < Number(props.minValue)) {
|
||||
setTimeout(() => {
|
||||
this.updateValue(Number(this.minValue))
|
||||
updateValue(Number(props.minValue))
|
||||
}, 10)
|
||||
}
|
||||
},
|
||||
updateValue(value) {
|
||||
this.$emit('update:modelValue', value === '-' ? '-' : Number(value))
|
||||
},
|
||||
up() {
|
||||
this.updateValue(parseInt(this.modelValue || 0, 10) + this.step)
|
||||
},
|
||||
down() {
|
||||
let value = parseInt(this.modelValue || 0, 10) - this.step
|
||||
this.updateValue(value)
|
||||
},
|
||||
opNumber(e) {
|
||||
}
|
||||
|
||||
function updateValue(value: string | number) {
|
||||
emit('update:modelValue', value === '-' ? '-' : Number(value))
|
||||
}
|
||||
|
||||
function up() {
|
||||
updateValue(parseInt(`${props.modelValue}` ?? '0', 10) + props.step)
|
||||
}
|
||||
function down() {
|
||||
let value = parseInt(`${props.modelValue}` ?? '0', 10) - props.step
|
||||
updateValue(value)
|
||||
}
|
||||
|
||||
function opNumber(e: KeyboardEvent) {
|
||||
e.stopPropagation()
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
this.up()
|
||||
up()
|
||||
return
|
||||
case 40:
|
||||
this.down()
|
||||
down()
|
||||
return
|
||||
}
|
||||
},
|
||||
verifyNumber() {
|
||||
let value = String(this.modelValue)
|
||||
}
|
||||
function verifyNumber() {
|
||||
let value = String(props.modelValue)
|
||||
let len = value.length
|
||||
let newValue = ''
|
||||
let isNegative = value[0] === '-'
|
||||
@ -132,23 +152,23 @@ export default {
|
||||
if (isNegative) {
|
||||
newValue = '-' + (newValue === '0' ? '' : newValue)
|
||||
}
|
||||
this.updateValue(newValue)
|
||||
updateValue(newValue)
|
||||
// this.updateValue(parseInt(newValue, 10))
|
||||
},
|
||||
focusInput() {
|
||||
this.inputBorder = true
|
||||
this.tagText = this.modelValue
|
||||
},
|
||||
blurInput() {
|
||||
if (this.modelValue === '-') {
|
||||
this.updateValue(0)
|
||||
}
|
||||
|
||||
function focusInput() {
|
||||
inputBorder.value = true
|
||||
tagText.value = props.modelValue
|
||||
}
|
||||
|
||||
function blurInput() {
|
||||
if (props.modelValue === '-') {
|
||||
updateValue(0)
|
||||
}
|
||||
this.inputBorder = false
|
||||
if (this.modelValue !== this.tagText) {
|
||||
this.$emit('finish', this.modelValue)
|
||||
inputBorder.value = false
|
||||
if (props.modelValue !== tagText.value) {
|
||||
emit('finish', props.modelValue)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -8,68 +8,71 @@
|
||||
<template>
|
||||
<div id="number-slider">
|
||||
<span :style="{ width: labelWidth }" class="label">{{ label }}</span>
|
||||
<el-slider v-model="innerValue" :min="minValue" :max="maxValue" :step="step" input-size="small" :show-input="showInput" :show-tooltip="false" :show-input-controls="false" @change="changeValue"> </el-slider>
|
||||
<el-slider
|
||||
v-model="innerValue"
|
||||
:min="minValue" :max="maxValue" :step="step"
|
||||
input-size="small"
|
||||
:show-input="showInput" :show-tooltip="false" :show-input-controls="false"
|
||||
@change="changeValue"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const NAME = 'number-slider'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
<script lang="ts" setup>
|
||||
// const NAME = 'number-slider'
|
||||
import { watch, ref, onMounted } from 'vue';
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
props: {
|
||||
label: {
|
||||
default: '',
|
||||
},
|
||||
labelWidth: {
|
||||
default: '71px',
|
||||
},
|
||||
modelValue: {
|
||||
default: 0,
|
||||
},
|
||||
minValue: {
|
||||
default: 0,
|
||||
},
|
||||
maxValue: {
|
||||
default: 500,
|
||||
},
|
||||
step: {
|
||||
default: 1,
|
||||
},
|
||||
showInput: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'finish'],
|
||||
data() {
|
||||
return {
|
||||
innerValue: 0,
|
||||
// first: true,
|
||||
type TProps = {
|
||||
label?: string
|
||||
labelWidth?: string
|
||||
modelValue?: number
|
||||
minValue?: number
|
||||
maxValue?: number
|
||||
step?: number
|
||||
showInput?: boolean
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event: 'update:modelValue', data: number): void
|
||||
(event: 'finish', data: number | number[]): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
label: '',
|
||||
labelWidth: '71px',
|
||||
modelValue: 0,
|
||||
minValue: 0,
|
||||
maxValue: 500,
|
||||
step: 1,
|
||||
showInput: true
|
||||
})
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const innerValue = ref<number>(0)
|
||||
|
||||
watch(
|
||||
() => innerValue.value,
|
||||
(value) => {
|
||||
if (props.modelValue !== value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([]),
|
||||
},
|
||||
watch: {
|
||||
innerValue(value) {
|
||||
if (this.modelValue !== value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
}
|
||||
},
|
||||
modelValue(val) {
|
||||
this.innerValue = this.modelValue
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.innerValue = this.modelValue
|
||||
},
|
||||
methods: {
|
||||
...mapActions([]),
|
||||
changeValue(value) {
|
||||
this.$emit('finish', value)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
innerValue.value = props.modelValue
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
innerValue.value = props.modelValue
|
||||
})
|
||||
|
||||
function changeValue(value: number | number[]) {
|
||||
emit('finish', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -9,51 +9,59 @@
|
||||
<div class="text-input">
|
||||
<p v-if="label" class="input-label">{{ label }}</p>
|
||||
<div :class="{ 'input-wrap': true, active: inputBorder }">
|
||||
<input :class="{ 'real-input': true, disable: !editable }" type="text" :value="modelValue" :readonly="editable ? false : 'readonly'" @input="updateValue($event.target.value)" @focus="focusInput" @blur="blurInput" />
|
||||
<input
|
||||
:class="{ 'real-input': true, disable: !props.editable }"
|
||||
type="text" :value="props.modelValue"
|
||||
:readonly="!editable"
|
||||
@input="updateValue(($event.target as HTMLInputElement).value)"
|
||||
@focus="focusInput"
|
||||
@blur="blurInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
// 文本输入组件
|
||||
const NAME = 'text-input'
|
||||
// const NAME = 'text-input'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
props: {
|
||||
label: {
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
default: '',
|
||||
},
|
||||
editable: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'finish'],
|
||||
data() {
|
||||
return {
|
||||
inputBorder: false,
|
||||
tagText: '',
|
||||
type TProps = {
|
||||
label?: string
|
||||
modelValue?: string
|
||||
editable?: boolean
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event:'update:modelValue', data: string): void
|
||||
(event: 'finish', data: string): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
label: '',
|
||||
modelValue: '',
|
||||
editable: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<TEmits>()
|
||||
|
||||
const inputBorder = ref(false)
|
||||
const tagText = ref<string>('')
|
||||
|
||||
function updateValue(value: string) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
function focusInput() {
|
||||
inputBorder.value = true
|
||||
tagText.value = props.modelValue
|
||||
}
|
||||
|
||||
function blurInput() {
|
||||
inputBorder.value = false
|
||||
if (props.modelValue !== tagText.value) {
|
||||
emit('finish', props.modelValue)
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
updateValue(value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
},
|
||||
focusInput() {
|
||||
this.inputBorder = true
|
||||
this.tagText = this.modelValue
|
||||
},
|
||||
blurInput() {
|
||||
this.inputBorder = false
|
||||
if (this.modelValue !== this.tagText) {
|
||||
this.$emit('finish', this.modelValue)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -9,61 +9,65 @@
|
||||
<div id="text-input-area">
|
||||
<p v-if="label" class="input-label">{{ label }}</p>
|
||||
<div :class="{ 'input-wrap': true, active: inputBorder }">
|
||||
<textarea :maxlength="max" :class="{ 'real-input': true, disable: !editable }" type="text" rows="3" :value="dealValue" :readonly="editable ? false : 'readonly'" @input="updateValue($event.target.value)" @focus="focusInput" @blur="blurInput" />
|
||||
<textarea
|
||||
:maxlength="max" :class="{ 'real-input': true, disable: !editable }"
|
||||
type="text" rows="3" :value="dealValue"
|
||||
:readonly="!editable" @input="updateValue(($event.target as HTMLTextAreaElement).value)"
|
||||
@focus="focusInput" @blur="blurInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
// 文本域输入组件
|
||||
const NAME = 'text-input-area'
|
||||
// const NAME = 'text-input-area'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
props: {
|
||||
label: {
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
default: '',
|
||||
},
|
||||
editable: {
|
||||
default: true,
|
||||
},
|
||||
max: {},
|
||||
},
|
||||
emits: ['update:modelValue', 'finish'],
|
||||
data() {
|
||||
return {
|
||||
inputBorder: false,
|
||||
tagText: '',
|
||||
type TProps = {
|
||||
label?: string
|
||||
modelValue?: string
|
||||
editable?: boolean
|
||||
max?: string
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event:'update:modelValue', data: string): void
|
||||
(event: 'finish', data: string): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
label: '',
|
||||
modelValue: '',
|
||||
editable: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<TEmits>()
|
||||
const inputBorder = ref(false)
|
||||
const tagText = ref<string>('')
|
||||
|
||||
const dealValue = computed(() => {
|
||||
return props.modelValue
|
||||
})
|
||||
|
||||
function updateValue(value: string) {
|
||||
emit('update:modelValue', getValue(value))
|
||||
}
|
||||
|
||||
function focusInput() {
|
||||
inputBorder.value = true
|
||||
tagText.value = props.modelValue
|
||||
}
|
||||
|
||||
function blurInput() {
|
||||
inputBorder.value = false
|
||||
let v = getValue(props.modelValue)
|
||||
if (v !== tagText.value) {
|
||||
emit('finish', v)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dealValue() {
|
||||
return this.modelValue
|
||||
// return this.modelValue.replace(/<br\/>/g, '\r\n').replace(/ /g, ' ')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateValue(value) {
|
||||
this.$emit('update:modelValue', this.getValue(value))
|
||||
},
|
||||
focusInput() {
|
||||
this.inputBorder = true
|
||||
this.tagText = this.modelValue
|
||||
},
|
||||
blurInput() {
|
||||
this.inputBorder = false
|
||||
let v = this.getValue(this.modelValue)
|
||||
if (v !== this.tagText) {
|
||||
this.$emit('finish', v)
|
||||
}
|
||||
},
|
||||
getValue(value) {
|
||||
}
|
||||
function getValue(value: string) {
|
||||
return value.replace(/\n|\r\n/g, '<br/>').replace(/ /g, ' ')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -13,18 +13,22 @@
|
||||
<el-popover placement="bottom-end" trigger="click" width="auto">
|
||||
<!-- 单列表 -->
|
||||
<ul v-if="data && Array.isArray(data)" class="list-ul">
|
||||
<li v-for="listItem in data" :key="typeof listItem === 'object' ? listItem.alias : listItem" :class="{ active: listItem == innerValue }" @click="selectItem(listItem)">
|
||||
<img v-if="listItem.preview" class="preview" :src="listItem.preview" />
|
||||
<li
|
||||
v-for="listItem in data" :key="typeof listItem === 'object' ? listItem.alias : listItem"
|
||||
:class="{ active: listItem == state.innerValue }"
|
||||
@click="selectItem(listItem)"
|
||||
>
|
||||
<img v-if="listItem.preview" class="preview" :src="listItem.preview" alt="preview" />
|
||||
<span v-else>{{ (typeof listItem === 'object' ? listItem.alias : listItem) + suffix }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- tab分类列表 -->
|
||||
<div v-else class="tabs-wrap">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tabs v-model="state.activeTab">
|
||||
<el-tab-pane v-for="(val, key, i) in data" :key="'tab' + i" :label="key" :name="key">
|
||||
<ul class="list-ul">
|
||||
<li v-for="listItem in data[key]" :key="typeof listItem === 'object' ? listItem.alias : listItem" :class="{ active: listItem == innerValue }" @click="selectItem(listItem)">
|
||||
<img v-if="listItem.preview" class="preview" :src="listItem.preview" />
|
||||
<li v-for="listItem in data[key]" :key="typeof listItem === 'object' ? listItem.alias : listItem" :class="{ active: listItem == state.innerValue }" @click="selectItem(listItem)">
|
||||
<img v-if="listItem.preview" class="preview" :src="listItem.preview" alt="preview" />
|
||||
<span v-else :style="{ fontFamily: `'${listItem.value}'` }">{{ (typeof listItem === 'object' ? listItem.alias : listItem) + suffix }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
@ -32,9 +36,16 @@
|
||||
</el-tabs>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div :class="['input-wrap', { active: inputBorder }]" :style="{ width: inputWidth }">
|
||||
<div :class="['input-wrap', { active: state.inputBorder }]" :style="{ width: inputWidth }">
|
||||
<!-- <img v-if="innerPreview" class="preview" :src="innerPreview" /> -->
|
||||
<input :style="{ fontFamily: modelValue.value }" :class="['real-input', { disable: !disable }]" :readonly="readonly ? 'readonly' : false" type="text" :value="showValue" @input="inputText" @focus="inputBorder = true" @blur="inputBorder = false" @keydown="(e) => opNumber(e)" />
|
||||
<input
|
||||
:style="{ fontFamily: modelValue.value }"
|
||||
:class="['real-input', { disable: !disable }]"
|
||||
:readonly="readonly" type="text"
|
||||
:value="showValue"
|
||||
@input="inputText" @focus="state.inputBorder = true"
|
||||
@blur="state.inputBorder = false" @keydown="(e) => opNumber(e)"
|
||||
/>
|
||||
<!-- <span class="input-unit">{{ suffix }}</span> -->
|
||||
<div class="op-btn">
|
||||
<!-- <div class="down" @click="inputBorder = !inputBorder"></div> -->
|
||||
@ -46,124 +57,127 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
// 下拉选择框
|
||||
const NAME = 'value-input'
|
||||
import { ElTabPane, ElTabs } from 'element-plus'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
components: { ElTabPane, ElTabs },
|
||||
props: {
|
||||
label: {
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
default: '',
|
||||
},
|
||||
suffix: {
|
||||
default: '',
|
||||
},
|
||||
data: {
|
||||
required: true,
|
||||
},
|
||||
disable: {
|
||||
default: true,
|
||||
},
|
||||
inputWidth: {
|
||||
default: '80px',
|
||||
},
|
||||
// textAlign: {
|
||||
// default: 'center',
|
||||
// },
|
||||
readonly: {
|
||||
default: false,
|
||||
},
|
||||
step: {
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
emits: ['finish', 'update:modelValue'],
|
||||
data() {
|
||||
return {
|
||||
type TProps = {
|
||||
label?: string
|
||||
modelValue?: Record<string, any>
|
||||
suffix?: string
|
||||
data: Record<string, any>
|
||||
disable?: boolean
|
||||
inputWidth?: string
|
||||
readonly?: boolean
|
||||
step?: number
|
||||
}
|
||||
|
||||
type TEmits = {
|
||||
(event:'update:modelValue', data: Record<string, any> | string | number): void
|
||||
(event: 'finish', data: Record<string, any> | string | number): void
|
||||
}
|
||||
|
||||
type TState = {
|
||||
inputBorder: boolean
|
||||
tagText: string
|
||||
width: string | number
|
||||
innerValue: string
|
||||
innerPreview: string
|
||||
activeTab: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
label: '',
|
||||
modelValue: () => ({}),
|
||||
suffic: '',
|
||||
data: () => ({}),
|
||||
disable: true,
|
||||
inputWidth: '80px',
|
||||
readonly: false,
|
||||
step: 1,
|
||||
})
|
||||
const emit = defineEmits<TEmits>()
|
||||
const state = reactive<TState>({
|
||||
inputBorder: false,
|
||||
tagText: '',
|
||||
width: '0',
|
||||
innerValue: '',
|
||||
innerPreview: '',
|
||||
activeTab: '中文',
|
||||
})
|
||||
const selectRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const showValue = computed(() => {
|
||||
return state.innerValue
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
state.innerValue = typeof props.modelValue === 'object' ? props.modelValue.alias : props.modelValue
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showValue() {
|
||||
// return this.innerValue + this.suffix
|
||||
return this.innerValue
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(value) {
|
||||
this.innerValue = typeof this.modelValue === 'object' ? this.modelValue.alias : this.modelValue
|
||||
},
|
||||
inputBorder(value) {
|
||||
)
|
||||
|
||||
watch(
|
||||
() => state.inputBorder,
|
||||
(value) => {
|
||||
if (value) {
|
||||
this.tagText = this.innerValue
|
||||
state.tagText = state.innerValue
|
||||
} else {
|
||||
if (this.innerValue !== this.tagText) {
|
||||
this.$emit('finish', this.innerValue)
|
||||
if (state.innerValue !== state.tagText) {
|
||||
emit('finish', state.innerValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.innerValue = typeof this.modelValue === 'object' ? this.modelValue.alias : this.modelValue
|
||||
},
|
||||
mounted() {
|
||||
this.width = this.$refs.select.offsetWidth
|
||||
// if (Object.prototype.toString.call(this.data) === '[Object Object]') {
|
||||
// for (const key in this.data) {
|
||||
// console.log(key)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
},
|
||||
methods: {
|
||||
selectItem(item) {
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
state.innerValue = typeof props.modelValue === 'object' ? props.modelValue.alias : props.modelValue
|
||||
if (selectRef.value) {
|
||||
state.width = selectRef.value.offsetWidth
|
||||
}
|
||||
})
|
||||
|
||||
function selectItem(item: Record<string, any>) {
|
||||
let value = typeof item === 'object' ? item.alias : item
|
||||
if (this.innerValue !== value) {
|
||||
this.innerValue = value
|
||||
this.innerPreview = item.preview
|
||||
this.$emit('finish', item)
|
||||
if (state.innerValue !== value) {
|
||||
state.innerValue = value
|
||||
state.innerPreview = item.preview
|
||||
emit('finish', item)
|
||||
}
|
||||
},
|
||||
inputText(e) {
|
||||
}
|
||||
|
||||
function inputText(e: Event) {
|
||||
// this.innerValue = e.target.value.replace(RegExp(this.suffix), '')
|
||||
this.innerValue = e.target.value
|
||||
state.innerValue = (e.target as HTMLInputElement).value
|
||||
setTimeout(() => {
|
||||
this.$emit('finish', this.innerValue)
|
||||
emit('finish', state.innerValue)
|
||||
}, 100)
|
||||
},
|
||||
opNumber(e) {
|
||||
}
|
||||
function opNumber(e: KeyboardEvent) {
|
||||
e.stopPropagation()
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
typeof this.innerValue === 'number' && this.up()
|
||||
typeof state.innerValue === 'number' && up()
|
||||
return
|
||||
case 40:
|
||||
typeof this.innerValue === 'number' && this.down()
|
||||
typeof state.innerValue === 'number' && down()
|
||||
return
|
||||
}
|
||||
},
|
||||
up() {
|
||||
this.$emit('update:modelValue', parseInt(this.modelValue || 0, 10) + this.step)
|
||||
},
|
||||
down() {
|
||||
let value = parseInt(this.modelValue || 0, 10) - this.step
|
||||
}
|
||||
|
||||
function up() {
|
||||
emit('update:modelValue', parseInt(`${props.modelValue}` ?? '0', 10) + props.step)
|
||||
}
|
||||
|
||||
function down() {
|
||||
let value = parseInt(`${props.modelValue}` ?? '0', 10) - props.step
|
||||
if (value < 0) {
|
||||
value = 0
|
||||
}
|
||||
this.$emit('update:modelValue', value)
|
||||
},
|
||||
},
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
23
src/components/modules/widgets/wGroup/groupSetting.ts
Normal file
23
src/components/modules/widgets/wGroup/groupSetting.ts
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
|
||||
export const wGroupSetting = {
|
||||
name: '组合',
|
||||
type: 'w-group',
|
||||
uuid: -1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
transform: '',
|
||||
opacity: 1,
|
||||
parent: '-1',
|
||||
isContainer: true,
|
||||
record: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
dir: 'none',
|
||||
},
|
||||
}
|
||||
|
@ -8,11 +8,11 @@
|
||||
<template>
|
||||
<div
|
||||
ref="widget"
|
||||
:class="['w-group', { 'layer-lock': params.lock }]"
|
||||
:class="['w-group', { 'layer-lock': props.params?.lock }]"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: params.left - parent.left + 'px',
|
||||
top: params.top - parent.top + 'px',
|
||||
left: (props.params.left || 0) - (props.parent?.left || 0) + 'px',
|
||||
top: (props.params.top || 0) - (props.parent.top || 0) + 'px',
|
||||
width: params.width + 'px',
|
||||
height: params.height + 'px',
|
||||
opacity: params.opacity,
|
||||
@ -22,16 +22,40 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
// 组合组件
|
||||
const NAME = 'w-group'
|
||||
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref } from 'vue'
|
||||
import { mapGetters, mapActions, useStore } from 'vuex'
|
||||
import { setTransformAttribute } from '@/common/methods/handleTransform'
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters';
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
setting: {
|
||||
type TParamsData = {
|
||||
left: number
|
||||
top: number
|
||||
width: number
|
||||
height: number
|
||||
opacity: number
|
||||
rotate: number
|
||||
uuid: string
|
||||
lock: boolean
|
||||
fontSize: number
|
||||
}
|
||||
|
||||
type TProps = {
|
||||
params?: Partial<TParamsData>
|
||||
parent?: Partial<Pick<TParamsData, "top" | "left">>
|
||||
}
|
||||
const props = withDefaults(defineProps<TProps>(), {
|
||||
params: () => ({}),
|
||||
parent: () => ({})
|
||||
})
|
||||
const store = useStore();
|
||||
const widget = ref<HTMLElement | null>(null)
|
||||
const ratio = ref(0)
|
||||
const temp = ref<Record<string, any>>({})
|
||||
const compWidgetsRecord = ref<Record<string, any>>({})
|
||||
const setting = {
|
||||
name: '组合',
|
||||
type: NAME,
|
||||
uuid: -1,
|
||||
@ -50,156 +74,171 @@ export default {
|
||||
minHeight: 0,
|
||||
dir: 'none',
|
||||
},
|
||||
},
|
||||
props: ['params', 'parent'],
|
||||
data() {
|
||||
return {
|
||||
// loading: false,
|
||||
timer: null,
|
||||
}
|
||||
|
||||
const timer = ref<number | null>(null)
|
||||
const { dActiveElement, dWidgets } = useSetupMapGetters(['dActiveElement', 'dWidgets'])
|
||||
|
||||
// watch: {
|
||||
// params: {
|
||||
// async handler(nval) {
|
||||
// this.updateRecord(nval.tempScale)
|
||||
// },
|
||||
// immediate: true,
|
||||
// deep: true,
|
||||
// },
|
||||
// },
|
||||
|
||||
onUpdated(() => {
|
||||
updateRecord()
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
touchstart()
|
||||
updateRecord()
|
||||
document.addEventListener('mousedown', touchstart, false)
|
||||
document.addEventListener('mouseup', touchend, false)
|
||||
if (props.params?.rotate && widget.value) {
|
||||
(widget.value.style.transform += `rotate(${props.params.rotate})`)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dActiveElement', 'dWidgets']),
|
||||
},
|
||||
// watch: {
|
||||
// params: {
|
||||
// async handler(nval) {
|
||||
// this.updateRecord(nval.tempScale)
|
||||
// },
|
||||
// immediate: true,
|
||||
// deep: true,
|
||||
// },
|
||||
// },
|
||||
updated() {
|
||||
this.updateRecord()
|
||||
},
|
||||
async mounted() {
|
||||
await this.$nextTick()
|
||||
this.touchstart()
|
||||
this.updateRecord()
|
||||
document.addEventListener('mousedown', this.touchstart, false)
|
||||
document.addEventListener('mouseup', this.touchend, false)
|
||||
this.params.rotate && (this.$refs.widget.style.transform += `rotate(${this.params.rotate})`)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('mousedown', this.touchstart, false)
|
||||
document.removeEventListener('mouseup', this.touchend, false)
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateWidgetData']),
|
||||
updateRecord(tempScale) {
|
||||
if (this.dActiveElement.uuid === this.params.uuid) {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('mousedown', touchstart, false)
|
||||
document.removeEventListener('mouseup', touchend, false)
|
||||
})
|
||||
// ...mapActions(['updateWidgetData']),
|
||||
|
||||
function updateRecord(tempScale ?: number) {
|
||||
if (dActiveElement.value.uuid === props.params.uuid) {
|
||||
// clearTimeout(this.timer)
|
||||
let record = this.dActiveElement.record
|
||||
let record = dActiveElement.value.record
|
||||
if (record.width <= 0) {
|
||||
this.touchend()
|
||||
touchend()
|
||||
}
|
||||
// if (this.tempRecord && this.tempRecord.width && this.tempRecord.width != record.width) {
|
||||
// return
|
||||
// }
|
||||
this.ratio = tempScale || this.params.width / record.width
|
||||
ratio.value = tempScale || (props.params.width || 0) / record.width
|
||||
|
||||
if (this.ratio != 1) {
|
||||
this.temp = {}
|
||||
if (ratio.value != 1) {
|
||||
if (record.width != 0) {
|
||||
for (let i = this.dWidgets.length - 1; i >= 0; --i) {
|
||||
if (this.dWidgets[i].parent === this.params.uuid) {
|
||||
this.temp[this.dWidgets[i].uuid] = { width: this.dWidgets[i].width * this.ratio, height: this.dWidgets[i].height * this.ratio, raw: this.dWidgets[i] }
|
||||
for (let i = dWidgets.value.length - 1; i >= 0; --i) {
|
||||
if (dWidgets.value[i].parent === props.params.uuid) {
|
||||
temp.value[dWidgets.value[i].uuid] = { width: dWidgets.value[i].width * ratio.value, height: dWidgets.value[i].height * ratio.value, raw: dWidgets.value[i] }
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO DOM Change
|
||||
// this.dActiveElement.scale = this.ratio
|
||||
this.$refs.widget.style.transformOrigin = 'left top' // 设置scale的原点
|
||||
setTransformAttribute(this.$refs.widget, 'scale', this.ratio)
|
||||
if (widget.value) {
|
||||
widget.value.style.transformOrigin = 'left top' // 设置scale的原点
|
||||
setTransformAttribute(widget.value, 'scale', ratio.value)
|
||||
}
|
||||
// this.timer = setTimeout(() => {
|
||||
// this.touchend()
|
||||
// }, 300)
|
||||
}
|
||||
}
|
||||
},
|
||||
touchstart() {
|
||||
if (this.dActiveElement.uuid !== this.params.uuid) {
|
||||
}
|
||||
|
||||
function touchstart() {
|
||||
if (dActiveElement.value.uuid !== props.params.uuid) {
|
||||
return
|
||||
}
|
||||
this.tempRecord = {
|
||||
width: this.params.width,
|
||||
height: this.params.height,
|
||||
const tempRecord = {
|
||||
width: props.params.width,
|
||||
height: props.params.height,
|
||||
}
|
||||
this.compWidgetsRecord = {}
|
||||
for (let i = this.dWidgets.length - 1; i >= 0; --i) {
|
||||
if (this.dWidgets[i].parent === this.params.uuid) {
|
||||
this.compWidgetsRecord[this.dWidgets[i].uuid] = {
|
||||
left: Number(document.getElementById(this.dWidgets[i].uuid).style.left.replace('px', '')),
|
||||
top: Number(document.getElementById(this.dWidgets[i].uuid).style.top.replace('px', '')),
|
||||
fontSize: Number(document.getElementById(this.dWidgets[i].uuid).style.fontSize?.replace('px', '')),
|
||||
for (let i = dWidgets.value.length - 1; i >= 0; --i) {
|
||||
if (dWidgets.value[i].parent === props.params.uuid) {
|
||||
const el = document.getElementById(dWidgets.value[i].uuid)
|
||||
if (el) {
|
||||
compWidgetsRecord.value[dWidgets.value[i].uuid] = {
|
||||
left: Number(el.style.left.replace('px', '')),
|
||||
top: Number(el.style.top.replace('px', '')),
|
||||
fontSize: Number(el.style.fontSize?.replace('px', '')),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
touchend() {
|
||||
if (this.dActiveElement.uuid !== this.params.uuid) {
|
||||
}
|
||||
}
|
||||
|
||||
function touchend() {
|
||||
if (dActiveElement.value.uuid !== props.params.uuid) {
|
||||
return
|
||||
}
|
||||
// const opacity = this.$refs.widget.style.opacity
|
||||
// this.$refs.widget.style.opacity = 1
|
||||
setTimeout(() => {
|
||||
if (!this.temp) {
|
||||
if (!temp.value || !widget.value) {
|
||||
return
|
||||
}
|
||||
this.$refs.widget.style.opacity = 0
|
||||
setTransformAttribute(this.$refs.widget, 'scale', 1)
|
||||
widget.value.style.opacity = `${0}`
|
||||
setTransformAttribute(widget.value, 'scale', 1)
|
||||
setTimeout(() => {
|
||||
this.$refs.widget.style.opacity = this.params.opacity
|
||||
if (!widget.value) return
|
||||
widget.value.style.opacity = `${props.params.opacity}`
|
||||
// this.$refs.widget.style.transformOrigin = 'center' // 设置scale的原点
|
||||
}, 100)
|
||||
|
||||
// const opacity = this.$refs.widget.style.opacity
|
||||
// setTransformAttribute(this.$refs.widget, 'scale', 1)
|
||||
for (const key in this.temp) {
|
||||
if (Object.hasOwnProperty.call(this.temp, key)) {
|
||||
this.keyChange(key, 'width', this.temp[key].width)
|
||||
this.keyChange(key, 'height', this.temp[key].height)
|
||||
for (const key in temp.value) {
|
||||
if (Object.hasOwnProperty.call(temp.value, key)) {
|
||||
keyChange(key, 'width', temp.value[key].width)
|
||||
keyChange(key, 'height', temp.value[key].height)
|
||||
// 重新拿前面设定好的,实时DOM修改过了
|
||||
this.keySetValue(key, 'left', this.compWidgetsRecord[key].left * this.ratio)
|
||||
this.keySetValue(key, 'top', this.compWidgetsRecord[key].top * this.ratio)
|
||||
keySetValue(key, 'left', compWidgetsRecord.value[key].left * ratio.value)
|
||||
keySetValue(key, 'top', compWidgetsRecord.value[key].top * ratio.value)
|
||||
// this.keySetValue(key, 'left', Number(document.getElementById(key).style.left.replace('px', '')) * this.ratio)
|
||||
// this.keySetValue(key, 'top', Number(document.getElementById(key).style.top.replace('px', '')) * this.ratio)
|
||||
if (this.temp[key].raw.type === 'w-text') {
|
||||
this.keyChange(key, 'fontSize', this.compWidgetsRecord[key].fontSize * this.ratio)
|
||||
if (temp.value[key].raw.type === 'w-text') {
|
||||
keyChange(key, 'fontSize', compWidgetsRecord.value[key].fontSize * ratio.value)
|
||||
// this.keyChange(key, 'fontSize', this.temp[key].raw.fontSize * this.ratio)
|
||||
// this.keyChange(key, 'letterSpacing', this.temp[key].raw.letterSpacing * this.ratio)
|
||||
}
|
||||
}
|
||||
}
|
||||
// this.$refs.widget.style.opacity = opacity
|
||||
this.temp = null
|
||||
temp.value = {}
|
||||
|
||||
if (this.dActiveElement.uuid === this.params.uuid) {
|
||||
let record = this.dActiveElement.record
|
||||
record.width = this.$refs.widget?.offsetWidth
|
||||
record.height = this.$refs.widget?.offsetHeight
|
||||
this.dActiveElement.width = this.$refs.widget?.offsetWidth
|
||||
this.dActiveElement.height = this.$refs.widget?.offsetHeight
|
||||
if (dActiveElement.value.uuid === props.params.uuid) {
|
||||
let record = dActiveElement.value.record
|
||||
record.width = widget.value?.offsetWidth
|
||||
record.height = widget.value?.offsetHeight
|
||||
dActiveElement.value.width = widget.value?.offsetWidth
|
||||
dActiveElement.value.height = widget.value?.offsetHeight
|
||||
}
|
||||
}, 10)
|
||||
},
|
||||
keyChange(uuid, key, value) {
|
||||
this.updateWidgetData({
|
||||
}
|
||||
function keyChange(uuid: string, key: keyof TParamsData, value: number) {
|
||||
store.dispatch('updateWidgetData', {
|
||||
uuid,
|
||||
key,
|
||||
value,
|
||||
pushHistory: false,
|
||||
})
|
||||
},
|
||||
keySetValue(uuid, key, value) {
|
||||
setTimeout(() => {
|
||||
const widget = this.dWidgets.find((item) => item.uuid === uuid)
|
||||
widget[key] = value + Number(this.params[key])
|
||||
}, 10)
|
||||
},
|
||||
},
|
||||
// updateWidgetData({
|
||||
// uuid,
|
||||
// key,
|
||||
// value,
|
||||
// pushHistory: false,
|
||||
// })
|
||||
}
|
||||
|
||||
function keySetValue(uuid: string, key: keyof TParamsData, value: number) {
|
||||
setTimeout(() => {
|
||||
const widget = dWidgets.value.find((item: TParamsData) => item.uuid === uuid)
|
||||
widget[key] = value + Number(props.params[key] || '')
|
||||
}, 10)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
setting
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -20,3 +20,7 @@ export default {
|
||||
QINIUYUN_PLUGIN: 'https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/qiniu-js/2.5.5/qiniu.min.js',
|
||||
supportSubFont: true, // 是否开启服务端字体压缩
|
||||
}
|
||||
|
||||
export const LocalStorageKey = {
|
||||
tokenKey: "xp_token"
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-03-03 14:13:16
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-06-29 15:11:46
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-02-26 17:54:00
|
||||
*/
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
@ -20,7 +20,7 @@ elementConfig.components.forEach((component) => {
|
||||
app.component(component.name, component)
|
||||
})
|
||||
|
||||
elementConfig.plugins.forEach((plugin: any) => {
|
||||
elementConfig.plugins.forEach((plugin) => {
|
||||
app.use(plugin)
|
||||
})
|
||||
|
||||
|
@ -48,7 +48,7 @@ const move = {
|
||||
|
||||
const moveInit = {
|
||||
methods: {
|
||||
initmovement(e: any) {
|
||||
initmovement(e: MouseEvent) {
|
||||
if (!store.getters.dAltDown) {
|
||||
// 设置mouseevent给moveable初始
|
||||
// 在组合操作时排除
|
||||
|
@ -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)
|
||||
},
|
||||
},
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-19 18:43:22
|
||||
@ -40,4 +42,4 @@ export default [
|
||||
name: 'Psd',
|
||||
component: () => import(/* webpackChunkName: 'psd' */ '@/views/Psd.vue'),
|
||||
},
|
||||
]
|
||||
] as RouteRecordRaw[]
|
||||
|
@ -1,8 +1,10 @@
|
||||
// import store from '@/store'
|
||||
|
||||
export default (router: Type.Object) => {
|
||||
import { NavigationGuardNext, RouteLocationNormalized, Router } from "vue-router"
|
||||
|
||||
router.beforeEach((to: Type.Object, from: Type.Object, next: () => void) => {
|
||||
export default (router: Router) => {
|
||||
|
||||
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
|
||||
// if (to.meta.requireAuth) { }
|
||||
|
||||
// 有必要时清除残余的loading框
|
||||
|
11
src/types/env.d.ts
vendored
11
src/types/env.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
/// <reference lib="dom" />
|
||||
// / <reference lib="dom" />
|
||||
|
||||
interface ImportMeta {
|
||||
url: string
|
||||
@ -23,13 +23,14 @@ interface ImportMeta {
|
||||
on(event: string, cb: (...args: any[]) => void): void
|
||||
}
|
||||
|
||||
readonly env: ImportMetaEnv
|
||||
// readonly env: ImportMetaEnv
|
||||
|
||||
glob(pattern: string): Record<
|
||||
|
||||
glob(pattern: string, { eager: boolean }): Record<
|
||||
string,
|
||||
() => Promise<{
|
||||
{
|
||||
[key: string]: any
|
||||
}>
|
||||
}
|
||||
>
|
||||
|
||||
globEager(pattern: string): Record<
|
||||
|
65
src/types/global.d.ts
vendored
Normal file
65
src/types/global.d.ts
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
|
||||
/** 公共API返回结果 */
|
||||
type TCommResResult<T> = {
|
||||
code: number
|
||||
msg: string
|
||||
result: T
|
||||
}
|
||||
|
||||
type TCommonItemData = {
|
||||
type: string
|
||||
fontFamily?: string
|
||||
color?: string
|
||||
fontSize: 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 }
|
||||
key: string
|
||||
hash: string
|
||||
}): void
|
||||
}
|
||||
|
||||
interface Window {
|
||||
qiniu: {
|
||||
upload: (
|
||||
file: File | Blob,
|
||||
name: string,
|
||||
token: string,
|
||||
exObj: Record<string, any>,
|
||||
exOption: {
|
||||
useCdnDomain: boolean
|
||||
}) => {
|
||||
subscribe: (cb: {
|
||||
next: IQiniuSubscribeCb
|
||||
error: (err: string) => void
|
||||
complete: IQiniuSubscribeCb
|
||||
}) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface MouseEvent {
|
||||
layerX: number
|
||||
layerY: number
|
||||
}
|
||||
|
@ -2,12 +2,12 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-07-13 02:48:38
|
||||
* @Description: 本地测试项目请勿修改此文件
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-11 17:36:33
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-02-26 17:54:00
|
||||
*/
|
||||
import axios from 'axios'
|
||||
import axios, { AxiosRequestConfig, AxiosResponse, AxiosStatic } from 'axios'
|
||||
import store from '@/store'
|
||||
import app_config from '@/config'
|
||||
import app_config, { LocalStorageKey } from '@/config'
|
||||
|
||||
axios.defaults.timeout = 30000
|
||||
axios.defaults.headers.authorization = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTAwMDEsImV4cCI6MTc4ODU3NDc1MDU4NX0.L_t6DFD48Dm6rUPfgIgOWJkz18En1m_-hhMHcpbxliY';
|
||||
@ -16,15 +16,15 @@ const baseUrl = app_config.API_URL
|
||||
|
||||
// 请求拦截器
|
||||
axios.interceptors.request.use(
|
||||
(config: Type.Object) => {
|
||||
(config: AxiosRequestConfig) => {
|
||||
// const access_token = store.state.currentUser.access_token;
|
||||
const url = config.url
|
||||
const url = config.url ?? ""
|
||||
const values = {}
|
||||
// values.access_token = access_token;
|
||||
// values.version = version;
|
||||
|
||||
if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) {
|
||||
url.indexOf('/') === 0 ? (config.url = baseUrl + url) : (config.url = baseUrl + '/' + url)
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
config.url = url.startsWith('/') ? baseUrl + url : config.url = baseUrl + '/' + url
|
||||
}
|
||||
|
||||
if (config.method === 'get') {
|
||||
@ -44,10 +44,8 @@ axios.interceptors.request.use(
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
axios.interceptors.response.use(
|
||||
(res: Type.Object) => {
|
||||
axios.interceptors.response.use((res: AxiosResponse<any>) => {
|
||||
// store.dispatch('hideLoading');
|
||||
|
||||
// 接口规则:只有正确code为200时返回result结果对象,错误返回整个结果对象
|
||||
|
||||
if (!res.data) {
|
||||
@ -74,16 +72,28 @@ axios.interceptors.response.use(
|
||||
},
|
||||
)
|
||||
|
||||
type TFetchRequestConfigParams = AxiosRequestConfig & Record<string, any>
|
||||
type TFetchMethod = keyof Pick<
|
||||
AxiosStatic,
|
||||
"get" | "post" | "put" | "getUri" | "request" | "delete" | "head" | "options" | "patch"
|
||||
>
|
||||
|
||||
// export default axios;
|
||||
const fetch = (url: string, params: Type.Object, type: string | undefined = 'get', exheaders: Type.Object = {}, extra: any = {}) => {
|
||||
if (params && params._noLoading) {
|
||||
const fetch = <T = any> (
|
||||
url: string,
|
||||
params: TFetchRequestConfigParams,
|
||||
type: TFetchMethod = 'get',
|
||||
exheaders: Record<string, any> = {},
|
||||
extra: Record<string, any> = {}
|
||||
): Promise<T> => {
|
||||
if (params?._noLoading) {
|
||||
delete params._noLoading
|
||||
} else {
|
||||
// store.commit('loading', '加载中..');
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('xp_token')
|
||||
const headerObject: Type.Object = { }
|
||||
const token = localStorage.getItem(LocalStorageKey.tokenKey)
|
||||
const headerObject: Record<string, any> = {}
|
||||
token && (headerObject.authorization = token)
|
||||
|
||||
if (type === 'get') {
|
||||
@ -93,10 +103,10 @@ const fetch = (url: string, params: Type.Object, type: string | undefined = 'get
|
||||
...extra,
|
||||
})
|
||||
} else {
|
||||
return (axios as Type.Object)[type](url, params, {
|
||||
return axios[type](url, params, {
|
||||
headers: Object.assign(headerObject, exheaders),
|
||||
...extra,
|
||||
})
|
||||
}) as Promise<T>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-07-13 02:48:38
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2022-03-07 20:25:54
|
||||
*/
|
||||
// import store from '../store'
|
||||
@ -11,12 +11,13 @@ import * as utils from './utils'
|
||||
import _config from '@/config'
|
||||
import modules from './plugins/modules'
|
||||
import cssLoader from './plugins/cssLoader'
|
||||
import type {App} from 'vue'
|
||||
|
||||
/**
|
||||
* 全局组件方法
|
||||
*/
|
||||
export default {
|
||||
install(myVue: Type.Object) {
|
||||
install(myVue: App) {
|
||||
/** 全局组件注册 */
|
||||
modules(myVue)
|
||||
/** iconfont 注入 */
|
||||
|
@ -7,8 +7,9 @@
|
||||
*/
|
||||
// import { Button, Field, Divider, NavBar, Toast, Popup } from 'vant'
|
||||
import coms from '@/components/modules'
|
||||
import { App } from 'vue'
|
||||
|
||||
export default (Vue: any) => {
|
||||
export default (Vue: App) => {
|
||||
coms(Vue)
|
||||
// Vue.component(Button.name, Button)
|
||||
// Vue.use(Field).use(Divider).use(NavBar).use(Toast).use(Popup)
|
||||
|
@ -6,9 +6,10 @@
|
||||
* @LastEditTime: 2023-09-19 17:32:40
|
||||
*/
|
||||
export default class PointImg {
|
||||
private canvas: any
|
||||
private cvs: any
|
||||
constructor(img: any) {
|
||||
private canvas: HTMLCanvasElement | undefined
|
||||
private cvs: CanvasRenderingContext2D | null | undefined
|
||||
|
||||
constructor(img: HTMLImageElement) {
|
||||
if (img.src) {
|
||||
try {
|
||||
this.canvas = document.createElement('canvas')
|
||||
@ -16,6 +17,8 @@ export default class PointImg {
|
||||
this.canvas.height = img.height
|
||||
img.crossOrigin = 'Anonymous'
|
||||
this.cvs = this.canvas.getContext('2d')
|
||||
if (!this.cvs) return
|
||||
|
||||
this.cvs.drawImage(img, 0, 0, img.width, img.height)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
@ -28,8 +31,9 @@ export default class PointImg {
|
||||
* @param y Number y坐标起点
|
||||
* @return color Object 包含颜色的rgba #16进制颜色
|
||||
*/
|
||||
const color: any = {}
|
||||
const color: Record<string, string> = {}
|
||||
try {
|
||||
if (this.cvs) {
|
||||
const obj = this.cvs.getImageData(x, y, 1, 1)
|
||||
const arr = obj.data.toString().split(',')
|
||||
|
||||
@ -42,11 +46,12 @@ export default class PointImg {
|
||||
let third = parseInt(arr[2], 10).toString(16)
|
||||
third = third.length === 2 ? third : third + third
|
||||
|
||||
let last = parseInt(arr.pop(), 10) / 255
|
||||
let last = parseInt(arr.pop() || '0', 10) / 255
|
||||
last = Number(last.toFixed(0))
|
||||
|
||||
color['rgba'] = 'rgba(' + arr.join(',') + ',' + last + ')'
|
||||
color['#'] = '#' + first + second + third
|
||||
}
|
||||
} catch (error) {
|
||||
// console.log('此为解析图片点位异常')
|
||||
}
|
||||
|
@ -2,27 +2,27 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-12-24 15:13:58
|
||||
* @Description: 资源加载
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-19 17:19:07
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-03-05 12:00:00
|
||||
*/
|
||||
export default class PreLoad {
|
||||
private i: number
|
||||
private arr: any[]
|
||||
constructor(arr: string[]) {
|
||||
private arr: (string | HTMLImageElement | ChildNode[])[]
|
||||
constructor(arr: (string | HTMLImageElement | ChildNode[])[]) {
|
||||
this.i = 0
|
||||
this.arr = arr
|
||||
}
|
||||
public imgs() {
|
||||
return new Promise((resolve: any) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const work = (src: string) => {
|
||||
if (this.i < this.arr.length) {
|
||||
const img = new Image()
|
||||
img.src = src
|
||||
if (img.complete) {
|
||||
work(this.arr[this.i++])
|
||||
work(this.arr[this.i++] as string)
|
||||
} else {
|
||||
img.onload = () => {
|
||||
work(this.arr[this.i++])
|
||||
work(this.arr[this.i++] as string)
|
||||
img.onload = null
|
||||
}
|
||||
}
|
||||
@ -31,14 +31,14 @@ export default class PreLoad {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
work(this.arr[this.i])
|
||||
work(this.arr[this.i] as string)
|
||||
})
|
||||
}
|
||||
public doms() {
|
||||
return new Promise((resolve: Function) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const work = () => {
|
||||
if (this.i < this.arr.length) {
|
||||
this.arr[this.i].complete && this.i++
|
||||
(this.arr[this.i] as HTMLImageElement).complete && this.i++
|
||||
setTimeout(() => {
|
||||
work()
|
||||
}, 100)
|
||||
@ -51,10 +51,10 @@ export default class PreLoad {
|
||||
}
|
||||
/** 判断是否加载svg */
|
||||
public svgs() {
|
||||
return new Promise((resolve: Function) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const work = () => {
|
||||
if (this.i < this.arr.length) {
|
||||
this.arr[this.i].length > 0 && this.i++
|
||||
(this.arr[this.i] as ChildNode[]).length > 0 && this.i++
|
||||
setTimeout(() => {
|
||||
work()
|
||||
}, 100)
|
||||
|
@ -2,11 +2,11 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-03-06 13:53:30
|
||||
* @Description: 计算密集型任务
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-14 17:28:53
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-03-05 12:00:00
|
||||
*/
|
||||
export default class WebWorker {
|
||||
private worker: any
|
||||
private worker: Worker | undefined
|
||||
|
||||
constructor(name: string) {
|
||||
if (typeof Worker === 'undefined') {
|
||||
@ -21,12 +21,15 @@ export default class WebWorker {
|
||||
}
|
||||
public start(data: any) {
|
||||
return new Promise((resolve) => {
|
||||
if (!this.worker) resolve('')
|
||||
else {
|
||||
// 监听Web Worker的消息
|
||||
this.worker.onmessage = (e: any) => {
|
||||
this.worker.onmessage = (e) => {
|
||||
resolve(e.data)
|
||||
}
|
||||
// 发送数据给Web Worker
|
||||
this.worker.postMessage(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import app_config from '@/config'
|
||||
export const config = app_config
|
||||
|
||||
type TComObj = Record<string,any>
|
||||
|
||||
/**
|
||||
* 星期换算
|
||||
* @param {String} 'YYYY-MM-DD'
|
||||
@ -38,30 +40,31 @@ export const config = app_config
|
||||
// }
|
||||
// }
|
||||
// 判断是否在数组中并返回下标
|
||||
export const isInArray = (arr: Type.Object[], value: any) => {
|
||||
if (arr.indexOf && typeof arr.indexOf === 'function') {
|
||||
export const isInArray = (arr: (string | number)[], value: (string | number)) => {
|
||||
const index = arr.indexOf(value)
|
||||
if (index >= 0) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** 删除多个对象元素 */
|
||||
export const deleteSome = (obj: Type.Object, arr: string[]) => {
|
||||
export const deleteSome = <R extends TComObj, T extends TComObj = TComObj>(obj: T, arr: string[]) => {
|
||||
arr.forEach((key) => {
|
||||
delete obj[key]
|
||||
})
|
||||
return obj
|
||||
return obj as R extends T ? R : Partial<T>
|
||||
}
|
||||
|
||||
/** 拾取对象元素 */
|
||||
export const pickSome = (obj: Type.Object, arr: string[]) => {
|
||||
const newObj: Type.Object = {}
|
||||
export const pickSome = <R extends TComObj, T extends TComObj = TComObj>(obj: T, arr: string[]) => {
|
||||
const newObj: Record<string, any> = {}
|
||||
arr.forEach((key) => {
|
||||
newObj[key] = obj[key]
|
||||
})
|
||||
return newObj
|
||||
return newObj as R extends T ? R : Partial<T>
|
||||
}
|
||||
|
||||
/** String长度 */
|
||||
// export const getBLen = (str: string | any) => {
|
||||
// if (str === null) {
|
||||
|
@ -169,7 +169,7 @@ const components = [
|
||||
// ElUpload,
|
||||
]
|
||||
|
||||
const plugins: any = [
|
||||
const plugins = [
|
||||
ElInfiniteScroll,
|
||||
ElLoading,
|
||||
// ElMessage,
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-08-23 17:37:16
|
||||
* @Description: 提取字体子集
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-14 18:31:29
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastEditTime: 2024-02-27 10:32:00
|
||||
*/
|
||||
/**
|
||||
* 只有ttf/otf这种原始字体支持提取,如果服务端不支持该功能请设置false,以保证页面能加载字体。
|
||||
|
@ -8,67 +8,77 @@
|
||||
</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'
|
||||
import FontFaceObserver from 'fontfaceobserver'
|
||||
import { fontWithDraw, font2style } from '@/utils/widgets/loadFontRule'
|
||||
import designBoard from '@/components/modules/layout/designBoard.vue'
|
||||
import zoomControl from '@/components/modules/layout/zoomControl.vue'
|
||||
import designBoard from '@/components/modules/layout/designBoard/index.vue'
|
||||
import zoomControl from '@/components/modules/layout/zoomControl/index.vue'
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { wGroupSetting } from '@/components/modules/widgets/wGroup/groupSetting'
|
||||
|
||||
export default defineComponent({
|
||||
components: { designBoard, zoomControl },
|
||||
// mixins: [shortcuts],
|
||||
setup() {
|
||||
const state = reactive({
|
||||
type TState = {
|
||||
style: StyleValue
|
||||
}
|
||||
|
||||
// mixins: [shortcuts],
|
||||
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(wGroupSetting))
|
||||
// initGroupJson(JSON.stringify(wGroup.setting))
|
||||
nextTick(() => {
|
||||
load()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['initGroupJson', 'setTemplate', 'addGroup']),
|
||||
async load() {
|
||||
})
|
||||
|
||||
// ...mapActions(['initGroupJson', 'setTemplate', 'addGroup']),
|
||||
async function load() {
|
||||
let loadFlag = false
|
||||
const { id, tempid, tempType: type } = this.$route.query
|
||||
const { id, tempid, tempType: type } = route.query
|
||||
if (id || tempid) {
|
||||
const { data, width, height } = await api.home[id ? 'getWorks' : 'getTempDetail']({ id: id || tempid, type })
|
||||
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 = type == 1 ? content : content.widgets
|
||||
const widgets = Number(type) == 1 ? content : content.widgets
|
||||
|
||||
if (type == 1) {
|
||||
this.dPage.width = width
|
||||
this.dPage.height = height
|
||||
this.dPage.backgroundColor = '#ffffff00'
|
||||
this.addGroup(content)
|
||||
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 +141,7 @@ export default defineComponent({
|
||||
setTimeout(() => {
|
||||
!loadFlag && (window as any).loadFinishToInject('done')
|
||||
}, 60000)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -1,9 +1,17 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-09-18 17:34:44
|
||||
* @Description:
|
||||
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @LastUpdateContent: Support typescript
|
||||
* @LastEditTime: 2024-02-25 14:51:00
|
||||
-->
|
||||
<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>
|
||||
@ -12,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">
|
||||
@ -26,54 +34,70 @@
|
||||
<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" cancelText="取消" @cancel="downloadCancel" @done="downloadPercent = 0" />
|
||||
<ProgressLoading
|
||||
:percent="state.downloadPercent"
|
||||
:text="state.downloadText"
|
||||
cancelText="取消"
|
||||
@cancel="downloadCancel"
|
||||
@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.vue'
|
||||
import zoomControl from '@/components/modules/layout/zoomControl.vue'
|
||||
import designBoard from '@/components/modules/layout/designBoard/index.vue'
|
||||
import zoomControl from '@/components/modules/layout/zoomControl/index.vue'
|
||||
import lineGuides from '@/components/modules/layout/lineGuides.vue'
|
||||
|
||||
import shortcuts from '@/mixins/shortcuts'
|
||||
import wGroup from '@/components/modules/widgets/wGroup/wGroup.vue'
|
||||
import HeaderOptions from './components/HeaderOptions.vue'
|
||||
import ProgressLoading from '@/components/common/ProgressLoading/index.vue'
|
||||
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { wGroupSetting } from '@/components/modules/widgets/wGroup/groupSetting'
|
||||
|
||||
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)
|
||||
// mixins: [shortcuts],
|
||||
!_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',
|
||||
},
|
||||
@ -83,93 +107,123 @@ export default defineComponent({
|
||||
isContinue: true,
|
||||
APP_NAME: _config.APP_NAME,
|
||||
showLineGuides: false,
|
||||
})
|
||||
// const draw = () => {
|
||||
// state.openDraw = true
|
||||
// }
|
||||
function jump2home() {
|
||||
})
|
||||
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)
|
||||
},
|
||||
},
|
||||
// watch: {
|
||||
// $route() {
|
||||
// console.log('change route', this.$route.query)
|
||||
// this.loadData()
|
||||
// },
|
||||
// },
|
||||
mounted() {
|
||||
this.initGroupJson(JSON.stringify(wGroup.setting))
|
||||
window.addEventListener('scroll', this.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)
|
||||
// window.removeEventListener('click', this.clickListener)
|
||||
document.removeEventListener('keydown', this.handleKeydowm, false)
|
||||
document.removeEventListener('keyup', this.handleKeyup, false)
|
||||
document.oncontextmenu = null
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['selectWidget', 'initGroupJson', 'handleHistory']),
|
||||
changeLineGuides() {
|
||||
this.showLineGuides = !this.showLineGuides
|
||||
},
|
||||
downloadCancel() {
|
||||
this.downloadPercent = 0
|
||||
this.isContinue = false
|
||||
},
|
||||
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()
|
||||
// 初始化激活的控件为page
|
||||
this.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() {
|
||||
const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
|
||||
this.style.left = `-${scrollLeft}px`
|
||||
},
|
||||
clickListener(e) {
|
||||
console.log('click listener', e)
|
||||
},
|
||||
optionsChange({ downloadPercent, downloadText }: any) {
|
||||
this.downloadPercent = downloadPercent
|
||||
this.downloadText = downloadText
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
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()
|
||||
// },
|
||||
// },
|
||||
|
||||
const { handleKeydowm, handleKeyup, dealCtrl } = shortcuts.methods
|
||||
let checkCtrl: number | undefined
|
||||
|
||||
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('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('keyup', handleKeyup(store, checkCtrl), false)
|
||||
document.oncontextmenu = null
|
||||
})
|
||||
// ...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 } = route.query
|
||||
if (!optionsRef.value) return
|
||||
optionsRef.value.load(id, tempid, tempType, async () => {
|
||||
if (!zoomControlRef.value) return
|
||||
zoomControlRef.value.screenChange()
|
||||
await nextTick()
|
||||
// 初始化激活的控件为page
|
||||
store.dispatch('selectWidget', { uuid: '-1' })
|
||||
// selectWidget({
|
||||
// uuid: '-1',
|
||||
// })
|
||||
})
|
||||
}
|
||||
|
||||
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 optionsChange({ downloadPercent, downloadText }: { downloadPercent: number, downloadText: string }) {
|
||||
state.downloadPercent = downloadPercent
|
||||
state.downloadText = downloadText
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -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'
|
||||
@ -52,47 +64,61 @@ import wText from '@/components/modules/widgets/wText/wText.vue'
|
||||
import wImage from '@/components/modules/widgets/wImage/wImage.vue'
|
||||
import useLoading from '@/common/methods/loading'
|
||||
import uploader from '@/components/common/Uploader/index.vue'
|
||||
import designBoard from '@/components/modules/layout/designBoard.vue'
|
||||
import zoomControl from '@/components/modules/layout/zoomControl.vue'
|
||||
import HeaderOptions from './components/UploadTemplate.vue'
|
||||
import designBoard from '@/components/modules/layout/designBoard/index.vue'
|
||||
import zoomControl from '@/components/modules/layout/zoomControl/index.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: '',
|
||||
downloadMsg: '',
|
||||
cancelText: '',
|
||||
})
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const { proxy }: any = getCurrentInstance() as ComponentInternalInstance
|
||||
let loading: any = null
|
||||
})
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
|
||||
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 () => {
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
if (zoomControlRef.value){
|
||||
zoomControlRef.value.screenChange()
|
||||
}
|
||||
state.isDone = false
|
||||
})
|
||||
})
|
||||
|
||||
function loadJS() {
|
||||
function loadJS() {
|
||||
const link_element = document.createElement('script')
|
||||
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,68 +137,74 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
async function clear() {
|
||||
async function clear() {
|
||||
store.commit('setDWidgets', [])
|
||||
store.commit('setDPage', Object.assign(store.getters.dPage, { width: 1920, height: 1080, backgroundColor: '#ffffff', backgroundImage: '' }))
|
||||
store.commit('setShowMoveable', false)
|
||||
await nextTick()
|
||||
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 = () => {
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
},
|
||||
})
|
||||
const content = '本站为个人项目,所使用素材图片等均为网络收集而来,下载之作品仅供学习研究或欣赏目的而使用,无法提供商用授权哦。'
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -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,62 +38,81 @@ 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
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const state = reactive({
|
||||
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 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,
|
||||
})
|
||||
})
|
||||
|
||||
// 保存作品
|
||||
async function save(hasCover: boolean = false) {
|
||||
// 保存作品
|
||||
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() {
|
||||
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>
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user