rewards.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <template>
  2. <view class="container">
  3. </view>
  4. <view class="reward-list" v-if="rewards.length > 0">
  5. <view v-for="reward in rewards" :key="reward.id" class="reward-item">
  6. <view class="reward-info">
  7. <view class="reward-title">{{ reward.title }}</view>
  8. <view class="reward-progress">
  9. <progress
  10. :percent="(currentPoints / reward.pointsRequired) * 100"
  11. stroke-width="6"
  12. activeColor="#F97316"
  13. backgroundColor="#eee"
  14. />
  15. <text class="progress-text">{{ currentPoints }}/{{ reward.pointsRequired }}积分</text>
  16. </view>
  17. </view>
  18. <button
  19. class="btn-exchange"
  20. :disabled="currentPoints < reward.pointsRequired"
  21. @click="exchangeReward(reward.id)"
  22. >
  23. 兑换
  24. </button>
  25. </view>
  26. </view>
  27. <view class="empty" v-else>
  28. <text>暂无心愿单,快去添加吧</text>
  29. </view>
  30. <button class="btn-primary add-btn" @click="showAddModal = true">
  31. + 添加心愿
  32. </button>
  33. <!-- 添加心愿弹窗 -->
  34. <view class="modal-mask" v-if="showAddModal" @click="showAddModal = false">
  35. <view class="modal" @click.stop>
  36. <view class="modal-title">{{ currentRole === 'child' ? '添加心愿' : '添加心愿' }}</view>
  37. <view class="form-item">
  38. <view class="form-label">心愿名称 <text class="required">*</text></view>
  39. <input v-model="newReward.title" placeholder="请输入心愿名称" />
  40. </view>
  41. <!-- 家长模式可以填写积分,孩子模式不需要填写积分 -->
  42. <view class="form-item" v-if="currentRole === 'parent'">
  43. <view class="form-label">所需积分 <text class="required">*</text></view>
  44. <input v-model="newReward.pointsRequired" type="number" placeholder="请输入所需积分" />
  45. </view>
  46. <!-- 孩子模式提示 -->
  47. <view class="form-tip" v-if="currentRole === 'child'">
  48. <text>孩子创建心愿后,需要家长填写所需积分才能生效</text>
  49. </view>
  50. <view class="modal-btns">
  51. <button @click="showAddModal = false">取消</button>
  52. <button class="btn-primary" @click="createReward">确定</button>
  53. </view>
  54. </view>
  55. </view>
  56. </view>
  57. </template>
  58. <script>
  59. import config from '@/config.js'
  60. import { getWishlist, exchangeReward as exchangeRewardApi, createReward as createRewardApi, getPointsBalance, getChildren, getParentWishlist, createParentWishlist, exchangeParentWishlist } from '../../utils/api.js'
  61. export default {
  62. data() {
  63. return {
  64. rewards: [],
  65. currentPoints: 0,
  66. currentChildId: '',
  67. currentRole: 'parent', // 当前角色: parent/child
  68. showAddModal: false,
  69. newReward: {
  70. title: '',
  71. pointsRequired: ''
  72. }
  73. }
  74. },
  75. onShow() {
  76. // 规划师角色跳转到消息页
  77. const role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
  78. if (role === 'teacher') {
  79. uni.redirectTo({ url: '/pages/teacher/messages' })
  80. return
  81. }
  82. this.loadRewards()
  83. },
  84. methods: {
  85. async loadRewards() {
  86. // 获取当前角色用于判断加载哪个API
  87. const currentRole = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
  88. try {
  89. if (currentRole === 'parent') {
  90. // 家长模式:使用家长心愿单API
  91. const res = await getParentWishlist()
  92. this.rewards = res.data || []
  93. // 家长积分从用户信息获取
  94. const userInfo = await this.getUserInfo()
  95. this.currentPoints = userInfo.totalPoints || 0
  96. } else {
  97. // 孩子模式:使用孩子心愿单API
  98. const childrenRes = await getChildren()
  99. const children = childrenRes.data
  100. if (children && children.length > 0) {
  101. this.currentChildId = children[0].id
  102. const balanceRes = await getPointsBalance(this.currentChildId)
  103. this.currentPoints = balanceRes.data.balance
  104. const res = await getWishlist(this.currentChildId)
  105. this.rewards = res.data || []
  106. }
  107. }
  108. } catch (e) {
  109. console.error('加载心愿单失败', e)
  110. }
  111. },
  112. async getUserInfo() {
  113. return new Promise((resolve, reject) => {
  114. uni.request({
  115. url: config.api('/api/user/info'),
  116. method: 'POST',
  117. header: {
  118. 'Authorization': 'Bearer ' + uni.getStorageSync('token')
  119. },
  120. success: (res) => {
  121. if (res.data.code === 200) {
  122. resolve(res.data.data)
  123. } else {
  124. reject(res.data)
  125. }
  126. },
  127. fail: reject
  128. })
  129. })
  130. },
  131. async exchangeReward(rewardId) {
  132. uni.showModal({
  133. title: '确认兑换',
  134. content: '确定要兑换这个奖励吗?',
  135. success: async (res) => {
  136. if (res.confirm) {
  137. try {
  138. if (this.currentRole === 'parent') {
  139. await exchangeParentWishlist(rewardId)
  140. } else {
  141. await exchangeRewardApi(rewardId, this.currentChildId)
  142. }
  143. uni.showToast({ title: '兑换成功', icon: 'success' })
  144. this.loadRewards()
  145. } catch (e) {
  146. console.error('兑换失败', e)
  147. }
  148. }
  149. }
  150. })
  151. },
  152. goToBadge() {
  153. uni.navigateTo({ url: '/pages/rewards/badge' })
  154. },
  155. async createReward() {
  156. if (!this.newReward.title) {
  157. uni.showToast({ title: '请填写心愿名称', icon: 'none' })
  158. return
  159. }
  160. try {
  161. if (this.currentRole === 'parent') {
  162. // 家长模式:创建家长心愿,需要填写积分
  163. if (!this.newReward.pointsRequired) {
  164. uni.showToast({ title: '请填写所需积分', icon: 'none' })
  165. return
  166. }
  167. await createParentWishlist({
  168. title: this.newReward.title,
  169. pointsRequired: parseInt(this.newReward.pointsRequired)
  170. })
  171. uni.showToast({ title: '添加成功', icon: 'success' })
  172. } else {
  173. // 孩子模式:创建孩子心愿,积分为0,等待家长填写
  174. await createRewardApi({
  175. title: this.newReward.title,
  176. memberId: this.currentChildId
  177. // pointsRequired不传,默认为0
  178. })
  179. uni.showToast({ title: '已提交,等待家长填写积分', icon: 'success' })
  180. }
  181. this.showAddModal = false
  182. this.newReward = { title: '', pointsRequired: '' }
  183. this.loadRewards()
  184. } catch (e) {
  185. console.error('添加心愿失败', e)
  186. }
  187. }
  188. }
  189. }
  190. </script>
  191. <style scoped>
  192. .container {
  193. padding: 30rpx;
  194. padding-bottom: 120rpx;
  195. }
  196. .nav-item {
  197. display: flex;
  198. align-items: center;
  199. padding: 16rpx 24rpx;
  200. background: #fff;
  201. border-radius: 12rpx;
  202. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
  203. }
  204. .nav-icon {
  205. font-size: 32rpx;
  206. margin-right: 8rpx;
  207. }
  208. .nav-text {
  209. font-size: 28rpx;
  210. color: #333;
  211. font-weight: 500;
  212. }
  213. .reward-item {
  214. background: #fff;
  215. border-radius: 16rpx;
  216. padding: 30rpx;
  217. margin-bottom: 20rpx;
  218. display: flex;
  219. justify-content: space-between;
  220. align-items: center;
  221. }
  222. .reward-title {
  223. font-size: 30rpx;
  224. color: #333;
  225. margin-bottom: 15rpx;
  226. }
  227. .reward-progress {
  228. width: 400rpx;
  229. }
  230. .progress-text {
  231. font-size: 22rpx;
  232. color: #999;
  233. display: block;
  234. margin-top: 8rpx;
  235. }
  236. .btn-exchange {
  237. background: #F97316;
  238. color: #fff;
  239. font-size: 26rpx;
  240. padding: 10rpx 30rpx;
  241. border-radius: 30rpx;
  242. }
  243. .btn-exchange[disabled] {
  244. background: #ccc;
  245. }
  246. .add-btn {
  247. position: fixed;
  248. bottom: 30rpx;
  249. left: 30rpx;
  250. right: 30rpx;
  251. }
  252. .empty {
  253. text-align: center;
  254. color: #999;
  255. padding: 100rpx;
  256. }
  257. .modal-mask {
  258. position: fixed;
  259. top: 0;
  260. left: 0;
  261. right: 0;
  262. bottom: 0;
  263. background: rgba(0, 0, 0, 0.5);
  264. display: flex;
  265. align-items: center;
  266. justify-content: center;
  267. }
  268. .modal {
  269. background: #fff;
  270. border-radius: 20rpx;
  271. padding: 40rpx;
  272. width: 80%;
  273. }
  274. .modal-title {
  275. font-size: 32rpx;
  276. font-weight: bold;
  277. margin-bottom: 30rpx;
  278. text-align: center;
  279. }
  280. .form-item {
  281. margin-bottom: 20rpx;
  282. }
  283. .form-item input {
  284. border: 1rpx solid #ddd;
  285. border-radius: 10rpx;
  286. padding: 20rpx;
  287. font-size: 28rpx;
  288. }
  289. .form-label {
  290. font-size: 28rpx;
  291. color: #333;
  292. margin-bottom: 10rpx;
  293. }
  294. .form-label .required {
  295. color: #F97316;
  296. }
  297. .form-tip {
  298. font-size: 24rpx;
  299. color: #999;
  300. padding: 20rpx;
  301. background: #f5f5f5;
  302. border-radius: 10rpx;
  303. margin-bottom: 20rpx;
  304. }
  305. .modal-btns {
  306. display: flex;
  307. gap: 20rpx;
  308. margin-top: 30rpx;
  309. }
  310. .modal-btns button {
  311. flex: 1;
  312. }
  313. </style>