fix: 海报优化

This commit is contained in:
lukaijie
2022-12-09 10:04:19 +08:00
parent f6acce145a
commit 86ae200b34
16 changed files with 92 additions and 1081 deletions

View File

@@ -336,10 +336,10 @@ export default {
'navigationBarTextStyle': 'black'
},
plugins: {
'live-player-plugin': {
'version': '1.3.0', // 注意填写该直播组件最新版本号,微信开发者工具调试时可获取最新版本号(复制时请去掉注释)
'provider': 'wx2b03c6e691cd7370' // 必须填该直播组件appid该示例值即为直播组件appid复制时请去掉注释
}
// 'live-player-plugin': {
// 'version': '1.3.0', // 注意填写该直播组件最新版本号,微信开发者工具调试时可获取最新版本号(复制时请去掉注释)
// 'provider': 'wx2b03c6e691cd7370' // 必须填该直播组件appid该示例值即为直播组件appid复制时请去掉注释
// }
},
requiredPrivateInfos: [
"getLocation",

View File

@@ -61,7 +61,7 @@ function SpAddress(props) {
level
}
})
console.log('resAddressList:', resAddressList, _areaList)
// console.log('resAddressList:', resAddressList, _areaList)
setState((draft) => {
draft.addressList = resAddressList
draft.areaList = _areaList
@@ -123,7 +123,7 @@ function SpAddress(props) {
console.log('pindex',pindex);
console.log('selectValue',selectValue);
console.log('areaList',areaList);
return (
<View className='sp-address'>
<View className='address-content'></View>
@@ -131,7 +131,7 @@ function SpAddress(props) {
<View className='address-hd'>
{selectValue.map((vitem, vindex) => (
<View
className={classNames(`tab-item`,
className={classNames(`tab-item`,
{
active: pindex === vindex || vindex === 2 && pindex === 3
})}

View File

@@ -1,34 +1,18 @@
import Taro, { Component } from '@tarojs/taro'
import api from '@/api'
import { getExtConfigData, isAlipay } from '@/utils'
import { drawText, drawImage, drawBlock } from './helper.alipay'
import { drawText, drawImage, drawBlock } from './helper'
// const canvasWidth = 750
// const canvasHeight = 1335
const UI_Width = 750
//同步获取设备系统信息
const info = Taro.getSystemInfoSync()
//设备像素密度
const dpr = info.pixelRatio;
//计算比例
const scale = info.screenWidth / UI_Width
console.log('scale', scale)
//计算canvas实际渲染尺寸
let width = parseInt(scale * 335)
let height = parseInt(scale * 596)
//计算canvas画布尺寸
let canvasWidth = width * dpr
let canvasHeight = height * dpr
let alipayScaleW = 1.5
let alipayScaleH = 1.2
const canvasWidth = 600
const canvasHeight = 960
class DistributionPoster {
constructor(props) {
const { ctx, info, userInfo, toPx, toRpx } = props
this.ctx = ctx
ctx.scale(dpr, dpr)
this.info = info
this.userInfo = userInfo
this.toPx = toPx
@@ -51,18 +35,15 @@ class DistributionPoster {
const url = (isOpenShop && shop_status == 1) ? `marketing/pages/distribution/shop-home` : `pages/index`
let wxappCode
// TODO 获取微信二维码的接口需要换alipay https://ecshopx1.shopex123.com/api/h5app/alipaymini/qrcode.png?company_id=1&page=page/index
if(isAlipay){
// const res = await api.alipay.alipay_qrcode(`page=${`pages/item/espier-detail`}&appid=${appid}&company_id=${company_id}&id=${itemId}&uid=${user_id}`)
const res = await Taro.request({
url: `${host}/api/h5app/alipaymini/qrcode.png?path=${url}&appid=${appid}&company_id=${company_id}&user_id=${user_id}`, //仅为示例,并非真实的接口地址
header: {
'content-type': 'application/json' // 默认值
},
})
wxappCode = res.data.data.qr_code_url
}else{
wxappCode = `${process.env.APP_BASE_URL}/promoter/qrcode.png?path=${url}&appid=${appid}&company_id=${company_id}&user_id=${user_id}`
}
// const res = await api.alipay.alipay_qrcode(`page=${`pages/item/espier-detail`}&appid=${appid}&company_id=${company_id}&id=${itemId}&uid=${user_id}`)
const res = await Taro.request({
url: `${host}/api/h5app/alipaymini/qrcode.png?path=${url}&appid=${appid}&company_id=${company_id}&user_id=${user_id}`, //仅为示例,并非真实的接口地址
header: {
'content-type': 'application/json' // 默认值
},
})
wxappCode = res.data.data.qr_code_url
debugger
console.log('DistributionPoster-wxappCode:', wxappCode)
@@ -99,12 +80,12 @@ class DistributionPoster {
imgPath: this.bkg.path,
x: 0,
y: 0,
w: isAlipay ? canvasWidth / alipayScaleW : canvasWidth,
h: isAlipay ? canvasHeight / alipayScaleH : canvasHeight,
w: canvasWidth,
h: canvasHeight,
sx: 0,
sy: 0,
sw: isAlipay ? this.bkg.width : this.bkg.width,
sh: isAlipay ? this.bkg.height : this.bkg.height,
sw: this.bkg.width,
sh: this.bkg.height,
// borderRadius: 80
},
drawOptions
@@ -113,24 +94,24 @@ class DistributionPoster {
drawImage(
{
imgPath: this.avatar.path,
x: isAlipay ? 40 : 220,
y: isAlipay ? 20 : 60,
w: isAlipay ? 30 : 80,
h: isAlipay ? 30 : 80,
x: 145,
y: 50,
w: 80,
h: 80,
sx: 0,
sy: 0,
sw: isAlipay ? 30 : this.avatar.width,
sh: isAlipay ? 30 : this.avatar.height,
borderRadius: isAlipay ? 30 : 80
sw: this.avatar.width,
sh: this.avatar.height,
borderRadius: 80
},
drawOptions
)
// 姓名
drawText(
{
x: isAlipay ? 76 : 310,
y: isAlipay ? 34 : 110,
fontSize: isAlipay ? 10 : 30,
x: 260,
y: 100,
fontSize: 30,
color: '#000',
text: username
},
@@ -140,14 +121,14 @@ class DistributionPoster {
drawImage(
{
imgPath: this.codeImg.path,
x: isAlipay ? 30 : 120,
y: isAlipay ? 90 : 620,
w: isAlipay ? 220 : 480,
h: isAlipay ? 240 : 480,
x: 140,
y: 442,
w: 320,
h: 390,
sx: 0,
sy: 0,
sw:this.codeImg.width,
sh:this.codeImg.width
sw: this.codeImg.width,
sh: this.codeImg.height
},
drawOptions
)

View File

@@ -1,7 +1,7 @@
import Taro, { Component } from '@tarojs/taro'
import api from '@/api'
import { getExtConfigData } from '@/utils'
import { drawText, drawImage, drawBlock, drawLine } from './helper.alipay'
import { drawText, drawImage, drawBlock, drawLine } from './helper'
const canvasWidth = 600
const canvasHeight = 960

View File

@@ -1,7 +1,7 @@
import Taro, { Component } from '@tarojs/taro'
import api from '@/api'
import { getExtConfigData } from '@/utils'
import { drawText, drawImage, drawBlock } from './helper.alipay'
import { drawText, drawImage, drawBlock } from './helper'
const canvasWidth = 600
const canvasHeight = 960

View File

@@ -1,458 +0,0 @@
/**
* @description 绘制圆角矩形
* @param { object } drawData - 绘制数据
* @param { number } drawData.x - 左上角x坐标
* @param { number } drawData.y - 左上角y坐标
* @param { number } drawData.w - 矩形的宽
* @param { number } drawData.h - 矩形的高
* @param { number } drawData.r - 圆角半径
*
* @param { object } drawOptions - 绘制对象
* @param { object } drawOptions.ctx - ctx对象
* @param { function } drawOptions.toPx - toPx方法
* @param { function } drawOptions.toRpx - toRpx方法
*/
import { isAlipay, } from '@/utils'
export function _drawRadiusRect(drawData, drawOptions) {
const { x, y, w, h, r } = drawData
const {
ctx,
toPx
// toRpx,
} = drawOptions
const br = r / 2
ctx.beginPath()
ctx.moveTo(toPx(x + br), toPx(y)) // 移动到左上角的点
ctx.lineTo(toPx(x + w - br), toPx(y))
ctx.arc(toPx(x + w - br), toPx(y + br), toPx(br), 2 * Math.PI * (3 / 4), 2 * Math.PI * (4 / 4))
ctx.lineTo(toPx(x + w), toPx(y + h - br))
ctx.arc(toPx(x + w - br), toPx(y + h - br), toPx(br), 0, 2 * Math.PI * (1 / 4))
ctx.lineTo(toPx(x + br), toPx(y + h))
ctx.arc(toPx(x + br), toPx(y + h - br), toPx(br), 2 * Math.PI * (1 / 4), 2 * Math.PI * (2 / 4))
ctx.lineTo(toPx(x), toPx(y + br))
ctx.arc(toPx(x + br), toPx(y + br), toPx(br), 2 * Math.PI * (2 / 4), 2 * Math.PI * (3 / 4))
}
/**
* @description 计算文本长度
* @param { Array | Object } text 数组 或者 对象
*
* @param { object } drawOptions - 绘制对象
* @param { object } drawOptions.ctx - ctx对象
* @param { function } drawOptions.toPx - toPx方法
* @param { function } drawOptions.toRpx - toRpx方法
*/
export function _getTextWidth(text, drawOptions) {
const { ctx, toPx, toRpx } = drawOptions
let texts = []
if (Object.prototype.toString.call(text) === '[object Object]') {
texts.push(text)
} else {
texts = text
}
let width = 0
// eslint-disable-next-line no-shadow
texts.forEach(({ fontSize, text, marginLeft = 0, marginRight = 0 }) => {
ctx.setFontSize(toPx(fontSize))
width += ctx.measureText(text).width + marginLeft + marginRight
})
return toRpx(width)
}
/**
* @description 渲染一段文字
* @param { object } drawData - 绘制数据
* @param { number } drawData.x - x坐标 rpx
* @param { number } drawData.y - y坐标 rpx
* @param { number } drawData.fontSize - 文字大小 rpx
* @param { number } [drawData.color] - 颜色
* @param { string } [drawData.baseLine] - 基线对齐方式 top| middle|bottom
* @param { string } [drawData.textAlign='left'] - 对齐方式 left|center|right
* @param { string } drawData.text - 当Object类型时参数为 text 字段的参数marginLeft、marginRight这两个字段可用
* @param { number } [drawData.opacity=1] - 1为不透明0为透明
* @param { string } [drawData.textDecoration='none']
* @param { number } [drawData.width] - 文字宽度 没有指定为画布宽度
* @param { number } [drawData.lineNum=1] - 根据宽度换行,最多的行数
* @param { number } [drawData.lineHeight=0] - 行高
* @param { string } [drawData.fontWeight='normal'] - 'bold' 加粗字体,目前小程序不支持 100 - 900 加粗
* @param { string } [drawData.fontStyle='normal'] - 'italic' 倾斜字体
* @param { string } [drawData.fontFamily="sans-serif"] - 小程序默认字体为 'sans-serif', 请输入小程序支持的字体
*
* @param { object } drawOptions - 绘制对象
* @param { object } drawOptions.ctx - ctx对象
* @param { function } drawOptions.toPx - toPx方法
* @param { function } drawOptions.toRpx - toRpx方法
*/
export function _drawSingleText(drawData, drawOptions) {
if(isAlipay){
// drawData.x = drawData.x / 4
// drawData.y = drawData.y / 4
// drawData.fontSize = drawData.fontSize / 2
}
const {
x,
y,
fontSize,
color,
baseLine,
textAlign = 'left',
text,
opacity = 1,
textDecoration = 'none',
width,
lineNum = 1,
lineHeight = 0,
fontWeight = 'normal',
fontStyle = 'normal',
fontFamily = 'sans-serif'
} = drawData
console.log('_drawSingleText:drawData',drawData)
const { ctx, toPx, toRpx } = drawOptions
ctx.save()
ctx.beginPath()
ctx.font = fontStyle + ' ' + fontWeight + ' ' + toPx(fontSize, true) + 'px ' + fontFamily
ctx.setGlobalAlpha(opacity)
// ctx.setFontSize(toPx(fontSize));
ctx.setFillStyle(color)
ctx.setTextBaseline(baseLine)
ctx.setTextAlign(textAlign)
let textWidth = toRpx(ctx.measureText(text).width)
const textArr = []
if (textWidth > width) {
// 文本宽度 大于 渲染宽度
let fillText = ''
let line = 1
for (let i = 0; i <= text.length - 1; i++) {
// 将文字转为数组,一行文字一个元素
fillText = fillText + text[i]
if (toRpx(ctx.measureText(fillText).width) >= width) {
if (line === lineNum) {
if (i !== text.length - 1) {
fillText = fillText.substring(0, fillText.length - 1) + '...'
}
}
if (line <= lineNum) {
textArr.push(fillText)
}
fillText = ''
line++
} else {
if (line <= lineNum) {
if (i === text.length - 1) {
textArr.push(fillText)
}
}
}
}
textWidth = width
} else {
textArr.push(text)
}
textArr.forEach((item, index) => {
ctx.fillText(item, toPx(x), toPx(y + (lineHeight || fontSize) * index))
})
ctx.restore()
// textDecoration
if (textDecoration !== 'none') {
let lineY = y
if (textDecoration === 'line-through') {
// 目前只支持贯穿线
lineY = y - fontSize * 0.4
}
ctx.save()
ctx.moveTo(toPx(x), toPx(lineY))
ctx.lineTo(toPx(x) + toPx(textWidth), toPx(lineY))
ctx.setStrokeStyle(color)
ctx.stroke()
ctx.restore()
}
return textWidth
}
/**
* 渲染文字
* @param { object } params - 绘制数据
* @param { number } params.x - x坐标 rpx
* @param { number } params.y - y坐标 rpx
* @param { number } params.fontSize - 文字大小 rpx
* @param { number } [params.color] - 颜色
* @param { string } [params.baseLine] - 基线对齐方式 top| middle|bottom
* @param { string } [params.textAlign='left'] - 对齐方式 left|center|right
* @param { string } params.text - 当Object类型时参数为 text 字段的参数marginLeft、marginRight这两个字段可用
* @param { number } [params.opacity=1] - 1为不透明0为透明
* @param { string } [params.textDecoration='none']
* @param { number } [params.width] - 文字宽度 没有指定为画布宽度
* @param { number } [params.lineNum=1] - 根据宽度换行,最多的行数
* @param { number } [params.lineHeight=0] - 行高
* @param { string } [params.fontWeight='normal'] - 'bold' 加粗字体,目前小程序不支持 100 - 900 加粗
* @param { string } [params.fontStyle='normal'] - 'italic' 倾斜字体
* @param { string } [params.fontFamily="sans-serif"] - 小程序默认字体为 'sans-serif', 请输入小程序支持的字体
*
* @param { object } drawOptions - 绘制对象
* @param { object } drawOptions.ctx - ctx对象
* @param { function } drawOptions.toPx - toPx方法
* @param { function } drawOptions.toRpx - toRpx方法
*/
export function drawText(params, drawOptions) {
// const { ctx, toPx, toRpx } = drawOptions;
const {
x,
y,
text,
baseLine
// fontSize,
// color,
// textAlign,
// opacity = 1,
// width,
// lineNum,
// lineHeight
} = params
console.log('drawText', params)
if (Object.prototype.toString.call(text) === '[object Array]') {
let preText = { x, y, baseLine }
text.forEach((item) => {
preText.x += item.marginLeft || 0
const textWidth = _drawSingleText(
Object.assign(item, {
...preText
}),
drawOptions
)
preText.x += textWidth + (item.marginRight || 0) // 下一段字的 x 轴为上一段字 x + 上一段字宽度
})
} else {
_drawSingleText(params, drawOptions)
}
}
/**
* @description 渲染图片
* @param { object } data
* @param { number } x - 图像的左上角在目标 canvas 上 x 轴的位置
* @param { number } y - 图像的左上角在目标 canvas 上 y 轴的位置
* @param { number } w - 在目标画布上绘制图像的宽度,允许对绘制的图像进行缩放
* @param { number } h - 在目标画布上绘制图像的高度,允许对绘制的图像进行缩放
* @param { number } sx - 源图像的矩形选择框的左上角 x 坐标
* @param { number } sy - 源图像的矩形选择框的左上角 y 坐标
* @param { number } sw - 源图像的矩形选择框的宽度
* @param { number } sh - 源图像的矩形选择框的高度
* @param { number } [borderRadius=0] - 圆角
* @param { number } [borderWidth=0] - 边框
*
* @param { object } drawOptions - 绘制对象
* @param { object } drawOptions.ctx - ctx对象
* @param { function } drawOptions.toPx - toPx方法
* @param { function } drawOptions.toRpx - toRpx方法
*/
export function drawImage(data, drawOptions) {
const { ctx, toPx } = drawOptions
if(isAlipay){
data.sw = data.sw * 2
data.sh = data.sh * 2
data.h = data.h / 1.3
}
const {
imgPath,
x,
y,
w,
h,
sx,
sy,
sw,
sh,
borderRadius = 0,
borderWidth = 0,
borderColor
} = data
ctx.save()
console.log('drawImage:data', data)
if (borderRadius > 0) {
let drawData = {
x,
y,
w,
h,
r: borderRadius
}
_drawRadiusRect(drawData, drawOptions)
ctx.strokeStyle = 'rgba(255,255,255,0)'
ctx.stroke()
ctx.clip()
ctx.drawImage(
imgPath,
toPx(sx),
toPx(sy),
toPx(sw),
toPx(sh),
toPx(x),
toPx(y),
toPx(w),
toPx(h)
)
if (borderWidth > 0) {
ctx.setStrokeStyle(borderColor)
ctx.setLineWidth(toPx(borderWidth))
ctx.stroke()
}
} else {
ctx.drawImage(
imgPath,
toPx(sx),
toPx(sy),
toPx(sw),
toPx(sh),
toPx(x),
toPx(y),
toPx(w),
toPx(h)
)
}
ctx.restore()
}
/**
* @description 渲染线
* @param { number } startX - 起始坐标
* @param { number } startY - 起始坐标
* @param { number } endX - 终结坐标
* @param { number } endY - 终结坐标
* @param { number } width - 线的宽度
* @param { string } [color] - 线的颜色
*
* @param { object } drawOptions - 绘制对象
* @param { object } drawOptions.ctx - ctx对象
* @param { function } drawOptions.toPx - toPx方法
* @param { function } drawOptions.toRpx - toRpx方法
*/
export function drawLine(drawData, drawOptions) {
const { startX, startY, endX, endY, color, width } = drawData
const { ctx, toPx } = drawOptions
ctx.save()
ctx.beginPath()
ctx.setStrokeStyle(color)
ctx.setLineWidth(toPx(width))
ctx.moveTo(toPx(startX), toPx(startY))
ctx.lineTo(toPx(endX), toPx(endY))
ctx.stroke()
ctx.closePath()
ctx.restore()
}
/**
* @description 渲染块
* @param { number } x - x坐标
* @param { number } y - y坐标
* @param { number } height -高
* @param { string|object } [text] - 块里面可以填充文字参考texts字段
* @param { number } [width=0] - 宽 如果内部有文字,由文字宽度和内边距决定
* @param { number } [paddingLeft=0] - 内左边距
* @param { number } [paddingRight=0] - 内右边距
* @param { number } [borderWidth] - 边框宽度
* @param { string } [backgroundColor] - 背景颜色
* @param { string } [borderColor] - 边框颜色
* @param { number } [borderRadius=0] - 圆角
* @param { number } [opacity=1] - 透明度
*
* @param { object } drawOptions - 绘制对象
* @param { object } drawOptions.ctx - ctx对象
* @param { function } drawOptions.toPx - toPx方法
* @param { function } drawOptions.toRpx - toRpx方法
*/
export function drawBlock(
{
text,
width = 0,
height,
x,
y,
paddingLeft = 0,
paddingRight = 0,
borderWidth,
backgroundColor,
borderColor,
borderRadius = 0,
opacity = 1
},
drawOptions
) {
console.log('drawBlock:data',arguments[0])
height = height * 2
const { ctx, toPx } = drawOptions
// 判断是否块内有文字
let blockWidth = 0 // 块的宽度
let textX = 0
let textY = 0
if (typeof text !== 'undefined') {
// 如果有文字并且块的宽度小于文字宽度,块的宽度为 文字的宽度 + 内边距
const textWidth = _getTextWidth(typeof text.text === 'string' ? text : text.text, drawOptions)
blockWidth = textWidth > width ? textWidth : width
blockWidth += paddingLeft + paddingLeft
const {
textAlign = 'left'
// text: textCon,
} = text
textY = height / 2 + y // 文字的y轴坐标在块中线
if (textAlign === 'left') {
// 如果是右对齐那x轴在块的最左边
textX = x + paddingLeft
} else if (textAlign === 'center') {
textX = blockWidth / 2 + x
} else {
textX = x + blockWidth - paddingRight
}
} else {
blockWidth = width
}
if (backgroundColor) {
// 画面
ctx.save()
ctx.setGlobalAlpha(opacity)
ctx.setFillStyle(backgroundColor)
if (borderRadius > 0) {
// 画圆角矩形
let drawData = {
x,
y,
w: blockWidth,
h: height,
r: borderRadius
}
_drawRadiusRect(drawData, drawOptions)
ctx.fill()
} else {
ctx.fillRect(toPx(x), toPx(y), toPx(blockWidth), toPx(height))
}
ctx.restore()
}
if (borderWidth) {
// 画线
ctx.save()
ctx.setGlobalAlpha(opacity)
ctx.setStrokeStyle(borderColor)
ctx.setLineWidth(toPx(borderWidth))
if (borderRadius > 0) {
// 画圆角矩形边框
let drawData = {
x,
y,
w: blockWidth,
h: height,
r: borderRadius
}
_drawRadiusRect(drawData, drawOptions)
ctx.stroke()
} else {
ctx.strokeRect(toPx(x), toPx(y), toPx(blockWidth), toPx(height))
}
ctx.restore()
}
if (text) {
drawText(Object.assign(text, { x: textX, y: textY }), drawOptions)
}
}

View File

@@ -1,299 +0,0 @@
import { useEffect } from 'react'
import { useSelector } from 'react-redux'
import Taro from '@tarojs/taro'
import { View, Text, Image, Button, Canvas } from '@tarojs/components'
import { useImmer } from 'use-immer'
import { useAsyncCallback } from '@/hooks'
import { classNames, authSetting, showToast, isAlipay } from '@/utils'
import GoodsDetailPoster from './dw-goodsdetail.alipay'
import GuideGoodsDetailPoster from './dw-guidegoodsdetail.wx'
import GuideCheckout from './dw-guidecheckout.wx'
import Distribution from './dw-distribution.wx'
import './index.scss'
const initialState = {
poster: null,
pxWidth: 200,
pxHeight: 200,
factor: 1,
eleId: 'poster-canvas',
ctx: null
}
function SpPoster(props) {
const { info, type, onClose = () => {} } = props
console.log('SpPoster:props', props)
const { userInfo } = useSelector((state) => state.user)
const { userInfo: guideInfo } = useSelector((state) => state.guide)
const [state, setState] = useAsyncCallback(initialState)
const { poster, pxWidth, pxHeight, eleId, ctx } = state
console.log('SpPoster:state', state)
useEffect(() => {
// !isAlipay ? handleCreatePoster() : handleCreatePoster3()
handleCreatePoster()
}, [])
/**
* @description rpx => px 基础方法
* @param { number } rpx - 需要转换的数值
* @param { boolean} int - 是否为 int
* @param { number } [factor] - 转化因子
* @returns { number }
*/
const toPx = (rpx, int, factor = 1) => {
if (int) {
return parseInt(rpx * factor)
}
return rpx * factor
}
/**
* @description px => rpx
* @param { number } px - 需要转换的数值
* @param { boolean} int - 是否为 int
* @param { number } [factor] - 转化因子
* @returns { number }
*/
const toRpx = (px, int, factor = 1) => {
if (int) {
return parseInt(px / factor)
}
return px / factor
}
const handleCreatePoster = async () => {
Taro.showLoading({
title: '海报生成中...'
})
const ctx = Taro.createCanvasContext(eleId, Taro.getCurrentInstance().page)
console.log('sp-handleCreatePoster:ctx', ctx)
let canvasObj
switch (type) {
case 'goodsDetial':
canvasObj = new GoodsDetailPoster({
ctx,
info,
userInfo,
toPx,
toRpx
})
break
case 'guideGoodsDetial':
canvasObj = new GuideGoodsDetailPoster({
ctx,
info,
userInfo: guideInfo,
toPx,
toRpx
})
break
case 'guideCheckout':
canvasObj = new GuideCheckout({
ctx,
info,
userInfo: guideInfo,
toPx,
toRpx
})
break
case 'distribution':
canvasObj = new Distribution({
ctx,
info,
userInfo,
toPx,
toRpx
})
break
default:
break
}
const { canvasWidth, canvasHeight } = canvasObj.getCanvasSize()
setState(
(draft) => {
draft.pxWidth = canvasWidth
draft.pxHeight = canvasHeight
draft.ctx = ctx
},
async (_state) => {
console.log('handleCreatePoster-setState:_state1', _state)
await canvasObj.drawPoster()
const poster = await getPoster(_state)
Taro.hideLoading()
setState((draft) => {
draft.poster = poster
})
}
)
}
const handleCreatePoster2 = () => {
Taro.showLoading({
title: '海报生成中...'
})
console.log('handleCreatePoster run')
let ctx = null
// 通过 SelectorQuery 获取 Canvas 实例
Taro.createSelectorQuery()
.select('#poster-canvas')
.node()
.exec((res) => {
console.log('alipay:res', res)
const canvas = res[0].node
ctx = canvas.getContext('2d')
console.log('canvas', canvas)
console.log('alipay:ctx1', ctx)
// 开始绘画
// ctx.fillRect(0, 100, 50, 50)
console.log('alipay:ctx2', ctx)
let canvasObj
switch (type) {
case 'goodsDetial':
canvasObj = new GoodsDetailPoster({
canvas,
ctx,
info,
userInfo,
toPx,
toRpx
})
break
case 'guideGoodsDetial':
canvasObj = new GuideGoodsDetailPoster({
ctx,
info,
userInfo: guideInfo,
toPx,
toRpx
})
break
case 'guideCheckout':
canvasObj = new GuideCheckout({
ctx,
info,
userInfo: guideInfo,
toPx,
toRpx
})
break
case 'distribution':
canvasObj = new Distribution({
ctx,
info,
userInfo,
toPx,
toRpx
})
break
default:
break
}
console.log('handleCreatePoster:canvasObj', canvasObj)
const { canvasWidth, canvasHeight } = canvasObj.getCanvasSize()
console.log('handleCreatePoster:canvasWidth', canvasWidth)
console.log('handleCreatePoster:canvasHeight', canvasHeight)
setState(
(draft) => {
draft.pxWidth = canvasWidth
draft.pxHeight = canvasHeight
draft.ctx = ctx
draft.canvas = res[0].node
},
async (_state) => {
console.log('handleCreatePoster-setState:_state2', _state)
await canvasObj.drawPoster()
const poster = await getPoster(_state)
Taro.hideLoading()
setState((draft) => {
draft.poster = poster
})
}
)
})
}
const getPoster = ({ ctx, pxWidth, pxHeight, eleId }) => {
console.log('getPoster:ctx', ctx)
return new Promise((resolve, reject) => {
ctx.draw(false, async () => {
const { tempFilePath: poster } = await Taro.canvasToTempFilePath(
{
x: 0,
y: 0,
width: pxWidth,
height: pxHeight,
canvasId: eleId
},
Taro.getCurrentInstance().page
)
console.log('getPoster:poster', poster)
resolve(poster)
})
})
}
const saveToAlbum = () => {
authSetting(
'writePhotosAlbum',
() => {
savePoster()
},
() => {
showToast('请设置允许保存到相册')
}
)
}
const savePoster = () => {
Taro.saveImageToPhotosAlbum({
filePath: poster
})
.then((res) => {
showToast('保存成功')
})
.catch((res) => {
showToast('保存失败')
})
}
// const onCanvasReady = () => {
// console.log('onCanvasReady2222')
// // 通过 SelectorQuery 获取 Canvas 实例
// Taro.createSelectorQuery().select('#canvas1').node().exec((res) => {
// console.log('canvas1:Res', res)
// const canvas = res[0].node;
// const ctx = canvas.getContext('2d');
// console.log('canvas 宽高', canvas.width, canvas.height)
// // 开始绘画
// ctx.fillRect(0, 0, 50, 50);
// });
// }
return (
<View className={classNames('sp-poster')}>
{/* <Canvas id="canvas1" type="2d" onReady={onCanvasReady} /> */}
<View className='share-panel__overlay'></View>
{poster && (
<View className='share-panel__poster'>
<View className='poster-container'>
<Image className='poster' src={poster} mode='widthFix' />
</View>
<View className='poster-ft'>
<Text className='iconfont icon-guanbi' onClick={onClose}></Text>
<View className='poster-save' onClick={saveToAlbum}>
<Text className='iconfont icon-download'></Text>
保存图片
</View>
</View>
</View>
)}
<Canvas
className='canvasbox'
// type='2d'
id='poster-canvas'
canvasId='poster-canvas'
style={`width:${pxWidth}px; height:${pxHeight}px;`}
// onReady={handleCreatePoster2}
/>
</View>
)
}
export default SpPoster

View File

@@ -4,22 +4,11 @@ import Taro from '@tarojs/taro'
import { View, Text, Image, Canvas } from '@tarojs/components'
import { useAsyncCallback } from '@/hooks'
import { classNames, authSetting, showToast, isAlipay } from '@/utils'
import GoodsDetailPosterAl from './dw-goodsdetail.alipay'
import GoodsDetailPosterWx from './dw-goodsdetail.wx'
import GuideGoodsDetailPosterAl from './dw-guidegoodsdetail.alipay'
import GuideGoodsDetailPosterWx from './dw-guidegoodsdetail.wx'
import GuideCheckoutWx from './dw-guidecheckout.wx'
import GuideCheckoutAl from './dw-guidecheckout.alipay'
import DistributionAl from './dw-distribution.alipay'
import DistributionWx from './dw-distribution.wx'
import GoodsDetailPoster from './dw-goodsdetail'
import Distribution from './dw-distribution'
import GuideGoodsDetailPoster from './dw-guidegoodsdetail'
import GuideCheckout from './dw-guidecheckout'
const DwArray = [
isAlipay ? GoodsDetailPosterAl : GoodsDetailPosterWx,
isAlipay ? GuideGoodsDetailPosterAl : GuideGoodsDetailPosterWx,
isAlipay ? GuideCheckoutAl : GuideCheckoutWx,
isAlipay ? DistributionAl : DistributionWx,
]
const [GoodsDetailPoster, GuideGoodsDetailPoster, GuideCheckout, Distribution] = DwArray
import './index.scss'

View File

@@ -42,8 +42,8 @@ function SpSkuSelect(props) {
const {
info,
open = false,
onClose = () => {},
onChange = () => {},
onClose = () => { },
onChange = () => { },
type,
hideInputNumber = false
} = props
@@ -69,7 +69,7 @@ function SpSkuSelect(props) {
skuDictRef.current[key] = item
})
// 默认选中有库存并且前端可销售的sku
const defaultSpecItem = specItems.find((item) => item.store > 0 && item.approveStatus == 'onsale')
const defaultSpecItem = specItems.find((item) => item.store > 0 && ['onsale', 'offline_sale'].includes(item.approveStatus))
let selection = Array(specItems.length).fill(null)
if (defaultSpecItem) {
selection = defaultSpecItem.specItem.map((item) => item.specId)
@@ -91,7 +91,7 @@ function SpSkuSelect(props) {
const reg = makeReg(sel, row, val)
return Object.keys(skuDictRef.current).some((key) => {
return key.match(reg) && skuDictRef.current[key].store > 0 && skuDictRef.current[key].approveStatus == 'onsale'
return key.match(reg) && skuDictRef.current[key].store > 0 && ['onsale', 'offline_sale'].includes(skuDictRef.current[key].approveStatus)
})
}
@@ -212,7 +212,7 @@ function SpSkuSelect(props) {
item_id: curItem ? curItem.itemId : info.itemId,
num,
distributor_id: distributorId
}, !!info.point ) // info.point 有积分值时是积分商品
}, !!info.point) // info.point 有积分值时是积分商品
let url = !!info.point ? '/subpages/pointshop/espier-checkout?cart_type=fastbuy&shop_id=0' : `/pages/cart/espier-checkout?cart_type=fastbuy&shop_id=${distributorId}`
if (activityType == 'seckill' || activityType === 'limited_time_sale') {
const { seckill_id } = activityInfo
@@ -295,7 +295,7 @@ function SpSkuSelect(props) {
limitTxt = `(限购${purlimitByCart}件)`
}
if(type == 'fastbuy' && purlimitByFastbuy) {
if (type == 'fastbuy' && purlimitByFastbuy) {
limitNum = purlimitByFastbuy
limitTxt = `(限购${purlimitByFastbuy}件)`
}
@@ -352,7 +352,7 @@ function SpSkuSelect(props) {
</View> */}
<SpGoodsPrice info={curItem || info} />
<View className='goods-sku-txt'>{skuText}</View>
<View className='goods-sku-store'>库存{curItem ? curItem.store : info.store}</View>
{info.store_setting && <View className='goods-sku-store'>库存{curItem ? curItem.store : info.store}</View>}
</View>
</View>
<View className='sku-list'>

View File

@@ -12,35 +12,16 @@ import './index.scss'
@connect(({ colors }) => ({
colors: colors.current
}))
export default class DistributionDashboard extends Component {
constructor(props) {
super(props)
const UI_Width = 750
//同步获取设备系统信息
const info = Taro.getSystemInfoSync()
//设备像素密度
const dpr = info.pixelRatio;
//计算比例
const scale = info.screenWidth / UI_Width
//计算canvas实际渲染尺寸
let width = parseInt(scale * 335)
let height = parseInt(scale * 536)
//计算canvas画布尺寸
let canvasWidth = width * dpr
let canvasHeight = height * dpr
this.state = {
info: null,
showPoster: false,
poster: null,
posterImgs: null,
adapay_status: null,
dpr,
canvasWidth,
canvasHeight
adapay_status: null
}
}
@@ -152,195 +133,14 @@ export default class DistributionDashboard extends Component {
}
handleClick = async () => {
const { posterImgs } = this.state
if (!posterImgs || !posterImgs.avatar || !posterImgs.code || !posterImgs.username) {
const imgs = await this.downloadPosterImg()
if (imgs && imgs.avatar && imgs.code && imgs.username) {
this.setState({
showPoster: true
})
}
} else {
this.setState({
showPoster: true
})
}
}
downloadPosterImg = async () => {
// 处理海报信息以及太阳码
let {
info: { username, isOpenShop, shop_status, qrcode_bg_img },
canvasWidth,
canvasHeight,
} = this.state
let userinfo = Taro.getStorageSync('userinfo')
const { avatar, userId } = userinfo
if (S.getAuthToken() && (!userinfo || !userId)) {
const res = await api.member.memberInfo()
const userObj = {
avatar: res.memberInfo.avatar,
userId: res.memberInfo.user_id
}
Taro.setStorageSync('userinfo', userObj)
userinfo = userObj
}
// const extConfig =
// Taro.getEnv() === 'WEAPP' && wx.getExtConfigSync
// ? wx.getExtConfigSync()
// : { appid: process.env.APP_ID, company_id: process.env.APP_COMPANY_ID }
const extConfig = getExtConfigData()
shop_status = JSON.parse(shop_status === 1)
const url = isOpenShop && shop_status ? `marketing/pages/distribution/shop-home` : `pages/index`
const wxappCode = `${req.baseURL}promoter/qrcode.png?path=${url}&appid=${extConfig.appid}&company_id=${extConfig.company_id}&user_id=${userId}`
console.log('DistributionDashboard-wxappCode:', wxappCode)
let avatarImg, bck
//头像
let nAvatar = avatar === '' ? `${process.env.APP_IMAGE_CDN}/user_icon.png` : avatar
avatarImg = await Taro.getImageInfo({ src: nAvatar })
if (qrcode_bg_img) {
// 背景
bck = await Taro.getImageInfo({ src: qrcode_bg_img })
} else {
bck = await Taro.getImageInfo({
src: `${process.env.APP_IMAGE_CDN}/fenxiao_bk.png`
}) // 背景图片
}
const codeImg = await Taro.getImageInfo({ src: wxappCode }) // 二维码
console.log('DistributionDashboard-avatarImg:', avatarImg)
console.log('DistributionDashboard-codeImg:', codeImg)
if (avatarImg || isAlipay) {
const posterImgs = {
avatar: avatarImg ? avatarImg.path : null,
code: codeImg.path,
bck: bck.path,
username
}
await this.setState(
{
posterImgs
},
() => {
this.drawImage()
}
)
return posterImgs
} else {
return null
}
}
drawImage = () => {
// 分享海报
console.log('drawImage run')
const { posterImgs } = this.state
if (!posterImgs) return
const { avatar, code, bck, username } = posterImgs
const ctx = Taro.createCanvasContext('myCanvas')
console.log('drawImage:ctx', ctx)
canvasExp.roundRect(ctx, '#fff', 0, 0, 375, 640, 5)
// canvasExp.drawImageFill(ctx, bck, 0, 0, 375, 640)
// ctx.drawImage(bck, 0, 0, 750, 1335, 0, 0, 375, 640)
// ctx.save()
canvasExp.textFill(ctx, username, 180, 50, 18, '#222')
canvasExp.drawImageFill(ctx, code, 100, 325, 180, 180)
canvasExp.imgCircleClip(ctx, avatar, 100, 15, 65, 65)
ctx.draw(true, () => {
Taro.canvasToTempFilePath({
x: 0,
y: 0,
width: 375,
height: 640,
canvasId: 'myCanvas'
}).then((res) => {
const shareImg = res.tempFilePath
this.setState(
{
poster: shareImg
},
() => {
this.setState({
showPoster: true
})
}
)
})
})
}
/** 点击保存按钮 */
handleSavePoster = () => {
const { poster } = this.state
Taro.getSetting().then((res) => {
if (!res.authSetting['scope.writePhotosAlbum']) {
Taro.authorize({
scope: 'scope.writePhotosAlbum',
success: async () => {
await this.savePoster(poster)
},
fail: () => {
Taro.showModal({
title: '提示',
content: '请打开保存到相册权限',
success: async (resConfirm) => {
if (resConfirm.confirm) {
await Taro.openSetting()
const setting = await Taro.getSetting()
if (setting.authSetting['scope.writePhotosAlbum']) {
await this.savePoster(poster)
} else {
Taro.showToast({ title: '保存失败', icon: 'none' })
}
}
}
})
}
})
} else {
this.savePoster(poster)
}
})
}
/** 保存图片 */
savePoster = (poster) => {
Taro.saveImageToPhotosAlbum({
filePath: poster
})
.then(() => {
Taro.showToast({
icon: 'none',
title: '保存成功'
})
this.setState({
showPoster: false
})
})
.catch((res) => {
console.log(res)
Taro.showToast({
icon: 'none',
title: '保存失败'
})
})
}
handleHidePoster = () => {
this.setState({
showPoster: false
showPoster: true
})
}
render() {
const { colors } = this.props
const { info, showPoster, poster, adapay_status,canvasWidth,
canvasHeight, } = this.state
const { info, showPoster, adapay_status } = this.state
if (!info) {
return <Loading />
}
@@ -471,9 +271,8 @@ export default class DistributionDashboard extends Component {
<Navigator
className='list-item'
open-type='navigateTo'
url={`/marketing/pages/distribution/goods?status=${
info.isOpenShop && info.shop_status === 1
}`}
url={`/marketing/pages/distribution/goods?status=${info.isOpenShop && info.shop_status === 1
}`}
>
<View className='iconfont icon-weChat icon-fontsize' />
<View className='list-item-txt'>推广商品</View>
@@ -519,9 +318,9 @@ export default class DistributionDashboard extends Component {
className={classNames(
'cicle',
(adapay_status == '审核中' && 'approve') ||
(adapay_status == '未认证' && 'NotCertified') ||
(adapay_status == '认证失败' && 'fail') ||
(adapay_status == '已认证' && 'success')
(adapay_status == '未认证' && 'NotCertified') ||
(adapay_status == '认证失败' && 'fail') ||
(adapay_status == '已认证' && 'success')
)}
/>
<View style={{ marginRight: '15rpx' }}>{adapay_status}</View>
@@ -555,8 +354,8 @@ export default class DistributionDashboard extends Component {
}}
/>
)}
<Canvas className='canvas' canvas-id='myCanvas' width={canvasWidth}
height={canvasHeight} id='myCanvas'></Canvas>
{/* <Canvas className='canvas' canvas-id='myCanvas' width={canvasWidth}
height={canvasHeight} id='myCanvas'></Canvas> */}
</SpPage>
)
}

View File

@@ -18,7 +18,7 @@ const initialState = {
locationIng: false,
chooseValue: ['北京市', '北京市', '昌平区'],
keyword: '', // 参数
type: 0, // 参数
type: 0, // 0:正常流程 1:基于省市区过滤 2:基于默认收货地址强制定位
search_type: undefined, // 参数
isSpAddressOpened: false,
}
@@ -37,9 +37,14 @@ function NearlyShop(props) {
const dispatch = useDispatch()
useEffect(() => {
// fetchAddressList()
if (address) {
shopRef.current.reset()
}
return () => {
dispatch(updateChooseAddress(null)) // 清空地址信息
}
// onPickerClick()
}, [])
}, [address])
const fetchShop = async (params) => {
const { pageIndex: page, pageSize } = params
@@ -62,7 +67,7 @@ function NearlyShop(props) {
const { list, total_count: total, defualt_address } = await api.shop.list(query)
setState((v) => {
v.shopList = uniqueFunc(v.shopList.concat(pickBy(list, doc.shop.SHOP_ITEM)),'distributor_id')
v.shopList = uniqueFunc(v.shopList.concat(pickBy(list, doc.shop.SHOP_ITEM)), 'distributor_id')
v.chooseValue = [query.province, query.city, query.area]
})
@@ -195,7 +200,7 @@ function NearlyShop(props) {
selectValue[1].label,
selectValue[2].label
]
setState((v)=>{
setState((v) => {
v.chooseValue = chooseValue
v.type = 1
})
@@ -210,7 +215,7 @@ function NearlyShop(props) {
<View className='search-block'>
<View className='search-bar'>
<View className='region-picker'>
<SpAddress isOpened={isSpAddressOpened} onClose={handleClickCloseSpAddress} onChange={onPickerChange}/>
<SpAddress isOpened={isSpAddressOpened} onClose={handleClickCloseSpAddress} onChange={onPickerChange} />
<View className='pick-title' onClick={handleClickOpenSpAddress}>
<View className='iconfont icon-periscope'></View>
<Text className='pick-address'>{chooseValue.join('') || '选择地区'}</Text>
@@ -253,7 +258,7 @@ function NearlyShop(props) {
</View>
<View className='block-title block-flex'>
<View>我的收货地址</View>
{address && (
{!address && (
<View
className='arrow'
onClick={() =>

View File

@@ -138,34 +138,28 @@ function PointShopEspierCheckout() {
return
}
setState(
(draft) => {
draft.submitLoading = true
},
async () => {
if (isWeixin) {
const templeparams = {
temp_name: 'yykweishop',
source_type: receiptType === 'logistics' ? 'logistics_order' : 'ziti_order'
}
const { template_id } = await api.user.newWxaMsgTmpl(templeparams)
Taro.requestSubscribeMessage({
tmplIds: template_id,
success: () => {
handlePay()
},
fail: () => {
handlePay()
}
})
} else {
handlePay()
}
if (isWeixin) {
const templeparams = {
temp_name: 'yykweishop',
source_type: receiptType === 'logistics' ? 'logistics_order' : 'ziti_order'
}
)
const { template_id } = await api.user.newWxaMsgTmpl(templeparams)
try {
await Taro.requestSubscribeMessage({ tmplIds: template_id })
handlePay()
} catch (e) {
console.error(e)
handlePay()
}
} else {
handlePay()
}
}
const handlePay = async () => {
setState((draft) => {
draft.submitLoading = true
})
const params = await getParamsInfo()
console.log('trade params:', params)
if (payType === 'deposit') {