index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. <template>
  2. <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
  3. <view class="custom_activities">
  4. <view class="info-card">
  5. <view class="content-title">
  6. <view v-t="'Home.page_customer.item6'"></view>
  7. </view>
  8. <!-- 主要内容 -->
  9. <view class="main-content">
  10. <scroll-view class="tab" scroll-y :scroll-top="scrollTop" @scrolltolower="loadMore"
  11. refresher-enabled :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
  12. <!-- 加载状态 -->
  13. <view v-if="pictLoading" class="loading-mask">
  14. <view class="loading-content">
  15. <uni-icons type="spinner-cycle" size="30" color="#007aff" class="spin"></uni-icons>
  16. <text class="loading-text">{{ t('common.loading') }}</text>
  17. </view>
  18. </view>
  19. <!-- 遍历静态活动配置 -->
  20. <ActivityCard v-for="activity in visibleStaticActivities" :key="activity.id" :config="activity"
  21. :state="activityState" :lang="lang" @action="handleActivityAction" />
  22. <!-- 动态活动列表 -->
  23. <ActivityCard v-for="item in tableData" :key="'dynamic-' + item.id"
  24. :config="createDynamicConfig(item)" :state="activityState" :lang="lang"
  25. @action="handleActivityAction" />
  26. <!-- 赠送活动列表 -->
  27. <ActivityCard v-for="item in tableDataGive" :key="'give-' + item.id"
  28. :config="createGiveConfig(item)" :state="activityState" :lang="lang"
  29. @action="handleActivityAction" />
  30. <!-- 加载更多状态 -->
  31. <view v-if="loadingMore" class="loading-more">
  32. <uni-icons type="spinner-cycle" size="20" color="#999" class="spin"></uni-icons>
  33. <text>{{ t('common.loadingMore') }}</text>
  34. </view>
  35. <!-- 没有更多数据 -->
  36. <view v-if="!hasMore && tableData.length > 0" class="no-more">
  37. <text>{{ t('common.noMore') }}</text>
  38. </view>
  39. </scroll-view>
  40. </view>
  41. <!-- 分页 -->
  42. <!-- <view class="crm_pagination" v-if="pagerInfo.rowTotal">
  43. <view class="pagination">
  44. <view class="page-item prev" :class="{ disabled: pagerInfo.current === 1 }"
  45. @click="handlePrevPage">
  46. <uni-icons type="arrowleft" size="16" color="#666"></uni-icons>
  47. <text>{{ t('common.prev') }}</text>
  48. </view>
  49. <view class="page-numbers">
  50. <view v-for="page in visiblePages" :key="page" class="page-number"
  51. :class="{ active: pagerInfo.current === page }" @click="handlePageChange(page)">
  52. {{ page }}
  53. </view>
  54. </view>
  55. <view class="page-item next" :class="{ disabled: pagerInfo.current === pagerInfo.pageTotal }"
  56. @click="handleNextPage">
  57. <text>{{ t('common.next') }}</text>
  58. <uni-icons type="arrowright" size="16" color="#666"></uni-icons>
  59. </view>
  60. </view>
  61. </view> -->
  62. <!-- 所有弹窗组件 -->
  63. <ActivityDialogs :visible="dialogState" :form-data="formData" :login-options="loginOptions"
  64. :surplus-options="surplusOptions" @update:visible="updateDialogVisible"
  65. @action="handleDialogAction" />
  66. </view>
  67. </view>
  68. </cwg-page-wrapper>
  69. </template>
  70. <script setup lang="ts">
  71. import { ref, computed, onMounted } from 'vue'
  72. import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
  73. import { useI18n } from 'vue-i18n'
  74. import ActivityCard from './components/ActivityCard.vue'
  75. import ActivityDialogs from './components/ActivityDialogs.vue'
  76. import { useActivityData } from './composables/useActivityData'
  77. import { useActivityActions } from './composables/useActivityActions'
  78. import { staticActivityConfigs } from './config/activityConfigs'
  79. import Config from '@/config/index'
  80. const { t } = useI18n()
  81. const { Host80 } = Config
  82. // ==================== 数据逻辑 ====================
  83. const {
  84. pictLoading,
  85. tableData,
  86. tableDataGive,
  87. pagerInfo,
  88. loginOptions,
  89. activityState,
  90. lang,
  91. country,
  92. isSupportedCountry,
  93. isGuoQin,
  94. overdue,
  95. timeExpireJx,
  96. isAfterSeptember30,
  97. refreshAllActivities,
  98. handlePageChange
  99. } = useActivityData()
  100. // ==================== 操作逻辑 ====================
  101. const {
  102. // 弹窗状态
  103. dialogChinaUnionPay,
  104. dialogChinaUnionPay1,
  105. dialogChinaUnionPayJX,
  106. dialogDealResult,
  107. dialogDealResultJx,
  108. dialogDealResultJxVip,
  109. dialogDealResultCpt,
  110. dialogDealResultJxJYB,
  111. dialogDealResultNoWorries,
  112. dialogNoWorries,
  113. dialogNoWorriesApply,
  114. dialogNewTask,
  115. dialogNewTaskDraw,
  116. dialogSurplusActivity,
  117. dialogSurplusActivity1,
  118. openCalculatorFlag,
  119. // 表单数据
  120. dialogDealResult_form,
  121. dialogDealResultJx_form,
  122. dialogDealResultJx_formVip,
  123. dialogDealResultJx_formJYB,
  124. dialogDealResultCpt_form,
  125. dialogDeal_formNoWorries,
  126. selectedAccount,
  127. valueInfo,
  128. surplusActivityOptions,
  129. surplusActivityLoading,
  130. selectedSurplusActivity,
  131. // 页面跳转
  132. toSingle,
  133. toDeposit,
  134. toActivity24nianzhong,
  135. toActivity24Trading,
  136. toHistoryLuckyDraw,
  137. toTaskList,
  138. goSurplusTaskList,
  139. goMonthlyTaskList,
  140. toDocumentary,
  141. backActivity,
  142. // PDF打开
  143. openPdf,
  144. // 活动操作
  145. toApply23Open,
  146. toApply23,
  147. toApply23Cancel,
  148. toTransform,
  149. toTransformActive,
  150. toRealization,
  151. toRealizationActive,
  152. toApply23Jx,
  153. toApply23JxCancel,
  154. toApply23JxVip,
  155. toApply23JxCancelVip,
  156. toRealizationJx,
  157. toRealizationJxVip,
  158. toApplyCpt,
  159. toApplyCptCancel,
  160. toApply24JYBVip,
  161. toApply24JYBCancelVip,
  162. toRealization24JYBVip,
  163. toApplyNoWorries,
  164. toApplyNoWorriesCancel,
  165. realizationNoWorries,
  166. getSurplusActivityOptions,
  167. confirmSurplusActivity,
  168. confirmSurplusActivity1,
  169. closedialogNewTaskDraw,
  170. toOpenSingle,
  171. cashBack,
  172. calculateIncome
  173. } = useActivityActions(activityState, loginOptions, ref(''), refreshAllActivities)
  174. // ==================== 分页相关 ====================
  175. const refreshing = ref(false)
  176. const loadingMore = ref(false)
  177. const scrollTop = ref(0)
  178. const hasMore = ref(true)
  179. // 计算显示的页码
  180. const visiblePages = computed(() => {
  181. const maxVisible = 5
  182. const half = Math.floor(maxVisible / 2)
  183. let start = Math.max(1, pagerInfo.value.current - half)
  184. let end = Math.min(pagerInfo.value.pageTotal, start + maxVisible - 1)
  185. if (end - start + 1 < maxVisible) {
  186. start = Math.max(1, end - maxVisible + 1)
  187. }
  188. return Array.from({ length: end - start + 1 }, (_, i) => start + i)
  189. })
  190. // 上一页
  191. const handlePrevPage = () => {
  192. if (pagerInfo.value.current > 1) {
  193. handlePageChange(pagerInfo.value.current - 1)
  194. }
  195. }
  196. // 下一页
  197. const handleNextPage = () => {
  198. if (pagerInfo.value.current < pagerInfo.value.pageTotal) {
  199. handlePageChange(pagerInfo.value.current + 1)
  200. }
  201. }
  202. // 加载更多
  203. const loadMore = () => {
  204. if (hasMore.value && !loadingMore.value && pagerInfo.value.current < pagerInfo.value.pageTotal) {
  205. loadingMore.value = true
  206. handlePageChange(pagerInfo.value.current + 1)
  207. setTimeout(() => {
  208. loadingMore.value = false
  209. }, 500)
  210. }
  211. }
  212. // 刷新
  213. const onRefresh = () => {
  214. refreshing.value = true
  215. refreshAllActivities()
  216. setTimeout(() => {
  217. refreshing.value = false
  218. uni.stopPullDownRefresh()
  219. }, 1000)
  220. }
  221. // ==================== 可见活动计算 ====================
  222. const visibleStaticActivities = computed(() => {
  223. return staticActivityConfigs.filter(config => {
  224. if (!config.showCondition) return true
  225. switch (config.showCondition) {
  226. case 'tableDataNewYear24Flag':
  227. return activityState.tableDataNewYear24Flag
  228. case "country === 'CN'":
  229. return country.value === 'CN'
  230. case 'choujiaClose':
  231. return activityState.choujiaClose
  232. case 'tableDataNoWorriesFlag':
  233. return activityState.tableDataNoWorriesFlag
  234. case 'standard':
  235. return activityState.standard
  236. case 'tableData2Flag':
  237. return activityState.tableData2Flag
  238. case 'tableDataCptFlag':
  239. return activityState.tableDataCptFlag
  240. case 'tableData3FlagJYB':
  241. return activityState.tableData3FlagJYB
  242. case '!isAfterSeptember30() && !isSupportedCountry':
  243. return !isAfterSeptember30() && !isSupportedCountry.value
  244. case '!isAfterSeptember30() && isSupportedCountry':
  245. return !isAfterSeptember30() && isSupportedCountry.value
  246. default:
  247. return true
  248. }
  249. })
  250. })
  251. // ==================== 创建动态活动配置 ====================
  252. const createDynamicConfig = (item: any) => {
  253. return {
  254. id: item.id,
  255. title: item.title,
  256. description: item.subTitle,
  257. image: Host80 + item.coverImage,
  258. time: item.deliveryTime?.split(' ')[0],
  259. hot: item.hot,
  260. onClick: 'toSingle',
  261. onClickParams: [item.id],
  262. buttons: [
  263. {
  264. type: item.startTime && item.endTime && overdue(item.startTime, item.endTime) ? 'red' : 'gray',
  265. text: 'Custom.Activity.Apply',
  266. action: 'applications',
  267. params: [item]
  268. },
  269. {
  270. type: 'check',
  271. text: 'Custom.Activity.List',
  272. action: 'checkActivity',
  273. params: [item]
  274. },
  275. {
  276. type: 'check',
  277. text: 'Custom.Activity.Single',
  278. action: 'toSingle',
  279. params: [item.id]
  280. }
  281. ]
  282. }
  283. }
  284. const createGiveConfig = (item: any) => {
  285. return {
  286. id: item.id,
  287. title: item.title,
  288. description: item.subTitle,
  289. image: Host80 + item.coverUrl,
  290. time: item.revokeDate?.split(' ')[0],
  291. hot: item.hot,
  292. onClick: 'toSingle',
  293. onClickParams: ['newList', item.id],
  294. buttons: [
  295. {
  296. type: item.valid === 1 ? 'red' : 'gray',
  297. text: 'Custom.Activity.Apply',
  298. action: 'toActivity24nianzhong',
  299. condition: item.valid === 1
  300. },
  301. {
  302. type: 'check',
  303. text: 'Custom.Activity.Single',
  304. action: 'toSingle',
  305. params: ['newList', item.id]
  306. }
  307. ]
  308. }
  309. }
  310. // ==================== 弹窗状态聚合 ====================
  311. const dialogState = {
  312. dialogChinaUnionPay,
  313. dialogChinaUnionPay1,
  314. dialogChinaUnionPayJX,
  315. dialogDealResult,
  316. dialogDealResultJx,
  317. dialogDealResultJxVip,
  318. dialogDealResultCpt,
  319. dialogDealResultJxJYB,
  320. dialogDealResultNoWorries,
  321. dialogNoWorries,
  322. dialogNoWorriesApply,
  323. dialogNewTask,
  324. dialogNewTaskDraw,
  325. dialogSurplusActivity,
  326. dialogSurplusActivity1,
  327. openCalculatorFlag
  328. }
  329. const formData = {
  330. dialogDealResult_form,
  331. dialogDealResultJx_form,
  332. dialogDealResultJx_formVip,
  333. dialogDealResultJx_formJYB,
  334. dialogDealResultCpt_form,
  335. dialogDeal_formNoWorries,
  336. selectedAccount,
  337. valueInfo,
  338. selectedSurplusActivity
  339. }
  340. const surplusOptions = {
  341. options: surplusActivityOptions,
  342. loading: surplusActivityLoading
  343. }
  344. // 更新弹窗可见性
  345. const updateDialogVisible = (key: string, value: boolean) => {
  346. const dialogMap: Record<string, any> = {
  347. dialogChinaUnionPay,
  348. dialogChinaUnionPay1,
  349. dialogChinaUnionPayJX,
  350. dialogDealResult,
  351. dialogDealResultJx,
  352. dialogDealResultJxVip,
  353. dialogDealResultCpt,
  354. dialogDealResultJxJYB,
  355. dialogDealResultNoWorries,
  356. dialogNoWorries,
  357. dialogNoWorriesApply,
  358. dialogNewTask,
  359. dialogNewTaskDraw,
  360. dialogSurplusActivity,
  361. dialogSurplusActivity1,
  362. openCalculatorFlag
  363. }
  364. if (dialogMap[key]) {
  365. dialogMap[key].value = value
  366. }
  367. }
  368. // 处理弹窗操作
  369. const handleDialogAction = (action: string, data?: any) => {
  370. switch (action) {
  371. case 'toApply23':
  372. toApply23()
  373. break
  374. case 'toApply23Cancel':
  375. toApply23Cancel()
  376. break
  377. case 'toTransformActive':
  378. toTransformActive()
  379. break
  380. // ... 其他操作
  381. }
  382. }
  383. // ==================== 活动操作处理 ====================
  384. const handleActivityAction = ({ type, params }: { type: string; params?: any[] }) => {
  385. switch (type) {
  386. case 'toSingle':
  387. toSingle(...(params || []))
  388. break
  389. case 'openCalculator':
  390. openCalculatorFlag.value = true
  391. break
  392. case 'openPdf':
  393. openPdf(params?.[0] || type)
  394. break
  395. case 'openSurplusActivityDialog':
  396. dialogSurplusActivity.value = true
  397. break
  398. case 'openSurplusActivityDialog1':
  399. dialogSurplusActivity1.value = true
  400. break
  401. case 'goMonthlyTaskList':
  402. goMonthlyTaskList()
  403. break
  404. case 'goSurplusTaskList':
  405. goSurplusTaskList()
  406. break
  407. case 'toHistoryLuckyDraw':
  408. toHistoryLuckyDraw()
  409. break
  410. case 'toOpenTask':
  411. dialogNewTaskDraw.value = true
  412. break
  413. case 'toNewTask':
  414. dialogNewTask.value = true
  415. break
  416. case 'toTaskList':
  417. toTaskList()
  418. break
  419. case 'toApplyNoWorriesOpen':
  420. dialogDealResultNoWorries.value = true
  421. break
  422. case 'toRealizationNoWorries':
  423. dialogNoWorries.value = true
  424. break
  425. case 'toActivity24Trading':
  426. toActivity24Trading()
  427. break
  428. case 'toActivity24nianzhong':
  429. toActivity24nianzhong()
  430. break
  431. case 'toApply23Open':
  432. toApply23Open()
  433. break
  434. case 'toTransform':
  435. toTransform()
  436. break
  437. case 'toRealization':
  438. toRealization()
  439. break
  440. case 'toApplyCptOpen':
  441. dialogDealResultCpt.value = true
  442. break
  443. case 'toOpenSingle':
  444. // 处理打开外部链接
  445. break
  446. case 'toApply24JYBOpenVip':
  447. dialogDealResultJxJYB.value = true
  448. break
  449. case 'toRealization24JYBVip':
  450. // 处理变现
  451. break
  452. case 'applications':
  453. // 处理申请
  454. break
  455. case 'checkActivity':
  456. // 处理查看活动
  457. break
  458. case 'openDialog':
  459. if (params?.[0]) {
  460. updateDialogVisible(params[0], true)
  461. }
  462. break
  463. case 'cashBack':
  464. // 处理返现
  465. break
  466. case 'toApply23JxOpen':
  467. dialogDealResultJx.value = true
  468. break
  469. case 'toApply23JxOpenVip':
  470. dialogDealResultJxVip.value = true
  471. break
  472. case 'toApplyCptOpen':
  473. dialogDealResultCpt.value = true
  474. break
  475. case 'toApply24JYBOpenVip':
  476. dialogDealResultJxJYB.value = true
  477. break
  478. case 'toApplyNoWorriesOpen':
  479. dialogDealResultNoWorries.value = true
  480. break
  481. case 'toRealizationJx':
  482. toRealizationJx()
  483. break
  484. case 'toRealizationJxVip':
  485. toRealizationJxVip()
  486. break
  487. case 'toRealization24JYBVip':
  488. toRealization24JYBVip()
  489. break
  490. case 'toRealizationNoWorries':
  491. dialogNoWorries.value = true
  492. break
  493. case 'toOpenSingle':
  494. toOpenSingle(params?.[0])
  495. break
  496. case 'cashBack':
  497. cashBack()
  498. break
  499. default:
  500. console.warn('未知操作类型:', type)
  501. }
  502. }
  503. // ==================== 生命周期 ====================
  504. onLoad(() => {
  505. refreshAllActivities()
  506. })
  507. onPullDownRefresh(() => {
  508. refreshAllActivities()
  509. setTimeout(() => {
  510. uni.stopPullDownRefresh()
  511. }, 1000)
  512. })
  513. onReachBottom(() => {
  514. loadMore()
  515. })
  516. </script>
  517. <style scoped lang="scss">
  518. .custom_activities {
  519. height: 100vh;
  520. display: flex;
  521. flex-direction: column;
  522. background-color: #f5f5f5;
  523. .crm-title-box {
  524. padding: 20rpx 30rpx;
  525. background-color: #fff;
  526. border-bottom: 1rpx solid #e5e5e5;
  527. .tit {
  528. font-size: 32rpx;
  529. font-weight: bold;
  530. color: #333;
  531. }
  532. }
  533. .main-content {
  534. flex: 1;
  535. overflow: hidden;
  536. .tab {
  537. height: 100%;
  538. padding: 20rpx;
  539. }
  540. }
  541. .loading-mask {
  542. position: absolute;
  543. top: 0;
  544. left: 0;
  545. right: 0;
  546. bottom: 0;
  547. display: flex;
  548. align-items: center;
  549. justify-content: center;
  550. background-color: rgba(255, 255, 255, 0.9);
  551. z-index: 100;
  552. .loading-content {
  553. display: flex;
  554. flex-direction: column;
  555. align-items: center;
  556. padding: 30rpx;
  557. background-color: #fff;
  558. border-radius: 16rpx;
  559. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
  560. .loading-text {
  561. margin-top: 20rpx;
  562. font-size: 28rpx;
  563. color: #666;
  564. }
  565. }
  566. }
  567. .loading-more,
  568. .no-more {
  569. display: flex;
  570. align-items: center;
  571. justify-content: center;
  572. padding: 30rpx;
  573. font-size: 28rpx;
  574. color: #999;
  575. }
  576. .spin {
  577. animation: spin 1s linear infinite;
  578. }
  579. @keyframes spin {
  580. from {
  581. transform: rotate(0deg);
  582. }
  583. to {
  584. transform: rotate(360deg);
  585. }
  586. }
  587. .crm_pagination {
  588. padding: 20rpx;
  589. background-color: #fff;
  590. border-top: 1rpx solid #e5e5e5;
  591. .pagination {
  592. display: flex;
  593. align-items: center;
  594. justify-content: center;
  595. .page-item {
  596. display: flex;
  597. align-items: center;
  598. padding: 0 20rpx;
  599. height: 60rpx;
  600. background-color: #fff;
  601. border: 1rpx solid #dcdfe6;
  602. border-radius: 8rpx;
  603. font-size: 28rpx;
  604. color: #606266;
  605. cursor: pointer;
  606. margin: 0 10rpx;
  607. &.disabled {
  608. opacity: 0.5;
  609. cursor: not-allowed;
  610. pointer-events: none;
  611. }
  612. }
  613. .page-numbers {
  614. display: flex;
  615. gap: 10rpx;
  616. .page-number {
  617. display: flex;
  618. align-items: center;
  619. justify-content: center;
  620. min-width: 60rpx;
  621. height: 60rpx;
  622. padding: 0 8rpx;
  623. background-color: #fff;
  624. border: 1rpx solid #dcdfe6;
  625. border-radius: 8rpx;
  626. font-size: 28rpx;
  627. color: #606266;
  628. cursor: pointer;
  629. &.active {
  630. background-color: #007aff;
  631. border-color: #007aff;
  632. color: #fff;
  633. }
  634. }
  635. }
  636. }
  637. }
  638. }
  639. </style>