index.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <template>
  2. <view class="container">
  3. <view class="header">
  4. <text class="title">邀请家人加入</text>
  5. <text class="desc">分享给好友,好友注册后自动成为家庭成员</text>
  6. </view>
  7. <!-- 邀请码展示 -->
  8. <view class="invite-card" v-if="inviteCode">
  9. <view class="invite-code">{{ inviteCode }}</view>
  10. <text class="invite-tip">好友也可输入邀请码加入</text>
  11. </view>
  12. <!-- 分享按钮区域 -->
  13. <view class="share-section">
  14. <button class="btn-share-large" open-type="share">
  15. <text class="share-icon">📤</text>
  16. <text class="share-text">发送给好友</text>
  17. </button>
  18. <text class="share-hint">点击上方按钮,分享到微信好友</text>
  19. </view>
  20. <!-- 其他方式 -->
  21. <view class="other-methods">
  22. <view class="method-title">其他方式</view>
  23. <view class="method-item" @click="copyCode">
  24. <text class="method-icon">📋</text>
  25. <text class="method-text">复制邀请码</text>
  26. </view>
  27. <view class="method-item" @click="generatePoster">
  28. <text class="method-icon">🖼️</text>
  29. <text class="method-text">生成邀请海报</text>
  30. </view>
  31. </view>
  32. <!-- 成长规划师邀请说明 -->
  33. <view class="guide-tip" v-if="role === 'teacher'">
  34. <text class="tip-title">成长规划师权限说明</text>
  35. <text class="tip-text">通过邀请码绑定家庭后,您将获得:</text>
  36. <text class="tip-item">• 查看家庭成员任务完成情况</text>
  37. <text class="tip-item">• 审核任务执行</text>
  38. <text class="tip-item">• 制定任务</text>
  39. </view>
  40. <!-- 成长规划师绑定家庭 -->
  41. <view class="section" v-if="role === 'teacher'">
  42. <view class="section-title">绑定已有家庭</view>
  43. <input
  44. class="input"
  45. v-model="familyCode"
  46. placeholder="输入家庭邀请码"
  47. />
  48. <button class="btn-primary" @click="bindFamily">绑定家庭</button>
  49. </view>
  50. <!-- 海报弹窗 -->
  51. <view class="poster-mask" v-if="showPoster" @click="showPoster = false">
  52. <view class="poster-content" @click.stop>
  53. <canvas canvas-id="posterCanvas" class="poster-canvas"></canvas>
  54. <button class="btn-save" @click="savePoster">保存到相册</button>
  55. <text class="poster-hint">保存图片后发送给好友</text>
  56. </view>
  57. </view>
  58. </view>
  59. </template>
  60. <script>
  61. import { getInviteCode, generateFamilyInviteCode, bindGuideFamily } from '@/utils/api.js'
  62. export default {
  63. data() {
  64. return {
  65. inviteCode: '',
  66. familyCode: '',
  67. role: 'parent',
  68. showPoster: false,
  69. familyName: ''
  70. }
  71. },
  72. onLoad() {
  73. this.role = uni.getStorageSync('role') || 'parent'
  74. this.loadInviteCode()
  75. this.loadFamilyName()
  76. },
  77. onShareAppMessage() {
  78. var code = this.inviteCode || ''
  79. var path = '/pages/invite/join'
  80. if (code) {
  81. path = path + '?inviteCode=' + code
  82. }
  83. return {
  84. title: '我在浠艾福发现了好用的家庭成长工具,快来加入!',
  85. path: path,
  86. imageUrl: ''
  87. }
  88. },
  89. methods: {
  90. async loadInviteCode() {
  91. try {
  92. const res = await getInviteCode()
  93. if (res.data) {
  94. this.inviteCode = res.data
  95. } else {
  96. const genRes = await generateFamilyInviteCode()
  97. this.inviteCode = genRes.data
  98. }
  99. } catch (e) {
  100. console.error('获取邀请码失败', e)
  101. }
  102. },
  103. async loadFamilyName() {
  104. try {
  105. const userInfo = uni.getStorageSync('userInfo')
  106. if (userInfo && userInfo.nickname) {
  107. this.familyName = userInfo.nickname + '的家庭'
  108. } else {
  109. this.familyName = '我的家庭'
  110. }
  111. } catch (e) {
  112. this.familyName = '我的家庭'
  113. }
  114. },
  115. copyCode() {
  116. uni.setClipboardData({
  117. data: this.inviteCode,
  118. success: () => {
  119. uni.showToast({ title: '已复制邀请码', icon: 'success' })
  120. }
  121. })
  122. },
  123. generatePoster() {
  124. this.showPoster = true
  125. this.$nextTick(() => {
  126. this.drawPoster()
  127. })
  128. },
  129. drawPoster() {
  130. const ctx = uni.createCanvasContext('posterCanvas')
  131. const width = 300
  132. const height = 450
  133. // 背景
  134. ctx.setFillStyle('#667eea')
  135. ctx.fillRect(0, 0, width, height)
  136. // 标题
  137. ctx.setFillStyle('#ffffff')
  138. ctx.setFontSize(20)
  139. ctx.fillText('邀请加入家庭', width / 2, 40)
  140. // 家庭名
  141. ctx.setFontSize(14)
  142. ctx.fillText(this.familyName, width / 2, 70)
  143. // 邀请码
  144. ctx.setFillStyle('#ffffff')
  145. ctx.setFontSize(32)
  146. ctx.fillText(this.inviteCode, width / 2, 130)
  147. // 提示
  148. ctx.setFontSize(12)
  149. ctx.setFillStyle('rgba(255,255,255,0.8)')
  150. ctx.fillText('输入邀请码加入家庭', width / 2, 170)
  151. // 二维码占位
  152. ctx.setFillStyle('#ffffff')
  153. ctx.fillRect(75, 210, 150, 150)
  154. ctx.setFillStyle('#667eea')
  155. ctx.setFontSize(12)
  156. ctx.fillText('扫码注册', width / 2, 300)
  157. ctx.draw()
  158. },
  159. savePoster() {
  160. uni.canvasToTempFilePath({
  161. canvasId: 'posterCanvas',
  162. success: (res) => {
  163. uni.saveImageToPhotosAlbum({
  164. filePath: res.tempFilePath,
  165. success: () => {
  166. uni.showToast({ title: '已保存到相册', icon: 'success' })
  167. this.showPoster = false
  168. },
  169. fail: () => {
  170. uni.showToast({ title: '保存失败', icon: 'none' })
  171. }
  172. })
  173. },
  174. fail: () => {
  175. uni.showToast({ title: '生成失败', icon: 'none' })
  176. }
  177. })
  178. },
  179. async bindFamily() {
  180. if (!this.familyCode) {
  181. uni.showToast({ title: '请输入家庭邀请码', icon: 'none' })
  182. return
  183. }
  184. try {
  185. await bindGuideFamily(this.familyCode)
  186. uni.showToast({ title: '绑定成功', icon: 'success' })
  187. } catch (e) {
  188. uni.showToast({ title: e.message || '绑定失败', icon: 'none' })
  189. }
  190. }
  191. }
  192. }
  193. </script>
  194. <style scoped>
  195. .container { padding: 30rpx; min-height: 100vh; background: #f5f5f5; }
  196. .header { text-align: center; padding: 40rpx 0; }
  197. .title { font-size: 40rpx; font-weight: bold; display: block; margin-bottom: 16rpx; color: #333; }
  198. .desc { font-size: 28rpx; color: #666; }
  199. .invite-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20rpx; padding: 50rpx; text-align: center; margin: 30rpx 0; }
  200. .invite-code { font-size: 56rpx; font-weight: bold; color: #fff; letter-spacing: 8rpx; margin-bottom: 16rpx; }
  201. .invite-tip { font-size: 24rpx; color: rgba(255,255,255,0.8); }
  202. .share-section { background: #fff; border-radius: 20rpx; padding: 40rpx; margin: 30rpx 0; text-align: center; }
  203. .btn-share-large { background: #07c160; color: #fff; border-radius: 60rpx; padding: 24rpx 60rpx; display: inline-flex; align-items: center; justify-content: center; }
  204. .btn-share-large::after { border: none; }
  205. .share-icon { font-size: 36rpx; margin-right: 16rpx; }
  206. .share-text { font-size: 32rpx; font-weight: bold; }
  207. .share-hint { display: block; margin-top: 20rpx; font-size: 24rpx; color: #999; }
  208. .other-methods { background: #fff; border-radius: 20rpx; padding: 30rpx; margin: 30rpx 0; }
  209. .method-title { font-size: 28rpx; color: #999; margin-bottom: 20rpx; }
  210. .method-item { display: flex; align-items: center; padding: 20rpx 0; border-bottom: 1rpx solid #f0f0f0; }
  211. .method-item:last-child { border-bottom: none; }
  212. .method-icon { font-size: 40rpx; margin-right: 20rpx; }
  213. .method-text { font-size: 28rpx; color: #333; }
  214. .guide-tip { background: #fff3e0; border-radius: 16rpx; padding: 30rpx; margin: 30rpx 0; }
  215. .tip-title { font-size: 28rpx; font-weight: bold; color: #ff9800; display: block; margin-bottom: 16rpx; }
  216. .tip-text { font-size: 24rpx; color: #666; display: block; margin-bottom: 12rpx; }
  217. .tip-item { font-size: 24rpx; color: #666; display: block; margin-left: 20rpx; line-height: 1.8; }
  218. .section { background: #fff; border-radius: 20rpx; padding: 30rpx; margin: 30rpx 0; }
  219. .section-title { font-size: 28rpx; font-weight: bold; margin-bottom: 20rpx; }
  220. .input { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; margin-bottom: 20rpx; font-size: 28rpx; }
  221. .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; }
  222. .poster-content { background: #fff; border-radius: 20rpx; padding: 30rpx; text-align: center; }
  223. .poster-canvas { width: 300px; height: 450px; margin: 0 auto; }
  224. .btn-save { background: #07c160; color: #fff; border-radius: 40rpx; padding: 20rpx; margin: 20rpx 0; }
  225. .poster-hint { font-size: 24rpx; color: #999; display: block; }
  226. </style>