Journal.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. import JournalCover from "@/features/journal/components/journal_cover";
  2. import { getJournals } from "@/services/health";
  3. import { View, Text, Image, ScrollView } from "@tarojs/components";
  4. import { useEffect, useState } from "react";
  5. import './Journal.scss'
  6. import dayjs from "dayjs";
  7. import { rpxToPx } from "@/utils/tools";
  8. import { jumpPage } from "@/features/trackTimeDuration/hooks/Common";
  9. import NewHeader, { NewHeaderType } from "@/_health/components/new_header";
  10. import Taro, { useRouter } from "@tarojs/taro";
  11. import StickyDateList from "@/_health/components/sticky_date_list";
  12. import { MainColorType } from "@/context/themes/color";
  13. import ListFooter from "@/_health/components/list_footer";
  14. import TimeTitleDesc from "@/_health/components/time_title_desc";
  15. import { TimeFormatter } from "@/utils/time_format";
  16. import { useTranslation } from "react-i18next";
  17. import NewButton, { NewButtonType } from "@/_health/base/new_button";
  18. import { getScenario, getThemeColor } from "@/features/health/hooks/health_hooks";
  19. import { useSelector } from "react-redux";
  20. import { IconClose } from "@/components/basic/Icons";
  21. import TargetProgress from "@/_health/components/target_progress";
  22. import NoRecord from "@/_health/components/no_record";
  23. import TimelineDate from "@/_health/components/timeline_date";
  24. let myScrollTop = 0
  25. let useRoute;
  26. let useNavigation;
  27. let scenario = '';
  28. if (process.env.TARO_ENV == 'rn') {
  29. useRoute = require("@react-navigation/native").useRoute
  30. useNavigation = require("@react-navigation/native").useNavigation
  31. }
  32. export default function Journal() {
  33. const health = useSelector((state: any) => state.health);
  34. const [journals, setJournals] = useState<any>([])
  35. const [isPulling, setIsPulling] = useState(false)
  36. const [itemLayouts, setItemLayouts] = useState<any>([])
  37. const [itemHeights, setItemHeights] = useState<any>([])
  38. const [showDate, setShowDate] = useState(false)
  39. const [date, setDate] = useState('')
  40. const [loaded, setLoaded] = useState(false)
  41. const [page, setPage] = useState(1)
  42. const [total, setTotal] = useState(0)
  43. const [loading, setLoading] = useState(false)
  44. const { t } = useTranslation()
  45. const [pageTop, setPageTop] = useState(0)
  46. let router
  47. let navigation;
  48. if (useNavigation) {
  49. navigation = useNavigation()
  50. }
  51. if (process.env.TARO_ENV == 'rn') {
  52. router = useRoute()
  53. }
  54. else {
  55. router = useRouter()
  56. }
  57. const [window, setWindow] = useState(router.params.type ?? '')
  58. const items = ['FAST', 'SLEEP', 'EAT', 'ACTIVE']
  59. const [showTip, setShowTip] = useState(router.params.show_tip == '1')
  60. const [showBadge, setShowBadge] = useState(router.params.show_badge == '1')
  61. // useEffect(() => {
  62. // }, [])
  63. useEffect(() => {
  64. getJounalsData(1)
  65. }, [window])
  66. useEffect(() => {
  67. console.log('last show status', showDate)
  68. }, [showDate])
  69. useEffect(() => {
  70. if (journals.length > 0) {
  71. setTimeout(() => {
  72. measureItemLayouts()
  73. }, 500)
  74. }
  75. }, [journals])
  76. function more() {
  77. if (loading) return;
  78. if (total == journals.length) return;
  79. var index = page;
  80. index++;
  81. setPage(index)
  82. getJounalsData(index)
  83. }
  84. function getJounalsData(index = 1) {
  85. setLoading(true)
  86. setPage(index)
  87. var params: any = {
  88. page: index,
  89. limit: 10,
  90. }
  91. params.window = window
  92. getJournals(params).then(res => {
  93. setLoaded(true)
  94. let list = (res as any).data
  95. // list.forEach(element => {
  96. // let array: any = []
  97. // element.windows.map(item => {
  98. // item.events.map(event => {
  99. // event.moments && event.moments.map(moment => {
  100. // if (moment.media && moment.media.length > 0) {
  101. // moment.media.map(media => {
  102. // array.push(media.url)
  103. // })
  104. // }
  105. // })
  106. // })
  107. // })
  108. // element.imgs = array
  109. // });
  110. setLoading(false)
  111. if (index == 1) {
  112. setTotal((res as any).total)
  113. setJournals(list)
  114. setIsPulling(false)
  115. }
  116. else {
  117. setJournals([...journals, ...list])
  118. }
  119. }).catch(e => {
  120. setLoading(false)
  121. })
  122. }
  123. function measureItemLayouts() {
  124. const query = Taro.createSelectorQuery()
  125. journals.forEach((item, index) => {
  126. query.select(`#history-${index}`).boundingClientRect()
  127. });
  128. query.exec((res) => {
  129. var layouts: any = []
  130. var heights: any = []
  131. res.forEach((rect, index) => {
  132. if (rect) {
  133. layouts[index] = rect.top + myScrollTop
  134. heights[index] = rect.height
  135. }
  136. });
  137. setItemLayouts(layouts)
  138. setItemHeights(heights)
  139. })
  140. }
  141. function onScroll(e) {
  142. var top = e.detail.scrollTop - e.detail.deltaY
  143. myScrollTop = e.detail.scrollTop
  144. setPageTop(top)
  145. if (top > 60) {
  146. Taro.setNavigationBarTitle({
  147. title: pageTitle()
  148. })
  149. }
  150. else {
  151. Taro.setNavigationBarTitle({
  152. title: ''
  153. })
  154. }
  155. if (itemLayouts.length > 0) {
  156. var i = -1
  157. var dt = ''
  158. journals.forEach((item, index) => {
  159. if (top >= itemLayouts[index] - 50) {
  160. i = index
  161. dt = global.language=='en'?dayjs(item.timestamp).format('MMMM YYYY'):dayjs(item.timestamp).format('YYYY年M月')
  162. }
  163. })
  164. setShowDate(i != -1)
  165. setDate(dt)
  166. }
  167. else {
  168. setShowDate(false)
  169. setDate('')
  170. }
  171. // if (itemLayouts.length > 0 && itemLayouts[0] > top) {
  172. // setShowDate(false)
  173. // setDate('')
  174. // console.log(itemLayouts[0], showDate, date, top, e.detail.deltaY)
  175. // }
  176. // else {
  177. // console.log(itemLayouts[0], showDate, date, top, e.detail.deltaY)
  178. // }
  179. }
  180. function getTitle(item) {
  181. if (item.title) {
  182. return item.title;
  183. }
  184. if (item.moments) {
  185. return item.moments[0].title
  186. }
  187. return ''
  188. }
  189. function journalItem(window, index) {
  190. var array: any = [];
  191. window.events.map(event => {
  192. event.moments && event.moments.map(moment => {
  193. if (moment.media && moment.media.length > 0) {
  194. moment.media.map(media => {
  195. array.push(media.url)
  196. })
  197. }
  198. })
  199. })
  200. return <View style={{ display: 'flex', flexDirection: 'row', marginBottom: rpxToPx(12), overflow: 'hidden', flexShrink: 0 }} key={index}>
  201. {
  202. array.length > 0 && <JournalCover imgs={array} />
  203. }
  204. <View style={{
  205. display: 'flex',
  206. flexDirection: 'column',
  207. // flex: 1,
  208. overflow: 'hidden',
  209. backgroundColor: array.length == 0 ? '#fafafa' : 'transparent',
  210. paddingLeft: array.length == 0 ? rpxToPx(20) : 0,
  211. paddingRight: array.length == 0 ? rpxToPx(20) : 0,
  212. paddingTop: array.length == 0 ? rpxToPx(12) : 0,
  213. paddingBottom: array.length == 0 ? rpxToPx(12) : 0,
  214. marginRight: rpxToPx(40),
  215. flexShrink: 0
  216. }}>
  217. {
  218. window.events.map((item2, index2) => {
  219. if (item2.moments && item2.moments[0].type == 'PIC') return <View key={index2 * 1000} />
  220. return <TimeTitleDesc
  221. key={index2 * 1000}
  222. className="line1"
  223. style={{ width: array.length > 0 ? rpxToPx(350) : rpxToPx(512) }}
  224. timeObj={item2.time}
  225. time={dayjs(item2.time.timestamp).format('HH:mm')}
  226. title={getTitle(item2)}
  227. desc={item2.moments && item2.moments.length > 0 ? item2.moments[0].description : ''}
  228. />
  229. })
  230. }
  231. </View>
  232. </View>
  233. }
  234. function historyYear(index) {
  235. var showDate = false;
  236. var dateStr2: any = ''
  237. if (index == 0) {
  238. var currentDate = global.language == 'en' ? dayjs(journals[index].timestamp).format('YYYY') : dayjs(journals[index].timestamp).format('YYYY年')
  239. var now = global.language == 'en' ? dayjs().format('YYYY') : dayjs().format('YYYY年')
  240. if (currentDate != now) {
  241. showDate = true
  242. dateStr2 = currentDate
  243. }
  244. }
  245. else {
  246. var currentDate = global.language == 'en' ? dayjs(journals[index].timestamp).format('YYYY') : dayjs(journals[index].timestamp).format('YYYY年')
  247. var now = global.language == 'en' ? dayjs(journals[index - 1].timestamp).format('YYYY') : dayjs(journals[index - 1].timestamp).format('YYYY年')
  248. if (currentDate != now) {
  249. showDate = true
  250. dateStr2 = currentDate
  251. }
  252. }
  253. if (showDate) {
  254. return <View className="history_year_month h42 bold" style={{ marginBottom: rpxToPx(60) }}>{dateStr2}</View>
  255. }
  256. return <View />
  257. }
  258. function historyDate(item, index) {
  259. if (index == 0) {
  260. if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
  261. return '今天'
  262. }
  263. if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
  264. return '昨天'
  265. }
  266. return dayjs(item.timestamp).format('DD')
  267. }
  268. if (dayjs(item.timestamp).format('MM-DD') ==
  269. dayjs(journals[index - 1].timestamp).format('MM-DD')) {
  270. return ''
  271. }
  272. if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
  273. return '今天'
  274. }
  275. if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
  276. return '昨天'
  277. }
  278. return dayjs(item.timestamp).format('DD')
  279. }
  280. function historyMonth(item, index) {
  281. if (index == 0) {
  282. if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
  283. return ''
  284. }
  285. if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
  286. return ''
  287. }
  288. return dayjs(item.timestamp).format('MMM')
  289. }
  290. if (dayjs(item.timestamp).format('MM-DD') ==
  291. dayjs(journals[index - 1].timestamp).format('MM-DD')) {
  292. return ''
  293. }
  294. if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
  295. return ''
  296. }
  297. if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
  298. return ''
  299. }
  300. return dayjs(item.timestamp).format('MMM')
  301. }
  302. function journalCellText(index) {
  303. var windows = journals[index].windows
  304. var array: any = []
  305. windows.map(window => {
  306. window.events.map(event => {
  307. event.moments && event.moments.map(moment => {
  308. array.push({
  309. title: moment.title,
  310. description: moment.description
  311. })
  312. })
  313. })
  314. })
  315. return array
  316. if (array.length > 0) {
  317. return <View style={{ display: 'flex', flexDirection: 'column' }}>
  318. {
  319. array.map((item, index) => {
  320. if (index >= 3) return <View key={index * 10000} />
  321. return <TimeTitleDesc key={index * 10000}
  322. time=''
  323. title={item.title}
  324. desc={item.description}
  325. />
  326. })
  327. }
  328. </View>
  329. }
  330. return <View />
  331. }
  332. function journalCell(item, index) {
  333. // if (item.show_ring) {
  334. // if (item.windows) {
  335. // return <View key={index}>{
  336. // item.windows.map((temp, i) => {
  337. // return <TargetProgress key={i * 1000} showLine={i < item.windows.length - 1 ? true : false}
  338. // color={getThemeColor(temp.window)}
  339. // showRing={true}
  340. // desc={temp.description}
  341. // icon={
  342. // null
  343. // }
  344. // canvasId={`${temp.window}${temp.window_range.start_timestamp}${index}`}
  345. // startTimestamp={temp.window_range.start_timestamp}
  346. // endTimerstamp={temp.window_range.end_timestamp}
  347. // />
  348. // })
  349. // }</View>
  350. // }
  351. // return <View key={index} />
  352. // }
  353. return <View style={{ display: 'flex', flexDirection: 'column' }}>
  354. {
  355. (item.pics.length > 0 || item.texts.length > 0) && <View style={{ display: 'flex', flexDirection: 'row', marginBottom: rpxToPx(12), overflow: 'hidden', flexShrink: 0 }} key={index}>
  356. {
  357. item.pics.length > 0 && <View onClick={(e) => {
  358. if (process.env.TARO_ENV == 'weapp') {
  359. e.stopPropagation()
  360. }
  361. Taro.previewImage({
  362. current: item.pics[0],
  363. urls: item.pics
  364. })
  365. }}>
  366. <JournalCover imgs={item.pics} />
  367. </View>
  368. }
  369. <View style={{
  370. display: 'flex',
  371. flexDirection: 'column',
  372. // flex: 1,
  373. overflow: 'hidden',
  374. backgroundColor: item.pics.length == 0 ? '#fafafa' : 'transparent',
  375. paddingLeft: item.pics.length == 0 ? rpxToPx(20) : 0,
  376. paddingRight: item.pics.length == 0 ? rpxToPx(20) : 0,
  377. paddingTop: item.pics.length == 0 ? rpxToPx(12) : 0,
  378. paddingBottom: item.pics.length == 0 ? rpxToPx(12) : 0,
  379. marginRight: rpxToPx(40),
  380. flexShrink: 0
  381. }}>
  382. {
  383. item.texts.map((item2, index2) => {
  384. return <TimeTitleDesc
  385. key={index2 * 1000}
  386. className='line1'//{item.pics.length == 0 ? 'line2' : 'line3'}
  387. style={{ width: item.pics.length > 0 ? rpxToPx(350) : rpxToPx(512) }}
  388. time=''
  389. title={item2.title}
  390. desc={item2.description}
  391. />
  392. })
  393. }
  394. {/* <Text style={{backgroundColor:'pink'}}>...</Text> */}
  395. </View>
  396. </View>
  397. }
  398. {
  399. item.show_ring && item.windows && <View key={index}>{
  400. item.windows.map((temp, i) => {
  401. return <TargetProgress key={i * 1000} showLine={i < item.windows.length - 1 ? true : false}
  402. color={getThemeColor(temp.window)}
  403. showRing={true}
  404. desc={temp.description}
  405. icon={
  406. null
  407. }
  408. canvasId={`${temp.window}${temp.window_range.start_timestamp}${index}`}
  409. startTimestamp={temp.window_range.start_timestamp}
  410. endTimerstamp={temp.window_range.end_timestamp}
  411. />
  412. })
  413. }</View>
  414. }
  415. </View>
  416. }
  417. function pageTitle() {
  418. if (router.params.type == 'EAT') {
  419. return t('health.title_food_journal')
  420. }
  421. else if (router.params.type == 'ACTIVE') {
  422. return t('health.title_active_journal')
  423. }
  424. return t('health.title_journal')
  425. }
  426. function pageYear(){
  427. if (!loaded) return ''
  428. if (journals.length==0) return global.language == 'en'?dayjs().format('YYYY'):dayjs().format('YYYY年')
  429. if (journals[0].timestamp) return global.language == 'en'?dayjs(journals[0].timestamp).format('YYYY'):dayjs(journals[0].timestamp).format('YYYY年')
  430. return ''
  431. }
  432. function markDoneTip() {
  433. if (health.mode != 'EAT' && health.mode != 'ACTIVE') return
  434. var scenario = getScenario(health.windows, health.mode)
  435. if (scenario.status != 'OG') return
  436. if (!showTip) return
  437. var strTitle = ''
  438. if (scenario.archive_timestamp) {
  439. var today = new Date().getDate()
  440. var date = new Date(scenario.archive_timestamp).getDate()
  441. if (today == date) {
  442. strTitle = t('health.tonight_at', { time: dayjs(scenario.archive_timestamp).format('HH:mm') })
  443. }
  444. else {
  445. strTitle = t('health.tomorrow_at', { time: dayjs(scenario.archive_timestamp).format('HH:mm') })
  446. }
  447. }
  448. return <View className="mark_done_tip" style={{
  449. backgroundColor: getThemeColor(health.mode) + '1A'
  450. }}>
  451. <View style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
  452. <Text className="h28 bold" style={{ color: getThemeColor(health.mode) }}>{strTitle}</Text>
  453. <Text className="h24 bold"><Text style={{ fontWeight: 'normal' }}>{t('health.detail_complete_tip_head')}</Text>{t('health.detail_complete_tip_end')}</Text>
  454. </View>
  455. <NewButton type={NewButtonType.img} btnStyle={{
  456. height: rpxToPx(32),
  457. width: rpxToPx(32)
  458. }} onClick={() => {
  459. setShowTip(false)
  460. if (health.mode == 'EAT') {
  461. global.hideEatArchiveTip = true
  462. }
  463. else {
  464. global.hideActiveArchiveTip = true
  465. }
  466. }}>
  467. <IconClose color={MainColorType.g03} width={rpxToPx(32)} height={rpxToPx(32)} />
  468. </NewButton>
  469. </View>
  470. }
  471. if (!loaded) return <View />
  472. // return <recycle-view
  473. // batch={true}
  474. // id="recycleId"
  475. // enableBackToTop
  476. // refresherEnabled
  477. // upperThreshold={70}
  478. // lowerThreshold={80}
  479. // refresherBackground={MainColorType.g05}
  480. // onRefresherRefresh={() => {
  481. // setIsPulling(true)
  482. // getJounalsData(1)
  483. // }}
  484. // refresherTriggered={isPulling}
  485. // onScroll={onScroll}
  486. // onScrollToLower={() => {
  487. // more()
  488. // }}
  489. // >
  490. // <View>
  491. // <NewHeader type={NewHeaderType.left} title={pageTitle()} />
  492. // {
  493. // markDoneTip()
  494. // }
  495. // </View>
  496. // {
  497. // journals.map((item, index) => {
  498. // return <recycle-item key={index}><View key={index}>
  499. // {
  500. // historyYear(index)
  501. // }
  502. // <View className="history_item2" id={`history-${index}`} onClick={() => {
  503. // jumpPage('/pages/account/JournalDetail?date=' + item.date + '&window=' + window) //JSON.stringify(item))
  504. // }}>
  505. // <View className="cell_date" >
  506. // <View className="h42 bold" style={{ lineHeight: rpxToPx(60) + 'px' }}>{historyDate(item, index)}</View>
  507. // <View className="h24 bold" style={{ marginLeft: rpxToPx(6), marginTop: rpxToPx(13), lineHeight: rpxToPx(47) + 'px' }}>{historyMonth(item, index)}</View>
  508. // </View>
  509. // <View style={{ display: 'flex', flexDirection: 'column', flex: 1, width: rpxToPx(552), }}>
  510. // {
  511. // journalCell(item, index)
  512. // }
  513. // </View>
  514. // </View></View>
  515. // </recycle-item>
  516. // })
  517. // }
  518. // <View>长列表后面的内容</View>
  519. // </recycle-view>
  520. function itemData(index,item) {
  521. if (itemLayouts.length >= index + 1 && index > 5) {
  522. if (Math.abs(itemLayouts[index] - pageTop) > 2500) {
  523. return <View style={{ height: itemHeights[index] }} id={`history-${index}`}>
  524. {/* {index} */}
  525. </View>
  526. }
  527. }
  528. return <View className="history_item2" id={`history-${index}`} onClick={() => {
  529. jumpPage('/pages/account/JournalDetail?date=' + item.date + '&window=' + window) //JSON.stringify(item))
  530. }}>
  531. <TimelineDate
  532. timestamp={item.timestamp}
  533. pre_timestamp={index>0?journals[index - 1].timestamp:null}
  534. isJournal
  535. />
  536. {/* <View className="cell_date" >
  537. <View className="h42 bold" style={{ lineHeight: rpxToPx(60) + 'px' }}>{historyDate(item, index)}</View>
  538. <View className="h24 bold" style={{ marginLeft: rpxToPx(6), marginTop: rpxToPx(13), lineHeight: rpxToPx(47) + 'px' }}>{historyMonth(item, index)}</View>
  539. </View> */}
  540. <View style={{ display: 'flex', flexDirection: 'column', flex: 1, width: rpxToPx(552), }}>
  541. {
  542. journalCell(item, index)
  543. }
  544. </View>
  545. </View>
  546. }
  547. return <StickyDateList onRefresherRefresh={() => {
  548. setIsPulling(true)
  549. getJounalsData(1)
  550. }} isPulling={isPulling}
  551. onScroll={onScroll}
  552. showDate={showDate}
  553. date={date}
  554. loadMore={() => {
  555. more()
  556. }}
  557. ><View style={{ display: 'flex', flexDirection: 'column', minHeight: rpxToPx(464), backgroundColor: '#f5f5f5' }}>
  558. <NewHeader type={NewHeaderType.left} title={pageYear()} whiteBg/>
  559. {
  560. markDoneTip()
  561. }
  562. <View style={{ minHeight: '100vh', backgroundColor: '#fff', paddingTop: rpxToPx(60) }}>
  563. {
  564. journals.map((item, index) => {
  565. return <View key={index}>
  566. {
  567. historyYear(index)
  568. }
  569. {
  570. itemData(index,item)
  571. }
  572. </View>
  573. })
  574. }
  575. {
  576. loaded && journals.length == 0 && <NoRecord />
  577. }
  578. </View>
  579. <ListFooter noMore={(journals.length > 0) && (total == journals.length)} loading={loading} />
  580. </View></StickyDateList>
  581. }