HistorySearchCard.vue 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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 v-if="!canSearch" class="history-tip">订阅后可查询该期间的入池股票及表现。</text>
  23. <text v-else class="history-tip">选择时间区间,查询该期间的入池股票及表现。</text>
  24. <!-- 自定义日期选择弹窗 -->
  25. <view v-if="showDatePicker" class="date-picker-mask" @click="closeDatePicker">
  26. <view class="date-picker-popup" @click.stop>
  27. <view class="picker-header">
  28. <text class="picker-cancel" @click="closeDatePicker">取消</text>
  29. <text class="picker-title">{{ currentPickerType === 'start' ? '选择开始日期' : '选择结束日期' }}</text>
  30. <text class="picker-confirm" @click="confirmDate">确定</text>
  31. </view>
  32. <!-- 年月选择 -->
  33. <view class="month-selector">
  34. <view class="month-nav" @click="prevMonth">
  35. <text class="nav-arrow">‹</text>
  36. </view>
  37. <text class="current-month">{{ tempYear }}年{{ tempMonth }}月</text>
  38. <view class="month-nav" @click="nextMonth">
  39. <text class="nav-arrow">›</text>
  40. </view>
  41. </view>
  42. <!-- 星期标题 -->
  43. <view class="weekday-row">
  44. <text class="weekday-item" v-for="day in weekDays" :key="day">{{ day }}</text>
  45. </view>
  46. <!-- 日期网格 -->
  47. <view class="days-grid">
  48. <view
  49. v-for="(day, index) in calendarDays"
  50. :key="index"
  51. :class="['day-item', {
  52. 'empty': !day,
  53. 'selected': day && isSelected(day),
  54. 'today': day && isToday(day)
  55. }]"
  56. @click="day && selectDay(day)"
  57. >
  58. <text v-if="day" class="day-text">{{ day }}</text>
  59. </view>
  60. </view>
  61. </view>
  62. </view>
  63. </view>
  64. </template>
  65. <script setup>
  66. import { ref, computed, onMounted } from 'vue'
  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 (!props.canSearch) {
  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. if (startDate.value > endDate.value) {
  198. uni.showToast({ title: '开始日期不能晚于结束日期', icon: 'none' })
  199. return
  200. }
  201. // 跳转到历史查询结果页面
  202. uni.navigateTo({
  203. url: `/pages/history/history?startDate=${startDate.value}&endDate=${endDate.value}&poolType=${props.poolType}`
  204. })
  205. }
  206. // 组件挂载时通知父组件默认日期
  207. onMounted(() => {
  208. emitDateChange()
  209. })
  210. </script>
  211. <style scoped>
  212. .card {
  213. background: #ffffff;
  214. border-radius: 24rpx;
  215. box-shadow: 0 16rpx 40rpx rgba(37, 52, 94, 0.08);
  216. margin-bottom: 32rpx;
  217. }
  218. .history-card {
  219. padding: 32rpx;
  220. }
  221. .history-header {
  222. display: flex;
  223. align-items: center;
  224. margin-bottom: 24rpx;
  225. }
  226. .history-title {
  227. font-size: 30rpx;
  228. font-weight: 600;
  229. color: #222222;
  230. }
  231. .date-range-row {
  232. display: flex;
  233. align-items: center;
  234. gap: 16rpx;
  235. margin-bottom: 24rpx;
  236. }
  237. .date-separator {
  238. font-size: 26rpx;
  239. color: #666a7f;
  240. padding: 0 8rpx;
  241. }
  242. .date-input {
  243. flex: 1;
  244. background: #f7f8fc;
  245. border-radius: 12rpx;
  246. padding: 24rpx;
  247. display: flex;
  248. justify-content: space-between;
  249. align-items: center;
  250. }
  251. .date-text {
  252. font-size: 26rpx;
  253. color: #222222;
  254. }
  255. .date-icon {
  256. font-size: 24rpx;
  257. }
  258. .history-search-button-full {
  259. width: 100%;
  260. background: linear-gradient(135deg, #5d55e8, #7568ff);
  261. border-radius: 16rpx;
  262. padding: 28rpx 0;
  263. text-align: center;
  264. margin-top: 12rpx;
  265. margin-bottom: 24rpx;
  266. box-shadow: 0 8rpx 20rpx rgba(93, 85, 232, 0.3);
  267. }
  268. .search-button-text {
  269. font-size: 28rpx;
  270. font-weight: 600;
  271. color: #ffffff;
  272. }
  273. .history-tip {
  274. font-size: 24rpx;
  275. color: #9ca2b5;
  276. line-height: 1.6;
  277. }
  278. /* 日期选择器弹窗 */
  279. .date-picker-mask {
  280. position: fixed;
  281. top: 0;
  282. left: 0;
  283. right: 0;
  284. bottom: 0;
  285. background: rgba(0, 0, 0, 0.5);
  286. z-index: 1000;
  287. display: flex;
  288. align-items: flex-end;
  289. justify-content: center;
  290. }
  291. .date-picker-popup {
  292. width: 100%;
  293. background: #ffffff;
  294. border-radius: 32rpx 32rpx 0 0;
  295. padding: 32rpx;
  296. padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
  297. }
  298. .picker-header {
  299. display: flex;
  300. justify-content: space-between;
  301. align-items: center;
  302. margin-bottom: 32rpx;
  303. }
  304. .picker-cancel {
  305. font-size: 28rpx;
  306. color: #999999;
  307. padding: 16rpx;
  308. }
  309. .picker-title {
  310. font-size: 32rpx;
  311. font-weight: 600;
  312. color: #222222;
  313. }
  314. .picker-confirm {
  315. font-size: 28rpx;
  316. color: #5d55e8;
  317. font-weight: 600;
  318. padding: 16rpx;
  319. }
  320. .month-selector {
  321. display: flex;
  322. justify-content: center;
  323. align-items: center;
  324. margin-bottom: 24rpx;
  325. gap: 48rpx;
  326. }
  327. .month-nav {
  328. width: 64rpx;
  329. height: 64rpx;
  330. display: flex;
  331. align-items: center;
  332. justify-content: center;
  333. background: #f7f8fc;
  334. border-radius: 50%;
  335. }
  336. .nav-arrow {
  337. font-size: 36rpx;
  338. color: #5d55e8;
  339. font-weight: bold;
  340. }
  341. .current-month {
  342. font-size: 32rpx;
  343. font-weight: 600;
  344. color: #222222;
  345. min-width: 200rpx;
  346. text-align: center;
  347. }
  348. .weekday-row {
  349. display: flex;
  350. margin-bottom: 16rpx;
  351. }
  352. .weekday-item {
  353. flex: 1;
  354. text-align: center;
  355. font-size: 24rpx;
  356. color: #999999;
  357. padding: 16rpx 0;
  358. }
  359. .days-grid {
  360. display: flex;
  361. flex-wrap: wrap;
  362. }
  363. .day-item {
  364. width: 14.28%;
  365. aspect-ratio: 1;
  366. display: flex;
  367. align-items: center;
  368. justify-content: center;
  369. }
  370. .day-item.empty {
  371. background: transparent;
  372. }
  373. .day-text {
  374. width: 72rpx;
  375. height: 72rpx;
  376. line-height: 72rpx;
  377. text-align: center;
  378. font-size: 28rpx;
  379. color: #222222;
  380. border-radius: 50%;
  381. }
  382. .day-item.selected .day-text {
  383. background: linear-gradient(135deg, #5d55e8, #7568ff);
  384. color: #ffffff;
  385. font-weight: 600;
  386. }
  387. .day-item.today .day-text {
  388. border: 2rpx solid #5d55e8;
  389. }
  390. .day-item.today.selected .day-text {
  391. border: none;
  392. }
  393. </style>