docs: readme

This commit is contained in:
ShawnPhang 2023-07-18 16:31:19 +08:00
parent 2c37bf84ee
commit 9b9796e571
6 changed files with 23 additions and 425 deletions

View File

@ -1,10 +1,12 @@
[在线体验](https://design.palxp.com/) | [文档网站](https://xp.palxp.com/) | [项目架构及目录讲解](https://xp.palxp.com/#/articles/1689321259854) [在线体验](https://design.palxp.com/) | [文档网站](https://xp.palxp.com/) | [项目架构及目录](https://xp.palxp.com/#/articles/1689321259854)
## 迅排设计 ## 迅排设计
一款漂亮且功能强大的在线海报图片设计器,仿稿定设计。适用于海报图片生成、电商分享图、文章长图、视频/公众号封面等多种场景。 一款漂亮且功能强大的在线海报图片设计器,仿稿定设计。
![](https://xp.palxp.com/images/2023-7-17-1689558055663.png) 适用于海报图片生成、电商分享图、文章长图、视频/公众号封面等多种场景。
![](https://xp.palxp.com/images/2023-7-16-1689500112694.gif)
- 丝滑的操作体验,丰富的交互细节,基础功能完善 - 丝滑的操作体验,丰富的交互细节,基础功能完善
- 采用服务端生成图片,确保多端出图统一性,支持各种 CSS 特性 - 采用服务端生成图片,确保多端出图统一性,支持各种 CSS 特性
@ -12,14 +14,16 @@
### 技术栈概括 ### 技术栈概括
前端:Vue3 、Vite2 、Vuex 、ElementPlus - Vue3 、Vite2 、Vuex 、ElementPlus
图片生成Puppeteer、Express - 图片生成Puppeteer、Express
一些可独立的功能会被抽取出来作为单独的库引入使用,仓库地址:[front-end-arsenal](https://github.com/palxiao/front-end-arsenal)[组件文档网站](https://fe-doc.palxp.com/#/) 一些可独立的功能会被抽取出来作为单独的库引入使用,仓库地址:[front-end-arsenal](https://github.com/palxiao/front-end-arsenal)[组件文档网站](https://fe-doc.palxp.com/#/)
> 环境需求:**Node.js v16** 以上版本 > 环境需求:**Node.js v16** 以上版本
## 快速开始
### 拉取源码 ### 拉取源码
``` ```
@ -43,28 +47,22 @@ npm run serve
> >
> ![](https://xp.palxp.com/images/2023-7-16-1689498291322.png) > ![](https://xp.palxp.com/images/2023-7-16-1689498291322.png)
### 运行结果
![](https://xp.palxp.com/images/2023-7-16-1689500112694.gif)
合成图片时本地会启动一个 Chrome 浏览器实例。 合成图片时本地会启动一个 Chrome 浏览器实例。
### 打包前端页面 ### 打包
``` ```
npm run v-build npm run v-build <- 前端页面
``` npm run build <- 图片生成服务 sreenshot 目录下
### 打包图片生成服务
```
cd sreenshot
npm run build
``` ```
### 服务端 ### 服务端
可参考接口 API 文档自行实现服务端。 可参考[接口 API 文档](https://xp.palxp.com/apidoc/index.html)自行实现服务端。
### 图片生成服务
代码位于 `screenshots` 目录下,[接口 API 文档](https://xp.palxp.com/apidoc/screenshot.html)。
### 服务器配置 ### 服务器配置

View File

@ -3,7 +3,7 @@
* @Date: 2022-02-01 13:41:59 * @Date: 2022-02-01 13:41:59
* @Description: * @Description:
* @LastEditors: ShawnPhang <site: book.palxp.com> * @LastEditors: ShawnPhang <site: book.palxp.com>
* @LastEditTime: 2023-07-04 19:04:44 * @LastEditTime: 2023-07-18 16:30:53
--> -->
# Node截图服务 # Node截图服务
@ -15,6 +15,10 @@ ts-node 直接运行,并监听文件修改自动热重启
使用 webpack 打包文件 使用 webpack 打包文件
### build:apidoc
生成 API 文档
### npm run serve (不使用) ### npm run serve (不使用)
webpack/tsc 编译 tssupervisor/pm2 监听编译后文件变动重启服务gulp 自动化任务 webpack/tsc 编译 tssupervisor/pm2 监听编译后文件变动重启服务gulp 自动化任务

View File

@ -1,67 +0,0 @@
/*
* @Author: ShawnPhang
* @Date: 2022-04-19 14:19:13
* @Description:
* @LastEditors: ShawnPhang
* @LastEditTime: 2022-04-21 18:38:10
*/
// const fs2 = require('fs')
const path = require('path')
const puppeteer = require('puppeteer')
const GIFEncoder = require('gif-encoder-2')
const { createWriteStream } = require('fs')
const PNG = require('png-js')
const params = { width: 1242/3, height: 2208/3 }
function decode(png) {
return new Promise((r) => {
png.decode((pixels) => r(pixels))
})
}
async function gifAddFrame(page, encoder) {
const pngBuffer = await page.screenshot({ clip: { width: params.width, height: params.height, x: 0, y: 0 } })
const png = new PNG(pngBuffer)
await decode(png).then((pixels) => encoder.addFrame(pixels))
}
;(async () => {
const browser = await puppeteer.launch({
headless: true,
slowMo: 0,
})
const page = await browser.newPage()
page.setViewport({ width: params.width, height: params.height })
await page.goto('http://localhost:3000/draw?tempid=519', {
waitUntil: ['networkidle0'],
timeout: 60000,
})
const dstPath = path.join(__dirname, `test.gif`)
// create a write stream for GIF data
const writeStream = createWriteStream(dstPath)
writeStream.on('close', () => {
console.log('create is done')
})
// createWriteStream().pipe(fs2.createWriteStream('test.gif'))
// setting gif encoder
// record gif
var encoder = new GIFEncoder(params.width, params.height)
encoder.createReadStream().pipe(writeStream)
encoder.start()
encoder.setRepeat(0)
encoder.setDelay(200)
// encoder.setQuality(10) // default
for (let i = 0; i < 5; i++) {
await gifAddFrame(page, encoder)
}
// finish encoder, test.gif saved
encoder.finish()
await browser.close()
})()

View File

@ -1,16 +0,0 @@
/*
* @Author: ShawnPhang
* @Date: 2022-02-28 15:35:59
* @Description:
* @LastEditors: ShawnPhang
* @LastEditTime: 2022-02-28 16:52:35
*/
const path = '/Users/mac/Documents/workSpace/Products/Management-Center/screenshot-service/static/screenshot-1-new.jpg'
const images = require('images')
const path233 = require('path')
// let tinyJpg = images(path233.resolve(__dirname, `../static/screenshot-1.png`)).size(300).encode('jpg', { quality: 20 })
// images(tinyJpg).save(path)
let tinyJpg = images(path233.resolve(__dirname, `../static/screenshot-1.png`)).size(300).save(path, { quality: 30 })

View File

@ -1,248 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
ul,
li {
list-style: none;
margin: 0;
padding: 0;
}
ul {
margin: 20px auto;
font-size: 0px;
}
ul li {
display: inline-block;
min-width: 100px;
height: 50px;
padding: 0 20px;
margin: 1px;
text-align: center;
font-size: 15px;
line-height: 50px;
color: #000;
border-radius: 4px;
transition: all 0.3s linear 0s;
cursor: pointer;
border: 1px solid #e8e8e8;
}
ul li:hover {
opacity: 0.8;
}
</style>
</head>
<body style="transform: scale(1);">
<input type="file" id="file">
<img style="width: 300px;height: 300px;" src="" id="img">
<p id="text"></p>
<ul id="ul"></ul>
</body>
<script>
// 封装函数库
function getImgColor(img) {
/**
* @ param 传入的图片
* @ this.progress 解析图片的进度 实时
* @ this.canvas canvas元素
* @ this.cvs context对象
* @ this.accuracy Number 解析图片颜色的精确度 1 - 7 数字选择
*
*
* @ anther taoqun <taoquns@foxmail.com>
*/
this.canvas = document.createElement("canvas")
this.canvas.width = 300 || img.width
this.canvas.height = 300 || img.height
this.cvs = this.canvas.getContext("2d")
this.cvs.drawImage(img, 0, 0, 300, 300)
this.accuracy = 5
this.progress = ''
}
getImgColor.prototype.getColorXY = function (x, y) {
/**
* @param x Number x坐标起点
* @param y Number y坐标起点
* @return color Object 包含颜色的rgba #16进制颜色
*/
let obj = this.cvs.getImageData(x, y, 1, 1)
let arr = obj.data.toString().split(",")
let first = parseInt(arr[0]).toString(16)
first = first.length === 2 ? first : first + first
let second = parseInt(arr[1]).toString(16)
second = second.length === 2 ? second : second + second
let third = parseInt(arr[2]).toString(16)
third = third.length === 2 ? third : third + third
let last = parseInt(arr.pop()) / 255
last = last.toFixed(0)
let color = {}
color['rgba'] = 'rgba(' + arr.join(',') + ',' + last + ')'
color['#'] = '#' + first + second + third
return color
}
getImgColor.prototype.getColors = function () {
/**
* 避免图片过大,阻塞卡死
* 每加载一行像素延迟20毫秒加载下一行
* return Promise
* promise resolve 解析完成后,返回颜色的总计数组,降序排列
* promise reject none
*/
return (new Promise((resolve, reject) => {
let arr = []
let getY = (i) => {
for (let j = 0; j < this.canvas.height; j++) {
let obj = {}
obj = this.getColorXY(i, j)
obj.index = 1
let is = true
arr.forEach((item) => {
if (item['#'] === obj['#']) {
is = false
item.index += 1
}
let l = []
for (let i = 0; i < obj['#'].length; i++) {
if (item['#'].indexOf(obj['#'][i]) > -1) {
l.push('1')
}
}
let acc = (this.accuracy > 7) ? 7 : this.accuracy
acc = (this.accuracy < 1) ? 2 : this.accuracy
if (l.length > acc) {
is = false
item.index += 1
}
})
if (is) {
arr.push(obj)
}
}
};
let getX = (i) => {
if (i < this.canvas.width) {
getY(i)
this.progress = (i / this.canvas.width * 100).toFixed(2) + '%'
console.log(this.progress)
setTimeout(() => {
getX(++i)
}, 1)
} else {
this.progress = '100%'
console.log(this.progress)
resolve(arr.sort(function (a, b) {
return a.index < b.index ? 1 : (a.index > b.index ? -1 : 0)
}))
}
};
getX(0)
}))
}
/*-----------无视这条分割线----------*/
/*-----------无视这条分割线----------*/
/*-----------无视这条分割线----------*/
// 实例代码
let input = document.querySelector("#file")
input.addEventListener("change", (event) => {
/**
* 上传图片之后
* 替换图片
* 执行方法
*/
let img = document.querySelector("#img")
let file = event.target.files[0]
let fr = new FileReader()
fr.onload = (e) => {
let n_img = new Image()
n_img.src = e.target.result
n_img.onload = (e) => {
n_img.id = 'img'
n_img.width = 300 || n_img.width
n_img.height = 300 || n_img.height
document.body.replaceChild(n_img, img)
getImg()
}
}
fr.readAsDataURL(file)
})
let a = null
function getImg() {
/**
* 获取图片,实例化图片
* 执行方法
* 解析完成,获得数组,操作回调函数
*
*/
let img = document.querySelector("#img")
a = new getImgColor(img)
// 获取 坐标 0 0 点的颜色值
console.log(a.getColorXY(100, 100))
document.getElementById('img').addEventListener('mousedown', handleSelection, false)
return
a.getColors().then((arr) => {
let ul = document.querySelector("#ul")
let text = document.querySelector("#text")
text.innerText = '共有' + arr.length + '个颜色';
let str = ''
arr.forEach((obj, index) => {
str += `<li style="background-color:${obj['#']}">${obj['#']} - ${obj['index']}次</li>`;
})
ul.innerHTML = str
})
}
function handleSelection(e) {
const r = 1
console.log(e.offsetX, e.offsetY, a.getColorXY(e.offsetX * r, e.offsetY * r))
}
</script>
</html>

View File

@ -1,73 +0,0 @@
<!--
* @Author: ShawnPhang
* @Date: 2022-03-14 15:41:17
* @Description:
* @LastEditors: ShawnPhang
* @LastEditTime: 2022-03-14 15:54:24
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
background-color: #999999;
}
.out {
background: #ffffff;
}
.page-design-index-wrap {
display: flex;
flex-direction: row;
flex: 1;
height: 100%;
overflow: hidden;
width: 100%;
}
.page-design-wrap {
flex: 1;
}
#page-design {
height: 100%;
overflow: auto;
position: relative;
width: 100%;
}
.out-page {
margin: 0 auto;
padding: 60px;
position: relative;
}
.design-canvas {
background-position: center;
background-repeat: no-repeat;
background-size: cover;
box-shadow: 1px 1px 10px 3px rgba(0, 0, 0, 0.1);
margin: 0 auto;
position: relative;
overflow: hidden;
}
</style>
</head>
<body>
<div class="page-design-index-wrap">
<div class="page-design-wrap out">
<div id="page-design" ref="page-design">
<div id="out-page" class="out-page">
<div class="edit-text" spellcheck="false" contenteditable="plaintext-only">asd</div>
</div>
</div>
</div>
</div>
</body>
</html>