小说小程序前端代码仓库(小程序)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

407 lines
8.1 KiB

  1. <template>
  2. <uv-popup ref="popup" mode="bottom" :round="20" :safeAreaInsetBottom="true" @close="handleClose">
  3. <view class="interactive-gift-popup">
  4. <!-- 标题栏 -->
  5. <view class="popup-header">
  6. <text class="popup-title">互动打赏</text>
  7. <uv-icon name="close" size="40rpx" color="#999" @click="close"></uv-icon>
  8. </view>
  9. <!-- 当前选中礼物信息 -->
  10. <view class="selected-gift-info" v-if="selectedGift">
  11. <view class="selected-gift-left">
  12. <view class="selected-gift-img">
  13. <image :src="selectedGift.image" mode="aspectFill"></image>
  14. </view>
  15. <view class="selected-gift-details">
  16. <text class="selected-gift-name">{{ selectedGift.title }}</text>
  17. <text class="selected-gift-price">{{ selectedGift.integerPrice }}豆豆</text>
  18. </view>
  19. </view>
  20. <view class="selected-gift-right">
  21. <uv-number-box
  22. v-model="giftCount"
  23. :min="1"
  24. :max="99"
  25. size="small"
  26. bgColor="#f5f5f5"
  27. color="#223a7a"
  28. />
  29. </view>
  30. </view>
  31. <!-- 用户余额信息 -->
  32. <view class="balance-info">
  33. <view class="balance-text">
  34. 账户余额<text class="bean-amount">{{ userInfo.integerPrice }} 豆豆</text>
  35. </view>
  36. <view v-if="selectedGift && totalPrice > userInfo.integerPrice" class="insufficient-tip">
  37. 余额不足请先充值
  38. </view>
  39. </view>
  40. <view class="gift-grid">
  41. <view
  42. v-for="(gift, idx) in giftList"
  43. :key="gift.id"
  44. :class="['gift-item', { selected: selectedIndex === idx }]"
  45. @click="selectGift(idx)"
  46. >
  47. <view class="gift-img">
  48. <image :src="gift.image" mode="aspectFill"></image>
  49. </view>
  50. <view class="gift-name">{{ gift.title }}</view>
  51. <view class="gift-price">{{ gift.integerPrice }}豆豆</view>
  52. </view>
  53. </view>
  54. <!-- 底部操作栏 -->
  55. <view class="popup-bottom">
  56. <view class="total-info">
  57. <text class="total-text">总计</text>
  58. <text class="total-price">{{ totalPrice }}豆豆</text>
  59. </view>
  60. <button class="gift-btn" @click="sendGift" :disabled="!selectedGift || loading">
  61. {{ loading ? '赠送中...' : '赠送' }}
  62. </button>
  63. </view>
  64. </view>
  65. </uv-popup>
  66. </template>
  67. <script>
  68. export default {
  69. name: 'InteractiveGiftPopup',
  70. props: {
  71. bookId: {
  72. type: [String, Number],
  73. required: true
  74. }
  75. },
  76. data() {
  77. return {
  78. giftList: [],
  79. selectedIndex: 0,
  80. selectedGift: null,
  81. giftCount: 1,
  82. loading: false,
  83. }
  84. },
  85. computed: {
  86. totalPrice() {
  87. if (!this.selectedGift) return 0;
  88. return this.selectedGift.integerPrice * this.giftCount;
  89. }
  90. },
  91. methods: {
  92. // 打开弹窗
  93. open() {
  94. this.$refs.popup.open();
  95. this.getGiftList();
  96. this.getUserBalance();
  97. },
  98. // 关闭弹窗
  99. close() {
  100. this.$refs.popup.close();
  101. },
  102. // 处理关闭事件
  103. handleClose() {
  104. this.resetData();
  105. },
  106. // 重置数据
  107. resetData() {
  108. this.selectedIndex = 0;
  109. this.selectedGift = null;
  110. this.giftCount = 1;
  111. this.loading = false;
  112. },
  113. // 获取礼物列表
  114. getGiftList() {
  115. this.$fetch('getInteractionGiftList').then(res => {
  116. this.giftList = res.records || [];
  117. if (this.giftList.length > 0) {
  118. this.selectGift(0);
  119. }
  120. }).catch(err => {
  121. console.error('获取礼物列表失败:', err);
  122. uni.showToast({
  123. title: '获取礼物列表失败',
  124. icon: 'none'
  125. });
  126. });
  127. },
  128. // 选择礼物
  129. selectGift(index) {
  130. this.selectedIndex = index;
  131. this.selectedGift = this.giftList[index];
  132. this.giftCount = 1;
  133. },
  134. // 获取用户余额
  135. getUserBalance() {
  136. // 从store中获取用户信息
  137. this.$store.commit('getUserInfo');
  138. },
  139. // 赠送礼物
  140. sendGift() {
  141. if (!this.selectedGift) {
  142. uni.showToast({
  143. title: '请选择礼物',
  144. icon: 'none'
  145. });
  146. return;
  147. }
  148. // 检查余额是否足够
  149. if (this.totalPrice > this.userInfo.integerPrice) {
  150. uni.showToast({
  151. title: '余额不足,请先充值',
  152. icon: 'none'
  153. });
  154. setTimeout(() => {
  155. uni.navigateTo({
  156. url: '/pages_order/mine/recharge'
  157. })
  158. }, 600)
  159. return;
  160. }
  161. this.loading = true;
  162. // 创建订单
  163. this.$fetch('giveGift', {
  164. giftId: this.selectedGift.id,
  165. num: this.giftCount,
  166. bookId: this.bookId,
  167. }).then(res => {
  168. // 支付成功
  169. uni.showToast({
  170. title: '赠送成功',
  171. icon: 'success'
  172. });
  173. // 通知父组件更新数据
  174. this.$emit('giftSent', {
  175. gift: this.selectedGift,
  176. count: this.giftCount,
  177. totalPrice: this.totalPrice
  178. });
  179. this.getUserBalance()
  180. this.close();
  181. }).catch(err => {
  182. console.error('赠送失败:', err);
  183. uni.showToast({
  184. title: err.message || '赠送失败',
  185. icon: 'none'
  186. });
  187. }).finally(() => {
  188. this.loading = false;
  189. });
  190. }
  191. }
  192. }
  193. </script>
  194. <style lang="scss" scoped>
  195. .interactive-gift-popup {
  196. background: #fff;
  197. border-radius: 20rpx 20rpx 0 0;
  198. max-height: 80vh;
  199. display: flex;
  200. flex-direction: column;
  201. }
  202. .popup-header {
  203. display: flex;
  204. justify-content: space-between;
  205. align-items: center;
  206. padding: 32rpx 32rpx 24rpx 32rpx;
  207. border-bottom: 1rpx solid #f5f5f5;
  208. .popup-title {
  209. font-size: 32rpx;
  210. font-weight: bold;
  211. color: #333;
  212. }
  213. }
  214. .selected-gift-info {
  215. display: flex;
  216. justify-content: space-between;
  217. align-items: center;
  218. padding: 24rpx 32rpx;
  219. background: #f8f9ff;
  220. margin: 0 32rpx 24rpx 32rpx;
  221. border-radius: 16rpx;
  222. .selected-gift-left {
  223. display: flex;
  224. align-items: center;
  225. gap: 16rpx;
  226. .selected-gift-img {
  227. width: 60rpx;
  228. height: 60rpx;
  229. border-radius: 8rpx;
  230. overflow: hidden;
  231. image {
  232. width: 100%;
  233. height: 100%;
  234. }
  235. }
  236. .selected-gift-details {
  237. display: flex;
  238. flex-direction: column;
  239. gap: 4rpx;
  240. .selected-gift-name {
  241. font-size: 28rpx;
  242. color: #333;
  243. font-weight: 500;
  244. }
  245. .selected-gift-price {
  246. font-size: 24rpx;
  247. color: #223a7a;
  248. }
  249. }
  250. }
  251. }
  252. .balance-info {
  253. padding: 0 32rpx 24rpx 32rpx;
  254. .balance-text {
  255. font-size: 28rpx;
  256. color: #333;
  257. margin-bottom: 8rpx;
  258. .bean-amount {
  259. color: #223a7a;
  260. font-weight: 600;
  261. }
  262. }
  263. .insufficient-tip {
  264. font-size: 24rpx;
  265. color: #e94f7a;
  266. background: rgba(233, 79, 122, 0.1);
  267. padding: 8rpx 16rpx;
  268. border-radius: 8rpx;
  269. text-align: center;
  270. }
  271. }
  272. .gift-grid {
  273. flex: 1;
  274. display: flex;
  275. flex-wrap: wrap;
  276. gap: 20rpx;
  277. padding: 0 32rpx;
  278. max-height: 400rpx;
  279. overflow-y: auto;
  280. }
  281. .gift-item {
  282. width: calc(25% - 18rpx);
  283. background: #fff;
  284. border-radius: 12rpx;
  285. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
  286. display: flex;
  287. flex-direction: column;
  288. align-items: center;
  289. padding: 20rpx 4rpx;
  290. border: 2rpx solid transparent;
  291. transition: all 0.2s;
  292. box-sizing: border-box;
  293. &.selected {
  294. border-color: #223a7a;
  295. background: #f8f9ff;
  296. }
  297. .gift-img {
  298. width: 70rpx;
  299. height: 70rpx;
  300. border-radius: 8rpx;
  301. overflow: hidden;
  302. margin-bottom: 8rpx;
  303. image {
  304. width: 100%;
  305. height: 100%;
  306. }
  307. }
  308. .gift-name {
  309. font-size: 22rpx;
  310. color: #333;
  311. text-align: center;
  312. margin-bottom: 4rpx;
  313. line-height: 1.2;
  314. }
  315. .gift-price {
  316. font-size: 20rpx;
  317. color: #223a7a;
  318. text-align: center;
  319. }
  320. }
  321. .popup-bottom {
  322. display: flex;
  323. justify-content: space-between;
  324. align-items: center;
  325. padding: 24rpx 32rpx;
  326. border-top: 1rpx solid #f5f5f5;
  327. background: #fff;
  328. width: 100%;
  329. box-sizing: border-box;
  330. .total-info {
  331. display: flex;
  332. align-items: center;
  333. gap: 8rpx;
  334. .total-text {
  335. font-size: 28rpx;
  336. color: #666;
  337. }
  338. .total-price {
  339. font-size: 32rpx;
  340. color: #223a7a;
  341. font-weight: bold;
  342. }
  343. }
  344. .gift-btn {
  345. background: #223a7a;
  346. color: #fff;
  347. font-size: 28rpx;
  348. border-radius: 32rpx;
  349. padding: 0 48rpx;
  350. height: 68rpx;
  351. line-height: 68rpx;
  352. border: none;
  353. min-width: 140rpx;
  354. margin-left: auto;
  355. margin-right: 20rpx;
  356. &[disabled] {
  357. background: #ccc;
  358. color: #999;
  359. }
  360. }
  361. }
  362. </style>