| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- <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'
- export default {
- data() {
- return {
- inviteCodes: [],
- currentInviteCode: null,
- showQrcodeModal: false
- }
- },
- onLoad() {
- this.loadInviteCodes()
- },
- onShow() {
- 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 = new Date(dateStr)
- 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>
|