374 lines
12 KiB
TypeScript
374 lines
12 KiB
TypeScript
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
|