pool.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. <template>
  2. <view class="page-container">
  3. <!-- 顶部标题卡片 -->
  4. <view class="page-title-card">
  5. <text class="page-title-text">量化选股大师</text>
  6. </view>
  7. <scroll-view class="scroll-view" scroll-y>
  8. <view class="content-wrapper">
  9. <!-- 性能指标卡片 -->
  10. <view class="card performance-card">
  11. <view class="performance-item">
  12. <text class="performance-label">历史成功率</text>
  13. <text class="performance-value success">75%</text>
  14. </view>
  15. <view class="performance-item">
  16. <text class="performance-label">平均收益率</text>
  17. <text class="performance-value profit">+3.2%</text>
  18. </view>
  19. <view class="performance-item">
  20. <text class="performance-label">总交易次数</text>
  21. <text class="performance-value">120</text>
  22. </view>
  23. </view>
  24. <!-- 超短精选池区域 -->
  25. <view class="card pool-card">
  26. <view class="pool-header">
  27. <text class="pool-icon">⚡</text>
  28. <text class="pool-title">超短精选池</text>
  29. </view>
  30. <text class="pool-desc">今日更新,高频捕捉短期爆发机会</text>
  31. <!-- 未购买时显示锁定状态 -->
  32. <view v-if="!isPurchased" class="locked-content">
  33. <view class="lock-icon-wrapper">
  34. <text class="lock-icon">🔒</text>
  35. </view>
  36. <text class="lock-text">权限锁定: 查看 超短精选池 需付费</text>
  37. <text class="lock-desc">超短池为每日实时更新,捕捉短期爆发机会。</text>
  38. <view class="unlock-button" @click="showPurchaseModal">
  39. <text class="unlock-button-text">立即解锁 日 精选 (¥18/日)</text>
  40. </view>
  41. </view>
  42. <!-- 已购买时显示内容 -->
  43. <view v-else class="unlocked-content">
  44. <text class="unlocked-tip">您已解锁,可以查看超短精选池内容</text>
  45. <!-- 这里可以显示实际的股票池内容 -->
  46. <view class="stock-list-placeholder">
  47. <text class="placeholder-text">股票池内容将在此显示</text>
  48. </view>
  49. </view>
  50. </view>
  51. <!-- 历史股票池回顾 -->
  52. <view class="card history-card">
  53. <view class="history-header">
  54. <text class="history-icon">📅</text>
  55. <text class="history-title">历史股票池回顾</text>
  56. </view>
  57. <view class="history-search-row">
  58. <input
  59. class="history-date-input"
  60. type="text"
  61. placeholder="2025年11月20日"
  62. v-model="selectedDate"
  63. disabled
  64. />
  65. <view class="history-search-button" @click="onHistorySearch">
  66. <text class="search-icon">🔍</text>
  67. </view>
  68. </view>
  69. <text class="history-tip">请选择一个历史日期,查询当日入池股及次日表现。</text>
  70. </view>
  71. <!-- 预留底部空间 -->
  72. <view class="bottom-safe-area"></view>
  73. </view>
  74. </scroll-view>
  75. <!-- 手机号授权弹窗 -->
  76. <view v-if="showPhoneAuth" class="phone-auth-mask" @click="closePhoneAuth">
  77. <view class="phone-auth-prompt" @click.stop>
  78. <view class="auth-icon">📱</view>
  79. <text class="auth-title">授权手机号</text>
  80. <text class="auth-desc">为了完成登录,需要获取您的手机号</text>
  81. <button
  82. class="auth-button"
  83. open-type="getPhoneNumber"
  84. @getphonenumber="onGetPhoneNumber"
  85. >
  86. 授权手机号
  87. </button>
  88. </view>
  89. </view>
  90. <!-- 购买弹窗 -->
  91. <view v-if="showModal" class="modal-overlay" @click="closePurchaseModal">
  92. <view class="modal-content" @click.stop>
  93. <view class="modal-header">
  94. <text class="modal-title">超短池(日/周)订阅服务</text>
  95. <text class="modal-close" @click="closePurchaseModal">×</text>
  96. </view>
  97. <view class="subscription-options">
  98. <view
  99. class="subscription-option"
  100. :class="{ active: selectedPlan === 'daily' }"
  101. @click="selectedPlan = 'daily'"
  102. >
  103. <view class="option-info">
  104. <text class="option-title">日订阅</text>
  105. <text class="option-desc">当日有效</text>
  106. </view>
  107. <text class="option-price">¥18</text>
  108. </view>
  109. <view
  110. class="subscription-option"
  111. :class="{ active: selectedPlan === 'weekly' }"
  112. @click="selectedPlan = 'weekly'"
  113. >
  114. <view class="option-info">
  115. <text class="option-title">周套餐</text>
  116. <text class="option-desc">7天连续查看</text>
  117. </view>
  118. <text class="option-price">¥98</text>
  119. </view>
  120. </view>
  121. <view class="modal-footer">
  122. <view class="pay-button" @click="handlePurchase">
  123. <text class="pay-button-text">确认支付并解锁</text>
  124. </view>
  125. <text class="agreement-text">点击即表示同意《用户订阅协议》</text>
  126. </view>
  127. </view>
  128. </view>
  129. </view>
  130. </template>
  131. <script setup>
  132. import { ref } from 'vue'
  133. import { onLoad, onShow } from '@dcloudio/uni-app'
  134. import { isLoggedIn as checkLoginStatus, wxAuthLogin } from '../../utils/auth.js'
  135. const isPurchased = ref(false)
  136. const showModal = ref(false)
  137. const showPhoneAuth = ref(false) // 是否显示手机号授权按钮
  138. const selectedPlan = ref('daily')
  139. const selectedDate = ref('2025年11月20日')
  140. const isLoggedIn = ref(false)
  141. // 检查登录状态
  142. const checkLogin = () => {
  143. isLoggedIn.value = checkLoginStatus()
  144. console.log('[超短池] 登录状态:', isLoggedIn.value)
  145. return isLoggedIn.value
  146. }
  147. // 检查购买状态
  148. const checkPurchaseStatus = () => {
  149. try {
  150. const purchaseInfo = uni.getStorageSync('pool_purchase')
  151. if (purchaseInfo) {
  152. const now = Date.now()
  153. const expireTime = purchaseInfo.expireTime
  154. if (now < expireTime) {
  155. isPurchased.value = true
  156. } else {
  157. // 已过期,清除购买信息
  158. uni.removeStorageSync('pool_purchase')
  159. isPurchased.value = false
  160. }
  161. } else {
  162. isPurchased.value = false
  163. }
  164. } catch (e) {
  165. console.error('检查购买状态失败:', e)
  166. isPurchased.value = false
  167. }
  168. }
  169. // 显示购买弹窗(需要登录)
  170. const showPurchaseModal = () => {
  171. console.log('点击立即解锁')
  172. // 检查登录状态
  173. if (!checkLogin()) {
  174. console.log('未登录,显示手机号授权弹窗')
  175. showPhoneAuth.value = true
  176. return
  177. }
  178. console.log('已登录,显示购买弹窗')
  179. showModal.value = true
  180. }
  181. // 处理手机号授权并完成登录
  182. const onGetPhoneNumber = async (e) => {
  183. console.log('[超短池] 获取手机号回调:', e.detail)
  184. if (e.detail.errMsg === 'getPhoneNumber:ok') {
  185. const phoneCode = e.detail.code
  186. console.log('[超短池] phoneCode:', phoneCode)
  187. uni.showLoading({
  188. title: '登录中...',
  189. mask: true
  190. })
  191. try {
  192. const loginRes = await uni.login()
  193. console.log('[超短池] uni.login完整响应:', loginRes)
  194. console.log('[超短池] 微信登录code:', loginRes.code)
  195. if (!loginRes.code) {
  196. throw new Error('获取微信登录code失败')
  197. }
  198. const result = await wxAuthLogin(loginRes.code, phoneCode)
  199. uni.hideLoading()
  200. if (result) {
  201. showPhoneAuth.value = false
  202. checkLogin()
  203. // 登录成功后显示购买弹窗
  204. showModal.value = true
  205. }
  206. } catch (error) {
  207. uni.hideLoading()
  208. console.error('[超短池] 登录失败:', error)
  209. }
  210. } else {
  211. showPhoneAuth.value = false
  212. uni.showToast({
  213. title: '需要授权手机号才能完成登录',
  214. icon: 'none',
  215. duration: 2000
  216. })
  217. }
  218. }
  219. // 关闭手机号授权弹窗
  220. const closePhoneAuth = () => {
  221. showPhoneAuth.value = false
  222. }
  223. // 关闭购买弹窗
  224. const closePurchaseModal = () => {
  225. showModal.value = false
  226. }
  227. // 处理购买
  228. const handlePurchase = () => {
  229. if (!selectedPlan.value) {
  230. uni.showToast({
  231. title: '请选择订阅方案',
  232. icon: 'none'
  233. })
  234. return
  235. }
  236. // 计算过期时间
  237. const now = Date.now()
  238. let expireTime = now
  239. if (selectedPlan.value === 'daily') {
  240. // 日订阅:当天23:59:59过期
  241. const today = new Date()
  242. today.setHours(23, 59, 59, 999)
  243. expireTime = today.getTime()
  244. } else if (selectedPlan.value === 'weekly') {
  245. // 周套餐:7天后过期
  246. expireTime = now + 7 * 24 * 60 * 60 * 1000
  247. }
  248. // 保存购买信息
  249. const purchaseInfo = {
  250. plan: selectedPlan.value,
  251. purchaseTime: now,
  252. expireTime: expireTime
  253. }
  254. uni.setStorageSync('pool_purchase', purchaseInfo)
  255. // 更新购买状态
  256. isPurchased.value = true
  257. // 关闭弹窗
  258. closePurchaseModal()
  259. // 显示成功提示
  260. uni.showToast({
  261. title: '解锁成功',
  262. icon: 'success'
  263. })
  264. }
  265. // 历史查询
  266. const onHistorySearch = () => {
  267. uni.showToast({
  268. title: '历史查询功能开发中',
  269. icon: 'none'
  270. })
  271. }
  272. // 使用uni-app生命周期钩子
  273. onLoad(() => {
  274. checkLogin()
  275. checkPurchaseStatus()
  276. })
  277. onShow(() => {
  278. // 每次页面显示时都检查登录状态和购买状态
  279. checkLogin()
  280. checkPurchaseStatus()
  281. })
  282. </script>
  283. <style>
  284. .page-container {
  285. height: 100vh;
  286. display: flex;
  287. flex-direction: column;
  288. background: #f5f6fb;
  289. }
  290. .scroll-view {
  291. flex: 1;
  292. height: 0;
  293. }
  294. .content-wrapper {
  295. padding: 32rpx;
  296. background: #f5f6fb;
  297. min-height: 100%;
  298. }
  299. .page-title-card {
  300. background: #ffffff;
  301. padding: 30rpx 0;
  302. text-align: center;
  303. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
  304. border-radius: 0;
  305. }
  306. .page-title-text {
  307. font-size: 36rpx;
  308. font-weight: 800;
  309. color: #3F51F7;
  310. letter-spacing: 2rpx;
  311. }
  312. .card {
  313. background: #ffffff;
  314. border-radius: 24rpx;
  315. padding: 32rpx;
  316. box-shadow: 0 16rpx 40rpx rgba(37, 52, 94, 0.08);
  317. margin-bottom: 32rpx;
  318. }
  319. /* 性能指标卡片 */
  320. .performance-card {
  321. display: flex;
  322. justify-content: space-around;
  323. align-items: center;
  324. background: #f7f8fc;
  325. padding: 32rpx 24rpx;
  326. }
  327. .performance-item {
  328. display: flex;
  329. flex-direction: column;
  330. align-items: center;
  331. }
  332. .performance-label {
  333. font-size: 24rpx;
  334. color: #666a7f;
  335. margin-bottom: 12rpx;
  336. }
  337. .performance-value {
  338. font-size: 32rpx;
  339. font-weight: 700;
  340. color: #222222;
  341. }
  342. .performance-value.success {
  343. color: #3abf81;
  344. }
  345. .performance-value.profit {
  346. color: #f16565;
  347. }
  348. /* 超短精选池卡片 */
  349. .pool-card {
  350. padding: 40rpx 32rpx;
  351. }
  352. .pool-header {
  353. display: flex;
  354. align-items: center;
  355. margin-bottom: 16rpx;
  356. }
  357. .pool-icon {
  358. font-size: 32rpx;
  359. margin-right: 12rpx;
  360. }
  361. .pool-title {
  362. font-size: 32rpx;
  363. font-weight: 600;
  364. color: #222222;
  365. }
  366. .pool-desc {
  367. font-size: 26rpx;
  368. color: #666a7f;
  369. margin-bottom: 32rpx;
  370. }
  371. /* 锁定内容 */
  372. .locked-content {
  373. display: flex;
  374. flex-direction: column;
  375. align-items: center;
  376. padding: 60rpx 0 40rpx;
  377. }
  378. .lock-icon-wrapper {
  379. margin-bottom: 32rpx;
  380. }
  381. .lock-icon {
  382. font-size: 80rpx;
  383. }
  384. .lock-text {
  385. font-size: 28rpx;
  386. color: #222222;
  387. margin-bottom: 16rpx;
  388. text-align: center;
  389. }
  390. .lock-desc {
  391. font-size: 24rpx;
  392. color: #9ca2b5;
  393. margin-bottom: 48rpx;
  394. text-align: center;
  395. line-height: 1.6;
  396. }
  397. .unlock-button {
  398. width: 100%;
  399. background: linear-gradient(135deg, #5d55e8, #7568ff);
  400. border-radius: 16rpx;
  401. padding: 28rpx 0;
  402. text-align: center;
  403. box-shadow: 0 12rpx 24rpx rgba(93, 85, 232, 0.4);
  404. }
  405. .unlock-button-text {
  406. font-size: 30rpx;
  407. font-weight: 600;
  408. color: #ffffff;
  409. }
  410. /* 已解锁内容 */
  411. .unlocked-content {
  412. margin-top: 32rpx;
  413. }
  414. .unlocked-tip {
  415. font-size: 26rpx;
  416. color: #3abf81;
  417. margin-bottom: 24rpx;
  418. display: block;
  419. }
  420. .stock-list-placeholder {
  421. padding: 60rpx 0;
  422. text-align: center;
  423. background: #f7f8fc;
  424. border-radius: 16rpx;
  425. }
  426. .placeholder-text {
  427. font-size: 26rpx;
  428. color: #9ca2b5;
  429. }
  430. /* 历史股票池回顾 */
  431. .history-card {
  432. padding: 32rpx;
  433. }
  434. .history-header {
  435. display: flex;
  436. align-items: center;
  437. margin-bottom: 24rpx;
  438. }
  439. .history-icon {
  440. font-size: 28rpx;
  441. margin-right: 12rpx;
  442. }
  443. .history-title {
  444. font-size: 30rpx;
  445. font-weight: 600;
  446. color: #222222;
  447. }
  448. .history-search-row {
  449. display: flex;
  450. align-items: center;
  451. margin-bottom: 16rpx;
  452. }
  453. .history-date-input {
  454. flex: 1;
  455. background: #f7f8fc;
  456. border-radius: 12rpx;
  457. padding: 24rpx 24rpx;
  458. font-size: 26rpx;
  459. color: #222222;
  460. }
  461. .history-search-button {
  462. width: 80rpx;
  463. height: 80rpx;
  464. background: #5d55e8;
  465. border-radius: 12rpx;
  466. display: flex;
  467. align-items: center;
  468. justify-content: center;
  469. margin-left: 16rpx;
  470. }
  471. .search-icon {
  472. font-size: 32rpx;
  473. color: #ffffff;
  474. }
  475. .history-tip {
  476. font-size: 24rpx;
  477. color: #9ca2b5;
  478. line-height: 1.6;
  479. }
  480. /* 购买弹窗 */
  481. .modal-overlay {
  482. position: fixed;
  483. top: 0;
  484. left: 0;
  485. right: 0;
  486. bottom: 0;
  487. background: rgba(0, 0, 0, 0.5);
  488. display: flex;
  489. align-items: center;
  490. justify-content: center;
  491. z-index: 1000;
  492. }
  493. .modal-content {
  494. width: 640rpx;
  495. background: #ffffff;
  496. border-radius: 24rpx;
  497. padding: 40rpx 32rpx 32rpx;
  498. box-sizing: border-box;
  499. }
  500. .modal-header {
  501. display: flex;
  502. justify-content: space-between;
  503. align-items: center;
  504. margin-bottom: 32rpx;
  505. }
  506. .modal-title {
  507. font-size: 32rpx;
  508. font-weight: 600;
  509. color: #222222;
  510. }
  511. .modal-close {
  512. font-size: 48rpx;
  513. color: #9ca2b5;
  514. line-height: 1;
  515. width: 48rpx;
  516. height: 48rpx;
  517. display: flex;
  518. align-items: center;
  519. justify-content: center;
  520. }
  521. .subscription-options {
  522. margin-bottom: 32rpx;
  523. }
  524. .subscription-option {
  525. display: flex;
  526. justify-content: space-between;
  527. align-items: center;
  528. padding: 32rpx 24rpx;
  529. border: 2rpx solid #e5e7eb;
  530. border-radius: 16rpx;
  531. margin-bottom: 20rpx;
  532. transition: all 0.3s;
  533. }
  534. .subscription-option.active {
  535. border-color: #5d55e8;
  536. background: #f7f8fc;
  537. }
  538. .option-info {
  539. display: flex;
  540. flex-direction: column;
  541. }
  542. .option-title {
  543. font-size: 28rpx;
  544. font-weight: 600;
  545. color: #222222;
  546. margin-bottom: 8rpx;
  547. }
  548. .option-desc {
  549. font-size: 24rpx;
  550. color: #9ca2b5;
  551. }
  552. .option-price {
  553. font-size: 36rpx;
  554. font-weight: 700;
  555. color: #f16565;
  556. }
  557. .modal-footer {
  558. display: flex;
  559. flex-direction: column;
  560. align-items: center;
  561. }
  562. .pay-button {
  563. width: 100%;
  564. background: #f16565;
  565. border-radius: 16rpx;
  566. padding: 28rpx 0;
  567. text-align: center;
  568. margin-bottom: 24rpx;
  569. }
  570. .pay-button-text {
  571. font-size: 30rpx;
  572. font-weight: 600;
  573. color: #ffffff;
  574. }
  575. .agreement-text {
  576. font-size: 22rpx;
  577. color: #9ca2b5;
  578. }
  579. .bottom-safe-area {
  580. height: 80rpx;
  581. }
  582. /* 手机号授权弹窗 */
  583. .phone-auth-mask {
  584. position: fixed;
  585. top: 0;
  586. left: 0;
  587. right: 0;
  588. bottom: 0;
  589. background: rgba(0, 0, 0, 0.6);
  590. display: flex;
  591. align-items: center;
  592. justify-content: center;
  593. z-index: 9999;
  594. }
  595. .phone-auth-prompt {
  596. width: 560rpx;
  597. background: #ffffff;
  598. border-radius: 24rpx;
  599. padding: 50rpx 40rpx;
  600. text-align: center;
  601. box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3);
  602. }
  603. .auth-icon {
  604. font-size: 72rpx;
  605. margin-bottom: 20rpx;
  606. }
  607. .auth-title {
  608. display: block;
  609. font-size: 32rpx;
  610. font-weight: 600;
  611. color: #222222;
  612. margin-bottom: 12rpx;
  613. }
  614. .auth-desc {
  615. display: block;
  616. font-size: 24rpx;
  617. color: #999999;
  618. line-height: 1.5;
  619. margin-bottom: 32rpx;
  620. }
  621. .auth-button {
  622. width: 100%;
  623. height: 80rpx;
  624. background: linear-gradient(135deg, #FF9800, #FFA726);
  625. color: #ffffff;
  626. border-radius: 40rpx;
  627. font-size: 30rpx;
  628. font-weight: 600;
  629. box-shadow: 0 8rpx 24rpx rgba(255, 152, 0, 0.4);
  630. display: flex;
  631. align-items: center;
  632. justify-content: center;
  633. border: none;
  634. padding: 0;
  635. line-height: 80rpx;
  636. }
  637. .auth-button::after {
  638. border: none;
  639. }
  640. </style>