points.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. <template>
  2. <view class="page-container">
  3. <!-- 顶部导航栏 -->
  4. <view class="custom-navbar">
  5. <view class="navbar-back" @click="handleBack">
  6. <text class="back-icon">←</text>
  7. </view>
  8. <view class="navbar-title">
  9. <text class="title-text">积分记录</text>
  10. </view>
  11. <view class="navbar-placeholder"></view>
  12. </view>
  13. <!-- 积分记录列表 -->
  14. <scroll-view class="scroll-view" scroll-y>
  15. <view class="transaction-list">
  16. <!-- 按日期分组显示 -->
  17. <view v-for="(group, dateKey) in groupedRecords" :key="dateKey" class="date-group">
  18. <view class="date-header">
  19. <text class="date-text">{{ dateKey }}</text>
  20. </view>
  21. <view
  22. v-for="(item, index) in group"
  23. :key="index"
  24. class="transaction-item"
  25. >
  26. <view class="item-left">
  27. <view class="stock-info">
  28. <text class="stock-name">{{ item.title }}</text>
  29. </view>
  30. <text class="transaction-time">{{ formatTime(item.timestamp) }}</text>
  31. </view>
  32. <view class="item-right">
  33. <text :class="['amount-text', item.type === 'add' ? 'amount-sell' : 'amount-buy']">
  34. {{ item.type === 'add' ? '+' : '-' }}{{ item.points }}积分
  35. </text>
  36. <view :class="['status-badge', item.type === 'add' ? 'status-sell' : 'status-buy']">
  37. <text class="status-text">{{ item.type === 'add' ? '获得' : '消耗' }}</text>
  38. </view>
  39. </view>
  40. </view>
  41. </view>
  42. <!-- 空状态 -->
  43. <view v-if="records.length === 0" class="empty-state">
  44. <text class="empty-icon">💰</text>
  45. <text class="empty-text">暂无积分记录</text>
  46. <text class="empty-desc">完成任务即可获得积分</text>
  47. </view>
  48. <!-- 底部安全区域 -->
  49. <view class="bottom-safe-area"></view>
  50. </view>
  51. </scroll-view>
  52. </view>
  53. </template>
  54. <script setup>
  55. import { ref, computed, onMounted } from 'vue'
  56. const records = ref([])
  57. // 返回上一页
  58. const handleBack = () => {
  59. const pages = getCurrentPages()
  60. if (pages.length > 1) {
  61. // 有上一页,返回
  62. uni.navigateBack()
  63. } else {
  64. // 没有上一页,跳转到个人中心
  65. uni.switchTab({
  66. url: '/pages/mine/mine'
  67. })
  68. }
  69. }
  70. // 按日期分组的积分记录
  71. const groupedRecords = computed(() => {
  72. const groups = {}
  73. records.value.forEach(item => {
  74. const dateKey = formatDate(item.timestamp)
  75. if (!groups[dateKey]) {
  76. groups[dateKey] = []
  77. }
  78. groups[dateKey].push(item)
  79. })
  80. return groups
  81. })
  82. // 格式化日期(用于分组)
  83. const formatDate = (timestamp) => {
  84. const date = new Date(timestamp)
  85. const today = new Date()
  86. const yesterday = new Date(today)
  87. yesterday.setDate(yesterday.getDate() - 1)
  88. const dateStr = date.toLocaleDateString('zh-CN', {
  89. year: 'numeric',
  90. month: '2-digit',
  91. day: '2-digit'
  92. })
  93. const todayStr = today.toLocaleDateString('zh-CN', {
  94. year: 'numeric',
  95. month: '2-digit',
  96. day: '2-digit'
  97. })
  98. const yesterdayStr = yesterday.toLocaleDateString('zh-CN', {
  99. year: 'numeric',
  100. month: '2-digit',
  101. day: '2-digit'
  102. })
  103. if (dateStr === todayStr) {
  104. return '今天'
  105. } else if (dateStr === yesterdayStr) {
  106. return '昨天'
  107. } else {
  108. return dateStr
  109. }
  110. }
  111. // 格式化时间(仅显示时分)
  112. const formatTime = (timestamp) => {
  113. const date = new Date(timestamp)
  114. const hours = String(date.getHours()).padStart(2, '0')
  115. const minutes = String(date.getMinutes()).padStart(2, '0')
  116. return `${hours}:${minutes}`
  117. }
  118. // 加载积分记录
  119. const loadRecords = () => {
  120. try {
  121. const storedRecords = uni.getStorageSync('points_records') || []
  122. // 按时间倒序排列
  123. records.value = storedRecords.sort((a, b) => b.timestamp - a.timestamp)
  124. } catch (e) {
  125. console.error('加载积分记录失败:', e)
  126. records.value = []
  127. }
  128. }
  129. // 初始化模拟数据(仅用于演示)
  130. const initMockData = () => {
  131. const mockRecords = [
  132. {
  133. title: '每日签到',
  134. points: 10,
  135. type: 'add',
  136. timestamp: Date.now() - 1000 * 60 * 30
  137. },
  138. {
  139. title: '完成交易',
  140. points: 5,
  141. type: 'add',
  142. timestamp: Date.now() - 1000 * 60 * 60 * 2
  143. },
  144. {
  145. title: '查询股票',
  146. points: 2,
  147. type: 'use',
  148. timestamp: Date.now() - 1000 * 60 * 60 * 5
  149. },
  150. {
  151. title: '邀请好友',
  152. points: 20,
  153. type: 'add',
  154. timestamp: Date.now() - 1000 * 60 * 60 * 24
  155. },
  156. {
  157. title: '解锁超短池',
  158. points: 18,
  159. type: 'use',
  160. timestamp: Date.now() - 1000 * 60 * 60 * 24 * 2
  161. }
  162. ]
  163. // 如果没有记录,使用模拟数据
  164. if (records.value.length === 0) {
  165. records.value = mockRecords
  166. uni.setStorageSync('points_records', mockRecords)
  167. }
  168. }
  169. onMounted(() => {
  170. loadRecords()
  171. initMockData()
  172. })
  173. </script>
  174. <style scoped>
  175. .page-container {
  176. min-height: 100vh;
  177. background: #f5f6fb;
  178. display: flex;
  179. flex-direction: column;
  180. }
  181. /* 自定义导航栏 */
  182. .custom-navbar {
  183. background: #ffffff;
  184. display: flex;
  185. align-items: center;
  186. justify-content: space-between;
  187. padding: 80rpx 32rpx 30rpx;
  188. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
  189. position: relative;
  190. }
  191. .navbar-back {
  192. width: 80rpx;
  193. height: 60rpx;
  194. display: flex;
  195. align-items: center;
  196. justify-content: flex-start;
  197. }
  198. .back-icon {
  199. font-size: 40rpx;
  200. color: #222222;
  201. font-weight: bold;
  202. }
  203. .navbar-title {
  204. position: absolute;
  205. left: 50%;
  206. transform: translateX(-50%);
  207. }
  208. .title-text {
  209. font-size: 36rpx;
  210. font-weight: 800;
  211. color: #3F51F7;
  212. letter-spacing: 2rpx;
  213. }
  214. .navbar-placeholder {
  215. width: 80rpx;
  216. }
  217. /* 交易记录列表 */
  218. .scroll-view {
  219. flex: 1;
  220. height: 0;
  221. }
  222. .transaction-list {
  223. padding: 0 0 32rpx;
  224. }
  225. /* 日期分组 */
  226. .date-group {
  227. margin-bottom: 32rpx;
  228. }
  229. .date-header {
  230. padding: 24rpx 32rpx 16rpx;
  231. }
  232. .date-text {
  233. font-size: 26rpx;
  234. color: #9ca2b5;
  235. font-weight: 500;
  236. }
  237. /* 交易项 */
  238. .transaction-item {
  239. background: #ffffff;
  240. padding: 32rpx;
  241. display: flex;
  242. justify-content: space-between;
  243. align-items: center;
  244. border-bottom: 1rpx solid #f5f6fb;
  245. }
  246. .transaction-item:first-child {
  247. border-top-left-radius: 0;
  248. border-top-right-radius: 0;
  249. }
  250. .transaction-item:last-child {
  251. border-bottom: none;
  252. }
  253. .item-left {
  254. flex: 1;
  255. display: flex;
  256. flex-direction: column;
  257. }
  258. .stock-info {
  259. display: flex;
  260. align-items: center;
  261. margin-bottom: 8rpx;
  262. }
  263. .stock-name {
  264. font-size: 30rpx;
  265. font-weight: 600;
  266. color: #222222;
  267. margin-right: 12rpx;
  268. }
  269. .transaction-time {
  270. font-size: 24rpx;
  271. color: #9ca2b5;
  272. }
  273. .item-right {
  274. display: flex;
  275. flex-direction: column;
  276. align-items: flex-end;
  277. }
  278. .amount-text {
  279. font-size: 32rpx;
  280. font-weight: 700;
  281. margin-bottom: 8rpx;
  282. }
  283. .amount-buy {
  284. color: #3abf81;
  285. }
  286. .amount-sell {
  287. color: #f16565;
  288. }
  289. .status-badge {
  290. padding: 4rpx 16rpx;
  291. border-radius: 12rpx;
  292. }
  293. .status-buy {
  294. background: #e7f7ef;
  295. }
  296. .status-sell {
  297. background: #ffe7ee;
  298. }
  299. .status-text {
  300. font-size: 22rpx;
  301. font-weight: 500;
  302. }
  303. .status-buy .status-text {
  304. color: #3abf81;
  305. }
  306. .status-sell .status-text {
  307. color: #f16565;
  308. }
  309. /* 空状态 */
  310. .empty-state {
  311. display: flex;
  312. flex-direction: column;
  313. align-items: center;
  314. padding: 120rpx 0;
  315. background: #ffffff;
  316. margin: 0 32rpx;
  317. border-radius: 24rpx;
  318. }
  319. .empty-icon {
  320. font-size: 120rpx;
  321. margin-bottom: 32rpx;
  322. opacity: 0.5;
  323. }
  324. .empty-text {
  325. font-size: 32rpx;
  326. color: #666666;
  327. margin-bottom: 16rpx;
  328. font-weight: 500;
  329. }
  330. .empty-desc {
  331. font-size: 26rpx;
  332. color: #9ca2b5;
  333. }
  334. .bottom-safe-area {
  335. height: 40rpx;
  336. }
  337. </style>