feat: add back-end

This commit is contained in:
ShawnPhang 2024-08-14 20:53:43 +08:00
parent 85275c32ff
commit f6512b30b0
106 changed files with 11228 additions and 2891 deletions

20
LICENSE
View File

@ -19,3 +19,23 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------
中文许可证:
“迅排设计”(以下简称迅排)项目开源旨在促进技术社区交流与发展,本软件代码允许任何人士免费使用、研究、修改及发布、销售等行为,但必须保留本许可声明。
在可能的情况下,请在产品界面始终注明软件来源,并链接到此 Github 仓库,这种行为值得赞扬!但也并非是强制的。
一些行为或用途会直接损害作者的努力成果,特此声明:
❌ 您不得以原样形式倒卖或分发内容,原样形式是指未对内容进行任何创造性努力,并且其形式与我们网站上的内容完全相同。
❌ 您不得以误导或欺骗的方式使用内容,或暗示你的产品获得了来自迅排的授意。
❌ 您不得将迅排的任何内容用作商标、设计标记、商号或服务标记的一部分。
免责声明:
1本许可证项下的软件代码是“按原样”提供不作任何明示或暗示的保证包括但不限于对适销性、特定用途的适用性和不侵犯版权、专利、商标或其他权利的保证。在任何情况下版权持有人均不承担因使用或无法使用本软件而产生、引起的任何索赔、损害或其他责任包括任何一般、特殊、间接、附带或结果性损害。
2这是一个非官方翻译的开源许可证在 MIT 协议下的版本只有原始英文版才有效。然而,我们认识到,这种非官方的翻译将帮助开发者们更好地理解迅排设计的开源理念。我们鼓励自由地在迅排开源基础上进行各种创造、作品衍生,并期待社区能够出现更精彩的产品。

View File

@ -1,26 +1,37 @@
**[在线体验网站](https://design.palxp.cn/)** | **[在线文档](https://xp.palxp.cn/)** | [常见问题](https://xp.palxp.cn/#/articles/1689323321667) | [性能压测](https://juejin.cn/post/7348288810722869300)
<!--
* @Author: ShawnPhang
* @Date: 2024-08-11 16:17:52
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-14 18:54:40
-->
<h2>迅排设计 - Poster Design</h2>
---
<p>
<a href=""><img src="https://img.shields.io/github/stars/palxiao/poster-design?style=flat" alt="starts"></a>
<a href="https://github.com/palxiao/poster-design?tab=MIT-1-ov-file"><img src="https://img.shields.io/github/license/palxiao/poster-design?style=flat" alt="License"></a>
</p>
## 迅排设计
<p>
<a href="https://trendshift.io/repositories/8728" target="_blank"><img src="https://trendshift.io/api/badge/repositories/8728" alt="palxiao%2Fposter-design | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
一款漂亮易用且功能强大的创意图片编辑器对标稿定设计、创客贴、Canva 等商业产品。
漂亮易用且功能强大的在线创意图片编辑器对标稿定设计、创客贴、Canva 等产品,开源免费
适用于多种场景:海报图片生成、电商分享图、文章长图、视频/公众号封面等,无需下载软件即可轻松实现云端编辑、迅速完成图文排版。
适用于多种场景:海报图片生成、电商图文分享、制作文章长图、视频/公众号封面等,在线实现创意,迅速图文排版,设计就是如此简单!
[![](https://xp.palxp.cn/images/2023-7-16-1689500112694.gif)](https://design.palxp.cn/)
### 特点
**[官方网站](https://design.palxp.cn/)** | **[在线文档](https://xp.palxp.cn/)** | [常见问题](https://xp.palxp.cn/#/articles/1689323321667) | [性能压测](https://juejin.cn/post/7348288810722869300)
- 丝滑的页面操作体验,丰富的交互细节,基础功能完善
- 采用服务端生成图片,确保多端出图统一性,支持各种 HTML5 特性
- 简易 AI 抠图工具,上传图片一键去除背景
- 技术栈Vue3 、Vite5 、Pinia 、ElementPlus
- 图片生成Puppeteer、Express
- 丝滑的操作体验,丰富的交互细节,基础功能完善
- 服务端生成图片,支持各种 HTML5 特性
- 主要技术栈Vue3 、Vite5 、Pinia 、ElementPlus、Puppeteer、Express
### 支持功能
### 功能简介
- 导入 PSD 文件解析成模板、在线导出图片下载。
- 简易 AI 抠图工具,上传图片一键去除背景。
- 元素拖拽、组合、缩放、层级调整、对齐等操作。
- 图片素材插入、替换、裁剪,图片容器等功能。
- SVG 素材颜色、透明度编辑,文字花字组合。
@ -30,36 +41,30 @@
- 风格二维码编辑,支持单色、渐变、自定义 logo 等。
- 颜色调色板原生级取色器颜色吸管Chrome
## 快速开始
### 快速开始
```
git clone https://github.com/palxiao/poster-design.git
cd poster-design
npm run prepared
npm run dev
cd screenshot
npm run dev
npm run server
```
![](https://xp.palxp.cn/images/2023-7-16-1689498291322.png)
输入网址 http://127.0.0.1:5173/ 访问站点,更多参阅[详细说明文档](https://xp.palxp.cn/#/articles/1689319644311)。
访问 http://127.0.0.1:5173/ 查看网页。点此查看[更多说明文档](https://xp.palxp.cn/#/articles/1689319644311)。
### 服务端 & 图片生成
### 图片生成服务
本仓库中所提供的后端代码仅为示例作用,目的在于帮助开发者更好地理解项目,实际生产则推荐根据自身需求进行开发。原项目中请求的远程 API 接口已不再适用,但 [接口 API 文档](https://xp.palxp.cn/apidoc/index.html) 仍具有一定参考性。
代码位于根目录 [/screenshot](https://github.com/palxiao/poster-design/tree/main/screenshot)接口API文档点此查看[接口 API 文档](https://xp.palxp.cn/apidoc/screenshot.html)。
### 服务端
后端需要自己开发,目前本项目演示 Demo 中的后端接口参考:[接口 API 文档](https://xp.palxp.cn/apidoc/index.html)。
关于部署等说明请前往项目中查看,后端项目代码位于根目录 [/service](https://github.com/palxiao/poster-design/tree/main/service) 下。
### 其它
一些问题修改与记录[点击这里查看](https://xp.palxp.cn/#/articles/1689319986889?id=%e8%bf%ad%e4%bb%a3%e8%ae%a1%e5%88%92)项目持续迭代中,还有很多不足,可以将你遇到的问题在 Issues 中提出,或者提交 Pull Request 帮助完善。
项目持续迭代中,还有很多不足,可以将你遇到的问题在 Issues 中提出,或者提交 Pull Request 帮助完善。
### 交流群
关注公众号:品味前端,回复 “加群” 获取二维码,更新公告不错过
了解项目最新资讯、或技术交流,欢迎关注公众号《品味前端》;回复“加群”获取群号
<img style="width: 380px;" src="https://xp.palxp.cn/images/2024-3-1-1709306365949.png" />
@ -68,26 +73,20 @@ npm run dev
项目还使用或参考了一些优秀开源项目,包括但不限于:
- [moveable](https://github.com/daybrush/moveable): 提供了画布中选择、拖动缩放等能力
- [html2canvas](https://github.com/niklasvh/html2canvas): 前端生图的一种快捷方案
- [qr-code-styling](https://qr-code-styling.com/): 风格化二维码
- [rembg](https://github.com/danielgatis/rembg): 图片抠图,使用 u2net 预训练模型
- [html2canvas](https://github.com/niklasvh/html2canvas): 前端出图的简单方案
- [qr-code-styling](https://qr-code-styling.com/): 生成风格化二维码
- [rembg](https://github.com/danielgatis/rembg): 图片自动抠图,使用 u2net 预训练模型
### `Star`
开源不易,最后别忘了给本项目点个 **Star** ~
[![Star History Chart](https://api.star-history.com/svg?repos=palxiao/poster-design&type=Date)](https://star-history.com/#palxiao/poster-design&Date)
### `Star`
感谢所有支持本项目的朋友 :heart:
感谢所有喜欢和支持本项目的朋友 :heart:
[![Stargazers](https://bytecrank.com/nastyox/reporoster/php/stargazersSVG.php?user=palxiao&repo=poster-design)](https://github.com/palxiao/poster-design/stargazers)
### `Fork`
这些小伙伴都在使用迅排设计 :heart:
[![Forkers](https://bytecrank.com/nastyox/reporoster/php/forkersSVG.php?user=palxiao&repo=poster-design)](https://github.com/palxiao/poster-design/network/members)
### 友情赞助商
| Dooring低代码 | DrawOn桌案 |
@ -102,5 +101,4 @@ npm run dev
### `LICENSE`
本项目完全免费,可在保留 [MIT 开源许可证](https://github.com/palxiao/poster-design/blob/main/LICENSE) 的前提下使用。
本项目遵循 MIT 开源协议,建议仔细阅读并保留 [开源许可证](https://github.com/palxiao/poster-design/blob/main/LICENSE)。

41
package-lock.json generated
View File

@ -21,7 +21,7 @@
"dayjs": "^1.10.7",
"element-plus": "^2.6.3",
"fontfaceobserver": "^2.1.0",
"html2canvas": "^1.4.1",
"html2canvas": "^1.0.0",
"immer": "^10.0.4",
"microdiff": "^1.4.0",
"mitt": "^3.0.1",
@ -1665,9 +1665,9 @@
"dev": true
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==",
"engines": {
"node": ">= 0.6.0"
}
@ -1914,11 +1914,11 @@
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-1.1.1.tgz",
"integrity": "sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA==",
"dependencies": {
"utrie": "^1.0.2"
"base64-arraybuffer": "^0.2.0"
}
},
"node_modules/css-styled": {
@ -2665,12 +2665,11 @@
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.0.0.tgz",
"integrity": "sha512-0d/f2Aj1Brn+EeNWkuRdtnT13qu1NdvxhBMvts3ssme7jgPU7dtuwnm1P6cXvXmnDdUUerH5XdhveWvuLfqkew==",
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
"css-line-break": "1.1.1"
},
"engines": {
"node": ">=8.0.0"
@ -3845,14 +3844,6 @@
"node": ">=10"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -4017,14 +4008,6 @@
"punycode": "^2.1.0"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/vite": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz",

View File

@ -4,8 +4,8 @@
"private": true,
"author": "ShawnPhang",
"scripts": {
"prepared": "npm i && cd screenshot && npm i",
"serve": "npm run dev & cd screenshot && npm run dev",
"prepared": "npm i && cd service && npm i",
"serve": "npm run dev & cd service && npm run dev",
"dev": "cross-env NODE_ENV=development vite",
"v-build": "cross-env NODE_ENV=production && vite build",
"v-build-hard": "cross-env NODE_ENV=production vue-tsc --noEmit && vite build",
@ -24,7 +24,7 @@
"dayjs": "^1.10.7",
"element-plus": "^2.6.3",
"fontfaceobserver": "^2.1.0",
"html2canvas": "^1.4.1",
"html2canvas": "^1.0.0",
"immer": "^10.0.4",
"microdiff": "^1.4.0",
"mitt": "^3.0.1",

View File

@ -1,14 +0,0 @@
/*
* @Author: ShawnPhang
* @Date: 2020-07-22 20:13:14
* @Description:
* @LastEditors: ShawnPhang <site: m.palxp.cn>
* @LastEditTime: 2023-07-27 17:51:53
*/
let path = '/api';
module.exports = {
GETIMAGE: path + '/get_img',
SCREENGHOT: path + '/screenshots',
PRINTSCREEN: path + '/printscreen'
};

View File

@ -1,21 +0,0 @@
/*
* @Author: ShawnPhang
* @Date: 2020-07-22 20:13:14
* @Description:
* @LastEditors: ShawnPhang <site: m.palxp.cn>
* @LastEditTime: 2023-07-27 17:51:36
*/
const rExpress = require('express');
const screenshots = require('../service/screenshots.ts');
const api = require('./api.ts');
const rRouter = rExpress.Router();
rRouter.get(api.SCREENGHOT, screenshots.screenshots);
if (process.env.NODE_ENV === 'development') {
rRouter.get(api.PRINTSCREEN, screenshots.printscreen);
rRouter.get(api.GETIMAGE, screenshots.getImg);
}
module.exports = rRouter;

View File

@ -1,52 +0,0 @@
/*
* @Author: ShawnPhang
* @Date: 2022-02-01 13:41:59
* @Description:
* @LastEditors: ShawnPhang <site: m.palxp.cn>
* @LastEditTime: 2023-07-06 10:19:18
*/
const express = require('express')
const bodyParser = require('body-parser')
const fs = require('fs')
// const path = require('path')
const router = require('./control/router.ts')
const { filePath, servicePort } = require('./configs.ts')
const handleTimeout = require('./utils/timeout.ts')
const port = process.env.PORT || servicePort
const app = express()
// 创建目录
const createFolder = (folder: string) => {
try {
fs.accessSync(folder)
} catch (e) {
fs.mkdirSync(folder)
}
}
createFolder(filePath)
app.all('*', (req: any, res: any, next: any) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization,Content-Length,Content-Size');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});
app.use('/static', express.static('static'))
// app.use('/cache', express.static('cache'))
app.use(handleTimeout)
app.use((req: any, res: any, next: any) => {
console.log(req.path)
next()
})
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.use(router)
app.listen(port, () => console.log(`Screenshot Server start on port:${port}`))

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,8 @@
## Node截图服务
目录结构比较简单,主要就实现了三个接口,其中 `api/screenshots` 即是项目中所使用到的图片生成接口,在真实生产项目中可以把该服务单独部署,于内网调用,这样利于做一些鉴权之类的处理。
项目中所使用到的图片生成接口为:`api/screenshots`,在真实生产项目中可以把该服务单独部署,于内网调用,这样利于做一些鉴权之类的处理。
另外 `api/printscreen` 这个接口实现的是网页截图的 API该接口可以传入一个 URL 对整个网页进行截图,可用于合成长图分享海报等场景,本项目中没有使用到此接口
注:另外的 `api/printscreen` 本项目中并未使用,这个接口的作用是实现普通网页截图,可以传入一个 URL 生成该网址的预览图片,用于合成长图分享海报等场景
### 安装依赖
@ -15,9 +14,9 @@
ERROR: Failed to set up Chromium xxx! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to skip download.
```
不用慌,这是因为 puppeteer 会自动下载 Chromium国内会受到网络波动的影响
不用慌,这是因为 puppeteer 会自动下载 Chromium国内可能受到网络波动的影响而失败
如果跳过的话需要手动安装,比较麻烦所以并不推荐。解决方法是**多尝试几次,或者更换国内的镜像源**即可
如果跳过的话需要手动安装,比较麻烦所以并不推荐,请**多尝试安装几次,或者更换国内的镜像源再安装**
### 启动项目并热更新
@ -29,7 +28,11 @@ ERROR: Failed to set up Chromium xxx! Set "PUPPETEER_SKIP_DOWNLOAD" env variable
#### 打包部署步骤
> 服务器环境需求Node.js 16.18.1版本不同则可能出现错误、PM2进程守护
> 服务器环境需求:
>
> - Node.js 16.18.1(尽量保持生产版本相同,避免出现错误)
>
> - PM2进程守护
1. 本地执行 `npm run build` 打包
2. 打包后项目根目录 `dist/` 文件夹上传服务器,并执行 `npm install` 安装依赖

6398
service/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -17,13 +17,17 @@
"author": "ShawnPhang",
"license": "ISC",
"dependencies": {
"axios": "^1.7.3",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"express": "^4.19.2",
"image-size": "^1.1.1",
"images": "^3.2.4",
"multiparty": "^4.2.3",
"puppeteer": "^10.4.0"
},
"devDependencies": {
"@types/node": "^16.18.87",
"@types/express": "^4.17.21",
"@types/node": "^16.18.105",
"cross-env": "^7.0.3",
"ts-loader": "^6.0.4",
"ts-node": "^8.3.0",

View File

@ -3,14 +3,14 @@
* @Date: 2022-02-01 13:41:59
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-12-06 19:17:27
* @LastEditTime: 2024-08-12 05:13:19
*/
const isDev = process.env.NODE_ENV === 'development'
// 服务器常用修改项
const serviceComfig = {
port: 7001, // 端口号
website: 'https://design.palxp.cn', // 编辑器项目的地址
website: 'http://127.0.0.1:5173/', // 编辑器项目的地址
filePath: '/cache/' // 生成图片保存的目录
}
@ -22,7 +22,7 @@ exports.servicePort = serviceComfig.port
/**
*
*/
exports.drawLink = isDev ? 'http://localhost:5173/draw' : serviceComfig.website + '/draw'
exports.drawLink = isDev ? 'http://127.0.0.1:5173/draw' : serviceComfig.website + '/draw'
/**
*

View File

@ -0,0 +1,21 @@
/*
* @Author: ShawnPhang
* @Date: 2020-07-22 20:13:14
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 13:39:59
*/
let path = '/api'
module.exports = {
SCREENGHOT: path + '/screenshots',
PRINTSCREEN: path + '/printscreen',
// 后端示例
UPLOAD: path + '/file/upload',
USER_IMAGES: '/design/user/image',
GET_TEMPLATE_LIST: '/design/list',
GET_TEMPLATE: '/design/temp',
GET_MATERIAL: '/design/material',
GET_PHOTOS: '/design/imgs',
UPDATE_TEMPLATE: '/design/edit',
}

View File

@ -0,0 +1,26 @@
/*
* @Author: ShawnPhang
* @Date: 2020-07-22 20:13:14
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 13:40:13
*/
const rExpress = require('express')
const screenshots = require('../service/screenshots.ts')
const fileService = require('../service/files.ts')
const userService = require('../service/user.ts')
const designService = require('../service/design.ts')
const api = require('./api.ts')
const rRouter = rExpress.Router()
rRouter.get(api.SCREENGHOT, screenshots.screenshots)
rRouter.get(api.PRINTSCREEN, screenshots.printscreen)
rRouter.post(api.UPLOAD, fileService.upload)
rRouter.get(api.USER_IMAGES, userService.getUserImages)
rRouter.get(api.GET_TEMPLATE_LIST, designService.getTemplates)
rRouter.get(api.GET_TEMPLATE, designService.getDetail)
rRouter.get(api.GET_MATERIAL, designService.getMaterial)
rRouter.get(api.GET_PHOTOS, designService.getPhotos)
rRouter.post(api.UPDATE_TEMPLATE, designService.saveTemplate)
module.exports = rRouter

75
service/src/main.ts Normal file
View File

@ -0,0 +1,75 @@
/*
* @Author: ShawnPhang
* @Date: 2022-02-01 13:41:59
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 06:18:56
*/
const express = require('express')
const bodyParser = require('body-parser')
const fs = require('fs')
// const path = require('path')
const router = require('./control/router.ts')
const { filePath, servicePort } = require('./configs.ts')
const handleTimeout = require('./utils/timeout.ts')
const port = process.env.PORT || servicePort
const app = express()
// 创建目录
const createFolder = (folder: string) => {
try {
fs.accessSync(folder)
} catch (e) {
fs.mkdirSync(folder)
}
}
createFolder(filePath)
app.all('*', (req: any, res: any, next: any) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'X-Access-Token,Content-Type,Authorization,Content-Length,Content-Size')
res.header('Access-Control-Allow-Methods', '*')
res.header('Content-Type', 'application/json;charset=utf-8')
next()
})
app.use('/static', setUploadContentType, express.static(process.cwd() + `/static/`))
app.use(handleTimeout)
app.use((req: any, res: any, next: any) => {
console.log(req.path)
next()
})
app.use(bodyParser.urlencoded({ limit: '100mb', extended: true, parameterLimit: 100000 }))
app.use(bodyParser.json({ limit: '100mb' }))
app.use(router)
app.listen(port, () => console.log(`Screenshot Server start on port:${port}`))
const getContentType = function (path: any) {
const extension = path.split('.').pop().toLowerCase()
switch (extension) {
case 'jpg':
case 'jpeg':
return 'image/jpeg'
case 'png':
return 'image/png'
case 'gif':
return 'image/gif'
case 'svg':
return 'image/svg+xml'
default:
return null
}
}
function setUploadContentType(req: any, res: any, next: any) {
const contentType = getContentType(req.path)
if (contentType) {
res.setHeader('Content-Type', contentType)
}
next()
}

View File

@ -0,0 +1,8 @@
[
{
"id": 1,
"name": "手机海报",
"type": 1,
"pid": null
}
]

View File

@ -0,0 +1,12 @@
{
"id": 1907,
"cover": "https://pic.imgdb.cn/item/66b96b6ad9c307b7e9568778.png",
"data": "{\"name\":\"文本\",\"type\":\"w-text\",\"uuid\":-1,\"editable\":false,\"left\":40,\"top\":292,\"transform\":\"\",\"lineHeight\":1.2,\"letterSpacing\":0,\"fontSize\":180,\"fontClass\":{\"alias\":\"站酷快乐体\",\"id\":543,\"value\":\"zcool-kuaile-regular\",\"url\":\"https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2\"},\"fontWeight\":400,\"fontStyle\":\"normal\",\"writingMode\":\"horizontal-tb\",\"textDecoration\":\"none\",\"color\":\"#000000ff\",\"textAlign\":\"center\",\"text\":\"%E8%BE%93%E5%85%A5%E6%96%87%E5%AD%97\",\"opacity\":1,\"backgroundColor\":\"\",\"parent\":\"-1\",\"record\":{\"width\":0,\"height\":0,\"minWidth\":0,\"minHeight\":0,\"dir\":\"horizontal\"},\"width\":721,\"height\":217,\"rotate\":0,\"transformData\":{\"a\":1,\"b\":0,\"c\":0,\"d\":1,\"tx\":0,\"ty\":0},\"textEffects\":[{\"stroke\":{\"enable\":true,\"type\":\"center\",\"color\":\"#8581f7ff\",\"width\":11.122105263157893},\"offset\":{\"x\":3.7082741393523917,\"y\":6.171604565055211,\"enable\":true}},{\"stroke\":{\"enable\":true,\"type\":\"center\",\"color\":\"#8581f7ff\",\"width\":11.520000000000001}},{\"filling\":{\"enable\":true,\"type\":0,\"color\":\"#ffffffff\",\"imageContent\":{\"repeat\":0,\"scaleX\":1,\"scaleY\":1,\"image\":null,\"width\":null,\"height\":null},\"gradient\":{\"byLine\":0,\"angle\":0,\"stops\":[{\"color\":\"#ffffffff\",\"offset\":0},{\"color\":\"#000000ff\",\"offset\":1}]}}}]}",
"created_time": "2023-11-29T11:07:49.000Z",
"updated_time": "2023-11-29T11:13:04.000Z",
"title": "",
"width": null,
"height": null,
"state": 1,
"tag": null
}

View File

@ -0,0 +1,12 @@
{
"id": 1897,
"cover": "https://pic.imgdb.cn/item/66b96b84d9c307b7e9569826.png",
"data": "{\"name\":\"文本\",\"type\":\"w-text\",\"uuid\":-1,\"editable\":false,\"left\":46.00000000000002,\"top\":292,\"transform\":\"\",\"lineHeight\":1.2,\"letterSpacing\":0,\"fontSize\":180,\"fontClass\":{\"alias\":\"站酷快乐体\",\"id\":543,\"value\":\"zcool-kuaile-regular\",\"url\":\"https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2\"},\"fontWeight\":400,\"fontStyle\":\"normal\",\"writingMode\":\"horizontal-tb\",\"textDecoration\":\"none\",\"color\":\"#b8f5ffff\",\"textAlign\":\"center\",\"text\":\"%E8%BE%93%E5%85%A5%E6%96%87%E5%AD%97\",\"opacity\":1,\"backgroundColor\":\"\",\"parent\":\"-1\",\"record\":{\"width\":0,\"height\":0,\"minWidth\":0,\"minHeight\":0,\"dir\":\"horizontal\"},\"width\":709,\"height\":217,\"rotate\":0,\"transformData\":{\"a\":1,\"b\":0,\"c\":0,\"d\":1,\"tx\":0,\"ty\":0},\"textEffects\":[{\"stroke\":{\"color\":\"#148ef5ff\",\"type\":\"outer\",\"width\":5.538461538461537,\"enable\":true},\"filling\":{\"enable\":true,\"type\":0,\"color\":\"#b8f5ffff\",\"imageContent\":{\"repeat\":0,\"scaleX\":1,\"scaleY\":1,\"image\":null,\"width\":null,\"height\":null},\"gradient\":{\"byLine\":0,\"angle\":0,\"stops\":[{\"color\":\"#ffffffff\",\"offset\":0},{\"color\":\"#000000ff\",\"offset\":1}]}}}]}",
"created_time": "2023-11-29T11:07:38.000Z",
"updated_time": "2023-11-29T11:18:09.000Z",
"title": "花字-输入文字",
"width": null,
"height": null,
"state": 1,
"tag": null
}

View File

@ -0,0 +1,12 @@
{
"id": 1893,
"cover": "https://pic.imgdb.cn/item/66b96ba4d9c307b7e956b11f.png",
"data": "{\"name\":\"文本\",\"type\":\"w-text\",\"uuid\":-1,\"editable\":false,\"left\":40,\"top\":292,\"transform\":\"\",\"lineHeight\":1.2,\"letterSpacing\":0,\"fontSize\":180,\"fontClass\":{\"alias\":\"站酷快乐体\",\"id\":543,\"value\":\"zcool-kuaile-regular\",\"url\":\"https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2\"},\"fontWeight\":400,\"fontStyle\":\"normal\",\"writingMode\":\"horizontal-tb\",\"textDecoration\":\"none\",\"color\":\"#e5b187ff\",\"textAlign\":\"center\",\"text\":\"%E8%BE%93%E5%85%A5%E6%96%87%E5%AD%97\",\"opacity\":1,\"backgroundColor\":\"\",\"parent\":\"-1\",\"record\":{\"width\":0,\"height\":0,\"minWidth\":0,\"minHeight\":0,\"dir\":\"horizontal\"},\"width\":721,\"height\":217,\"rotate\":0,\"transformData\":{\"a\":1,\"b\":0,\"c\":0,\"d\":1,\"tx\":0,\"ty\":0},\"textEffects\":[{\"stroke\":{\"enable\":true,\"type\":\"outer\",\"color\":\"#ffffffff\",\"width\":10.080000000000002},\"filling\":{\"enable\":true,\"type\":0,\"color\":\"#e5b187ff\",\"imageContent\":{\"repeat\":0,\"scaleX\":1,\"scaleY\":1,\"image\":null,\"width\":null,\"height\":null},\"gradient\":{\"byLine\":0,\"angle\":0,\"stops\":[{\"color\":\"#ffffffff\",\"offset\":0},{\"color\":\"#000000ff\",\"offset\":1}]}},\"offset\":{\"x\":0.00045311068155588286,\"y\":0.0005595450922490192,\"enable\":true}},{\"filling\":{\"enable\":true,\"type\":2,\"color\":\"linear-gradient(90deg, #b6aff4ff 0%,#ffa6a9ff 98.83720930232558%)\",\"imageContent\":{\"repeat\":0,\"scaleX\":1,\"scaleY\":1,\"image\":null,\"width\":null,\"height\":null},\"gradient\":{\"byLine\":0,\"angle\":90,\"stops\":[{\"color\":\"#b6aff4ff\",\"offset\":0},{\"color\":\"#ffa6a9ff\",\"offset\":0.9883720930232558}]}}}]}",
"created_time": "2023-11-29T11:07:30.000Z",
"updated_time": "2023-11-29T11:12:58.000Z",
"title": "",
"width": null,
"height": null,
"state": 1,
"tag": null
}

View File

@ -0,0 +1,12 @@
{
"id": 1708,
"cover": "https://pic.imgdb.cn/item/66b96bbbd9c307b7e956c11f.png",
"data": "[{\"name\":\"图片\",\"type\":\"w-image\",\"uuid\":\"b7757b96efa5\",\"width\":764.4985269919339,\"height\":345.4354257106468,\"left\":0,\"top\":0,\"zoom\":1,\"transform\":\" scale(1)\",\"radius\":0,\"opacity\":1,\"parent\":\"8004f90b6dc9\",\"imgUrl\":\"https://pic.imgdb.cn/item/66b96be4d9c307b7e956dd5d.png\",\"setting\":[],\"record\":{\"width\":0,\"height\":0,\"minWidth\":10,\"minHeight\":10,\"dir\":\"all\"},\"letterSpacing\":null,\"rotate\":0,\"imageTransform\":{\"a\":0.9567650714184636,\"b\":0,\"c\":0,\"d\":0.9567650714184635,\"tx\":0,\"ty\":0},\"filter\":{\"contrast\":0,\"sharpness\":0,\"hueRotate\":0,\"saturate\":0,\"brightness\":0,\"gaussianBlur\":0,\"temperature\":0,\"tint\":0}},{\"name\":\"文本\",\"type\":\"w-text\",\"uuid\":\"ed669ec12cad\",\"editable\":false,\"left\":85.44999999999999,\"top\":321.05,\"transform\":\"\",\"lineHeight\":1.2,\"letterSpacing\":-3.0004813902799334,\"fontSize\":46.3,\"fontClass\":{\"alias\":\"站酷快乐体\",\"id\":543,\"value\":\"zcool-kuaile-regular\",\"url\":\"https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2\"},\"fontWeight\":400,\"fontStyle\":\"normal\",\"writingMode\":\"horizontal-tb\",\"textDecoration\":\"none\",\"color\":\"#ffbe55ff\",\"textAlign\":\"left\",\"text\":\"Good thing recommendation\",\"opacity\":1,\"backgroundColor\":\"\",\"parent\":\"8004f90b6dc9\",\"record\":{\"width\":387,\"height\":111,\"minWidth\":46.3,\"minHeight\":55.559999999999995,\"dir\":\"horizontal\"},\"width\":387.16,\"height\":111,\"imgUrl\":\"\",\"rotate\":0,\"filter\":{\"contrast\":0,\"sharpness\":0,\"hueRotate\":0,\"saturate\":0,\"brightness\":0,\"gaussianBlur\":0,\"temperature\":0,\"tint\":0}},{\"name\":\"文本\",\"type\":\"w-text\",\"uuid\":\"bb59ff748689\",\"editable\":false,\"left\":25.058536726412704,\"top\":74.51670112876556,\"transform\":\"\",\"lineHeight\":1.2,\"letterSpacing\":-3.4640083745899415,\"fontSize\":165,\"fontClass\":{\"alias\":\"站酷快乐体\",\"id\":543,\"value\":\"zcool-kuaile-regular\",\"url\":\"https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2\"},\"fontWeight\":400,\"fontStyle\":\"normal\",\"writingMode\":\"horizontal-tb\",\"textDecoration\":\"none\",\"color\":\"#d86356ff\",\"textAlign\":\"center\",\"text\":\"好物推荐\",\"opacity\":1,\"backgroundColor\":\"\",\"parent\":\"8004f90b6dc9\",\"record\":{\"width\":0,\"height\":0,\"minWidth\":0,\"minHeight\":0,\"dir\":\"horizontal\"},\"width\":703.1134861689588,\"height\":230.00000000000003,\"imgUrl\":\"\",\"rotate\":0,\"filter\":{\"contrast\":0,\"sharpness\":0,\"hueRotate\":0,\"saturate\":0,\"brightness\":0,\"gaussianBlur\":0,\"temperature\":0,\"tint\":0}},{\"name\":\"组合\",\"type\":\"w-group\",\"uuid\":\"8004f90b6dc9\",\"width\":765,\"height\":432,\"left\":0,\"top\":0,\"transform\":\"\",\"opacity\":1,\"parent\":\"-1\",\"isContainer\":true,\"record\":{\"width\":0,\"height\":0,\"minWidth\":0,\"minHeight\":0,\"dir\":\"none\"}}]",
"created_time": "2023-10-16T01:51:00.000Z",
"updated_time": "2023-10-16T11:16:16.000Z",
"title": "穿搭品牌文字组合-好物推荐",
"width": 763.5,
"height": 436.41,
"state": 1,
"tag": null
}

View File

@ -0,0 +1,12 @@
{
"id": 1665,
"cover": "https://pic.imgdb.cn/item/66b96c65d9c307b7e95738de.png",
"data": "[{\"name\":\"图片\",\"type\":\"w-image\",\"uuid\":\"7951dcdd7692\",\"width\":795,\"height\":501,\"left\":0,\"top\":0,\"zoom\":1,\"transform\":\" scale(1)\",\"radius\":0,\"opacity\":1,\"parent\":\"ab07c35d8793\",\"imgUrl\":\"https://pic.imgdb.cn/item/66b96c87d9c307b7e9575fcf.png\",\"setting\":[],\"record\":{\"width\":0,\"height\":0,\"minWidth\":10,\"minHeight\":10,\"dir\":\"all\"},\"letterSpacing\":null,\"rotate\":0,\"imageTransform\":{\"a\":1,\"b\":0,\"c\":0,\"d\":1,\"tx\":0,\"ty\":0},\"filter\":{\"contrast\":0,\"sharpness\":0,\"hueRotate\":0,\"saturate\":0,\"brightness\":0,\"gaussianBlur\":0,\"temperature\":0,\"tint\":0}},{\"name\":\"文本\",\"type\":\"w-text\",\"uuid\":\"250fd3b14808\",\"editable\":false,\"left\":64.48518998724226,\"top\":124.72493062188272,\"transform\":\"\",\"lineHeight\":1.2,\"letterSpacing\":0,\"fontSize\":164,\"fontClass\":{\"alias\":\"站酷快乐体\",\"id\":543,\"value\":\"zcool-kuaile-regular\",\"url\":\"https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2\"},\"fontWeight\":400,\"fontStyle\":\"normal\",\"writingMode\":\"horizontal-tb\",\"textDecoration\":\"none\",\"color\":\"#fa7f69ff\",\"textAlign\":\"center\",\"text\":\"今日探店\",\"opacity\":1,\"backgroundColor\":\"\",\"parent\":\"ab07c35d8793\",\"record\":{\"width\":0,\"height\":0,\"minWidth\":0,\"minHeight\":0,\"dir\":\"horizontal\"},\"width\":675.563157252699,\"height\":198.00000000000003,\"imgUrl\":\"\",\"rotate\":0,\"filter\":{\"contrast\":0,\"sharpness\":0,\"hueRotate\":0,\"saturate\":0,\"brightness\":0,\"gaussianBlur\":0,\"temperature\":0,\"tint\":0}},{\"name\":\"文本\",\"type\":\"w-text\",\"uuid\":\"99118d63bc46\",\"editable\":false,\"left\":204.35,\"top\":321.93,\"transform\":\"\",\"lineHeight\":1.2,\"letterSpacing\":24.10222222222222,\"fontSize\":45,\"fontClass\":{\"alias\":\"站酷快乐体\",\"id\":543,\"value\":\"zcool-kuaile-regular\",\"url\":\"https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2\"},\"fontWeight\":400,\"fontStyle\":\"normal\",\"writingMode\":\"horizontal-tb\",\"textDecoration\":\"none\",\"color\":\"#9f5b40ff\",\"textAlign\":\"center\",\"text\":\"Today's shop\",\"opacity\":1,\"backgroundColor\":\"\",\"parent\":\"ab07c35d8793\",\"record\":{\"width\":476,\"height\":54,\"minWidth\":45,\"minHeight\":54,\"dir\":\"horizontal\"},\"width\":475.84,\"height\":54,\"imgUrl\":\"\",\"rotate\":0,\"filter\":{\"contrast\":0,\"sharpness\":0,\"hueRotate\":0,\"saturate\":0,\"brightness\":0,\"gaussianBlur\":0,\"temperature\":0,\"tint\":0}},{\"name\":\"组合\",\"type\":\"w-group\",\"uuid\":\"ab07c35d8793\",\"width\":795,\"height\":501,\"left\":0,\"top\":0,\"transform\":\"\",\"opacity\":1,\"parent\":\"-1\",\"isContainer\":true,\"record\":{\"width\":0,\"height\":0,\"minWidth\":0,\"minHeight\":0,\"dir\":\"none\"}}]",
"created_time": "2023-10-16T01:49:31.000Z",
"updated_time": "2023-10-16T11:24:01.000Z",
"title": "穿搭品牌文字组合",
"width": 794,
"height": 500,
"state": 1,
"tag": null
}

View File

@ -0,0 +1,12 @@
{
"id": 1692,
"cover": "https://pic.imgdb.cn/item/66b96cc9d9c307b7e958455f.png",
"data": "[{\"name\":\"图片\",\"type\":\"w-image\",\"uuid\":\"53f98d18aeae\",\"width\":767.4314928425358,\"height\":367.5040822916187,\"left\":0,\"top\":0,\"zoom\":1,\"transform\":\" scale(1)\",\"radius\":0,\"opacity\":1,\"parent\":\"e885329bd396\",\"imgUrl\":\"https://pic.imgdb.cn/item/66b96cddd9c307b7e9588873.png\",\"setting\":[],\"record\":{\"width\":0,\"height\":0,\"minWidth\":10,\"minHeight\":10,\"dir\":\"all\"},\"letterSpacing\":null,\"rotate\":0,\"imageTransform\":{\"a\":1.1525285606654676,\"b\":0,\"c\":0,\"d\":1.1525285606654676,\"tx\":0,\"ty\":0},\"filter\":{\"contrast\":0,\"sharpness\":0,\"hueRotate\":0,\"saturate\":0,\"brightness\":0,\"gaussianBlur\":0,\"temperature\":0,\"tint\":0}},{\"name\":\"文本\",\"type\":\"w-text\",\"uuid\":\"092893910ceb\",\"editable\":false,\"left\":41.16266426491816,\"top\":91.58384458077711,\"transform\":\"\",\"lineHeight\":1.2,\"letterSpacing\":0,\"fontSize\":150,\"fontClass\":{\"alias\":\"站酷快乐体\",\"id\":543,\"value\":\"zcool-kuaile-regular\",\"url\":\"https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2\"},\"fontWeight\":400,\"fontStyle\":\"normal\",\"writingMode\":\"horizontal-tb\",\"textDecoration\":\"none\",\"color\":\"#057bccff\",\"textAlign\":\"center\",\"text\":\"今日上新\",\"opacity\":1,\"backgroundColor\":\"\",\"parent\":\"e885329bd396\",\"record\":{\"width\":0,\"height\":0,\"minWidth\":0,\"minHeight\":0,\"dir\":\"horizontal\"},\"width\":698.1940988730267,\"height\":204,\"imgUrl\":\"\",\"rotate\":0,\"textEffects\":[{\"shadow\":{\"color\":\"#45ece4ff\",\"offsetX\":4.610655737704919,\"offsetY\":7.985889993913879,\"blur\":0,\"enable\":true},\"filling\":{\"enable\":true,\"type\":0,\"color\":\"#057bccff\",\"imageContent\":{\"repeat\":0,\"scaleX\":1,\"scaleY\":1,\"image\":null,\"width\":null,\"height\":null},\"gradient\":{\"byLine\":0,\"angle\":0,\"stops\":[{\"color\":\"#ffffffff\",\"offset\":0},{\"color\":\"#000000ff\",\"offset\":1}]}}},{\"stroke\":{\"color\":\"#ffffffff\",\"type\":\"outer\",\"width\":2.0491803278688523,\"enable\":true},\"filling\":{\"enable\":true,\"type\":0,\"color\":\"#057bccff\",\"imageContent\":{\"repeat\":0,\"scaleX\":1,\"scaleY\":1,\"image\":null,\"width\":null,\"height\":null},\"gradient\":{\"byLine\":0,\"angle\":0,\"stops\":[{\"color\":\"#ffffffff\",\"offset\":0},{\"color\":\"#000000ff\",\"offset\":1}]}}}],\"filter\":{\"contrast\":0,\"sharpness\":0,\"hueRotate\":0,\"saturate\":0,\"brightness\":0,\"gaussianBlur\":0,\"temperature\":0,\"tint\":0}},{\"name\":\"组合\",\"type\":\"w-group\",\"uuid\":\"e885329bd396\",\"width\":766.4314928425358,\"height\":366.50408229161866,\"left\":0,\"top\":0,\"transform\":\"\",\"opacity\":1,\"parent\":\"-1\",\"isContainer\":true,\"setting\":[],\"record\":{\"width\":0,\"height\":0,\"minWidth\":0,\"minHeight\":0,\"dir\":\"none\"}}]",
"created_time": "2023-10-16T01:50:26.000Z",
"updated_time": "2023-10-16T11:19:39.000Z",
"title": "穿搭品牌文字组合",
"width": 766.43,
"height": 366.5,
"state": 1,
"tag": null
}

View File

@ -0,0 +1,26 @@
[
{
"id": 4,
"cover": "https://pic.imgdb.cn/item/66b96bbbd9c307b7e956c11f.png",
"title": "穿搭品牌文字组合",
"width": 763.5,
"height": 436.41,
"state": 1
},
{
"id": 5,
"cover": "https://pic.imgdb.cn/item/66b96c65d9c307b7e95738de.png",
"title": "穿搭品牌文字组合",
"width": 794,
"height": 500,
"state": 1
},
{
"id": 6,
"cover": "https://pic.imgdb.cn/item/66b96cc9d9c307b7e958455f.png",
"title": "穿搭品牌文字组合",
"width": 766.43,
"height": 366.5,
"state": 1
}
]

View File

@ -0,0 +1,26 @@
[
{
"id": 1,
"cover": "https://pic.imgdb.cn/item/66b96b6ad9c307b7e9568778.png",
"title": "",
"width": null,
"height": null,
"state": 1
},
{
"id": 2,
"cover": "https://pic.imgdb.cn/item/66b96b84d9c307b7e9569826.png",
"title": "花字-输入文字",
"width": null,
"height": null,
"state": 1
},
{
"id": 3,
"cover": "https://pic.imgdb.cn/item/66b96ba4d9c307b7e956b11f.png",
"title": "",
"width": null,
"height": null,
"state": 1
}
]

View File

@ -0,0 +1,41 @@
[
{
"id": 1,
"title": "爱心图片容器",
"width": 1000,
"height": 1000,
"type": "mask",
"model": "{}",
"thumb": "https://pic.imgdb.cn/item/66b96d96d9c307b7e95b20a9.png",
"url": "https://pic.imgdb.cn/item/66b96d96d9c307b7e95b20a9.png",
"created_time": "2023-08-20T21:46:49.000Z",
"updated_time": "2023-09-15T11:42:14.000Z",
"state": 1
},
{
"id": 2,
"title": "扇形图片容器",
"width": 1000,
"height": 1000,
"type": "mask",
"model": "{}",
"thumb": "https://pic.imgdb.cn/item/66b96da6d9c307b7e95b5cb8.png",
"url": "https://pic.imgdb.cn/item/66b96da6d9c307b7e95b5cb8.png",
"created_time": "2023-08-20T21:46:52.000Z",
"updated_time": "2023-09-15T11:42:14.000Z",
"state": 1
},
{
"id": 3,
"title": "星星图片容器",
"width": 1000,
"height": 1000,
"type": "mask",
"model": "{}",
"thumb": "https://pic.imgdb.cn/item/66b96db5d9c307b7e95b9c96.png",
"url": "https://pic.imgdb.cn/item/66b96db5d9c307b7e95b9c96.png",
"created_time": "2023-08-20T21:47:03.000Z",
"updated_time": "2023-09-15T11:42:14.000Z",
"state": 1
}
]

View File

@ -0,0 +1,422 @@
[
{
"id": 392,
"thumb": "https://plus.unsplash.com/premium_photo-1669904021308-567d085a0ee7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1669904021308-567d085a0ee7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3943,
"height": 5907,
"created_time": "2023-10-04T01:29:13.000Z",
"updated_time": "2023-10-04T01:29:13.000Z",
"category": 26,
"original": "BnHJf5bx6zY",
"author": "Kateryna Hliznitsova",
"description": "一个人在一杯咖啡旁边的一张纸上写字",
"color": "#EFEFEF"
},
{
"id": 394,
"thumb": "https://plus.unsplash.com/premium_photo-1669686968068-ef4133a3e782?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1669686968068-ef4133a3e782?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6630,
"height": 5304,
"created_time": "2023-10-04T01:29:13.000Z",
"updated_time": "2023-10-04T01:29:13.000Z",
"category": 26,
"original": "KRUtalZrz04",
"author": "Brock Wegner",
"description": "桌上放着一台笔记本电脑",
"color": "#EFEFEF"
},
{
"id": 395,
"thumb": "https://plus.unsplash.com/premium_photo-1661274209157-118069b926f3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1661274209157-118069b926f3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5760,
"height": 3840,
"created_time": "2023-10-04T01:29:13.000Z",
"updated_time": "2023-10-04T01:29:13.000Z",
"category": 26,
"original": "BCeRwWOvenM",
"author": "Getty Images",
"description": "商务人士在办公室工作和头脑风暴",
"color": "#EFEFEF"
},
{
"id": 396,
"thumb": "https://plus.unsplash.com/premium_photo-1672287578699-618ea6dbcc9e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1672287578699-618ea6dbcc9e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2724,
"height": 4086,
"created_time": "2023-10-04T01:29:13.000Z",
"updated_time": "2023-10-04T01:29:13.000Z",
"category": 26,
"original": "P1nKvKqkXGk",
"author": "Giulia Squillace",
"description": "一个在建筑里雕刻雕塑的人",
"color": "#EFEFEF"
},
{
"id": 397,
"thumb": "https://images.unsplash.com/photo-1587554801471-37976a256db0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1587554801471-37976a256db0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3684,
"height": 5526,
"created_time": "2023-10-04T01:29:13.000Z",
"updated_time": "2023-10-04T01:29:13.000Z",
"category": 26,
"original": "tqkDGqPW8Vo",
"author": "Olga Serjantu",
"description": "穿黄衬衫的女人坐在椅子上",
"color": "#f3f3f3"
},
{
"id": 398,
"thumb": "https://plus.unsplash.com/premium_photo-1674777843121-a205f3e0bc62?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1674777843121-a205f3e0bc62?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2473,
"height": 3300,
"created_time": "2023-10-04T01:29:13.000Z",
"updated_time": "2023-10-04T01:29:13.000Z",
"category": 26,
"original": "mMVFQwDAlPQ",
"author": "Lia Bekyan",
"description": "一个穿西装打领带的男人",
"color": "#260c0c"
},
{
"id": 399,
"thumb": "https://plus.unsplash.com/premium_photo-1677695598264-6d88f8ee31f0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1677695598264-6d88f8ee31f0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5000,
"height": 3333,
"created_time": "2023-10-04T01:29:13.000Z",
"updated_time": "2023-10-04T01:29:13.000Z",
"category": 26,
"original": "j1esUYunPdo",
"author": "JSB Co.",
"description": "剪刀放在机器上的一把剪刀",
"color": "#c0c0c0"
},
{
"id": 400,
"thumb": "https://plus.unsplash.com/premium_photo-1668383778611-a817740e1b6b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1668383778611-a817740e1b6b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6000,
"height": 4000,
"created_time": "2023-10-04T01:29:13.000Z",
"updated_time": "2023-10-04T01:29:13.000Z",
"category": 26,
"original": "Mal1M5DQWdc",
"author": "Michael Tucker",
"description": "几个人坐在房间里的一张桌子旁",
"color": "#EFEFEF"
},
{
"id": 401,
"thumb": "https://plus.unsplash.com/premium_photo-1668383205037-8d3f75314a06?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1668383205037-8d3f75314a06?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTYzNTQwOTR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5472,
"height": 3648,
"created_time": "2023-10-04T01:29:13.000Z",
"updated_time": "2023-10-04T01:29:13.000Z",
"category": 26,
"original": "FePq2NkJ6wU",
"author": "Michael Tucker",
"description": "一个男人和一个小女孩坐在一张桌子旁",
"color": "#EFEFEF"
},
{
"id": 492,
"thumb": "https://images.unsplash.com/photo-1695408251539-31edab27e8e1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695408251539-31edab27e8e1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4000,
"height": 6000,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "e2btSmpVyVM",
"author": "Wesley Tingey",
"description": "一名男子拿着照相机正在录像",
"color": "#262626"
},
{
"id": 493,
"thumb": "https://images.unsplash.com/photo-1695395855294-92b404ec00e9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695395855294-92b404ec00e9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 8377,
"height": 5755,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "W6Fh690Ntfk",
"author": "aboodi vesakaran",
"description": "一辆白色货车上面有亚马逊prime的标志",
"color": "#c0c0c0"
},
{
"id": 494,
"thumb": "https://images.unsplash.com/photo-1695624825386-ec67689eb983?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695624825386-ec67689eb983?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3847,
"height": 6000,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "9IdFjHfP0lo",
"author": "Declan Sun",
"description": "走廊上有文字装饰的墙壁和海报",
"color": "#d9d9d9"
},
{
"id": 495,
"thumb": "https://images.unsplash.com/photo-1695548053857-c070504389f0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695548053857-c070504389f0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4595,
"height": 6893,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "VnrTSfWmP1s",
"author": "Maxence Pira",
"description": "一间摆满了桌椅的大房间",
"color": "#260c0c"
},
{
"id": 496,
"thumb": "https://images.unsplash.com/photo-1694951558444-03b27ca33665?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694951558444-03b27ca33665?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3024,
"height": 4032,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "XTkIub6UDnU",
"author": "Yana Marudova",
"description": "从Ludwig Erhard Haus电梯内部看展示了建筑的未来主义内部电梯本身装饰着木板。",
"color": "#73260c"
},
{
"id": 497,
"thumb": "https://images.unsplash.com/photo-1695407773557-ea2dc7b3ed4a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695407773557-ea2dc7b3ed4a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6240,
"height": 4160,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "zCX0mBy1-iU",
"author": "Komarov Egor ",
"description": "带有按钮的控制面板的特写",
"color": "#0c2626"
},
{
"id": 498,
"thumb": "https://plus.unsplash.com/premium_photo-1677508967195-a3951205ca70?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1677508967195-a3951205ca70?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6004,
"height": 3935,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "seGYeyekwAU",
"author": "Mina Rad",
"description": "一个戴安全帽的女人拿着一张蓝图",
"color": "#262626"
},
{
"id": 499,
"thumb": "https://plus.unsplash.com/premium_photo-1677920190308-ea03b51a9d9b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1677920190308-ea03b51a9d9b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3597,
"height": 5381,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "4NKaNzL1Iqg",
"author": "Mina Rad",
"description": "一男一女站在大楼前",
"color": "#c0c0c0"
},
{
"id": 500,
"thumb": "https://images.unsplash.com/photo-1695029400320-f3fc8efa30b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695029400320-f3fc8efa30b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5984,
"height": 3848,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "27Eot-Zulmw",
"author": "Cai Fang",
"description": "密集宿舍楼",
"color": "#d9d9d9"
},
{
"id": 501,
"thumb": "https://images.unsplash.com/photo-1694881227503-ddc502d4b4cc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694881227503-ddc502d4b4cc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjczODl8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6227,
"height": 4151,
"created_time": "2023-10-04T21:49:57.000Z",
"updated_time": "2023-10-04T21:49:57.000Z",
"category": 26,
"original": "E8JXH2PNahs",
"author": "Tim Mossholder",
"description": "播客主持人Dorian Djougoue。关注他:@dorian.djougoue",
"color": "#c0c0c0"
},
{
"id": 502,
"thumb": "https://images.unsplash.com/photo-1694638275387-9971d6b19880?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694638275387-9971d6b19880?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4480,
"height": 5600,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "cdiSpITPwvE",
"author": "Ben Iwara",
"description": "一个人站在红灯前",
"color": "#8c0c0c"
},
{
"id": 503,
"thumb": "https://images.unsplash.com/photo-1694638278223-4c3907aa2354?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694638278223-4c3907aa2354?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6720,
"height": 4480,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "V51__4DiYV4",
"author": "Ben Iwara",
"description": "一个女人正在对着镜子照自己",
"color": "#c0590c"
},
{
"id": 504,
"thumb": "https://images.unsplash.com/photo-1694543668881-f5a1174bfca9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694543668881-f5a1174bfca9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4000,
"height": 5000,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "qELWBcqOzdo",
"author": "Sayan Majhi",
"description": "苹果2023年发布会。",
"color": "#f3f3f3"
},
{
"id": 505,
"thumb": "https://images.unsplash.com/photo-1694324217586-3b065a9eeee8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694324217586-3b065a9eeee8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2626,
"height": 3283,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "0vJz_fyVcZ8",
"author": "Vishwasa Navada K",
"description": "班加罗尔国际机场休息室的工作舱",
"color": "#594040"
},
{
"id": 506,
"thumb": "https://plus.unsplash.com/premium_photo-1682702597511-df891e5e2219?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1682702597511-df891e5e2219?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6000,
"height": 4000,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "4_-k_FGlU8Y",
"author": "Lala Azizli",
"description": "手里拿着白牌的人",
"color": "#8c8ca6"
},
{
"id": 507,
"thumb": "https://plus.unsplash.com/premium_photo-1681319269837-98a23dfb24aa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1681319269837-98a23dfb24aa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3376,
"height": 5071,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "k3ifGQUJGf8",
"author": "Jason Hawke ",
"description": "从钱包里伸出来的一美元钞票",
"color": "#59408c"
},
{
"id": 508,
"thumb": "https://plus.unsplash.com/premium_photo-1681305758171-f28f43aa0dc7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1681305758171-f28f43aa0dc7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4738,
"height": 2961,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "a74eSALUep0",
"author": "Jason Hawke ",
"description": "皮沙发上放着一部手机",
"color": "#594026"
},
{
"id": 509,
"thumb": "https://images.unsplash.com/photo-1694409496215-d116011d0d1b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694409496215-d116011d0d1b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5842,
"height": 3895,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "kOdl-epUGUE",
"author": "Michael Pointner",
"description": "有桌子和椅子的房间",
"color": "#260c0c"
},
{
"id": 510,
"thumb": "https://images.unsplash.com/photo-1694401460834-87b727378a63?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694401460834-87b727378a63?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5463,
"height": 4000,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "cDOXyyBeK8c",
"author": "Declan Sun",
"description": "张江科学馆的自动扶梯",
"color": "#8ca6c0"
},
{
"id": 511,
"thumb": "https://images.unsplash.com/photo-1694401460698-8b7b0489b27a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694401460698-8b7b0489b27a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgyMjV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5455,
"height": 4000,
"created_time": "2023-10-04T22:03:53.000Z",
"updated_time": "2023-10-04T22:03:53.000Z",
"category": 26,
"original": "pYLuh6fRdhw",
"author": "Declan Sun",
"description": "张江科学馆近景",
"color": "#8cc0f3"
},
{
"id": 512,
"thumb": "https://images.unsplash.com/photo-1694399319378-efcfd00aa1e8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgzMjh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694399319378-efcfd00aa1e8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8YWV1NnJMLWo2ZXd8fHx8fDJ8fDE2OTY0MjgzMjh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2995,
"height": 2995,
"created_time": "2023-10-04T22:08:45.000Z",
"updated_time": "2023-10-04T22:08:45.000Z",
"category": 26,
"original": "s0DKhyQftnk",
"author": "Hillary Black",
"description": "无题",
"color": "#8c8c8c"
}
]

View File

@ -0,0 +1,394 @@
[
{
"id": 432,
"thumb": "https://images.unsplash.com/photo-1674684417927-0008e103930f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1674684417927-0008e103930f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3077,
"height": 2040,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "H28oH0s7gac",
"author": "Megan Nixon",
"description": "无题",
"color": "#262626"
},
{
"id": 433,
"thumb": "https://images.unsplash.com/photo-1674684493970-a153e378af72?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1674684493970-a153e378af72?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3130,
"height": 2075,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "OcfpHpQNiI8",
"author": "Megan Nixon",
"description": "太阳正落在海面上,海面上有岩石",
"color": "#a6c0c0"
},
{
"id": 434,
"thumb": "https://images.unsplash.com/photo-1696258361221-9f9bc7b99c92?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696258361221-9f9bc7b99c92?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5807,
"height": 3869,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "8Yjvz56y7QU",
"author": "Samuele Giglio",
"description": "一群棕榈树,背景是蓝天",
"color": "#d9d9f3"
},
{
"id": 435,
"thumb": "https://images.unsplash.com/photo-1696258361311-0b25ee8af854?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696258361311-0b25ee8af854?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5807,
"height": 3869,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "krA_f-jEobI",
"author": "Samuele Giglio",
"description": "以山为背景的城市景观",
"color": "#d9d9d9"
},
{
"id": 436,
"thumb": "https://plus.unsplash.com/premium_photo-1690164161389-1921e4981b69?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1690164161389-1921e4981b69?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3130,
"height": 2075,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "N7GTHx2F8H0",
"author": "Hans Isaacson",
"description": "有很多树和岩石的海滩",
"color": "#d9d9d9"
},
{
"id": 437,
"thumb": "https://images.unsplash.com/photo-1696259630326-9cf6be5e96aa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696259630326-9cf6be5e96aa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 1846,
"height": 2785,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "dXSe4d9xbyQ",
"author": "Richard Stachmann",
"description": "柯达黄金200",
"color": "#f3f3f3"
},
{
"id": 438,
"thumb": "https://images.unsplash.com/photo-1684183164497-f62ee4c78620?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1684183164497-f62ee4c78620?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2411,
"height": 3616,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "0aWJqicnOLM",
"author": "Chris Weiher",
"description": "地中海的海浪冲击着意大利阿马尔菲海岸。水的颜色可以用青绿色来形容。",
"color": "#a6a68c"
},
{
"id": 439,
"thumb": "https://images.unsplash.com/photo-1684183164552-df1258404d38?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1684183164552-df1258404d38?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3612,
"height": 2408,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "2HwdhMvgYfE",
"author": "Chris Weiher",
"description": "意大利南部两棵丑陋的棕榈树之间的蓝色天空中的月亮。在西尔伯萨尔兹250D模拟胶片上拍摄。",
"color": "#8ca6c0"
},
{
"id": 440,
"thumb": "https://images.unsplash.com/photo-1696268516413-56e2c7c37639?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696268516413-56e2c7c37639?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3167,
"height": 4492,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "OMGAR5MSSM8",
"author": "Robert Bye",
"description": "一群人站在沙滩上",
"color": "#c0d9f3"
},
{
"id": 441,
"thumb": "https://images.unsplash.com/photo-1696258361232-5cf43e06afa8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696258361232-5cf43e06afa8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MTgxODV8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5807,
"height": 3869,
"created_time": "2023-10-04T19:16:32.000Z",
"updated_time": "2023-10-04T19:16:32.000Z",
"category": 33,
"original": "-pQUCnVLqFs",
"author": "Samuele Giglio",
"description": "灯光昏暗的房间里有几张木桌和椅子",
"color": "#260c0c"
},
{
"id": 632,
"thumb": "https://images.unsplash.com/photo-1696268547236-09b9669f8a7e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696268547236-09b9669f8a7e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3167,
"height": 4492,
"created_time": "2023-10-05T00:24:59.000Z",
"updated_time": "2023-10-05T00:24:59.000Z",
"category": 33,
"original": "mxkqjQtYhlU",
"author": "Robert Bye",
"description": "在阳光明媚的日子里通往大海的楼梯",
"color": "#f3f3f3"
},
{
"id": 633,
"thumb": "https://images.unsplash.com/photo-1696268615869-f6be40a7ebd8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696268615869-f6be40a7ebd8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3167,
"height": 4492,
"created_time": "2023-10-05T00:24:59.000Z",
"updated_time": "2023-10-05T00:24:59.000Z",
"category": 33,
"original": "u4RGxjShwnc",
"author": "Robert Bye",
"description": "一座石头建筑,前面有一座钟",
"color": "#c0d9f3"
},
{
"id": 634,
"thumb": "https://images.unsplash.com/photo-1694816602214-72f2729e86e7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694816602214-72f2729e86e7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4881,
"height": 3272,
"created_time": "2023-10-05T00:24:59.000Z",
"updated_time": "2023-10-05T00:24:59.000Z",
"category": 33,
"original": "UNYHQM0ElwI",
"author": "Ethan Hansen",
"description": "搭载富士GW690II的Porta 800",
"color": "#a65940"
},
{
"id": 635,
"thumb": "https://images.unsplash.com/photo-1681332192745-1f00ceed0b45?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1681332192745-1f00ceed0b45?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5444,
"height": 3649,
"created_time": "2023-10-05T00:24:59.000Z",
"updated_time": "2023-10-05T00:24:59.000Z",
"category": 33,
"original": "23XSW5hNFRU",
"author": "Mykyta Kravenko",
"description": "本地汽车隧道。尼康F(1968)尼克尔50mm(21世纪初)。高分辨率模拟扫描www.boutiquefilmlab.com cinstill 800钨",
"color": "#0c2626"
},
{
"id": 636,
"thumb": "https://images.unsplash.com/photo-1695370530104-3417106f11e3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695370530104-3417106f11e3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2872,
"height": 4000,
"created_time": "2023-10-05T00:24:59.000Z",
"updated_time": "2023-10-05T00:24:59.000Z",
"category": 33,
"original": "bidoZaK4ZIE",
"author": "The Australian War Memorial",
"description": "一名德国士兵从他的观察气球上跳下来身份证号:H13483摄影师:德意志帝国档案馆地点:未知一名德国士兵从他的观察气球上跳下来。当他们的气球被盟军飞机袭击时德国观察员遵循了这个程序。https://www.flickr.com/photos/australian-war-memorial/21795002060/in/album-72157659120304398/",
"color": "#f3f3f3"
},
{
"id": 637,
"thumb": "https://images.unsplash.com/photo-1695649424908-b98530375146?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695649424908-b98530375146?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3507,
"height": 5108,
"created_time": "2023-10-05T00:24:59.000Z",
"updated_time": "2023-10-05T00:24:59.000Z",
"category": 33,
"original": "sZM6zQObTFU",
"author": "szm 4",
"description": "一栋有很多窗户的建筑",
"color": "#d9d9d9"
},
{
"id": 638,
"thumb": "https://plus.unsplash.com/premium_photo-1690164680142-701cf794a832?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1690164680142-701cf794a832?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2075,
"height": 3130,
"created_time": "2023-10-05T00:24:59.000Z",
"updated_time": "2023-10-05T00:24:59.000Z",
"category": 33,
"original": "RZkg3a-RVSE",
"author": "Hans Isaacson",
"description": "拿着冲浪板走在沙滩上的人",
"color": "#d9d9d9"
},
{
"id": 639,
"thumb": "https://images.unsplash.com/photo-1681625597273-a8888f25eddb?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1681625597273-a8888f25eddb?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MDN8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2645,
"height": 3826,
"created_time": "2023-10-05T00:24:59.000Z",
"updated_time": "2023-10-05T00:24:59.000Z",
"category": 33,
"original": "vgShIkGBqFc",
"author": "Howen",
"description": "在街上骑自行车的人",
"color": "#c0a68c"
},
{
"id": 640,
"thumb": "https://images.unsplash.com/photo-1693416789619-0dfb6485943d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1693416789619-0dfb6485943d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2333,
"height": 3500,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "xNuTNri-6Hs",
"author": "Markus Spiske",
"description": "慕尼黑的地铁。徕卡M6 (1987) Summilux-M 1.4 35mm(1983)。高分辨率模拟扫描www.urbanfilmlab.com cinstill Xpro 800钨",
"color": "#262626"
},
{
"id": 641,
"thumb": "https://images.unsplash.com/photo-1695404468642-12830a1d4c7d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695404468642-12830a1d4c7d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3637,
"height": 2433,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "Dolz-BphJjQ",
"author": "Chase Caldwell",
"description": "一个女人站在海边的悬崖顶上",
"color": "#8ca6c0"
},
{
"id": 642,
"thumb": "https://images.unsplash.com/photo-1692026801134-dc74fa1d9aa5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1692026801134-dc74fa1d9aa5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4313,
"height": 6504,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "1HuDHbAnd78",
"author": "Annie Spratt",
"description": "一片被树木和山脉包围的水域",
"color": "#d9d9f3"
},
{
"id": 643,
"thumb": "https://images.unsplash.com/photo-1691824103453-2b9c1d89504b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1691824103453-2b9c1d89504b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4492,
"height": 6774,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "Ms-FanpBBU4",
"author": "Annie Spratt",
"description": "一条流经郁郁葱葱的山坡的河流",
"color": "#c0d9f3"
},
{
"id": 644,
"thumb": "https://images.unsplash.com/photo-1695659650981-a0afd184d6ad?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695659650981-a0afd184d6ad?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 1889,
"height": 2897,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "IJEw0v34dak",
"author": "Valeria Vaganian",
"description": "生日花",
"color": "#f3f3f3"
},
{
"id": 645,
"thumb": "https://images.unsplash.com/photo-1695307965322-61077ed0bc22?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695307965322-61077ed0bc22?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3600,
"height": 2400,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "aB-a8q-x5hM",
"author": "Randy Laybourne",
"description": "我们使用的越多,我们制造的垃圾就越多。回收这些垃圾的尝试似乎只是杯水车薪。",
"color": "#d92626"
},
{
"id": 646,
"thumb": "https://images.unsplash.com/photo-1695676304344-06f3f16faf59?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695676304344-06f3f16faf59?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3510,
"height": 5293,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "NZFzU0ILyC0",
"author": "Caleb Woods",
"description": "一辆顶着头盔的红色汽车",
"color": "#f3f3f3"
},
{
"id": 647,
"thumb": "https://images.unsplash.com/photo-1696215123982-f6ced9faa34f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696215123982-f6ced9faa34f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 7719,
"height": 11520,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "TerTY3G7GJI",
"author": "Venti Views",
"description": "使用尼康f100 35mm胶片拍摄",
"color": "#c0c0c0"
},
{
"id": 648,
"thumb": "https://images.unsplash.com/photo-1694588579227-e5d9c9524dfa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694588579227-e5d9c9524dfa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4722,
"height": 3108,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "h-OwCCAMMVk",
"author": "Nik",
"description": "",
"color": "#262626"
},
{
"id": 649,
"thumb": "https://images.unsplash.com/photo-1694588586438-703ef60dc56a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1694588586438-703ef60dc56a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8aG1lbnZRaFVteE18fHx8fDJ8fDE2OTY0MzY3MjR8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5689,
"height": 3803,
"created_time": "2023-10-05T00:25:25.000Z",
"updated_time": "2023-10-05T00:25:25.000Z",
"category": 33,
"original": "zQgNc8IDtqY",
"author": "Nik",
"description": "",
"color": "#404040"
}
]

View File

@ -0,0 +1,422 @@
[
{
"id": 482,
"thumb": "https://images.unsplash.com/photo-1695763608950-d224f9189ef7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695763608950-d224f9189ef7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6336,
"height": 9520,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "lc9uYwe54us",
"author": "Karsten Winegeart",
"description": "通往天堂的阶梯",
"color": "#c0d9d9"
},
{
"id": 483,
"thumb": "https://images.unsplash.com/photo-1695763491781-748b1e3b2c3c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695763491781-748b1e3b2c3c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6264,
"height": 9412,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "6hy-3Uc0jS0",
"author": "Karsten Winegeart",
"description": "教堂",
"color": "#738ca6"
},
{
"id": 484,
"thumb": "https://images.unsplash.com/photo-1695760442128-6db7202ea8cc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695760442128-6db7202ea8cc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4091,
"height": 6136,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "baWtGQuyNVo",
"author": "Karsten Winegeart",
"description": "独自一人",
"color": "#d9d9d9"
},
{
"id": 485,
"thumb": "https://images.unsplash.com/photo-1696171667963-ad7a0ab6ffac?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696171667963-ad7a0ab6ffac?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5629,
"height": 7505,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "O_D4pTrPcBQ",
"author": "Martin Katler",
"description": "以山为背景的城市景观",
"color": "#d9d9f3"
},
{
"id": 486,
"thumb": "https://images.unsplash.com/photo-1696259141244-650be1219522?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696259141244-650be1219522?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3648,
"height": 5472,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "8IU8l_39XR0",
"author": "eberhard grossgasteiger",
"description": "一个湖边的小镇,背后是群山",
"color": "#262626"
},
{
"id": 487,
"thumb": "https://images.unsplash.com/photo-1696346299667-808c1f233dbc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696346299667-808c1f233dbc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 9240,
"height": 13883,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "q-MldlOR9EE",
"author": "Karsten Winegeart",
"description": "安静...",
"color": "#735940"
},
{
"id": 488,
"thumb": "https://images.unsplash.com/photo-1696371254452-e8fd8e0cd191?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696371254452-e8fd8e0cd191?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3308,
"height": 4961,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "U96T__GBdUU",
"author": "Mathias Reding",
"description": "一座大建筑物,上面有一座钟楼",
"color": "#d9d9f3"
},
{
"id": 489,
"thumb": "https://images.unsplash.com/photo-1695043722490-72207a74a7be?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695043722490-72207a74a7be?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3605,
"height": 5634,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "ovfl4RfHoac",
"author": "Jorgen Hendriksen",
"description": "绿色的。",
"color": "#f3f3f3"
},
{
"id": 490,
"thumb": "https://images.unsplash.com/photo-1695418416357-4b248028af92?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695418416357-4b248028af92?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4000,
"height": 6000,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "5x3Cfw2h7sI",
"author": "Anne Laure P",
"description": "神社",
"color": "#c0d9d9"
},
{
"id": 491,
"thumb": "https://images.unsplash.com/photo-1696085709531-81fba5dbcffd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696085709531-81fba5dbcffd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MjYxMzh8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5966,
"height": 3356,
"created_time": "2023-10-04T21:43:34.000Z",
"updated_time": "2023-10-04T21:43:34.000Z",
"category": 35,
"original": "B34Vmj0ZgCA",
"author": "Fahrul Razi",
"description": "太阳落山,海面上露出一块岩石",
"color": "#c0a673"
},
{
"id": 670,
"thumb": "https://images.unsplash.com/photo-1695883668752-3e6bdf0f3649?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695883668752-3e6bdf0f3649?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6001,
"height": 3993,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "uzVUwf4ri8E",
"author": "Zetong Li",
"description": "太阳照耀着沙漠中的群山",
"color": "#594026"
},
{
"id": 671,
"thumb": "https://images.unsplash.com/photo-1691788793928-2924fb185699?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1691788793928-2924fb185699?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 7727,
"height": 5152,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "LiI5VQEWKqg",
"author": "Alexey Iskhakov",
"description": "一座山,背景是粉红色的天空",
"color": "#c08c8c"
},
{
"id": 672,
"thumb": "https://images.unsplash.com/photo-1673881140563-6779096df45c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1673881140563-6779096df45c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6048,
"height": 4024,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "iNknvUAC3BY",
"author": "Tianhao Wang",
"description": "雪中一个人站在一辆红色汽车的车顶上",
"color": "#c0d9f3"
},
{
"id": 673,
"thumb": "https://images.unsplash.com/photo-1673881142553-673849892c3e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1673881142553-673849892c3e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6048,
"height": 4024,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "0wmUgIDXFy4",
"author": "Tianhao Wang",
"description": "一个人站在布满星星的天空下被雪覆盖的路上",
"color": "#262640"
},
{
"id": 674,
"thumb": "https://images.unsplash.com/photo-1695982207069-49678b07a4c9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695982207069-49678b07a4c9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6000,
"height": 4000,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "8qIx1JfP1GA",
"author": "David Becker",
"description": "一座红色的小房子坐落在郁郁葱葱的绿色山坡上",
"color": "#404040"
},
{
"id": 675,
"thumb": "https://images.unsplash.com/photo-1696142990758-581061f2801d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696142990758-581061f2801d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3569,
"height": 5353,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "Dn48lk8FyMk",
"author": "Wenhao Ryan",
"description": "夜晚的城市,摩天大楼灯火通明",
"color": "#404059"
},
{
"id": 676,
"thumb": "https://images.unsplash.com/photo-1696317425062-6c7454c1808a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696317425062-6c7454c1808a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4000,
"height": 6000,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "oHqcohDTka0",
"author": "AXP Photography",
"description": "瓦迪拉姆沙漠,约旦。",
"color": "#f3f3f3"
},
{
"id": 677,
"thumb": "https://images.unsplash.com/photo-1696280787336-5abad24b7df8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1696280787336-5abad24b7df8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4770,
"height": 2683,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "Z3LNnjtQtNU",
"author": "Marek Piwnicki",
"description": "“Birds&quot;",
"color": "#f3d98c"
},
{
"id": 678,
"thumb": "https://images.unsplash.com/photo-1692912364084-97b9ae31a8e1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1692912364084-97b9ae31a8e1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6000,
"height": 4000,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "IxqUT4zy89U",
"author": "Ilia Bronskiy",
"description": "一座山倒映在平静的湖水中",
"color": "#d9d9d9"
},
{
"id": 679,
"thumb": "https://images.unsplash.com/photo-1691871095969-bae4e5ff8640?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1691871095969-bae4e5ff8640?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY4NjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5435,
"height": 8153,
"created_time": "2023-10-05T00:28:20.000Z",
"updated_time": "2023-10-05T00:28:20.000Z",
"category": 35,
"original": "QgdIt34lXNA",
"author": "Mauro Lima",
"description": "站在一幢大楼前的人",
"color": "#c08c73"
},
{
"id": 680,
"thumb": "https://images.unsplash.com/photo-1695918433515-2d6be66c02c1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695918433515-2d6be66c02c1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5801,
"height": 3867,
"created_time": "2023-10-05T00:28:46.000Z",
"updated_time": "2023-10-05T00:28:46.000Z",
"category": 35,
"original": "MoKOZql7cpE",
"author": "Adam Davis",
"description": "一个男人站在两块独木舟旁边的岩石上",
"color": "#d9d9f3"
},
{
"id": 681,
"thumb": "https://plus.unsplash.com/premium_photo-1695930107980-efac202e0345?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://plus.unsplash.com/premium_photo-1695930107980-efac202e0345?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3543,
"height": 5314,
"created_time": "2023-10-05T00:28:46.000Z",
"updated_time": "2023-10-05T00:28:46.000Z",
"category": 35,
"original": "K1v6UqQOQnw",
"author": "laura adai",
"description": "去五岛度假",
"color": "#f38c73"
},
{
"id": 682,
"thumb": "https://images.unsplash.com/photo-1681835108931-aea72262dd5c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1681835108931-aea72262dd5c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5202,
"height": 3716,
"created_time": "2023-10-05T00:28:46.000Z",
"updated_time": "2023-10-05T00:28:46.000Z",
"category": 35,
"original": "CMVHlKRLMGI",
"author": "Stas Ostrikov",
"description": "一个女孩在海上的波浪上游泳",
"color": "#f3f3f3"
},
{
"id": 683,
"thumb": "https://images.unsplash.com/photo-1695153374208-1b8382bd6388?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1695153374208-1b8382bd6388?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5472,
"height": 3648,
"created_time": "2023-10-05T00:28:46.000Z",
"updated_time": "2023-10-05T00:28:46.000Z",
"category": 35,
"original": "GsnB-RKgWT8",
"author": "Tim Oun",
"description": "在Aiguille du Midi的登山运动员",
"color": "#d9d9d9"
},
{
"id": 684,
"thumb": "https://images.unsplash.com/photo-1671616215399-1dd96cae9917?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1671616215399-1dd96cae9917?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 5239,
"height": 3889,
"created_time": "2023-10-05T00:28:46.000Z",
"updated_time": "2023-10-05T00:28:46.000Z",
"category": 35,
"original": "wAnb_CyK-1Y",
"author": "Vincenzo De Simone",
"description": "几幢相邻的建筑",
"color": "#f3d9c0"
},
{
"id": 685,
"thumb": "https://images.unsplash.com/photo-1675440941747-3edab9ae2b4a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1675440941747-3edab9ae2b4a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3690,
"height": 4612,
"created_time": "2023-10-05T00:28:46.000Z",
"updated_time": "2023-10-05T00:28:46.000Z",
"category": 35,
"original": "GkoQKCa8NF4",
"author": "Jovan Vasiljevi",
"description": "一个年轻人蹲着,小心翼翼地开枪;被华丽、郁郁葱葱的大自然包围|| Photo by: Jovan Vasiljevi;模特:Jovan Vasiljevi(自画像)",
"color": "#26260c"
},
{
"id": 686,
"thumb": "https://images.unsplash.com/photo-1689457362806-af3a973a3c45?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1689457362806-af3a973a3c45?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 6240,
"height": 4160,
"created_time": "2023-10-05T00:28:46.000Z",
"updated_time": "2023-10-05T00:28:46.000Z",
"category": 35,
"original": "sn7aS4DD9Ro",
"author": "Jovan Vasiljevi",
"description": "阿马尔菲海岸:Jovan Vasiljevi",
"color": "#26260c"
},
{
"id": 687,
"thumb": "https://images.unsplash.com/photo-1690070416537-fb5e0aab54c0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1690070416537-fb5e0aab54c0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 4160,
"height": 5200,
"created_time": "2023-10-05T00:28:46.000Z",
"updated_time": "2023-10-05T00:28:46.000Z",
"category": 35,
"original": "pzeFjX6HoWY",
"author": "Jovan Vasiljevi",
"description": "Jovan Vasiljevi拿着相机站在庞贝遗址(意大利)庞贝是意大利南部坎帕尼亚地区的一个巨大的考古遗址靠近那不勒斯湾的海岸。庞贝曾经是一个繁荣而精致的罗马城市在公元79年维苏威火山灾难性爆发后庞贝被埋在几米厚的火山灰和浮石之下。保存完好的遗址以挖掘出来的街道和房屋废墟为特色游客可以自由探索。",
"color": "#735940"
},
{
"id": 688,
"thumb": "https://images.unsplash.com/photo-1670382393921-516cfd9f3119?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1670382393921-516cfd9f3119?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0MzY5MjZ8&ixlib=rb-4.0.3&q=80&w=400",
"width": 3707,
"height": 5479,
"created_time": "2023-10-05T00:28:46.000Z",
"updated_time": "2023-10-05T00:28:46.000Z",
"category": 35,
"original": "bHiCWmbQL8E",
"author": "Mauro Lima",
"description": "一座城市的钟楼",
"color": "#d9d9f3"
},
{
"id": 689,
"thumb": "https://images.unsplash.com/photo-1670469628385-21e0515dc946?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0ODE1OTB8&ixlib=rb-4.0.3&q=80&w=200",
"url": "https://images.unsplash.com/photo-1670469628385-21e0515dc946?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMjA3fDB8MXx0b3BpY3x8RnpvM3p1T0hONnd8fHx8fDJ8fDE2OTY0ODE1OTB8&ixlib=rb-4.0.3&q=80&w=400",
"width": 2695,
"height": 4043,
"created_time": "2023-10-05T12:53:07.000Z",
"updated_time": "2023-10-05T12:53:07.000Z",
"category": 35,
"original": "Z4mZYFeTxLY",
"author": "Mauro Lima",
"description": "人们在等餐车",
"color": "#260c0c"
}
]

View File

@ -0,0 +1,41 @@
[
{
"id": 886,
"title": "卡通-计划便利贴元素",
"width": 800,
"height": 800,
"type": "image",
"model": "{}",
"thumb": "https://pic.imgdb.cn/item/66b96dddd9c307b7e95c2db6.png",
"url": "https://pic.imgdb.cn/item/66b96dddd9c307b7e95c2db6.png",
"created_time": "2023-08-20T21:46:13.000Z",
"updated_time": "2023-09-15T11:42:14.000Z",
"state": 1
},
{
"id": 885,
"title": "卡通-计划便利贴元素",
"width": 800,
"height": 800,
"type": "image",
"model": "{}",
"thumb": "https://pic.imgdb.cn/item/66b96df2d9c307b7e95c8109.png",
"url": "https://pic.imgdb.cn/item/66b96df2d9c307b7e95c8109.png",
"created_time": "2023-08-20T21:46:12.000Z",
"updated_time": "2023-09-15T11:42:14.000Z",
"state": 1
},
{
"id": 882,
"title": "卡通-计划便利贴元素",
"width": 800,
"height": 800,
"type": "image",
"model": "{}",
"thumb": "https://pic.imgdb.cn/item/66b96e05d9c307b7e95cc86f.png",
"url": "https://pic.imgdb.cn/item/66b96e05d9c307b7e95cc86f.png",
"created_time": "2023-08-20T21:46:07.000Z",
"updated_time": "2023-09-15T11:42:14.000Z",
"state": 1
}
]

View File

@ -0,0 +1,41 @@
[
{
"id": 996,
"title": "SVG-基础形状-多边形",
"width": 747.9,
"height": 747.9,
"type": "svg",
"model": "{\"colors\":[\"#B59683\"]}",
"thumb": "https://pic.imgdb.cn/item/66b96ee7d9c307b7e9604257.png",
"url": "<svg xmlns=\"http://www.w3.org/2000/svg\" data-name=\"图层 1\" viewBox=\"0 0 747.9 747.9\" preserveAspectRatio=\"none\"><path d=\"M747.8 373.9c0 30.9-60.2 53.5-67.8 82s32.7 79.2 17.8 105-78.6 15.7-99.9 37-10.8 84.7-37 99.9-75.5-25.7-105-17.8-51.1 67.8-82 67.8-53.5-60.2-82-67.8-79.2 32.7-105 17.8-15.7-78.6-37-99.9-84.7-10.8-99.9-37 25.7-75.5 17.8-105S0 404.8 0 373.9s60.2-53.5 67.8-82-32.7-79.2-17.8-105 78.6-15.7 99.9-37 10.8-84.7 37-99.9 75.5 25.7 105 17.8S343 0 373.9 0s53.5 60.2 82 67.8 79.2-32.7 105-17.8 15.7 78.6 37 99.9 84.7 10.8 99.9 37-25.7 75.5-17.8 105 67.8 51.1 67.8 82z\" fill=\"{{colors[0]}}\"/></svg>",
"created_time": "2023-08-20T21:51:23.000Z",
"updated_time": "2023-09-15T11:42:14.000Z",
"state": 1
},
{
"id": 994,
"title": "SVG-基础形状-三角形",
"width": 771.2,
"height": 682.2,
"type": "svg",
"model": "{\"colors\":[\"#F3CFD3\"]}",
"thumb": "https://pic.imgdb.cn/item/66b97267d9c307b7e962f070.png",
"url": "<svg xmlns=\"http://www.w3.org/2000/svg\" data-name=\"图层 1\" viewBox=\"0 0 771.2 682.2\" preserveAspectRatio=\"none\"><path d=\"M338.9 27L7.3 601.3c-20.7 35.9 5.2 80.9 46.8 80.9h663.1c41.5 0 67.5-45 46.7-80.9L432.3 27c-20.7-36-72.6-36-93.4 0z\" fill=\"{{colors[0]}}\"/></svg>",
"created_time": "2023-08-20T21:51:21.000Z",
"updated_time": "2023-09-15T11:42:14.000Z",
"state": 1
},
{
"id": 995,
"title": "装饰图形圆形条",
"width": 798.92,
"height": 161,
"type": "svg",
"model": "{\"colors\":[\"#b8a8ffff\"]}",
"thumb": "https://pic.imgdb.cn/item/66b9727dd9c307b7e96303ae.png",
"url": "<svg xmlns=\"http://www.w3.org/2000/svg\" data-name=\"图层 1\" viewBox=\"0 0 1052.3 212.87\"><path d=\"M945.86 0H106.43a106.44 106.44 0 0 0 0 212.87h839.43a106.44 106.44 0 1 0 0-212.87z\" fill=\"{{colors[0]}}\"/></svg>",
"created_time": "2023-08-20T21:51:21.000Z",
"updated_time": "2023-09-15T11:42:14.000Z",
"state": 1
}
]

View File

@ -0,0 +1,4 @@
[
{ "id": 1, "cover": "https://pic.imgdb.cn/item/66b93ff9d9c307b7e92cbb2b.jpg", "title": "示例模板1", "width": 1242, "height": 2208, "state": 1 },
{ "id": 2, "cover": "https://pic.imgdb.cn/item/66b93f95d9c307b7e92c86aa.webp", "title": "示例模板2", "width": 1242, "height": 2208, "state": 1 }
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,111 @@
/*
* @Author: ShawnPhang
* @Date: 2024-05-16 18:25:10
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 16:05:33
*/
import { Request, Response } from 'express'
const fs = require('fs')
const path = require('path')
const axios = require('../utils/http.ts')
const multiparty = require('multiparty')
const { filePath } = require('../configs.ts')
const { checkCreateFolder, randomCode, send } = require('../utils/tools.ts')
const FileUrl = 'http://localhost:7001/static/'
module.exports = {
// design/list 获取模板列表(虚拟)
async getTemplates(req: any, res: Response) {
/**
* @api {get} /design/list
* @apiVersion 1.0.0
* @apiGroup design
*/
const { cate, type } = req.query
const tempPath = type == 1 ? `../mock/components/list/${cate}.json` : '../mock/templates/list.json'
try {
const list = fs.readFileSync(path.resolve(__dirname, tempPath), 'utf8')
send.success(res, { list: JSON.parse(list) })
} catch (error) {}
},
// design/temp 获取模板(虚拟)
async getDetail(req: any, res: Response) {
/**
* @api {get} /design/list
* @apiVersion 1.0.0
* @apiGroup design
*/
const { cate, type, id } = req.query
const dPath = type == 1 ? `../mock/components/detail/${id}.json` : `../mock/templates/poster${id}.json`
try {
const detail = fs.readFileSync(path.resolve(__dirname, dPath), 'utf8')
send.success(res, JSON.parse(detail))
} catch (error) {}
},
// design/material 获取素材(虚拟)
async getMaterial(req: any, res: any) {
/**
* @api {get} /design/material
* @apiVersion 1.0.0
* @apiGroup design
*/
const { cate } = req.query
try {
const detail = fs.readFileSync(path.resolve(__dirname, `../mock/materials/${cate}.json`), 'utf8')
send.success(res, { list: JSON.parse(detail) })
} catch (error) {}
},
// design/imgs 获取照片素材(虚拟)
async getPhotos(req: any, res: any) {
/**
* @api {get} /design/imgs
* @apiVersion 1.0.0
* @apiGroup design
*/
const { cate } = req.query
try {
const detail = fs.readFileSync(path.resolve(__dirname, `../mock/materials/photos/${cate}.json`), 'utf8')
send.success(res, { list: JSON.parse(detail) })
} catch (error) {}
},
// design/edit 保存模板(虚拟)
async saveTemplate(req: any, res: any) {
/**
* @api {post} /design/edit
* @apiVersion 1.0.0
* @apiGroup design
*/
let { id, title, data, width, height, type, cate, tag } = req.body
try {
const isAdd = !id // 是否新增模板
id = id || randomCode(8)
const savePath = path.resolve(__dirname, `../mock/templates/poster${id}.json`)
const jsonData = {
id,
data,
title,
width,
height,
}
fs.writeFileSync(savePath, JSON.stringify(jsonData))
// 生成封面
const size = width > height ? 640 : 320
const fetchScreenshotUrl = `http://localhost:7001/api/screenshots?tempid=${id}&tempType=${type}&width=${width}&height=${height}&type=cover&size=${size}&quality=75`
await axios.get(fetchScreenshotUrl, { responseType: 'arraybuffer' })
// 保存到其他地方可以设置 responseType: 'arraybuffer' 后操作buffer这里只为了得到封面发起请求就可以了
if (isAdd) {
const listVal = fs.readFileSync(path.resolve(__dirname, `../mock/templates/list.json`), 'utf8')
const list = JSON.parse(listVal)
list.unshift({ id, cover: FileUrl + `/${id}-cover.jpg`, title, width, height })
fs.writeFileSync(path.resolve(__dirname, `../mock/templates/list.json`), JSON.stringify(list))
}
send.success(res, { id })
} catch (error) {
console.log(error)
}
},
}
export {}

View File

@ -0,0 +1,60 @@
/*
* @Author: ShawnPhang
* @Date: 2024-05-16 18:25:10
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 18:52:18
*/
import { Request, Response } from 'express'
const multiparty = require('multiparty')
const { filePath } = require('../configs.ts')
const { checkCreateFolder, randomCode, copyFile, send } = require('../utils/tools.ts')
const FileUrl = 'http://localhost:7001/static/'
module.exports = {
// api/file/upload 上传接口
async upload(req: Request, res: Response) {
/**
* @api {post} /api/file/upload
* @apiVersion 1.0.0
* @apiGroup file
*
* @apiParam {File} file
* @apiParam {String} folder
* @apiParam {String} name
*
* @apiSuccess (__组__) {__类型__} __字段名__ __返回字段说明__
*/
const form = new multiparty.Form()
form.parse(req, async function (err: any, fields: any, files: any) {
if (err) {
console.error('上传文件出错!')
return
}
if (files) {
const file = files.file ? files.file[0] : {}
const { size, headers, originalFilename } = file
const fileType = headers['content-type'].split('/')[1]
const Suffix = originalFilename.split('.').pop() || fileType || 'png'
const { folder = '', name = `${randomCode(12)}.${Suffix}` } = fields
const folderPath = `${filePath}${folder ? `${folder}/` : ''}`
checkCreateFolder(folderPath) // 检测对应目录是否存在
const targetPath = `${folderPath}${name}`
copyFile(file.path, targetPath)
.then(() => {
const url = `${FileUrl}${folder ? folder + '/' : ''}${name}`
send.success(res, {
key: `${folder}/${name}`,
url,
})
})
.catch((err: any) => {
console.log('上传异常', err)
})
}
})
},
}
export {}

View File

@ -1,9 +1,9 @@
/*
* @Author: ShawnPhang
* @Date: 2020-07-22 20:13:14
* @Description:
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-16 15:43:29
* @LastEditTime: 2024-08-12 11:03:07
*/
const { saveScreenshot } = require('../utils/download-single.ts')
const uuid = require('../utils/uuid.ts')
@ -13,33 +13,6 @@ const { queueRun, queueList } = require('../utils/node-queue.ts')
const fs = require('fs')
module.exports = {
async getImg(req: any, res: any) {
/**
* @api {get} api/get_img
* @apiVersion 1.0.0
* @apiGroup screenShot
*
* @apiParam {String|Number} id () id
* @apiParam {String} type , file源文件cover封面图
*/
const isDev = process.env.NODE_ENV === 'development'
let { id, type = 'file' } = req.query
const path = filePath + `${id}-screenshot.png`
const thumbPath = type === 'cover' ? filePath + `${id}-cover.jpg` : null
if (id) {
try {
fs.statSync(path)
res.setHeader('Content-Type', 'image/jpg')
type === 'file' ? res.sendFile(path) : res.sendFile(thumbPath)
} catch (error) {
res.json({ code: 500, msg: '请求图片不存在' })
}
} else {
res.json({ code: 500, msg: '缺少参数,请检查' })
}
},
async screenshots(req: any, res: any) {
/**
* @api {get} api/screenshots
@ -58,6 +31,7 @@ module.exports = {
* @apiParam {String|Number} index ,
*/
let { id, tempid, tempType, width, height, screenshot_url, type = 'file', size, quality, index = 0 } = req.query
id == 'undefined' && (id = null)
const url = (screenshot_url || drawLink) + `${id ? '?id=' : '?tempid='}`
id = id || tempid
const path = filePath + `${id}-screenshot.png`
@ -68,7 +42,7 @@ module.exports = {
res.json({ code: 200, msg: '服务器表示顶不住啊,等等再来吧~' })
return
}
const targetUrl = url + id + `${tempType?'&tempType='+tempType:''}` + `&index=${index}`
const targetUrl = url + id + `${tempType ? '&tempType=' + tempType : ''}` + `&index=${index}`
queueRun(saveScreenshot, targetUrl, { width, height, path, thumbPath, size, quality })
.then(() => {
res.setHeader('Content-Type', 'image/jpg')

View File

@ -0,0 +1,28 @@
/*
* @Author: ShawnPhang
* @Date: 2024-05-16 18:25:10
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 06:25:23
*/
import { Request, Response } from 'express'
const multiparty = require('multiparty')
const { filePath } = require('../configs.ts')
const { checkCreateFolder, filesReader, send } = require('../utils/tools.ts')
const FileUrl = 'http://localhost:7001/static/'
module.exports = {
// design/user/image 获取用户上传列表(虚拟)
async getUserImages(req: Request, res: Response) {
/**
* @api {post} /design/user/image
* @apiVersion 1.0.0
* @apiGroup user
*/
const list = await filesReader('user')
send.success(res, { list })
},
}
export {}

78
service/src/utils/fs.ts Normal file
View File

@ -0,0 +1,78 @@
/*
* @Author: ShawnPhang
* @Date: 2024-05-28 17:10:51
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 06:29:58
*/
import fs from 'fs'
const path = require('path')
const imageSize = require('image-size')
const { filePath: StaticPath } = require('../configs.ts')
const FileUrl = 'http://localhost:7001/static/'
module.exports = {
copyFile(sourceFile: string, destinationFile: string): Promise<void> {
return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(sourceFile)
const writeStream = fs.createWriteStream(destinationFile)
readStream.on('error', (err: any) => {
reject(err)
})
writeStream.on('error', (err: any) => {
reject(err)
})
writeStream.on('finish', () => {
resolve()
})
readStream.pipe(writeStream)
})
},
// 读取目录
filesReader(directoryPath: string) {
return new Promise((resolve) => {
try {
const files = fs.readdirSync(StaticPath + directoryPath)
const filesArray: any = []
files.forEach((file) => {
const filePath = path.join(directoryPath, file)
// const stats = fs.statSync(filePath);
const { width, height } = imageSize(StaticPath + filePath)
if (file !== '.DS_Store') {
const fileInfo = {
width,
height,
// filename: file,
// link: FileUrl + directoryPath,
url: `${FileUrl + directoryPath}/${file}`,
// filepath: StaticPath + filePath
// size: stats.size, // 文件大小
// modified: stats.mtime // 最后修改时间
}
filesArray.push(fileInfo)
}
})
// JSON.stringify(filesArray, null, 2)
resolve(filesArray)
} catch (err) {
console.error('Error reading directory:', err)
}
})
},
// 读取文件
readFile(directoryPath: string) {
return new Promise((resolve) => {
try {
resolve(fs.readFileSync(StaticPath + directoryPath, 'utf8'))
} catch (err) {
console.error('Error reading file:', err)
}
})
},
}
export {}

19
service/src/utils/http.ts Normal file
View File

@ -0,0 +1,19 @@
/*
* @Author: ShawnPhang
* @Date: 2022-01-25 17:02:02
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 13:59:34
*/
const axios = require('axios')
const httpRequest = axios.create({
maxContentLength: Infinity,
maxBodyLength: Infinity,
})
httpRequest.interceptors.response.use((config: any) => {
return config.data
})
module.exports = httpRequest

View File

@ -0,0 +1,84 @@
/*
* @Author: ShawnPhang
* @Date: 2024-05-11 02:26:55
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 13:48:40
*/
const fs = require('fs')
const path = require('path')
const fsFunc = require('./fs.ts')
module.exports = {
send: {
success: (res: any, result: any, msg: string = 'ok') => {
res.json({
code: 200,
msg,
result: result || undefined,
})
},
error: (res: any, msg: string = 'error') => {
res.json({
code: 400,
msg,
})
},
},
isNumber: (value: any) => {
return typeof value === 'number' && !isNaN(value)
},
buildTree: (data: any[]) => {},
groupBy: (array: any[], property: any) => {},
// 检测目录并创建目录(支持深层级)
checkCreateFolder: (folder: string) => {
try {
const pathArr = splitPath(folder)
let _path = ''
for (let i = 0; i < pathArr.length; i++) {
if (pathArr[i]) {
_path += `/${pathArr[i]}`
!fs.existsSync(_path) && fs.mkdirSync(_path)
}
}
} catch (e) {}
},
// 检测文件
checkCreateFile: (filePath: string) => {
try {
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, '')
}
} catch (e) {
fs.writeFileSync(filePath, '')
}
},
// 生成随机码
randomCode: (length = 5) => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let result = ''
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * chars.length)
result += chars[randomIndex]
}
return result
},
// 取数组差集
findDifference: (a: any, b: any) => {
return a.concat(b).filter((v: any) => !a.includes(v) || !b.includes(v))
},
...fsFunc,
}
/**
*
* @param dirPath
* @returns Array
*/
function splitPath(dirPath: string) {
const normalizedPath = path.normalize(dirPath)
const separator = path.sep
return normalizedPath.split(separator)
}
export {}

2190
service/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,10 @@
* @Date: 2021-08-27 14:42:15
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-12-11 11:40:47
* @LastEditTime: 2024-08-11 19:27:41
*/
import fetch from '@/utils/axios'
import _config from '@/config'
import { IGetTempListData } from './home'
// 获取素材分类:
@ -130,3 +131,20 @@ type TAddMyPhotoParam = {
// 添加图片
export const addMyPhoto = (params: TAddMyPhotoParam) => fetch<void>('design/user/add_image', params)
// 上传接口
export const upload = ({ file, folder = 'user' }: any, cb: Function) => {
const formData = new FormData()
formData.append('file', file)
formData.append('folder', folder)
const extra = {
responseType: 'application/json',
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(`${_config.SCREEN_URL}/api/file/upload`, formData, 'post', {}, extra)
}

View File

@ -8,11 +8,16 @@
@height2: 54px;
@canvasBG: #f8f8f8;
@pageBG: #f0f2f5;
@canvasDeepBG: #000;
.page-design-bg-color {
// background-color: #4b678c;
// background-color: #4682b4;
background-color: @canvasDeepBG;
}
.page-bg-color {
background-color: @pageBG;
}
#page-design-index {
display: flex;
flex-direction: column;

View File

@ -2,52 +2,28 @@
* @Author: ShawnPhang
* @Date: 2021-09-30 15:52:59
* @Description:
* @LastEditors: ShawnPhang <site: book.palxp.com>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-02 11:50:00
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 17:01:59
*/
type TCallBack = (progress: number, xhr: XMLHttpRequest) => void
export default (src: string, cb: TCallBack) => {
export default (src: string, cb: TCallBack, fileName?: string) => {
return new Promise<void>((resolve) => {
// const image = new Image()
// // 解决跨域 Canvas 污染问题
// image.setAttribute('crossOrigin', 'anonymous')
// image.onload = function() {
// const canvas = document.createElement('canvas')
// canvas.width = image.width
// canvas.height = image.height
// const context = canvas.getContext('2d')
// context?.drawImage(image, 0, 0, image.width, image.height)
// const url = canvas.toDataURL('image/jpg')
// const a = document.createElement('a')
// const event = new MouseEvent('click')
// a.download = String(new Date().getTime())
// a.href = url
// // 触发a的单击事件
// a.dispatchEvent(event)
// resolve()
// }
fetchImageDataFromUrl(src, (progress: number, xhr: XMLHttpRequest) => {
cb(progress, xhr)
}).then((res) => {
}).then((res: any) => {
const reader = new FileReader()
reader.onload = function (event) {
const txt = event?.target?.result as string
// image.src = txt
const a = document.createElement('a')
const mE = new MouseEvent('click')
// TODO: 部分浏览器会丢失后缀,所以补上
a.download = String(new Date().getTime()) + '.png'
a.href = txt
const suffix = res.type ? res.type.split('/')[1] : 'png'
const randomName = String(new Date().getTime()) + `.${suffix || 'png'}`
a.download = fileName || randomName
a.href = event?.target?.result as string
// 触发a的单击事件
a.dispatchEvent(mE)
resolve()
resolve(res)
}
if (!res) {
resolve()

View File

@ -3,7 +3,7 @@
* @Date: 2022-01-08 09:43:37
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-10-13 01:30:33
* @LastEditTime: 2024-08-12 10:33:36
*/
// import { isSupportFontFamily, blob2Base64 } from './utils'
import { TGetFontItemData, getFonts } from '@/api/material'
@ -11,7 +11,7 @@ import { TGetFontItemData, getFonts } from '@/api/material'
const nowVersion = '2' // 当前字体文件版本更新,将刷新前端缓存
/** 字体item类型 */
export type TFontItemData = { url: string } & Omit<TGetFontItemData, "woff">
export type TFontItemData = { url: string } & Omit<TGetFontItemData, 'woff'>
const fontList: TFontItemData[] = []
// const download: any = {}
@ -27,11 +27,27 @@ export const useFontStore = {
}
if (this.list.length === 0) {
const res = await getFonts({ pageSize: 400 })
// TODO: 模拟
const res = {
list: [
{
id: 543,
alias: '站酷快乐体',
preview: '',
ttf: null,
woff: 'https://lib.baomitu.com/fonts/zcool-kuaile/zcool-kuaile-regular.woff2',
value: 'zcool-kuaile-regular',
font_family: '',
size: 0,
lang: 'zh',
woff_size: 0,
},
],
}
this.list.unshift(
...res.list.map((x) => {
const { id, alias, oid, value, preview, woff, lang } = x
return { id, oid, value, preview, alias, url: woff, lang }
const { id, alias, value, preview, woff, lang } = x
return { id, oid: 0, value, preview, alias, url: woff, lang }
}),
)
localStorage.setItem('FONTS', JSON.stringify(this.list))

View File

@ -6,13 +6,14 @@
* @Date: 2024-03-03 19:00:00
-->
<template>
<el-dialog v-model="state.show" title="AI 智能抠图" align-center width="650" @close="handleClose">
<el-dialog v-model="state.show" title="AI 抠图(模拟演示)" align-center width="650" @close="handleClose">
<uploader v-if="!state.rawImage" :hold="true" :drag="true" :multiple="true" class="uploader" @load="handleUploaderLoad">
<div class="uploader__box">
<upload-filled style="width: 64px; height: 64px" />
<div class="el-upload__text">在此拖入或选择<em>上传图片</em></div>
<!-- <div class="el-upload__text">在此拖入或选择<em>上传图片</em></div> -->
<div class="el-upload__text">自动抠图目前依赖后端服务需自行部署</div>
</div>
<div class="el-upload__tip">服务器带宽小为了更好的体验请上传 2M 内的图片</div>
<div class="el-upload__tip el-upload__text"><em>体验前端效果演示以及修补编辑器随便上传一张图片即可触发</em></div>
</uploader>
<el-progress v-if="!state.cutImage && state.progressText" :percentage="state.progress">
<el-button text>
@ -104,7 +105,8 @@ defineExpose({
const handleUploaderLoad = (file: File) => {
selectImageFile(state as TImageCutoutState, raw, file, (result, name) => {
fileName = name
const resultImage = URL.createObjectURL(result)
// TODO:
const resultImage = 'https://pic.imgdb.cn/item/6522253ec458853aefb0b013.webp' // URL.createObjectURL(result)
state.rawImage && (state.cutImage = resultImage)
requestAnimationFrame(run)
})

View File

@ -2,51 +2,47 @@
* @Author: Jeremy Yu
* @Date: 2024-03-03 19:00:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @Date: 2024-03-03 19:00:00
*/
import Qiniu from '@/common/methods/QiNiu'
import { TCommonUploadCb, TUploadErrorResult } from "@/api/ai"
import { TImageCutoutState } from "./index.vue"
import api from "@/api"
import { TCommonUploadCb, TUploadErrorResult } from '@/api/ai'
import { TImageCutoutState } from './index.vue'
import api from '@/api'
import { getImage } from '@/common/methods/getImgDetail'
import _config from '@/config'
import { Ref } from 'vue'
/** 选择图片 */
export const selectImageFile = async (
state: TImageCutoutState,
raw: Ref<HTMLElement | null>,
file: File,
successCb?: (result: MediaSource, fileName: string) => void,
uploadCb?: TCommonUploadCb,
) => {
if (file.size > 1024 * 1024 * 2) {
alert('上传图片超出限制')
return false
}
export const selectImageFile = async (state: TImageCutoutState, raw: Ref<HTMLElement | null>, file: File, successCb?: (result: MediaSource, fileName: string) => void, uploadCb?: TCommonUploadCb) => {
// if (file.size > 1024 * 1024 * 2) {
// alert('上传图片超出限制')
// return false
// }
if (!raw.value) return
// 显示选择的图片
raw.value.addEventListener('load', () => {
state.offsetWidth = (raw.value as HTMLElement).offsetWidth
})
state.rawImage = URL.createObjectURL(file)
// TODO: 模拟演示
state.rawImage = 'https://pic.imgdb.cn/item/65222530c458853aefb0adeb.webp' // URL.createObjectURL(file)
// 返回抠图结果
const result = await api.ai.upload(file, (up: number, dp: number) => {
uploadCb && uploadCb(up, dp)
if (dp) {
state.progressText = dp === 100 ? '' : '导入中..'
state.progress = dp
} else {
state.progressText = up < 100 ? '上传中..' : '正在处理,请稍候..'
state.progress = up < 100 ? up : 0
}
})
if (typeof result == 'object' && (result as TUploadErrorResult).type !== 'application/json') {
successCb && successCb(result as MediaSource, file.name)
} else alert('服务器繁忙,请稍等下重新尝试~')
// const result = await api.ai.upload(file, (up: number, dp: number) => {
// uploadCb && uploadCb(up, dp)
// if (dp) {
// state.progressText = dp === 100 ? '' : '导入中..'
// state.progress = dp
// } else {
// state.progressText = up < 100 ? '上传中..' : '正在处理,请稍候..'
// state.progress = up < 100 ? up : 0
// }
// })
// if (typeof result == 'object' && (result as TUploadErrorResult).type !== 'application/json') {
// successCb && successCb(result as MediaSource, file.name)
// } else alert('服务器繁忙,请稍等下重新尝试~')
successCb('', file.name)
}
export async function uploadCutPhotoToCloud(cutImage: string) {
@ -61,7 +57,7 @@ export async function uploadCutPhotoToCloud(cutImage: string) {
const url = _config.IMG_URL + result.key
await api.material.addMyPhoto({ width, height, url })
return url
} catch(e) {
} catch (e) {
console.error(`upload cut file error: msg: ${e}`)
return ''
}

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-04 11:46:39
* @Description: 原版movable插件
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-06 14:56:35
* @LastEditTime: 2024-08-12 09:24:51
-->
<template>
<div id="empty" class="moveable__remove-item zk-moveable-style"></div>
@ -345,10 +345,6 @@ onMounted(() => {
// })
holdPosition = null // important
setTimeout(() => {
historyStore.pushHistory()
// store.dispatch("pushHistory")
}, 100)
}
})
// .on('keyUp', (e) => {

View File

@ -2,7 +2,7 @@
* @Author: ShawnPhang
* @Date: 2021-08-01 11:12:17
* @Description: 前端出图 - 用于封面
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @Date: 2024-03-04 18:50:00
-->
<template>
@ -13,8 +13,9 @@
import html2canvas from 'html2canvas'
import Qiniu from '@/common/methods/QiNiu'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters'
import { storeToRefs } from 'pinia';
import { useCanvasStore, useWidgetStore } from '@/store';
import { storeToRefs } from 'pinia'
import { useCanvasStore, useWidgetStore } from '@/store'
import FontFaceObserver from 'fontfaceobserver'
// const { dZoom } = useSetupMapGetters(['dZoom'])
@ -22,15 +23,14 @@ const canvasStore = useCanvasStore()
const widgetStore = useWidgetStore()
const { dZoom } = storeToRefs(canvasStore)
// props: ['modelValue'],
// props: ['modelValue'],
// emits: ['update:modelValue'],
async function createCover(cb: any) {
const nowZoom = dZoom.value
//
widgetStore.selectWidget({
uuid: '-1'
uuid: '-1',
})
// store.dispatch('selectWidget', {
// uuid: '-1',
@ -66,8 +66,46 @@ async function createCover(cb: any) {
}, 10)
}
async function createPoster() {
await checkFonts() //
const fonts = document.fonts
const opts = {
backgroundColor: null, //
useCORS: true,
scale: 100 / dZoom.value, // * window.devicePixelRatio
onclone: (document: any) => fonts.forEach((font) => document.fonts.add(font)),
}
// const style = document.createElement('style')
// document.head.appendChild(style)
// style.sheet?.insertRule('body > img { display: initial; }')
return new Promise((resolve) => {
const clonePage = document.getElementById('page-design-canvas')?.cloneNode(true) as HTMLElement
if (!clonePage) return
clonePage.setAttribute('id', 'clone-page')
document.body.appendChild(clonePage)
html2canvas(clonePage, opts).then((canvas) => {
canvas.toBlob(async (blob) => resolve({ blob }), `image/png`)
clonePage.remove()
})
})
}
//
async function checkFonts() {
const widgets = widgetStore.getWidgets()
const fontLoaders: Promise<void>[] = []
widgets.forEach((item: any) => {
if (item.fontClass && item.fontClass.value) {
const loader = new FontFaceObserver(item.fontClass.value)
fontLoaders.push(loader.load(null, 120000)) //
}
})
await Promise.all(fontLoaders)
}
defineExpose({
createCover
createCover,
createPoster,
})
</script>

View File

@ -3,13 +3,13 @@
* @Date: 2024-03-17 16:10:21
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-03 12:25:15
* @LastEditTime: 2024-08-11 18:42:09
-->
<template>
<div v-if="percent" v-show="!hide" class="mask">
<div class="content">
<div class="tool">
<div v-show="percent < 100" class="backstage" @click="close"><iconSell width="18" /> <span style="margin-left: 0.4rem">后台下载</span></div>
<div v-show="percent < 100" class="backstage" @click="close"><span style="margin-left: 0.4rem">后台下载</span></div>
<iconClose v-show="percent >= 100" class="backstage" @click="cancel" width="20" />
</div>
<div class="text">{{ text }}</div>
@ -26,8 +26,8 @@
<script lang="ts" setup>
import { watch, ref } from 'vue'
import { ElProgress } from 'element-plus'
import { Close as iconClose, Sell as iconSell } from '@element-plus/icons-vue'
import toolTip from '@/components/common/PopoverTip.vue'
import { Close as iconClose } from '@element-plus/icons-vue'
// import toolTip from '@/components/common/PopoverTip.vue'
type TProps = {
percent: number

View File

@ -16,7 +16,8 @@
<script lang="ts" setup>
import { onMounted, nextTick, withDefaults } from 'vue'
import { ElUpload, UploadRequestOptions } from 'element-plus'
import Qiniu from '@/common/methods/QiNiu'
// import Qiniu from '@/common/methods/QiNiu'
import api from '@/api'
import { getImage } from '@/common/methods/getImgDetail'
import _config from '@/config'
import useNotification from '@/common/methods/notification'
@ -32,11 +33,11 @@ export type TUploadDoneData = {
url: string
}
type TQiNiuUploadReturn = { hash: string, key: string }
type TQiNiuUploadReturn = { hash: string; key: string }
type TProps = {
modelValue?: TModelData
options?: { bucket: string, prePath: string }
options?: { bucket: string; prePath: string }
hold?: boolean
}
@ -49,7 +50,7 @@ type TEmits = {
const props = withDefaults(defineProps<TProps>(), {
modelValue: () => ({}),
options: () => ({ bucket: 'xp-design', prePath: 'user' }),
hold: false
hold: false,
})
const emit = defineEmits<TEmits>()
@ -63,15 +64,15 @@ let count: number = 0 // 当前上传总数
let tempSimpleRes: TQiNiuUploadReturn | null //
onMounted(async () => {
await nextTick()
setTimeout(() => {
//
const link_element = document.createElement('script')
link_element.setAttribute('src', _config.QINIUYUN_PLUGIN)
document.head.appendChild(link_element)
}, 1000)
})
// onMounted(async () => {
// await nextTick()
// setTimeout(() => {
// //
// const link_element = document.createElement('script')
// link_element.setAttribute('src', _config.QINIUYUN_PLUGIN)
// document.head.appendChild(link_element)
// }, 1000)
// })
const upload = async ({ file }: UploadRequestOptions) => {
if (props.hold) {
@ -93,10 +94,9 @@ const uploadQueue = async () => {
if (file) {
if (file.size <= 1024 * 1024) {
tempSimpleRes = await qiNiuUpload(file) //
console.log("tempSimpleRes", tempSimpleRes)
const { width, height } = await getImage(file)
useNotification('上传成功', '公共测试账户,上传请注意保护隐私哦!', { position: 'bottom-left' })
emit('done', { width, height, url: _config.IMG_URL + tempSimpleRes?.key }) //
useNotification('上传成功', '', { position: 'bottom-left' })
emit('done', { width, height, url: tempSimpleRes?.url }) //
} else useNotification('爱护小水管', '请上传小于 1M 的图片哦!', { type: 'error', position: 'bottom-left' })
uploading = false
handleRemove() //
@ -120,9 +120,12 @@ const qiNiuUpload = async (file: File): Promise<null | TQiNiuUploadReturn> => {
emit('load', file)
resolve(null)
} else {
const result = await Qiniu.upload(file, props.options, (res: Type.Object) => {
updatePercent(res.total.percent)
const result = await api.material.upload({ file }, (up: any, dp: any) => {
console.log(up, dp)
})
// const result = await Qiniu.upload(file, props.options, (res: Type.Object) => {
// updatePercent(res.total.percent)
// })
resolve(result)
}
})

View File

@ -158,8 +158,7 @@ function changeValue() {
function finish(key: keyof TPageState, value: string | number) {
pageStore.updatePageData({
key: key,
value: value,
pushHistory: true,
value: value
})
}
async function uploadImgDone(img: TUploadDoneData) {
@ -174,8 +173,7 @@ async function deleteBg() {
_localTempBG = null
pageStore.updatePageData({
key: 'backgroundImage',
value: '',
pushHistory: true,
value: ''
})
await nextTick()
state.mode = state.modes[1]

View File

@ -3,7 +3,7 @@
* @Date: 2022-03-07 17:25:19
* @Description: 图层组件
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-11-24 11:39:02
* @LastEditTime: 2024-08-12 09:25:15
-->
<template>
<ul class="widget-list">
@ -141,8 +141,7 @@ export default defineComponent({
widgetStore.updateWidgetData({
uuid: item.uuid,
key: 'lock',
value: typeof item.lock === 'undefined' ? true : !item.lock,
pushHistory: false,
value: typeof item.lock === 'undefined' ? true : !item.lock
})
// store.dispatch('updateWidgetData', {
// uuid: item.uuid,

View File

@ -69,7 +69,6 @@ function alignAction(item: TIconItemSelectData) {
group,
})
});
historyStore.pushHistory()
})
}
function layerChange(newLayer: TdWidgetData[]) {

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-27 15:16:07
* @Description: 背景图
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-09 22:29:51
* @LastEditTime: 2024-08-12 09:49:35
-->
<template>
<div class="wrap">
@ -100,7 +100,7 @@ const load = async (init: boolean = false) => {
pageOptions.page = 1
}
await api.material.getImagesList({ cate: 16, page: pageOptions.page }).then(({ list }) => {
await api.material.getImagesList({ cate: 3, page: pageOptions.page }).then(({ list }) => {
if (list.length > 0) {
state.bgList.push(...list)
} else {
@ -120,8 +120,7 @@ function setBGcolor(color: string) {
})
pageStore.updatePageData({
key: 'backgroundColor',
value: color,
pushHistory: true
value: color
})
widgetStore.selectWidget({
uuid: '-1'
@ -139,8 +138,7 @@ async function selectItem(item: TGetImageListResult) {
})
pageStore.updatePageData({
key: 'backgroundImage',
value: item.url,
pushHistory: true,
value: item.url
})
widgetStore.selectWidget({
uuid: '-1'

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-27 15:16:07
* @Description: 素材列表主要用于文字组合列表
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-09 22:30:10
* @LastEditTime: 2024-08-14 18:49:06
-->
<template>
<div class="wrap">
@ -18,13 +18,7 @@
<classHeader v-show="!state.currentCategory" :types="state.types" @select="selectTypes">
<template v-slot="{ index }">
<div class="list-wrap">
<div
v-for="(item, i) in state.showList[index]" :key="i + 'sl'"
draggable="false"
@mousedown="dragStart($event, item)" @mousemove="mousemove"
@mouseup="mouseup" @click.stop="selectItem(item)"
@dragstart="dragStart($event, item)"
>
<div v-for="(item, i) in state.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>
@ -34,13 +28,7 @@
<ul v-if="state.currentCategory" v-infinite-scroll="load" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
<classHeader :is-back="true" @back="back">{{ state.currentCategory.name }}</classHeader>
<el-space fill wrap :fillRatio="30" direction="horizontal" class="list">
<div
v-for="(item, i) in state.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)"
>
<div v-for="(item, i) in state.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" lazy loading="lazy" />
<!-- </edit-model> -->
@ -95,13 +83,15 @@ const pageOptions = { type: 1, page: 0, pageSize: 20 }
onMounted(async () => {
if (state.types.length <= 0) {
const types = await api.material.getKinds({ type: 3 })
state.types = types
for (const iterator of types) {
// const types = await api.material.getKinds({ type: 3 })
state.types = [
{ cate: 'text', name: '高级特效文字' },
{ cate: 'comp', name: '示例组合模板' },
]
for (const iterator of state.types) {
const { list } = await api.home.getCompList({
cate: iterator.id,
type: 1,
pageSize: 3,
cate: iterator.cate,
})
state.showList.push(list)
}
@ -202,7 +192,7 @@ const selectItem = async (item: TGetCompListResult) => {
}
// store.commit('setShowMoveable', false) //
controlStore.setShowMoveable(false) //
tempDetail = tempDetail || (await getCompDetail({ id: item.id, type: 1 }))
// let group = JSON.parse(tempDetail.data)
const group: any = await getComponentsData(tempDetail.data)
@ -234,10 +224,11 @@ function getCompDetail(params: TGetTempDetail): Promise<TTempDetail> {
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 //
})
} else
api.home.getTempDetail(params).then((res: any) => {
resolve(res)
compsCache[params.id] = res //
})
})
}

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-27 15:16:07
* @Description: 素材列表
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-02-29 16:49:59
* @LastEditTime: 2024-08-14 18:48:34
-->
<template>
<div class="wrap">
@ -62,7 +62,7 @@ type TState = {
sub: []
list: TGetListData[]
currentType: Number
currentCheck:number
currentCheck: number
colors: string[]
currentCategory: TCurrentCategory | null
types: []
@ -83,7 +83,6 @@ const props = defineProps<TProps>()
const colors = ['#f8704b', '#5b89ff', '#2cc4cc', '#a8ba73', '#f8704b']
const controlStore = useControlStore()
const widgetStore = useWidgetStore()
@ -105,12 +104,15 @@ const pageOptions = { page: 0, pageSize: 20 }
onMounted(async () => {
if (state.types.length <= 0) {
const types = await api.material.getKinds({ type: 2 })
state.types = types
for (const iterator of types) {
// const types = await api.material.getKinds({ type: 2 })
state.types = [
{ cate: 'png', name: '贴纸,图片类型' },
{ cate: 'svg', name: 'SVG矢量元素可编辑' },
{ cate: 'mask', name: '容器Mask图形遮罩' },
]
for (const iterator of state.types) {
const { list } = await api.material.getList({
cate: iterator.id,
pageSize: 3,
cate: iterator.cate,
})
state.showList.push(list)
}
@ -182,7 +184,6 @@ defineExpose({
mousemove,
})
// computed: {
// ...mapGetters(['dPage']),
// }

View File

@ -3,7 +3,7 @@
* @Date: 2022-02-11 18:48:23
* @Description: 照片图库 Form:Unsplash无版权图片
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-03-19 10:04:39
* @LastEditTime: 2024-08-14 18:50:09
-->
<template>
<div class="wrap">
@ -42,12 +42,12 @@ type TProps = {
}
type TState = {
recommendImgList: TGetImageListResult[],
recommendImgList: TGetImageListResult[]
loadDone: boolean
page: number
currentCategory: TCurrentCategory | null,
types: [],
showList: TGetImageListResult[][],
currentCategory: TCurrentCategory | null
types: []
showList: TGetImageListResult[][]
}
type TCurrentCategory = {
@ -57,7 +57,6 @@ type TCurrentCategory = {
const props = defineProps<TProps>()
const controlStore = useControlStore()
const widgetStore = useWidgetStore()
@ -74,9 +73,12 @@ let loading = false
onMounted(async () => {
if (state.types.length <= 0) {
const types = await api.material.getKinds({ type: 4 })
state.types = types
for (const iterator of types) {
// const types = await api.material.getKinds({ type: 4 })
state.types = [
{ id: 1, name: '照片列表,自适应布局' },
{ id: 2, name: '照片列表,自适应布局' },
]
for (const iterator of state.types) {
const { list } = await api.material.getImagesList({ cate: iterator.id, pageSize: 2 })
state.showList.push(list)
}
@ -85,7 +87,7 @@ onMounted(async () => {
const selectImg = async (index: number, list: TGetImageListResult[]) => {
const item = list ? list[index] : state.recommendImgList[index]
// store.commit('setShowMoveable', false) //
controlStore.setShowMoveable(false) //

View File

@ -8,10 +8,13 @@
<template>
<div class="wrap">
<search-header v-model="state.searchKeyword" @change="cateChange" />
<el-divider v-show="state.title" style="margin-top: 1.7rem" content-position="left">
<span style="font-weight: bold">{{ state.title }}</span>
</el-divider>
<el-button class="upload-psd" plain type="primary" @click="openPSD">导入 PSD 创建模板</el-button>
<ul ref="listRef" v-infinite-scroll="load" class="infinite-list" :infinite-scroll-distance="150" style="overflow: auto">
<img-water-fall :listData="state.list" @select="selectItem" />
<div v-show="state.loading" class="loading"><i class="el-icon-loading"></i> 拼命加载中</div>
@ -64,7 +67,7 @@ const state = reactive<TState>({
loading: false,
loadDone: false,
list: [],
title: '推荐模板',
title: '示例模板',
searchKeyword: '',
})
@ -122,7 +125,7 @@ let hideReplacePrompt: any = localStorage.getItem('hide_replace_prompt')
async function selectItem(item: IGetTempListData) {
controlStore.setShowMoveable(false) //
if (!hideReplacePrompt && dHistoryParams.value.length > 0) {
const doNotPrompt = await useConfirm('添加到作品', '模板内容将替换页面内容', 'warning', {confirmButtonText: '知道了',cancelButtonText: '不再提示'})
const doNotPrompt = await useConfirm('添加到作品', '模板内容将替换页面内容', 'warning', { confirmButtonText: '知道了', cancelButtonText: '不再提示' })
if (!doNotPrompt) {
localStorage.setItem('hide_replace_prompt', '1')
hideReplacePrompt = true
@ -139,9 +142,15 @@ async function selectItem(item: IGetTempListData) {
} else {
result = JSON.parse(item.data)
}
const { page, widgets } = result
pageStore.setDPage(page)
widgetStore.setTemplate(widgets)
if (Array.isArray(result)) {
const { global, layers } = result[0]
pageStore.setDPage(global)
widgetStore.setTemplate(layers)
} else {
const { page, widgets } = result
pageStore.setDPage(page)
widgetStore.setTemplate(widgets)
}
setTimeout(() => {
forceStore.setZoomScreenChange()
}, 300)
@ -155,6 +164,10 @@ function setTempId(tempId: number | string) {
router.push({ path: '/home', query: { tempid: tempId, id }, replace: true })
}
const openPSD = () => {
window.open(router.resolve('/psd').href, '_blank')
}
defineExpose({
load,
cateChange,
@ -211,4 +224,9 @@ defineExpose({
font-size: 14px;
color: #999;
}
.upload-psd {
margin: 0 1rem;
width: calc(100% - 2rem);
}
</style>

View File

@ -3,7 +3,7 @@
* @Date: 2022-02-13 22:18:35
* @Description: 我的
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-03 21:00:26
* @LastEditTime: 2024-08-12 09:32:00
-->
<template>
<div class="wrap">
@ -15,14 +15,9 @@
<uploader v-model="state.percent" class="upload" @done="uploadDone">
<el-button class="upload-btn" plain><i class="iconfont icon-upload" /> 上传图片</el-button>
</uploader>
<el-button class="upload-btn upload-psd" plain type="primary" @click="openPSD">导入 PSD</el-button>
<el-button disabled class="upload-btn upload-psd" plain type="primary" @click="openPSD">导入 PSD</el-button>
<div style="margin: 1rem; height: 100vh">
<photo-list
ref="imgListRef"
:edit="state.editOptions.photo" :isDone="state.isDone"
:listData="state.imgList"
@load="load" @drag="dragStart" @select="selectImg"
/>
<photo-list ref="imgListRef" :edit="state.editOptions.photo" :isDone="state.isDone" :listData="state.imgList" @load="load" @drag="dragStart" @select="selectImg" />
</div>
</div>
<div v-show="state.tabActiveName === 'design'" class="wrap">
@ -60,20 +55,19 @@ type TProps = {
}
type TState = {
prePath: string,
percent: { num: number }, //
imgList: IGetTempListData[],
designList: IGetTempListData[],
isDone: boolean,
editOptions: Record<string, any>,
tabActiveName: string,
prePath: string
percent: { num: number } //
imgList: IGetTempListData[]
designList: IGetTempListData[]
isDone: boolean
editOptions: Record<string, any>
tabActiveName: string
}
const props = defineProps<TProps>()
const router = useRouter()
const controlStore = useControlStore()
const widgetStore = useWidgetStore()
@ -107,7 +101,6 @@ const load = (init?: boolean) => {
loading = true
page += 1
api.material.getMyPhoto({ page }).then(({ list }) => {
if (list.length <= 0) {
state.isDone = true
} else {
@ -166,7 +159,7 @@ onMounted(() => {
const selectImg = async (index: number) => {
const item = state.imgList[index]
// store.commit('setShowMoveable', false) //
controlStore.setShowMoveable(false) //
@ -189,7 +182,6 @@ type controlImgParam = {
}
const deleteImg = async ({ i, item }: controlImgParam) => {
// store.commit('setShowMoveable', false) //
controlStore.setShowMoveable(false) //
@ -208,9 +200,9 @@ const deleteWorks = async ({ i, item }: controlImgParam) => {
if (isPass) {
await api.material.deleteMyWorks({ id: item.id })
setTimeout(() => {
router.push({ path: '/home', query: { }, replace: true })
router.push({ path: '/home', query: {}, replace: true })
loadDesign(true)
}, 300);
}, 300)
}
}
@ -226,7 +218,7 @@ state.editOptions = {
name: '删除',
fn: deleteWorks,
},
]
],
}
const dragStart = (index: number) => {
@ -234,10 +226,15 @@ const dragStart = (index: number) => {
widgetStore.setSelectItem({ data: { value: item }, type: 'image' })
// store.commit('selectItem', { data: { value: item }, type: 'image' })
}
const uploadDone = async (res: TUploadDoneData) => {
await api.material.addMyPhoto(res)
const uploadDone = async (res: any) => {
// await api.material.addMyPhoto(res)
// state.imgList = []
// load(true)
const newList = [res, ...state.imgList]
state.imgList = []
load(true)
setTimeout(() => {
state.imgList = newList //
}, 300)
}
const tabChange = (tabName: TabPaneName) => {

View File

@ -102,7 +102,7 @@ watch(
return
}
let list = newList.filter((v: IGetTempListData) => !oldList.includes(v)) // difference
list = JSON.parse(JSON.stringify(list))
list = JSON.parse(JSON.stringify(list))
const marginRight = 6 //
const limitWidth = (await getFatherWidth()) - marginRight
const standardHeight = 280 //

View File

@ -65,13 +65,14 @@ const state = reactive<TState>({
})
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 as string)
cate && action('change', state.materialCates[Number(cate)], Number(cate))
})
state.materialCates = [{ id: 0, name: '示例模板' }]
// api.home.getCategories({ type: 1 }).then((list: any) => {
// list.unshift({ id: 0, name: '' })
// state.materialCates = list
// const { cate } = route.query
// cate && (state.currentIndex = cate as string)
// cate && action('change', state.materialCates[Number(cate)], Number(cate))
// })
}
watch(

View File

@ -2,8 +2,8 @@
* @Author: ShawnPhang
* @Date: 2021-08-09 14:00:23
* @Description: 图片容器
* @LastEditors: ShawnPhang <site: book.palxp.com>
* @LastEditTime: 2023-06-29 17:53:39
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 09:48:42
-->
<template>
<el-card class="box-card" shadow="hover" :body-style="{ padding: state.effectSelect ? '20px' : 0 }">
@ -85,8 +85,7 @@ onMounted(async () => {
async function getList() {
const res = await api.material.getList({
cate: 8,
pageSize: 29
cate: 'mask',
})
state.list = res.list.map(({ thumb, url }) => {
return { thumb, url }

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-02 09:41:41
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-16 17:19:15
* @LastEditTime: 2024-08-12 09:25:33
-->
<template>
<div
@ -203,8 +203,7 @@ function keyChange(uuid: string, key: keyof TParamsData, value: number) {
widgetStore.updateWidgetData({
uuid,
key,
value,
pushHistory: false,
value
})
// store.dispatch('updateWidgetData', {
// uuid,

View File

@ -2,8 +2,8 @@
* @Author: ShawnPhang
* @Date: 2021-08-09 11:21:37
* @Description: 组合设置
* @LastEditors: ShawnPhang <site: book.palxp.com>
* @LastEditTime: 2023-06-29 17:57:24
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 09:25:37
-->
<template>
<div id="w-group-style">
@ -142,8 +142,7 @@ function finish(key: string, value: string | number | number[]) {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || "",
key: key as TUpdateWidgetPayload['key'],
value: value as TUpdateWidgetPayload['value'],
pushHistory: true,
value: value as TUpdateWidgetPayload['value']
})
// store.dispatch("updateWidgetData", {
// uuid: dActiveElement.value.uuid,

View File

@ -249,8 +249,7 @@ function setTransform(attrName: string, value: string | number) {
widgetStore.updateWidgetData({
uuid: props.params.uuid,
key: 'transform',
value: setValue,
pushHistory: false,
value: setValue
})
// store.dispatch("updateWidgetData", {
// uuid: props.params.uuid,

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-09 11:41:53
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-03-22 16:14:48
* @LastEditTime: 2024-08-12 09:25:47
-->
<template>
<div id="w-image-style">
@ -37,16 +37,8 @@
</div>
</el-collapse-item>
<el-collapse-item v-if="state.innerElement.isNinePatch" title="点九图设置" name="3">
<number-slider
v-model="state.innerElement.sliceData.ratio"
:step="0.01" label="比率" :maxValue="10"
@finish="(value) => finishSliceData('ratio', value)"
/>
<number-slider
v-model="state.innerElement.sliceData.left"
:step="0.5" label="大小"
@finish="(value) => finishSliceData('left', value)"
/>
<number-slider v-model="state.innerElement.sliceData.ratio" :step="0.01" label="比率" :maxValue="10" @finish="(value) => finishSliceData('ratio', value)" />
<number-slider v-model="state.innerElement.sliceData.left" :step="0.5" label="大小" @finish="(value) => finishSliceData('left', value)" />
</el-collapse-item>
<br />
<icon-item-select class="style-item" label="" :data="layerIconList" @finish="layerAction" />
@ -55,11 +47,7 @@
</el-collapse>
<!-- <CropImage ref="crop" @done="cropDone" /> -->
<inner-tool-bar v-show="state.innerElement.cropEdit" :style="toolBarStyle">
<number-slider
v-model="state.innerElement.zoom"
class="inner-bar" label="缩放" labelWidth="40px"
:step="0.01" :minValue="1" :maxValue="3"
/>
<number-slider v-model="state.innerElement.zoom" class="inner-bar" label="缩放" labelWidth="40px" :step="0.01" :minValue="1" :maxValue="3" />
<i style="padding: 0 8px; cursor: pointer" class="icon sd-queren" @click="imgCrop(false)" />
</inner-tool-bar>
<picBox ref="picBoxRef" @select="selectDone" />
@ -134,7 +122,6 @@ const state = reactive<TState>({
const picBoxRef = ref<typeof picBox | null>(null)
const imageCutoutRef = ref<typeof imageCutout | null>(null)
const widgetStore = useWidgetStore()
const forceStore = useForceStore()
const canvasStore = useCanvasStore()
@ -145,10 +132,9 @@ const controlStore = useControlStore()
const { dMoving } = storeToRefs(controlStore)
const { dActiveElement, dWidgets } = storeToRefs(widgetStore)
let lastUuid: string | undefined = undefined
let tag: boolean
let toolBarStyle: { left: string, top: string } | null = null
let toolBarStyle: { left: string; top: string } | null = null
onBeforeUnmount(() => {
imgCrop(false)
@ -166,7 +152,7 @@ watch(
}
lastUuid = newValue.uuid
},
{ deep: true }
{ deep: true },
)
watch(
@ -175,7 +161,7 @@ watch(
changeValue()
cropHandle()
},
{ deep: true }
{ deep: true },
)
function created() {
@ -201,15 +187,12 @@ function changeValue() {
}
for (let key in state.innerElement) {
if (state.ingoreKeys.indexOf(key) !== -1) {
(dActiveElement.value as Record<string, any>)[key] = state.innerElement[(key as keyof TImageSetting)]
} else if (
key !== 'cropEdit' && key !== 'record' &&
state.innerElement[(key as keyof TImageSetting)] !== (dActiveElement.value as Record<string, any>)[key]
) {
;(dActiveElement.value as Record<string, any>)[key] = state.innerElement[key as keyof TImageSetting]
} else if (key !== 'cropEdit' && key !== 'record' && state.innerElement[key as keyof TImageSetting] !== (dActiveElement.value as Record<string, any>)[key]) {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || "",
key: (key as TUpdateWidgetPayload['key']),
value: (state.innerElement[(key as keyof TImageSetting)] as TUpdateWidgetPayload['value']),
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: state.innerElement[key as keyof TImageSetting] as TUpdateWidgetPayload['value'],
})
// store.dispatch('updateWidgetData', {
// uuid: dActiveElement.value.uuid,
@ -229,51 +212,37 @@ function finishSliceData(key: string, value: number | number[]) {
uuid: dActiveElement.value.uuid,
key: 'sliceData',
value: data,
pushHistory: true,
})
// store.dispatch('updateWidgetData', {
// uuid: dActiveElement.value.uuid,
// key: 'sliceData',
// value: data,
// pushHistory: true,
// })
}
}
function finish(key: string = "", value: string | number | (string | number)[] | null = "") {
function finish(key: string = '', value: string | number | (string | number)[] | null = '') {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || "",
key: (key as TUpdateWidgetPayload['key']),
value: value as TUpdateWidgetPayload['value'],
pushHistory: true,
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: value as TUpdateWidgetPayload['value']
})
// store.dispatch('updateWidgetData', {
// uuid: dActiveElement.value.uuid,
// key: key,
// value: value,
// pushHistory: true,
// })
}
function layerAction(item: TIconItemSelectData) {
if (item.key === 'zIndex') {
widgetStore.updateLayerIndex({
uuid: dActiveElement.value?.uuid || "",
value: (item.value as TupdateLayerIndexData['value']),
uuid: dActiveElement.value?.uuid || '',
value: item.value as TupdateLayerIndexData['value'],
})
// store.dispatch("updateLayerIndex", {
// uuid: dActiveElement.value.uuid,
// value: item.value,
// })
} else {
finish(item.key || "", item.value === dActiveElement.value?.flip ? null : item.value)
finish(item.key || '', item.value === dActiveElement.value?.flip ? null : item.value)
}
}
async function alignAction(item: TIconItemSelectData) {
widgetStore.updateAlign({
align: (item.value as TUpdateAlignData['align']),
uuid: dActiveElement.value?.uuid || "",
align: item.value as TUpdateAlignData['align'],
uuid: dActiveElement.value?.uuid || '',
})
// store.dispatch("updateAlign", {
// align: item.value,
@ -295,7 +264,6 @@ function openCropper() {
// this.innerElement.height = height.toFixed(0)
// }
async function changeContainer(setting: any) {
state.innerElement.mask = setting.svgUrl
// const index = this.dWidgets.findIndex((x) => x.uuid == this.innerElement.uuid)
@ -318,18 +286,17 @@ async function changeContainer(setting: any) {
// this.$store.commit('setShowMoveable', true)
// }
async function selectDone(img: TGetImageListResult) {
state.innerElement.imgUrl = img.url
const loadImg = await getImage(img.url)
state.innerElement.width = loadImg.width * canvasStore.dZoom / 100
state.innerElement.height = loadImg.height * canvasStore.dZoom / 100
state.innerElement.width = (loadImg.width * canvasStore.dZoom) / 100
state.innerElement.height = (loadImg.height * canvasStore.dZoom) / 100
// this.imgCrop(true)
}
function imgCrop(val: boolean) {
// TODO:
const el = document.getElementById(state.innerElement.uuid || "")
const el = document.getElementById(state.innerElement.uuid || '')
if (!el) return
const { left, top } = el.getBoundingClientRect()
toolBarStyle = { left: left + 'px', top: top + 'px' }
@ -337,7 +304,6 @@ function imgCrop(val: boolean) {
controlStore.setShowRotatable(!val)
}
function cropHandle() {
controlStore.setCropUuid(state.innerElement.cropEdit ? state.innerElement.uuid : '-1')
// store.commit('setCropUuid', state.innerElement.cropEdit ? state.innerElement.uuid : -1)
@ -352,7 +318,7 @@ function openPicBox() {
//
function openImageCutout() {
fetch(state.innerElement.imgUrl || "")
fetch(state.innerElement.imgUrl || '')
.then((response) => response.blob())
.then((blob) => {
const file = new File([blob], `image_${Math.random()}.jpg`, { type: 'image/jpeg' })
@ -371,7 +337,6 @@ async function cutImageDone(url: string) {
state.innerElement.imgUrl = url
}, 300)
}
</script>
<style lang="less" scoped>

View File

@ -2,8 +2,8 @@
* @Author: ShawnPhang
* @Date: 2021-08-09 11:41:53
* @Description:
* @LastEditors: ShawnPhang <site: book.palxp.com>
* @LastEditTime: 2023-06-29 17:53:23
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 09:26:04
-->
<template>
<div id="w-image-style">
@ -80,12 +80,12 @@ import { TUpdateAlignData } from '@/store/design/widget/actions/align'
type TState = {
activeNames: string[]
innerElement: TWQrcodeSetting,
tag: boolean,
ingoreKeys: string[],
layerIconList: TIconItemSelectData[],
alignIconList: TIconItemSelectData[],
localization: QrCodeLocalizationData,
innerElement: TWQrcodeSetting
tag: boolean
ingoreKeys: string[]
layerIconList: TIconItemSelectData[]
alignIconList: TIconItemSelectData[]
localization: QrCodeLocalizationData
}
const state = reactive<TState>({
@ -98,12 +98,10 @@ const state = reactive<TState>({
localization,
})
const controlStore = useControlStore()
const widgetStore = useWidgetStore()
const forceStore = useForceStore()
// const {
// dActiveElement, dWidgets
// } = useSetupMapGetters(['dActiveElement', 'dWidgets'])
@ -121,7 +119,7 @@ watch(
if (Number(newValue.uuid) == -1) {
state.innerElement.cropEdit = false
widgetStore.updateWidgetData({
uuid: lastUuid ?? "",
uuid: lastUuid ?? '',
key: 'cropEdit',
value: false,
})
@ -134,7 +132,7 @@ watch(
lastUuid = newValue.uuid
}
},
{ deep: true }
{ deep: true },
)
watch(
@ -142,7 +140,7 @@ watch(
(newValue, oldValue) => {
changeValue()
},
{ deep: true }
{ deep: true },
)
function created() {
@ -168,13 +166,10 @@ function changeValue() {
for (let key in state.innerElement) {
const itemKey = key as keyof TWQrcodeSetting
if (state.ingoreKeys.indexOf(key) !== -1) {
(dActiveElement.value as Record<string, any>)[itemKey] = state.innerElement[itemKey]
} else if (
key !== 'setting' && key !== 'record' &&
state.innerElement[itemKey] !== (dActiveElement.value as Record<string, any>)[itemKey]
) {
;(dActiveElement.value as Record<string, any>)[itemKey] = state.innerElement[itemKey]
} else if (key !== 'setting' && key !== 'record' && state.innerElement[itemKey] !== (dActiveElement.value as Record<string, any>)[itemKey]) {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || "",
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: state.innerElement[itemKey] as TUpdateWidgetPayload['value'],
})
@ -192,20 +187,13 @@ function finish(key: string, value: number | number[] | string) {
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: value,
pushHistory: true,
})
// store.dispatch("updateWidgetData", {
// uuid: dActiveElement.value.uuid,
// key: key,
// value: value,
// pushHistory: true,
// })
}
function layerAction(item: TIconItemSelectData) {
console.log(item)
widgetStore.updateLayerIndex({
uuid: dActiveElement.value?.uuid || "",
uuid: dActiveElement.value?.uuid || '',
value: item.value as number,
})
// store.dispatch("updateLayerIndex", {
@ -217,7 +205,7 @@ function layerAction(item: TIconItemSelectData) {
async function alignAction(item: TIconItemSelectData) {
widgetStore.updateAlign({
align: item.value as TUpdateAlignData['align'],
uuid: dActiveElement.value?.uuid || "",
uuid: dActiveElement.value?.uuid || '',
})
// store.dispatch("updateAlign", {
// align: item.value,
@ -237,7 +225,7 @@ async function uploadImgDone(img: TUploadDoneData) {
// this.innerElement.width = img.width
// this.innerElement.height = img.height * (this.innerElement.width / img.width)
state.innerElement.url = img.url
// store.commit('setShowMoveable', true)
controlStore.setShowMoveable(true)
}

View File

@ -21,11 +21,11 @@
// svg
// const NAME = 'w-svg'
import { useCanvasStore, useForceStore, useWidgetStore } from '@/store';
import { useCanvasStore, useForceStore, useWidgetStore } from '@/store'
import { TWSvgSetting } from './wSvgSetting'
import { CSSProperties, computed, nextTick, onBeforeMount, onMounted, onUpdated, reactive, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { TUpdateWidgetPayload } from '@/store/design/widget/actions/widget';
import { CSSProperties, computed, nextTick, onBeforeMount, onMounted, onUpdated, reactive, ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { TUpdateWidgetPayload } from '@/store/design/widget/actions/widget'
// import { useSetupMapGetters } from '@/common/hooks/mapGetters';
type TProps = {
@ -37,12 +37,12 @@ type TProps = {
}
type TState = {
position: CSSProperties['position'], // 'absolute'relative
editBoxStyle: CSSProperties,
editBoxs: Record<string, any>,
editingKey: string,
cropWidgetXY: Record<string, any>, //
attrRecord: Record<string, any>, //
position: CSSProperties['position'] // 'absolute'relative
editBoxStyle: CSSProperties
editBoxs: Record<string, any>
editingKey: string
cropWidgetXY: Record<string, any> //
attrRecord: Record<string, any> //
svgImg: Record<string, any> | null
}
@ -56,7 +56,7 @@ const state = reactive<TState>({
editingKey: '',
cropWidgetXY: {}, //
attrRecord: {}, //
svgImg: null
svgImg: null,
})
const widgetStore = useWidgetStore()
@ -88,7 +88,7 @@ watch(
() => {
attrsChange()
},
{ immediate: true, deep: true }
{ immediate: true, deep: true },
)
watch(
@ -96,7 +96,7 @@ watch(
async () => {
await nextTick()
updateRecord()
}
},
)
watch(
@ -106,7 +106,7 @@ watch(
state.svgImg.attr({
'xlink:href': props.params.imgUrl,
})
}
},
)
watch(
@ -118,7 +118,7 @@ watch(
} else {
el?.removeEventListener('mousedown', touchstart, false)
}
}
},
)
onUpdated(() => {
@ -195,16 +195,14 @@ function loadSvg() {
// console.log(this.params)
const Snap = (window as any).Snap
return new Promise<void>((resolve) => {
Snap.load(
props.params.svgUrl,
function (svg: Record<string, any>) {
let svg2 = Snap(svg.node)
// let item = svg2.select('circle')
// item.attr({
// fill: 'rgb(255, 0, 0)',
// })
// console.log(item.attr('fill'))
// Snap.load(
// props.params.svgUrl,
// function (svg: Record<string, any>) {
//
// },
// )
const svg = Snap.parse(props.params.svgUrl)
let svg2 = Snap(svg.node)
let items = svg2.node.childNodes
svg2.node.removeAttribute('width')
svg2.node.removeAttribute('height')
@ -250,24 +248,12 @@ function loadSvg() {
}
// console.log(element.attributes, element.getAttribute('fill'), _this.params.colors)
}
// _this.viewBox = svg2.node.viewBox.baseVal
// _this.svgImg = img
// img.attr({
// width: '100%',
// height: '100%',
// transform: '',
// 'xlink:href': _this.params.imgUrl || '',
// })
if (widgetRef.value) {
// svg.node.classList.add('svg__box')
widgetRef.value.appendChild(svg.node)
}
resolve()
},
document.getElementById(props.params.uuid),
)
})
}
@ -323,14 +309,7 @@ function changeFinish(key: string, value: number) {
uuid: props.params.uuid,
key: key as TUpdateWidgetPayload['key'],
value: value,
pushHistory: true,
})
// store.dispatch("updateWidgetData", {
// uuid: props.params.uuid,
// key: key,
// value: value,
// pushHistory: true,
// })
}
function move(payload: Record<string, any>) {

View File

@ -2,8 +2,8 @@
* @Author: ShawnPhang
* @Date: 2021-08-09 11:41:53
* @Description:
* @LastEditors: ShawnPhang
* @LastEditTime: 2022-04-15 11:08:36
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 09:26:17
-->
<template>
<div id="w-image-style">
@ -83,7 +83,7 @@ watch(
() => {
change()
},
{ deep: true }
{ deep: true },
)
watch(
@ -91,7 +91,7 @@ watch(
() => {
changeValue()
},
{ deep: true }
{ deep: true },
)
function created() {
@ -118,13 +118,10 @@ function changeValue() {
for (let key in state.innerElement) {
const itemKey = key as keyof TWSvgSetting
if (state.ingoreKeys.indexOf(itemKey) !== -1) {
(dActiveElement.value as Record<string, any>)[key] = state.innerElement[itemKey]
} else if (
itemKey !== 'setting' && itemKey !== 'record' &&
state.innerElement[itemKey] !== (dActiveElement.value as Record<string, any>)[itemKey]
) {
;(dActiveElement.value as Record<string, any>)[key] = state.innerElement[itemKey]
} else if (itemKey !== 'setting' && itemKey !== 'record' && state.innerElement[itemKey] !== (dActiveElement.value as Record<string, any>)[itemKey]) {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || "",
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: state.innerElement[itemKey] as TUpdateWidgetPayload['value'],
})
@ -146,14 +143,7 @@ function finish(key: string, value: any) {
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: value,
pushHistory: true,
})
// store.dispatch("updateWidgetData", {
// uuid: dActiveElement.value.uuid,
// key: key,
// value: value,
// pushHistory: true,
// })
}
function layerAction(item: TIconItemSelectData) {

View File

@ -45,13 +45,7 @@
v-html="params.text"
></div>
</template>
<div
ref="editWrap" :style="{ fontFamily: `'${params.fontClass.value}'` }"
class="edit-text" spellcheck="false"
:contenteditable="state.editable ? 'plaintext-only' : false"
@input="writingText($event)"
@blur="writeDone($event)"
v-html="params.text"></div>
<div ref="editWrap" :style="{ fontFamily: `'${params.fontClass.value}'` }" class="edit-text" spellcheck="false" :contenteditable="state.editable ? 'plaintext-only' : false" @input="writingText($event)" @blur="writeDone($event)" v-html="params.text"></div>
</div>
</template>
@ -147,14 +141,7 @@ watch(
uuid: String(props.params.uuid),
key: 'editable',
value,
pushHistory: false,
})
// store.dispatch('updateWidgetData', {
// uuid: props.params.uuid,
// key: 'editable',
// value,
// pushHistory: false,
// })
},
)
@ -171,20 +158,13 @@ function updateRecord() {
}
function updateText(e?: Event) {
const value = e && e.target ? (e.target as HTMLElement).innerHTML : props.params.text//.replace(/\n/g, '<br/>')
const value = e && e.target ? (e.target as HTMLElement).innerHTML : props.params.text //.replace(/\n/g, '<br/>')
if (value !== props.params.text) {
widgetStore.updateWidgetData({
uuid: String(props.params.uuid),
key: 'text',
value,
pushHistory: false,
})
// store.dispatch('updateWidgetData', {
// uuid: props.params.uuid,
// key: 'text',
// value,
// pushHistory: false,
// })
}
}
@ -197,24 +177,13 @@ function writingText(e?: Event) {
uuid: String(props.params.uuid),
key: 'height',
value: el.offsetHeight,
pushHistory: false,
})
// store.dispatch('updateWidgetData', {
// uuid: props.params.uuid,
// key: 'height',
// value: el.offsetHeight,
// pushHistory: false,
// })
forceStore.setUpdateRect()
// store.commit('updateRect')
}
function writeDone(e: Event) {
state.editable = false
setTimeout(() => {
historyStore.pushHistory("文字修改")
// store.dispatch('pushHistory', '')
}, 100)
updateText(e)
}
@ -231,9 +200,9 @@ function dblclickText(_: MouseEvent) {
range.select()
} else {
const range = document.createRange()
range.selectNodeContents(el);
window.getSelection()?.removeAllRanges();
window.getSelection()?.addRange(range);
range.selectNodeContents(el)
window.getSelection()?.removeAllRanges()
window.getSelection()?.addRange(range)
}
}, 100)
}

View File

@ -64,25 +64,25 @@ import valueSelect from '../../settings/valueSelect.vue'
import effectWrap from '../../settings/EffectSelect/TextWrap.vue'
import { useFontStore } from '@/common/methods/fonts'
import usePageFontsFilter from './pageFontsFilter'
import { wTextSetting ,TwTextData } from './wTextSetting';
import { storeToRefs } from 'pinia';
import { useControlStore, useForceStore, useWidgetStore } from '@/store';
import { TUpdateWidgetPayload } from '@/store/design/widget/actions/widget';
import { TUpdateAlignData } from '@/store/design/widget/actions/align';
import { wTextSetting, TwTextData } from './wTextSetting'
import { storeToRefs } from 'pinia'
import { useControlStore, useForceStore, useWidgetStore } from '@/store'
import { TUpdateWidgetPayload } from '@/store/design/widget/actions/widget'
import { TUpdateAlignData } from '@/store/design/widget/actions/align'
type TState = {
activeNames: string[],
innerElement: TwTextData,
tag: boolean,
ingoreKeys: string[],
fontSizeList: number[],
fontClassList: Record<string, any>, //
lineHeightList: number[],
letterSpacingList: number[],
layerIconList: TIconItemSelectData[],
styleIconList1: TStyleIconData[],
styleIconList2: TStyleIconData2[],
alignIconList: TIconItemSelectData[],
activeNames: string[]
innerElement: TwTextData
tag: boolean
ingoreKeys: string[]
fontSizeList: number[]
fontClassList: Record<string, any> //
lineHeightList: number[]
letterSpacingList: number[]
layerIconList: TIconItemSelectData[]
styleIconList1: TStyleIconData[]
styleIconList2: TStyleIconData2[]
alignIconList: TIconItemSelectData[]
}
const widgetStore = useWidgetStore()
@ -108,13 +108,21 @@ const { dMoving } = storeToRefs(useControlStore())
// const isDraw = computed(() => route.name === 'Draw')
watch(() => dActiveElement.value, () => {
change()
}, { deep: true })
watch(
() => dActiveElement.value,
() => {
change()
},
{ deep: true },
)
watch(() => state.innerElement, () => {
changeValue()
}, { deep: true })
watch(
() => state.innerElement,
() => {
changeValue()
},
{ deep: true },
)
let timer: boolean | null = null
@ -150,24 +158,13 @@ function changeValue() {
for (let key in state.innerElement) {
const itemKey = key as keyof TwTextData
if (state.ingoreKeys.indexOf(itemKey) !== -1) {
(dActiveElement.value as Record<string, any>)[itemKey] = state.innerElement[itemKey]
} else if (
key !== 'setting' && key !== 'record' &&
state.innerElement[itemKey] !== (dActiveElement.value as Record<string, any>)[itemKey]
) {
// const pushHistory = !['textEffects', 'transformData', 'fontClass'].includes(key)
;(dActiveElement.value as Record<string, any>)[itemKey] = state.innerElement[itemKey]
} else if (key !== 'setting' && key !== 'record' && state.innerElement[itemKey] !== (dActiveElement.value as Record<string, any>)[itemKey]) {
widgetStore.updateWidgetData({
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value: state.innerElement[itemKey],
pushHistory: false,
})
// store.dispatch('updateWidgetData', {
// uuid: dActiveElement.value.uuid,
// key,
// value: state.innerElement[itemKey],
// pushHistory: false,
// })
}
}
}
@ -198,14 +195,7 @@ function finish(key: string, value: number | Record<string, any> | string) {
uuid: dActiveElement.value?.uuid || '',
key: key as TUpdateWidgetPayload['key'],
value,
pushHistory: false,
})
// store.dispatch('updateWidgetData', {
// uuid: dActiveElement.value.uuid,
// key,
// value,
// pushHistory: false,
// })
setTimeout(() => {
key === 'fontClass' && (state.fontClassList['当前页面'] = usePageFontsFilter())
}, 300)
@ -223,8 +213,8 @@ function layerAction(item: TIconItemSelectData) {
}
async function textStyleAction(item: TIconItemSelectData) {
let value = item.key === 'textAlign' ? item.value : (item.value as number[])[item.select ? 1 : 0];
(state.innerElement as Record<string, any>)[item.key || ""] = value
let value = item.key === 'textAlign' ? item.value : (item.value as number[])[item.select ? 1 : 0]
;(state.innerElement as Record<string, any>)[item.key || ''] = value
// TODO:
item.key === 'writingMode' && relationChange()
await nextTick()
@ -291,7 +281,7 @@ defineExpose({
textStyleAction,
finish,
layerAction,
alignAction
alignAction,
})
</script>

View File

@ -3,7 +3,7 @@
* @Date: 2024-04-05 07:31:45
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-22 20:47:43
* @LastEditTime: 2024-08-12 05:30:15
*/
// const prefix = import.meta.env
const prefix = process.env
@ -16,10 +16,9 @@ export default {
BASE_URL: isDev ? '/' : './',
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}', // 截图服务地址
COPYRIGHT: 'ShawnPhang - Design.pPalxp.cn',
API_URL: isDev ? 'http://localhost:7001' : '', // 后端地址
SCREEN_URL: isDev ? 'http://localhost:7001' : '', // 截图服务地址
IMG_URL: 'https://store.palxp.cn/', // 七牛云资源地址
// ICONFONT_URL: '//at.alicdn.com/t/font_3223711_74mlzj4jdue.css',
ICONFONT_URL: '//at.alicdn.com/t/font_2717063_ypy8vprc3b.css?display=swap',

View File

@ -3,10 +3,10 @@
* @Date: 2024-05-19 05:14:10
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-05-19 05:15:09
* @LastEditTime: 2024-08-12 15:52:36
*/
export default {
logout: '退出登录',
save: '保存',
download: '下载作品',
download: '下载模版',
}

View File

@ -3,7 +3,7 @@
* @Date: 2024-04-05 06:23:23
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-05-06 11:59:28
* @LastEditTime: 2024-08-12 09:27:06
*/
export type TScreeData = {
/** 记录编辑界面的宽度 */
@ -51,7 +51,7 @@ export type TStoreAction = {
updatePageData<T extends keyof TPageState>(data: {
key: T
value: TPageState[T]
pushHistory?: boolean
// pushHistory?: boolean
}): void
getDPage(data: TPageState): () => TPageState
/** 设置dPage */

View File

@ -4,7 +4,7 @@
* @Date: 2024-03-18 21:00:00
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-05-06 12:05:02
* @LastEditTime: 2024-08-12 09:27:22
*/
import { Store, defineStore } from 'pinia'
@ -61,9 +61,9 @@ const CanvasStore = defineStore<"canvasStore", TCanvasState, {}, TStoreAction>("
// this.dPage.tag = tag === 0 ? 0.01 : 0
},
/** 更新 Page 字段 */
updatePageData({ key, value, pushHistory }) {
updatePageData({ key, value }) {
const data = this.dPage
if (data[key] !== value || pushHistory) {
if (data[key] !== value) {
data[key] = value
}
},

View File

@ -3,8 +3,8 @@
* @Author: Jeremy Yu
* @Date: 2024-03-18 21:00:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-18 21:00:00
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 09:27:45
*/
import { useHistoryStore } from "@/store";
@ -92,20 +92,18 @@ const ControlStore = defineStore<"controlStore", TControlState, {}, TControlAct
},
/** 组件调整结束 */
stopDResize() {
if (this.dResizeing) {
const historyStore = useHistoryStore()
historyStore.pushHistory('stopDResize')
// store.dispatch('pushHistory', 'stopDResize')
}
// if (this.dResizeing) {
// // store.dispatch('pushHistory', 'stopDResize')
// }
this.dResizeing = false
},
/** 组件移动结束 */
stopDMove() {
if (this.dMoving) {
const historyStore = useHistoryStore()
historyStore.pushHistory("stopDMove")
// store.dispatch('pushHistory', 'stopDMove')
}
// if (this.dMoving) {
// const historyStore = useHistoryStore()
// historyStore.pushHistory("stopDMove")
// // store.dispatch('pushHistory', 'stopDMove')
// }
this.dMoving = false
},
setCropUuid(uuid: string) {

View File

@ -2,8 +2,8 @@
* @Author: Jeremy Yu
* @Date: 2024-03-28 14:00:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-28 14:00:00
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 09:28:15
*/
import { customAlphabet } from 'nanoid/non-secure'
@ -78,10 +78,6 @@ export function realCombined(store: TGroupStore) {
group.height = Number(bottom - top)
widgetStore.dActiveElement = group
widgetStore.dSelectWidgets = []
historyStore.pushHistory('realCombined')
// store.dispatch('pushHistory', 'realCombined')
// store.dispatch('reChangeCanvas')
}
}

View File

@ -3,16 +3,14 @@
* @Date: 2024-03-18 21:00:00
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-18 18:17:13
* @LastEditTime: 2024-08-12 09:29:10
*/
import { useCanvasStore, useWidgetStore } from "@/store"
import { THistoryStore } from ".."
/** push操作历史记录 */
/** push操作历史记录(历史记录功能已重构,该方法不再使用) */
export function pushHistory(store: THistoryStore, msg: string = "") {
console.error('历史记录功能已重构该方法即将移除pushHistory');
// const pageStore = useCanvasStore()
// const widgetStore = useWidgetStore()
// console.log('history压栈', '来源: ' + msg)

View File

@ -3,11 +3,11 @@
* @Date: 2024-03-18 21:00:00
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-18 15:41:25
* @LastEditTime: 2024-08-12 09:28:41
*/
import { Store, defineStore } from 'pinia'
import { pushHistory, pushColorToHistory } from './actions/pushHistory'
import { pushColorToHistory } from './actions/pushHistory'
import handleHistory from './actions/handleHistory'
import { useCanvasStore, useWidgetStore } from '@/store'
@ -36,7 +36,6 @@ type THistoryState = {
}
type THistoryAction = {
pushHistory: (msg?: string) => void
/** 写入历史记录 */
changeHistory: (patches: any) => void
/**
@ -67,9 +66,6 @@ const HistoryStore = defineStore<'historyStore', THistoryState, {}, THistoryActi
}),
actions: {
pushHistory(msg) {
pushHistory(this, msg)
},
changeHistory({ patches, inversePatches }) {
const pointer = ++this.dHistoryParams.stackPointer
// 如若之前撤销过,当新增记录时,后面的记录就清空了

View File

@ -2,8 +2,8 @@
* @Author: Jeremy Yu
* @Date: 2024-03-28 14:00:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-28 14:00:00
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 09:29:27
*/
import { useCanvasStore, useHistoryStore } from "@/store"
@ -82,8 +82,6 @@ export function updateAlign(store: TWidgetStore, { align, uuid, group }: TUpdate
target.left = left
target.top = top
historyStore.pushHistory("updateAlign")
// store.dispatch('pushHistory', 'updateAlign')
canvasStore.reChangeCanvas()
// store.dispatch('reChangeCanvas')
}

View File

@ -2,8 +2,8 @@
* @Author: Jeremy Yu
* @Date: 2024-03-28 21:00:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2024-03-28 14:00:00
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-08-12 09:29:31
*/
import { useCanvasStore, useHistoryStore } from "@/store"
@ -29,8 +29,6 @@ export function addGroup(store: TWidgetStore, group: TdWidgetData[]) {
const len = store.dWidgets.length
store.dActiveElement = store.dWidgets[len - 1]
historyStore.pushHistory("addGroup")
// store.dispatch('pushHistory', 'addGroup')
canvasStore.reChangeCanvas()
// store.dispatch('reChangeCanvas')
}

View File

@ -3,7 +3,7 @@
* @Date: 2024-03-28 21:00:00
* @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2024-04-16 01:03:05
* @LastEditTime: 2024-08-12 09:29:35
*/
@ -23,6 +23,5 @@ export function setTemplate(store: TWidgetStore, allWidgets: TdWidgetData[]) {
store.dWidgets.push(item)
})
widgetStore.updateDWidgets()
// historyStore.pushHistory("setTemplate")
canvasStore.reChangeCanvas()
}

Some files were not shown because too many files have changed in this diff Show More