detail.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <template>
  2. <view class="container">
  3. <!-- 任务模板信息 -->
  4. <view class="package-header">
  5. <image class="cover" :src="getImageUrl(pkg.coverImage) || '/static/default-cover.png'" mode="aspectFill" />
  6. <view class="header-content">
  7. <text class="name">{{ pkg.name }}</text>
  8. <view class="meta">
  9. <text>{{ pkg.cycleDays }}天</text>
  10. <text>{{ pkg.category }}</text>
  11. </view>
  12. <text class="price">{{ formatPriceWithSymbol(pkg.price) }}</text>
  13. </view>
  14. </view>
  15. <!-- 描述 -->
  16. <view class="section">
  17. <text class="section-title">任务模板介绍</text>
  18. <text class="description">{{ pkg.description || '暂无介绍' }}</text>
  19. </view>
  20. <!-- 任务项选择 -->
  21. <view class="section">
  22. <text class="section-title">选择任务项</text>
  23. <view class="item-list">
  24. <view
  25. class="item-card"
  26. v-for="item in items"
  27. :key="item.id"
  28. :class="{ disabled: item.isRequired }"
  29. @click="toggleItem(item)"
  30. >
  31. <view class="item-check">
  32. <checkbox :checked="selectedItems.includes(item.id)" :disabled="item.isRequired" />
  33. </view>
  34. <view class="item-content">
  35. <text class="item-name">{{ item.taskName }}</text>
  36. <text class="item-desc">{{ item.description }}</text>
  37. <view class="item-meta">
  38. <text>积分: {{ item.points }}</text>
  39. <text>频率: {{ item.frequency }}</text>
  40. <text v-if="item.isRequired" class="required-tag">必选</text>
  41. </view>
  42. </view>
  43. </view>
  44. </view>
  45. </view>
  46. <!-- 底部操作 -->
  47. <view class="footer">
  48. <button class="btn-apply" @click="applyPackage">立即应用</button>
  49. </view>
  50. </view>
  51. </template>
  52. <script>
  53. import { getPackage, getPackageItems, purchasePackage } from '@/utils/api.js'
  54. import config from '@/config.js'
  55. export default {
  56. data() {
  57. return {
  58. pkgId: null,
  59. pkg: {},
  60. items: [],
  61. selectedItems: []
  62. }
  63. },
  64. onLoad(options) {
  65. this.pkgId = options.id
  66. this.loadPackage()
  67. this.loadItems()
  68. },
  69. methods: {
  70. async loadPackage() {
  71. try {
  72. const res = await getPackage(this.pkgId)
  73. this.pkg = res.data || {}
  74. } catch (e) {
  75. console.error(e)
  76. }
  77. },
  78. async loadItems() {
  79. try {
  80. const res = await getPackageItems(this.pkgId)
  81. this.items = res.data || []
  82. // 自动选中必选项
  83. this.items.forEach(item => {
  84. if (item.isRequired) {
  85. this.selectedItems.push(item.id)
  86. }
  87. })
  88. } catch (e) {
  89. console.error(e)
  90. }
  91. },
  92. toggleItem(item) {
  93. if (item.isRequired) return
  94. const idx = this.selectedItems.indexOf(item.id)
  95. if (idx > -1) {
  96. this.selectedItems.splice(idx, 1)
  97. } else {
  98. this.selectedItems.push(item.id)
  99. }
  100. },
  101. async applyPackage() {
  102. // 获取孩子列表,选择一个孩子
  103. uni.showActionSheet({
  104. itemList: ['选择孩子'],
  105. success: async () => {
  106. // 这里应该先获取孩子列表,让用户选择
  107. // 简化处理,直接使用当前家庭的孩子
  108. try {
  109. const res = await purchasePackage(this.pkgId, {
  110. selectedItemIds: this.selectedItems,
  111. memberId: 1 // TODO: 实际应该让用户选择
  112. })
  113. uni.showToast({ title: '应用成功' })
  114. setTimeout(() => {
  115. var pages = getCurrentPages()
  116. if (pages.length > 1) {
  117. uni.navigateBack()
  118. } else {
  119. uni.switchTab({ url: '/pages/index-home/index' })
  120. }
  121. }, 1500)
  122. } catch (e) {
  123. uni.showToast({ title: '应用失败', icon: 'none' })
  124. }
  125. }
  126. })
  127. },
  128. getImageUrl: function(path) {
  129. return config.API_BASE_URL + path
  130. }
  131. }
  132. }
  133. </script>
  134. <style scoped>
  135. .container { min-height: 100vh; background: #f5f5f5; padding-bottom: 120rpx; }
  136. .package-header { position: relative; }
  137. .cover { width: 100%; height: 400rpx; }
  138. .header-content { position: absolute; bottom: 0; left: 0; right: 0; padding: 30rpx; background: linear-gradient(transparent, rgba(0,0,0,0.7)); color: #fff; }
  139. .name { font-size: 36rpx; font-weight: bold; display: block; margin-bottom: 10rpx; }
  140. .meta { display: flex; gap: 20rpx; font-size: 24rpx; opacity: 0.8; margin-bottom: 10rpx; }
  141. .price { font-size: 40rpx; font-weight: bold; }
  142. .section { background: #fff; padding: 30rpx; margin-bottom: 10rpx; }
  143. .section-title { font-size: 30rpx; font-weight: bold; display: block; margin-bottom: 20rpx; }
  144. .description { font-size: 28rpx; color: #666; line-height: 1.6; }
  145. .item-list { display: flex; flex-direction: column; gap: 16rpx; }
  146. .item-card { display: flex; padding: 20rpx; background: #f9f9f9; border-radius: 12rpx; }
  147. .item-card.disabled { opacity: 0.7; }
  148. .item-check { margin-right: 16rpx; }
  149. .item-content { flex: 1; }
  150. .item-name { font-size: 28rpx; font-weight: bold; display: block; margin-bottom: 8rpx; }
  151. .item-desc { font-size: 24rpx; color: #999; display: block; margin-bottom: 8rpx; }
  152. .item-meta { display: flex; gap: 16rpx; font-size: 22rpx; color: #666; }
  153. .required-tag { color: #ff9800; }
  154. .footer { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 30rpx; box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05); }
  155. .btn-apply { background: #4CAF50; color: #fff; border-radius: 40rpx; }
  156. </style>