RelicList.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. <template>
  2. <div class="relic-list">
  3. <button
  4. class="return"
  5. @click="fakeBack"
  6. />
  7. <el-cascader
  8. v-model="cascaderValue"
  9. :options="cameraTree"
  10. :show-all-levels="false"
  11. :props="{
  12. expandTrigger: 'hover',
  13. }"
  14. />
  15. <div class="search-ui">
  16. <input
  17. v-model.trim="keyword"
  18. type="text"
  19. placeholder="请输入要搜索内容"
  20. @keydown.enter="onSearch"
  21. >
  22. <button
  23. class="search"
  24. />
  25. </div>
  26. <div
  27. class="the-list"
  28. :class="{
  29. scene1: cascaderValue[0] === '0',
  30. scene2: cascaderValue[0] === '1',
  31. scene3: cascaderValue[0] === '2',
  32. }"
  33. >
  34. <div
  35. ref="listEl"
  36. class="content-wrap"
  37. @scroll="handleScroll"
  38. >
  39. <div
  40. class="first-item"
  41. :class="{
  42. scene1: cascaderValue[0] === '0',
  43. scene2: cascaderValue[0] === '1',
  44. scene3: cascaderValue[0] === '2',
  45. }"
  46. />
  47. <div
  48. v-for="(item, idx) in relicData"
  49. :key="idx"
  50. class="relic-item"
  51. :class="{
  52. isScene1: item.sceneIdx === 0,
  53. isScene2: item.sceneIdx === 1,
  54. isScene3: item.sceneIdx === 2,
  55. isOdd: (idx) % 2 === 0,
  56. isEven: (idx) % 2 === 1,
  57. }"
  58. @click="onClickItem(item.idx)"
  59. >
  60. <div
  61. class="name"
  62. :class="{
  63. wide: item['名称'].replace(/<br\s*\/?>/g, '').length > 9,
  64. smallFontSize: item['名称'].replace(/<br\s*\/?>/g, '').length > 17,
  65. }"
  66. :title="item['名称']"
  67. v-html="item['名称']"
  68. />
  69. <img
  70. class=""
  71. :src="getRelicThumbUrl(idx)"
  72. alt=""
  73. draggable="false"
  74. >
  75. </div>
  76. </div>
  77. </div>
  78. </div>
  79. <div style="height: 100vh;" />
  80. </template>
  81. <script setup>
  82. /**
  83. * todo: 自动恢复上次滚动位置
  84. */
  85. import { ref, computed, watch, onMounted, nextTick, inject } from "vue"
  86. import { useRoute, useRouter } from "vue-router"
  87. import { useStore } from "vuex"
  88. import useSmoothSwipe from '@/useFunctions/useSmoothSwipe.js'
  89. import { useElementSize, useWindowSize } from '@vueuse/core'
  90. import { debounce } from "lodash"
  91. const fakeBack = inject('fakeBack')
  92. const $isMobile = inject('$isMobile')
  93. const SCROLL_KEY = 'relicListScrollLeft'
  94. const route = useRoute()
  95. const router = useRouter()
  96. const store = useStore()
  97. const { height: windowHeight } = useWindowSize()
  98. const cascaderValueInit = (route.query.sceneIdx && route.query.cameraIdx) ? [route.query.sceneIdx, route.query.cameraIdx] : ['0', '0']
  99. const cascaderValue = ref(cascaderValueInit)
  100. const cameraTree = ref([
  101. {
  102. value: '0',
  103. label: '天下大都',
  104. children: [
  105. {
  106. value: '0',
  107. label: '帝都',
  108. },
  109. {
  110. value: '1',
  111. label: '宫阙',
  112. },
  113. {
  114. value: '2',
  115. label: '览胜',
  116. },
  117. ]
  118. },
  119. {
  120. value: '1',
  121. label: '河润大都',
  122. children: [
  123. {
  124. value: '0',
  125. label: '河畅',
  126. },
  127. {
  128. value: '1',
  129. label: '航运',
  130. },
  131. {
  132. value: '2',
  133. label: '市井',
  134. },
  135. ]
  136. },
  137. {
  138. value: '2',
  139. label: '风雅大都',
  140. children: [
  141. {
  142. value: '0',
  143. label: '雅集',
  144. },
  145. {
  146. value: '1',
  147. label: '曲苑',
  148. },
  149. ]
  150. },
  151. ])
  152. const keyword = ref('')
  153. watch(route, () => {
  154. cascaderValue.value = (route.query.sceneIdx && route.query.cameraIdx) ? [route.query.sceneIdx, route.query.cameraIdx] : ['0', '0']
  155. })
  156. const relicData = computed(() => {
  157. return store.getters.relicData.filter((item) => {
  158. const selectedSceneIdx = Number(cascaderValue.value[0])
  159. if (selectedSceneIdx !== item.sceneIdx) {
  160. return false
  161. } else {
  162. const selectedCameraIdx = Number(cascaderValue.value[1])
  163. if (selectedCameraIdx === item.cameraIdx) {
  164. return true
  165. } else {
  166. return false
  167. }
  168. }
  169. }).filter((item) => {
  170. if (!keyword.value) {
  171. return true
  172. } else {
  173. if (item['名称'].includes(keyword.value)) {
  174. return true
  175. } else {
  176. return false
  177. }
  178. }
  179. })
  180. })
  181. function getRelicThumbUrl(idx) {
  182. if (Array.isArray(relicData.value[idx]['图片名']) && relicData.value[idx]['图片名'][0]) {
  183. return `${process.env.BASE_URL}relic-data/small-photo/${relicData.value[idx]['图片名'][0]}`
  184. } else {
  185. return ''
  186. }
  187. }
  188. const listEl = ref(null)
  189. let scrollLeft = Number(localStorage.getItem(SCROLL_KEY) || 0)
  190. const { width: listWidth, height: listHeight } = useElementSize(listEl)
  191. const { translateLength, hasOperatedThisTime, updateWidth, updateTranslateLength } = useSmoothSwipe({
  192. scrollTargetRef: listEl,
  193. viewportWidth: listWidth,
  194. dontHandlerMobile: true
  195. })
  196. watch(relicData, (vNew) => {
  197. nextTick(() => {
  198. updateWidth()
  199. })
  200. }, {
  201. deep: true
  202. })
  203. function onClickItem(idx) {
  204. if (!hasOperatedThisTime.value) {
  205. router[$isMobile ? 'replace' : 'push']({
  206. name: 'RelicDetail',
  207. query: {
  208. sceneIdx: route.query.sceneIdx,
  209. cameraIdx: route.query.cameraIdx,
  210. relicIdx: idx,
  211. }
  212. })
  213. }
  214. }
  215. watch(cascaderValue, val => {
  216. router.replace({
  217. name: 'RelicList',
  218. query: {
  219. sceneIdx: val[0],
  220. cameraIdx: val[1]
  221. }
  222. })
  223. updateTranslateLength(0)
  224. localStorage.setItem(SCROLL_KEY, 0)
  225. })
  226. router.beforeEach((to) => {
  227. if (!['RelicDetail', 'RelicList'].includes(to.name)) {
  228. localStorage.removeItem(SCROLL_KEY)
  229. }
  230. })
  231. onMounted(() => {
  232. if (scrollLeft) {
  233. updateTranslateLength(scrollLeft)
  234. }
  235. })
  236. const handleScroll = debounce(() => {
  237. listEl.value && localStorage.setItem(SCROLL_KEY, listEl.value.scrollLeft)
  238. }, 100)
  239. </script>
  240. <style lang="less" scoped>
  241. @page-height-design-px: 970;
  242. .relic-list {
  243. height: 100vh;
  244. background-image: url(@/assets/images/relic-list-bg.jpg);
  245. background-size: cover;
  246. background-repeat: no-repeat;
  247. background-position: center center;
  248. >button.return {
  249. position: absolute;
  250. width: calc(58 / @page-height-design-px * 100vh);
  251. height: calc(58 / @page-height-design-px * 100vh);
  252. left: calc(42 / @page-height-design-px * 100vh);
  253. top: calc(68 / @page-height-design-px * 100vh);
  254. background-image: url(@/assets/images/btn-return.png);
  255. background-size: contain;
  256. background-repeat: no-repeat;
  257. background-position: center center;
  258. }
  259. :deep {
  260. .el-cascader {
  261. position: absolute;
  262. width: calc(200 / @page-height-design-px * 100vh);
  263. height: calc(58 / @page-height-design-px * 100vh);
  264. left: calc(160 / @page-height-design-px * 100vh);
  265. top: calc(68 / @page-height-design-px * 100vh);
  266. z-index: 1;
  267. .el-input {
  268. height: 100%;
  269. background-image: url(@/assets/images/cascader-bg.png);
  270. background-size: 100% 100%;
  271. border: 0;
  272. .el-input__wrapper {
  273. background: none;
  274. border: 0;
  275. box-shadow: none;
  276. .el-input__inner {
  277. height: 100%;
  278. font-size: calc(30 / @page-height-design-px * 100vh);
  279. text-align: center;
  280. font-family: 'SourceHanSerifCN-Heavy';
  281. color: #6A3906;
  282. }
  283. .el-input__suffix {
  284. color: #6A3906;
  285. }
  286. }
  287. .icon-arrow-down {
  288. font-size: calc(24 / @page-height-design-px * 100vh);
  289. margin-top: calc(10 / @page-height-design-px * 100vh);
  290. }
  291. }
  292. }
  293. .el-popper .is-light {
  294. background: red;
  295. }
  296. }
  297. >.search-ui {
  298. position: absolute;
  299. top: calc(65 / @page-height-design-px * 100vh);
  300. right: calc(27 / @page-height-design-px * 100vh);
  301. width: calc(406 / @page-height-design-px * 100vh);
  302. height: calc(62 / @page-height-design-px * 100vh);
  303. background-image: url(@/assets/images/search-bg.png);
  304. background-size: cover;
  305. background-repeat: no-repeat;
  306. background-position: center center;
  307. z-index: 1;
  308. >input {
  309. position: absolute;
  310. left: 50px;
  311. top: 50%;
  312. transform: translateY(-50%);
  313. height: calc(35 / @page-height-design-px * 100vh);
  314. width: calc(250 / @page-height-design-px * 100vh);
  315. font-size: calc(24 / @page-height-design-px * 100vh);
  316. font-family: Source Han Sans CN, Source Han Sans CN;
  317. font-weight: 400;
  318. color: rgba(255, 255, 255, 0.7);
  319. line-height: calc(28 / @page-height-design-px * 100vh);
  320. &::placeholder {
  321. font-size: calc(24 / @page-height-design-px * 100vh);
  322. font-family: Source Han Sans CN, Source Han Sans CN;
  323. font-weight: 400;
  324. color: rgba(255, 255, 255, 0.3);
  325. line-height: calc(28 / @page-height-design-px * 100vh);
  326. }
  327. }
  328. >button.search {
  329. position: absolute;
  330. width: calc(31 / @page-height-design-px * 100vh);
  331. height: calc(31 / @page-height-design-px * 100vh);
  332. position: absolute;
  333. top: 50%;
  334. right: calc(53 / @page-height-design-px * 100vh);
  335. transform: translateY(-50%);
  336. background-image: url(@/assets/images/icon-search.png);
  337. background-size: cover;
  338. background-repeat: no-repeat;
  339. background-position: center center;
  340. }
  341. }
  342. >.the-list {
  343. position: absolute;
  344. left: 0;
  345. top: 55%;
  346. transform: translateY(-50%);
  347. width: 100%;
  348. height: calc(650 / @page-height-design-px * 100vh);
  349. background-size: auto 100%;
  350. background-repeat: no-repeat;
  351. background-position: left center;
  352. padding-left: calc(54 / @page-height-design-px * 100vh);
  353. box-sizing: border-box;
  354. &.scene1 {
  355. .content-wrap::after {
  356. background: #B8AD9C;
  357. }
  358. }
  359. &.scene2 {
  360. .content-wrap::after {
  361. background: #AEC5BB;
  362. }
  363. }
  364. &.scene3 {
  365. .content-wrap::after {
  366. background: #A6A3AD;
  367. }
  368. }
  369. >.content-wrap {
  370. position: relative;
  371. &::after {
  372. content: '';
  373. position: absolute;
  374. top: calc(42 / @page-height-design-px * 100vh);
  375. left: 0;
  376. right: 0;
  377. bottom: calc(42 / @page-height-design-px * 100vh);
  378. z-index: -1;
  379. }
  380. &::-webkit-scrollbar {
  381. height: 0;
  382. }
  383. box-sizing: border-box;
  384. margin-top: calc(27 / @page-height-design-px * 100vh);
  385. height: calc(589 / @page-height-design-px * 100vh);
  386. width: 100%;
  387. overflow: auto;
  388. user-select: none;
  389. display: flex;
  390. align-items: center;
  391. background: rgba(230,232,224,0.375);
  392. >.first-item{
  393. flex: 0 0 auto;
  394. width: calc(277 / @page-height-design-px * 100vh * 0.83);
  395. height: calc(608 / @page-height-design-px * 100vh * 0.83);
  396. background-size: cover;
  397. background-repeat: no-repeat;
  398. background-position: center center;
  399. }
  400. >.first-item.scene1{
  401. background-image: url(@/assets/images/relic-first-item-bg-1.jpg);
  402. }
  403. >.first-item.scene2{
  404. background-image: url(@/assets/images/relic-first-item-bg-2.jpg);
  405. }
  406. >.first-item.scene3{
  407. background-image: url(@/assets/images/relic-first-item-bg-3.jpg);
  408. }
  409. >.relic-item {
  410. flex: 0 0 auto;
  411. width: calc(277 / @page-height-design-px * 100vh * 0.83);
  412. height: calc(608 / @page-height-design-px * 100vh * 0.83);
  413. cursor: pointer;
  414. background-size: cover;
  415. background-repeat: no-repeat;
  416. background-position: center center;
  417. position: relative;
  418. >.name {
  419. flex: 0 0 auto;
  420. position: absolute;
  421. top: calc(10 / @page-height-design-px * 100vh * 0.83);
  422. right: calc(32 / @page-height-design-px * 100vh * 0.83);
  423. width: calc(47 / @page-height-design-px * 100vh * 0.83);
  424. line-height: calc(47 / @page-height-design-px * 100vh * 0.83);
  425. height: calc(261 / @page-height-design-px * 100vh * 0.83);
  426. font-size: calc(19 / @page-height-design-px * 100vh * 0.83);
  427. font-family: "SourceHanSansSC-Normal";
  428. font-weight: 800;
  429. color: #43310E;
  430. overflow: hidden;
  431. writing-mode: vertical-rl;
  432. letter-spacing: 0.2em;
  433. background-image: url(@/assets/images/relic-item-title-bg.png);
  434. background-size: contain;
  435. background-repeat: no-repeat;
  436. background-position: center center;
  437. text-align: center;
  438. padding-top: calc(17 / @page-height-design-px * 100vh * 0.83);
  439. padding-bottom: calc(17 / @page-height-design-px * 100vh * 0.83);
  440. z-index: 1;
  441. }
  442. >.name.wide{
  443. right: calc(12 / @page-height-design-px * 100vh * 0.83);
  444. width: calc(82 / @page-height-design-px * 100vh * 0.83);
  445. height: calc(255 / @page-height-design-px * 100vh * 0.83);
  446. line-height: 1.5em;
  447. background-image: url(@/assets/images/relic-item-title-bg-wide.png);
  448. padding-right: 0.7em;
  449. padding-top: calc(26 / @page-height-design-px * 100vh * 0.83);
  450. padding-bottom: calc(26 / @page-height-design-px * 100vh * 0.83);
  451. z-index: 1;
  452. }
  453. >.name.smallFontSize{
  454. padding-right: calc(20 / @page-height-design-px * 100vh * 0.83);;
  455. font-size: calc(14 / @page-height-design-px * 100vh * 0.83);
  456. }
  457. >.name.smFontSize {
  458. font-size: calc(14 / @page-height-design-px * 100vh * 0.83);
  459. }
  460. >img {
  461. position: absolute;
  462. left: 50%;
  463. transform: translateX(-50%) scale(calc(v-bind('windowHeight') / @page-height-design-px));
  464. object-fit: contain;
  465. transform-origin: center bottom;
  466. pointer-events: none;
  467. }
  468. }
  469. >.relic-item.isOdd{
  470. >img{
  471. bottom: calc(250 / @page-height-design-px * 100vh * 0.83);
  472. }
  473. }
  474. >.relic-item.isEven{
  475. >img{
  476. bottom: calc(120 / @page-height-design-px * 100vh * 0.83);
  477. }
  478. }
  479. >.relic-item.isScene1.isOdd{
  480. background-image: url(@/assets/images/relic-item-bg-1-odd.jpg);
  481. }
  482. >.relic-item.isScene1.isEven{
  483. background-image: url(@/assets/images/relic-item-bg-1-even.jpg);
  484. }
  485. >.relic-item.isScene2.isOdd{
  486. background-image: url(@/assets/images/relic-item-bg-2-odd.jpg);
  487. }
  488. >.relic-item.isScene2.isEven{
  489. background-image: url(@/assets/images/relic-item-bg-2-even.jpg);
  490. }
  491. >.relic-item.isScene3.isOdd{
  492. background-image: url(@/assets/images/relic-item-bg-3-odd.jpg);
  493. }
  494. >.relic-item.isScene3.isEven{
  495. background-image: url(@/assets/images/relic-item-bg-3-even.jpg);
  496. }
  497. }
  498. }
  499. >.the-list.scene1{
  500. background-image: url(@/assets/images/relic-list-bg-1-1.png);
  501. }
  502. >.the-list.scene2{
  503. background-image: url(@/assets/images/relic-list-bg-1-2.png);
  504. }
  505. >.the-list.scene3{
  506. background-image: url(@/assets/images/relic-list-bg-1-3.png);
  507. }
  508. }
  509. @media screen and (max-height: 480px) {
  510. .relic-list > button.return {
  511. left: calc(42 / 970* 100vh);
  512. top: calc(48 / 970* 100vh);
  513. width: calc(90 / 970* 100vh);
  514. height: calc(90 / 970* 100vh);
  515. }
  516. .relic-list > .the-list {
  517. top: unset;
  518. bottom: 0;
  519. padding-left: calc(66 / 970* 100vh);
  520. height: calc(800 / 970* 100vh);
  521. transform: unset;
  522. .content-wrap {
  523. margin-top: calc(33 / 970* 100vh);
  524. height: calc(726 / 970* 100vh);
  525. .first-item,
  526. .relic-item {
  527. width: calc(352 / 970* 100vh* 0.83);
  528. height: calc(758 / 970* 100vh* 0.83);
  529. }
  530. .relic-item {
  531. .name {
  532. width: calc(65 / 970* 100vh* 0.83);
  533. line-height: calc(65 / 970* 100vh* 0.83);
  534. height: calc(363 / 970* 100vh* 0.83);
  535. font-size: calc(30 / 970* 100vh* 0.83);
  536. &.wide {
  537. padding-right: 0.4em;
  538. width: calc(116 / 970* 100vh* 0.83);
  539. height: calc(363 / 970* 100vh* 0.83);
  540. }
  541. &.smallFontSize {
  542. font-size: calc(24 / 970* 100vh* 0.83);
  543. }
  544. }
  545. &.isOdd > img {
  546. bottom: calc(310 / 970* 100vh* 0.83);
  547. }
  548. &.isEven > img {
  549. bottom: calc(150 / 970* 100vh* 0.83);
  550. }
  551. }
  552. }
  553. }
  554. }
  555. </style>