|
|
@@ -1,581 +0,0 @@
|
|
|
-<template>
|
|
|
- <view class="container">
|
|
|
- <view class="header">
|
|
|
- <text class="title">邀请分享</text>
|
|
|
- <text class="subtitle">邀请家长或新成长规划师加入</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 邀请码列表 -->
|
|
|
- <view class="invite-codes-section">
|
|
|
- <view class="section-title">我的邀请码</view>
|
|
|
- <view class="invite-codes-list">
|
|
|
- <view
|
|
|
- class="invite-code-item"
|
|
|
- v-for="code in inviteCodes"
|
|
|
- :key="code.id"
|
|
|
- >
|
|
|
- <view class="code-info">
|
|
|
- <text class="code-text">{{ code.code }}</text>
|
|
|
- <text class="code-status" :class="'status-' + code.status">
|
|
|
- {{ getStatusText(code.status) }}
|
|
|
- </text>
|
|
|
- </view>
|
|
|
- <view class="code-stats">
|
|
|
- <text class="stat">已使用: {{ code.useCount }}/{{ code.maxUseCount }}</text>
|
|
|
- <text class="stat" v-if="code.expireAt">过期: {{ formatDate(code.expireAt) }}</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <button class="btn-generate" @click="generateInviteCode">生成新邀请码</button>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 分享方式 -->
|
|
|
- <view class="share-section">
|
|
|
- <view class="section-title">分享方式</view>
|
|
|
-
|
|
|
- <!-- 复制链接 -->
|
|
|
- <view class="share-item" @click="copyInviteLink">
|
|
|
- <view class="share-icon">🔗</view>
|
|
|
- <view class="share-content">
|
|
|
- <text class="share-title">复制邀请链接</text>
|
|
|
- <text class="share-desc">分享链接给家长或新成长规划师</text>
|
|
|
- </view>
|
|
|
- <text class="share-arrow">›</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 生成二维码 -->
|
|
|
- <view class="share-item" @click="showQrcode">
|
|
|
- <view class="share-icon">📱</view>
|
|
|
- <view class="share-content">
|
|
|
- <text class="share-title">生成二维码</text>
|
|
|
- <text class="share-desc">扫码即可加入</text>
|
|
|
- </view>
|
|
|
- <text class="share-arrow">›</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 转发给朋友 -->
|
|
|
- <view class="share-item" @click="shareToFriend">
|
|
|
- <view class="share-icon">📤</view>
|
|
|
- <view class="share-content">
|
|
|
- <text class="share-title">转发给朋友</text>
|
|
|
- <text class="share-desc">直接分享到微信</text>
|
|
|
- </view>
|
|
|
- <text class="share-arrow">›</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 使用说明 -->
|
|
|
- <view class="tips-section">
|
|
|
- <view class="section-title">使用说明</view>
|
|
|
- <view class="tips-list">
|
|
|
- <view class="tip-item">
|
|
|
- <text class="tip-icon">💡</text>
|
|
|
- <text class="tip-text">家长通过邀请码注册后,会自动关联到您</text>
|
|
|
- </view>
|
|
|
- <view class="tip-item">
|
|
|
- <text class="tip-icon">💡</text>
|
|
|
- <text class="tip-text">新成长规划师通过邀请码注册,会成为您的下级</text>
|
|
|
- </view>
|
|
|
- <view class="tip-item">
|
|
|
- <text class="tip-icon">💡</text>
|
|
|
- <text class="tip-text">您可以查看所有下级成长规划师的测评报告</text>
|
|
|
- </view>
|
|
|
- <view class="item">
|
|
|
- <text class="tip-icon">💡</text>
|
|
|
- <text class="tip-text">邀请码可以设置使用次数和过期时间</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 二维码弹窗 -->
|
|
|
- <view class="qrcode-modal" v-if="showQrcodeModal" @click="showQrcodeModal = false">
|
|
|
- <view class="modal-content" @click.stop>
|
|
|
- <view class="modal-header">
|
|
|
- <text class="modal-title">邀请二维码</text>
|
|
|
- <text class="modal-close" @click="showQrcodeModal = false">✕</text>
|
|
|
- </view>
|
|
|
- <view class="qrcode-container">
|
|
|
- <canvas canvas-id="qrcodeCanvas" class="qrcode-canvas"></canvas>
|
|
|
- </view>
|
|
|
- <view class="modal-actions">
|
|
|
- <button class="btn-save" @click="saveQrcode">保存图片</button>
|
|
|
- <button class="btn-close" @click="showQrcodeModal = false">关闭</button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script>
|
|
|
-import { getInviteCodes, generateInviteCode, generateQrCodeFromText } from '../../../utils/api.js'
|
|
|
-import { parseDate } from '../../../utils/format.js'
|
|
|
-
|
|
|
-export default {
|
|
|
- data() {
|
|
|
- return {
|
|
|
- inviteCodes: [],
|
|
|
- currentInviteCode: null,
|
|
|
- showQrcodeModal: false
|
|
|
- }
|
|
|
- },
|
|
|
- onLoad() {
|
|
|
- this.loadInviteCodes()
|
|
|
- this._onLoadFired = true
|
|
|
- },
|
|
|
- onShow() {
|
|
|
- if (this._onLoadFired) {
|
|
|
- this._onLoadFired = false
|
|
|
- } else {
|
|
|
- this.loadInviteCodes()
|
|
|
- }
|
|
|
- },
|
|
|
- methods: {
|
|
|
- async loadInviteCodes() {
|
|
|
- try {
|
|
|
- const res = await getInviteCodes()
|
|
|
- if (res.code === 200) {
|
|
|
- this.inviteCodes = res.data || []
|
|
|
- // 如果有可用的邀请码,使用第一个
|
|
|
- const activeCode = this.inviteCodes.find(code => code.status === 'active')
|
|
|
- if (activeCode) {
|
|
|
- this.currentInviteCode = activeCode
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error('获取邀请码失败', e)
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- async generateInviteCode() {
|
|
|
- try {
|
|
|
- uni.showLoading({ title: '生成中...' })
|
|
|
- const res = await generateInviteCode()
|
|
|
- uni.hideLoading()
|
|
|
-
|
|
|
- if (res.code === 200) {
|
|
|
- this.currentInviteCode = res.data
|
|
|
- uni.showToast({
|
|
|
- title: '生成成功',
|
|
|
- icon: 'success'
|
|
|
- })
|
|
|
- this.loadInviteCodes()
|
|
|
- } else {
|
|
|
- uni.showToast({
|
|
|
- title: res.message || '生成失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- uni.hideLoading()
|
|
|
- uni.showToast({
|
|
|
- title: '生成失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- copyInviteLink() {
|
|
|
- if (!this.currentInviteCode) {
|
|
|
- uni.showToast({
|
|
|
- title: '请先生成邀请码',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- const inviteUrl = `pages/guide/register/index?inviteCode=${this.currentInviteCode.code}`
|
|
|
-
|
|
|
- uni.setClipboardData({
|
|
|
- data: inviteUrl,
|
|
|
- success: () => {
|
|
|
- uni.showToast({
|
|
|
- title: '链接已复制',
|
|
|
- icon: 'success'
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
-
|
|
|
- showQrcode() {
|
|
|
- if (!this.currentInviteCode) {
|
|
|
- uni.showToast({
|
|
|
- title: '请先生成邀请码',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- this.showQrcodeModal = true
|
|
|
- this.$nextTick(() => {
|
|
|
- this.drawQrcode()
|
|
|
- })
|
|
|
- },
|
|
|
-
|
|
|
- async drawQrcode() {
|
|
|
- if (!this.currentInviteCode) return
|
|
|
- const code = this.currentInviteCode.code
|
|
|
- const pagePath = '/pages/guide/register/index?inviteCode=' + code
|
|
|
-
|
|
|
- // 通过后端生成真实二维码
|
|
|
- let qrBase64 = ''
|
|
|
- try {
|
|
|
- const res = await generateQrCodeFromText(pagePath)
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- qrBase64 = res.data.qrCodeBase64
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error('生成二维码失败', e)
|
|
|
- }
|
|
|
-
|
|
|
- const ctx = uni.createCanvasContext('qrcodeCanvas')
|
|
|
- const canvasWidth = 300
|
|
|
- const canvasHeight = 300
|
|
|
-
|
|
|
- ctx.setFillStyle('#FFFFFF')
|
|
|
- ctx.fillRect(0, 0, canvasWidth, canvasHeight)
|
|
|
-
|
|
|
- if (qrBase64) {
|
|
|
- // 将 base64 写入临时文件,再绘制到 Canvas
|
|
|
- const rawBase64 = qrBase64.indexOf('base64,') >= 0 ? qrBase64.split('base64,')[1] : qrBase64
|
|
|
- const fsm = uni.getFileSystemManager()
|
|
|
- const tempPath = wx.env.USER_DATA_PATH + '/qr_invite_' + Date.now() + '.png'
|
|
|
- try {
|
|
|
- fsm.writeFileSync(tempPath, rawBase64, 'base64')
|
|
|
- ctx.drawImage(tempPath, 40, 30, 220, 220)
|
|
|
- } catch (e) {
|
|
|
- this._drawQrFallback(ctx, canvasWidth, canvasHeight, code)
|
|
|
- }
|
|
|
- } else {
|
|
|
- this._drawQrFallback(ctx, canvasWidth, canvasHeight, code)
|
|
|
- }
|
|
|
-
|
|
|
- ctx.draw()
|
|
|
- },
|
|
|
-
|
|
|
- _drawQrFallback(ctx, w, h, code) {
|
|
|
- ctx.setFillStyle('#CCCCCC')
|
|
|
- ctx.fillRect(40, 30, 220, 220)
|
|
|
- ctx.setFillStyle('#999999')
|
|
|
- ctx.font = '16px sans-serif'
|
|
|
- ctx.textAlign = 'center'
|
|
|
- ctx.fillText('邀请码: ' + code, w / 2, 150)
|
|
|
- },
|
|
|
-
|
|
|
- saveQrcode() {
|
|
|
- uni.canvasToTempFilePath({
|
|
|
- canvasId: 'qrcodeCanvas',
|
|
|
- success: (res) => {
|
|
|
- uni.saveImageToPhotosAlbum({
|
|
|
- filePath: res.tempFilePath,
|
|
|
- success: () => {
|
|
|
- uni.showToast({
|
|
|
- title: '保存成功',
|
|
|
- icon: 'success'
|
|
|
- })
|
|
|
- },
|
|
|
- fail: () => {
|
|
|
- uni.showToast({
|
|
|
- title: '保存失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
-
|
|
|
- shareToFriend() {
|
|
|
- if (!this.currentInviteCode) {
|
|
|
- uni.showToast({
|
|
|
- title: '请先生成邀请码',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- const inviteUrl = `pages/guide/register/index?inviteCode=${this.currentInviteCode.code}`
|
|
|
-
|
|
|
- uni.share({
|
|
|
- provider: 'weixin',
|
|
|
- scene: {
|
|
|
- type: 0,
|
|
|
- title: '成为成长规划师',
|
|
|
- path: inviteUrl
|
|
|
- },
|
|
|
- success: () => {
|
|
|
- uni.showToast({
|
|
|
- title: '分享成功',
|
|
|
- icon: 'success'
|
|
|
- })
|
|
|
- },
|
|
|
- fail: () => {
|
|
|
- uni.showToast({
|
|
|
- title: '分享失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
-
|
|
|
- getStatusText(status) {
|
|
|
- const statusMap = {
|
|
|
- 'active': '可用',
|
|
|
- 'used': '已用完',
|
|
|
- 'expired': '已过期'
|
|
|
- }
|
|
|
- return statusMap[status] || status
|
|
|
- },
|
|
|
-
|
|
|
- formatDate(dateStr) {
|
|
|
- if (!dateStr) return ''
|
|
|
- const date = parseDate(dateStr)
|
|
|
- if (!date) return ''
|
|
|
- return `${date.getMonth() + 1}月${date.getDate()}日`
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped>
|
|
|
-.container {
|
|
|
- padding: 30rpx;
|
|
|
- background: #F8F8F8;
|
|
|
- min-height: 100vh;
|
|
|
-}
|
|
|
-
|
|
|
-.header {
|
|
|
- text-align: center;
|
|
|
- padding: 60rpx 0 40rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.title {
|
|
|
- font-size: 48rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- display: block;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.subtitle {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
-
|
|
|
-.section-title {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.invite-codes-section,
|
|
|
-.share-section,
|
|
|
-.tips-section {
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 30rpx;
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.invite-codes-list {
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.invite-code-item {
|
|
|
- background: #F8F8F8;
|
|
|
- border-radius: 10rpx;
|
|
|
- padding: 20rpx;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.code-info {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 10rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.code-text {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- font-family: monospace;
|
|
|
-}
|
|
|
-
|
|
|
-.code-status {
|
|
|
- font-size: 24rpx;
|
|
|
- padding: 4rpx 12rpx;
|
|
|
- border-radius: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.status-active {
|
|
|
- background: #E8F5E9;
|
|
|
- color: #4CAF50;
|
|
|
-}
|
|
|
-
|
|
|
-.status-used {
|
|
|
- background: #FFF3E0;
|
|
|
- color: #FF9800;
|
|
|
-}
|
|
|
-
|
|
|
-.status-expired {
|
|
|
- background: #FFEBEE;
|
|
|
- color: #F44336;
|
|
|
-}
|
|
|
-
|
|
|
-.code-stats {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- font-size: 24rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-.stat {
|
|
|
- margin-right: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-generate {
|
|
|
- width: 100%;
|
|
|
- height: 80rpx;
|
|
|
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
- color: #fff;
|
|
|
- border-radius: 40rpx;
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-
|
|
|
-.share-item {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 30rpx 0;
|
|
|
- border-bottom: 1rpx solid #F0F0F0;
|
|
|
-}
|
|
|
-
|
|
|
-.share-item:last-child {
|
|
|
- border-bottom: none;
|
|
|
-}
|
|
|
-
|
|
|
-.share-icon {
|
|
|
- font-size: 48rpx;
|
|
|
- margin-right: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.share-content {
|
|
|
- flex: 1;
|
|
|
-}
|
|
|
-
|
|
|
-.share-title {
|
|
|
- font-size: 30rpx;
|
|
|
- color: #333;
|
|
|
- display: block;
|
|
|
- margin-bottom: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.share-desc {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-
|
|
|
-.share-arrow {
|
|
|
- font-size: 40rpx;
|
|
|
- color: #CCC;
|
|
|
-}
|
|
|
-
|
|
|
-.tips-list {
|
|
|
- margin-top: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.tip-item {
|
|
|
- display: flex;
|
|
|
- align-items: flex-start;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.tip-icon {
|
|
|
- font-size: 32rpx;
|
|
|
- margin-right: 12rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.tip-text {
|
|
|
- font-size: 26rpx;
|
|
|
- color: #666;
|
|
|
- line-height: 1.5;
|
|
|
-}
|
|
|
-
|
|
|
-.qrcode-modal {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background: rgba(0, 0, 0, 0.5);
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- z-index: 999;
|
|
|
-}
|
|
|
-
|
|
|
-.modal-content {
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 40rpx;
|
|
|
- width: 80%;
|
|
|
- max-width: 400rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.modal-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.modal-title {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-.modal-close {
|
|
|
- font-size: 40rpx;
|
|
|
- color: #999;
|
|
|
- padding: 10rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.qrcode-container {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.qrcode-canvas {
|
|
|
- width: 300rpx;
|
|
|
- height: 300rpx;
|
|
|
- border: 1rpx solid #E0E0E0;
|
|
|
- border-radius: 10rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.modal-actions {
|
|
|
- display: flex;
|
|
|
- gap: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-save,
|
|
|
-.btn-close {
|
|
|
- flex: 1;
|
|
|
- height: 80rpx;
|
|
|
- border-radius: 40rpx;
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-save {
|
|
|
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
- color: #fff;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-close {
|
|
|
- background: #F0F0F0;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-</style>
|