HistorySearchCard.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. <template>
  2. <view class="card history-card">
  3. <view class="history-header">
  4. <text class="history-title">📊 历史标的池回顾</text>
  5. </view>
  6. <!-- 日期区间选择 -->
  7. <view class="date-range-row">
  8. <view class="date-input" @click="openStartDatePicker">
  9. <text class="date-text">{{ formatDateDisplay(startDate) }}</text>
  10. <text class="date-icon">📅</text>
  11. </view>
  12. <text class="date-separator">至</text>
  13. <view class="date-input" @click="openEndDatePicker">
  14. <text class="date-text">{{ formatDateDisplay(endDate) }}</text>
  15. <text class="date-icon">📅</text>
  16. </view>
  17. </view>
  18. <!-- 查询按钮 -->
  19. <view class="history-search-button-full" @click="onSearch">
  20. <text class="search-button-text">🔍 查询历史数据</text>
  21. </view>
  22. <text class="history-tip">选择时间区间,查询该期间的入池样本及表现。</text>
  23. <!-- 自定义日期选择弹窗 -->
  24. <view v-if="showDatePicker" class="date-picker-mask" @click="closeDatePicker">
  25. <view class="date-picker-popup" @click.stop>
  26. <view class="picker-header">
  27. <text class="picker-cancel" @click="closeDatePicker">取消</text>
  28. <text class="picker-title">{{ currentPickerType === 'start' ? '选择开始日期' : '选择结束日期' }}</text>
  29. <text class="picker-confirm" @click="confirmDate">确定</text>
  30. </view>
  31. <!-- 年月选择 -->
  32. <view class="month-selector">
  33. <view class="month-nav" @click="prevMonth">
  34. <text class="nav-arrow">‹</text>
  35. </view>
  36. <text class="current-month">{{ tempYear }}年{{ tempMonth }}月</text>
  37. <view class="month-nav" @click="nextMonth">
  38. <text class="nav-arrow">›</text>
  39. </view>
  40. </view>
  41. <!-- 星期标题 -->
  42. <view class="weekday-row">
  43. <text class="weekday-item" v-for="day in weekDays" :key="day">{{ day }}</text>
  44. </view>
  45. <!-- 日期网格 -->
  46. <view class="days-grid">
  47. <view
  48. v-for="(day, index) in calendarDays"
  49. :key="index"
  50. :class="['day-item', {
  51. 'empty': !day,
  52. 'selected': day && isSelected(day),
  53. 'today': day && isToday(day)
  54. }]"
  55. @click="day && selectDay(day)"
  56. >
  57. <text v-if="day" class="day-text">{{ day }}</text>
  58. </view>
  59. </view>
  60. </view>
  61. </view>
  62. </view>
  63. </template>
  64. <script setup>
  65. import { ref, computed, onMounted } from 'vue'
  66. import { isLoggedIn as checkLoginStatus } from '../utils/auth.js'
  67. const props = defineProps({
  68. poolType: { type: Number, default: 1 },
  69. canSearch: { type: Boolean, default: false }
  70. })
  71. const emit = defineEmits(['dateChange'])
  72. // 获取默认日期(当前月份的第一天和最后一天)
  73. const getDefaultDates = () => {
  74. const now = new Date()
  75. const year = now.getFullYear()
  76. const month = String(now.getMonth() + 1).padStart(2, '0')
  77. const lastDay = new Date(year, now.getMonth() + 1, 0).getDate()
  78. return {
  79. start: `${year}-${month}-01`,
  80. end: `${year}-${month}-${lastDay}`
  81. }
  82. }
  83. const defaultDates = getDefaultDates()
  84. const startDate = ref(defaultDates.start)
  85. const endDate = ref(defaultDates.end)
  86. // 日期选择器状态
  87. const showDatePicker = ref(false)
  88. const currentPickerType = ref('start') // 'start' | 'end'
  89. const tempYear = ref(new Date().getFullYear())
  90. const tempMonth = ref(new Date().getMonth() + 1)
  91. const tempSelectedDay = ref(1)
  92. const weekDays = ['日', '一', '二', '三', '四', '五', '六']
  93. // 计算当月日历
  94. const calendarDays = computed(() => {
  95. const days = []
  96. const firstDay = new Date(tempYear.value, tempMonth.value - 1, 1).getDay()
  97. const daysInMonth = new Date(tempYear.value, tempMonth.value, 0).getDate()
  98. // 填充空白
  99. for (let i = 0; i < firstDay; i++) {
  100. days.push(null)
  101. }
  102. // 填充日期
  103. for (let i = 1; i <= daysInMonth; i++) {
  104. days.push(i)
  105. }
  106. return days
  107. })
  108. // 格式化日期显示
  109. const formatDateDisplay = (dateStr) => {
  110. if (!dateStr) return '请选择'
  111. const [year, month, day] = dateStr.split('-')
  112. return `${year}/${month}/${day}`
  113. }
  114. // 通知父组件日期变化
  115. const emitDateChange = () => {
  116. emit('dateChange', {
  117. startDate: startDate.value,
  118. endDate: endDate.value,
  119. poolType: props.poolType
  120. })
  121. }
  122. // 打开开始日期选择器
  123. const openStartDatePicker = () => {
  124. currentPickerType.value = 'start'
  125. const [year, month, day] = startDate.value.split('-').map(Number)
  126. tempYear.value = year
  127. tempMonth.value = month
  128. tempSelectedDay.value = day
  129. showDatePicker.value = true
  130. }
  131. // 打开结束日期选择器
  132. const openEndDatePicker = () => {
  133. currentPickerType.value = 'end'
  134. const [year, month, day] = endDate.value.split('-').map(Number)
  135. tempYear.value = year
  136. tempMonth.value = month
  137. tempSelectedDay.value = day
  138. showDatePicker.value = true
  139. }
  140. // 关闭日期选择器
  141. const closeDatePicker = () => {
  142. showDatePicker.value = false
  143. }
  144. // 上一月
  145. const prevMonth = () => {
  146. if (tempMonth.value === 1) {
  147. tempMonth.value = 12
  148. tempYear.value--
  149. } else {
  150. tempMonth.value--
  151. }
  152. }
  153. // 下一月
  154. const nextMonth = () => {
  155. if (tempMonth.value === 12) {
  156. tempMonth.value = 1
  157. tempYear.value++
  158. } else {
  159. tempMonth.value++
  160. }
  161. }
  162. // 选择日期
  163. const selectDay = (day) => {
  164. tempSelectedDay.value = day
  165. }
  166. // 判断是否选中
  167. const isSelected = (day) => {
  168. return day === tempSelectedDay.value
  169. }
  170. // 判断是否今天
  171. const isToday = (day) => {
  172. const today = new Date()
  173. return tempYear.value === today.getFullYear() &&
  174. tempMonth.value === today.getMonth() + 1 &&
  175. day === today.getDate()
  176. }
  177. // 确认选择
  178. const confirmDate = () => {
  179. const dateStr = `${tempYear.value}-${String(tempMonth.value).padStart(2, '0')}-${String(tempSelectedDay.value).padStart(2, '0')}`
  180. if (currentPickerType.value === 'start') {
  181. startDate.value = dateStr
  182. } else {
  183. endDate.value = dateStr
  184. }
  185. showDatePicker.value = false
  186. emitDateChange()
  187. }
  188. const onSearch = () => {
  189. if (!startDate.value || !endDate.value) {
  190. uni.showToast({ title: '请选择开始和结束日期', icon: 'none' })
  191. return
  192. }
  193. if (startDate.value > endDate.value) {
  194. uni.showToast({ title: '开始日期不能晚于结束日期', icon: 'none' })
  195. return
  196. }
  197. // 检查登录状态
  198. if (!checkLoginStatus()) {
  199. uni.showModal({
  200. title: '登录提示',
  201. content: '此功能需要登录后使用,是否前往登录?',
  202. confirmText: '去登录',
  203. cancelText: '取消',
  204. success: (res) => {
  205. if (res.confirm) {
  206. uni.navigateTo({ url: '/pages/login/login' })
  207. }
  208. }
  209. })
  210. return
  211. }
  212. // 跳转到历史查询结果页面
  213. uni.navigateTo({
  214. url: `/pages/history/history?startDate=${startDate.value}&endDate=${endDate.value}&poolType=${props.poolType}`
  215. })
  216. }
  217. // 组件挂载时通知父组件默认日期
  218. onMounted(() => {
  219. emitDateChange()
  220. })
  221. </script>
  222. <style scoped>
  223. .card {
  224. background: #ffffff;
  225. border-radius: 24rpx;
  226. box-shadow: 0 16rpx 40rpx rgba(37, 52, 94, 0.08);
  227. margin-bottom: 32rpx;
  228. }
  229. .history-card {
  230. padding: 32rpx;
  231. }
  232. .history-header {
  233. display: flex;
  234. align-items: center;
  235. margin-bottom: 24rpx;
  236. }
  237. .history-title {
  238. font-size: 30rpx;
  239. font-weight: 600;
  240. color: #222222;
  241. }
  242. .date-range-row {
  243. display: flex;
  244. align-items: center;
  245. gap: 16rpx;
  246. margin-bottom: 24rpx;
  247. }
  248. .date-separator {
  249. font-size: 26rpx;
  250. color: #666a7f;
  251. padding: 0 8rpx;
  252. }
  253. .date-input {
  254. flex: 1;
  255. background: #f7f8fc;
  256. border-radius: 12rpx;
  257. padding: 24rpx;
  258. display: flex;
  259. justify-content: space-between;
  260. align-items: center;
  261. }
  262. .date-text {
  263. font-size: 26rpx;
  264. color: #222222;
  265. }
  266. .date-icon {
  267. font-size: 24rpx;
  268. }
  269. .history-search-button-full {
  270. width: 100%;
  271. background: linear-gradient(135deg, #5d55e8, #7568ff);
  272. border-radius: 16rpx;
  273. padding: 28rpx 0;
  274. text-align: center;
  275. margin-top: 12rpx;
  276. margin-bottom: 24rpx;
  277. box-shadow: 0 8rpx 20rpx rgba(93, 85, 232, 0.3);
  278. }
  279. .search-button-text {
  280. font-size: 28rpx;
  281. font-weight: 600;
  282. color: #ffffff;
  283. }
  284. .history-tip {
  285. font-size: 24rpx;
  286. color: #9ca2b5;
  287. line-height: 1.6;
  288. }
  289. /* 日期选择器弹窗 */
  290. .date-picker-mask {
  291. position: fixed;
  292. top: 0;
  293. left: 0;
  294. right: 0;
  295. bottom: 0;
  296. background: rgba(0, 0, 0, 0.5);
  297. z-index: 1000;
  298. display: flex;
  299. align-items: flex-end;
  300. justify-content: center;
  301. }
  302. .date-picker-popup {
  303. width: 100%;
  304. background: #ffffff;
  305. border-radius: 32rpx 32rpx 0 0;
  306. padding: 32rpx;
  307. padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  308. }
  309. .picker-header {
  310. display: flex;
  311. justify-content: space-between;
  312. align-items: center;
  313. margin-bottom: 32rpx;
  314. }
  315. .picker-cancel {
  316. font-size: 28rpx;
  317. color: #999999;
  318. padding: 16rpx;
  319. }
  320. .picker-title {
  321. font-size: 32rpx;
  322. font-weight: 600;
  323. color: #222222;
  324. }
  325. .picker-confirm {
  326. font-size: 28rpx;
  327. color: #5d55e8;
  328. font-weight: 600;
  329. padding: 16rpx;
  330. }
  331. .month-selector {
  332. display: flex;
  333. justify-content: center;
  334. align-items: center;
  335. margin-bottom: 24rpx;
  336. gap: 48rpx;
  337. }
  338. .month-nav {
  339. width: 64rpx;
  340. height: 64rpx;
  341. display: flex;
  342. align-items: center;
  343. justify-content: center;
  344. background: #f7f8fc;
  345. border-radius: 50%;
  346. }
  347. .nav-arrow {
  348. font-size: 36rpx;
  349. color: #5d55e8;
  350. font-weight: bold;
  351. }
  352. .current-month {
  353. font-size: 32rpx;
  354. font-weight: 600;
  355. color: #222222;
  356. min-width: 200rpx;
  357. text-align: center;
  358. }
  359. .weekday-row {
  360. display: flex;
  361. margin-bottom: 16rpx;
  362. }
  363. .weekday-item {
  364. flex: 1;
  365. text-align: center;
  366. font-size: 24rpx;
  367. color: #999999;
  368. padding: 16rpx 0;
  369. }
  370. .days-grid {
  371. display: flex;
  372. flex-wrap: wrap;
  373. }
  374. .day-item {
  375. width: 14.28%;
  376. aspect-ratio: 1;
  377. display: flex;
  378. align-items: center;
  379. justify-content: center;
  380. }
  381. .day-item.empty {
  382. background: transparent;
  383. }
  384. .day-text {
  385. width: 72rpx;
  386. height: 72rpx;
  387. line-height: 72rpx;
  388. text-align: center;
  389. font-size: 28rpx;
  390. color: #222222;
  391. border-radius: 50%;
  392. }
  393. .day-item.selected .day-text {
  394. background: linear-gradient(135deg, #5d55e8, #7568ff);
  395. color: #ffffff;
  396. font-weight: 600;
  397. }
  398. .day-item.today .day-text {
  399. border: 2rpx solid #5d55e8;
  400. }
  401. .day-item.today.selected .day-text {
  402. border: none;
  403. }
  404. </style>