|
@@ -0,0 +1,256 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="container">
|
|
|
|
|
+ <view class="header">
|
|
|
|
|
+ <text class="title">邀请家人加入</text>
|
|
|
|
|
+ <text class="desc">分享给好友,好友注册后自动成为家庭成员</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 邀请码展示 -->
|
|
|
|
|
+ <view class="invite-card" v-if="inviteCode">
|
|
|
|
|
+ <view class="invite-code">{{ inviteCode }}</view>
|
|
|
|
|
+ <text class="invite-tip">好友也可输入邀请码加入</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 分享按钮区域 -->
|
|
|
|
|
+ <view class="share-section">
|
|
|
|
|
+ <button class="btn-share-large" open-type="share">
|
|
|
|
|
+ <text class="share-icon">📤</text>
|
|
|
|
|
+ <text class="share-text">发送给好友</text>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <text class="share-hint">点击上方按钮,分享到微信好友</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 其他方式 -->
|
|
|
|
|
+ <view class="other-methods">
|
|
|
|
|
+ <view class="method-title">其他方式</view>
|
|
|
|
|
+ <view class="method-item" @click="copyCode">
|
|
|
|
|
+ <text class="method-icon">📋</text>
|
|
|
|
|
+ <text class="method-text">复制邀请码</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="method-item" @click="generatePoster">
|
|
|
|
|
+ <text class="method-icon">🖼️</text>
|
|
|
|
|
+ <text class="method-text">生成邀请海报</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 指导师邀请说明 -->
|
|
|
|
|
+ <view class="guide-tip" v-if="role === 'teacher'">
|
|
|
|
|
+ <text class="tip-title">指导师权限说明</text>
|
|
|
|
|
+ <text class="tip-text">通过邀请码绑定家庭后,您将获得:</text>
|
|
|
|
|
+ <text class="tip-item">• 查看家庭成员任务完成情况</text>
|
|
|
|
|
+ <text class="tip-item">• 审核任务执行</text>
|
|
|
|
|
+ <text class="tip-item">• 制定任务</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 指导师绑定家庭 -->
|
|
|
|
|
+ <view class="section" v-if="role === 'teacher'">
|
|
|
|
|
+ <view class="section-title">绑定已有家庭</view>
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="input"
|
|
|
|
|
+ v-model="familyCode"
|
|
|
|
|
+ placeholder="输入家庭邀请码"
|
|
|
|
|
+ />
|
|
|
|
|
+ <button class="btn-primary" @click="bindFamily">绑定家庭</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 海报弹窗 -->
|
|
|
|
|
+ <view class="poster-mask" v-if="showPoster" @click="showPoster = false">
|
|
|
|
|
+ <view class="poster-content" @click.stop>
|
|
|
|
|
+ <canvas canvas-id="posterCanvas" class="poster-canvas"></canvas>
|
|
|
|
|
+ <button class="btn-save" @click="savePoster">保存到相册</button>
|
|
|
|
|
+ <text class="poster-hint">保存图片后发送给好友</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { getInviteCode, generateInviteCode, bindGuideFamily } from '@/utils/api.js'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ inviteCode: '',
|
|
|
|
|
+ familyCode: '',
|
|
|
|
|
+ role: 'parent',
|
|
|
|
|
+ showPoster: false,
|
|
|
|
|
+ familyName: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onLoad() {
|
|
|
|
|
+ this.role = uni.getStorageSync('role') || 'parent'
|
|
|
|
|
+ this.loadInviteCode()
|
|
|
|
|
+ this.loadFamilyName()
|
|
|
|
|
+ },
|
|
|
|
|
+ onShareAppMessage() {
|
|
|
|
|
+ const title = this.role === 'teacher'
|
|
|
|
|
+ ? '指导师邀请 - 加入我的服务家庭'
|
|
|
|
|
+ : '邀请您加入我的家庭'
|
|
|
|
|
+ return {
|
|
|
|
|
+ title: title,
|
|
|
|
|
+ path: `/pages/login/login?inviteCode=${this.inviteCode}&inviteType=${this.role === 'teacher' ? 'guide' : 'family'}`,
|
|
|
|
|
+ imageUrl: '/static/share-card.png' // 可选:自定义分享卡片图片
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 分享到朋友圈
|
|
|
|
|
+ onShareTimeline() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ title: this.role === 'teacher'
|
|
|
|
|
+ ? '指导师邀请 - 加入我的服务家庭'
|
|
|
|
|
+ : '邀请您加入我的家庭',
|
|
|
|
|
+ query: `inviteCode=${this.inviteCode}&inviteType=${this.role === 'teacher' ? 'guide' : 'family'}`
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ async loadInviteCode() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getInviteCode()
|
|
|
|
|
+ if (res.data) {
|
|
|
|
|
+ this.inviteCode = res.data
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const genRes = await generateInviteCode()
|
|
|
|
|
+ this.inviteCode = genRes.data
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('获取邀请码失败', e)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ async loadFamilyName() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const userInfo = uni.getStorageSync('userInfo')
|
|
|
|
|
+ if (userInfo && userInfo.nickname) {
|
|
|
|
|
+ this.familyName = userInfo.nickname + '的家庭'
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.familyName = '我的家庭'
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.familyName = '我的家庭'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ copyCode() {
|
|
|
|
|
+ uni.setClipboardData({
|
|
|
|
|
+ data: this.inviteCode,
|
|
|
|
|
+ success: () => {
|
|
|
|
|
+ uni.showToast({ title: '已复制邀请码', icon: 'success' })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ generatePoster() {
|
|
|
|
|
+ this.showPoster = true
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.drawPoster()
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ drawPoster() {
|
|
|
|
|
+ const ctx = uni.createCanvasContext('posterCanvas')
|
|
|
|
|
+ const width = 300
|
|
|
|
|
+ const height = 450
|
|
|
|
|
+
|
|
|
|
|
+ // 背景
|
|
|
|
|
+ ctx.setFillStyle('#667eea')
|
|
|
|
|
+ ctx.fillRect(0, 0, width, height)
|
|
|
|
|
+
|
|
|
|
|
+ // 标题
|
|
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
|
|
+ ctx.setFontSize(20)
|
|
|
|
|
+ ctx.setTextAlign('center')
|
|
|
|
|
+ ctx.fillText('邀请加入家庭', width / 2, 40)
|
|
|
|
|
+
|
|
|
|
|
+ // 家庭名
|
|
|
|
|
+ ctx.setFontSize(14)
|
|
|
|
|
+ ctx.fillText(this.familyName, width / 2, 70)
|
|
|
|
|
+
|
|
|
|
|
+ // 邀请码
|
|
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
|
|
+ ctx.setFontSize(36)
|
|
|
|
|
+ ctx.setFontWeight('bold')
|
|
|
|
|
+ ctx.fillText(this.inviteCode, width / 2, 140)
|
|
|
|
|
+
|
|
|
|
|
+ // 提示
|
|
|
|
|
+ ctx.setFontSize(12)
|
|
|
|
|
+ ctx.setFillStyle('rgba(255,255,255,0.8)')
|
|
|
|
|
+ ctx.fillText('长按识别二维码或输入邀请码', width / 2, 180)
|
|
|
|
|
+
|
|
|
|
|
+ // 二维码占位 (实际项目中应该生成真实二维码)
|
|
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
|
|
+ ctx.fillRect(75, 220, 150, 150)
|
|
|
|
|
+ ctx.setFillStyle('#667eea')
|
|
|
|
|
+ ctx.setFontSize(10)
|
|
|
|
|
+ ctx.fillText('扫码注册', width / 2, 300)
|
|
|
|
|
+
|
|
|
|
|
+ ctx.draw()
|
|
|
|
|
+ },
|
|
|
|
|
+ savePoster() {
|
|
|
|
|
+ uni.canvasToTempFilePath({
|
|
|
|
|
+ canvasId: 'posterCanvas',
|
|
|
|
|
+ success: (res) => {
|
|
|
|
|
+ uni.saveImageToPhotosAlbum({
|
|
|
|
|
+ filePath: res.tempFilePath,
|
|
|
|
|
+ success: () => {
|
|
|
|
|
+ uni.showToast({ title: '已保存到相册', icon: 'success' })
|
|
|
|
|
+ this.showPoster = false
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: () => {
|
|
|
|
|
+ uni.showToast({ title: '保存失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: () => {
|
|
|
|
|
+ uni.showToast({ title: '生成失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ async bindFamily() {
|
|
|
|
|
+ if (!this.familyCode) {
|
|
|
|
|
+ uni.showToast({ title: '请输入家庭邀请码', icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ await bindGuideFamily(this.familyCode)
|
|
|
|
|
+ uni.showToast({ title: '绑定成功', icon: 'success' })
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ uni.showToast({ title: e.message || '绑定失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.container { padding: 30rpx; min-height: 100vh; background: #f5f5f5; }
|
|
|
|
|
+.header { text-align: center; padding: 40rpx 0; }
|
|
|
|
|
+.title { font-size: 40rpx; font-weight: bold; display: block; margin-bottom: 16rpx; color: #333; }
|
|
|
|
|
+.desc { font-size: 28rpx; color: #666; }
|
|
|
|
|
+.invite-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20rpx; padding: 50rpx; text-align: center; margin: 30rpx 0; }
|
|
|
|
|
+.invite-code { font-size: 56rpx; font-weight: bold; color: #fff; letter-spacing: 8rpx; margin-bottom: 16rpx; }
|
|
|
|
|
+.invite-tip { font-size: 24rpx; color: rgba(255,255,255,0.8); }
|
|
|
|
|
+
|
|
|
|
|
+.share-section { background: #fff; border-radius: 20rpx; padding: 40rpx; margin: 30rpx 0; text-align: center; }
|
|
|
|
|
+.btn-share-large { background: #07c160; color: #fff; border-radius: 60rpx; padding: 24rpx 60rpx; display: inline-flex; align-items: center; justify-content: center; }
|
|
|
|
|
+.btn-share-large::after { border: none; }
|
|
|
|
|
+.share-icon { font-size: 36rpx; margin-right: 16rpx; }
|
|
|
|
|
+.share-text { font-size: 32rpx; font-weight: bold; }
|
|
|
|
|
+.share-hint { display: block; margin-top: 20rpx; font-size: 24rpx; color: #999; }
|
|
|
|
|
+
|
|
|
|
|
+.other-methods { background: #fff; border-radius: 20rpx; padding: 30rpx; margin: 30rpx 0; }
|
|
|
|
|
+.method-title { font-size: 28rpx; color: #999; margin-bottom: 20rpx; }
|
|
|
|
|
+.method-item { display: flex; align-items: center; padding: 20rpx 0; border-bottom: 1rpx solid #f0f0f0; }
|
|
|
|
|
+.method-item:last-child { border-bottom: none; }
|
|
|
|
|
+.method-icon { font-size: 40rpx; margin-right: 20rpx; }
|
|
|
|
|
+.method-text { font-size: 28rpx; color: #333; }
|
|
|
|
|
+
|
|
|
|
|
+.guide-tip { background: #fff3e0; border-radius: 16rpx; padding: 30rpx; margin: 30rpx 0; }
|
|
|
|
|
+.tip-title { font-size: 28rpx; font-weight: bold; color: #ff9800; display: block; margin-bottom: 16rpx; }
|
|
|
|
|
+.tip-text { font-size: 24rpx; color: #666; display: block; margin-bottom: 12rpx; }
|
|
|
|
|
+.tip-item { font-size: 24rpx; color: #666; display: block; margin-left: 20rpx; line-height: 1.8; }
|
|
|
|
|
+
|
|
|
|
|
+.section { background: #fff; border-radius: 20rpx; padding: 30rpx; margin: 30rpx 0; }
|
|
|
|
|
+.section-title { font-size: 28rpx; font-weight: bold; margin-bottom: 20rpx; }
|
|
|
|
|
+.input { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; margin-bottom: 20rpx; font-size: 28rpx; }
|
|
|
|
|
+
|
|
|
|
|
+.poster-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 999; }
|
|
|
|
|
+.poster-content { background: #fff; border-radius: 20rpx; padding: 30rpx; text-align: center; }
|
|
|
|
|
+.poster-canvas { width: 300px; height: 450px; margin: 0 auto; }
|
|
|
|
|
+.btn-save { background: #07c160; color: #fff; border-radius: 40rpx; padding: 20rpx; margin: 20rpx 0; }
|
|
|
|
|
+.poster-hint { font-size: 24rpx; color: #999; display: block; }
|
|
|
|
|
+</style>
|