生成图片方法更改
This commit is contained in:
parent
5b37d7962d
commit
4a992c9847
@ -12,7 +12,6 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="app" v-cloak></div>
|
<div id="app" v-cloak></div>
|
||||||
<script src="/js/echarts.min.js"></script>
|
<script src="/js/echarts.min.js"></script>
|
||||||
<script src="/js/html2canvas.min.js"></script>
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
<style>
|
<style>
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
|
|||||||
7
package-lock.json
generated
7
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vant/area-data": "^2.1.0",
|
"@vant/area-data": "^2.1.0",
|
||||||
"@zxing/library": "^0.21.3",
|
"@zxing/library": "^0.21.3",
|
||||||
|
"html-to-image": "^1.11.13",
|
||||||
"image-conversion": "^2.1.1",
|
"image-conversion": "^2.1.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"postcss-px-to-viewport-8-plugin": "^1.2.5",
|
"postcss-px-to-viewport-8-plugin": "^1.2.5",
|
||||||
@ -822,6 +823,12 @@
|
|||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/html-to-image": {
|
||||||
|
"version": "1.11.13",
|
||||||
|
"resolved": "https://registry.npmmirror.com/html-to-image/-/html-to-image-1.11.13.tgz",
|
||||||
|
"integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vant/area-data": "^2.1.0",
|
"@vant/area-data": "^2.1.0",
|
||||||
"@zxing/library": "^0.21.3",
|
"@zxing/library": "^0.21.3",
|
||||||
|
"html-to-image": "^1.11.13",
|
||||||
"image-conversion": "^2.1.1",
|
"image-conversion": "^2.1.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"postcss-px-to-viewport-8-plugin": "^1.2.5",
|
"postcss-px-to-viewport-8-plugin": "^1.2.5",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 16 KiB |
@ -1,235 +0,0 @@
|
|||||||
/*
|
|
||||||
* Canvas2Image v0.1
|
|
||||||
* Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk
|
|
||||||
* MIT License [http://www.opensource.org/licenses/mit-license.php]
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Canvas2Image = (function() {
|
|
||||||
|
|
||||||
// check if we have canvas support
|
|
||||||
var bHasCanvas = false;
|
|
||||||
var oCanvas = document.createElement("canvas");
|
|
||||||
if (oCanvas.getContext("2d")) {
|
|
||||||
bHasCanvas = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no canvas, bail out.
|
|
||||||
if (!bHasCanvas) {
|
|
||||||
return {
|
|
||||||
saveAsBMP : function(){},
|
|
||||||
saveAsPNG : function(){},
|
|
||||||
saveAsJPEG : function(){}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bHasImageData = !!(oCanvas.getContext("2d").getImageData);
|
|
||||||
var bHasDataURL = !!(oCanvas.toDataURL);
|
|
||||||
var bHasBase64 = !!(window.btoa);
|
|
||||||
|
|
||||||
var strDownloadMime = "image/octet-stream";
|
|
||||||
|
|
||||||
// ok, we're good
|
|
||||||
var readCanvasData = function(oCanvas) {
|
|
||||||
var iWidth = parseInt(oCanvas.width);
|
|
||||||
var iHeight = parseInt(oCanvas.height);
|
|
||||||
return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// base64 encodes either a string or an array of charcodes
|
|
||||||
var encodeData = function(data) {
|
|
||||||
var strData = "";
|
|
||||||
if (typeof data == "string") {
|
|
||||||
strData = data;
|
|
||||||
} else {
|
|
||||||
var aData = data;
|
|
||||||
for (var i=0;i<aData.length;i++) {
|
|
||||||
strData += String.fromCharCode(aData[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return btoa(strData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// creates a base64 encoded string containing BMP data
|
|
||||||
// takes an imagedata object as argument
|
|
||||||
var createBMP = function(oData) {
|
|
||||||
var aHeader = [];
|
|
||||||
|
|
||||||
var iWidth = oData.width;
|
|
||||||
var iHeight = oData.height;
|
|
||||||
|
|
||||||
aHeader.push(0x42); // magic 1
|
|
||||||
aHeader.push(0x4D);
|
|
||||||
|
|
||||||
var iFileSize = iWidth*iHeight*3 + 54; // total header size = 54 bytes
|
|
||||||
aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
|
|
||||||
aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
|
|
||||||
aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
|
|
||||||
aHeader.push(iFileSize % 256);
|
|
||||||
|
|
||||||
aHeader.push(0); // reserved
|
|
||||||
aHeader.push(0);
|
|
||||||
aHeader.push(0); // reserved
|
|
||||||
aHeader.push(0);
|
|
||||||
|
|
||||||
aHeader.push(54); // dataoffset
|
|
||||||
aHeader.push(0);
|
|
||||||
aHeader.push(0);
|
|
||||||
aHeader.push(0);
|
|
||||||
|
|
||||||
var aInfoHeader = [];
|
|
||||||
aInfoHeader.push(40); // info header size
|
|
||||||
aInfoHeader.push(0);
|
|
||||||
aInfoHeader.push(0);
|
|
||||||
aInfoHeader.push(0);
|
|
||||||
|
|
||||||
var iImageWidth = iWidth;
|
|
||||||
aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
|
|
||||||
aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
|
|
||||||
aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
|
|
||||||
aInfoHeader.push(iImageWidth % 256);
|
|
||||||
|
|
||||||
var iImageHeight = iHeight;
|
|
||||||
aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
|
|
||||||
aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
|
|
||||||
aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
|
|
||||||
aInfoHeader.push(iImageHeight % 256);
|
|
||||||
|
|
||||||
aInfoHeader.push(1); // num of planes
|
|
||||||
aInfoHeader.push(0);
|
|
||||||
|
|
||||||
aInfoHeader.push(24); // num of bits per pixel
|
|
||||||
aInfoHeader.push(0);
|
|
||||||
|
|
||||||
aInfoHeader.push(0); // compression = none
|
|
||||||
aInfoHeader.push(0);
|
|
||||||
aInfoHeader.push(0);
|
|
||||||
aInfoHeader.push(0);
|
|
||||||
|
|
||||||
var iDataSize = iWidth*iHeight*3;
|
|
||||||
aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
|
|
||||||
aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
|
|
||||||
aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
|
|
||||||
aInfoHeader.push(iDataSize % 256);
|
|
||||||
|
|
||||||
for (var i=0;i<16;i++) {
|
|
||||||
aInfoHeader.push(0); // these bytes not used
|
|
||||||
}
|
|
||||||
|
|
||||||
var iPadding = (4 - ((iWidth * 3) % 4)) % 4;
|
|
||||||
|
|
||||||
var aImgData = oData.data;
|
|
||||||
|
|
||||||
var strPixelData = "";
|
|
||||||
var y = iHeight;
|
|
||||||
do {
|
|
||||||
var iOffsetY = iWidth*(y-1)*4;
|
|
||||||
var strPixelRow = "";
|
|
||||||
for (var x=0;x<iWidth;x++) {
|
|
||||||
var iOffsetX = 4*x;
|
|
||||||
|
|
||||||
strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+2]);
|
|
||||||
strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+1]);
|
|
||||||
strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX]);
|
|
||||||
}
|
|
||||||
for (var c=0;c<iPadding;c++) {
|
|
||||||
strPixelRow += String.fromCharCode(0);
|
|
||||||
}
|
|
||||||
strPixelData += strPixelRow;
|
|
||||||
} while (--y);
|
|
||||||
|
|
||||||
var strEncoded = encodeData(aHeader.concat(aInfoHeader)) + encodeData(strPixelData);
|
|
||||||
|
|
||||||
return strEncoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// sends the generated file to the client
|
|
||||||
var saveFile = function(strData) {
|
|
||||||
document.location.href = strData;
|
|
||||||
}
|
|
||||||
|
|
||||||
var makeDataURI = function(strData, strMime) {
|
|
||||||
return "data:" + strMime + ";base64," + strData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// generates a <img> object containing the imagedata
|
|
||||||
var makeImageObject = function(strSource) {
|
|
||||||
var oImgElement = document.createElement("img");
|
|
||||||
oImgElement.src = strSource;
|
|
||||||
return oImgElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scaleCanvas = function(oCanvas, iWidth, iHeight) {
|
|
||||||
if (iWidth && iHeight) {
|
|
||||||
var oSaveCanvas = document.createElement("canvas");
|
|
||||||
oSaveCanvas.width = iWidth;
|
|
||||||
oSaveCanvas.height = iHeight;
|
|
||||||
oSaveCanvas.style.width = iWidth+"px";
|
|
||||||
oSaveCanvas.style.height = iHeight+"px";
|
|
||||||
|
|
||||||
var oSaveCtx = oSaveCanvas.getContext("2d");
|
|
||||||
|
|
||||||
oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iHeight);
|
|
||||||
return oSaveCanvas;
|
|
||||||
}
|
|
||||||
return oCanvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) {
|
|
||||||
if (!bHasDataURL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);
|
|
||||||
var strData = oScaledCanvas.toDataURL("image/png");
|
|
||||||
if (bReturnImg) {
|
|
||||||
return makeImageObject(strData);
|
|
||||||
} else {
|
|
||||||
saveFile(strData.replace("image/png", strDownloadMime));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) {
|
|
||||||
if (!bHasDataURL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);
|
|
||||||
var strMime = "image/jpeg";
|
|
||||||
var strData = oScaledCanvas.toDataURL(strMime);
|
|
||||||
|
|
||||||
// check if browser actually supports jpeg by looking for the mime type in the data uri.
|
|
||||||
// if not, return false
|
|
||||||
if (strData.indexOf(strMime) != 5) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bReturnImg) {
|
|
||||||
return makeImageObject(strData);
|
|
||||||
} else {
|
|
||||||
saveFile(strData.replace(strMime, strDownloadMime));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) {
|
|
||||||
if (!(bHasImageData && bHasBase64)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);
|
|
||||||
|
|
||||||
var oData = readCanvasData(oScaledCanvas);
|
|
||||||
var strImgData = createBMP(oData);
|
|
||||||
if (bReturnImg) {
|
|
||||||
return makeImageObject(makeDataURI(strImgData, "image/bmp"));
|
|
||||||
} else {
|
|
||||||
saveFile(makeDataURI(strImgData, strDownloadMime));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
||||||
22
public/js/html2canvas.min.js
vendored
22
public/js/html2canvas.min.js
vendored
File diff suppressed because one or more lines are too long
@ -268,6 +268,12 @@ const routes = [
|
|||||||
component: () => import('./views/Merchant/MerchantIntroduction.vue'),
|
component: () => import('./views/Merchant/MerchantIntroduction.vue'),
|
||||||
meta: { title: '商家资料' }
|
meta: { title: '商家资料' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/PayCode',
|
||||||
|
name: 'PayCode',
|
||||||
|
component: () => import('./views/Merchant/PayCode.vue'),
|
||||||
|
meta: { title: '收款码' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/QrReader',
|
path: '/QrReader',
|
||||||
name: 'QrReader',
|
name: 'QrReader',
|
||||||
|
|||||||
@ -5566,3 +5566,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paycode-page {
|
||||||
|
.f5;
|
||||||
|
}
|
||||||
@ -2298,26 +2298,37 @@ img {
|
|||||||
.box;
|
.box;
|
||||||
.box-tb;
|
.box-tb;
|
||||||
.box-align-center;
|
.box-align-center;
|
||||||
padding: 6.8vw 9.2vw;
|
|
||||||
width: 80vw;
|
width: 80vw;
|
||||||
min-width: 80vw;
|
min-width: 80vw;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 2.67vw;
|
border-radius: 2.67vw;
|
||||||
|
|
||||||
.share_box {
|
.share_box {
|
||||||
.box;
|
.box;
|
||||||
.box-tb;
|
.box-tb;
|
||||||
.box-align-center;
|
.box-align-center;
|
||||||
|
padding: 6.8vw 9.2vw;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
background-image: linear-gradient(180deg,
|
||||||
|
#f5cfd8 0%,
|
||||||
|
#fff4f4 100%);
|
||||||
|
border-radius: 2.67vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
height: 10.53vw;
|
width: 18.93vw;
|
||||||
margin-bottom: 4vw;
|
height: 22.27vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom_box {
|
||||||
|
.box;
|
||||||
|
.box-align-center;
|
||||||
|
.box-pack-around;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.share_userinfo {
|
.share_userinfo {
|
||||||
@ -2335,10 +2346,10 @@ img {
|
|||||||
.name {
|
.name {
|
||||||
.box;
|
.box;
|
||||||
.box-center-center;
|
.box-center-center;
|
||||||
color: #222222;
|
.bs;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background-color: #fde5eb;
|
background-color: #841e36;
|
||||||
border-radius: 3.33vw;
|
border-radius: 3.33vw;
|
||||||
// padding: .667vw 0;
|
// padding: .667vw 0;
|
||||||
// padding: 1.2vw 4vw 6vw;
|
// padding: 1.2vw 4vw 6vw;
|
||||||
@ -2350,7 +2361,7 @@ img {
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
line-height: 8vw;
|
line-height: 8vw;
|
||||||
margin-bottom: 2.4vw;
|
// margin-bottom: 2.4vw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2377,9 +2388,25 @@ img {
|
|||||||
height: 8vw;
|
height: 8vw;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
.box;
|
||||||
|
.box-align-center;
|
||||||
|
.box-pack-between;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 3.33vw;
|
||||||
|
border-bottom: 1px #fff solid;
|
||||||
|
|
||||||
.goods_price {
|
.goods_price {
|
||||||
margin-top: 1.33vw;
|
font-size: 4vw;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ea3e23;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_price {
|
||||||
|
color: #b1b1b1;
|
||||||
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2398,7 +2425,7 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.share_result {
|
.share_result {
|
||||||
margin-top: 4vw;
|
// margin-top: 4vw;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
|||||||
@ -1,159 +1,94 @@
|
|||||||
/**
|
/**
|
||||||
* HTML转图片工具
|
* HTML转图片工具
|
||||||
* 基于 html2canvas + canvas2image
|
* 基于 html-to-image 将DOM元素转换为图片
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { toCanvas } from 'html-to-image'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将HTML元素转换为canvas
|
* 将DOM元素转换为图片
|
||||||
* @param {HTMLElement} element - DOM元素
|
* @param {string|HTMLElement} selector - DOM选择器或元素
|
||||||
* @param {Object} options - 配置参数
|
* @param {Object} options - 配置选项
|
||||||
* @param {number} [options.scale=2] - 缩放比例
|
* @param {number} [options.width] - 自定义宽度(会应用到元素上)
|
||||||
* @param {number} [options.width] - 指定宽度
|
* @param {number} [options.height] - 自定义高度(会应用到元素上)
|
||||||
* @param {number} [options.height] - 指定高度
|
* @param {number} [options.canvasWidth] - 输出画布宽度
|
||||||
* @param {boolean} [options.useCORS=true] - 是否支持跨域
|
* @param {number} [options.canvasHeight] - 输出画布高度
|
||||||
* @param {number} [options.dpi=window.devicePixelRatio] - DPI
|
* @param {number} [options.pixelRatio] - 像素比例,默认2
|
||||||
* @returns {Promise<HTMLCanvasElement>}
|
* @param {boolean} [options.useCORS] - 是否允许跨域,默认true
|
||||||
|
* @param {string} [options.format] - 图片格式,'png'或'jpeg',默认'png'
|
||||||
|
* @param {number} [options.quality] - 图片质量,0-1,仅jpeg有效,默认1.0
|
||||||
|
* @returns {Promise<string>} - 返回base64图片数据URL
|
||||||
*/
|
*/
|
||||||
export async function html2canvas(element, options = {}) {
|
export async function toDataURL(selector, options = {}) {
|
||||||
const {
|
const {
|
||||||
scale = 2,
|
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
canvasWidth,
|
||||||
|
canvasHeight,
|
||||||
|
pixelRatio = 2,
|
||||||
useCORS = true,
|
useCORS = true,
|
||||||
dpi = window.devicePixelRatio,
|
format = 'png',
|
||||||
onclone,
|
quality = 1.0
|
||||||
} = options;
|
} = options
|
||||||
|
|
||||||
const opts = {
|
const element = typeof selector === 'string' ? document.querySelector(selector) : selector
|
||||||
scale,
|
if (!element) {
|
||||||
useCORS,
|
throw new Error('Element not found')
|
||||||
dpi,
|
|
||||||
logging: false,
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (width) opts.width = width;
|
|
||||||
if (height) opts.height = height;
|
|
||||||
|
|
||||||
// 添加 onclone 回调用于调试
|
|
||||||
if (onclone) {
|
|
||||||
opts.onclone = onclone;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const html2canvasFn = window.html2canvas;
|
const canvasEl = await toCanvas(element, {
|
||||||
if (!html2canvasFn) {
|
width,
|
||||||
throw new Error('html2canvas not loaded, please include html2canvas.min.js');
|
height,
|
||||||
}
|
canvasWidth,
|
||||||
|
canvasHeight,
|
||||||
|
pixelRatio,
|
||||||
|
cors: useCORS,
|
||||||
|
backgroundColor: '#ffffff'
|
||||||
|
})
|
||||||
|
|
||||||
return html2canvasFn(element, opts);
|
const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png'
|
||||||
|
return canvasEl.toDataURL(mimeType, quality)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将canvas转换为dataURL
|
* 将DOM元素转换为图片并下载
|
||||||
* @param {HTMLCanvasElement} canvas
|
* @param {string|HTMLElement} selector - DOM选择器或元素
|
||||||
* @param {string} [type='image/png']
|
* @param {string} [filename] - 下载文件名,不含扩展名
|
||||||
* @param {number} [quality=1.0]
|
* @param {Object} options - 配置选项,同toDataURL
|
||||||
* @returns {string}
|
|
||||||
*/
|
*/
|
||||||
export function canvasToDataURL(canvas, type = 'image/png', quality = 1.0) {
|
export async function toImageDownload(selector, filename = 'download', options = {}) {
|
||||||
return canvas.toDataURL(type, quality);
|
const dataUrl = await toDataURL(selector, options)
|
||||||
|
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.download = filename + '.png'
|
||||||
|
link.href = dataUrl
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将canvas转换为Blob
|
* 将DOM元素转换为Blob
|
||||||
* @param {HTMLCanvasElement} canvas
|
* @param {string|HTMLElement} selector - DOM选择器或元素
|
||||||
* @param {string} [type='image/png']
|
* @param {Object} options - 配置选项,同toDataURL
|
||||||
* @param {number} [quality=1.0]
|
|
||||||
* @returns {Promise<Blob>}
|
* @returns {Promise<Blob>}
|
||||||
*/
|
*/
|
||||||
export function canvasToBlob(canvas, type = 'image/png', quality = 1.0) {
|
export async function toBlob(selector, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
const dataUrl = await toDataURL(selector, options)
|
||||||
canvas.toBlob(blob => {
|
|
||||||
if (blob) {
|
const arr = dataUrl.split(',')
|
||||||
resolve(blob);
|
const mime = arr[0].match(/:(.*?);/)[1]
|
||||||
} else {
|
const bstr = atob(arr[1])
|
||||||
reject(new Error('Canvas to Blob failed'));
|
let n = bstr.length
|
||||||
|
const u8arr = new Uint8Array(n)
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n)
|
||||||
}
|
}
|
||||||
}, type, quality);
|
return new Blob([u8arr], { type: mime })
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export default {
|
||||||
* 将HTML元素直接转成dataURL图片
|
toDataURL,
|
||||||
* @param {HTMLElement} element - DOM元素
|
toImageDownload,
|
||||||
* @param {Object} options - html2canvas配置
|
toBlob
|
||||||
* @param {string} [type='image/png'] - 图片类型
|
|
||||||
* @param {number} [quality=1.0] - 图片质量
|
|
||||||
* @returns {Promise<string>} dataURL
|
|
||||||
*/
|
|
||||||
export async function htmlToDataURL(element, options = {}, type = 'image/png', quality = 1.0) {
|
|
||||||
const canvas = await html2canvas(element, options);
|
|
||||||
return canvasToDataURL(canvas, type, quality);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将HTML元素直接转成Blob
|
|
||||||
* @param {HTMLElement} element - DOM元素
|
|
||||||
* @param {Object} options - html2canvas配置
|
|
||||||
* @param {string} [type='image/png'] - 图片类型
|
|
||||||
* @param {number} [quality=1.0] - 图片质量
|
|
||||||
* @returns {Promise<Blob>}
|
|
||||||
*/
|
|
||||||
export async function htmlToBlob(element, options = {}, type = 'image/png', quality = 1.0) {
|
|
||||||
const canvas = await html2canvas(element, options);
|
|
||||||
return canvasToBlob(canvas, type, quality);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载canvas为图片
|
|
||||||
* @param {HTMLCanvasElement} canvas
|
|
||||||
* @param {string} filename - 文件名(不带扩展名)
|
|
||||||
* @param {string} [type='png'] - 图片格式 ('png' | 'jpeg' | 'webp')
|
|
||||||
*/
|
|
||||||
export function downloadCanvas(canvas, filename, type = 'png') {
|
|
||||||
const mimeType = `image/${type}`;
|
|
||||||
const dataURL = canvasToDataURL(canvas, mimeType, 1.0);
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.download = `${filename}.${type}`;
|
|
||||||
link.href = dataURL;
|
|
||||||
link.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将HTML元素转成图片并下载
|
|
||||||
* @param {HTMLElement} element - DOM元素
|
|
||||||
* @param {string} filename - 文件名(不带扩展名)
|
|
||||||
* @param {Object} options - html2canvas配置
|
|
||||||
* @param {string} [type='png'] - 图片格式
|
|
||||||
*/
|
|
||||||
export async function htmlToImage(element, filename, options = {}, type = 'png') {
|
|
||||||
const canvas = await html2canvas(element, options);
|
|
||||||
downloadCanvas(canvas, filename, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将dataURL保存为图片文件
|
|
||||||
* @param {string} dataURL - 图片dataURL
|
|
||||||
* @param {string} filename - 文件名(不带扩展名)
|
|
||||||
*/
|
|
||||||
export function downloadDataURL(dataURL, filename) {
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.download = filename;
|
|
||||||
link.href = dataURL;
|
|
||||||
link.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将Blob保存为图片文件
|
|
||||||
* @param {Blob} blob - 图片Blob
|
|
||||||
* @param {string} filename - 文件名(不带扩展名)
|
|
||||||
*/
|
|
||||||
export async function downloadBlob(blob, filename) {
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.download = filename;
|
|
||||||
link.href = url;
|
|
||||||
link.click();
|
|
||||||
setTimeout(() => URL.revokeObjectURL(url), 100);
|
|
||||||
}
|
}
|
||||||
@ -125,7 +125,7 @@
|
|||||||
|
|
||||||
<van-popup v-model:show="show" class="shareimgbox" style="max-width: 80vw;">
|
<van-popup v-model:show="show" class="shareimgbox" style="max-width: 80vw;">
|
||||||
<div class="share_box" id="bsCard">
|
<div class="share_box" id="bsCard">
|
||||||
<img class="logo" src="/img/logo-lr.png" alt="">
|
|
||||||
|
|
||||||
<div class="share_userinfo">
|
<div class="share_userinfo">
|
||||||
<img :src="$file($userInfo.userimg)" alt="">
|
<img :src="$file($userInfo.userimg)" alt="">
|
||||||
@ -135,18 +135,24 @@
|
|||||||
<div class="goods_info">
|
<div class="goods_info">
|
||||||
<img class="goods_img" :src="$file(data.img)" alt="">
|
<img class="goods_img" :src="$file(data.img)" alt="">
|
||||||
<div class="goods_name">{{ data.name }}</div>
|
<div class="goods_name">{{ data.name }}</div>
|
||||||
<div class="goods_price">¥{{ data.saleprice?.toFixed(2) || '0.00' }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="price">
|
||||||
|
<div class="goods_price">¥{{ data.saleprice?.toFixed(2) || '0.00' }}</div>
|
||||||
|
<div class="o_price">¥{{ data.originalprice?.toFixed(2) || '0.00' }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bottom_box">
|
||||||
<div class="qrcode">
|
<div class="qrcode">
|
||||||
<vue-qr :margin="0" :text="shareLink" backgroundColor="rgb(255,255,255)"
|
<vue-qr :margin="0" :text="shareLink" backgroundColor="#ffffff00" colorLight="rgb(255,255,255)"></vue-qr>
|
||||||
colorLight="rgb(255,255,255)"></vue-qr>
|
|
||||||
长按扫码购买
|
长按扫码购买
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<img class="logo" src="/img/logo.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="share_result" v-if="ShareImg">
|
<div class="share_result" v-if="ShareImg">
|
||||||
<img :src="ShareImg" alt="分享海报">
|
<img :src="ShareImg" alt="分享海报">
|
||||||
<!-- <div class="tips">长按保存图片</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</van-popup>
|
</van-popup>
|
||||||
|
|
||||||
@ -158,7 +164,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { html2canvas, canvasToDataURL } from '@/utils/html2image';
|
|
||||||
|
import { toDataURL } from '@/utils/html2image.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GoodsDetail',
|
name: 'GoodsDetail',
|
||||||
@ -182,7 +189,8 @@ export default {
|
|||||||
],
|
],
|
||||||
shareLink: '',
|
shareLink: '',
|
||||||
ShareImg: '',
|
ShareImg: '',
|
||||||
loading: false
|
loading: false,
|
||||||
|
transparent: 'rgba(0,0,0,0)'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -242,7 +250,7 @@ export default {
|
|||||||
// 等待 DOM 渲染后再生成
|
// 等待 DOM 渲染后再生成
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.generateShareImg();
|
this.generateShareImg();
|
||||||
}, 200);
|
}, 500);
|
||||||
} else if (option.name == "复制链接") {
|
} else if (option.name == "复制链接") {
|
||||||
this.$copyText(location.href);
|
this.$copyText(location.href);
|
||||||
this.$showToast({ type: "success", message: "复制成功" });
|
this.$showToast({ type: "success", message: "复制成功" });
|
||||||
@ -263,19 +271,29 @@ export default {
|
|||||||
async generateShareImg() {
|
async generateShareImg() {
|
||||||
var shareContent = document.querySelector('#bsCard');
|
var shareContent = document.querySelector('#bsCard');
|
||||||
if (!shareContent) return;
|
if (!shareContent) return;
|
||||||
var width = shareContent.offsetWidth;
|
|
||||||
var height = shareContent.offsetHeight;
|
|
||||||
try {
|
try {
|
||||||
var canvas = await html2canvas(shareContent, {
|
// Wait for all images inside shareContent to load
|
||||||
scale: 2,
|
const images = shareContent.querySelectorAll('img');
|
||||||
width: width,
|
for (const img of images) {
|
||||||
height: height,
|
if (!img.complete) {
|
||||||
useCORS: true,
|
await new Promise((resolve) => {
|
||||||
allowTaint: true,
|
img.onload = resolve;
|
||||||
|
img.onerror = resolve;
|
||||||
});
|
});
|
||||||
this.ShareImg = canvasToDataURL(canvas, "image/png", 1.0);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for vue-qr to render
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
this.ShareImg = await toDataURL(shareContent, {
|
||||||
|
pixelRatio: 2,
|
||||||
|
useCORS: true,
|
||||||
|
format: 'png'
|
||||||
|
});
|
||||||
|
document.querySelector('.share_box').style = 'display: none';
|
||||||
|
this.loading = false;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
document.querySelector('.share_box').style.display = 'none';
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|||||||
@ -79,7 +79,8 @@ export default {
|
|||||||
onTabClick(name) {
|
onTabClick(name) {
|
||||||
if (this.$route.name === name) {
|
if (this.$route.name === name) {
|
||||||
// 当前路由,双击刷新
|
// 当前路由,双击刷新
|
||||||
this.$router.replace({ name })
|
// this.$router.replace({ name })
|
||||||
|
location.reload()
|
||||||
} else {
|
} else {
|
||||||
this.$router.push({ name })
|
this.$router.push({ name })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasePage>
|
<!-- <BasePage> -->
|
||||||
<ManagerPopup v-model="managerPopupVisible" />
|
<ManagerPopup v-model="managerPopupVisible" />
|
||||||
<div class="merchant">
|
<div class="merchant">
|
||||||
<div class="shopinfo">
|
<div class="shopinfo">
|
||||||
@ -74,7 +74,7 @@
|
|||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<div class="fastTo">
|
<div class="fastTo">
|
||||||
<div class="ft1" @click="showBottom = true">
|
<div class="ft1" @click="$navigate('PayCode?id=' + id)">
|
||||||
<span>收款码</span>
|
<span>收款码</span>
|
||||||
<img src="/img/ft_i1.png" alt="">
|
<img src="/img/ft_i1.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
@ -138,32 +138,11 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<van-action-sheet v-model:show="showBottom" title="收款码" @click-overlay="onCloseBottom" @opened="onOpenBottom">
|
<!-- </BasePage> -->
|
||||||
<div class="paycode-wrap">
|
|
||||||
<div class="paycode-original" ref="paycodeOriginal" v-show="!paycodeImg">
|
|
||||||
<img src="/img/paycode.jpg" alt="" style="width: 100%; display: block;">
|
|
||||||
<div class="info">
|
|
||||||
<img :src="$file(data.shopimg)" alt="" style="border-radius: 50%;">
|
|
||||||
</div>
|
|
||||||
<div class="name">
|
|
||||||
<span>{{ data.shopname }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="code">
|
|
||||||
<vue-qr v-if="!loading" :margin="0" :text="link" backgroundColor="rgb(255,255,255)"
|
|
||||||
colorLight="rgb(255,255,255)"></vue-qr>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="paycode-result" v-if="paycodeImg">
|
|
||||||
<img :src="paycodeImg" style="width: 100%;" alt="收款码">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</van-action-sheet>
|
|
||||||
</BasePage>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { html2canvas } from '@/utils/html2image'
|
|
||||||
import ManagerPopup from "@/components/ManagerPopup.vue"
|
import ManagerPopup from "@/components/ManagerPopup.vue"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -181,33 +160,11 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
managerPopupVisible: false,
|
managerPopupVisible: false,
|
||||||
showInfo: false,
|
|
||||||
showBottom: false,
|
|
||||||
paycodeImg: '',
|
|
||||||
isCapturing: false,
|
|
||||||
link: '',
|
|
||||||
id: this.$route.query.id,
|
id: this.$route.query.id,
|
||||||
data: {
|
data: {
|
||||||
id: 'merchant_001',
|
|
||||||
MerchantAvatar: '',
|
|
||||||
MerchantName: '泰古润直营店',
|
|
||||||
MerchantPhone: '13800138000',
|
|
||||||
Income: 12868.50,
|
|
||||||
Point: 5680.00,
|
|
||||||
TodayAmount: 5680.00,
|
|
||||||
YesterdayAmount: 4200.00,
|
|
||||||
TodayDeduct: 1280.00,
|
|
||||||
YesterdayDeduct: 980.00,
|
|
||||||
TodayQty: 45,
|
|
||||||
YesterdayQty: 38,
|
|
||||||
MonthIncome: 45600.00,
|
|
||||||
PrevMonthIncome: 38500.00,
|
|
||||||
endTimes: new Date().toLocaleString('zh-CN', { hour12: false })
|
endTimes: new Date().toLocaleString('zh-CN', { hour12: false })
|
||||||
},
|
},
|
||||||
countData: {},
|
countData: {},
|
||||||
States: 0,
|
|
||||||
expiring: 3,
|
|
||||||
loading: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -226,87 +183,14 @@ export default {
|
|||||||
init() {
|
init() {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
this.$get(`/v1/client/DShopsClient/${this.id}`).then(res => {
|
this.$get(`/v1/client/DShopsClient/${this.id}`).then(res => {
|
||||||
// console.log(res);
|
|
||||||
this.data = res.data;
|
this.data = res.data;
|
||||||
this.data.endTimes = new Date().toLocaleString('zh-CN', { hour12: false });
|
this.data.endTimes = new Date().toLocaleString('zh-CN', { hour12: false });
|
||||||
this.$ls.set('merchant_shopname', res.data.shopname);
|
this.$ls.set('merchant_shopname', res.data.shopname);
|
||||||
this.$ls.set('merchant_shopimg', res.data.shopimg);
|
this.$ls.set('merchant_shopimg', res.data.shopimg);
|
||||||
this.$ls.set('merchant_cellphone', res.data.cellphone);
|
this.$ls.set('merchant_cellphone', res.data.cellphone);
|
||||||
|
|
||||||
this.link = `${location.origin}${location.pathname}#/Checkout?id=${res.data.userid}`
|
|
||||||
}),
|
}),
|
||||||
this.getCount()
|
this.getCount()
|
||||||
]
|
])
|
||||||
)
|
|
||||||
},
|
|
||||||
async onOpenBottom() {
|
|
||||||
this.paycodeImg = ''
|
|
||||||
this.$nextTick(async () => {
|
|
||||||
const el = this.$refs.paycodeOriginal
|
|
||||||
if (!el) return
|
|
||||||
|
|
||||||
const scale = 2
|
|
||||||
const elRect = el.getBoundingClientRect()
|
|
||||||
|
|
||||||
// 不画全图,改为各部分分别截图再合成
|
|
||||||
// 创建最终画布
|
|
||||||
const canvas = document.createElement('canvas')
|
|
||||||
const w = el.offsetWidth * scale
|
|
||||||
const h = el.offsetHeight * scale
|
|
||||||
canvas.width = w
|
|
||||||
canvas.height = h
|
|
||||||
const ctx = canvas.getContext('2d')
|
|
||||||
|
|
||||||
// 画背景图(只截取背景img)
|
|
||||||
const bg = el.querySelector('img')
|
|
||||||
const bgCanvas = await html2canvas(bg, { scale, useCORS: true, width: el.offsetWidth, height: el.offsetHeight })
|
|
||||||
ctx.drawImage(bgCanvas, 0, 0, w, h)
|
|
||||||
|
|
||||||
// 截取头像并画成圆形
|
|
||||||
const infoEl = el.querySelector('.info')
|
|
||||||
const avatarEl = infoEl.querySelector('img')
|
|
||||||
const avatarCanvas = await html2canvas(avatarEl, { scale, useCORS: true, backgroundColor: 'transparent' })
|
|
||||||
|
|
||||||
const infoRect = infoEl.getBoundingClientRect()
|
|
||||||
const avatarSize = 30 * scale
|
|
||||||
const avatarX = (infoRect.left - elRect.left) * scale
|
|
||||||
const avatarY = (infoRect.top - elRect.top) * scale
|
|
||||||
|
|
||||||
ctx.save()
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2)
|
|
||||||
ctx.clip()
|
|
||||||
ctx.drawImage(avatarCanvas, avatarX, avatarY, avatarSize, avatarSize)
|
|
||||||
ctx.restore()
|
|
||||||
|
|
||||||
// 截取二维码
|
|
||||||
const codeEl = el.querySelector('.code')
|
|
||||||
const codeCanvas = await html2canvas(codeEl, { scale, useCORS: true, backgroundColor: '#ffffff' })
|
|
||||||
const codeRect = codeEl.getBoundingClientRect()
|
|
||||||
ctx.drawImage(
|
|
||||||
codeCanvas,
|
|
||||||
(codeRect.left - elRect.left) * scale,
|
|
||||||
(codeRect.top - elRect.top) * scale,
|
|
||||||
codeRect.width * scale,
|
|
||||||
codeRect.height * scale
|
|
||||||
)
|
|
||||||
|
|
||||||
// 画商户名文字 (根据.name样式定位)
|
|
||||||
const nameEl = el.querySelector('.name')
|
|
||||||
const nameRect = nameEl.getBoundingClientRect()
|
|
||||||
const fontSize = 20 * scale
|
|
||||||
ctx.font = `${fontSize}px 'PingFang SC'`
|
|
||||||
ctx.fillStyle = '#333'
|
|
||||||
ctx.textBaseline = 'middle'
|
|
||||||
if (this.data.shopname)
|
|
||||||
ctx.fillText(this.truncatedShopname, (nameRect.left - elRect.left) * scale, (nameRect.top - elRect.top) * scale + fontSize / 2)
|
|
||||||
|
|
||||||
this.paycodeImg = canvas.toDataURL('image/png')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onCloseBottom() {
|
|
||||||
this.showBottom = false
|
|
||||||
// this.paycodeImg = ''
|
|
||||||
},
|
},
|
||||||
getCount() {
|
getCount() {
|
||||||
this.$get('/v1/client/DShopsClient/statistics').then(res => {
|
this.$get('/v1/client/DShopsClient/statistics').then(res => {
|
||||||
@ -320,51 +204,4 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.paycode-original {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
font-family: 'PingFang SC';
|
|
||||||
|
|
||||||
>img {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
position: absolute;
|
|
||||||
left: 30.67vw;
|
|
||||||
top: 32.27vw;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 6.4vw;
|
|
||||||
height: 6.4vw;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
position: absolute;
|
|
||||||
left: 39.33vw;
|
|
||||||
top: 33.47vw;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 4vw;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.code {
|
|
||||||
position: absolute;
|
|
||||||
top: 42.4vw;
|
|
||||||
left: 23.33vw;
|
|
||||||
|
|
||||||
canvas,
|
|
||||||
img {
|
|
||||||
width: 53.33vw;
|
|
||||||
height: 53.33vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
196
src/views/Merchant/PayCode.vue
Normal file
196
src/views/Merchant/PayCode.vue
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<BasePage>
|
||||||
|
<div class="paycode-page">
|
||||||
|
<div class="paycode-wrap">
|
||||||
|
<!-- 生成后的图片 -->
|
||||||
|
<div v-if="generatedImage" class="paycode-generated">
|
||||||
|
<img :src="generatedImage" alt="收款码">
|
||||||
|
</div>
|
||||||
|
<!-- 原始元素 -->
|
||||||
|
<div v-else class="paycode-original" ref="paycodeOriginalRef">
|
||||||
|
<img src="/img/paycode.jpg" alt="" style="width: 100%; display: block;" class="bg-img" @load="onBgLoad">
|
||||||
|
<div class="user-info">
|
||||||
|
<div class="info">
|
||||||
|
<img :src="$file(shopimg)" alt="" style="border-radius: 50%;">
|
||||||
|
</div>
|
||||||
|
<div class="name">
|
||||||
|
<span>{{ shopname?.length > 8 ? shopname.slice(0, 8) + '...' : shopname }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="code">
|
||||||
|
<vue-qr v-if="link" :margin="0" :text="link" backgroundColor="rgb(255,255,255)"
|
||||||
|
colorLight="rgb(255,255,255)"></vue-qr>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="save-btn">
|
||||||
|
<van-button type="primary" color="#ca2904" round block :loading="loading" @click="handleSave">{{ loading ?
|
||||||
|
'生成中...' : ('保存图片')
|
||||||
|
}}</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BasePage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { toDataURL } from '@/utils/html2image'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PayCode',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
shopname: '',
|
||||||
|
shopimg: '',
|
||||||
|
link: '',
|
||||||
|
loading: false,
|
||||||
|
generatedImage: '',
|
||||||
|
bgLoaded: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init() {
|
||||||
|
const id = this.$route.query.id
|
||||||
|
this.$get(`/v1/client/DShopsClient/${id}`).then(res => {
|
||||||
|
this.shopname = res.data.shopname
|
||||||
|
this.shopimg = res.data.shopimg
|
||||||
|
this.link = `${location.origin}${location.pathname}#/Checkout?id=${res.data.userid}`
|
||||||
|
}).catch(err => {
|
||||||
|
this.$showFailToast('加载失败')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onBgLoad() {
|
||||||
|
this.bgLoaded = true
|
||||||
|
// 背景图加载完成后自动生成图片
|
||||||
|
this.generateImage()
|
||||||
|
},
|
||||||
|
|
||||||
|
async generateImage() {
|
||||||
|
if (!this.bgLoaded || this.loading) return
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
await this.$nextTick()
|
||||||
|
await document.fonts.ready
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300))
|
||||||
|
const el = this.$refs.paycodeOriginalRef
|
||||||
|
this.generatedImage = await toDataURL(el, { format: 'png', pixelRatio: 3, useCORS: true })
|
||||||
|
} catch (e) {
|
||||||
|
this.$showFailToast('生成失败')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleSave() {
|
||||||
|
if (this.loading) return
|
||||||
|
// 如果已生成图片,直接下载
|
||||||
|
if (this.generatedImage) {
|
||||||
|
this.downloadByBlob(this.generatedImage, `收款码_${this.shopname}.png`)
|
||||||
|
this.$showSuccessToast('保存成功')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 否则生成图片
|
||||||
|
await this.generateImage()
|
||||||
|
if (this.generatedImage) {
|
||||||
|
this.downloadByBlob(this.generatedImage, `收款码_${this.shopname}.png`)
|
||||||
|
this.$showSuccessToast('保存成功')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
downloadByBlob(dataUrl, filename) {
|
||||||
|
const arr = dataUrl.split(',')
|
||||||
|
const mime = arr[0].match(/:(.*?);/)[1]
|
||||||
|
const bstr = atob(arr[1])
|
||||||
|
let n = bstr.length
|
||||||
|
const u8arr = new Uint8Array(n)
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n)
|
||||||
|
}
|
||||||
|
const blob = new Blob([u8arr], { type: mime })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.download = filename
|
||||||
|
link.href = url
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.paycode-page {
|
||||||
|
// min-height: 100vh;
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paycode-generated {
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.paycode-original {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
font-family: 'PingFang SC';
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
top: 32.27vw;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 6.4vw;
|
||||||
|
height: 6.4vw;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin-left: 2.67vw;
|
||||||
|
// margin-bottom: 2.4vw;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 4vw;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
position: absolute;
|
||||||
|
top: 42.4vw;
|
||||||
|
left: 23.33vw;
|
||||||
|
|
||||||
|
canvas,
|
||||||
|
img {
|
||||||
|
width: 53.33vw;
|
||||||
|
height: 53.33vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasePage>
|
<!-- <BasePage> -->
|
||||||
<ManagerPopup v-model="managerPopupVisible" />
|
<ManagerPopup v-model="managerPopupVisible" />
|
||||||
<div class="opera">
|
<div class="opera">
|
||||||
|
|
||||||
@ -84,7 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BasePage>
|
<!-- </BasePage> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<img src="/img/search.png" alt="">
|
<img src="/img/search.png" alt="">
|
||||||
<input v-model="searchParams.Key" placeholder="请输入关键词搜索"
|
<input v-model="searchParams.Key" placeholder="请输入关键词搜索"
|
||||||
@search="$navigate('Category?Key=' + searchParams.Key)">
|
@search="$navigate('Category?Key=' + searchParams.Key)">
|
||||||
<button @click="$navigate('Category?Key=' + searchParams.Key)">搜索</button>
|
<button @click="$navigate('Category?Key=' + searchParams.Key)" :style="btnStyle">搜索</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
<van-swipe :autoplay="3000" indicator-color="white" @change="onSwipeChange">
|
<van-swipe :autoplay="3000" indicator-color="white" @change="onSwipeChange">
|
||||||
@ -288,6 +288,14 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
|
},
|
||||||
|
btnStyle() {
|
||||||
|
if (this.bgcolor) {
|
||||||
|
return {
|
||||||
|
background: this.bgcolor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,8 +27,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { html2canvas, canvasToDataURL } from '@/utils/html2image';
|
import { toDataURL } from '@/utils/html2image';
|
||||||
import { useUserStore } from '@/stores/user';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Invite',
|
name: 'Invite',
|
||||||
@ -41,7 +40,7 @@ export default {
|
|||||||
NickName: '',
|
NickName: '',
|
||||||
},
|
},
|
||||||
link: '',
|
link: '',
|
||||||
userStore: useUserStore(),
|
userStore: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -77,8 +76,6 @@ export default {
|
|||||||
},
|
},
|
||||||
async CreatePoster(divID, targetID) {
|
async CreatePoster(divID, targetID) {
|
||||||
var shareContent = document.querySelector(divID);
|
var shareContent = document.querySelector(divID);
|
||||||
var width = shareContent.offsetWidth;
|
|
||||||
var height = shareContent.offsetHeight;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 等待头像图片加载完成
|
// 等待头像图片加载完成
|
||||||
@ -91,18 +88,11 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var canvas = await html2canvas(shareContent, {
|
var dataUrl = await toDataURL(shareContent, { format: 'png', pixelRatio: 2, useCORS: true });
|
||||||
scale: 2,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
useCORS: true,
|
|
||||||
allowTaint: false,
|
|
||||||
});
|
|
||||||
var dataUrl = canvasToDataURL(canvas, "image/png", 1.0);
|
|
||||||
var newImg = document.createElement("img");
|
var newImg = document.createElement("img");
|
||||||
newImg.src = dataUrl;
|
newImg.src = dataUrl;
|
||||||
newImg.width = width;
|
newImg.width = shareContent.offsetWidth;
|
||||||
newImg.height = height;
|
newImg.height = shareContent.offsetHeight;
|
||||||
document.querySelector(targetID).appendChild(newImg);
|
document.querySelector(targetID).appendChild(newImg);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('生成海报失败', err);
|
console.error('生成海报失败', err);
|
||||||
@ -128,7 +118,7 @@ export default {
|
|||||||
.name {
|
.name {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 20.27vw;
|
left: 20.27vw;
|
||||||
bottom: 25.6vw;
|
bottom: 23.6vw;
|
||||||
line-height: 6vw;
|
line-height: 6vw;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
<div class="verif">
|
<div class="verif">
|
||||||
<div class="search_box">
|
<div class="search_box">
|
||||||
<img src="/img/search.png" alt="">
|
<img src="/img/search.png" alt="">
|
||||||
<input type="text" placeholder="输入用户账号/礼品券码搜索" v-model="keyword1">
|
<input type="text" placeholder="输入用户账号/礼品券码搜索" v-model="keyword1" v-if="type === 'verif'">
|
||||||
|
<input type="text" placeholder="输入用户账号/会员卡号搜索" v-model="keyword1" v-else>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BaseList ref="listRef"
|
<BaseList ref="listRef"
|
||||||
@ -17,9 +18,12 @@
|
|||||||
<span>{{ item.nickname || item.username || '未知用户' }}</span>
|
<span>{{ item.nickname || item.username || '未知用户' }}</span>
|
||||||
<p>账号:{{ item.cellphone || item.username }}</p>
|
<p>账号:{{ item.cellphone || item.username }}</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="r tt">
|
<span class="r tt" v-if="type === 'verif'">
|
||||||
已核销
|
已核销
|
||||||
</span>
|
</span>
|
||||||
|
<span class="r pre" v-else>
|
||||||
|
增送额度:<b style="color: f00;">500.00</b>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info_box">
|
<div class="info_box">
|
||||||
<div>
|
<div>
|
||||||
@ -28,17 +32,27 @@
|
|||||||
src="/img/copy_b.png" alt=""></span>
|
src="/img/copy_b.png" alt=""></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div v-if="type === 'verif'">
|
||||||
<span class="left">礼品券码:</span>
|
<span class="left">礼品券码:</span>
|
||||||
<span class="r rg">{{ item.sysid }}<img @click="$copyText(item.sysid); $showSuccessToast('复制成功')"
|
<span class="r rg">{{ item.sysid }}<img @click="$copyText(item.sysid); $showSuccessToast('复制成功')"
|
||||||
src="/img/copy_b.png" alt=""></span>
|
src="/img/copy_b.png" alt=""></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div v-else>
|
||||||
|
<span class="left">会员卡号:</span>
|
||||||
|
<span class="r rg">{{ item.huiyuankaid }}<img
|
||||||
|
@click="$copyText(item.huiyuankaid); $showSuccessToast('复制成功')" src="/img/copy_b.png" alt=""></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="type === 'verif'">
|
||||||
<span class="left">核销时间:</span>
|
<span class="left">核销时间:</span>
|
||||||
<span class="r rg">{{ formatTime(item.writeofftime) }}</span>
|
<span class="r rg">{{ formatTime(item.writeofftime) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<span class="left">赠送时间:</span>
|
||||||
|
<span class="r rg">{{ formatTime(item.addtime) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user