| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- <template>
- <view class="container">
- <!-- 邀请信息 -->
- <view class="invite-card" v-if="guideInfo">
- <view class="guide-avatar">{{ guideInfo.guideName ? guideInfo.guideName[0] : '师' }}</view>
- <text class="guide-name">{{ guideInfo.guideName }}</text>
- <text class="invite-desc">邀请您绑定家庭关系</text>
- <text class="expire-text">邀请有效期至 {{ formatDate(guideInfo.expireTime) }}</text>
- </view>
- <!-- 绑定表单 -->
- <view class="bind-form" v-if="!bound">
- <view class="form-item">
- <text class="label">选择家庭</text>
- <picker :range="families" range-key="name" @change="onFamilyChange">
- <view class="picker">
- {{ selectedFamily ? selectedFamily.name : '请选择家庭' }}
- </view>
- </picker>
- </view>
- <view class="form-item">
- <text class="label">服务类型</text>
- <picker :range="serviceTypes" range-key="label" @change="onServiceTypeChange">
- <view class="picker">
- {{ serviceType.label }}
- </view>
- </picker>
- </view>
- <view class="form-item">
- <text class="label">服务价格</text>
- <input class="input" type="digit" v-model="servicePrice" placeholder="请输入服务价格" />
- </view>
- <button class="btn-confirm" @click="confirmBind">确认绑定</button>
- </view>
- <!-- 已绑定提示 -->
- <view class="bound-card" v-else>
- <text class="success-icon">✓</text>
- <text class="success-text">绑定成功</text>
- <text class="success-desc">您已成功绑定成长规划师</text>
- <button class="btn-go" @click="goHome">返回首页</button>
- </view>
- <!-- 分享邀请 -->
- <view class="share-section" v-if="isGuide">
- <text class="section-title">邀请家长绑定</text>
- <view class="invite-url">
- <text selectable>{{ inviteUrl }}</text>
- </view>
- <button class="btn-copy" @click="copyUrl">复制邀请链接</button>
- <button class="btn-qrcode" @click="showQrcode">生成二维码</button>
- </view>
- <!-- 二维码弹窗 -->
- <uni-popup ref="qrPopup" type="center">
- <view class="qr-popup">
- <text class="qr-title">扫码绑定</text>
- <image class="qr-image" :src="qrcodeUrl" mode="widthFix" />
- <text class="qr-desc">微信扫一扫绑定</text>
- <text class="close-btn" @click="closeQrcode">关闭</text>
- </view>
- </uni-popup>
- </view>
- </template>
- <script>
- import { generateBindInvite, generateQrCodeFromText } from '@/utils/api.js'
- export default {
- data() {
- return {
- inviteToken: '',
- guideId: null,
- guideInfo: null,
- families: [],
- selectedFamily: null,
- serviceTypes: [
- { label: '咨询', value: 'consulting' },
- { label: '月度服务', value: 'monthly' },
- { label: '年度服务', value: 'yearly' }
- ],
- serviceType: { label: '咨询', value: 'consulting' },
- servicePrice: 0,
- bound: false,
- isGuide: false,
- inviteUrl: '',
- qrcodeUrl: ''
- }
- },
- onLoad(options) {
- if (options.token) {
- this.inviteToken = options.token
- this.guideId = options.guideId
- this.loadGuideInfo()
- }
-
- // 判断是否为成长规划师
- const roles = uni.getStorageSync('roles') || ''
- this.isGuide = roles.includes('teacher')
-
- if (this.isGuide) {
- this.generateInvite()
- }
-
- this.loadFamilies()
- },
- methods: {
- loadGuideInfo() {
- // 从服务器获取成长规划师信息
- uni.request({
- url: `${getApp().globalData.baseUrl}/api/guide/bind/validate`,
- data: { token: this.inviteToken },
- success: (res) => {
- if (res.data.code === 200) {
- this.guideInfo = res.data.data
- }
- }
- })
- },
- loadFamilies() {
- // 获取家庭列表
- uni.request({
- url: `${getApp().globalData.baseUrl}/api/family/user/family-members`,
- header: { Authorization: `Bearer ${uni.getStorageSync('token')}` },
- success: (res) => {
- if (res.data.code === 200) {
- this.families = res.data.data || []
- }
- }
- })
- },
- onFamilyChange(e) {
- this.selectedFamily = this.families[e.detail.value]
- },
- onServiceTypeChange(e) {
- this.serviceType = this.serviceTypes[e.detail.value]
- },
- async confirmBind() {
- if (!this.selectedFamily) {
- uni.showToast({ title: '请选择家庭', icon: 'none' })
- return
- }
-
- uni.showLoading({ title: '绑定中...' })
- try {
- await uni.request({
- url: `${getApp().globalData.baseUrl}/api/bind/accept`,
- method: 'POST',
- header: { Authorization: `Bearer ${uni.getStorageSync('token')}` },
- data: {
- guideId: this.guideId,
- bindToken: this.inviteToken,
- familyId: this.selectedFamily.id,
- serviceType: this.serviceType.value,
- servicePrice: this.servicePrice
- }
- })
- uni.showToast({ title: '绑定成功' })
- this.bound = true
- } catch (e) {
- uni.showToast({ title: '绑定失败', icon: 'none' })
- } finally {
- uni.hideLoading()
- }
- },
- async generateInvite() {
- try {
- const res = await generateBindInvite()
- this.inviteUrl = res.data.inviteUrl
- this.guideInfo = res.data
- } catch (e) {
- console.error(e)
- }
- },
- copyUrl() {
- uni.setClipboardData({ data: this.inviteUrl })
- uni.showToast({ title: '已复制' })
- },
- async showQrcode() {
- if (!this.inviteUrl) {
- uni.showToast({ title: '请先生成邀请', icon: 'none' })
- return
- }
- try {
- const res = await generateQrCodeFromText(this.inviteUrl)
- if (res.code === 200 && res.data) {
- this.qrcodeUrl = res.data.qrCodeBase64
- } else {
- uni.showToast({ title: res.message || '生成二维码失败', icon: 'none' })
- return
- }
- } catch (e) {
- uni.showToast({ title: '生成二维码失败', icon: 'none' })
- return
- }
- this.$refs.qrPopup.open()
- },
- closeQrcode() {
- this.$refs.qrPopup.close()
- },
- formatDate(date) {
- if (!date) return ''
- const d = new Date(date)
- return `${d.getMonth() + 1}-${d.getDate()}`
- },
- goHome() {
- uni.navigateTo({ url: '/pages/home-pages/parent-index' })
- }
- }
- }
- </script>
- <style scoped>
- .container { min-height: 100vh; background: #f5f5f5; padding: 30rpx; }
- .invite-card, .bound-card {
- background: #fff;
- border-radius: 16rpx;
- padding: 60rpx 30rpx;
- text-align: center;
- margin-bottom: 30rpx;
- }
- .guide-avatar {
- width: 120rpx;
- height: 120rpx;
- background: #4CAF50;
- border-radius: 50%;
- margin: 0 auto 20rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 48rpx;
- color: #fff;
- }
- .guide-name { font-size: 36rpx; font-weight: bold; display: block; margin-bottom: 10rpx; }
- .invite-desc { font-size: 28rpx; color: #666; display: block; margin-bottom: 20rpx; }
- .expire-text { font-size: 24rpx; color: #999; }
- .bind-form { background: #fff; border-radius: 16rpx; padding: 30rpx; }
- .form-item { margin-bottom: 30rpx; }
- .label { display: block; font-size: 28rpx; color: #333; margin-bottom: 16rpx; }
- .input, .picker { padding: 20rpx; background: #f5f5f5; border-radius: 12rpx; font-size: 28rpx; }
- .btn-confirm { background: #4CAF50; color: #fff; border-radius: 40rpx; margin-top: 40rpx; }
- .success-icon { font-size: 80rpx; color: #4CAF50; display: block; margin-bottom: 20rpx; }
- .success-text { font-size: 36rpx; font-weight: bold; display: block; margin-bottom: 10rpx; }
- .success-desc { font-size: 28rpx; color: #666; display: block; margin-bottom: 40rpx; }
- .btn-go { background: #4CAF50; color: #fff; border-radius: 40rpx; }
- .share-section { background: #fff; border-radius: 16rpx; padding: 30rpx; margin-top: 30rpx; }
- .section-title { font-size: 30rpx; font-weight: bold; display: block; margin-bottom: 20rpx; }
- .invite-url { background: #f5f5f5; padding: 20rpx; border-radius: 8rpx; margin-bottom: 20rpx; font-size: 24rpx; word-break: break-all; }
- .btn-copy, .btn-qrcode { background: #4CAF50; color: #fff; margin-bottom: 20rpx; }
- .qr-popup { background: #fff; border-radius: 16rpx; padding: 40rpx; text-align: center; width: 500rpx; }
- .qr-title { font-size: 32rpx; font-weight: bold; display: block; margin-bottom: 30rpx; }
- .qr-image { width: 300rpx; height: 300rpx; margin-bottom: 20rpx; }
- .qr-desc { font-size: 26rpx; color: #666; display: block; margin-bottom: 30rpx; }
- .close-btn { font-size: 28rpx; color: #999; }
- </style>
|