小说小程序前端代码仓库(小程序)
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.

433 lines
9.0 KiB

  1. <template>
  2. <view class="page-container">
  3. <navbar title="任务中心" leftClick @leftClick="$utils.navigateBack" />
  4. <view class="task-center">
  5. <!-- 账户剩余 -->
  6. <view class="account-balance">
  7. <view class="balance-label">账户剩余</view>
  8. <view class="balance-value"><text class="num">{{ maxVote }}</text> <text class="unit"> 推荐票</text>
  9. </view>
  10. </view>
  11. <!-- 打卡得奖励 -->
  12. <view class="checkin-section">
  13. <view class="section-header">
  14. <text>打卡得奖励</text>
  15. <view class="record-btn" @click="$refs.signRecordPopup.open()">打卡记录</view>
  16. </view>
  17. <view class="checkin-grid">
  18. <view v-for="day in clockList" :key="day" class="checkin-day"
  19. :class="{ active: day.commonSignLog }">
  20. <view class="day-label" :class="{ bold: day.commonSignLog }">
  21. {{ day.title }}
  22. </view>
  23. <image class="ticket-img" :src="day.image" mode="aspectFit" />
  24. <view class="ticket-num">+ {{ day.num }}</view>
  25. </view>
  26. </view>
  27. <button class="checkin-btn" :class="{ 'checked-btn': isSign }" :disabled="isSign"
  28. @click="clickSignTask">
  29. {{ isSign ? '已签到' : '签到得奖励' }}
  30. </button>
  31. </view>
  32. <!-- 更多任务 -->
  33. <view class="more-tasks">
  34. <view class="more-header">更多任务</view>
  35. <view class="task-list">
  36. <view class="task-item" v-for="(task, idx) in list" :key="idx"
  37. :class="{ 'no-border': idx === tasks.length - 1 }">
  38. <view class="task-info">
  39. <view class="task-title">{{ task.title }}</view>
  40. <view class="task-desc">推荐票 +{{ task.num }}</view>
  41. </view>
  42. <button class="get-btn" :class="{ 'received-btn': task.commonTaskLog }"
  43. :disabled="task.commonTaskLog" @click="clickMoreTask(task.id)">
  44. {{ task.commonTaskLog ? '已领取' : '去领取' }}
  45. </button>
  46. </view>
  47. </view>
  48. </view>
  49. </view>
  50. <!-- 签到记录弹窗组件 -->
  51. <signRecordPopup ref="signRecordPopup" />
  52. </view>
  53. </template>
  54. <script>
  55. import mixinsList from '@/mixins/list.js'
  56. import signRecordPopup from '../components/novel/signRecordPopup.vue'
  57. export default {
  58. mixins: [mixinsList],
  59. components: {
  60. signRecordPopup,
  61. },
  62. data() {
  63. return {
  64. checkedDays: 3, // 已签到天数
  65. tasks: [{
  66. title: '观看视频广告',
  67. received: false
  68. },
  69. {
  70. title: '每日首阅三个章节',
  71. received: false
  72. },
  73. {
  74. title: '每日首条评论',
  75. received: false
  76. },
  77. ],
  78. clockList: [],
  79. isChecked: false, // 新增:签到按钮状态
  80. maxVote: 0,
  81. mixinsListApi: '',
  82. canSignToday: false, // 今日是否可以签到
  83. }
  84. },
  85. computed: {
  86. isSign() {
  87. // 如果今日不可签到,则表示已签到
  88. return !this.canSignToday;
  89. },
  90. },
  91. onShow() {
  92. this.getMyRecommendTicketNum()
  93. this.getSignTaskList()
  94. this.getMoreTaskList()
  95. this.checkTodaySignStatus()
  96. },
  97. methods: {
  98. getMyRecommendTicketNum() {
  99. this.$fetch('getMyRecommendTicketNum')
  100. .then(res => {
  101. this.maxVote = res
  102. })
  103. },
  104. getSignTaskList() {
  105. this.$fetch('getSignTaskList', {
  106. token: uni.getStorageSync('token')
  107. })
  108. .then(res => {
  109. this.clockList = res
  110. })
  111. },
  112. getMoreTaskList() {
  113. this.$fetch('getMoreTaskList', {
  114. token: uni.getStorageSync('token')
  115. })
  116. .then(res => {
  117. this.list = res
  118. })
  119. },
  120. async clickSignTask() {
  121. if (!this.canSignToday) {
  122. uni.showToast({
  123. title: '今日已签到',
  124. icon: 'none'
  125. })
  126. return
  127. }
  128. let taskId = 0
  129. for (var index = 0; index < this.clockList.length; index++) {
  130. var element = this.clockList[index];
  131. if (!element.commonSignLog) {
  132. taskId = element.id
  133. break
  134. }
  135. }
  136. if (!taskId) {
  137. uni.showToast({
  138. title: '已全部签到',
  139. icon: 'none'
  140. })
  141. return
  142. }
  143. await this.$fetch('clickSignTask', {
  144. taskId,
  145. })
  146. uni.showToast({
  147. title: '签到成功',
  148. icon: 'none'
  149. });
  150. // 签到成功后更新状态
  151. this.canSignToday = false;
  152. this.getSignTaskList()
  153. this.getMyRecommendTicketNum()
  154. },
  155. async clickMoreTask(taskId) {
  156. await this.$fetch('clickMoreTask', {
  157. taskId,
  158. })
  159. uni.showToast({
  160. title: '领取成功',
  161. icon: 'none'
  162. });
  163. this.getMoreTaskList()
  164. this.getMyRecommendTicketNum()
  165. },
  166. handleCheckin() {
  167. if (this.checkedDays < 8) {
  168. this.checkedDays++;
  169. this.isChecked = true;
  170. uni.showToast({
  171. title: '签到成功',
  172. icon: 'none'
  173. });
  174. } else {
  175. this.isChecked = true;
  176. uni.showToast({
  177. title: '已全部签到',
  178. icon: 'none'
  179. });
  180. }
  181. },
  182. handleReceive(idx) {
  183. this.tasks[idx].received = true;
  184. uni.showToast({
  185. title: '领取成功',
  186. icon: 'success'
  187. });
  188. },
  189. checkTodaySignStatus() {
  190. this.$fetch('getSignTaskToday')
  191. .then(res => {
  192. // res.result == 0 表示可以签到,否则表示已签到
  193. this.canSignToday = res.result;
  194. })
  195. .catch(err => {
  196. console.error('获取今日签到状态失败:', err);
  197. // 默认设置为不可签到(已签到状态)
  198. this.canSignToday = false;
  199. });
  200. },
  201. },
  202. }
  203. </script>
  204. <style scoped lang="scss">
  205. .page-container {
  206. height: 100vh;
  207. overflow: hidden;
  208. }
  209. .task-center {
  210. background: #f8f8f8;
  211. height: 100vh;
  212. padding-bottom: 40rpx;
  213. overflow: hidden;
  214. box-sizing: border-box;
  215. }
  216. .navbar-placeholder {
  217. height: 100rpx;
  218. }
  219. .account-balance {
  220. background: linear-gradient(90deg, #ffe16b, #ffd700);
  221. border-radius: 20rpx;
  222. margin: 24rpx 24rpx 0 24rpx;
  223. padding: 24rpx 32rpx;
  224. display: flex;
  225. align-items: center;
  226. justify-content: space-between;
  227. font-size: 30rpx;
  228. color: #333;
  229. .balance-label {
  230. font-weight: 500;
  231. }
  232. .balance-value {
  233. font-weight: bold;
  234. .num {
  235. font-size: 24rpx;
  236. }
  237. .unit {
  238. font-size: 22rpx;
  239. }
  240. }
  241. }
  242. .checkin-section {
  243. background: #fff;
  244. border-radius: 20rpx;
  245. margin: 24rpx;
  246. padding: 32rpx 24rpx 24rpx 24rpx;
  247. box-shadow: 0 2rpx 8rpx rgba(255, 215, 0, 0.08);
  248. border: 4rpx solid #ffe16b;
  249. .section-header {
  250. display: flex;
  251. justify-content: space-between;
  252. align-items: center;
  253. font-size: 28rpx;
  254. font-weight: 600;
  255. margin-bottom: 24rpx;
  256. .record-btn {
  257. background: #d6ff4b;
  258. color: #383938;
  259. border-radius: 24rpx;
  260. padding: 6rpx 28rpx;
  261. font-size: 24rpx;
  262. }
  263. }
  264. .checkin-grid {
  265. display: flex;
  266. flex-wrap: wrap;
  267. gap: 18rpx;
  268. margin-bottom: 32rpx;
  269. .checkin-day {
  270. width: 22%;
  271. background: #f7f7f7;
  272. border-radius: 12rpx;
  273. display: flex;
  274. flex-direction: column;
  275. align-items: center;
  276. padding: 18rpx 0 10rpx 0;
  277. border: 2rpx solid #f0f0f0;
  278. &.active {
  279. background: #d6ff4b !important;
  280. border-color: #b6e900 !important;
  281. .day-label,
  282. .ticket-num {
  283. color: #222 !important;
  284. font-weight: bold;
  285. }
  286. }
  287. .day-label {
  288. font-size: 24rpx;
  289. font-weight: 600;
  290. height: 90rpx;
  291. margin-bottom: 8rpx;
  292. color: #333;
  293. &.bold {
  294. font-weight: bold;
  295. }
  296. }
  297. .ticket-img {
  298. width: 48rpx;
  299. height: 36rpx;
  300. margin-bottom: 6rpx;
  301. display: flex;
  302. }
  303. .ticket-num {
  304. font-size: 22rpx;
  305. color: #bfa100;
  306. }
  307. }
  308. }
  309. .checkin-btn {
  310. width: 600rpx;
  311. background: linear-gradient(90deg, #ffe16b, #ffd700);
  312. color: #333;
  313. font-size: 30rpx;
  314. border-radius: 46rpx;
  315. padding: 18rpx 0;
  316. font-weight: bold;
  317. margin-top: 8rpx;
  318. transition: background 0.2s;
  319. }
  320. }
  321. .checked-btn {
  322. background: linear-gradient(90deg, #e0e0e0, #bdbdbd) !important;
  323. color: #444 !important;
  324. cursor: not-allowed;
  325. }
  326. .more-tasks {
  327. background: #faffea;
  328. border-radius: 20rpx;
  329. margin: 0 24rpx;
  330. padding: 24rpx 24rpx 8rpx 24rpx;
  331. margin-top: 100rpx;
  332. .more-header {
  333. font-size: 28rpx;
  334. color: #bfa100;
  335. font-weight: 600;
  336. margin-bottom: 18rpx;
  337. }
  338. .task-list {
  339. .task-item {
  340. display: flex;
  341. align-items: center;
  342. justify-content: space-between;
  343. background: #fff;
  344. border-radius: 0;
  345. margin: 0;
  346. padding: 18rpx 20rpx 10rpx 20rpx;
  347. border-bottom: 1rpx solid #f2f2f2;
  348. .task-info {
  349. display: flex;
  350. flex-direction: column;
  351. align-items: flex-start;
  352. flex: 1;
  353. min-width: 0;
  354. margin-top: 30rpx;
  355. .task-title {
  356. font-size: 26rpx;
  357. color: #222;
  358. font-weight: bold;
  359. }
  360. .task-desc {
  361. font-size: 20rpx;
  362. color: #bbb;
  363. margin-top: 4rpx;
  364. }
  365. }
  366. .get-btn {
  367. background: #ffd700;
  368. color: #fff;
  369. font-size: 24rpx;
  370. border-radius: 24rpx;
  371. padding: 0 28rpx;
  372. font-weight: bold;
  373. border: none;
  374. height: 48rpx;
  375. display: flex;
  376. align-items: center;
  377. justify-content: center;
  378. box-shadow: none;
  379. white-space: nowrap;
  380. margin-left: 18rpx;
  381. transition: background 0.2s;
  382. }
  383. .received-btn {
  384. background: linear-gradient(90deg, #e0e0e0, #bdbdbd) !important;
  385. color: #444 !important;
  386. cursor: not-allowed;
  387. }
  388. &.no-border {
  389. border-bottom: none;
  390. }
  391. }
  392. }
  393. }
  394. </style>