mirror of
https://github.com/palxiao/poster-design.git
synced 2025-07-03 03:56:41 +08:00
Merge pull request #53 from JeremyYu-cn/feat-upgrade-vue3
Feat upgrade vue3
This commit is contained in:
commit
a25caeb762
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,6 @@
|
||||
node_modules
|
||||
/dist
|
||||
config.json
|
||||
package-lock.json
|
||||
|
||||
screenshot/node_modules/
|
||||
screenshot/dist/
|
||||
@ -16,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
@ -1,7 +1,7 @@
|
||||
{
|
||||
"files.eol": "\n",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSave": false,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"eslint.validate": ["javascript", "javascriptreact", "vue", "typescript", "typescriptreact"],
|
||||
"editor.codeActionsOnSave": {
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 ShawnPhang
|
||||
Copyright (c) 2023-present ShawnPhang (design.palxp.cn)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
105
README.md
105
README.md
@ -1,4 +1,4 @@
|
||||
**[在线体验](https://design.palxp.cn/)** | **[中文文档网站](https://xp.palxp.cn/)** | [架构及目录说明](https://xp.palxp.cn/#/articles/1689321259854)
|
||||
**[在线体验网址](https://design.palxp.cn/)** | **[中文文档](https://xp.palxp.cn/)** | [常见问题](https://xp.palxp.cn/#/articles/1689323321667) | [架构说明](https://xp.palxp.cn/#/articles/1689321259854)
|
||||
|
||||
---
|
||||
|
||||
@ -8,17 +8,17 @@
|
||||
|
||||
适用于海报图片生成、电商分享图、文章长图、视频/公众号封面等多种场景。
|
||||
|
||||

|
||||
[](https://design.palxp.cn/)
|
||||
|
||||
- 丝滑的操作体验,丰富的交互细节,基础功能完善
|
||||
- 采用服务端生成图片,确保多端出图统一性,支持各种 CSS 特性
|
||||
- 丝滑的页面操作体验,丰富的交互细节,基础功能完善
|
||||
- 采用服务端生成图片,能确保多端出图统一性,支持各种 CSS 特性
|
||||
- 简易 AI 抠图工具,上传图片一键去除背景
|
||||
- 技术栈:Vue3 、Vite2 、Vuex 、ElementPlus
|
||||
- 技术栈:Vue3 、Vite2 、Vuex 、ElementPlus,开发体验畅快
|
||||
- 图片生成:Puppeteer、Express
|
||||
|
||||
### 支持功能
|
||||
|
||||
- 导入 PSD 文件解析成模板、在线导出图片下载
|
||||
- 导入 PSD 文件解析成模板、在线导出图片下载。
|
||||
- 元素拖拽、组合、缩放、层级调整、对齐等操作。
|
||||
- 图片素材插入、替换、裁剪,图片容器等功能。
|
||||
- SVG 素材颜色、透明度编辑,文字花字组合。
|
||||
@ -29,98 +29,69 @@
|
||||
- 图层操作,支持拖拽变更层级。
|
||||
- 颜色调色板,原生级取色器颜色吸管(Chrome)。
|
||||
|
||||
### 拉取源码
|
||||
|
||||
> 环境需求:**Node.js v16.18** 以上版本
|
||||
## 快速开始
|
||||
|
||||
```
|
||||
git clone https://github.com/palxiao/poster-design.git
|
||||
cd poster-design
|
||||
```
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```
|
||||
npm run prepared
|
||||
```
|
||||
|
||||
> 网络太慢?尝试运行:npm config set registry https://registry.npmmirror.com 再安装依赖
|
||||
|
||||
### 本地运行
|
||||
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
> 将会同时运行前端界面与图片生成服务(`3000`端口为前端项目,`7001`端口为图片生成服务):
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 如果你本地没有成功启动两个服务,可能是 win 系统不兼容,手动进 `screenshot` 目录安装依赖(`npm install`)并启动服务(`npm run dev`) 或者使用 VSCode 自带的终端来运行命令,注意不要使用 CMD。
|
||||

|
||||
|
||||
### 打包
|
||||
访问 http://127.0.0.1:3000/ 查看网页。点此查看[完整说明文档](https://xp.palxp.cn/#/articles/1689319644311)。
|
||||
|
||||
| 前端页面 | 截图服务 |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `npm run v-build` | [/screenshot/README.md](https://github.com/palxiao/poster-design/blob/main/screenshot/README.md) |
|
||||
### 图片生成服务
|
||||
|
||||
### 截图服务
|
||||
代码位于根目录 [/screenshot](https://github.com/palxiao/poster-design/tree/main/screenshot),接口API文档点此查看:[接口 API 文档](https://xp.palxp.cn/apidoc/screenshot.html)。
|
||||
|
||||
代码位于 [/screenshot](https://github.com/palxiao/poster-design/tree/main/screenshot) 目录下,查看[接口 API 文档](https://xp.palxp.cn/apidoc/screenshot.html)。
|
||||
|
||||
> 打包注意事项与服务器配置相关请进入该目录下查看 README 文件说明。
|
||||
|
||||
### 截图服务 Docker 部署
|
||||
|
||||
可以通过 Docker 运行一个带 Linux 浏览器的容器,[参考说明](https://xp.palxp.cn/#/articles/1689319644311?id=docker%e5%ae%b9%e5%99%a8)。
|
||||
> 更多相关事项请进入该目录下查看 [README.md](https://github.com/palxiao/poster-design/blob/main/screenshot/README.md) 文件。 Docker 部署:[参考说明](https://xp.palxp.cn/#/articles/1689319644311?id=docker%e5%ae%b9%e5%99%a8)。
|
||||
|
||||
### 服务端
|
||||
|
||||
根据你的具体场景自行实现,目前本项目中的所有后端接口可参考:[接口 API 文档](https://xp.palxp.cn/apidoc/index.html)。
|
||||
目前本项目演示 Demo 中的后端接口参考:[接口 API 文档](https://xp.palxp.cn/apidoc/index.html)。
|
||||
|
||||
### 抠图服务部署
|
||||
## 其它
|
||||
|
||||
```
|
||||
docker run -d -p 5000:5000 --restart always danielgatis/rembg s
|
||||
```
|
||||
项目最早使用 Vue2 开发,后改用 Vue3 重构,目前有部分代码混合了 Options 写法。
|
||||
|
||||
目前也还在持续迭代中,有很多的不足,我也是一边学习一边成长。[实时迭代计划文档](https://xp.palxp.cn/#/articles/1689319986889?id=%e8%bf%ad%e4%bb%a3%e8%ae%a1%e5%88%92).
|
||||
|
||||
### 感谢
|
||||
|
||||
项目还使用或参考了一些优秀开源项目,包括但不限于:
|
||||
|
||||
- [moveable](https://github.com/daybrush/moveable): 提供了画布中选择、拖动缩放等能力
|
||||
- [html2canvas](https://github.com/niklasvh/html2canvas): 前端生成图片兜底方案
|
||||
- [html2canvas](https://github.com/niklasvh/html2canvas): 前端生图的一种快捷方案
|
||||
- [qr-code-styling](https://qr-code-styling.com/): 风格化二维码
|
||||
- [rembg](https://github.com/danielgatis/rembg): 图片删除背景,使用 u2net 预训练模型
|
||||
- [rembg](https://github.com/danielgatis/rembg): 图片抠图,使用 u2net 预训练模型
|
||||
|
||||
### Q&A
|
||||
或许你在工作中有类似的需求,或许你对开发编辑器感兴趣,也希望这个项目能给到你一些微薄帮助!
|
||||
|
||||
Q:**项目可以直接部署上线吗?**
|
||||
### `Star`
|
||||
|
||||
A:本项目支持本地运行体验完整功能,如需部署到生产,需自行开发配套的后端接口,自行部署图片生成服务,部署方法参考项目中的文档。
|
||||
|
||||
Q:**后端源码不开源吗?**
|
||||
|
||||
A:考虑到服务端的开发语言、数据库类型都可能不尽相同,且本项目中实现简单,代码不具备参考性,所以暂时只提供接口 API 文档。(目前仅是一些增删改查)
|
||||
|
||||
### 其它
|
||||
|
||||
项目最早使用 Vue2 开发,后改用 Vue3 重构,所以有部分代码混合了 Options 写法。
|
||||
|
||||
或许你在工作中有类似的需求,或许你也对开发编辑器感兴趣,希望这个项目能给到你一些微薄帮助!
|
||||
|
||||
目前本项目也还在迭代中,有很多的不足,我也是一边学习一边成长。开源不易,希望看到这里的你可以给本项目点个 **Star** 支持一下~
|
||||
开源不易,别忘了给本项目点个 **Star** ~
|
||||
|
||||
[](https://star-history.com/#palxiao/poster-design&Date)
|
||||
|
||||
#### 部分迭代计划:
|
||||
感谢所有支持本项目的朋友 :heart:
|
||||
|
||||
- [ ] P1: 文字特效属性编辑面板开发
|
||||
- [ ] P1:字体抽取功能
|
||||
- [ ] P1:PSD 解析重构(涉及基础库更换)
|
||||
[](https://github.com/palxiao/poster-design/stargazers)
|
||||
|
||||
[ -> 完整后续迭代计划文档](https://xp.palxp.cn/#/articles/1689319986889?id=%e8%bf%ad%e4%bb%a3%e8%ae%a1%e5%88%92)
|
||||
### `Fork`
|
||||
|
||||
### LICENSE
|
||||
这些小伙伴都在使用迅排设计 :heart:
|
||||
|
||||
[](https://github.com/palxiao/poster-design/network/members)
|
||||
|
||||
### `Contributions`
|
||||
|
||||
<a href="https://github.com/palxiao/poster-design/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=palxiao/poster-design" />
|
||||
</a>
|
||||
|
||||
### `LICENSE`
|
||||
|
||||
本项目完全免费,遵循 [MIT 开源许可证](https://github.com/palxiao/poster-design/blob/main/LICENSE)
|
||||
|
||||
[MIT License](https://github.com/palxiao/poster-design/blob/main/LICENSE)
|
||||
|
@ -3,7 +3,7 @@
|
||||
* @Date: 2022-01-17 16:06:30
|
||||
* @Description: Design Palxp
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-13 13:12:09
|
||||
* @LastEditTime: 2023-09-19 18:36:13
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
@ -18,6 +18,6 @@
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<script defer src="/snap.svg-min.js"></script>
|
||||
<script defer src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
|
||||
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
37684
package-lock.json
generated
Normal file
37684
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@ -14,8 +14,8 @@
|
||||
"publish-fast": "git add . && git commit -m 'build: auto publish' && sh script/publish.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gradio/client": "^0.1.4",
|
||||
"@palxp/color-picker": "^1.3.1",
|
||||
"@palxp/color-picker": "^1.5.5",
|
||||
"@palxp/image-extraction": "^1.2.4",
|
||||
"@scena/guides": "^0.18.1",
|
||||
"@webtoon/psd": "^0.4.0",
|
||||
"axios": "^0.21.1",
|
||||
@ -31,8 +31,7 @@
|
||||
"qr-code-styling": "^1.6.0-rc.1",
|
||||
"selecto": "^1.13.0",
|
||||
"throttle-debounce": "^3.0.1",
|
||||
"v-lazy-image": "^2.1.1",
|
||||
"vue": "^3.0.0",
|
||||
"vue": "3.2.22",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.0.0-0"
|
||||
@ -42,7 +41,7 @@
|
||||
"@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",
|
||||
"@vitejs/plugin-vue": "1.9.3",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
@ -58,13 +57,13 @@
|
||||
"eslint-config-alloy": "~4.1.0",
|
||||
"eslint-plugin-vue": "^7.12.1",
|
||||
"less": "^4.1.1",
|
||||
"typescript": "~4.1.5",
|
||||
"typescript": "^4.4.3",
|
||||
"unplugin-element-plus": "^0.7.1",
|
||||
"vite": "^2.4.1",
|
||||
"vite": "2.6.4",
|
||||
"vite-plugin-compression": "^0.3.0",
|
||||
"vue-cli-plugin-norm": "~1.2.2",
|
||||
"vue-eslint-parser": "^7.6.0",
|
||||
"vue-tsc": "^0.2.0"
|
||||
"vue-tsc": "0.3.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
@ -9,7 +9,7 @@ module.exports = {
|
||||
'ie >= 8',
|
||||
'last 10 versions', // 所有主流浏览器最近10版本用
|
||||
],
|
||||
grid: true,
|
||||
grid: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -7,8 +7,15 @@
|
||||
"author": "ShawnPhang",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.24.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"fontmin": "^1.0.1",
|
||||
"form-data": "^4.0.0",
|
||||
"moment": "^2.18.1",
|
||||
"multiparty": "^4.2.3",
|
||||
"mysql": "^2.13.0",
|
||||
"qiniu": "^7.4.0",
|
||||
"puppeteer": "^10.4.0",
|
||||
"images": "3.2.3"
|
||||
},
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2020-07-22 20:13:14
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <site: m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-04 14:08:10
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-16 10:03:51
|
||||
*/
|
||||
const { saveScreenshot } = require('../utils/download-single.ts')
|
||||
const uuid = require('../utils/uuid.ts')
|
||||
@ -48,6 +48,7 @@ module.exports = {
|
||||
*
|
||||
* @apiParam {String|Number} id (可选) 截图id,高优先级
|
||||
* @apiParam {String|Number} tempid (可选) 模板id,低优先级,无id时取该值
|
||||
* @apiParam {String|Number} tempType (可选) 区分模板和组件类型,临时用
|
||||
* @apiParam {String} width (必传)视窗大小
|
||||
* @apiParam {String} height (必传)视窗大小
|
||||
* @apiParam {String} screenshot_url 可选
|
||||
@ -55,19 +56,20 @@ module.exports = {
|
||||
* @apiParam {String} size 可选, 按比例缩小到宽度
|
||||
* @apiParam {String} quality 可选, 质量
|
||||
*/
|
||||
let { id, tempid, width, height, screenshot_url, type = 'file', size, quality } = req.query
|
||||
let { id, tempid, tempType, width, height, screenshot_url, type = 'file', size, quality } = req.query
|
||||
const url = (screenshot_url || drawLink) + `${id ? '?id=' : '?tempid='}`
|
||||
id = id || tempid
|
||||
const path = filePath + `${id}-screenshot.png`
|
||||
const thumbPath = type === 'cover' ? filePath + `${id}-cover.jpg` : null
|
||||
const thumbPath = type === 'cover' && tempType != 1 ? filePath + `${id}-cover.jpg` : null
|
||||
|
||||
if (id && width && height) {
|
||||
if (queueList.length > upperLimit) {
|
||||
res.json({ code: 200, msg: '服务器表示顶不住啊,等等再来吧~' })
|
||||
return
|
||||
}
|
||||
// console.log(url + id, path, thumbPath);
|
||||
queueRun(saveScreenshot, url + id, { width, height, path, thumbPath, size, quality })
|
||||
const targetUrl = url + id + `${tempType?'&tempType='+tempType:''}`
|
||||
// console.log(targetUrl, path, thumbPath);
|
||||
queueRun(saveScreenshot, targetUrl, { width, height, path, thumbPath, size, quality })
|
||||
.then(() => {
|
||||
res.setHeader('Content-Type', 'image/jpg')
|
||||
// const stats = fs.statSync(path)
|
||||
|
@ -3,7 +3,7 @@
|
||||
* @Date: 2021-09-30 14:47:22
|
||||
* @Description: 下载图片(单浏览器版,适用于低配置服务器)
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-18 18:17:54
|
||||
* @LastEditTime: 2023-10-16 10:56:35
|
||||
*/
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
const puppeteer = require('puppeteer')
|
||||
@ -16,16 +16,28 @@ const maxPXs = 4211840 // 超出此规格会触发限制器降低dpr,节省服
|
||||
const maximum = 5000 // 最大宽高限制,超过截断以防止服务崩溃
|
||||
|
||||
const saveScreenshot = async (url: string, { path, width, height, thumbPath, size = 0, quality = 0, prevent, ua, devices, scale, wait }: any) => {
|
||||
return new Promise(async (resolve: Function) => {
|
||||
return new Promise(async (resolve: Function, reject: Function) => {
|
||||
let isPageLoad = false
|
||||
let browser: any = null
|
||||
// 格式化浏览器宽高
|
||||
width = Number(width).toFixed(0)
|
||||
height = Number(height).toFixed(0)
|
||||
// 启动浏览器
|
||||
let browser = await puppeteer.launch({
|
||||
headless: true, // !isDev,
|
||||
executablePath: isDev ? null : executablePath,
|
||||
ignoreHTTPSErrors: true, // 忽略https安全提示
|
||||
args: ['–no-first-run', '–single-process', '–disable-gpu', '–no-zygote', '–disable-dev-shm-usage', '--no-sandbox', '--disable-setuid-sandbox', `--window-size=${width},${height}`], // 优化配置
|
||||
defaultViewport: null,
|
||||
})
|
||||
try {
|
||||
browser = await puppeteer.launch({
|
||||
headless: true, // !isDev,
|
||||
executablePath: isDev ? null : executablePath,
|
||||
ignoreHTTPSErrors: true, // 忽略https安全提示
|
||||
args: ['–no-first-run', '–single-process', '–disable-gpu', '–no-zygote', '–disable-dev-shm-usage', '--no-sandbox', '--disable-setuid-sandbox', `--window-size=${width},${height}`], // 优化配置
|
||||
defaultViewport: null,
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('Puppeteer启动错误!', '窗口大小:', width, height);
|
||||
}
|
||||
if (!browser) {
|
||||
reject()
|
||||
return false
|
||||
}
|
||||
const regulators = setTimeout(() => {
|
||||
browser && browser.close()
|
||||
browser = null
|
||||
|
17
src/App.vue
17
src/App.vue
@ -3,12 +3,12 @@
|
||||
* @Date: 2023-09-18 17:34:44
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-18 18:05:22
|
||||
* @LastEditTime: 2023-09-20 16:10:58
|
||||
-->
|
||||
<template>
|
||||
<div id="appindex">
|
||||
<div class="viewWrap">
|
||||
<router-view></router-view>
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -17,21 +17,8 @@
|
||||
#appindex {
|
||||
min-width: 1180px;
|
||||
.viewWrap {
|
||||
// background-color: #ffffff;
|
||||
min-height: calc(110vh - 110px);
|
||||
min-width: 1170px;
|
||||
}
|
||||
}
|
||||
// .fade-enter-active {
|
||||
// transition: opacity 0.3s;
|
||||
// }
|
||||
// .fade-leave-active {
|
||||
// transition: opacity 0.3s;
|
||||
// }
|
||||
// .fade-enter {
|
||||
// opacity: 0;
|
||||
// }
|
||||
// .fade-leave-to {
|
||||
// opacity: 0;
|
||||
// }
|
||||
</style>
|
||||
|
24
src/api/ai.ts
Normal file
24
src/api/ai.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-27 14:42:15
|
||||
* @Description: AI相关接口
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-13 00:07:19
|
||||
*/
|
||||
import fetch from '@/utils/axios'
|
||||
|
||||
// 上传接口
|
||||
export const upload = (file: File, cb: Function) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const extra = {
|
||||
responseType: 'blob',
|
||||
onUploadProgress: (progress: any) => {
|
||||
cb(Math.floor((progress.loaded / progress.total) * 100), 0)
|
||||
},
|
||||
onDownloadProgress: (progress: any) => {
|
||||
cb(100, Math.floor((progress.loaded / progress.total) * 100))
|
||||
},
|
||||
}
|
||||
return fetch('https://res.palxp.cn/ai/upload', formData, 'post', {}, extra)
|
||||
}
|
@ -2,13 +2,15 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-19 18:43:22
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-21 13:06:46
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-28 15:46:56
|
||||
*/
|
||||
import * as home from './home'
|
||||
import * as material from './material'
|
||||
import * as ai from './ai'
|
||||
|
||||
export default {
|
||||
home,
|
||||
material,
|
||||
ai,
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-27 14:42:15
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-21 11:19:04
|
||||
* @Description: 媒体相关接口
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-12-11 11:40:47
|
||||
*/
|
||||
import fetch from '@/utils/axios'
|
||||
|
||||
@ -15,7 +15,7 @@ export const getList = (params: Type.Object = {}) => fetch('design/material', pa
|
||||
|
||||
// 获取字体
|
||||
export const getFonts = (params: Type.Object = {}) => fetch('design/fonts', params)
|
||||
export const getFontSub = (params: Type.Object = {}) => fetch('design/font_sub', params)
|
||||
export const getFontSub = (params: Type.Object = {}, extra: any = {}) => fetch('design/font_sub', params, 'get', {}, extra)
|
||||
|
||||
// 图库列表
|
||||
export const getImagesList = (params: Type.Object = {}) => fetch('design/imgs', params, 'get')
|
||||
@ -23,6 +23,7 @@ export const getImagesList = (params: Type.Object = {}) => fetch('design/imgs',
|
||||
// 我的上传列表
|
||||
export const getMyPhoto = (params: Type.Object = {}) => fetch('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')
|
||||
|
||||
// 添加图片
|
||||
export const addMyPhoto = (params: Type.Object = {}) => fetch('design/user/add_image', params)
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-12 11:08:57
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-02-12 11:09:41
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-19 17:35:44
|
||||
*/
|
||||
export default [
|
||||
{
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-07-17 11:20:22
|
||||
* @Description:
|
||||
* @LastEditors: rayadaschn 115447518+rayadaschn@users.noreply.github.com
|
||||
* @LastEditTime: 2023-09-01 14:15:14
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-24 17:07:44
|
||||
*/
|
||||
export default [
|
||||
{
|
||||
@ -31,14 +31,14 @@ export default [
|
||||
show: false,
|
||||
component: 'photo-list-wrap',
|
||||
},
|
||||
// {
|
||||
// name: '背景',
|
||||
// icon: 'icon-beijing',
|
||||
// show: false,
|
||||
// component: 'bg-img-list-wrap',
|
||||
// },
|
||||
{
|
||||
name: '背景',
|
||||
icon: 'icon-beijing',
|
||||
show: false,
|
||||
component: 'bg-img-list-wrap',
|
||||
},
|
||||
{
|
||||
name: '工具',
|
||||
name: '工具箱',
|
||||
icon: 'icon-zujian01',
|
||||
show: false,
|
||||
component: 'tools-list-wrap',
|
@ -13,7 +13,7 @@
|
||||
@height2: 54px; // Appears 5 times
|
||||
|
||||
.page-design-bg-color {
|
||||
background-color: #4b678c;
|
||||
// background-color: #4b678c;
|
||||
// background-color: #4682b4;
|
||||
}
|
||||
#page-design-index {
|
||||
@ -132,16 +132,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
.shelter {
|
||||
box-shadow: 0 0 0 5000px rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 0 0 5000px rgba(248, 248, 248, 0.99);
|
||||
z-index: 8;
|
||||
}
|
||||
.shelter-bg {
|
||||
// background-color: #ffffff;
|
||||
background-color: #f0f0f0;
|
||||
background-image: linear-gradient(to top right, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff), linear-gradient(to top right, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
|
||||
background-position: 0 0, 8px 8px;
|
||||
background-size: 16px 16px;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
@ -25,3 +25,12 @@
|
||||
.line-clamp-2 {
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.transparent-bg {
|
||||
background-color: #f0f0f0;
|
||||
background-image: linear-gradient(to top right, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff), linear-gradient(to top right, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
|
||||
background-position: 0 0, 8px 8px;
|
||||
background-size: 16px 16px;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
// Some primary CSS, and reset CSS
|
||||
|
||||
@font-face {
|
||||
font-family: TitleFont;
|
||||
src: url(../fonts/xpsj.subset.woff2) format('woff2');
|
||||
@ -15,10 +17,14 @@ body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
color: #333333;
|
||||
font-family: Hiragino Sans GB, Hiragino Sans GB W3, Arial, Microsoft Yahei, STHeiti, sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: #e5e7eb;
|
||||
// font-size: 14px;
|
||||
scrollbar-width: none; /* 火狐滚动条无法自定义宽度,只能通过此属性使滚动条宽度变细 */
|
||||
-ms-overflow-style: none; /* 隐藏滚动条(在IE和Edge两个浏览器中很难更改样式,固采取隐藏方式) */
|
||||
@ -56,7 +62,9 @@ select:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
// text-decoration: none;
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
ul,
|
||||
ol,
|
||||
@ -73,10 +81,28 @@ h6 {
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
img {
|
||||
-webkit-user-drag: none;
|
||||
user-select: none;
|
||||
}
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
input {
|
||||
outline: 0;
|
||||
&:-moz-focusring {
|
||||
@ -105,5 +131,5 @@ p {
|
||||
pointer-events: none;
|
||||
}
|
||||
.hide {
|
||||
opacity: 0;
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-07-10 14:58:48
|
||||
* @Description: 拖拽优化
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-11 11:10:03
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-22 18:11:15
|
||||
*/
|
||||
import store from '@/store'
|
||||
export default class dragHelper {
|
||||
@ -18,6 +18,8 @@ export default class dragHelper {
|
||||
const { offsetX, offsetY, width, height } = this.initial
|
||||
// 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 {
|
||||
this.finish()
|
||||
}
|
||||
})
|
||||
// 鼠标抬起
|
||||
@ -91,6 +93,9 @@ export default class dragHelper {
|
||||
}
|
||||
// 结束/完成处理(动画)
|
||||
private finish(done = false) {
|
||||
if (!this.dragging) {
|
||||
return
|
||||
}
|
||||
this.dragging = false
|
||||
store.commit('setDraging', false)
|
||||
store.commit('selectItem', {})
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-22 15:06:14
|
||||
* @Description: 设置图片类型元素
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-10 17:37:27
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-11 17:36:44
|
||||
*/
|
||||
import store from '@/store'
|
||||
import { getImage } from '../getImgDetail'
|
||||
@ -29,6 +29,5 @@ export default async function setItem2Data(item: any) {
|
||||
|
||||
cloneItem.canvasWidth = cloneItem.width * (store.getters.dZoom / 100)
|
||||
// cloneItem.canvasHeight = cloneItem.height * (store.getters.dZoom / 100)
|
||||
|
||||
return cloneItem
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-29 20:35:31
|
||||
* @Description: 七牛上传方法
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-11 22:02:12
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-05 16:11:55
|
||||
*/
|
||||
import dayjs from 'dayjs'
|
||||
import api from '@/api/album'
|
||||
@ -18,7 +18,7 @@ export default {
|
||||
upload: async (file: File, options: Options, cb?: Function) => {
|
||||
const win: any = window
|
||||
let name = ''
|
||||
const suffix = file.type.split('/')[1] // 文件后缀
|
||||
const suffix = file.type.split('/')[1] || 'png' // 文件后缀
|
||||
if (!options.fullPath) {
|
||||
// const DT: any = await exifGetTime(file) // 照片时间
|
||||
const DT: any = new Date()
|
||||
|
@ -2,12 +2,13 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-01-08 09:43:37
|
||||
* @Description: 字体处理
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-25 11:13:01
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-13 01:30:33
|
||||
*/
|
||||
// import { isSupportFontFamily, blob2Base64 } from './utils'
|
||||
import { getFonts } from '@/api/material'
|
||||
// import store from '@/store'
|
||||
|
||||
const nowVersion = '2' // 当前字体文件版本更新,将刷新前端缓存
|
||||
|
||||
const fontList: any = []
|
||||
// const download: any = {}
|
||||
@ -16,7 +17,7 @@ export const useFontStore = {
|
||||
// download,
|
||||
async init() {
|
||||
this.list = []
|
||||
localStorage.getItem('FONTS_VERSION') !== '1' && localStorage.removeItem('FONTS')
|
||||
localStorage.getItem('FONTS_VERSION') !== nowVersion && localStorage.removeItem('FONTS')
|
||||
const localFonts: any = localStorage.getItem('FONTS') ? JSON.parse(localStorage.getItem('FONTS') || '') : []
|
||||
if (localFonts.length > 0) {
|
||||
this.list.push(...localFonts)
|
||||
@ -26,12 +27,12 @@ export const useFontStore = {
|
||||
const res = await getFonts({ pageSize: 400 })
|
||||
this.list.unshift(
|
||||
...res.list.map((x: any) => {
|
||||
const { alias, oid, value, preview, woff, lang } = x
|
||||
return { id: oid, value, preview, alias, url: woff, lang }
|
||||
const { id, alias, oid, value, preview, woff, lang } = x
|
||||
return { id, oid, value, preview, alias, url: woff, lang }
|
||||
}),
|
||||
)
|
||||
localStorage.setItem('FONTS', JSON.stringify(this.list))
|
||||
localStorage.setItem('FONTS_VERSION', '1')
|
||||
localStorage.setItem('FONTS_VERSION', nowVersion)
|
||||
}
|
||||
// store.dispatch('setFonts', this.list)
|
||||
},
|
||||
|
@ -77,7 +77,7 @@ export function filterSkyFonts() {
|
||||
// );
|
||||
const textClouds: any = []
|
||||
|
||||
;((textClouds as unknown) as CloudText[]).forEach((cloud) => {
|
||||
;(textClouds as unknown as CloudText[]).forEach((cloud) => {
|
||||
// 找到文字组件字体
|
||||
if (cloud.fontFamily && !fonts.includes(cloud.fontFamily)) {
|
||||
fonts.push(cloud.fontFamily)
|
||||
@ -114,6 +114,10 @@ export function base642Blob(b64Data: string, contentType = '', sliceSize = 512)
|
||||
|
||||
export async function blob2Base64(blob: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (blob.type === 'application/json') {
|
||||
resolve('')
|
||||
return
|
||||
}
|
||||
const fileReader = new FileReader()
|
||||
fileReader.onload = () => {
|
||||
resolve(fileReader.result as string)
|
||||
|
@ -1,9 +1,9 @@
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-23 17:25:35
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-02-24 00:16:59
|
||||
* @Description: 获取图片细节的相关方法
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-09 10:42:54
|
||||
*/
|
||||
export const getImage = (imgItem: string | File) => {
|
||||
// 创建对象
|
||||
@ -19,7 +19,7 @@ export const getImage = (imgItem: string | File) => {
|
||||
resolve(img)
|
||||
} else {
|
||||
// 加载完成执行
|
||||
img.onload = function() {
|
||||
img.onload = function () {
|
||||
resolve(img)
|
||||
}
|
||||
}
|
||||
|
@ -3,21 +3,25 @@
|
||||
* @Date: 2023-07-11 23:50:22
|
||||
* @Description: 抠图组件
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-15 12:38:49
|
||||
* @LastEditTime: 2023-10-09 00:42:48
|
||||
-->
|
||||
<template>
|
||||
<el-dialog v-model="show" title="AI抠图(测试版)" width="650" @close="handleClose">
|
||||
<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">支持 jpg 或 png 格式的文件,大小不超过 2M</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">
|
||||
<img ref="raw" :src="rawImage" alt="" />
|
||||
<div :style="{ right: 100 - percent + '%' }" class="bg"></div>
|
||||
<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>
|
||||
@ -25,23 +29,33 @@
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button v-show="rawImage" @click="clear">重新选择图片</el-button>
|
||||
<el-button v-show="cutImage" type="primary" plain @click="download"> 下载 </el-button>
|
||||
<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, onMounted, nextTick } from 'vue'
|
||||
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 },
|
||||
setup() {
|
||||
components: { uploader, UploadFilled, ElProgress, ImageExtraction },
|
||||
emits: ['done'],
|
||||
setup(props, { emit }) {
|
||||
const store = useStore()
|
||||
const state: any = reactive({
|
||||
show: false,
|
||||
@ -50,18 +64,18 @@ export default defineComponent({
|
||||
raw: null,
|
||||
offsetWidth: 0,
|
||||
percent: 0,
|
||||
progress: 0,
|
||||
progressText: '',
|
||||
toolModel: true,
|
||||
loading: false,
|
||||
matting: null,
|
||||
})
|
||||
|
||||
let app: any = null
|
||||
let fileName: string = 'unknow'
|
||||
let isRuning: boolean = false
|
||||
onMounted(async () => {
|
||||
app = await store.getters.app
|
||||
})
|
||||
|
||||
const selectFile = async (file: File) => {
|
||||
if (file.size > 1024 * 1024 * 2) {
|
||||
alert('请上传小于 2M 的图片')
|
||||
alert('上传图片超出限制')
|
||||
return false
|
||||
}
|
||||
// 显示选择的图片
|
||||
@ -70,15 +84,33 @@ export default defineComponent({
|
||||
})
|
||||
state.rawImage = URL.createObjectURL(file)
|
||||
fileName = file.name
|
||||
// 返回抠图
|
||||
const result = await app.predict('/predict', [file, 'u2netp', ''])
|
||||
state.rawImage && (state.cutImage = result?.data[0])
|
||||
requestAnimationFrame(run)
|
||||
// 返回抠图结果
|
||||
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 = () => {
|
||||
const open = (file: File) => {
|
||||
state.loading = false
|
||||
state.show = true
|
||||
store.commit('setShowMoveable', false)
|
||||
nextTick(() => {
|
||||
if (file) {
|
||||
selectFile(file)
|
||||
state.toolModel = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
@ -108,6 +140,30 @@ export default defineComponent({
|
||||
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,
|
||||
@ -116,6 +172,8 @@ export default defineComponent({
|
||||
open,
|
||||
handleClose,
|
||||
...toRefs(state),
|
||||
cutDone,
|
||||
edit,
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -146,12 +204,6 @@ export default defineComponent({
|
||||
object-fit: contain;
|
||||
position: absolute;
|
||||
}
|
||||
.bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.scan-line {
|
||||
@ -163,4 +215,8 @@ export default defineComponent({
|
||||
// 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>
|
||||
|
106
src/components/business/image-cutout/ImageExtraction.vue
Normal file
106
src/components/business/image-cutout/ImageExtraction.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<!--
|
||||
* @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>
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-04 11:46:39
|
||||
* @Description: 原版movable插件
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-17 11:53:30
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-14 11:41:23
|
||||
-->
|
||||
<template>
|
||||
<div id="empty" class="moveable__remove-item zk-moveable-style"></div>
|
||||
@ -40,9 +40,9 @@ export default defineComponent({
|
||||
case 'w-image':
|
||||
this.moveable.renderDirections = ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se']
|
||||
break
|
||||
// case 'w-svg':
|
||||
// this.moveable.renderDirections = ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se']
|
||||
// break
|
||||
case 'w-svg':
|
||||
this.moveable.renderDirections = ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se']
|
||||
break
|
||||
default:
|
||||
this.moveable.renderDirections = ['nw', 'ne', 'sw', 'se']
|
||||
break
|
||||
@ -227,12 +227,12 @@ export default defineComponent({
|
||||
this.updateWidgetData({
|
||||
uuid: this.dActiveElement.uuid,
|
||||
key: 'left',
|
||||
value: this.holdPosition?.left,
|
||||
value: Number(this.holdPosition?.left),
|
||||
})
|
||||
this.updateWidgetData({
|
||||
uuid: this.dActiveElement.uuid,
|
||||
key: 'top',
|
||||
value: this.holdPosition?.top,
|
||||
value: Number(this.holdPosition?.top),
|
||||
})
|
||||
this.holdPosition = null // important
|
||||
setTimeout(() => {
|
||||
|
@ -2,18 +2,22 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-10-08 10:07:19
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-06-29 17:57:46
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-05 00:04:51
|
||||
-->
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="选择图片" @close="close">
|
||||
<el-tabs tab-position="left" style="height: 60vh" class="demo-tabs">
|
||||
<el-tab-pane label="个人素材">
|
||||
<el-tabs tab-position="left" style="height: 60vh" class="demo-tabs" @tab-change="tabChange">
|
||||
<el-tab-pane label="我的素材">
|
||||
<div class="pic__box">
|
||||
<photo-list ref="imgListComp" :isDone="isDone" :listData="imgList" @load="load" @select="selectImg" />
|
||||
<photo-list :isDone="isDone" :listData="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)" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="照片库"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<!-- <template #footer>
|
||||
<span class="dialog-footer">
|
||||
@ -40,12 +44,14 @@ export default defineComponent({
|
||||
const state: any = reactive({
|
||||
dialogVisible: false,
|
||||
imgList: [],
|
||||
recommendImgList: [],
|
||||
isDone: false,
|
||||
isPicsDone: false,
|
||||
})
|
||||
|
||||
let loading = false
|
||||
let page = 0
|
||||
let listPage = 0
|
||||
let picPage = 0
|
||||
const load = (init?: boolean) => {
|
||||
if (init) {
|
||||
state.imgList = []
|
||||
@ -64,6 +70,22 @@ export default defineComponent({
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
const loadPic = (init?: boolean) => {
|
||||
if (state.isPicsDone || loading) {
|
||||
return
|
||||
}
|
||||
if (init && state.recommendImgList.length > 0) {
|
||||
return
|
||||
}
|
||||
loading = true
|
||||
picPage += 1
|
||||
api.material.getImagesList({ page: picPage }).then(({ list }: any) => {
|
||||
list.length <= 0 ? (state.isPicsDone = true) : (state.recommendImgList = state.recommendImgList.concat(list))
|
||||
setTimeout(() => {
|
||||
loading = false
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
state.dialogVisible = true
|
||||
@ -75,18 +97,26 @@ export default defineComponent({
|
||||
store.commit('setShowMoveable', true)
|
||||
}
|
||||
|
||||
const selectImg = (index: number) => {
|
||||
const item: any = state.imgList[index]
|
||||
const selectImg = (index: number, list: any) => {
|
||||
const item: any = list ? list[index] : state.imgList[index]
|
||||
context.emit('select', item)
|
||||
state.dialogVisible = false
|
||||
}
|
||||
|
||||
const tabChange = (index: any) => {
|
||||
if (index == 1) {
|
||||
loadPic(true)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
open,
|
||||
close,
|
||||
load,
|
||||
loadPic,
|
||||
selectImg,
|
||||
tabChange,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-03-16 09:15:52
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-04-08 17:53:00
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-31 15:43:10
|
||||
-->
|
||||
<template>
|
||||
<div ref="qrCodeDom" class="qrcode__wrap"></div>
|
||||
@ -46,7 +46,7 @@ export default defineComponent({
|
||||
type: 'canvas' as DrawType, // canvas svg
|
||||
data: props.value,
|
||||
image: props.image, // /favicon.svg
|
||||
margin: 2,
|
||||
margin: 0,
|
||||
qrOptions: {
|
||||
typeNumber: 3 as TypeNumber,
|
||||
mode: 'Byte' as Mode,
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-29 18:17:13
|
||||
* @Description: 二次封装上传组件
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-12 15:12:07
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-05 15:46:02
|
||||
-->
|
||||
<template>
|
||||
<el-upload action="" accept="image/*" :http-request="upload" :show-file-list="false" multiple>
|
||||
@ -75,7 +75,7 @@ export default defineComponent({
|
||||
if (file.size <= 1024 * 1024) {
|
||||
tempSimpleRes = await qiNiuUpload(file) // 队列有文件,执行上传
|
||||
const { width, height }: any = await getImage(file)
|
||||
useNotification('上传成功', '目前没有用户系统,请注意别上传隐私照片哦!', { position: 'bottom-left' })
|
||||
useNotification('上传成功', '公共测试账户,上传请注意保护隐私哦!', { position: 'bottom-left' })
|
||||
context.emit('done', { width, height, url: _config.IMG_URL + tempSimpleRes.key }) // 单个文件进行响应
|
||||
} else useNotification('爱护小水管', '请上传小于 1M 的图片哦!', { type: 'error', position: 'bottom-left' })
|
||||
uploading = false
|
||||
|
@ -91,6 +91,10 @@ export default defineComponent({
|
||||
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
|
||||
|
@ -19,15 +19,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { defineComponent } from 'vue'
|
||||
import addMouseWheel from '@/common/methods/addMouseWheel'
|
||||
|
||||
// 组件大小控制器
|
||||
const NAME = 'zoom-control'
|
||||
let holder = null
|
||||
let holder: number | undefined
|
||||
|
||||
export default {
|
||||
// TODO: TS类型补全
|
||||
export default defineComponent({
|
||||
name: NAME,
|
||||
data() {
|
||||
return {
|
||||
@ -101,6 +103,7 @@ export default {
|
||||
],
|
||||
otherIndex: -1,
|
||||
bestZoom: 0,
|
||||
curAction: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -154,7 +157,7 @@ export default {
|
||||
this.activezoomIndex = this.zoomList.length - 1
|
||||
}
|
||||
// 添加滚轮监听
|
||||
addMouseWheel('page-design', (isDown) => {
|
||||
addMouseWheel('page-design', (isDown: boolean) => {
|
||||
this.mousewheelZoom(isDown)
|
||||
})
|
||||
// 添加窗口大小监听
|
||||
@ -171,6 +174,7 @@ export default {
|
||||
clearTimeout(holder)
|
||||
holder = setTimeout(() => {
|
||||
const screen = document.getElementById('page-design')
|
||||
if (!screen) return
|
||||
this.updateScreen({
|
||||
width: screen.offsetWidth,
|
||||
height: screen.offsetHeight,
|
||||
@ -184,12 +188,12 @@ export default {
|
||||
this.autoFixTop()
|
||||
}
|
||||
},
|
||||
selectItem(index) {
|
||||
selectItem(index: number) {
|
||||
this.activezoomIndex = index
|
||||
this.otherIndex = -1
|
||||
this.show = false
|
||||
},
|
||||
close(e) {
|
||||
close(_: MouseEvent) {
|
||||
this.show = false
|
||||
},
|
||||
add() {
|
||||
@ -214,7 +218,7 @@ export default {
|
||||
}
|
||||
},
|
||||
sub() {
|
||||
this.curAction = null
|
||||
this.curAction = null as any
|
||||
this.show = false
|
||||
if (this.otherIndex === 0) {
|
||||
this.otherIndex = -1
|
||||
@ -237,13 +241,14 @@ export default {
|
||||
this.activezoomIndex--
|
||||
}
|
||||
},
|
||||
mousewheelZoom(down) {
|
||||
mousewheelZoom(down: boolean) {
|
||||
const value = Number(this.dZoom.toFixed(0))
|
||||
if (down && value <= 1) return
|
||||
this.updateZoom(down ? value - 1 : value + 1)
|
||||
this.zoom.text = value + '%'
|
||||
this.zoom.text = (value + '%') as any
|
||||
this.autoFixTop()
|
||||
},
|
||||
nearZoom(add) {
|
||||
nearZoom(add?: boolean) {
|
||||
for (let i = 0; i < this.zoomList.length; i++) {
|
||||
this.activezoomIndex = i
|
||||
if (this.zoomList[i].value > this.bestZoom) {
|
||||
@ -265,8 +270,10 @@ export default {
|
||||
await this.$nextTick()
|
||||
const presetPadding = 60
|
||||
const el = document.getElementById('out-page')
|
||||
if (!el) return
|
||||
// const clientHeight = document.body.clientHeight - 54
|
||||
const parentHeight = el.offsetParent.offsetHeight - 54
|
||||
|
||||
const parentHeight = (el.offsetParent as HTMLElement).offsetHeight - 54
|
||||
let padding = (parentHeight - el.offsetHeight) / 2
|
||||
if (typeof this.curAction === 'undefined') {
|
||||
padding += presetPadding / 2
|
||||
@ -275,7 +282,7 @@ export default {
|
||||
this.$store.commit('updatePaddingTop', padding > 0 ? padding : 0)
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-03-07 17:25:19
|
||||
* @Description: 图层组件
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-10 15:38:30
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-24 11:39:02
|
||||
-->
|
||||
<template>
|
||||
<ul class="widget-list">
|
||||
|
@ -3,7 +3,7 @@
|
||||
<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)">
|
||||
<i :class="['iconfont', 'icon', item.icon]" :style="item.style"></i>
|
||||
<div class="icon-box"><i :class="['iconfont', 'icon', item.icon]" :style="item.style" /></div>
|
||||
<p>{{ item.name }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
@ -16,9 +16,9 @@
|
||||
</div>
|
||||
<!-- <div v-show="active" class="side-wrap"><div class="pack__up" @click="active = false"><</div></div> -->
|
||||
<div v-show="active" class="side-wrap">
|
||||
<el-tooltip effect="dark" content="收起侧边栏" placement="right">
|
||||
<!-- <el-tooltip effect="dark" content="收起侧边栏" placement="right"> -->
|
||||
<div class="pack__up" @click="active = false"></div>
|
||||
</el-tooltip>
|
||||
<!-- </el-tooltip> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -26,7 +26,7 @@
|
||||
<script lang="ts">
|
||||
// 组件面板
|
||||
const NAME = 'widget-panel'
|
||||
import widgetClassifyListData from '@/assets/data/widgetClassifyList'
|
||||
import widgetClassifyListData from '@/assets/data/WidgetClassifyList.ts'
|
||||
import { reactive, toRefs, onMounted, watch, nextTick, getCurrentInstance, ComponentInternalInstance } from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
import { useRoute } from 'vue-router'
|
||||
@ -49,7 +49,7 @@ export default {
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
const { koutu } = route.query
|
||||
koutu && (state.activeWidgetClassify = 5)
|
||||
koutu && (state.activeWidgetClassify = 4)
|
||||
})
|
||||
|
||||
watch(
|
||||
@ -125,9 +125,17 @@ export default {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
p {
|
||||
color: #9da3ac;
|
||||
color: #666666;
|
||||
font-weight: 600;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.icon-box {
|
||||
width: 24px;
|
||||
height: 27px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.icon {
|
||||
color: #070707;
|
||||
}
|
||||
|
@ -2,20 +2,20 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-27 15:16:07
|
||||
* @Description: 背景图
|
||||
* @LastEditors: rayadaschn 115447518+rayadaschn@users.noreply.github.com
|
||||
* @LastEditTime: 2023-09-01 14:18:54
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-24 16:39:27
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<div class="color__box">
|
||||
<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>
|
||||
<ul v-if="showList" v-infinite-scroll="loadData" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
|
||||
<el-space fill wrap :fillRatio="30" direction="horizontal" class="list">
|
||||
<div v-for="(item, i) in bgList" :key="i + 'i'" draggable="false" @click.stop="selectItem(item)" @dragstart="dragStart($event, item)">
|
||||
<el-image class="list__img" :src="item.thumb" fit="cover"></el-image>
|
||||
</div>
|
||||
</el-space>
|
||||
<div class="list" :style="modelStyle.list">
|
||||
<imageTip v-for="(item, i) in 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>
|
||||
</ul>
|
||||
@ -23,13 +23,16 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, watch } from 'vue'
|
||||
// import { ElDivider } from 'element-plus'
|
||||
import { defineComponent, reactive, toRefs, computed } from 'vue'
|
||||
import api from '@/api'
|
||||
import { mapActions, useStore } from 'vuex'
|
||||
|
||||
export default defineComponent({
|
||||
// components: { ElDivider },
|
||||
props: {
|
||||
model: {
|
||||
default: 'widgetPanel'
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const state = reactive({
|
||||
@ -39,6 +42,19 @@ export default defineComponent({
|
||||
showList: true,
|
||||
colors: ['#000000ff', '#999999ff', '#CCCCCCff', '#FFFFFFff', '#E65353ff', '#FFD835ff', '#70BC59ff', '#607AF4ff', '#976BEEff'],
|
||||
})
|
||||
// 临时用于改变样式
|
||||
const models: any = {
|
||||
widgetPanel: {
|
||||
color: 'padding: 1.2rem 1rem',
|
||||
list: 'grid-template-columns: auto auto auto;padding: 0 1rem;',
|
||||
},
|
||||
stylePanel: {
|
||||
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 loadData = () => {
|
||||
@ -93,6 +109,7 @@ export default defineComponent({
|
||||
load,
|
||||
setBGcolor,
|
||||
loadData,
|
||||
modelStyle
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -137,11 +154,13 @@ export default defineComponent({
|
||||
// }
|
||||
.list {
|
||||
width: 100%;
|
||||
padding: 0 0 0 1rem;
|
||||
display: grid;
|
||||
grid-gap: 5px;
|
||||
&__img {
|
||||
cursor: pointer;
|
||||
width: 92px;
|
||||
width: 100%;
|
||||
height: 92px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
&__img:hover::before {
|
||||
content: ' ';
|
||||
@ -165,7 +184,6 @@ export default defineComponent({
|
||||
|
||||
.color {
|
||||
&__box {
|
||||
padding: 1.2rem 1rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-27 15:16:07
|
||||
* @Description: 素材列表,主要用于文字组合列表
|
||||
* @LastEditors: rayadaschn 115447518+rayadaschn@users.noreply.github.com
|
||||
* @LastEditTime: 2023-09-01 14:14:27
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-11 17:59:57
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
@ -15,27 +15,22 @@
|
||||
</el-input>
|
||||
</div>
|
||||
<el-divider content-position="left">推荐组件</el-divider> -->
|
||||
<div v-show="!currentCategory" class="content__wrap">
|
||||
<div v-for="(t, ti) in types" :key="ti + 't'">
|
||||
<div v-if="showList[ti]?.length > 0" class="types__header" @click="selectTypes(t)">
|
||||
<span style="flex: 1">{{ t.name }}</span>
|
||||
<span class="types__header-more">全部<i class="iconfont icon-right"></i></span>
|
||||
</div>
|
||||
<div v-else class="loading"><i class="el-icon-loading"></i> 拼命加载中</div>
|
||||
<classHeader v-show="!currentCategory" :types="types" @select="selectTypes">
|
||||
<template v-slot="{ index }">
|
||||
<div class="list-wrap">
|
||||
<div v-for="(item, i) in showList[ti]" :key="i + 'sl'" draggable="false" @mousedown="dragStart($event, item)" @mousemove="mousemove" @mouseup="mouseup" @click.stop="selectItem(item)" @dragstart="dragStart($event, item)">
|
||||
<el-image class="list__img-thumb" :src="item.cover" fit="contain"></el-image>
|
||||
<div v-for="(item, i) in showList[index]" :key="i + 'sl'" draggable="false" @mousedown="dragStart($event, item)" @mousemove="mousemove" @mouseup="mouseup" @click.stop="selectItem(item)" @dragstart="dragStart($event, item)">
|
||||
<el-image class="list__img-thumb" :src="item.cover" fit="contain" lazy loading="lazy"></el-image>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</classHeader>
|
||||
|
||||
<ul v-if="currentCategory" v-infinite-scroll="load" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
|
||||
<span class="types__header-back" @click="back"><i class="iconfont icon-right"></i>{{ currentCategory.name }}</span>
|
||||
<classHeader :is-back="true" @back="back">{{ currentCategory.name }}</classHeader>
|
||||
<el-space fill wrap :fillRatio="30" direction="horizontal" class="list">
|
||||
<div v-for="(item, i) in list" :key="i + 'i'" class="list__item" draggable="false" @mousedown="dragStart($event, item)" @mousemove="mousemove" @mouseup="mouseup" @click.stop="selectItem(item)" @dragstart="dragStart($event, item)">
|
||||
<!-- <edit-model :isComp="true" @action="action($event, item, i)"> -->
|
||||
<el-image class="list__img" :src="item.cover" fit="contain"> </el-image>
|
||||
<el-image class="list__img" :src="item.cover" fit="contain" lazy loading="lazy" />
|
||||
<!-- </edit-model> -->
|
||||
</div>
|
||||
</el-space>
|
||||
@ -47,20 +42,22 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, onMounted, watch } from 'vue'
|
||||
// import { ElDivider } from 'element-plus'
|
||||
import api from '@/api'
|
||||
import { mapActions } from 'vuex'
|
||||
import { useStore } from 'vuex'
|
||||
import getComponentsData from '@/common/methods/DesignFeatures/setComponents'
|
||||
import DragHelper from '@/common/hooks/dragHelper'
|
||||
import setImageData from '@/common/methods/DesignFeatures/setImage'
|
||||
const dragHelper = new DragHelper()
|
||||
let isDrag = false
|
||||
let startPoint = { x: 99999, y: 99999 }
|
||||
let tempDetail: any = null
|
||||
import setItem2Data from '@/common/methods/DesignFeatures/setImage'
|
||||
|
||||
export default defineComponent({
|
||||
// components: { ElDivider },
|
||||
components: {},
|
||||
setup(props) {
|
||||
// 拖拽效果相关
|
||||
const dragHelper = new DragHelper()
|
||||
let isDrag = false
|
||||
let startPoint = { x: 99999, y: 99999 }
|
||||
let tempDetail: any = null
|
||||
// 缓存组件用以减少接口请求的次数
|
||||
const compsCache: any = {}
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
loadDone: false,
|
||||
@ -70,12 +67,12 @@ export default defineComponent({
|
||||
types: [],
|
||||
showList: [],
|
||||
})
|
||||
const store = useStore()
|
||||
const pageOptions = { type: 1, page: 0, pageSize: 20 }
|
||||
|
||||
onMounted(async () => {
|
||||
if (state.types.length <= 0) {
|
||||
const types = await api.material.getKinds({ type: 3 })
|
||||
types.shift()
|
||||
state.types = types
|
||||
for (const iterator of types) {
|
||||
const { list } = await api.home.getCompList({
|
||||
@ -89,11 +86,11 @@ export default defineComponent({
|
||||
})
|
||||
const mouseup = (e: any) => {
|
||||
e.preventDefault()
|
||||
setTimeout(() => {
|
||||
isDrag = false
|
||||
tempDetail = null
|
||||
startPoint = { x: 99999, y: 99999 }
|
||||
}, 10)
|
||||
// setTimeout(() => {
|
||||
isDrag = false
|
||||
tempDetail = null
|
||||
startPoint = { x: 99999, y: 99999 }
|
||||
// }, 10)
|
||||
}
|
||||
const mousemove = (e: any) => {
|
||||
e.preventDefault()
|
||||
@ -150,29 +147,31 @@ export default defineComponent({
|
||||
state.currentCategory = null
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
load,
|
||||
action,
|
||||
back,
|
||||
selectTypes,
|
||||
mouseup,
|
||||
mousemove,
|
||||
const dragStart = async (e: any, { id, width, height, cover }: any) => {
|
||||
startPoint = { x: e.x, y: e.y }
|
||||
// tempDetail = await api.home.getTempDetail({ id, type: 1 })
|
||||
// let finalWidth = tempDetail.width
|
||||
// 计算出拖拽到画布数值
|
||||
const img = await setItem2Data({ width, height, url: cover })
|
||||
dragHelper.start(e, img.canvasWidth)
|
||||
tempDetail = await getCompDetail({ id, type: 1 })
|
||||
if (Array.isArray(JSON.parse(tempDetail.data))) {
|
||||
store.commit('selectItem', { data: JSON.parse(tempDetail.data), type: 'group' })
|
||||
} else {
|
||||
store.commit('selectItem', { data: JSON.parse(tempDetail.data), type: 'text' })
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['addGroup', 'addWidget']),
|
||||
async selectItem(item: any) {
|
||||
|
||||
const selectItem = async (item: any) => {
|
||||
if (isDrag) {
|
||||
return
|
||||
}
|
||||
this.$store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
tempDetail = tempDetail || (await api.home.getTempDetail({ id: item.id, type: 1 }))
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
tempDetail = tempDetail || (await getCompDetail({ id: item.id, type: 1 }))
|
||||
// let group = JSON.parse(tempDetail.data)
|
||||
const group: any = await getComponentsData(tempDetail.data)
|
||||
let parent: any = { x: 0, y: 0 }
|
||||
const { width: pW, height: pH } = this.$store.getters.dPage
|
||||
console.log(group)
|
||||
const { width: pW, height: pH } = store.getters.dPage
|
||||
|
||||
Array.isArray(group) &&
|
||||
group.forEach((element: any) => {
|
||||
@ -183,29 +182,38 @@ export default defineComponent({
|
||||
element.left += (pW - parent.width) / 2
|
||||
element.top += (pH - parent.height) / 2
|
||||
})
|
||||
this.addGroup(group)
|
||||
store.dispatch('addGroup', group)
|
||||
} else {
|
||||
group.text && (group.text = decodeURIComponent(group.text))
|
||||
group.left = pW / 2 - group.fontSize * (group.text.length / 2)
|
||||
group.top = pH / 2 - group.fontSize / 2
|
||||
this.addWidget(group)
|
||||
store.dispatch('addWidget', group)
|
||||
}
|
||||
},
|
||||
async dragStart(e: any, { id }: any) {
|
||||
startPoint = { x: e.x, y: e.y }
|
||||
tempDetail = await api.home.getTempDetail({ id, type: 1 })
|
||||
let finalWidth = tempDetail.width
|
||||
if (finalWidth) {
|
||||
const img = await setImageData(tempDetail)
|
||||
finalWidth = img.canvasWidth
|
||||
}
|
||||
dragHelper.start(e, finalWidth)
|
||||
if (Array.isArray(JSON.parse(tempDetail.data))) {
|
||||
this.$store.commit('selectItem', { data: JSON.parse(tempDetail.data), type: 'group' })
|
||||
} else {
|
||||
this.$store.commit('selectItem', { data: JSON.parse(tempDetail.data), type: 'text' })
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function getCompDetail(params: any) {
|
||||
// 有缓存则直接返回组件数据,否则请求获取数据
|
||||
return new Promise((resolve) => {
|
||||
if (compsCache[params.id]) {
|
||||
resolve(compsCache[params.id])
|
||||
} else api.home.getTempDetail(params).then((res: any) => {
|
||||
resolve(res)
|
||||
compsCache[params.id] = res // 缓存请求的组件数据
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
load,
|
||||
action,
|
||||
back,
|
||||
selectTypes,
|
||||
mouseup,
|
||||
mousemove,
|
||||
dragStart,
|
||||
selectItem,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@ -259,76 +267,9 @@ export default defineComponent({
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.types {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 0 0 6px;
|
||||
&__item {
|
||||
position: relative;
|
||||
width: 64px;
|
||||
// height: 44px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin: 8px 4px 0 4px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
text-shadow: 0 1px 0 rgb(0 0 0 / 25%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
&--select {
|
||||
opacity: 1;
|
||||
}
|
||||
&__header {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #a0a0a0;
|
||||
font-size: 13px;
|
||||
}
|
||||
&-back {
|
||||
cursor: pointer;
|
||||
padding: 0 0 0 0.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
height: 2.9rem;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
background: #ffffff;
|
||||
width: 320px;
|
||||
.icon-right {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.list-wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.8rem;
|
||||
}
|
||||
.content {
|
||||
&__wrap {
|
||||
padding: 0.5rem 1rem;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,38 +2,34 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-27 15:16:07
|
||||
* @Description: 素材列表
|
||||
* @LastEditors: rayadaschn 115447518+rayadaschn@users.noreply.github.com
|
||||
* @LastEditTime: 2023-09-01 14:18:30
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-24 11:14:28
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<search-header v-model="searchKeyword" type="none" @change="searchChange" />
|
||||
<div style="height: 0.5rem" />
|
||||
<!-- <div class="types">
|
||||
<div v-for="(t, ti) in types" :key="ti + 't'" :style="{ backgroundColor: colors[ti] }" :class="['types__item', { 'types--select': currentType === t.id }]" @click="selectType(t)"></div>
|
||||
</div> -->
|
||||
<!-- <div class="tags">
|
||||
<el-check-tag v-for="(t2, t2i) in sub" :key="t2i + 't2'" :checked="t2.id === currentCheck" class="tags__item" @click="tagsChange(t2.id)">{{ t2.name }}</el-check-tag>
|
||||
</div> -->
|
||||
<div v-show="!currentCategory" class="content__wrap">
|
||||
<div v-for="(t, ti) in types" :key="ti + 't'">
|
||||
<div v-if="showList[ti]?.length > 0" class="types__header" @click="selectTypes(t)">
|
||||
<span style="flex: 1">{{ t.name }}</span>
|
||||
<span class="types__header-more">全部<i class="iconfont icon-right"></i></span>
|
||||
</div>
|
||||
<div v-else class="loading"><i class="el-icon-loading" /> 拼命加载中</div>
|
||||
<classHeader v-show="!currentCategory" :types="types" @select="selectTypes">
|
||||
<template v-slot="{ index }">
|
||||
<div class="list-wrap">
|
||||
<div v-for="(item, i) in showList[ti]" :key="i + 'sl'" draggable="false" @mousedown="dragStart($event, item)" @mousemove="mousemove" @mouseup="mouseup" @click.stop="selectItem(item)" @dragstart="dragStart($event, item)">
|
||||
<el-image class="list__img-thumb" :src="item.thumb" fit="contain"></el-image>
|
||||
<div v-for="(item, i) in showList[index]" :key="i + 'sl'" draggable="false" @mousedown="dragStart($event, item)" @mousemove="mousemove" @mouseup="mouseup" @click.stop="selectItem(item)" @dragstart="dragStart($event, item)">
|
||||
<el-image class="list__img-thumb" :src="item.thumb" fit="contain" lazy loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</classHeader>
|
||||
|
||||
<ul v-if="currentCategory" v-infinite-scroll="load" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
|
||||
<span class="types__header-back" @click="back"><i class="iconfont icon-right"></i>{{ currentCategory.name }}</span>
|
||||
<classHeader :is-back="true" @back="back">{{ currentCategory.name }}</classHeader>
|
||||
<el-space fill wrap :fillRatio="30" direction="horizontal" class="list">
|
||||
<div v-for="(item, i) in list" :key="i + 'i'" class="list__item" draggable="false" @mousedown="dragStart($event, item)" @mousemove="mousemove" @mouseup="mouseup" @click.stop="selectItem(item)" @dragstart="dragStart($event, item)">
|
||||
<el-image class="list__img" :src="item.thumb" fit="contain"></el-image>
|
||||
<el-image class="list__img" :src="item.thumb" fit="contain" lazy loading="lazy" />
|
||||
</div>
|
||||
</el-space>
|
||||
<div v-show="loading" class="loading"><i class="el-icon-loading" /> 拼命加载中</div>
|
||||
@ -50,11 +46,13 @@ import wSvg from '../../widgets/wSvg/wSvg.vue'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import setImageData from '@/common/methods/DesignFeatures/setImage'
|
||||
import DragHelper from '@/common/hooks/dragHelper'
|
||||
|
||||
let isDrag = false
|
||||
let startPoint = { x: 99999, y: 99999 }
|
||||
const dragHelper = new DragHelper()
|
||||
|
||||
export default defineComponent({
|
||||
components: {},
|
||||
props: ['active'],
|
||||
setup(props) {
|
||||
const colors = ['#f8704b', '#5b89ff', '#2cc4cc', '#a8ba73', '#f8704b']
|
||||
@ -76,7 +74,6 @@ export default defineComponent({
|
||||
onMounted(async () => {
|
||||
if (state.types.length <= 0) {
|
||||
const types = await api.material.getKinds({ type: 2 })
|
||||
types.shift()
|
||||
state.types = types
|
||||
for (const iterator of types) {
|
||||
const { list } = await api.material.getList({
|
||||
@ -187,7 +184,8 @@ export default defineComponent({
|
||||
},
|
||||
async dragStart(e: any, item: any) {
|
||||
startPoint = { x: e.x, y: e.y }
|
||||
const img = await setImageData(item)
|
||||
const { width, height, thumb, url } = item
|
||||
const img = await setImageData({ width, height, url: thumb || url })
|
||||
dragHelper.start(e, img.canvasWidth)
|
||||
this.$store.commit('selectItem', { data: { value: item }, type: item.type })
|
||||
},
|
||||
@ -200,65 +198,6 @@ export default defineComponent({
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.types {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 0 0 6px;
|
||||
&__item {
|
||||
position: relative;
|
||||
width: 64px;
|
||||
// height: 44px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin: 8px 4px 0 4px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
text-shadow: 0 1px 0 rgb(0 0 0 / 25%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
&--select {
|
||||
opacity: 1;
|
||||
}
|
||||
&__header {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #a0a0a0;
|
||||
font-size: 13px;
|
||||
}
|
||||
&-back {
|
||||
cursor: pointer;
|
||||
padding: 0 0 0 0.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
height: 2.9rem;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
background: #ffffff;
|
||||
width: 320px;
|
||||
.icon-right {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
padding: 20px 0 0 10px;
|
||||
&__item {
|
||||
@ -309,13 +248,4 @@ export default defineComponent({
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.content {
|
||||
&__wrap {
|
||||
padding: 1rem;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,80 +1,122 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-11 18:48:23
|
||||
* @Description:
|
||||
* @LastEditors: rayadaschn 115447518+rayadaschn@users.noreply.github.com
|
||||
* @LastEditTime: 2023-09-01 14:18:14
|
||||
* @Description: 照片图库 Form:Unsplash无版权图片
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 19:04:39
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<search-header type="none" @change="searchChange" />
|
||||
<photo-list :isDone="loadDone" :listData="recommendImgList" @load="getDataList" @drag="dragStart" @select="selectImg" />
|
||||
<div style="height: 0.5rem" />
|
||||
<classHeader v-show="!currentCategory" :types="types" @select="selectTypes">
|
||||
<template v-slot="{ index }">
|
||||
<photo-list :isShort="true" :listData="showList[index]" @load="getDataList" @drag="dragStart($event, showList[index])" @select="selectImg($event, showList[index])" />
|
||||
</template>
|
||||
</classHeader>
|
||||
<div v-if="currentCategory">
|
||||
<classHeader :is-back="true" @back="back">{{ currentCategory.name }}</classHeader>
|
||||
<br /><br /><br />
|
||||
<div style="margin: 0 1rem; height: 100vh">
|
||||
<photo-list :isDone="loadDone" :listData="recommendImgList" @load="getDataList" @drag="dragStart" @select="selectImg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 图片列表
|
||||
const NAME = 'img-list-wrap'
|
||||
|
||||
import { toRefs, reactive, computed, onMounted } from 'vue'
|
||||
import wImage from '../../widgets/wImage/wImage.vue'
|
||||
import api from '@/api'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { useStore } from 'vuex'
|
||||
import setImageData from '@/common/methods/DesignFeatures/setImage'
|
||||
// import imgList from './components/imgList.vue'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
// components: { imgList },
|
||||
components: {},
|
||||
props: ['active'],
|
||||
data() {
|
||||
return {
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const state = reactive({
|
||||
recommendImgList: [],
|
||||
// loading: false,
|
||||
loadDone: false,
|
||||
page: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dPage']),
|
||||
},
|
||||
currentCategory: null,
|
||||
types: [],
|
||||
showList: [],
|
||||
})
|
||||
const dPage = computed(() => store.getters.dPage)
|
||||
let loading = false
|
||||
|
||||
mounted() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['addWidget']),
|
||||
async getDataList() {
|
||||
if (this.loadDone || this.loading) {
|
||||
return
|
||||
onMounted(async () => {
|
||||
if (state.types.length <= 0) {
|
||||
const types = await api.material.getKinds({ type: 4 })
|
||||
state.types = types
|
||||
for (const iterator of types) {
|
||||
const { list } = await api.material.getImagesList({ cate: iterator.id, pageSize: 2 })
|
||||
state.showList.push(list)
|
||||
}
|
||||
}
|
||||
this.loading = true
|
||||
this.page += 1
|
||||
let { list = [], total } = await api.material.getImagesList({ page: this.page, pageSize: 30 })
|
||||
list.length <= 0 ? (this.loadDone = true) : (this.recommendImgList = this.recommendImgList.concat(list))
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 100)
|
||||
},
|
||||
async selectImg(index) {
|
||||
const item = this.recommendImgList[index]
|
||||
this.$store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
})
|
||||
|
||||
const selectImg = async (index, list) => {
|
||||
const item = list ? list[index] : state.recommendImgList[index]
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
let setting = JSON.parse(JSON.stringify(wImage.setting))
|
||||
const img = await setImageData(item) // await getImage(item.url)
|
||||
setting.width = img.width
|
||||
setting.height = img.height // parseInt(100 / item.value.ratio, 10)
|
||||
setting.imgUrl = item.url
|
||||
const { width: pW, height: pH } = this.dPage
|
||||
const { width: pW, height: pH } = dPage
|
||||
setting.left = pW / 2 - img.width / 2
|
||||
setting.top = pH / 2 - img.height / 2
|
||||
this.addWidget(setting)
|
||||
},
|
||||
dragStart(index) {
|
||||
const item = this.recommendImgList[index]
|
||||
this.$store.commit('selectItem', { data: { value: item }, type: 'image' })
|
||||
},
|
||||
searchChange(e) {
|
||||
store.dispatch('addWidget', setting)
|
||||
}
|
||||
|
||||
const getDataList = async () => {
|
||||
if (state.loadDone || loading) {
|
||||
return
|
||||
}
|
||||
loading = true
|
||||
state.page += 1
|
||||
let { list = [], total } = await api.material.getImagesList({ cate: state.currentCategory.id, page: state.page, pageSize: 30 })
|
||||
list.length <= 0 ? (state.loadDone = true) : (state.recommendImgList = state.recommendImgList.concat(list))
|
||||
setTimeout(() => {
|
||||
loading = false
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const dragStart = (index, list) => {
|
||||
const item = list ? list[index] : state.recommendImgList[index]
|
||||
store.commit('selectItem', { data: { value: item }, type: 'image' })
|
||||
}
|
||||
|
||||
const searchChange = (e) => {
|
||||
console.log(e)
|
||||
},
|
||||
}
|
||||
|
||||
const selectTypes = (item) => {
|
||||
state.currentCategory = item
|
||||
getDataList()
|
||||
}
|
||||
const back = () => {
|
||||
state.currentCategory = null
|
||||
state.page = 0
|
||||
state.loadDone = false
|
||||
state.recommendImgList = []
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
selectImg,
|
||||
getDataList,
|
||||
dragStart,
|
||||
searchChange,
|
||||
selectTypes,
|
||||
back,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-27 15:16:07
|
||||
* @Description: 模板列表
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-04 00:00:54
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-22 09:55:59
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
@ -22,7 +22,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, onMounted, ref } from 'vue'
|
||||
import { ElDivider } from 'element-plus'
|
||||
import api from '@/api'
|
||||
import { mapActions, mapGetters, useStore } from 'vuex'
|
||||
import { useRoute } from 'vue-router'
|
||||
@ -32,7 +31,7 @@ import searchHeader from './components/searchHeader.vue'
|
||||
import useConfirm from '@/common/methods/confirm'
|
||||
|
||||
export default defineComponent({
|
||||
components: { ElDivider, searchHeader },
|
||||
components: { searchHeader },
|
||||
setup() {
|
||||
const listRef = ref(null)
|
||||
const route = useRoute()
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-11 18:48:23
|
||||
* @Description: 组件列表
|
||||
* @LastEditors: rayadaschn 115447518+rayadaschn@users.noreply.github.com
|
||||
* @LastEditTime: 2023-09-01 14:17:46
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-13 18:48:25
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
@ -12,9 +12,10 @@
|
||||
<i class="icon sd-w-qrcode" />
|
||||
<div class="text"><span>二维码</span><span class="desc">在设计中使用风格化二维码</span></div>
|
||||
</div>
|
||||
<div class="header">其它</div>
|
||||
<div class="item" @click="openImageCutout">
|
||||
<i class="icon sd-w-table" />
|
||||
<div class="text"><span>AI抠图</span> <span class="desc">上传图像一键去除背景</span></div>
|
||||
<i class="icon sd-AI_zhineng" />
|
||||
<div class="text"><span>智能抠图</span> <span class="desc">上传图像一键去除背景</span></div>
|
||||
</div>
|
||||
<imageCutout ref="imageCutout" />
|
||||
</div>
|
||||
@ -55,11 +56,6 @@ export default {
|
||||
}
|
||||
this.loading = true
|
||||
this.page += 1
|
||||
// let { list = [], total } = await api.material.getImagesList({ page: this.page, pageSize: 30 })
|
||||
// list.length <= 0 ? (this.loadDone = true) : (this.recommendImgList = this.recommendImgList.concat(list))
|
||||
// setTimeout(() => {
|
||||
// this.loading = false
|
||||
// }, 100)
|
||||
},
|
||||
addQrcode() {
|
||||
this.$store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-13 22:18:35
|
||||
* @Description: 我的
|
||||
* @LastEditors: rayadaschn 115447518+rayadaschn@users.noreply.github.com
|
||||
* @LastEditTime: 2023-09-01 14:17:23
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-12-11 11:50:34
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
@ -11,16 +11,18 @@
|
||||
<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'" class="wrap">
|
||||
<div v-show="tabActiveName === 'pics'">
|
||||
<uploader v-model="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>
|
||||
<photo-list ref="imgListRef" :edit="editOptions" :isDone="isDone" :listData="imgList" @load="load" @drag="dragStart" @select="selectImg" />
|
||||
<div style="margin: 1rem; height: 100vh">
|
||||
<photo-list ref="imgListRef" :edit="editOptions.photo" :isDone="isDone" :listData="imgList" @load="load" @drag="dragStart" @select="selectImg" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="tabActiveName === 'design'" class="wrap">
|
||||
<ul ref="listRef" v-infinite-scroll="loadDesign" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
|
||||
<img-water-fall :listData="designList" @select="selectDesign" />
|
||||
<img-water-fall :edit="editOptions.works" :listData="designList" @select="selectDesign" />
|
||||
<!-- <div v-show="loading" class="loading"><i class="el-icon-loading"></i>拼命加载中..</div> -->
|
||||
<div v-show="isDone" class="loading">全部加载完毕</div>
|
||||
</ul>
|
||||
@ -143,12 +145,29 @@ export default defineComponent({
|
||||
api.material.deleteMyPhoto({ id: item.id, key })
|
||||
state.imgListRef.delItem(i) // 通知标记
|
||||
}
|
||||
state.editOptions = [
|
||||
const deleteWorks = async ({ i, item }: any) => {
|
||||
const isPass = await useConfirm('警告', '删除后不可找回,请确认操作', 'warning')
|
||||
if (isPass) {
|
||||
await api.material.deleteMyWorks({ id: item.id })
|
||||
setTimeout(() => {
|
||||
router.push({ path: '/home', query: { }, replace: true })
|
||||
loadDesign(true)
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
state.editOptions = {
|
||||
photo: [
|
||||
{
|
||||
name: '删除',
|
||||
fn: deleteImg,
|
||||
},
|
||||
],works: [
|
||||
{
|
||||
name: '删除',
|
||||
fn: deleteWorks,
|
||||
},
|
||||
]
|
||||
}
|
||||
const dragStart = (index: number) => {
|
||||
const item = state.imgList[index]
|
||||
store.commit('selectItem', { data: { value: item }, type: 'image' })
|
||||
|
109
src/components/modules/panel/wrap/components/classHeader.vue
Normal file
109
src/components/modules/panel/wrap/components/classHeader.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-04 02:04:04
|
||||
* @Description: 列表分类头部
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 02:30:59
|
||||
-->
|
||||
<template>
|
||||
<div v-if="!isBack" class="content__wrap">
|
||||
<div v-for="(t, ti) in types" :key="ti + 't'">
|
||||
<div class="types__header" @click="select(t)">
|
||||
<span style="flex: 1">{{ t.name }}</span>
|
||||
<span class="types__header-more">全部<i class="iconfont icon-right"></i></span>
|
||||
</div>
|
||||
<slot :index="ti" />
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="types__header-back" @click="back">
|
||||
<i class="iconfont icon-right"></i>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
props: ['types', 'isBack'],
|
||||
emits: ['select', 'back'],
|
||||
setup(props, { emit }) {
|
||||
const select = (item: any) => {
|
||||
emit('select', item)
|
||||
}
|
||||
const back = () => {
|
||||
emit('back')
|
||||
}
|
||||
return { select, back }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.content {
|
||||
&__wrap {
|
||||
padding: 0.5rem 1rem;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
}
|
||||
.types {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 0 0 6px;
|
||||
&__item {
|
||||
position: relative;
|
||||
width: 64px;
|
||||
// height: 44px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin: 8px 4px 0 4px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
text-shadow: 0 1px 0 rgb(0 0 0 / 25%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
&--select {
|
||||
opacity: 1;
|
||||
}
|
||||
&__header {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #a0a0a0;
|
||||
font-size: 13px;
|
||||
}
|
||||
&-back {
|
||||
cursor: pointer;
|
||||
padding: 0 0 0 0.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
height: 2.9rem;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
background: #ffffff;
|
||||
width: 320px;
|
||||
.icon-right {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -2,19 +2,17 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-01-11 17:54:14
|
||||
* @Description: 模板编辑组件
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-02-24 15:00:36
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-02-11 00:07:36
|
||||
-->
|
||||
<template>
|
||||
<div class="wrap">
|
||||
<slot />
|
||||
<div class="showMask" @click.stop="">
|
||||
<el-dropdown placement="bottom-end">
|
||||
<el-dropdown placement="bottom-end" :show-arrow="false">
|
||||
<i class="iconfont icon-more"></i>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<!-- <el-dropdown-item @click="action('edit')">编辑</el-dropdown-item>
|
||||
<el-dropdown-item @click="action('del')">删除</el-dropdown-item> -->
|
||||
<el-dropdown-item v-for="(op, oi) in options" :key="oi + 'o'" @click="op.fn(data)">{{ op.name }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
@ -56,6 +54,8 @@ export default defineComponent({
|
||||
|
||||
<style lang="less" scoped>
|
||||
.wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.wrap:hover > .showMask {
|
||||
|
31
src/components/modules/panel/wrap/components/imageTip.vue
Normal file
31
src/components/modules/panel/wrap/components/imageTip.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-04 19:12:40
|
||||
* @Description: 图片描述ToolTip
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 22:51:06
|
||||
-->
|
||||
<template>
|
||||
<el-tooltip :disabled="!detail.author" :offset="1" effect="light" placement="bottom-start" :hide-after="0" :enterable="false" raw-content>
|
||||
<template #content>
|
||||
<p style="max-width: 140px">
|
||||
<b>{{ detail.description }}</b>
|
||||
</p>
|
||||
<p>@{{ detail.author }}</p>
|
||||
</template>
|
||||
<slot />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
detail: {},
|
||||
},
|
||||
setup() {
|
||||
return {}
|
||||
},
|
||||
})
|
||||
</script>
|
@ -2,92 +2,95 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-12-16 16:20:16
|
||||
* @Description: 瀑布流组件
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-06 17:58:49
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-12-11 11:45:24
|
||||
-->
|
||||
<template>
|
||||
<div ref="imgWaterFall" :style="{ height: countHeight + 'px' }" class="img-water-fall">
|
||||
<!-- backgroundImage: `url(${item.cover})` -->
|
||||
<div v-for="(item, index) in list" :key="index + 'iwf'" :style="{ top: item.top + 'px', left: item.left + 'px', width: width + 'px', height: item.height + 'px' }" class="img-box" @click.stop="selectItem(item, index)">
|
||||
<v-lazy-image v-if="!item.fail" class="img" @error="loadError(item)" :src="item.cover" />
|
||||
<div class="fail_img" v-else>{{ item.title }}</div>
|
||||
<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 }">
|
||||
{{ 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)" />
|
||||
<div v-else class="fail_img">{{ item.title }}</div>
|
||||
</edit-model>
|
||||
<el-image v-else class="img" :src="item.cover" lazy loading="lazy" @error="loadError(item)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vLazyImage from 'v-lazy-image'
|
||||
const NAME = 'img-water-fall'
|
||||
// import api from '@/api/album'
|
||||
const columnHeights = [] // 列的高度
|
||||
const columnNums = 2 // 总共有多少列
|
||||
const gap = 7 // 图片之间的间隔
|
||||
import { defineComponent, toRefs, reactive, watch } from 'vue'
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: NAME,
|
||||
components: { vLazyImage },
|
||||
props: {
|
||||
listData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
// scroll: {
|
||||
// default: true,
|
||||
// },
|
||||
edit: {}
|
||||
},
|
||||
emits: ['select', 'load'],
|
||||
data() {
|
||||
return {
|
||||
setup(props, { emit }) {
|
||||
const state = reactive({
|
||||
width: 146, // 图片的宽度
|
||||
list: [],
|
||||
countHeight: 0,
|
||||
})
|
||||
|
||||
const columnHeights = [] // 列的高度
|
||||
const columnNums = 2 // 总共有多少列
|
||||
const gap = 7 // 图片之间的间隔
|
||||
|
||||
watch(
|
||||
() => props.listData,
|
||||
() => {
|
||||
columnHeights.length = 0
|
||||
const widthLimit = state.width * columnNums // + gap * (columnNums - 1) // 每行宽度
|
||||
const cloneList = JSON.parse(JSON.stringify(props.listData))
|
||||
for (let i = 0; i < cloneList.length; i++) {
|
||||
let index = i % columnNums
|
||||
const item = cloneList[i]
|
||||
item.height = (item.height / item.width) * state.width // 图片高度
|
||||
item.left = index * (widthLimit / columnNums + gap) // 定位
|
||||
item.top = columnHeights[index] + gap || 0 // 定位
|
||||
// columnHeights[index] = isNaN(columnHeights[index]) ? item.height : item.height + columnHeights[index] + gap // 记录列高度
|
||||
// 找出最短边
|
||||
if (isNaN(columnHeights[index])) {
|
||||
columnHeights[index] = item.height
|
||||
} else {
|
||||
index = columnHeights.indexOf(Math.min(...columnHeights))
|
||||
item.left = index * (widthLimit / columnNums + gap)
|
||||
item.top = columnHeights[index] + gap || 0
|
||||
columnHeights[index] = item.height + columnHeights[index] + gap
|
||||
}
|
||||
}
|
||||
state.countHeight = Math.max(...columnHeights)
|
||||
state.list = cloneList
|
||||
},
|
||||
)
|
||||
|
||||
const load = () => {
|
||||
emit('load')
|
||||
}
|
||||
const selectItem = (value, index) => {
|
||||
emit('select', value)
|
||||
}
|
||||
const loadError = (item) => {
|
||||
item.fail = true
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
load,
|
||||
selectItem,
|
||||
loadError,
|
||||
}
|
||||
},
|
||||
// async mounted() {
|
||||
|
||||
// },
|
||||
watch: {
|
||||
listData() {
|
||||
columnHeights.length = 0
|
||||
const widthLimit = this.width * columnNums // + gap * (columnNums - 1) // 每行宽度
|
||||
const cloneList = JSON.parse(JSON.stringify(this.listData))
|
||||
let lineWidth = 0
|
||||
let index = 0
|
||||
for (let i = 0; i < cloneList.length; i++) {
|
||||
const item = cloneList[i]
|
||||
const r = item.width / this.width
|
||||
item.height = item.height / r
|
||||
lineWidth += this.width
|
||||
if (lineWidth >= widthLimit) {
|
||||
index++
|
||||
item.left = this.width + gap
|
||||
item.top = columnHeights[index] + gap || 0
|
||||
columnHeights[index] = typeof columnHeights[index] === 'number' ? item.height + columnHeights[index] + gap : item.height
|
||||
// init
|
||||
lineWidth = 0
|
||||
index = 0
|
||||
} else {
|
||||
item.top = columnHeights[index] + gap || 0
|
||||
item.left = 0
|
||||
columnHeights[index] = typeof columnHeights[index] === 'number' ? item.height + columnHeights[index] + gap : item.height
|
||||
}
|
||||
}
|
||||
this.countHeight = Math.max(...columnHeights)
|
||||
this.list = cloneList
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
this.$emit('load')
|
||||
},
|
||||
selectItem(value, index) {
|
||||
this.$emit('select', value)
|
||||
},
|
||||
loadError(item) {
|
||||
item.fail = true
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@ -128,4 +131,17 @@ export default {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.list {
|
||||
&__mask {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,21 +2,26 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-02-23 15:48:52
|
||||
* @Description: 图片列表组件 Bookshelf Layout
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-17 15:20:25
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-12-11 11:12:04
|
||||
-->
|
||||
<template>
|
||||
<ul ref="listRef" class="img-list-wrap" style="overflow: auto" @scroll="scrollEvent($event)">
|
||||
<ul ref="listRef" class="img-list-wrap" :style="{ paddingBottom: isShort ? '15px' : '200px' }" @scroll="scrollEvent($event)">
|
||||
<div class="list">
|
||||
<div v-for="(item, i) in list" :key="i + 'i'" :style="{ width: item.listWidth + 'px' }" class="list__img" draggable="false" @mousedown="dragStart($event, i)" @mousemove="mousemove" @mouseup="mouseup" @click.stop="select(i)" @dragstart="dragStart($event, i)">
|
||||
<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-if="item.isDelect" class="list__mask">已删除</div>
|
||||
<img class="img" :src="item.thumb || item.cover || item.url" />
|
||||
<el-image class="img transparent-bg" :src="item.thumb || item.url" :style="{ height: getInnerHeight(item) + 'px' }" lazy loading="lazy" />
|
||||
</edit-model>
|
||||
<template v-else>
|
||||
<img class="img" :src="item.thumb || item.cover || item.url" />
|
||||
<imageTip :detail="item">
|
||||
<el-image class="img" :src="item.thumb || item.url" :style="{ height: getInnerHeight(item) + 'px' }" lazy loading="lazy">
|
||||
<template #placeholder>
|
||||
<div :style="{ backgroundColor: item.color }" class="image-color" />
|
||||
</template>
|
||||
</el-image>
|
||||
</imageTip>
|
||||
</template>
|
||||
<!-- <el-image :src="item.thumb || item.url" fit="cover"></el-image> -->
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isDone" v-show="loading" class="loading"><i class="el-icon-loading" /> 拼命加载中</div>
|
||||
@ -34,6 +39,9 @@ export default defineComponent({
|
||||
listData: {},
|
||||
edit: {},
|
||||
isDone: {},
|
||||
isShort: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['load', 'drag', 'select'],
|
||||
setup(props, context) {
|
||||
@ -63,14 +71,15 @@ export default defineComponent({
|
||||
watch(
|
||||
() => props.listData,
|
||||
async (newList: any, oldList: any) => {
|
||||
!oldList && (oldList = [])
|
||||
if (newList.length <= 0) {
|
||||
state.list.length = 0
|
||||
return
|
||||
}
|
||||
let list = newList.filter((v: any) => !newList.includes(v) || !oldList.includes(v)) // difference
|
||||
list = JSON.parse(JSON.stringify(list))
|
||||
const limitWidth = (await getFatherWidth()) - 32 // 296 // 256
|
||||
const marginRight = 6 // 间距
|
||||
const limitWidth = (await getFatherWidth()) - marginRight
|
||||
const standardHeight = 280 // 高度阈值
|
||||
const neatArr: any = [] // 整理后的数组
|
||||
function factory(cutArr: any) {
|
||||
@ -103,8 +112,9 @@ export default defineComponent({
|
||||
}
|
||||
const { list: newList, height }: any = await factory([list.shift()])
|
||||
neatArr.push(
|
||||
newList.map((x: any) => {
|
||||
newList.map((x: any, index) => {
|
||||
x.listWidth = (x.width / x.height) * height
|
||||
x.gap = index !== newList.length - 1 ? marginRight : 0
|
||||
return x
|
||||
}),
|
||||
)
|
||||
@ -126,6 +136,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function getRef() {
|
||||
// 用于在组件外调用内部ref
|
||||
return state.listRef
|
||||
}
|
||||
|
||||
@ -154,6 +165,9 @@ export default defineComponent({
|
||||
load()
|
||||
}
|
||||
}
|
||||
|
||||
const getInnerHeight = ({ height, listWidth, width }: any) => (height * listWidth) / width
|
||||
|
||||
return {
|
||||
load,
|
||||
dragStart,
|
||||
@ -164,6 +178,7 @@ export default defineComponent({
|
||||
getRef,
|
||||
mouseup,
|
||||
mousemove,
|
||||
getInnerHeight,
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -172,23 +187,29 @@ export default defineComponent({
|
||||
<style lang="less" scoped>
|
||||
.img-list-wrap {
|
||||
height: 100%;
|
||||
margin-top: 14px;
|
||||
// overflow-y: scroll;
|
||||
padding-bottom: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
.img {
|
||||
transform-origin: center;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.image-color {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
animation: breathe 600ms ease-out infinite alternate;
|
||||
}
|
||||
.list {
|
||||
position: relative;
|
||||
padding: 4px 0 0 14px;
|
||||
// padding: 4px 0 0 14px;
|
||||
padding: 4px 0 0 0;
|
||||
&__img {
|
||||
background: #f1f2f4;
|
||||
// background: #f1f2f4;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin: 0 6px 2px 0;
|
||||
// margin: 0 6px 2px 0;
|
||||
margin-bottom: 3px;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@ -201,9 +222,10 @@ export default defineComponent({
|
||||
}
|
||||
&__mask {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -216,4 +238,13 @@ export default defineComponent({
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
/* 呼吸效果 */
|
||||
@keyframes breathe {
|
||||
0% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-01-27 11:05:48
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-06-29 16:50:02
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-04 01:53:10
|
||||
-->
|
||||
<template>
|
||||
<div class="search__wrap">
|
||||
@ -19,7 +19,7 @@
|
||||
</el-dropdown>
|
||||
<span v-else style="width: 1rem"></span>
|
||||
|
||||
<el-input size="large" v-model="searchValue" placeholder="输入关键词搜索" class="input-with-select">
|
||||
<el-input v-model="searchValue" size="large" placeholder="输入关键词搜索" class="input-with-select">
|
||||
<template #append>
|
||||
<el-button><i class="iconfont icon-search"></i></el-button>
|
||||
</template>
|
||||
@ -46,6 +46,7 @@ export default defineComponent({
|
||||
|
||||
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)
|
||||
|
@ -2,125 +2,253 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-09 14:00:23
|
||||
* @Description: 文字特效选择框组件
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-06-29 17:52:31
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-30 10:14:30
|
||||
-->
|
||||
<template>
|
||||
<el-card class="box-card" shadow="hover" :body-style="{ padding: effectSelect ? '20px' : 0 }">
|
||||
<el-card class="box-card" shadow="hover" :body-style="{ padding: '20px' }">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<!-- <template v-if="effectSelect">
|
||||
<component :is="effectSelect" class="demo" />
|
||||
</template> -->
|
||||
<div v-show="effectSelect" style="position: relative; margin-right: 24px">
|
||||
<div
|
||||
:style="{
|
||||
position: 'relative',
|
||||
width: '27px',
|
||||
fontSize: '27px',
|
||||
color: data.color,
|
||||
fontWeight: data.fontWeight,
|
||||
fontStyle: data.fontStyle,
|
||||
textDecoration: data.textDecoration,
|
||||
opacity: data.opacity,
|
||||
backgroundColor: data.backgroundColor,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-for="(ef, efi) in data"
|
||||
v-for="(ef, efi) in modelValue"
|
||||
:key="efi + 'effect'"
|
||||
:style="{
|
||||
color: ef.filling && ef.filling.type === 2 ? 'transparent' : ef.filling ? ef.filling.color : undefined,
|
||||
webkitTextStroke: ef.stroke ? `${ef.stroke.width}px ${ef.stroke.color}` : undefined,
|
||||
textShadow: ef.shadow ? `${ef.shadow.offsetX}px ${ef.shadow.offsetY}px ${ef.shadow.blur}px ${ef.shadow.color}` : undefined,
|
||||
backgroundImage: ef.filling && ef.filling.type === 2 ? `linear-gradient(${ef.filling.gradient.angle}deg, ${ef.filling.gradient.stops[0].color} ${Number(ef.filling.gradient.stops[0].offset) * 100}%, ${ef.filling.gradient.stops[1].color} ${Number(ef.filling.gradient.stops[1].offset) * 100}%)` : undefined,
|
||||
webkitBackgroundClip: ef.filling && ef.filling.type === 2 ? 'text' : undefined,
|
||||
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,
|
||||
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,
|
||||
}"
|
||||
class="demo"
|
||||
>
|
||||
A
|
||||
</div>
|
||||
A
|
||||
</div>
|
||||
<div v-show="!effectSelect">A</div>
|
||||
<span class="title">文字特效</span>
|
||||
<el-popover :visible="visiable" placement="left" :width="220" trigger="click">
|
||||
<div class="select__box">
|
||||
<div class="select__box__select-item" @click="select(null, { color: '' })">无</div>
|
||||
<div class="select__box__select-item">
|
||||
<div class="e-box" @click="select('effect', { color: '#ffffff' })" />
|
||||
<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)">
|
||||
<img :src="l.cover" />
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button class="button" link @click="visiable = !visiable">{{ visiable ? '取消' : '选择' }}</el-button>
|
||||
<el-button class="button" link @click="openSet">{{ visiable ? '取消' : '选择' }}</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
<!-- filling 描边 stroke 阴影 shadow -->
|
||||
<div v-for="(f, fi) in data" v-show="effectSelect" :key="'f' + fi" class="feature__wrap">
|
||||
<template v-if="f.filling && f.filling.type == 0">
|
||||
<div class="feature__header">填充</div>
|
||||
<color-select v-model="f.filling.color" class="feature__color" label="" @finish="(value) => finish('color', value)" />
|
||||
<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>
|
||||
<el-collapse-item>
|
||||
<template #title>
|
||||
<b>高级编辑</b>
|
||||
</template>
|
||||
<template v-if="f.stroke">
|
||||
<div class="feature__header">描边</div>
|
||||
<color-select v-model="f.stroke.color" class="feature__color" label="" @finish="(value) => finish('color', value)" />
|
||||
<div class="text item"><span class="strength">强度</span> <el-slider v-model="f.stroke.width" show-input input-size="small" :show-input-controls="false"> </el-slider></div>
|
||||
</template>
|
||||
<template v-if="f.shadow">
|
||||
<div class="feature__header">阴影</div>
|
||||
<color-select v-model="f.shadow.color" class="feature__color" label="" @finish="(value) => finish('color', value)" />
|
||||
<div class="text item"><span class="strength">模糊</span> <el-slider v-model="f.shadow.blur" show-input input-size="small" :show-input-controls="false"> </el-slider></div>
|
||||
</template>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<draggable v-model="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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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" />
|
||||
<numberInput v-model="element.shadow.offsetY" prepend="y" style="width: 30px; margin-right: 0.5rem" type="simple" />
|
||||
<color-select v-model="element.shadow.color" width="28px" label="" @finish="(value) => finish('color', value)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</el-collapse-item>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, watch, onMounted, nextTick } from 'vue'
|
||||
import { defineComponent, toRefs, reactive, watch, onMounted, nextTick, computed } 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 = []
|
||||
|
||||
export default defineComponent({
|
||||
components: { colorSelect },
|
||||
components: { colorSelect, ElInputNumber, numberInput, ElCheckbox, draggable },
|
||||
props: ['modelValue', 'degree', 'data'],
|
||||
emits: ['select'],
|
||||
setup(props, context) {
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const state = reactive({
|
||||
strength: 20, // 强度
|
||||
effectSelect: '', // 选择的模板
|
||||
strength: 50, // 强度
|
||||
visiable: false, // 弹出选择层控制
|
||||
list: [],
|
||||
layers: [],
|
||||
draging: false,
|
||||
unfold: true,
|
||||
})
|
||||
const select = (value: string = '', style: any) => {
|
||||
state.effectSelect = value
|
||||
state.visiable = false
|
||||
context.emit('select', { key: 'isEffect', value, style })
|
||||
context.emit('select', { key: 'degree', value: 20 })
|
||||
const dragOptions = {
|
||||
animation: 300,
|
||||
ghostClass: 'ghost',
|
||||
chosenClass: 'choose',
|
||||
}
|
||||
const coefficient = computed(() => Math.round(160 / 27))
|
||||
let rawData: any = [] // 初始化记录数据,用于强度修改
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
// state.effectSelect = props?.modelValue || ''
|
||||
if (props.data && props.data.length > 0) {
|
||||
state.effectSelect = 'true'
|
||||
// console.log(props.data)
|
||||
if (!props.data.textEffects) {
|
||||
return
|
||||
}
|
||||
// state.strength = props?.degree || state.strength
|
||||
const clone = JSON.parse(JSON.stringify(props.data.textEffects)) || []
|
||||
state.layers = clone
|
||||
.map((x: any) => {
|
||||
x.uuid = String(Math.random())
|
||||
return x
|
||||
})
|
||||
.reverse()
|
||||
rawData = JSON.parse(JSON.stringify(state.layers))
|
||||
})
|
||||
// watch(
|
||||
// () => props.degree,
|
||||
// (value) => {
|
||||
// state.strength = props?.degree || state.strength
|
||||
// },
|
||||
// )
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
state.effectSelect = value || ''
|
||||
},
|
||||
)
|
||||
// watch(
|
||||
// () => state.strength,
|
||||
// (strength) => {
|
||||
// context.emit('select', { key: 'degree', value: strength }) // 组件改变强度
|
||||
// },
|
||||
// )
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
state.effectSelect = props.data ? 'true' : ''
|
||||
() => state.layers,
|
||||
(v) => {
|
||||
const newEffect = v.map((x) => {
|
||||
delete x.uuid
|
||||
return x
|
||||
})
|
||||
emit('update:modelValue', newEffect.reverse())
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
// 选中加载特效预设
|
||||
const selectEffect = async (id) => {
|
||||
state.visiable = false
|
||||
if (id) {
|
||||
const { data } = await api.home.getTempDetail({ id, type: 1 })
|
||||
state.layers = JSON.parse(data)
|
||||
.textEffects.map((x) => {
|
||||
x.uuid = String(Math.random())
|
||||
return x
|
||||
})
|
||||
.reverse()
|
||||
} else state.layers = []
|
||||
}
|
||||
|
||||
// 删除效果层
|
||||
const removeLayer = (i: number) => {
|
||||
state.layers.splice(i, 1)
|
||||
rawData = JSON.parse(JSON.stringify(state.layers))
|
||||
}
|
||||
|
||||
// 添加效果层
|
||||
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 colorChange = (e: any, item: any) => {
|
||||
const modeStr: any = {
|
||||
渐变: 2,
|
||||
纯色: 0,
|
||||
}
|
||||
item.gradient = {
|
||||
angle: e.angle,
|
||||
stops: e.stops,
|
||||
}
|
||||
setTimeout(() => {
|
||||
item.type = modeStr[e.mode] || 0
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// const onMove = ({ relatedContext, draggedContext }: any) => {
|
||||
// const relatedElement = relatedContext.element
|
||||
// const draggedElement = draggedContext.element
|
||||
// return (!relatedElement || relatedElement.parent == -1) && draggedElement.parent == -1
|
||||
// }
|
||||
const onDone = () => {
|
||||
state.draging = false
|
||||
}
|
||||
|
||||
const strengthChange = (x: any) => {
|
||||
const effectScale = 1 + (x - 50) / 50
|
||||
state.layers.forEach((item: any, index) => {
|
||||
if (item.stroke) {
|
||||
item.stroke.width = rawData[index].stroke.width * effectScale
|
||||
}
|
||||
if (item.shadow) {
|
||||
item.shadow.blur = rawData[index].shadow.blur * effectScale
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 打开特效字体集
|
||||
const openSet = async () => {
|
||||
state.visiable = !state.visiable
|
||||
if (froze_font_effect_list.length <= 0) {
|
||||
const { list } = await api.home.getCompList({
|
||||
cate: 12,
|
||||
type: 1,
|
||||
pageSize: 30,
|
||||
})
|
||||
state.list = list
|
||||
froze_font_effect_list = list
|
||||
} else state.list = froze_font_effect_list
|
||||
}
|
||||
return {
|
||||
...toRefs(state),
|
||||
select,
|
||||
selectEffect,
|
||||
finish,
|
||||
coefficient,
|
||||
removeLayer,
|
||||
addLayer,
|
||||
dragOptions,
|
||||
onDone,
|
||||
strengthChange,
|
||||
openSet,
|
||||
colorChange,
|
||||
getGradientOrImg,
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
@ -128,19 +256,58 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.el-input-group__prepend) {
|
||||
padding: 0 8px;
|
||||
}
|
||||
:deep(.el-input-number__decrease) {
|
||||
width: 18px;
|
||||
}
|
||||
:deep(.el-input-number__increase) {
|
||||
width: 18px;
|
||||
}
|
||||
:deep(.el-input-number.is-controls-right .el-input__wrapper) {
|
||||
padding-right: 32px;
|
||||
}
|
||||
:deep(.el-input-group__prepend) {
|
||||
background: #ffffff;
|
||||
}
|
||||
:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
|
||||
color: #333333;
|
||||
}
|
||||
:deep(.el-collapse-item__header) {
|
||||
border-bottom: none;
|
||||
}
|
||||
:deep(.el-collapse-item__wrap) {
|
||||
border-bottom: none;
|
||||
}
|
||||
.feature {
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 6px;
|
||||
}
|
||||
&__header {
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
padding: 12px 0 2px 0;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
&__header:first-of-type {
|
||||
padding: 0 0 2px 0;
|
||||
}
|
||||
&__grab-wrap {
|
||||
position: relative;
|
||||
padding: 10px 0;
|
||||
}
|
||||
&__grab-wrap:first-of-type {
|
||||
padding-top: 0;
|
||||
}
|
||||
&__grab-wrap:last-of-type {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
&__wrap {
|
||||
position: relative;
|
||||
padding-top: 20px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
&__wrap::after {
|
||||
position: absolute;
|
||||
@ -148,7 +315,7 @@ export default defineComponent({
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: #e9e9e9;
|
||||
top: 2px;
|
||||
top: 16px;
|
||||
}
|
||||
&__wrap:first-of-type {
|
||||
padding: 0;
|
||||
@ -156,9 +323,6 @@ export default defineComponent({
|
||||
&__wrap:first-of-type::after {
|
||||
height: 0;
|
||||
}
|
||||
&__color {
|
||||
margin: 8px 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@ -182,12 +346,11 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.demo {
|
||||
margin: 0 0 0 0.15rem;
|
||||
font-size: 27px;
|
||||
color: #ffffff;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -197,9 +360,6 @@ export default defineComponent({
|
||||
font-weight: 600;
|
||||
color: #33383e;
|
||||
}
|
||||
.strength {
|
||||
width: 80px;
|
||||
}
|
||||
.select__box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -208,17 +368,69 @@ export default defineComponent({
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
height: 40px;
|
||||
width: 25%;
|
||||
width: 33%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
.e-box {
|
||||
font-size: 21px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
&__select-item:hover {
|
||||
background: rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
}
|
||||
|
||||
.layer {
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
.sd-yidong {
|
||||
cursor: grab !important;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.icon {
|
||||
// display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.icon:hover {
|
||||
transform: scale(1.1);
|
||||
color: @active-text-color;
|
||||
}
|
||||
}
|
||||
// &__title:hover > .icon {
|
||||
// display: block;
|
||||
// }
|
||||
}
|
||||
|
||||
.add-layer {
|
||||
// margin-top: 12px;
|
||||
margin-bottom: 10px;
|
||||
// width: 100%;
|
||||
}
|
||||
|
||||
// dragable
|
||||
.choose {
|
||||
border: 1px dashed #999999 !important;
|
||||
}
|
||||
|
||||
.flip-list-move {
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
.no-move {
|
||||
transition: transform 0s;
|
||||
}
|
||||
.disable {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.ghost {
|
||||
opacity: 0.3;
|
||||
background: @main-color;
|
||||
}
|
||||
.line {
|
||||
margin-top: 8px;
|
||||
height: 18px;
|
||||
border-top: 1px solid #e9e9e9;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,270 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
:id="params.uuid"
|
||||
ref="widget"
|
||||
class="w-svg"
|
||||
:style="{
|
||||
position,
|
||||
left: params.left - parent.left + 'px',
|
||||
top: params.top - parent.top + 'px',
|
||||
width: params.width + 'px',
|
||||
height: params.height + 'px',
|
||||
opacity: params.opacity,
|
||||
}"
|
||||
>
|
||||
<!-- <div v-for="(img, i) in params.imgs" v-show="cropEdit" :key="i" :ref="params.uuid + '_ebox_' + i" :style="editBoxs[i]" class="svg__edit__wrap">
|
||||
<img :img-key="i" class="edit__model" :src="img" />
|
||||
</div> -->
|
||||
<div v-show="cropEdit" :ref="params.uuid + '_ebox'" :style="editBoxStyle" class="svg__edit__wrap">
|
||||
<img class="edit__model" :src="params.imgUrl" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// svg
|
||||
const NAME = 'w-svg'
|
||||
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
setting: {
|
||||
name: '图片容器',
|
||||
type: NAME,
|
||||
uuid: -1,
|
||||
width: 300,
|
||||
height: 300,
|
||||
left: 0,
|
||||
top: 0,
|
||||
zoom: 1.5,
|
||||
transform: '',
|
||||
radius: 0,
|
||||
opacity: 1,
|
||||
parent: '-1',
|
||||
svgUrl: '',
|
||||
setting: [],
|
||||
record: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
minWidth: 10,
|
||||
minHeight: 10,
|
||||
dir: 'all',
|
||||
},
|
||||
},
|
||||
props: ['params', 'parent'],
|
||||
data() {
|
||||
return {
|
||||
position: 'absolute', // 'absolute'relative
|
||||
editBoxStyle: {
|
||||
transformOrigin: 'center',
|
||||
},
|
||||
editBoxs: {},
|
||||
editingKey: '',
|
||||
cropWidgetXY: {}, // 裁剪框移动作用
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dActiveElement', 'dZoom', 'dMouseXY']),
|
||||
tZoom() {
|
||||
return this.params.zoom
|
||||
},
|
||||
cropEdit() {
|
||||
return this.params.cropEdit
|
||||
},
|
||||
imgChange() {
|
||||
return this.params.imgUrl
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
async tZoom() {
|
||||
await this.$nextTick()
|
||||
this.updateRecord()
|
||||
},
|
||||
imgChange() {
|
||||
// TODO 更新所有图片
|
||||
this.svgImg.attr({
|
||||
'xlink:href': this.params.imgUrl,
|
||||
})
|
||||
},
|
||||
cropEdit(val) {
|
||||
// TODO 移动事件绑定
|
||||
if (val) {
|
||||
document.getElementById(this.params.uuid).addEventListener('mousedown', this.touchstart, false)
|
||||
} else {
|
||||
document.getElementById(this.params.uuid).removeEventListener('mousedown', this.touchstart, false)
|
||||
}
|
||||
},
|
||||
},
|
||||
updated() {
|
||||
this.updateRecord()
|
||||
},
|
||||
async mounted() {
|
||||
await this.$nextTick()
|
||||
await this.loadSvg()
|
||||
this.updateRecord()
|
||||
// document.getElementById(this.params.uuid).addEventListener('mousedown', this.touchstart, false)
|
||||
document.addEventListener('mouseup', this.touchend, false)
|
||||
this.params.transform && (this.$refs.widget.style.transform = this.params.transform)
|
||||
this.params.rotate && (this.$refs.widget.style.transform += `rotate(${this.params.rotate})`)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('mouseup', this.touchend, false)
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateWidgetData']),
|
||||
touchstart(e) {
|
||||
// TODO move start
|
||||
// const imgKey = e.target.getAttribute('img-key')
|
||||
// this.editingKey = imgKey
|
||||
// this.editBoxs[this.editingKey] = {
|
||||
// transformOrigin: 'center',
|
||||
// }
|
||||
// const editBox = this.$refs[this.params.uuid + '_ebox_' + imgKey]
|
||||
const editBox = this.$refs[this.params.uuid + '_ebox']
|
||||
this.cropWidgetXY = {
|
||||
x: Number(editBox.style.left.replace('px', '')) || 0,
|
||||
y: Number(editBox.style.top.replace('px', '')) || 0,
|
||||
}
|
||||
// 绑定鼠标移动事件
|
||||
document.addEventListener('mousemove', this.handlemousemove, true)
|
||||
},
|
||||
touchend() {
|
||||
// 取消鼠标移动事件
|
||||
document.removeEventListener('mousemove', this.handlemousemove, true)
|
||||
// document.removeEventListener('mouseup', () => {}, true)
|
||||
},
|
||||
handlemousemove(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
const { left, top } = this.move(e)
|
||||
// TODO
|
||||
this.editBoxStyle.left = left + 'px'
|
||||
this.editBoxStyle.top = top + 'px'
|
||||
// this.editBoxs[this.editingKey].left = left + 'px'
|
||||
// this.editBoxs[this.editingKey].top = top + 'px'
|
||||
const { width, height } = this.params
|
||||
const { width: vWidth, height: vHeight } = this.viewBox
|
||||
const params = {
|
||||
x: left / (width / vWidth) / this.params.zoom,
|
||||
y: top / (height / vHeight) / this.params.zoom,
|
||||
}
|
||||
// this.svgImg.attr(params)
|
||||
this.changeFinish('x', params.x)
|
||||
this.changeFinish('y', params.y)
|
||||
// console.log('-----', left / (width / vWidth) / this.params.zoom)
|
||||
},
|
||||
loadSvg() {
|
||||
// console.log(this.params)
|
||||
const _this = this
|
||||
const Snap = window.Snap
|
||||
return new Promise((resolve) => {
|
||||
Snap.load(
|
||||
this.params.svgUrl,
|
||||
function (svg) {
|
||||
let svg2 = Snap(svg.node)
|
||||
let img = svg2.select('image')
|
||||
_this.viewBox = svg2.node.viewBox.baseVal
|
||||
_this.svgImg = img
|
||||
// TODO 收集所有图片容器
|
||||
// _this.svgImgs = {}
|
||||
// svg2.selectAll('image').forEach((element) => {
|
||||
// element.attr({
|
||||
// width: '100%',
|
||||
// height: '100%',
|
||||
// transform: '',
|
||||
// 'xlink:href': _this.params.imgs ? _this.params.imgs[element.node.className.baseVal] : '',
|
||||
// })
|
||||
// _this.svgImgs[element.node.className.baseVal] = element
|
||||
// _this.editBoxs[element.node.className.baseVal] = {}
|
||||
// })
|
||||
// img.attr({
|
||||
// width: '100%',
|
||||
// height: '100%',
|
||||
// transform: '',
|
||||
// 'xlink:href': _this.params.imgUrl || '',
|
||||
// })
|
||||
const el = this || _this.$refs.widget
|
||||
el.appendChild(svg.node)
|
||||
resolve()
|
||||
},
|
||||
document.getElementById(this.params.uuid),
|
||||
)
|
||||
})
|
||||
},
|
||||
updateRecord() {
|
||||
if (this.dActiveElement.uuid === this.params.uuid) {
|
||||
let record = this.dActiveElement.record
|
||||
record.width = this.$refs.widget.offsetWidth
|
||||
record.height = this.$refs.widget.offsetHeight
|
||||
}
|
||||
this.updateZoom()
|
||||
},
|
||||
updateZoom() {
|
||||
// TODO
|
||||
this.editBoxStyle.transform = `scale(${this.params.zoom})`
|
||||
// this.editingKey && (this.editBoxs[this.editingKey].transform = `scale(${this.params.zoom})`)
|
||||
if (this.svgImg) {
|
||||
const { x, y } = this.params
|
||||
this.svgImg.attr({
|
||||
x: x || 0,
|
||||
y: y || 0,
|
||||
style: `transform-origin: center;transform: scale(${this.params.zoom})`,
|
||||
})
|
||||
// 根据图片位置设置回editBox的位置
|
||||
const { width, height } = this.params
|
||||
const { width: vWidth, height: vHeight } = this.viewBox
|
||||
const params = {
|
||||
left: x * (width / vWidth) * this.params.zoom,
|
||||
top: y * (height / vHeight) * this.params.zoom,
|
||||
}
|
||||
// TODO
|
||||
this.editBoxStyle.left = params.left + 'px'
|
||||
this.editBoxStyle.top = params.top + 'px'
|
||||
// if (this.editingKey) {
|
||||
// this.editBoxs[this.editingKey].left = params.left + 'px'
|
||||
// this.editBoxs[this.editingKey].top = params.top + 'px'
|
||||
// }
|
||||
}
|
||||
},
|
||||
changeFinish(key, value) {
|
||||
this.updateWidgetData({
|
||||
uuid: this.params.uuid,
|
||||
key: key,
|
||||
value: value,
|
||||
pushHistory: true,
|
||||
})
|
||||
},
|
||||
move(payload) {
|
||||
// const widgetXY = { x: this.cropWidgetXY.x / this.dZoom, y: this.cropWidgetXY.y / this.dZoom }
|
||||
const widgetXY = { x: this.cropWidgetXY.x, y: this.cropWidgetXY.y }
|
||||
const dx = Number(payload.pageX) - this.dMouseXY.x
|
||||
const dy = Number(payload.pageY) - this.dMouseXY.y
|
||||
let left = Number(widgetXY.x) + Math.floor((dx * 100) / this.dZoom)
|
||||
let top = Number(widgetXY.y) + Math.floor((dy * 100) / this.dZoom)
|
||||
return { left, top }
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.svg__edit__wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.w-svg {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
.edit__model {
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="color__select">
|
||||
<div class="color__select" :style="{ width }">
|
||||
<p v-if="label" class="input-label">
|
||||
{{ label }}
|
||||
</p>
|
||||
<div class="content">
|
||||
<el-popover placement="left-end" width="auto">
|
||||
<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="['纯色']" @blur="inputBlur" @nativePick="dropColor" />
|
||||
<color-picker v-model:value="innerColor" :modes="modes" @change="colorChange" @nativePick="dropColor" />
|
||||
<template #reference>
|
||||
<div class="color__bar" :style="{ background: innerColor }"></div>
|
||||
</template>
|
||||
@ -35,9 +35,15 @@ export default defineComponent({
|
||||
modelValue: {
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
default: '100%',
|
||||
},
|
||||
modes: {
|
||||
default: () => ['纯色'],
|
||||
},
|
||||
},
|
||||
emits: ['finish', 'update:modelValue'],
|
||||
setup(props, context) {
|
||||
emits: ['finish', 'update:modelValue', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const store = useStore()
|
||||
const state: any = reactive({
|
||||
innerColor: '#ffffffff',
|
||||
@ -50,7 +56,9 @@ export default defineComponent({
|
||||
// })
|
||||
|
||||
onMounted(() => {
|
||||
state.innerColor = props.modelValue + (props.modelValue.length === 7 ? 'ff' : '')
|
||||
if (props.modelValue) {
|
||||
state.innerColor = props.modelValue + (props.modelValue.length === 7 ? 'ff' : '')
|
||||
}
|
||||
})
|
||||
const dropColor = async (e: any) => {
|
||||
console.log('取色: ', e)
|
||||
@ -75,13 +83,13 @@ export default defineComponent({
|
||||
)
|
||||
|
||||
const updateValue = (value: any) => {
|
||||
context.emit('update:modelValue', value)
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
const activeChange = (value: any) => {
|
||||
updateValue(value)
|
||||
}
|
||||
const onChange = () => {
|
||||
context.emit('finish', state.innerColor)
|
||||
emit('finish', state.innerColor)
|
||||
}
|
||||
// const addHistory = debounce(300, false, async (value) => {
|
||||
// store.dispatch('pushColorToHistory', value)
|
||||
@ -94,6 +102,18 @@ export default defineComponent({
|
||||
state.innerColor = color
|
||||
}
|
||||
|
||||
const enter = () => {
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择框
|
||||
}
|
||||
|
||||
const hide = () => {
|
||||
store.commit('setShowMoveable', true) // 恢复上一次的选择框
|
||||
}
|
||||
|
||||
const colorChange = (e) => {
|
||||
emit('change', e)
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
// dColorHistory,
|
||||
@ -101,6 +121,9 @@ export default defineComponent({
|
||||
onChange,
|
||||
dropColor,
|
||||
inputBlur,
|
||||
enter,
|
||||
hide,
|
||||
colorChange,
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -115,15 +138,16 @@ export default defineComponent({
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
// border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: inset 0 0 0 1px rgb(0 0 0 / 6%);
|
||||
cursor: pointer;
|
||||
}
|
||||
&__bar:hover {
|
||||
border: 1px solid#bdbfc5;
|
||||
// border: 1px solid #bdbfc5;
|
||||
box-shadow: inset 0 0 0 1px #bdbfc5;
|
||||
}
|
||||
}
|
||||
.color__select {
|
||||
width: 100%;
|
||||
.content {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
|
@ -11,7 +11,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="number-input2">
|
||||
<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)" />
|
||||
</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>
|
||||
@ -46,6 +50,9 @@ export default {
|
||||
default: 1,
|
||||
},
|
||||
maxValue: {},
|
||||
minValue: {},
|
||||
type: {},
|
||||
prepend: {},
|
||||
},
|
||||
emits: ['finish', 'update:modelValue'],
|
||||
data() {
|
||||
@ -73,24 +80,25 @@ export default {
|
||||
this.updateValue(Number(this.modelValue).toFixed(2))
|
||||
}, 10)
|
||||
}
|
||||
// 超过阈值时
|
||||
// 限定数字范围
|
||||
if (this.maxValue && this.modelValue > this.maxValue) {
|
||||
setTimeout(() => {
|
||||
this.updateValue(Number(this.maxValue))
|
||||
}, 10)
|
||||
} else if (typeof this.minValue === 'number' && this.modelValue < this.minValue) {
|
||||
setTimeout(() => {
|
||||
this.updateValue(Number(this.minValue))
|
||||
}, 10)
|
||||
}
|
||||
},
|
||||
updateValue(value) {
|
||||
this.$emit('update:modelValue', 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
|
||||
if (value < 0) {
|
||||
value = 0
|
||||
}
|
||||
this.updateValue(value)
|
||||
},
|
||||
opNumber(e) {
|
||||
@ -108,9 +116,11 @@ export default {
|
||||
let value = String(this.modelValue)
|
||||
let len = value.length
|
||||
let newValue = ''
|
||||
for (let i = 0; i < len; ++i) {
|
||||
let isNegative = value[0] === '-'
|
||||
// 判断是否连续字符,否则置为0
|
||||
for (let i = isNegative ? 1 : 0; i < len; ++i) {
|
||||
let c = value[i]
|
||||
if (c >= '0' && c <= '9') {
|
||||
if (c == '.' || (c >= '0' && c <= '9')) {
|
||||
newValue += c
|
||||
} else {
|
||||
break
|
||||
@ -119,13 +129,20 @@ export default {
|
||||
if (newValue === '') {
|
||||
newValue = '0'
|
||||
}
|
||||
this.updateValue(parseInt(newValue, 10))
|
||||
if (isNegative) {
|
||||
newValue = '-' + (newValue === '0' ? '' : newValue)
|
||||
}
|
||||
this.updateValue(newValue)
|
||||
// this.updateValue(parseInt(newValue, 10))
|
||||
},
|
||||
focusInput() {
|
||||
this.inputBorder = true
|
||||
this.tagText = this.modelValue
|
||||
},
|
||||
blurInput() {
|
||||
if (this.modelValue === '-') {
|
||||
this.updateValue(0)
|
||||
}
|
||||
this.inputBorder = false
|
||||
if (this.modelValue !== this.tagText) {
|
||||
this.$emit('finish', this.modelValue)
|
||||
@ -227,4 +244,20 @@ export default {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.small-input {
|
||||
font-size: 12px;
|
||||
width: 100%;
|
||||
padding: 4px 7px;
|
||||
box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
|
||||
border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
|
||||
cursor: text;
|
||||
}
|
||||
.prepend {
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
margin: -14px 0 0 3px;
|
||||
transform: scale(0.85);
|
||||
transform-origin: left;
|
||||
color: #888888;
|
||||
}
|
||||
</style>
|
||||
|
@ -2,13 +2,13 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-09 11:44:29
|
||||
* @Description: 数值滑块组件
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-06-29 15:41:38
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-16 09:46:23
|
||||
-->
|
||||
<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 :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"> </el-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -32,11 +32,14 @@ export default {
|
||||
default: 0,
|
||||
},
|
||||
maxValue: {
|
||||
default: 100,
|
||||
default: 500,
|
||||
},
|
||||
step: {
|
||||
default: 1,
|
||||
},
|
||||
showInput: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'finish'],
|
||||
data() {
|
||||
|
@ -1,29 +1,16 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-02 19:10:06
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-04-07 14:29:42
|
||||
* @Description: 选项选择(未拆分字体选择器)
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-20 18:21:55
|
||||
-->
|
||||
<template>
|
||||
<div id="value-select" ref="select" :style="{ width: inputWidth }">
|
||||
<div ref="select" class="value-select" :style="{ width: inputWidth }">
|
||||
<p v-if="label" class="input-label">
|
||||
{{ label }}
|
||||
</p>
|
||||
<!-- <div class="input-wrap" :class="{ active: inputBorder }" :style="{ width: inputWidth }">
|
||||
<input class="real-input" :style="{ textAlign: textAlign }" :class="{ disable: !disable }" :readonly="readonly ? 'readonly' : ''" type="text" :value="showValue" @input="innerValue = $event.target.value.replace(RegExp(suffix), '')" @focus="inputBorder = true" @blur="inputBorder = false" />
|
||||
<div class="op-btn">
|
||||
<div class="down" @click="inputBorder = !inputBorder"></div>
|
||||
</div>
|
||||
</div>
|
||||
<el-popover ref="list" v-model="inputBorder" placement="bottom-start" trigger="click" :width="width" popper-class="value-select-list">
|
||||
<ul v-if="data" class="list-ul">
|
||||
<li v-for="listItem in data" :key="typeof listItem === 'object' ? listItem.name : listItem" :class="{ active: listItem == innerValue }" @click="selectItem(listItem)">
|
||||
{{ (typeof listItem === 'object' ? listItem.name : listItem) + suffix }}
|
||||
</li>
|
||||
</ul>
|
||||
</el-popover> -->
|
||||
<el-popover placement="bottom-end" width="auto">
|
||||
<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)">
|
||||
@ -32,16 +19,18 @@
|
||||
</li>
|
||||
</ul>
|
||||
<!-- tab分类列表 -->
|
||||
<el-tabs v-else v-model="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" />
|
||||
<span v-else>{{ (typeof listItem === 'object' ? listItem.alias : listItem) + suffix }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-else class="tabs-wrap">
|
||||
<el-tabs v-model="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" />
|
||||
<span v-else :style="{ fontFamily: `'${listItem.value}'` }">{{ (typeof listItem === 'object' ? listItem.alias : listItem) + suffix }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div :class="['input-wrap', { active: inputBorder }]" :style="{ width: inputWidth }">
|
||||
<!-- <img v-if="innerPreview" class="preview" :src="innerPreview" /> -->
|
||||
@ -190,7 +179,7 @@ export default {
|
||||
@color0: #e1e1e1; // Appears 2 times
|
||||
@color1: #d1d1d1; // Appears 2 times
|
||||
|
||||
#value-select {
|
||||
.value-select {
|
||||
// height: 60px;
|
||||
line-height: 1.15;
|
||||
width: 80px;
|
||||
@ -298,4 +287,8 @@ export default {
|
||||
height: 1.6em;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-wrap {
|
||||
width: 210px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<div id="page-style">
|
||||
<el-collapse v-model="activeNames">
|
||||
<div v-if="showBgLib" style="width: 256px;height: 100%;">
|
||||
<span class="header-back" @click="showBgLib = false">
|
||||
<i class="iconfont icon-right"></i> 选择背景
|
||||
</span>
|
||||
<bg-img-list-wrap style="padding-top: 2rem;" model="stylePanel" />
|
||||
</div>
|
||||
<el-collapse v-else v-model="activeNames">
|
||||
<el-collapse-item title="画布尺寸" name="1">
|
||||
<div class="position-size">
|
||||
<number-input v-model="innerElement.width" label="宽" :maxValue="5000" @finish="(value) => finish('width', value)" />
|
||||
@ -8,23 +14,22 @@
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="背景设置" name="2">
|
||||
<el-button style="width: 100%; margin: 0 0 1rem 0;" type="primary" link @click="showBgLib = true">在背景库中选择</el-button>
|
||||
<Tabs :value="mode" @update:value="onChangeMode">
|
||||
<TabPanel v-for="label in modes" :key="label" :label="label"></TabPanel>
|
||||
</Tabs>
|
||||
<color-select v-show="mode === '颜色'" v-model="innerElement.backgroundColor" @finish="(value) => finish('backgroundColor', value)" />
|
||||
<color-select v-show="mode === '颜色'" v-model="innerElement.backgroundColor" :modes="['纯色']" @change="colorChange" @finish="(value) => finish('backgroundColor', value)" />
|
||||
<!-- <bg-img-select :img="innerElement.backgroundImage"/> -->
|
||||
<div v-if="mode === '图片' && innerElement.backgroundImage" style="margin-top: 2rem">
|
||||
<el-image style="max-height: 428px" :src="innerElement.backgroundImage" fit="contain"></el-image>
|
||||
<el-button style="width: 100%; margin-top: 0.7rem" size="small" @click="deleteBg">删除</el-button>
|
||||
<el-button class="btn-wrap" size="small" @click="deleteBg">删除</el-button>
|
||||
</div>
|
||||
<uploader v-show="mode === '图片'" style="width: 100%; margin-top: 0.7rem" @done="uploadImgDone">
|
||||
<el-button style="width: 100%" plain>{{ innerElement.backgroundImage ? '替换背景' : '上传背景' }}图</el-button>
|
||||
<uploader v-show="mode === '图片'" class="btn-wrap" @done="uploadImgDone">
|
||||
<el-button style="width: 100%" plain>{{ innerElement.backgroundImage ? '更换背景' : '上传背景' }}图</el-button>
|
||||
</uploader>
|
||||
<el-button v-show="mode === '图片' && innerElement.backgroundImage" style="width: 100%; margin-top: 0.7rem" size="small" @click="downloadBG">{{ downP ? downP + ' %' : '下载背景图' }}</el-button>
|
||||
<el-button v-show="mode === '图片' && innerElement.backgroundImage" class="btn-wrap" size="small" @click="downloadBG">{{ downP ? downP + ' %' : '下载背景图' }}</el-button>
|
||||
|
||||
</el-collapse-item>
|
||||
<!-- <el-collapse-item title="其他设置" name="3">
|
||||
<el-input v-model="innerElement.name" label="名称" @finish="(value) => finish('name', value)" />
|
||||
</el-collapse-item> -->
|
||||
</el-collapse>
|
||||
</div>
|
||||
</template>
|
||||
@ -52,9 +57,9 @@ export default {
|
||||
tag: false,
|
||||
ingoreKeys: ['backgroundColor', 'name', 'width', 'height'],
|
||||
downP: 0,
|
||||
// canvasRunning: false,
|
||||
mode: '颜色',
|
||||
modes: ['颜色', '图片'],
|
||||
showBgLib: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -79,6 +84,14 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updatePageData']),
|
||||
colorChange(e) {
|
||||
if (e.mode === '渐变') {
|
||||
// setTimeout(() => {
|
||||
// console.log(1, e)
|
||||
// this.finish('backgroundImage', e.color)
|
||||
// }, 1000)
|
||||
}
|
||||
},
|
||||
onChangeMode(value) {
|
||||
this.mode = value
|
||||
if (value === '颜色') {
|
||||
@ -181,4 +194,25 @@ export default {
|
||||
.select {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.btn-wrap {
|
||||
width: 100%; margin-top: 0.7rem;
|
||||
}
|
||||
.header {
|
||||
&-back {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
height: 2.9rem;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
background: #ffffff;
|
||||
width: 259px;
|
||||
.icon-right {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-02 09:41:41
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-03-15 10:22:14
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-16 00:30:03
|
||||
-->
|
||||
<template>
|
||||
<div
|
||||
@ -144,7 +144,6 @@ export default {
|
||||
// const opacity = this.$refs.widget.style.opacity
|
||||
// this.$refs.widget.style.opacity = 1
|
||||
setTimeout(() => {
|
||||
console.log(this.temp)
|
||||
if (!this.temp) {
|
||||
return
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-09 11:41:53
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-12 12:50:39
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-09 00:59:44
|
||||
-->
|
||||
<template>
|
||||
<div id="w-image-style">
|
||||
@ -21,12 +21,12 @@
|
||||
<el-button style="width: 100%; margin-bottom: 12px" plain @click="openPicBox">替换图片</el-button>
|
||||
<div class="options">
|
||||
<el-button v-if="innerElement.cropEdit" plain type="primary" @click="imgCrop(false)">完成</el-button>
|
||||
<el-button v-else plain type="primary" @click="imgCrop(true)"><i class="icon sd-caijian" />裁剪</el-button>
|
||||
<el-button @click="openImageCutout" plain>抠图</el-button>
|
||||
<el-button v-else plain type="primary" @click="imgCrop(true)"><i class="icon sd-caijian" /> 裁剪</el-button>
|
||||
<el-button plain @click="openImageCutout"><i class="icon sd-AIkoutu" /> 抠图</el-button>
|
||||
<!-- <uploader class="options__upload" @done="uploadImgDone">
|
||||
<el-button size="small" plain>替换图片</el-button>
|
||||
</uploader> -->
|
||||
<el-button size="small" disabled plain @click="openCropper">图片美化</el-button>
|
||||
<el-button size="small" disabled plain @click="openCropper">美化</el-button>
|
||||
</div>
|
||||
<!-- <container-wrap @change="changeContainer" />
|
||||
<br /> -->
|
||||
@ -52,7 +52,7 @@
|
||||
<i style="padding: 0 8px; cursor: pointer" class="icon sd-queren" @click="imgCrop(false)" />
|
||||
</inner-tool-bar>
|
||||
<picBox ref="picBox" @select="selectDone" />
|
||||
<imageCutout ref="imageCutout" />
|
||||
<imageCutout ref="imageCutout" @done="cutImageDone" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -241,8 +241,23 @@ export default {
|
||||
openPicBox() {
|
||||
this.$refs.picBox.open()
|
||||
},
|
||||
// 打开抠图
|
||||
openImageCutout() {
|
||||
this.$refs.imageCutout.open()
|
||||
fetch(this.innerElement.imgUrl)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const file = new File([blob], `image_${Math.random()}.jpg`, { type: 'image/jpeg' })
|
||||
this.$refs.imageCutout.open(file)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取图片失败:', error)
|
||||
})
|
||||
},
|
||||
// 完成抠图
|
||||
async cutImageDone(url) {
|
||||
setTimeout(() => {
|
||||
this.innerElement.imgUrl = url
|
||||
}, 300)
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -277,6 +292,9 @@ export default {
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
.icon {
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
}
|
||||
|
||||
.slide-wrap {
|
||||
|
26
src/components/modules/widgets/wText/getGradientOrImg.ts
Normal file
26
src/components/modules/widgets/wText/getGradientOrImg.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-11-29 11:00:41
|
||||
* @Description: 处理字体填充特效
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-29 11:01:50
|
||||
*/
|
||||
export default (effect: any) => {
|
||||
let result = ''
|
||||
switch (Number(effect.filling.type)) {
|
||||
case 2:
|
||||
{
|
||||
const { angle, stops } = effect.filling.gradient
|
||||
const gradients = stops.map((x: any) => `${x.color} ${Number(x.offset) * 100}%`)
|
||||
result = `linear-gradient(${angle}deg, ${gradients.toString()})`
|
||||
}
|
||||
break
|
||||
case 1:
|
||||
result = `url(${effect.filling.imageContent.image})`
|
||||
break
|
||||
default:
|
||||
result = effect.filling.color
|
||||
break
|
||||
}
|
||||
return result
|
||||
}
|
22
src/components/modules/widgets/wText/pageFontsFilter.ts
Normal file
22
src/components/modules/widgets/wText/pageFontsFilter.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-14 20:16:48
|
||||
* @Description: 找出页面中使用的字体
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-14 20:29:26
|
||||
*/
|
||||
import store from '@/store'
|
||||
import { toRaw } from 'vue'
|
||||
export default () => {
|
||||
const collector = new Set()
|
||||
const fonts: any = {}
|
||||
const { dWidgets: widgets } = store.getters
|
||||
for (let i = 0; i < widgets.length; i++) {
|
||||
const { type, fontClass } = widgets[i]
|
||||
if (type === 'w-text') {
|
||||
collector.add(fontClass.id)
|
||||
fonts[fontClass.id] = toRaw(fontClass)
|
||||
}
|
||||
}
|
||||
return Array.from(collector).map((id: any) => fonts[id])
|
||||
}
|
@ -33,11 +33,12 @@
|
||||
:key="efi + 'effect'"
|
||||
:style="{
|
||||
fontFamily: `'${params.fontClass.value}'`,
|
||||
color: ef.filling && ef.filling.type === 0 ? ef.filling.color : 'transparent',
|
||||
webkitTextStroke: ef.stroke ? `${ef.stroke.width}px ${ef.stroke.color}` : undefined,
|
||||
textShadow: ef.shadow ? `${ef.shadow.offsetX}px ${ef.shadow.offsetY}px ${ef.shadow.blur}px ${ef.shadow.color}` : undefined,
|
||||
backgroundImage: ef.filling ? (ef.filling.type === 0 ? undefined : getGradientOrImg(ef)) : undefined,
|
||||
webkitBackgroundClip: ef.filling ? (ef.filling.type === 0 ? undefined : 'text') : undefined,
|
||||
color: ef.filling && ef.filling.enable && ef.filling.type === 0 ? ef.filling.color : 'transparent',
|
||||
webkitTextStroke: ef.stroke && ef.stroke.enable ? `${ef.stroke.width}px ${ef.stroke.color}` : undefined,
|
||||
textShadow: ef.shadow && ef.shadow.enable ? `${ef.shadow.offsetX}px ${ef.shadow.offsetY}px ${ef.shadow.blur}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,
|
||||
transform: ef.offset && ef.offset.enable ? `translate(${ef.offset.x}px, ${ef.offset.y}px)` : undefined,
|
||||
}"
|
||||
class="edit-text effect-text"
|
||||
spellcheck="false"
|
||||
@ -54,6 +55,7 @@ const NAME = 'w-text'
|
||||
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { fontWithDraw } from '@/utils/widgets/loadFontRule'
|
||||
import getGradientOrImg from './getGradientOrImg.ts'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
@ -70,10 +72,10 @@ export default {
|
||||
fontSize: 24,
|
||||
zoom: 1,
|
||||
fontClass: {
|
||||
alias: '素材集市酷方体',
|
||||
id: 33925853,
|
||||
value: 'sucaijishikufangti',
|
||||
url: 'https://res.palxp.com/static/fonts/20200809-152508-8654.woff',
|
||||
alias: '站酷快乐体',
|
||||
id: 543,
|
||||
value: 'zcool-kuaile-regular',
|
||||
url: 'https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2',
|
||||
},
|
||||
fontFamily: 'SourceHanSansSC-Regular',
|
||||
fontWeight: 'normal',
|
||||
@ -120,10 +122,12 @@ export default {
|
||||
|
||||
if (font.url && !isDone) {
|
||||
if (font.id && this.isDraw) {
|
||||
// 如果为绘制模式,且开启了字体抽取,那么会跳过加载字体url的逻辑
|
||||
// 此前该功能在demo中存在换行bug,实际上是由于抽取字体时忽略了空格导致的
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.loading = !this.isDraw
|
||||
const loadFont = new window.FontFace(font.value, `url(${font.url})`)
|
||||
await loadFont.load()
|
||||
document.fonts.add(loadFont)
|
||||
@ -151,34 +155,13 @@ export default {
|
||||
async mounted() {
|
||||
this.updateRecord()
|
||||
// await this.$nextTick()
|
||||
this.styleEffect()
|
||||
this.params.transform && (this.$refs.widget.style.transform = this.params.transform)
|
||||
this.params.rotate && (this.$refs.widget.style.transform += `translate(0px, 0px) rotate(${this.params.rotate}) scale(1, 1)`)
|
||||
// this.$store.commit('updateRect')
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateWidgetData', 'pushHistory']),
|
||||
getGradientOrImg(ef) {
|
||||
return ef.filling.type === 2 ? `linear-gradient(${ef.filling.gradient.angle}deg, ${ef.filling.gradient.stops[0].color} ${Number(ef.filling.gradient.stops[0].offset) * 100}%, ${ef.filling.gradient.stops[1].color} ${Number(ef.filling.gradient.stops[1].offset) * 100}%)` : `url(${ef.filling.imageContent.image})`
|
||||
},
|
||||
styleEffect() {
|
||||
if (this.params.textEffects && this.params.textEffects.length > 0) {
|
||||
const style = this.params.textEffects[this.params.textEffects.length - 1]
|
||||
if (style.filling && style.filling.color && style.filling.type == 0) {
|
||||
this.params.color = style.filling.color
|
||||
}
|
||||
}
|
||||
// if (this.params.textEffects && this.params.textEffects.length <= 2 && this.params.textEffects[0]) {
|
||||
// // 填充
|
||||
// const grad = this.params.textEffects[0].type == 1 && this.params.textEffects[0].filling?.gradient
|
||||
// if (grad) {
|
||||
// this.$refs.widget.style['-webkit-background-clip'] = 'text' // background-clip: text;
|
||||
// // this.$refs.widget.style['-webkit-text-fill-color'] = 'transparent' // -webkit-text-fill-color: transparent;
|
||||
// // // this.$refs.widget.style.backgroundImage = `url('https://st-gdx.dancf.com/gaodingx/0/personal/my-tasks/20210827-183407-abd3.jpg?x-oss-process=image/resize,w_176,h_176,m_fill/interlace,1,image/format,webp')`
|
||||
// this.$refs.widget.style.backgroundImage = `linear-gradient(${grad.angle}deg, ${grad.stops[0].color} ${Number(grad.stops[0].offset) * 100}%, ${grad.stops[1].color} ${Number(grad.stops[1].offset) * 100}%)`
|
||||
// }
|
||||
// }
|
||||
},
|
||||
getGradientOrImg,
|
||||
updateRecord() {
|
||||
if (this.dActiveElement.uuid === this.params.uuid) {
|
||||
let record = this.dActiveElement.record
|
||||
@ -212,7 +195,6 @@ export default {
|
||||
pushHistory: false,
|
||||
})
|
||||
this.$store.commit('updateRect')
|
||||
// this.styleEffect()
|
||||
},
|
||||
writeDone(e) {
|
||||
this.editable = false
|
||||
|
@ -24,7 +24,7 @@
|
||||
</div> -->
|
||||
<!-- <el-collapse-item title="位置尺寸" name="1"> -->
|
||||
<div class="style-item slide-wrap">
|
||||
<number-slider v-model="innerElement.letterSpacing" style="font-size: 14px" label="字距" labelWidth="40px" :step="0.05" :minValue="-50" :maxValue="innerElement.fontSize" @finish="(value) => finish('letterSpacing', value)" />
|
||||
<number-slider v-model="innerElement.letterSpacing" style="font-size: 14px" label="字距" labelWidth="40px" :step="0.05" :minValue="-innerElement.fontSize" :maxValue="innerElement.fontSize * 2" @finish="(value) => finish('letterSpacing', value)" />
|
||||
<number-slider v-model="innerElement.lineHeight" style="font-size: 14px" label="行距" labelWidth="40px" :step="0.05" :minValue="0" :maxValue="2.5" @finish="(value) => finish('lineHeight', value)" />
|
||||
</div>
|
||||
<!-- </el-collapse-item> -->
|
||||
@ -34,7 +34,7 @@
|
||||
<!-- <color-select v-model="innerElement.backgroundColor" label="背景颜色" @finish="(value) => finish('backgroundColor', value)" /> -->
|
||||
</div>
|
||||
<div class="line-layout style-item">
|
||||
<effect-wrap v-model="innerElement.isEffect" :data="innerElement.textEffects" :degree="innerElement.degree" @select="testEffect" />
|
||||
<effect-wrap v-model="innerElement.textEffects" :data="innerElement" :degree="innerElement.degree" @select="testEffect" />
|
||||
</div>
|
||||
<icon-item-select class="style-item" :data="layerIconList" @finish="layerAction" />
|
||||
<icon-item-select class="style-item" :data="alignIconList" @finish="alignAction" />
|
||||
@ -51,8 +51,8 @@
|
||||
<script>
|
||||
// 文本组件样式
|
||||
const NAME = 'w-text-style'
|
||||
import api from '@/api'
|
||||
import _config from '@/config'
|
||||
// import api from '@/api'
|
||||
// import _config from '@/config'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { styleIconList1, styleIconList2, alignIconList } from '../../../../assets/data/TextIconsData'
|
||||
import layerIconList from '@/assets/data/LayerIconList'
|
||||
@ -64,6 +64,7 @@ import textInputArea from '../../settings/textInputArea.vue'
|
||||
import valueSelect from '../../settings/valueSelect.vue'
|
||||
import effectWrap from '../../settings/EffectSelect/TextWrap.vue'
|
||||
import { useFontStore } from '@/common/methods/fonts'
|
||||
import usePageFontsFilter from './pageFontsFilter.ts'
|
||||
|
||||
export default {
|
||||
name: NAME,
|
||||
@ -114,6 +115,7 @@ export default {
|
||||
methods: {
|
||||
...mapActions(['updateWidgetData', 'updateAlign', 'updateLayerIndex', 'pushHistory']),
|
||||
testEffect({ key, value, style }) {
|
||||
console.log('选择回调')
|
||||
const uuid = this.dActiveElement.uuid
|
||||
this.$store.commit('setWidgetStyle', { uuid, key, value })
|
||||
if (style) {
|
||||
@ -124,11 +126,13 @@ export default {
|
||||
// if (!this.isDraw) {
|
||||
// useFontStore().init()
|
||||
const localFonts = useFontStore.list
|
||||
const fontLists = { 中文: [], 英文: [] }
|
||||
const fontLists = { 当前页面: [], 中文: [], 英文: [] }
|
||||
for (const font of localFonts) {
|
||||
const { id, value, url, alias, preview, lang } = font
|
||||
lang === 'zh' ? fontLists['中文'].unshift({ id, value, url, alias, preview }) : fontLists['英文'].unshift({ id, value, url, alias, preview })
|
||||
const { id, oid, value, url, alias, preview, lang } = font
|
||||
const item = { id, oid, value, url, alias, preview }
|
||||
lang === 'zh' ? fontLists['中文'].unshift(item) : fontLists['英文'].unshift(item)
|
||||
}
|
||||
fontLists['当前页面'] = usePageFontsFilter()
|
||||
this.fontClassList = fontLists
|
||||
// }
|
||||
// const isDev = process.env.NODE_ENV === 'development'
|
||||
@ -180,6 +184,9 @@ export default {
|
||||
value: value,
|
||||
pushHistory: false,
|
||||
})
|
||||
setTimeout(() => {
|
||||
key === 'fontClass' && (this.fontClassList['当前页面'] = usePageFontsFilter())
|
||||
}, 300)
|
||||
},
|
||||
layerAction(item) {
|
||||
this.updateLayerIndex({
|
||||
|
@ -1,28 +1,22 @@
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-09-07 22:56:09
|
||||
* @Description: 配置文件
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-16 20:39:29
|
||||
*/
|
||||
// const prefix = import.meta.env
|
||||
const prefix = process.env
|
||||
|
||||
const isDev = prefix.NODE_ENV === 'development'
|
||||
import { version } from '../package.json'
|
||||
|
||||
export default {
|
||||
isDev,
|
||||
BASE_URL: isDev ? '/' : './',
|
||||
VERSION: '1.1.0',
|
||||
VERSION: version,
|
||||
APP_NAME: '迅排设计',
|
||||
COPYRIGHT: 'ShawnPhang - Palxp.cn',
|
||||
// API_URL: isDev ? 'http://localhost:9998' : '${API}',
|
||||
API_URL: 'https://palxp.cn:8887', // 服务端地址
|
||||
SCREEN_URL: isDev ? 'http://localhost:7001' : '#{SCREEN_URL}', // 截图服务地址
|
||||
IMG_URL: 'https://store.palxp.com/', // 七牛云资源地址
|
||||
KT_URL: 'https://res.palxp.cn:5001', // 抠图服务地址
|
||||
// ICONFONT_URL: '//at.alicdn.com/t/font_3223711_74mlzj4jdue.css',
|
||||
ICONFONT_URL: '//at.alicdn.com/t/font_2717063_ypy8vprc3b.css?display=swap',
|
||||
ICONFONT_EXTRA: '//at.alicdn.com/t/c/font_3228074_6qsac4kteu7.css?&display=swap',
|
||||
ICONFONT_EXTRA: '//at.alicdn.com/t/c/font_3228074_42xym3extur.css',
|
||||
QINIUYUN_PLUGIN: 'https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/qiniu-js/2.5.5/qiniu.min.js',
|
||||
supportSubFont: true, // 是否开启服务端字体压缩
|
||||
}
|
||||
|
8
src/env.d.ts
vendored
Normal file
8
src/env.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// / <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
@ -2,10 +2,11 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-03-09 16:29:54
|
||||
* @Description: 处理和ctrl建相关的操作
|
||||
* @LastEditors: ShawnPhang
|
||||
* @LastEditTime: 2022-03-25 16:12:27
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-09 09:49:54
|
||||
*/
|
||||
import store from '@/store'
|
||||
import handlePaste from './handlePaste'
|
||||
|
||||
export default function dealWithCtrl(e: any, _this: any) {
|
||||
switch (e.keyCode) {
|
||||
@ -63,6 +64,7 @@ function copy() {
|
||||
* 粘贴
|
||||
*/
|
||||
function paste() {
|
||||
handlePaste()
|
||||
if (store.getters.dCopyElement.length === 0) {
|
||||
return
|
||||
} else if (store.getters.dActiveElement.isContainer && checkGroupChild(store.getters.dActiveElement.uuid, 'editable')) {
|
60
src/mixins/methods/handlePaste.ts
Normal file
60
src/mixins/methods/handlePaste.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-10-09 09:47:40
|
||||
* @Description: 处理剪贴板
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-09 10:35:21
|
||||
*/
|
||||
|
||||
// window.addEventListener('paste', (e: any) => {
|
||||
// const clipdata = e.clipboardData || (window as any).clipboardData
|
||||
// console.log('主动粘贴', clipdata.getData('text/plain'))
|
||||
// })
|
||||
|
||||
import store from '@/store'
|
||||
import api from '@/api'
|
||||
import Qiniu from '@/common/methods/QiNiu'
|
||||
import _config from '@/config'
|
||||
import { getImage } from '@/common/methods/getImgDetail'
|
||||
import wImage from '@/components/modules/widgets/wImage/wImage.vue'
|
||||
import wText from '@/components/modules/widgets/wText/wText.vue'
|
||||
|
||||
export default () => {
|
||||
navigator.clipboard
|
||||
.read()
|
||||
.then(async (dataTransfer: any) => {
|
||||
for (let i = 0; i < dataTransfer.length; i++) {
|
||||
const item = dataTransfer[i]
|
||||
if (item.types.toString().indexOf('image') !== -1) {
|
||||
const imageBlob = await item.getType(item.types[0])
|
||||
const file = new File([imageBlob], 'screenshot.png', { type: 'image/png' })
|
||||
// 上传图片
|
||||
const qnOptions = { bucket: 'xp-design', prePath: 'user' }
|
||||
const result: any = await Qiniu.upload(file, qnOptions)
|
||||
const { width, height }: any = await getImage(file)
|
||||
const url = _config.IMG_URL + result.key
|
||||
await api.material.addMyPhoto({ width, height, url })
|
||||
// 添加图片到画布中
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
const setting = JSON.parse(JSON.stringify(wImage.setting))
|
||||
setting.width = width
|
||||
setting.height = height
|
||||
setting.imgUrl = url
|
||||
const { width: pW, height: pH } = store.getters.dPage
|
||||
setting.left = pW / 2 - width / 2
|
||||
setting.top = pH / 2 - height / 2
|
||||
store.dispatch('addWidget', setting)
|
||||
break
|
||||
} else if (item.types.toString().indexOf('text') !== -1) {
|
||||
store.commit('setShowMoveable', false) // 清理掉上一次的选择
|
||||
const setting = JSON.parse(JSON.stringify(wText.setting))
|
||||
setting.text = await navigator.clipboard.readText()
|
||||
store.dispatch('addWidget', setting)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('无法读取剪贴板内容:', error)
|
||||
})
|
||||
}
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-03-09 14:20:09
|
||||
* @Description: 处理常用操作
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-06-30 17:29:28
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-11-30 10:09:55
|
||||
*/
|
||||
import store from '@/store'
|
||||
|
||||
@ -24,12 +24,19 @@ export default function keyCodeOptions(e: any, params: any) {
|
||||
break
|
||||
case 46:
|
||||
case 8:
|
||||
if (store.getters.dActiveElement.isContainer) {
|
||||
if (checkGroupChild(store.getters.dActiveElement.uuid, 'editable')) {
|
||||
return
|
||||
{
|
||||
if (store.getters.dActiveElement.isContainer) {
|
||||
if (checkGroupChild(store.getters.dActiveElement.uuid, 'editable')) {
|
||||
return
|
||||
}
|
||||
}
|
||||
const { type, editable }: any = store.getters.dActiveElement
|
||||
|
||||
if (type === 'w-text') {
|
||||
// 不在编辑状态则执行删除
|
||||
!editable && store.getters.showMoveable && store.dispatch('deleteWidget')
|
||||
} else store.dispatch('deleteWidget')
|
||||
}
|
||||
!store.getters.dActiveElement.editable && store.dispatch('deleteWidget')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
* @Date: 2021-08-01 14:12:08
|
||||
* @Description: 快捷键,目前是mixin形式放入views/index.vue中
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-11 23:37:26
|
||||
* @LastEditTime: 2023-09-19 17:29:06
|
||||
*/
|
||||
import store from '@/store'
|
||||
const _this: any = {}
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-08-19 18:43:22
|
||||
* @Description: 前端路由
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-25 17:00:52
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-19 17:32:04
|
||||
*/
|
||||
export default [
|
||||
// {
|
@ -3,11 +3,10 @@
|
||||
* @Date: 2021-12-16 16:20:16
|
||||
* @Description:
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-15 12:47:57
|
||||
* @LastEditTime: 2023-09-28 17:42:25
|
||||
*/
|
||||
import mutations from './mutations'
|
||||
import actions from './actions'
|
||||
import { client } from '@gradio/client'
|
||||
import _config from '@/config'
|
||||
|
||||
const all = {
|
||||
@ -39,10 +38,6 @@ const all = {
|
||||
fonts: (state: Type.Object) => {
|
||||
return state.fonts
|
||||
},
|
||||
app: async (state: Type.Object) => {
|
||||
!state.app && (state.app = await client(_config.KT_URL))
|
||||
return state.app
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
...mutations,
|
||||
|
@ -623,8 +623,8 @@ export default {
|
||||
const group = JSON.parse(store.state.dGroupJson)
|
||||
group.uuid = nanoid()
|
||||
widgets.push(group)
|
||||
let left = store.state.dPage.width
|
||||
let top = store.state.dPage.height
|
||||
let left = Number(store.state.dPage.width)
|
||||
let top = Number(store.state.dPage.height)
|
||||
let right = 0
|
||||
let bottom = 0
|
||||
const sortWidgets = [] // 顺序取出元素
|
||||
@ -672,11 +672,10 @@ export default {
|
||||
// widgets.push(sortWidgets[i].widget)
|
||||
// }
|
||||
|
||||
group.left = left
|
||||
group.top = top
|
||||
group.width = right - left
|
||||
group.height = bottom - top
|
||||
|
||||
group.left = Number(left)
|
||||
group.top = Number(top)
|
||||
group.width = Number(right - left)
|
||||
group.height = Number(bottom - top)
|
||||
store.state.dActiveElement = group
|
||||
store.state.dSelectWidgets = []
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-07-13 02:48:38
|
||||
* @Description: 接口规则:只有正确code为200时返回result结果对象,错误返回整个结果对象
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-31 09:34:34
|
||||
* @Description: 本地测试项目请勿修改此文件
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2024-01-11 17:36:33
|
||||
*/
|
||||
import axios from 'axios'
|
||||
import store from '@/store'
|
||||
import app_config from '@/config'
|
||||
|
||||
axios.defaults.timeout = 30000
|
||||
// axios.defaults.headers.Authorization = 'Bearer ';
|
||||
axios.defaults.headers.authorization = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTAwMDEsImV4cCI6MTc4ODU3NDc1MDU4NX0.L_t6DFD48Dm6rUPfgIgOWJkz18En1m_-hhMHcpbxliY';
|
||||
// const version = app_config.VERSION;
|
||||
const baseUrl = app_config.API_URL
|
||||
|
||||
@ -48,14 +48,11 @@ axios.interceptors.response.use(
|
||||
(res: Type.Object) => {
|
||||
// store.dispatch('hideLoading');
|
||||
|
||||
// if (res.status !== 200) {
|
||||
// // return falseInfo
|
||||
// }
|
||||
// 接口规则:只有正确code为200时返回result结果对象,错误返回整个结果对象
|
||||
|
||||
if (!res.data) {
|
||||
return Promise.reject(res)
|
||||
}
|
||||
// console.log(res.headers.authorization);
|
||||
if (res.data.code === 401) {
|
||||
console.log('登录失效')
|
||||
store.commit('changeOnline', false)
|
||||
@ -71,10 +68,6 @@ axios.interceptors.response.use(
|
||||
},
|
||||
(error) => {
|
||||
// if (error.response.status === 401) {
|
||||
// setTimeout(() => {
|
||||
// window.localStorage.clear()
|
||||
// // window.location.href = app_config.login + "?" + "redirect=" + window.location.href;
|
||||
// }, 1000)
|
||||
// }
|
||||
store.dispatch('hideLoading')
|
||||
return Promise.reject(error)
|
||||
@ -89,21 +82,19 @@ const fetch = (url: string, params: Type.Object, type: string | undefined = 'get
|
||||
// store.commit('loading', '加载中..');
|
||||
}
|
||||
|
||||
const objtest: Type.Object = {}
|
||||
// const { value } = JSON.parse(localStorage.getItem('pro__Access-Token') || '{}')
|
||||
// objtest.Authorization = `Bearer ${value}`
|
||||
const token = localStorage.getItem('xp_token')
|
||||
const headerObject: Type.Object = { }
|
||||
token && (headerObject.authorization = token)
|
||||
|
||||
if (type === 'get') {
|
||||
return axios.get(url, {
|
||||
// headers: {
|
||||
// // Authorization: String(localStorage.getItem('token')),
|
||||
// },
|
||||
headers: Object.assign(objtest, exheaders),
|
||||
headers: Object.assign(headerObject, exheaders),
|
||||
params,
|
||||
...extra,
|
||||
})
|
||||
} else {
|
||||
return (axios as Type.Object)[type](url, params, {
|
||||
headers: Object.assign(objtest, exheaders),
|
||||
headers: Object.assign(headerObject, exheaders),
|
||||
...extra,
|
||||
})
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-03-06 13:53:30
|
||||
* @Description: 获取图片在鼠标焦点的颜色
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-17 14:24:39
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-19 17:32:40
|
||||
*/
|
||||
export default class PointImg {
|
||||
private canvas: any
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2021-12-24 15:13:58
|
||||
* @Description: 资源加载
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-07-17 14:25:53
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-19 17:19:07
|
||||
*/
|
||||
export default class PreLoad {
|
||||
private i: number
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
// ElContainer,
|
||||
// ElDatePicker,
|
||||
ElDialog,
|
||||
// ElDivider,
|
||||
ElDivider,
|
||||
// ElDrawer,
|
||||
// ElDropdown,
|
||||
// ElDropdownItem,
|
||||
@ -115,7 +115,7 @@ const components = [
|
||||
// ElContainer,
|
||||
// ElDatePicker,
|
||||
ElDialog,
|
||||
// ElDivider,
|
||||
ElDivider,
|
||||
// ElDrawer,
|
||||
// ElDropdown,
|
||||
// ElDropdownItem,
|
||||
|
@ -1,29 +1,34 @@
|
||||
/*
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-08-23 17:37:16
|
||||
* @Description: 提取字体子集,如服务端不支持请关闭该功能,以保证页面能加载字体
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-08-23 17:48:34
|
||||
* @Description: 提取字体子集
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-10-14 18:31:29
|
||||
*/
|
||||
/**
|
||||
* 只有ttf/otf这种原始字体支持提取,如果服务端不支持该功能请设置false,以保证页面能加载字体。
|
||||
*/
|
||||
import _config from '@/config'
|
||||
export const fontWithDraw = _config.supportSubFont // true 开启,false 关闭
|
||||
|
||||
import api from '@/api'
|
||||
import { blob2Base64, generateFontStyle } from '@/common/methods/fonts/utils'
|
||||
|
||||
export const fontWithDraw = true // true开启,false关闭
|
||||
|
||||
export const font2style = async (fontContent: any, fontData: any = []) => {
|
||||
return new Promise((resolve: Function) => {
|
||||
Promise.all(
|
||||
// 提取字体子集。只有ttf/otf这种原始字体支持提取,如果服务端不支持则关闭该功能,以保证页面能加载字体。
|
||||
Object.keys(fontContent).map(async (key) => {
|
||||
const font = fontData.find((font: any) => font.value === key) as any
|
||||
if (font.id) {
|
||||
const extra = font.oid ? {} : { responseType: 'blob' }
|
||||
const params = {
|
||||
font_id: font.oid,
|
||||
id: font.id,
|
||||
content: shortText(fontContent[key]),
|
||||
}
|
||||
try {
|
||||
const base64 = await api.material.getFontSub({
|
||||
font_id: font.id,
|
||||
url: font.url,
|
||||
content: 'Aa' + fontContent[key],
|
||||
})
|
||||
fontContent[key] = base64
|
||||
const result = await api.material.getFontSub(params, extra)
|
||||
fontContent[key] = font.oid ? result : await blob2Base64(result)
|
||||
} catch (e) {
|
||||
console.log('字体获取失败', e)
|
||||
}
|
||||
@ -37,3 +42,9 @@ export const font2style = async (fontContent: any, fontData: any = []) => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function shortText(text: string) {
|
||||
// 文字去重
|
||||
const textArr = Array.from(new Set(text.split('')))
|
||||
return textArr.join('')
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs } from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import api from '@/api'
|
||||
import wGroup from '@/components/modules/widgets/wGroup/wGroup.vue'
|
||||
import Preload from '@/utils/plugins/preload'
|
||||
@ -33,6 +33,9 @@ export default defineComponent({
|
||||
...toRefs(state),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['dPage']),
|
||||
},
|
||||
mounted() {
|
||||
this.initGroupJson(JSON.stringify(wGroup.setting))
|
||||
this.$nextTick(() => {
|
||||
@ -40,16 +43,25 @@ export default defineComponent({
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['initGroupJson', 'setTemplate']),
|
||||
...mapActions(['initGroupJson', 'setTemplate', 'addGroup']),
|
||||
async load() {
|
||||
let loadFlag = false
|
||||
const { id, tempid } = this.$route.query
|
||||
const { id, tempid, tempType: type } = this.$route.query
|
||||
if (id || tempid) {
|
||||
const { data } = await api.home[id ? 'getWorks' : 'getTempDetail']({ id: id || tempid })
|
||||
const { data, width, height } = await api.home[id ? 'getWorks' : 'getTempDetail']({ id: id || tempid, type })
|
||||
const content = JSON.parse(data)
|
||||
const widgets = type == 1 ? content : content.widgets
|
||||
|
||||
if (type == 1) {
|
||||
this.dPage.width = width
|
||||
this.dPage.height = height
|
||||
this.dPage.backgroundColor = '#ffffff00'
|
||||
this.addGroup(content)
|
||||
} else {
|
||||
this.$store.commit('setDPage', content.page)
|
||||
id ? this.$store.commit('setDWidgets', widgets) : this.setTemplate(widgets)
|
||||
}
|
||||
|
||||
this.$store.commit('setDPage', content.page)
|
||||
id ? this.$store.commit('setDWidgets', content.widgets) : this.setTemplate(content.widgets)
|
||||
await this.$nextTick()
|
||||
|
||||
const imgsData: any = []
|
||||
@ -57,7 +69,7 @@ export default defineComponent({
|
||||
const fontLoaders: any = []
|
||||
const fontContent: any = {}
|
||||
let fontData: any = []
|
||||
content.widgets.forEach((item: any) => {
|
||||
widgets.forEach((item: any) => {
|
||||
if (item.fontClass && item.fontClass.value) {
|
||||
const loader = new FontFaceObserver(item.fontClass.value)
|
||||
fontData.push(item.fontClass)
|
||||
@ -71,29 +83,25 @@ export default defineComponent({
|
||||
}
|
||||
// 收集图片元素、svg元素
|
||||
try {
|
||||
if (item.imgUrl && !item.isNinePatch) {
|
||||
if (item.svgUrl && item.type === 'w-svg') {
|
||||
const cNodes: any = (window as any).document.getElementById(item.uuid).childNodes
|
||||
svgsData.push(cNodes)
|
||||
} else if (item.imgUrl && !item.isNinePatch) {
|
||||
const cNodes: any = (window as any).document.getElementById(item.uuid).childNodes
|
||||
for (const el of cNodes) {
|
||||
if (el.className && el.className.includes('img__box')) {
|
||||
imgsData.push(el.firstChild)
|
||||
}
|
||||
}
|
||||
} else if (item.svgUrl) {
|
||||
const cNodes: any = (window as any).document.getElementById(item.uuid).childNodes
|
||||
svgsData.push(cNodes)
|
||||
}
|
||||
} catch (e) {}
|
||||
})
|
||||
// TODO优化: 背景图无法检测是否加载完毕,考虑应该将设置背景作为独立事件
|
||||
if (content.page.backgroundImage) {
|
||||
if (content.page?.backgroundImage) {
|
||||
const preloadBg = new Preload([content.page.backgroundImage])
|
||||
await preloadBg.imgs()
|
||||
}
|
||||
try {
|
||||
const { list } = await api.material.getFonts({ ids: fontData.map((x: any) => x.id) })
|
||||
fontData.forEach((item: any) => {
|
||||
item.url = list.find((x: any) => x.oid == item.id)?.ttf
|
||||
})
|
||||
fontWithDraw && (await font2style(fontContent, fontData))
|
||||
// console.log('1. base64 yes')
|
||||
const preload = new Preload(imgsData)
|
||||
@ -138,3 +146,9 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.layer-hover {
|
||||
outline: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,3 +1,10 @@
|
||||
<!--
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2023-09-18 17:34:44
|
||||
* @Description:
|
||||
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||
* @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">
|
||||
@ -18,10 +25,10 @@
|
||||
<div class="page-design-index-wrap">
|
||||
<widget-panel></widget-panel>
|
||||
<design-board class="page-design-wrap" pageDesignCanvasId="page-design-canvas">
|
||||
<!-- 用于挡住画布溢出部分,因为使用overflow有bug. PS:如shadow没有透明度则可以完全遮挡元素 -->
|
||||
<!-- 用于挡住画布溢出部分,因为使用overflow有bug -->
|
||||
<div class="shelter" :style="{ width: Math.floor((dPage.width * dZoom) / 100) + 'px', height: Math.floor((dPage.height * dZoom) / 100) + 'px' }"></div>
|
||||
<!-- 提供一个背景图层以免遮挡穿帮 -->
|
||||
<div class="shelter-bg" :style="{ width: Math.floor((dPage.width * dZoom) / 100) + 'px', height: Math.floor((dPage.height * dZoom) / 100) + 'px' }"></div>
|
||||
<!-- 提供一个背景图层 -->
|
||||
<div class="shelter-bg transparent-bg" :style="{ width: Math.floor((dPage.width * dZoom) / 100) + 'px', height: Math.floor((dPage.height * dZoom) / 100) + 'px' }"></div>
|
||||
</design-board>
|
||||
<style-panel></style-panel>
|
||||
</div>
|
||||
@ -34,7 +41,13 @@
|
||||
<!-- 旋转缩放组件 -->
|
||||
<Moveable />
|
||||
<!-- 遮罩百分比进度条 -->
|
||||
<ProgressLoading :percent="downloadPercent" :text="downloadText" cancelText="取消" @cancel="downloadCancel" @done="downloadPercent = 0" />
|
||||
<ProgressLoading
|
||||
:percent="downloadPercent"
|
||||
:text="downloadText"
|
||||
cancelText="取消"
|
||||
@cancel="downloadCancel"
|
||||
@done="downloadPercent = 0"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -69,7 +82,7 @@ export default defineComponent({
|
||||
zoomControl,
|
||||
lineGuides,
|
||||
},
|
||||
mixins: [shortcuts],
|
||||
// mixins: [shortcuts],
|
||||
setup() {
|
||||
!_config.isDev && window.addEventListener('beforeunload', beforeUnload)
|
||||
|
||||
@ -129,6 +142,7 @@ export default defineComponent({
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['selectWidget', 'initGroupJson', 'handleHistory']),
|
||||
...shortcuts.methods,
|
||||
changeLineGuides() {
|
||||
this.showLineGuides = !this.showLineGuides
|
||||
},
|
||||
@ -137,8 +151,9 @@ export default defineComponent({
|
||||
this.isContinue = false
|
||||
},
|
||||
loadData() {
|
||||
const { id, tempid } = this.$route.query
|
||||
;(this.$refs as any).options.load(id, tempid, async () => {
|
||||
// 初始化加载页面
|
||||
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
|
||||
@ -160,7 +175,7 @@ export default defineComponent({
|
||||
const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
|
||||
this.style.left = `-${scrollLeft}px`
|
||||
},
|
||||
clickListener(e) {
|
||||
clickListener(e: Event) {
|
||||
console.log('click listener', e)
|
||||
},
|
||||
optionsChange({ downloadPercent, downloadText }: any) {
|
||||
|
@ -3,7 +3,7 @@
|
||||
* @Date: 2022-01-10 14:57:53
|
||||
* @Description: Psd文件解析
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-09-15 12:57:02
|
||||
* @LastEditTime: 2023-10-13 00:12:11
|
||||
-->
|
||||
<template>
|
||||
<div id="page-design-index" ref="pageDesignIndex">
|
||||
@ -56,7 +56,7 @@ import designBoard from '@/components/modules/layout/designBoard.vue'
|
||||
import zoomControl from '@/components/modules/layout/zoomControl.vue'
|
||||
import HeaderOptions from './components/UploadTemplate.vue'
|
||||
import ProgressLoading from '@/components/common/ProgressLoading/index.vue'
|
||||
import MyWorker from '@/utils/plugins/webWorker'
|
||||
// import MyWorker from '@/utils/plugins/webWorker'
|
||||
import { processPSD2Page } from '@/utils/plugins/psd'
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -2,8 +2,8 @@
|
||||
* @Author: ShawnPhang
|
||||
* @Date: 2022-01-12 11:26:53
|
||||
* @Description: 顶部操作按钮组
|
||||
* @LastEditors: ShawnPhang <site: book.palxp.com>
|
||||
* @LastEditTime: 2023-08-23 17:33:12
|
||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
||||
* @LastEditTime: 2023-12-11 12:40:59
|
||||
-->
|
||||
<template>
|
||||
<div class="top-title"><el-input v-model="title" placeholder="未命名的设计" class="input-wrap" /></div>
|
||||
@ -36,6 +36,8 @@ import SaveImage from '@/components/business/save-download/CreateCover.vue'
|
||||
import { useFontStore } from '@/common/methods/fonts'
|
||||
import copyRight from './CopyRight.vue'
|
||||
import _config from '@/config'
|
||||
import useConfirm from '@/common/methods/confirm'
|
||||
import wGroup from '@/components/modules/widgets/wGroup/wGroup.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { copyRight, SaveImage },
|
||||
@ -70,20 +72,43 @@ export default defineComponent({
|
||||
}
|
||||
// 保存模板
|
||||
async function saveTemp() {
|
||||
const { tempid } = route.query
|
||||
const { stat } = 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 })
|
||||
stat != 0 && useNotification('保存成功', '模板内容已变更')
|
||||
const { tempid, tempType: type } = route.query
|
||||
let res = null
|
||||
if (type == 1) {
|
||||
// 保存组件,组合元素要保证在最后一位,才能默认选中
|
||||
if (proxy.dWidgets[0].type === 'w-group') {
|
||||
const group = proxy.dWidgets.shift()
|
||||
group.record.width = 0
|
||||
group.record.height = 0
|
||||
proxy.dWidgets.push(group)
|
||||
}
|
||||
// TODO:如果保存组件不存在组合,则添加组合。该功能待优化
|
||||
if (!proxy.dWidgets.some((x) => 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.stat != 0 && useNotification('保存成功', '模板内容已变更')
|
||||
}
|
||||
// 停用启用
|
||||
async function stateChange(e: any) {
|
||||
const { tempid } = route.query
|
||||
const { stat } = await api.home.saveTemp({ id: tempid, state: e ? 1 : 0 })
|
||||
const { tempid, tempType: type } = route.query
|
||||
const { stat } = await api.home.saveTemp({ id: tempid, type, state: e ? 1 : 0 })
|
||||
stat != 0 && useNotification('保存成功', '模板内容已变更')
|
||||
}
|
||||
async function download() {
|
||||
if (state.loading === true) {
|
||||
return
|
||||
}
|
||||
// 临时提示
|
||||
if (proxy.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: '正在处理封面' })
|
||||
@ -132,8 +157,8 @@ export default defineComponent({
|
||||
...mapGetters(['dPage', 'dWidgets', 'tempEditing', 'dHistory', 'dPageHistory']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['pushHistory']),
|
||||
async load(id: any, tempId: any, cb: Function) {
|
||||
...mapActions(['pushHistory', 'addGroup']),
|
||||
async load(id: any, tempId: any, type: any, cb: Function) {
|
||||
if (this.$route.name !== 'Draw') {
|
||||
await useFontStore.init() // 初始化加载字体
|
||||
}
|
||||
@ -142,15 +167,22 @@ export default defineComponent({
|
||||
cb()
|
||||
return
|
||||
}
|
||||
const { data: content, title, state } = await api.home[apiName]({ id: id || tempId })
|
||||
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) // 清理掉上一次的选择框
|
||||
// this.$store.commit('setDWidgets', [])
|
||||
this.$store.commit('setDPage', data.page)
|
||||
id ? this.$store.commit('setDWidgets', data.widgets) : this.$store.dispatch('setTemplate', data.widgets)
|
||||
if (type == 1) {
|
||||
// 加载文字组合组件
|
||||
this.dPage.width = width
|
||||
this.dPage.height = height
|
||||
this.addGroup(data)
|
||||
} else {
|
||||
this.$store.commit('setDPage', data.page)
|
||||
id ? this.$store.commit('setDWidgets', data.widgets) : this.$store.dispatch('setTemplate', data.widgets)
|
||||
}
|
||||
cb()
|
||||
this.pushHistory('请求加载load')
|
||||
}
|
||||
|
@ -12,12 +12,13 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": ["webpack-env"],
|
||||
"types": ["webpack-env", "element-plus/global"],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user