history.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. <template>
  2. <view class="page-container">
  3. <!-- 自定义导航栏 -->
  4. <view class="nav-bar">
  5. <view class="nav-back" @click="goBack">
  6. <text class="back-icon">←</text>
  7. </view>
  8. <text class="nav-title">历史股票池回顾</text>
  9. <view class="nav-placeholder"></view>
  10. </view>
  11. <scroll-view class="scroll-view" scroll-y @scrolltolower="loadMore">
  12. <view class="content-wrapper">
  13. <!-- 查询条件显示 -->
  14. <view class="query-info">
  15. <text class="query-label">查询区间:</text>
  16. <text class="query-value">{{ formatDate(startDate) }} 至 {{ formatDate(endDate) }}</text>
  17. </view>
  18. <!-- 统计信息 (仅超短池显示) -->
  19. <view v-if="poolType === 1" class="stats-card">
  20. <view class="stat-item">
  21. <text class="stat-value">{{ total }}</text>
  22. <text class="stat-label">总记录</text>
  23. </view>
  24. <view class="stat-divider"></view>
  25. <view class="stat-item">
  26. <text class="stat-value success-text">{{ successCount }}</text>
  27. <text class="stat-label">成功</text>
  28. </view>
  29. <view class="stat-divider"></view>
  30. <view class="stat-item">
  31. <text class="stat-value fail-text">{{ failCount }}</text>
  32. <text class="stat-label">失败</text>
  33. </view>
  34. </view>
  35. <!-- 表头 -->
  36. <view v-if="poolType === 1" class="table-header">
  37. <text class="th-name">名称/代码</text>
  38. <text class="th-close">收盘价</text>
  39. <text class="th-high">隔日最高</text>
  40. <text class="th-trend">隔日涨幅</text>
  41. </view>
  42. <view v-else class="table-header-simple">
  43. <text class="th-name-simple">名称/代码</text>
  44. <text class="th-trend-simple">最高涨幅</text>
  45. </view>
  46. <!-- 数据列表 -->
  47. <view class="stock-list">
  48. <view v-if="poolType === 1" class="stock-item" v-for="(item, index) in historyList" :key="index">
  49. <view class="td-name">
  50. <text class="stock-name">{{ item.stockName }}</text>
  51. <text class="stock-code">{{ item.stockCode }}</text>
  52. <text class="stock-date">入池: {{ formatRecordDate(item.recordDate) }}</text>
  53. </view>
  54. <text class="td-close">{{ item.closePrice ? item.closePrice.toFixed(2) : '-' }}</text>
  55. <text class="td-high">{{ item.nextDayHighPrice ? item.nextDayHighPrice.toFixed(2) : '-' }}</text>
  56. <view class="td-trend">
  57. <text :class="['trend-value', getTrendClass(item.nextDayHighTrend)]">
  58. {{ formatTrend(item.nextDayHighTrend) }}
  59. </text>
  60. <text v-if="item.status === 'success'" class="status-tag success">成功</text>
  61. <text v-else-if="item.status === 'fail'" class="status-tag fail">失败</text>
  62. </view>
  63. </view>
  64. <view v-else class="stock-item-simple" v-for="(item, index) in historyList" :key="index">
  65. <view class="td-name-simple">
  66. <text class="stock-name">{{ item.stockName }}</text>
  67. <text class="stock-code">{{ item.stockCode }}</text>
  68. <text class="stock-date">入池: {{ formatRecordDate(item.recordDate) }}</text>
  69. </view>
  70. <view class="td-trend-simple">
  71. <text :class="['trend-value', getTrendClass(item.nextDayHighTrend)]">
  72. {{ formatTrend(item.nextDayHighTrend) }}
  73. </text>
  74. </view>
  75. </view>
  76. </view>
  77. <!-- 加载状态 -->
  78. <view class="load-status">
  79. <text v-if="loading" class="load-text">加载中...</text>
  80. <text v-else-if="!hasMore && historyList.length > 0" class="load-text">已加载全部</text>
  81. <text v-else-if="historyList.length === 0 && !loading" class="load-text">暂无数据</text>
  82. </view>
  83. <!-- 底部安全区 -->
  84. <view class="bottom-safe-area"></view>
  85. </view>
  86. </scroll-view>
  87. </view>
  88. </template>
  89. <script setup>
  90. import { ref, computed } from 'vue'
  91. import { onLoad } from '@dcloudio/uni-app'
  92. import { getStockHistory } from '../../utils/api.js'
  93. const startDate = ref('')
  94. const endDate = ref('')
  95. const poolType = ref(1)
  96. const historyList = ref([])
  97. const total = ref(0)
  98. const hasMore = ref(false)
  99. const loading = ref(false)
  100. const pageNum = ref(1)
  101. const pageSize = ref(20)
  102. // 统计成功和失败数量
  103. const successCount = computed(() => historyList.value.filter(item => item.status === 'success').length)
  104. const failCount = computed(() => historyList.value.filter(item => item.status === 'fail').length)
  105. // 格式化日期显示
  106. const formatDate = (dateStr) => {
  107. if (!dateStr) return ''
  108. const [year, month, day] = dateStr.split('-')
  109. return `${year}年${month}月${day}日`
  110. }
  111. // 格式化入池日期(简短格式)
  112. const formatRecordDate = (dateStr) => {
  113. if (!dateStr) return '-'
  114. // 处理可能的日期格式:2025-01-10 或 数组格式 [2025, 1, 10]
  115. if (Array.isArray(dateStr)) {
  116. const [year, month, day] = dateStr
  117. return `${month}/${day}`
  118. }
  119. const parts = dateStr.split('-')
  120. if (parts.length >= 3) {
  121. return `${parseInt(parts[1])}/${parseInt(parts[2])}`
  122. }
  123. return dateStr
  124. }
  125. // 获取涨幅样式
  126. const getTrendClass = (trend) => {
  127. if (trend === null || trend === undefined) return ''
  128. return trend >= 0 ? 'text-red' : 'text-green'
  129. }
  130. // 格式化涨幅
  131. const formatTrend = (trend) => {
  132. if (trend === null || trend === undefined) return '-'
  133. const prefix = trend >= 0 ? '+' : ''
  134. return `${prefix}${trend.toFixed(2)}%`
  135. }
  136. // 返回上一页
  137. const goBack = () => {
  138. const pages = getCurrentPages()
  139. pages.length > 1 ? uni.navigateBack() : uni.switchTab({ url: '/pages/index/index' })
  140. }
  141. // 加载历史数据
  142. const loadHistoryData = async () => {
  143. if (loading.value) return
  144. loading.value = true
  145. try {
  146. const res = await getStockHistory({
  147. startDate: startDate.value,
  148. endDate: endDate.value,
  149. poolType: poolType.value,
  150. pageNum: pageNum.value,
  151. pageSize: pageSize.value
  152. })
  153. if (res.code === 200 && res.data) {
  154. if (pageNum.value === 1) {
  155. historyList.value = res.data.list || []
  156. } else {
  157. historyList.value = [...historyList.value, ...(res.data.list || [])]
  158. }
  159. total.value = res.data.total || 0
  160. hasMore.value = res.data.hasMore || false
  161. }
  162. } catch (e) {
  163. console.error('加载历史数据失败:', e)
  164. uni.showToast({ title: '加载失败', icon: 'none' })
  165. } finally {
  166. loading.value = false
  167. }
  168. }
  169. // 加载更多
  170. const loadMore = () => {
  171. if (loading.value || !hasMore.value) return
  172. pageNum.value++
  173. loadHistoryData()
  174. }
  175. onLoad((options) => {
  176. startDate.value = options.startDate || ''
  177. endDate.value = options.endDate || ''
  178. poolType.value = parseInt(options.poolType) || 1
  179. // 设置导航栏标题
  180. const poolName = poolType.value === 1 ? '超短池' : '强势池'
  181. uni.setNavigationBarTitle({ title: `${poolName}历史回顾` })
  182. loadHistoryData()
  183. })
  184. </script>
  185. <style scoped>
  186. .page-container {
  187. height: 100vh;
  188. display: flex;
  189. flex-direction: column;
  190. background: #f5f6fb;
  191. }
  192. /* 自定义导航栏 */
  193. .nav-bar {
  194. display: flex;
  195. align-items: center;
  196. justify-content: space-between;
  197. padding: 44px 32rpx 20rpx;
  198. background: #ffffff;
  199. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  200. }
  201. .nav-back {
  202. width: 60rpx;
  203. height: 60rpx;
  204. display: flex;
  205. align-items: center;
  206. justify-content: center;
  207. }
  208. .back-icon {
  209. font-size: 36rpx;
  210. color: #333;
  211. }
  212. .nav-title {
  213. font-size: 32rpx;
  214. font-weight: 600;
  215. color: #222222;
  216. }
  217. .nav-placeholder {
  218. width: 60rpx;
  219. }
  220. .scroll-view {
  221. flex: 1;
  222. height: 0;
  223. }
  224. .content-wrapper {
  225. padding: 24rpx 32rpx;
  226. }
  227. /* 查询条件 */
  228. .query-info {
  229. display: flex;
  230. align-items: center;
  231. padding: 20rpx 24rpx;
  232. background: #ffffff;
  233. border-radius: 16rpx;
  234. margin-bottom: 24rpx;
  235. }
  236. .query-label {
  237. font-size: 26rpx;
  238. color: #666a7f;
  239. }
  240. .query-value {
  241. font-size: 26rpx;
  242. color: #222222;
  243. font-weight: 500;
  244. }
  245. /* 统计卡片 */
  246. .stats-card {
  247. display: flex;
  248. align-items: center;
  249. justify-content: space-around;
  250. padding: 32rpx 24rpx;
  251. background: #ffffff;
  252. border-radius: 20rpx;
  253. margin-bottom: 24rpx;
  254. box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.06);
  255. }
  256. .stat-item {
  257. display: flex;
  258. flex-direction: column;
  259. align-items: center;
  260. flex: 1;
  261. }
  262. .stat-value {
  263. font-size: 40rpx;
  264. font-weight: 700;
  265. color: #1a1a2e;
  266. margin-bottom: 8rpx;
  267. }
  268. .stat-value.success-text {
  269. color: #22c55e;
  270. }
  271. .stat-value.fail-text {
  272. color: #ef4444;
  273. }
  274. .stat-label {
  275. font-size: 24rpx;
  276. color: #9ca3af;
  277. }
  278. .stat-divider {
  279. width: 1rpx;
  280. height: 60rpx;
  281. background: #eef0f5;
  282. }
  283. /* 表头 */
  284. .table-header {
  285. display: flex;
  286. align-items: center;
  287. padding: 20rpx 24rpx;
  288. background: #eef0f5;
  289. border-radius: 12rpx;
  290. margin-bottom: 16rpx;
  291. }
  292. .table-header-simple {
  293. display: flex;
  294. align-items: center;
  295. padding: 20rpx 24rpx;
  296. background: #eef0f5;
  297. border-radius: 12rpx;
  298. margin-bottom: 16rpx;
  299. }
  300. .th-name {
  301. width: 180rpx;
  302. font-size: 24rpx;
  303. font-weight: 600;
  304. color: #666a7f;
  305. }
  306. .th-close {
  307. width: 120rpx;
  308. font-size: 24rpx;
  309. font-weight: 600;
  310. color: #666a7f;
  311. text-align: center;
  312. }
  313. .th-high {
  314. width: 120rpx;
  315. font-size: 24rpx;
  316. font-weight: 600;
  317. color: #666a7f;
  318. text-align: center;
  319. }
  320. .th-trend {
  321. flex: 1;
  322. font-size: 24rpx;
  323. font-weight: 600;
  324. color: #666a7f;
  325. text-align: right;
  326. }
  327. .th-name-simple {
  328. flex: 1;
  329. font-size: 24rpx;
  330. font-weight: 600;
  331. color: #666a7f;
  332. }
  333. .th-trend-simple {
  334. width: 200rpx;
  335. font-size: 24rpx;
  336. font-weight: 600;
  337. color: #666a7f;
  338. text-align: right;
  339. }
  340. /* 数据列表 */
  341. .stock-list {
  342. display: flex;
  343. flex-direction: column;
  344. gap: 16rpx;
  345. }
  346. .stock-item {
  347. display: flex;
  348. align-items: center;
  349. padding: 24rpx;
  350. background: #ffffff;
  351. border-radius: 16rpx;
  352. box-shadow: 0 4rpx 12rpx rgba(37, 52, 94, 0.04);
  353. }
  354. .stock-item-simple {
  355. display: flex;
  356. align-items: center;
  357. padding: 24rpx;
  358. background: #ffffff;
  359. border-radius: 16rpx;
  360. box-shadow: 0 4rpx 12rpx rgba(37, 52, 94, 0.04);
  361. }
  362. .td-name {
  363. width: 180rpx;
  364. display: flex;
  365. flex-direction: column;
  366. }
  367. .stock-name {
  368. font-size: 28rpx;
  369. font-weight: 700;
  370. color: #1a1a2e;
  371. margin-bottom: 4rpx;
  372. }
  373. .stock-code {
  374. font-size: 22rpx;
  375. color: #9ca3af;
  376. }
  377. .stock-date {
  378. font-size: 20rpx;
  379. color: #5d55e8;
  380. margin-top: 4rpx;
  381. }
  382. .td-close {
  383. width: 120rpx;
  384. font-size: 28rpx;
  385. font-weight: 600;
  386. color: #1a1a2e;
  387. text-align: center;
  388. }
  389. .td-high {
  390. width: 120rpx;
  391. font-size: 28rpx;
  392. font-weight: 600;
  393. color: #1a1a2e;
  394. text-align: center;
  395. }
  396. .td-trend {
  397. flex: 1;
  398. display: flex;
  399. flex-direction: column;
  400. align-items: flex-end;
  401. gap: 6rpx;
  402. }
  403. .td-name-simple {
  404. flex: 1;
  405. display: flex;
  406. flex-direction: column;
  407. }
  408. .td-trend-simple {
  409. width: 200rpx;
  410. display: flex;
  411. flex-direction: column;
  412. align-items: flex-end;
  413. gap: 6rpx;
  414. }
  415. .trend-value {
  416. font-size: 28rpx;
  417. font-weight: 700;
  418. }
  419. .text-red {
  420. color: #ef4444;
  421. }
  422. .text-green {
  423. color: #22c55e;
  424. }
  425. .status-tag {
  426. font-size: 20rpx;
  427. padding: 6rpx 16rpx;
  428. border-radius: 8rpx;
  429. font-weight: 600;
  430. }
  431. .status-tag.success {
  432. background: rgba(34, 197, 94, 0.15);
  433. color: #22c55e;
  434. }
  435. .status-tag.fail {
  436. background: rgba(239, 68, 68, 0.15);
  437. color: #ef4444;
  438. }
  439. /* 加载状态 */
  440. .load-status {
  441. padding: 40rpx 0;
  442. text-align: center;
  443. }
  444. .load-text {
  445. font-size: 26rpx;
  446. color: #9ca3af;
  447. }
  448. .bottom-safe-area {
  449. height: 40rpx;
  450. }
  451. </style>