2022-12-10 14:14:36 +08:00

374 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Canvas, Image, Text, View } from '@tarojs/components'
import Taro, { useReady } from '@tarojs/taro'
import React, { useCallback, useRef, useState } from 'react'
import style from './index.module.scss'
import Dialog from '@/components/Dialog'
import LayoutBlock from '@/components/layoutBlock'
import Divider from '@/components/divider'
import type { TablePropsType } from '@/components/table'
import Table from '@/components/table'
import NormalButton from '@/components/normalButton'
import { alert } from '@/common/common'
import { GenBarCodeOrQrCode, GetInvitationInfo } from '@/api'
import { getCDNSource } from '@/common/constant'
import Painter from '@/components/painter'
const SystemInfo = Taro.getSystemInfoSync()
let screenK = 5
/**
* 是否支持负数
* @param {Boolean} minus 是否支持负数
*/
const toPx = (num: string, minus = false) => {
let reg
if (minus) {
reg = /^-?[0-9]+([.]{1}[0-9]+){0,1}(rpx|px)$/g
}
else {
reg = /^[0-9]+([.]{1}[0-9]+){0,1}(rpx|px)$/g
}
const results = reg.exec(num)
console.log('results', results)
if (!num || !results) {
console.log(`The size: ${num} is illegal`)
return 0
}
const unit = results[2]
const value = parseFloat(num)
let res = 0
if (unit === 'rpx') {
res = Math.round(value * screenK)
}
else if (unit === 'px') {
res = value
}
return res
}
// 需要传进来的表头数据示例
const inviteColumns = [
{
key: 'invitee',
title: '被邀请人',
dataIndex: 'invitee',
width: '50%',
},
{
key: 'InviteResults',
title: '邀请进度',
dataIndex: 'InviteResults',
width: '50%',
},
]
// 邀请码
const InviteCode = () => {
screenK = SystemInfo.screenWidth / 750
const { fetchData } = GetInvitationInfo()
const { fetchData: genCode } = GenBarCodeOrQrCode()
const [inviteInfo, setInviteInfo] = useState<any>({})
const [currentTable, setCurrentTable] = useState<TablePropsType>({
columns: inviteColumns,
dataSource: { list: [], total: 0 },
})
const [loading, setLoading] = useState(false)
// 获取邀请码
const getInviteCode = async() => {
const res = await fetchData()
if (res.success) {
console.log('getInviteCode', res)
setCurrentTable((prev: any) => ({
...prev,
dataSource: {
list: res.data.invitation_record.map((item, index: number) => ({
key: index,
index: index + 1,
invitee: item.name || '--',
InviteResults: '已邀请',
})),
total: res.data.invitation_record.length,
},
}))
setInviteInfo(res.data)
}
}
// 生成二维码
const genQRcode = async() => {
const res = await genCode({ content: `InviteCode:${inviteInfo.invitation_code}` })
if (res.success) {
return res.data.qrcode_base64
}
else {
setLoading(false)
}
}
const [, setForceUpdate] = useState({})
const canvasNode = useRef<Taro.Canvas | null>(null)
const ctx = useRef<Taro.RenderingContext | null>(null)
const [showPopup, setShowPopup] = useState(false)
const [targetImageUrl, setTargetImageUrl] = useState('')
const getImageObject = (canvas: Taro.Canvas, src: string) => {
return new Promise<Taro.Image>((resolve, reject) => {
console.log('getImageObject param', canvas, src)
const img = canvas.createImage()
img.src = src
img.onload = () => {
console.log('image===>', img)
resolve(img)
}
img.onerror = (err) => {
console.log('image error===>', err)
alert.error('图片加载失败')
reject(err)
}
})
}
// canvas 生成 图片
const saveCanvasToImage = (canvas) => {
console.log('pixelRatio', SystemInfo.pixelRatio, canvas)
Taro.canvasToTempFilePath({
canvas,
fileType: 'png',
success: (res) => {
console.log('tempFilePath', res.tempFilePath)
setTargetImageUrl(res.tempFilePath)
},
fail: (error) => {
console.log('error', error)
},
complete: () => {
setLoading(false)
},
})
}
const getImageInfo = (filePath) => {
Taro.getImageInfo({
src: filePath,
success: (infoRes) => {
console.log('', infoRes)
},
})
}
// 初始化 canvas
const initCanvas = async() => {
return new Promise((resolve, reject) => {
Taro.nextTick(() => {
const query = Taro.createSelectorQuery()
query.select('#canvas').node(({ node: canvas }: { node: Taro.Canvas }) => {
console.log('canvas==>', canvas)
const context = canvas.getContext('2d')
console.log('ctx', context)
canvasNode.current = canvas
ctx.current = context
console.log('canvas', canvas)
setForceUpdate({})
resolve(true)
}).exec()
})
})
}
const doublePick = (num, minus = false) => {
if (minus) {
return num / 2
}
else {
return num * 2
}
}
const [canvasStyle, setCanvasStyle] = useState<React.CSSProperties>({})
const painter = useRef<any>(null)
const startPaint = async(ctx: Taro.RenderingContext, canvas: Taro.Canvas, image: Taro.Image) => {
// 开始绘制
const { width, height } = canvas
console.log('startPaint param', ctx, canvas, image)
const cCanvasCtx = ctx as any
cCanvasCtx.clearRect(0, 0, width, height)
cCanvasCtx.drawImage(image, 0, 0, width, height)
cCanvasCtx.save()
cCanvasCtx.font = `${doublePick(40)}px 微软雅黑`
cCanvasCtx.fillStyle = '#000000'
cCanvasCtx.fillText('蜘蛛管家', doublePick(40), doublePick(80)) // text up above canvas
cCanvasCtx.save()
cCanvasCtx.font = `${doublePick(26)}px 微软雅黑`
cCanvasCtx.fillStyle = '#8f9398'
cCanvasCtx.fillText('真挚邀请您建立合作关系', doublePick(40), doublePick(130)) // text up above canvas
cCanvasCtx.save()
cCanvasCtx.font = `${doublePick(24)}px 微软雅黑`
cCanvasCtx.fillStyle = '#a6a6a6'
cCanvasCtx.fillText('请前往邀请码页面,进行扫描邀请', doublePick(100), doublePick(630)) // text up above canvas
cCanvasCtx.save()
cCanvasCtx.font = `${doublePick(36)}px 微软雅黑`
cCanvasCtx.fillStyle = '#7f7f7f'
cCanvasCtx.fillText('邀 请 码', doublePick(72), doublePick(730)) // text up above canvas
cCanvasCtx.save()
cCanvasCtx.font = `${doublePick(24)}px 微软雅黑`
cCanvasCtx.fillStyle = '#cccccc'
cCanvasCtx.fillText('|', doublePick(258), doublePick(724)) // text up above canvas
cCanvasCtx.save()
cCanvasCtx.font = `${doublePick(36)}px 微软雅黑`
cCanvasCtx.fillStyle = '#7f7f7f'
cCanvasCtx.fillText(`${inviteInfo.invitation_code}`, doublePick(311), doublePick(730)) // text up above canvas
cCanvasCtx.save()
const codeUrl = await genQRcode()
try {
const code = await getImageObject(canvas, codeUrl)
cCanvasCtx.drawImage(code, doublePick(110), doublePick(213), doublePick(342), doublePick(342))
}
catch (err) {
console.error('合成二维邀请码失败', err)
setLoading(false)
throw new Error('合成二维邀请码失败')
}
saveCanvasToImage(canvas)
}
// 绘制最终的海报图片使用两倍大小canvas大小是图片的二分之一 就能让canvas生成出来的图片清晰了
const drawPictorial = async() => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async(resolve, reject) => {
setLoading(true)
if (!ctx.current) {
// 重新初始化canvas
await initCanvas()
}
const canvas = canvasNode.current!
Taro.getImageInfo({
src: getCDNSource('/user/inviteCodePopup.png'),
success: (res) => {
console.log('res==>', res)
canvas.width = res.width
canvas.height = res.height
setCanvasStyle({
width: `${doublePick(canvas.width, true)}px`,
height: `${doublePick(canvas.height, true)}px`,
})
getImageObject(canvas, `${res.path}`).then(async(image) => {
try {
// 开始绘制
await startPaint(ctx.current!, canvas, image)
resolve(true)
}
catch (err) {
console.log(err)
setLoading(false)
reject(new Error('绘制失败'))
}
}).catch((error) => {
setLoading(false)
throw new Error(error)
})
},
})
})
}
// 加载更多
const handleLoadMore = () => {
}
const handleQRcodeShare = async() => {
try {
// painter.current.startPaint()
const flag = await drawPictorial()
if (flag) {
setShowPopup(true)
}
}
catch (err) {
throw new Error('弹出二维码失败')
}
}
// 复制二维码
const handleCopyInviteCode = () => {
Taro.setClipboardData({
data: inviteInfo.invitation_code,
})
}
const handleChange = (value: boolean) => {
setShowPopup(value)
}
useReady(() => {
getInviteCode()
setTimeout(() => {
initCanvas()
}, 200)
})
const onImgErr = (e) => {
Taro.hideLoading()
console.error('onImgErr', e.error)
Taro.showToast({
title: '生成分享图失败,请刷新页面重试',
})
}
const onImgOK = (e) => {
console.log('onImgOK', e.path)
setTargetImageUrl(e.path)
setShowPopup(true)
Taro.hideLoading()
}
return <View className={style.main}>
<View className={style.content}>
<View className={style.background}>
<View className={style.left}>
<View className={style.title}></View>
<View className={style.description}></View>
</View>
<View className={style.right}>
<View className={style.iconContainer}>
<Image className={style.icon} src={getCDNSource('/user/inviteCode.png')} mode="widthFix" />
</View>
</View>
</View>
<View className={style.inviteCodeContent}>
<LayoutBlock circle>
<View className={style.codeBar}>
<View className={style.inviteCodeBar}>
<View className={style.invite}></View>
<Divider direction="vertical" />
<View className={style.invite}>{inviteInfo.invitation_code}</View>
</View>
<View className={style.tips}></View>
</View>
</LayoutBlock>
<LayoutBlock circle customStyle={{ paddingTop: '10px', paddingBottom: '10px' }}>
<View className={style.inviteListTitle}>
<View className={style.titleIconLeft}></View>
<Text className={style.listTitle}></Text>
<View className={style.titleIconRight}></View>
</View>
<View className={style.inviteList}>
<Table columns={currentTable.columns} emptyText="暂无邀请信息" safeAreaInsetBottom={false} dataSource={currentTable.dataSource} onLoadMore={handleLoadMore}></Table>
</View>
</LayoutBlock>
<View className={style.tips} style={{ justifyContent: 'flex-start' }}></View>
</View>
</View>
{/* <Painter ref={painter} onImgErr={onImgErr} onImgOK={onImgOK} palette={imgDraw} customStyle={{ position: 'absolute', left: '-9999rpx' }}></Painter> */}
{/* 已踩坑这里必须设置canvas的style的width和height单单只设置canvas实例的width和height是不行的。会模糊 */}
<Canvas style={{ position: 'absolute', left: '-9999rpx', ...canvasStyle }} id="canvas" type="2d" />
<View className={style.bottomBar}>
<NormalButton loading={loading} plain type="primary" customTextClassName={style.bottomBar__text} customStyles={{ width: '45%' }} round onClick={handleQRcodeShare}>
</NormalButton>
<NormalButton type="primary" round customTextClassName={style.bottomBar__text} customStyles={{ width: '45%' }} onClick={handleCopyInviteCode}>
</NormalButton>
</View>
<Dialog show={showPopup} onChange={handleChange}>
<View className={style.codePreview}>
<View className={style.imageContainer}>
{/* showMenuByLongpress 属性只对 小程序有效 */}
<Image className={style.image} src={targetImageUrl} mode="widthFix" id="originImage" showMenuByLongpress />
</View>
<Text className={style.previewTips}></Text>
</View>
</Dialog>
</View>
}
export default InviteCode