timeScale.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. <!--
  2. 时间刻度尺组件 (TimeScale)
  3. 功能:
  4. - 显示水平时间轴,包含月份标记和小刻度
  5. - 支持用户滑动选择时间
  6. - 每两天显示一个小刻度
  7. - 中间固定竖线,显示当前选中的时间
  8. - 时间格式:YYYY-MM-DD
  9. Props:
  10. - currentDate: String - 当前选中的时间 (YYYY-MM-DD格式)
  11. Events:
  12. - timeChange: (date: String) - 时间变化时触发,返回YYYY-MM-DD格式的日期
  13. 使用示例:
  14. <time-scale
  15. :current-date="selectedDate"
  16. @time-change="handleTimeChange"
  17. ></time-scale>
  18. -->
  19. <template>
  20. <view class="time-scale-container">
  21. <view class="time-scale-scroll">
  22. <scroll-view
  23. class="time-scale-wrapper"
  24. scroll-x
  25. :scroll-left="scrollLeft"
  26. scroll-with-animation
  27. @scroll="handleScroll"
  28. @scrolltolower="handleScrollToLower"
  29. @scrolltoupper="handleScrollToUpper"
  30. ref="scrollViewRef"
  31. >
  32. <view class="time-scale">
  33. <!-- 月份标签 -->
  34. <view class="month-labels">
  35. <view
  36. class="month-label"
  37. v-for="(month, index) in monthLabels"
  38. :key="`month-${index}`"
  39. :style="{ left: month.left + 'rpx' }"
  40. >
  41. {{ month.label }}
  42. </view>
  43. </view>
  44. <!-- 时间轴 -->
  45. <view class="timeline">
  46. <!-- 月份大点 -->
  47. <view
  48. class="month-dot"
  49. v-for="(month, index) in monthLabels"
  50. :key="`dot-${index}`"
  51. :style="{ left: month.left + 'rpx' }"
  52. ></view>
  53. <!-- 小刻度 -->
  54. <view
  55. class="tick-mark"
  56. v-for="(tick, index) in tickMarks"
  57. :key="`tick-${index}`"
  58. :style="{ left: tick.left + 'rpx' }"
  59. ></view>
  60. </view>
  61. </view>
  62. </scroll-view>
  63. <!-- 中间竖线 -->
  64. <view class="center-line"></view>
  65. </view>
  66. <!-- 当前时间显示 -->
  67. <view class="current-time" v-if="currentTime">
  68. {{ formatDisplayTime(currentTime) }}
  69. </view>
  70. </view>
  71. </template>
  72. <script setup>
  73. import { ref, computed, onMounted, watch, nextTick } from 'vue'
  74. const props = defineProps({
  75. // 当前选中的时间
  76. currentDate: {
  77. type: String,
  78. default: ''
  79. }
  80. })
  81. const emit = defineEmits(['timeChange'])
  82. // 响应式数据
  83. const currentTime = ref('')
  84. let scrollTimer = null // 防抖定时器
  85. const scrollViewRef = ref(null) // 滚动视图引用
  86. const scrollLeft = ref(0) // 滚动位置
  87. // 计算属性:生成月份标签
  88. const monthLabels = computed(() => {
  89. const labels = []
  90. const now = new Date()
  91. const currentYear = now.getFullYear()
  92. const currentMonth = now.getMonth()
  93. // 从当前月份往前一年的月份,从右到左排列
  94. for (let i = 0; i <= 11; i++) {
  95. const date = new Date(currentYear, currentMonth - i, 1)
  96. const year = date.getFullYear()
  97. const month = date.getMonth() + 1
  98. labels.push({
  99. label: `${month}月`,
  100. year: year,
  101. month: month,
  102. left: (11 - i) * 200 + 100, // 从右到左排列,当前月份在最右边
  103. monthOffset: i // 添加月份偏移信息
  104. })
  105. }
  106. return labels
  107. })
  108. // 计算属性:生成小刻度
  109. const tickMarks = computed(() => {
  110. const ticks = []
  111. const now = new Date()
  112. const currentYear = now.getFullYear()
  113. const currentMonth = now.getMonth()
  114. // 从当前月份往前一年的刻度,从右到左排列
  115. for (let monthOffset = 0; monthOffset <= 11; monthOffset++) {
  116. const date = new Date(currentYear, currentMonth - monthOffset, 1)
  117. const year = date.getFullYear()
  118. const month = date.getMonth() + 1
  119. const daysInMonth = new Date(year, month, 0).getDate()
  120. // 每隔一天一个刻度,从第2天开始
  121. for (let day = 2; day <= daysInMonth; day++) {
  122. const monthLeft = (11 - monthOffset) * 200 + 100
  123. const dayOffset = ((day - 1) / daysInMonth) * 200
  124. ticks.push({
  125. left: monthLeft + dayOffset,
  126. date: `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
  127. })
  128. }
  129. }
  130. return ticks
  131. })
  132. // 格式化显示时间
  133. const formatDisplayTime = (dateStr) => {
  134. const [year, month, day] = dateStr.split('-')
  135. return `${year}年${month}月${day}日`
  136. }
  137. // 根据时间计算位置
  138. const calculatePositionByDate = (dateStr) => {
  139. const [year, month, day] = dateStr.split('-').map(Number)
  140. const now = new Date()
  141. const currentYear = now.getFullYear()
  142. const currentMonth = now.getMonth()
  143. // 计算月份偏移(从当前月份往前)- 使用与月份标签相同的逻辑
  144. // 月份标签: new Date(currentYear, currentMonth - i, 1) 其中 i 从 0 到 11
  145. // 所以对于目标日期,我们需要找到对应的 i 值
  146. let monthOffset = -1
  147. for (let i = 0; i <= 11; i++) {
  148. const testDate = new Date(currentYear, currentMonth - i, 1)
  149. if (testDate.getFullYear() === year && testDate.getMonth() + 1 === month) {
  150. monthOffset = i
  151. break
  152. }
  153. }
  154. if (monthOffset === -1 || monthOffset > 11) return 0
  155. // 计算月份位置(从右到左)
  156. const monthLeft = (11 - monthOffset) * 200 + 100
  157. // 计算在月份内的位置,使用与calculateDateByPosition相同的计算逻辑
  158. const daysInMonth = new Date(year, month, 0).getDate()
  159. const dayRatio = (day - 1) / daysInMonth
  160. const dayOffset = dayRatio * 200
  161. return monthLeft + dayOffset
  162. }
  163. // 根据位置计算时间
  164. const calculateDateByPosition = (left) => {
  165. // 计算月份索引(从右到左)
  166. const monthIndex = Math.floor((left - 100) / 200)
  167. const monthOffset = 11 - monthIndex
  168. if (monthOffset < 0 || monthOffset > 11) return ''
  169. const now = new Date()
  170. const currentYear = now.getFullYear()
  171. const currentMonth = now.getMonth()
  172. // 计算目标月份 - 使用与月份标签完全相同的逻辑
  173. // 月份标签: new Date(currentYear, currentMonth - i, 1) 其中 i 从 0 到 11
  174. // 这里 monthOffset 对应月份标签中的 i
  175. const targetDate = new Date(currentYear, currentMonth - monthOffset, 1)
  176. const year = targetDate.getFullYear()
  177. const month = targetDate.getMonth() + 1
  178. const daysInMonth = new Date(year, month, 0).getDate()
  179. // 计算在月份内的位置
  180. const monthLeft = monthIndex * 200 + 100
  181. const dayOffset = left - monthLeft
  182. // 确保dayOffset在有效范围内
  183. if (dayOffset < 0 || dayOffset > 200) return ''
  184. // 计算日期比例(0-1),使用更精确的计算方法
  185. const dayRatio = dayOffset / 200
  186. // 计算日期(1-31),使用四舍五入而不是向下取整
  187. let day = Math.round(dayRatio * daysInMonth) + 1
  188. // 确保日期在有效范围内
  189. day = Math.max(1, Math.min(daysInMonth, day))
  190. return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
  191. }
  192. // 调试函数:验证时间计算
  193. const debugTimeCalculation = (left) => {
  194. const date = calculateDateByPosition(left)
  195. const position = calculatePositionByDate(date)
  196. const difference = Math.abs(left - position)
  197. console.log(`位置: ${left}, 计算时间: ${date}, 反向计算位置: ${position}, 误差: ${difference}`)
  198. return date
  199. }
  200. // 精确调试函数:测试特定位置
  201. const preciseDebug = (left) => {
  202. const date = calculateDateByPosition(left)
  203. const [year, month, day] = date.split('-').map(Number)
  204. const monthIndex = Math.floor((left - 100) / 200)
  205. const monthOffset = 11 - monthIndex
  206. const monthLeft = monthIndex * 200 + 100
  207. const dayOffset = left - monthLeft
  208. const dayRatio = dayOffset / 200
  209. const daysInMonth = new Date(year, month, 0).getDate()
  210. console.log(`=== 精确调试 ===`)
  211. console.log(`输入位置: ${left}`)
  212. console.log(`月份索引: ${monthIndex}, 月份偏移: ${monthOffset}`)
  213. console.log(`月份左边界: ${monthLeft}, 日期偏移: ${dayOffset}`)
  214. console.log(`日期比例: ${dayRatio}, 月份天数: ${daysInMonth}`)
  215. console.log(`计算日期: ${day}, 最终时间: ${date}`)
  216. // 添加反向验证
  217. const reversePosition = calculatePositionByDate(date)
  218. const reverseDate = calculateDateByPosition(reversePosition)
  219. console.log(`反向验证: 位置 ${reversePosition} -> 时间 ${reverseDate}`)
  220. console.log(`一致性检查: ${date === reverseDate ? '✅' : '❌'}`)
  221. return date
  222. }
  223. // 时间差分析函数
  224. const analyzeTimeDifference = (left) => {
  225. console.log(`=== 时间差分析 ===`)
  226. console.log(`分析位置: ${left}`)
  227. // 计算当前时间
  228. const date = calculateDateByPosition(left)
  229. console.log(`计算时间: ${date}`)
  230. // 计算反向位置
  231. const reversePosition = calculatePositionByDate(date)
  232. console.log(`反向位置: ${reversePosition}`)
  233. // 计算位置差
  234. const positionDiff = Math.abs(left - reversePosition)
  235. console.log(`位置差: ${positionDiff}`)
  236. // 分析可能的原因
  237. if (positionDiff > 5) {
  238. console.log(`⚠️ 位置差较大,可能原因:`)
  239. console.log(`1. 日期计算精度问题`)
  240. console.log(`2. 月份边界处理问题`)
  241. console.log(`3. 四舍五入误差`)
  242. }
  243. return { date, reversePosition, positionDiff }
  244. }
  245. // 处理滚动事件
  246. const handleScroll = (e) => {
  247. // 清除之前的定时器
  248. if (scrollTimer) {
  249. clearTimeout(scrollTimer)
  250. }
  251. // 设置新的定时器,防抖处理
  252. scrollTimer = setTimeout(() => {
  253. const scrollLeft = e.detail.scrollLeft
  254. const containerWidth = 750 // 假设容器宽度为750rpx
  255. const centerPosition = containerWidth / 2
  256. // 计算当前中心位置对应的时间
  257. const absoluteLeft = scrollLeft + centerPosition
  258. const date = calculateDateByPosition(absoluteLeft)
  259. if (date && date !== currentTime.value) {
  260. currentTime.value = date
  261. emit('timeChange', date)
  262. console.log('当前选中时间:', date) // 打印格式为YYYY-MM-DD
  263. // 详细调试信息
  264. console.log(`滚动位置: ${scrollLeft}, 中心位置: ${centerPosition}, 绝对位置: ${absoluteLeft}`)
  265. preciseDebug(absoluteLeft)
  266. // 时间差分析
  267. analyzeTimeDifference(absoluteLeft)
  268. }
  269. }, 50) // 减少防抖时间,提高响应性
  270. }
  271. // 处理滚动到底部
  272. const handleScrollToLower = () => {
  273. console.log('滚动到底部')
  274. }
  275. // 处理滚动到顶部
  276. const handleScrollToUpper = () => {
  277. console.log('滚动到顶部')
  278. }
  279. // 监听props变化
  280. watch(() => props.currentDate, (newDate) => {
  281. if (newDate && newDate !== currentTime.value) {
  282. currentTime.value = newDate
  283. }
  284. }, { immediate: true })
  285. // 验证月份标签和位置计算的对应关系
  286. const validateMonthPositionMapping = () => {
  287. console.log('=== 月份位置映射验证 ===')
  288. monthLabels.value.forEach((label, index) => {
  289. const expectedDate = `${label.year}-${label.month.toString().padStart(2, '0')}-01`
  290. const calculatedPosition = calculatePositionByDate(expectedDate)
  291. const reverseDate = calculateDateByPosition(calculatedPosition)
  292. console.log(`月份${index + 1}: ${label.label}`)
  293. console.log(` 标签位置: ${label.left}`)
  294. console.log(` 期望日期: ${expectedDate}`)
  295. console.log(` 计算位置: ${calculatedPosition}`)
  296. console.log(` 反向日期: ${reverseDate}`)
  297. console.log(` 位置一致: ${Math.abs(label.left - calculatedPosition) < 1 ? '✅' : '❌'}`)
  298. console.log(` 日期一致: ${expectedDate === reverseDate ? '✅' : '❌'}`)
  299. console.log('---')
  300. })
  301. }
  302. // 验证月份标签和位置计算的一致性
  303. const validateMonthConsistency = () => {
  304. console.log('=== 月份一致性验证 ===')
  305. monthLabels.value.forEach((label, index) => {
  306. const expectedDate = `${label.year}-${label.month.toString().padStart(2, '0')}-01`
  307. const calculatedPosition = calculatePositionByDate(expectedDate)
  308. const reverseDate = calculateDateByPosition(calculatedPosition)
  309. const difference = Math.abs(label.left - calculatedPosition)
  310. console.log(`月份${index + 1}: ${label.label} (${expectedDate})`)
  311. console.log(` 标签位置: ${label.left}, 计算位置: ${calculatedPosition}, 误差: ${difference}`)
  312. console.log(` 反向验证: ${reverseDate} vs ${expectedDate}`)
  313. console.log(` 一致性: ${reverseDate === expectedDate ? '✅' : '❌'}`)
  314. console.log('---')
  315. })
  316. }
  317. // 验证月份标签位置
  318. const validateMonthLabels = () => {
  319. console.log('=== 月份标签验证 ===')
  320. monthLabels.value.forEach((label, index) => {
  321. const expectedDate = `${label.year}-${label.month.toString().padStart(2, '0')}-01`
  322. const calculatedPosition = calculatePositionByDate(expectedDate)
  323. const difference = Math.abs(label.left - calculatedPosition)
  324. console.log(`月份${index + 1}: ${label.label} (${expectedDate}) -> 标签位置: ${label.left}, 计算位置: ${calculatedPosition}, 误差: ${difference}`)
  325. })
  326. }
  327. // 测试函数:验证时间计算逻辑
  328. const testTimeCalculation = () => {
  329. console.log('=== 时间计算测试 ===')
  330. // 先验证月份位置映射
  331. validateMonthPositionMapping()
  332. // 再验证月份一致性
  333. validateMonthConsistency()
  334. // 再验证月份标签
  335. validateMonthLabels()
  336. // 测试几个关键位置(从右到左)
  337. const testPositions = [
  338. 2300, // 最右边(当前月份第一天)
  339. 2100, // 当前月份中间
  340. 1900, // 上个月第一天
  341. 1700, // 上上个月第一天
  342. 1500, // 三个月前第一天
  343. 1300, // 四个月前第一天
  344. 1100, // 五个月前第一天
  345. 900, // 六个月前第一天
  346. 700, // 七个月前第一天
  347. 500, // 八个月前第一天
  348. 300, // 九个月前第一天
  349. 100 // 最左边(一年前)
  350. ]
  351. testPositions.forEach((pos, index) => {
  352. const date = calculateDateByPosition(pos)
  353. const reversePos = calculatePositionByDate(date)
  354. const difference = Math.abs(pos - reversePos)
  355. console.log(`测试${index + 1}: 位置 ${pos} -> 时间 ${date} -> 反向位置 ${reversePos}, 误差: ${difference}`)
  356. // 如果误差太大,输出详细调试信息
  357. if (difference > 10) {
  358. console.log('--- 误差过大,详细调试 ---')
  359. preciseDebug(pos)
  360. }
  361. })
  362. }
  363. // 显示月份对比信息
  364. const showMonthComparison = () => {
  365. console.log('=== 月份对比信息 ===')
  366. const now = new Date()
  367. const currentYear = now.getFullYear()
  368. const currentMonth = now.getMonth() + 1
  369. console.log(`当前时间: ${currentYear}年${currentMonth}月`)
  370. console.log(`月份标签数量: ${monthLabels.value.length}`)
  371. // 显示所有月份标签
  372. monthLabels.value.forEach((label, index) => {
  373. console.log(`标签${index + 1}: ${label.label} (${label.year}-${label.month}) 位置: ${label.left}`)
  374. // 计算该月份第一天对应的位置
  375. const firstDayDate = `${label.year}-${label.month.toString().padStart(2, '0')}-01`
  376. const calculatedPosition = calculatePositionByDate(firstDayDate)
  377. const reverseDate = calculateDateByPosition(calculatedPosition)
  378. console.log(` 计算位置: ${calculatedPosition}, 反向日期: ${reverseDate}`)
  379. console.log(` 位置一致: ${Math.abs(label.left - calculatedPosition) < 1 ? '✅' : '❌'}`)
  380. console.log(` 日期一致: ${firstDayDate === reverseDate ? '✅' : '❌'}`)
  381. })
  382. }
  383. // 显示当前月份信息
  384. const showCurrentMonthInfo = () => {
  385. const now = new Date()
  386. const currentYear = now.getFullYear()
  387. const currentMonth = now.getMonth() + 1
  388. console.log('=== 当前月份信息 ===')
  389. console.log(`当前年份: ${currentYear}, 当前月份: ${currentMonth}`)
  390. console.log(`月份标签数量: ${monthLabels.value.length}`)
  391. // 显示所有月份标签
  392. monthLabels.value.forEach((label, index) => {
  393. console.log(`标签${index + 1}: ${label.label} (${label.year}-${label.month}) 位置: ${label.left}`)
  394. })
  395. return { year: currentYear, month: currentMonth }
  396. }
  397. // 显示当前日期信息
  398. const showCurrentDateInfo = () => {
  399. const now = new Date()
  400. const currentYear = now.getFullYear()
  401. const currentMonth = now.getMonth() + 1
  402. const currentDay = now.getDate()
  403. console.log('=== 当前日期信息 ===')
  404. console.log(`当前日期: ${currentYear}-${currentMonth.toString().padStart(2, '0')}-${currentDay.toString().padStart(2, '0')}`)
  405. console.log(`当前年份: ${currentYear}, 当前月份: ${currentMonth}, 当前日期: ${currentDay}`)
  406. // 计算当前日期在时间轴上的位置
  407. const currentDateStr = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-${currentDay.toString().padStart(2, '0')}`
  408. const currentPosition = calculatePositionByDate(currentDateStr)
  409. console.log(`当前日期在时间轴上的位置: ${currentPosition}`)
  410. return currentDateStr
  411. }
  412. // 测试每天刻度的准确性
  413. const testDailyTicks = () => {
  414. console.log('=== 每天刻度测试 ===')
  415. const now = new Date()
  416. const currentYear = now.getFullYear()
  417. const currentMonth = now.getMonth()
  418. // 测试当前月份的几个关键日期
  419. const testDays = [1, 5, 10, 15, 20, 25, 30]
  420. testDays.forEach(day => {
  421. const testDate = new Date(currentYear, currentMonth, day)
  422. const year = testDate.getFullYear()
  423. const month = testDate.getMonth() + 1
  424. const daysInMonth = new Date(year, month, 0).getDate()
  425. // 只测试有效的日期
  426. if (day <= daysInMonth) {
  427. const dateStr = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
  428. const position = calculatePositionByDate(dateStr)
  429. const reverseDate = calculateDateByPosition(position)
  430. const isAccurate = dateStr === reverseDate
  431. console.log(`日期: ${dateStr} -> 位置: ${position} -> 反向: ${reverseDate} ${isAccurate ? '✅' : '❌'}`)
  432. }
  433. })
  434. }
  435. // 简单测试:验证月份对应关系
  436. const simpleTest = () => {
  437. console.log('=== 简单测试 ===')
  438. // 测试当前月份
  439. const now = new Date()
  440. const currentYear = now.getFullYear()
  441. const currentMonth = now.getMonth() + 1
  442. const currentDate = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-01`
  443. console.log(`当前月份: ${currentMonth}月`)
  444. console.log(`当前日期: ${currentDate}`)
  445. // 计算位置
  446. const position = calculatePositionByDate(currentDate)
  447. console.log(`计算位置: ${position}`)
  448. // 反向计算时间
  449. const reverseDate = calculateDateByPosition(position)
  450. console.log(`反向计算: ${reverseDate}`)
  451. // 验证一致性
  452. const isConsistent = currentDate === reverseDate
  453. console.log(`一致性: ${isConsistent ? '✅' : '❌'}`)
  454. return isConsistent
  455. }
  456. // 测试3个月差问题
  457. const testThreeMonthDifference = () => {
  458. console.log('=== 3个月差问题测试 ===')
  459. const now = new Date()
  460. const currentYear = now.getFullYear()
  461. const currentMonth = now.getMonth() + 1
  462. console.log(`当前时间: ${currentYear}年${currentMonth}月`)
  463. // 测试当前月份的位置
  464. const currentMonthDate = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-01`
  465. const currentPosition = calculatePositionByDate(currentMonthDate)
  466. const currentReverseDate = calculateDateByPosition(currentPosition)
  467. console.log(`当前月份: ${currentMonthDate}`)
  468. console.log(`计算位置: ${currentPosition}`)
  469. console.log(`反向计算: ${currentReverseDate}`)
  470. console.log(`当前月份一致性: ${currentMonthDate === currentReverseDate ? '✅' : '❌'}`)
  471. // 测试3个月前的月份
  472. const threeMonthsAgo = new Date(currentYear, currentMonth - 4, 1) // 减4是因为getMonth()返回0-11
  473. const threeMonthsAgoYear = threeMonthsAgo.getFullYear()
  474. const threeMonthsAgoMonth = threeMonthsAgo.getMonth() + 1
  475. const threeMonthsAgoDate = `${threeMonthsAgoYear}-${threeMonthsAgoMonth.toString().padStart(2, '0')}-01`
  476. const threeMonthsPosition = calculatePositionByDate(threeMonthsAgoDate)
  477. const threeMonthsReverseDate = calculateDateByPosition(threeMonthsPosition)
  478. console.log(`3个月前: ${threeMonthsAgoDate}`)
  479. console.log(`计算位置: ${threeMonthsPosition}`)
  480. console.log(`反向计算: ${threeMonthsReverseDate}`)
  481. console.log(`3个月前一致性: ${threeMonthsAgoDate === threeMonthsReverseDate ? '✅' : '❌'}`)
  482. // 检查月份标签
  483. console.log('月份标签检查:')
  484. monthLabels.value.forEach((label, index) => {
  485. if (label.year === threeMonthsAgoYear && label.month === threeMonthsAgoMonth) {
  486. console.log(`找到3个月前标签: ${label.label} 位置: ${label.left}`)
  487. console.log(`位置差: ${Math.abs(label.left - threeMonthsPosition)}`)
  488. }
  489. })
  490. }
  491. // 组件挂载后初始化
  492. onMounted(() => {
  493. // 运行简单测试
  494. simpleTest()
  495. // 测试每天刻度
  496. testDailyTicks()
  497. // 测试3个月差问题
  498. testThreeMonthDifference()
  499. // 显示月份对比信息
  500. showMonthComparison()
  501. // 显示当前月份信息
  502. showCurrentMonthInfo()
  503. // 显示当前日期信息
  504. showCurrentDateInfo()
  505. // 运行测试
  506. testTimeCalculation()
  507. // 如果没有传入当前日期,使用今天
  508. if (!props.currentDate) {
  509. const today = new Date()
  510. const year = today.getFullYear()
  511. const month = (today.getMonth() + 1).toString().padStart(2, '0')
  512. const day = today.getDate().toString().padStart(2, '0')
  513. const todayStr = `${year}-${month}-${day}`
  514. currentTime.value = todayStr
  515. emit('timeChange', todayStr)
  516. // 设置默认滚动到最右边(当前日期位置)
  517. nextTick(() => {
  518. // 计算需要滚动的距离,让最右边(当前月份)显示在屏幕中间
  519. // 总宽度2400rpx,屏幕宽度750rpx,要让最右边显示在中间
  520. scrollLeft.value = 2400 - 750
  521. })
  522. } else {
  523. // 如果有传入日期,初始化时间
  524. currentTime.value = props.currentDate
  525. // 设置默认滚动到传入日期位置
  526. nextTick(() => {
  527. const targetPosition = calculatePositionByDate(props.currentDate)
  528. // 计算需要滚动的距离,让目标日期显示在屏幕中间
  529. scrollLeft.value = targetPosition - 375
  530. })
  531. }
  532. })
  533. </script>
  534. <style lang="scss" scoped>
  535. .time-scale-container {
  536. width: 100%;
  537. position: relative;
  538. margin-bottom: 20rpx;
  539. padding: 0 20rpx;
  540. }
  541. .time-scale-scroll {
  542. position: relative;
  543. width: 100%;
  544. height: 120rpx;
  545. }
  546. .time-scale-wrapper {
  547. width: 100%;
  548. height: 100%;
  549. white-space: nowrap;
  550. }
  551. .time-scale {
  552. display: inline-block;
  553. min-width: 2400rpx; // 12个月 * 200rpx
  554. height: 100%;
  555. position: relative;
  556. padding: 0 375rpx; // 左右各留一半屏幕宽度,确保最左和最右都在中间
  557. }
  558. .month-labels {
  559. position: relative;
  560. height: 40rpx;
  561. }
  562. .month-label {
  563. position: absolute;
  564. top: 0;
  565. transform: translateX(-50%);
  566. font-size: 24rpx;
  567. color: #777777;
  568. font-weight: 500;
  569. }
  570. .timeline {
  571. position: relative;
  572. height: 80rpx;
  573. margin-top: 20rpx;
  574. &::before {
  575. content: '';
  576. position: absolute;
  577. top: 50%;
  578. left: 0;
  579. right: 0;
  580. height: 2rpx;
  581. background: rgba(136, 136, 136, 0.1);
  582. transform: translateY(-50%);
  583. }
  584. }
  585. .month-dot {
  586. position: absolute;
  587. top: 50%;
  588. width: 14rpx;
  589. height: 14rpx;
  590. background: #777777;
  591. border-radius: 50%;
  592. transform: translate(-50%, -50%);
  593. z-index: 5;
  594. }
  595. .tick-mark {
  596. position: absolute;
  597. top: 50%;
  598. width: 2rpx;
  599. height: 8rpx;
  600. background: rgba(136, 136, 136, 0.3);
  601. transform: translate(-50%, -50%);
  602. }
  603. .center-line {
  604. position: absolute;
  605. left: 50%;
  606. top: 0;
  607. bottom: 0;
  608. width: 2rpx;
  609. background: #2199F8;
  610. z-index: 15;
  611. transform: translateX(-50%);
  612. pointer-events: none;
  613. }
  614. .current-time {
  615. text-align: center;
  616. margin-top: 16rpx;
  617. font-size: 28rpx;
  618. color: #2199F8;
  619. font-weight: 500;
  620. }
  621. </style>