RelicList.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <template>
  2. <div class="relic-list">
  3. <button
  4. class="return"
  5. @click="router.go(-1)"
  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['名称'].length > 11
  64. }"
  65. :title="item['名称']"
  66. >
  67. {{ item['名称'] }}
  68. </div>
  69. <img
  70. class=""
  71. :src="getRelicThumbUrl(idx)"
  72. alt=""
  73. draggable="false"
  74. >
  75. </div>
  76. </div>
  77. </div>
  78. </div>
  79. </template>
  80. <script setup>
  81. /**
  82. * todo: 自动恢复上次滚动位置
  83. */
  84. import { ref, computed, watch, watchEffect, onMounted, nextTick, defineComponent } from "vue"
  85. import { useRoute, useRouter } from "vue-router"
  86. import { useStore } from "vuex"
  87. import useSmoothSwipe from '@/useFunctions/useSmoothSwipe.js'
  88. import { useElementSize, useWindowSize } from '@vueuse/core'
  89. import { debounce } from "lodash"
  90. const SCROLL_KEY = 'relicListScrollLeft'
  91. const route = useRoute()
  92. const router = useRouter()
  93. const store = useStore()
  94. const { height: windowHeight } = useWindowSize()
  95. const cascaderValueInit = (route.query.sceneIdx && route.query.cameraIdx) ? [route.query.sceneIdx, route.query.cameraIdx] : ['0', '0']
  96. const cascaderValue = ref(cascaderValueInit)
  97. const cameraTree = ref([
  98. {
  99. value: '0',
  100. label: '天下大都',
  101. children: [
  102. {
  103. value: '0',
  104. label: '帝都',
  105. },
  106. {
  107. value: '1',
  108. label: '宫阙',
  109. },
  110. {
  111. value: '2',
  112. label: '览胜',
  113. },
  114. ]
  115. },
  116. {
  117. value: '1',
  118. label: '河润大都',
  119. children: [
  120. {
  121. value: '0',
  122. label: '河畅',
  123. },
  124. {
  125. value: '1',
  126. label: '航运',
  127. },
  128. {
  129. value: '2',
  130. label: '市井',
  131. },
  132. ]
  133. },
  134. {
  135. value: '2',
  136. label: '风雅大都',
  137. children: [
  138. {
  139. value: '0',
  140. label: '雅集',
  141. },
  142. {
  143. value: '1',
  144. label: '曲苑',
  145. },
  146. ]
  147. },
  148. ])
  149. const keyword = ref('')
  150. const sceneIdx = computed(() => {
  151. return route.query.sceneIdx
  152. })
  153. const cameraIdx = computed(() => {
  154. return route.query.cameraIdx
  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 { hasOperatedThisTime, updateWidth, updateTranslateLength } = useSmoothSwipe({
  192. scrollTargetRef: listEl,
  193. viewportWidth: listWidth,
  194. initTranslateLength: scrollLeft,
  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.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. })
  225. router.beforeEach((to) => {
  226. if (!['RelicDetail', 'RelicList'].includes(to.name)) {
  227. localStorage.removeItem(SCROLL_KEY)
  228. }
  229. })
  230. onMounted(() => {
  231. if (scrollLeft) {
  232. listEl.value.scrollLeft = scrollLeft
  233. }
  234. })
  235. const handleScroll = debounce(() => {
  236. listEl.value && localStorage.setItem(SCROLL_KEY, listEl.value.scrollLeft)
  237. }, 100)
  238. </script>
  239. <style lang="less" scoped>
  240. @page-height-design-px: 970;
  241. .relic-list {
  242. height: 100%;
  243. background-image: url(@/assets/images/relic-list-bg.jpg);
  244. background-size: cover;
  245. background-repeat: no-repeat;
  246. background-position: center center;
  247. >button.return {
  248. position: absolute;
  249. width: 58px;
  250. height: 58px;
  251. left: 42px;
  252. top: 68px;
  253. background-image: url(@/assets/images/btn-return.png);
  254. background-size: contain;
  255. background-repeat: no-repeat;
  256. background-position: center center;
  257. }
  258. :deep {
  259. .el-cascader {
  260. position: absolute;
  261. width: 200px;
  262. height: 58px;
  263. left: 160px;
  264. top: 68px;
  265. .el-input {
  266. height: 100%;
  267. background-image: url(@/assets/images/cascader-bg.png);
  268. background-size: 100% 100%;
  269. border: 0;
  270. .el-input__wrapper {
  271. background: none;
  272. border: 0;
  273. box-shadow: none;
  274. .el-input__inner {
  275. height: 100%;
  276. font-size: 30px;
  277. text-align: center;
  278. font-family: 'SourceHanSerifCN-Heavy';
  279. color: #6A3906;
  280. }
  281. .el-input__suffix {
  282. color: #6A3906;
  283. }
  284. }
  285. .icon-arrow-down {
  286. font-size: 24px;
  287. margin-top: 10px;
  288. }
  289. }
  290. }
  291. .el-popper .is-light {
  292. background: red;
  293. }
  294. }
  295. >.search-ui {
  296. position: absolute;
  297. top: 65px;
  298. right: 27px;
  299. width: 406px;
  300. height: 62px;
  301. background-image: url(@/assets/images/search-bg.png);
  302. background-size: cover;
  303. background-repeat: no-repeat;
  304. background-position: center center;
  305. >input {
  306. position: absolute;
  307. left: 50px;
  308. top: 50%;
  309. transform: translateY(-50%);
  310. height: 35px;
  311. width: 250PX;
  312. font-size: 24px;
  313. font-family: Source Han Sans CN, Source Han Sans CN;
  314. font-weight: 400;
  315. color: rgba(255, 255, 255, 0.7);
  316. line-height: 28px;
  317. &::placeholder {
  318. font-size: 24px;
  319. font-family: Source Han Sans CN, Source Han Sans CN;
  320. font-weight: 400;
  321. color: rgba(255, 255, 255, 0.3);
  322. line-height: 28px;
  323. }
  324. }
  325. >button.search {
  326. position: absolute;
  327. width: 31px;
  328. height: 31px;
  329. position: absolute;
  330. top: 50%;
  331. right: 53px;
  332. transform: translateY(-50%);
  333. background-image: url(@/assets/images/icon-search.png);
  334. background-size: cover;
  335. background-repeat: no-repeat;
  336. background-position: center center;
  337. }
  338. }
  339. >.the-list {
  340. position: absolute;
  341. left: 0;
  342. top: 55%;
  343. translate: 0 -50%;
  344. width: 100%;
  345. height: calc(650 / @page-height-design-px * 100vh);
  346. background-size: auto 100%;
  347. background-repeat: no-repeat;
  348. background-position: left center;
  349. padding-left: calc(54 / @page-height-design-px * 100vh);
  350. box-sizing: border-box;
  351. >.content-wrap {
  352. &::-webkit-scrollbar {
  353. height: 0;
  354. }
  355. box-sizing: border-box;
  356. margin-top: calc(27 / @page-height-design-px * 100vh);
  357. height: calc(589 / @page-height-design-px * 100vh);
  358. width: 100%;
  359. overflow: auto;
  360. user-select: none;
  361. display: flex;
  362. align-items: center;
  363. background: rgba(230,232,224,0.375);
  364. >.first-item{
  365. flex: 0 0 auto;
  366. width: calc(277 / @page-height-design-px * 100vh * 0.83);
  367. height: calc(608 / @page-height-design-px * 100vh * 0.83);
  368. background-size: cover;
  369. background-repeat: no-repeat;
  370. background-position: center center;
  371. }
  372. >.first-item.scene1{
  373. background-image: url(@/assets/images/relic-first-item-bg-1.jpg);
  374. }
  375. >.first-item.scene2{
  376. background-image: url(@/assets/images/relic-first-item-bg-2.jpg);
  377. }
  378. >.first-item.scene3{
  379. background-image: url(@/assets/images/relic-first-item-bg-3.jpg);
  380. }
  381. >.relic-item {
  382. flex: 0 0 auto;
  383. width: calc(277 / @page-height-design-px * 100vh * 0.83);
  384. height: calc(608 / @page-height-design-px * 100vh * 0.83);
  385. cursor: pointer;
  386. background-size: cover;
  387. background-repeat: no-repeat;
  388. background-position: center center;
  389. position: relative;
  390. >.name {
  391. flex: 0 0 auto;
  392. position: absolute;
  393. top: calc(10 / @page-height-design-px * 100vh * 0.83);
  394. right: calc(32 / @page-height-design-px * 100vh * 0.83);
  395. width: calc(47 / @page-height-design-px * 100vh * 0.83);
  396. line-height: calc(47 / @page-height-design-px * 100vh * 0.83);
  397. height: calc(261 / @page-height-design-px * 100vh * 0.83);
  398. font-size: calc(19 / @page-height-design-px * 100vh * 0.83);
  399. font-family: Source Han Serif CN, Source Han Serif CN;
  400. font-weight: 800;
  401. color: #43310E;
  402. display: -webkit-box;
  403. -webkit-box-orient: vertical;
  404. -webkit-line-clamp: 2;
  405. overflow: hidden;
  406. writing-mode: vertical-rl;
  407. background-image: url(@/assets/images/relic-item-title-bg.png);
  408. background-size: contain;
  409. background-repeat: no-repeat;
  410. background-position: center center;
  411. text-align: center;
  412. padding-top: calc(17 / @page-height-design-px * 100vh * 0.83);
  413. padding-bottom: calc(17 / @page-height-design-px * 100vh * 0.83);
  414. z-index: 1;
  415. }
  416. >.name.wide{
  417. right: calc(12 / @page-height-design-px * 100vh * 0.83);
  418. width: calc(82 / @page-height-design-px * 100vh * 0.83);
  419. height: calc(255 / @page-height-design-px * 100vh * 0.83);
  420. line-height: 1.5em;
  421. background-image: url(@/assets/images/relic-item-title-bg-wide.png);
  422. padding-right: 0.7em;
  423. z-index: 1;
  424. }
  425. >img {
  426. position: absolute;
  427. left: 0;
  428. left: 50%;
  429. translate: -50% 0;
  430. object-fit: contain;
  431. scale: calc(v-bind('windowHeight') / @page-height-design-px);
  432. transform-origin: center bottom;
  433. }
  434. }
  435. >.relic-item.isOdd{
  436. >img{
  437. bottom: calc(250 / @page-height-design-px * 100vh * 0.83);
  438. }
  439. }
  440. >.relic-item.isEven{
  441. >img{
  442. bottom: calc(120 / @page-height-design-px * 100vh * 0.83);
  443. }
  444. }
  445. >.relic-item.isScene1.isOdd{
  446. background-image: url(@/assets/images/relic-item-bg-1-odd.jpg);
  447. }
  448. >.relic-item.isScene1.isEven{
  449. background-image: url(@/assets/images/relic-item-bg-1-even.jpg);
  450. }
  451. >.relic-item.isScene2.isOdd{
  452. background-image: url(@/assets/images/relic-item-bg-2-odd.jpg);
  453. }
  454. >.relic-item.isScene2.isEven{
  455. background-image: url(@/assets/images/relic-item-bg-2-even.jpg);
  456. }
  457. >.relic-item.isScene3.isOdd{
  458. background-image: url(@/assets/images/relic-item-bg-3-odd.jpg);
  459. }
  460. >.relic-item.isScene3.isEven{
  461. background-image: url(@/assets/images/relic-item-bg-3-even.jpg);
  462. }
  463. }
  464. }
  465. >.the-list.scene1{
  466. background-image: url(@/assets/images/relic-list-bg-1-1.png);
  467. }
  468. >.the-list.scene2{
  469. background-image: url(@/assets/images/relic-list-bg-1-2.png);
  470. }
  471. >.the-list.scene3{
  472. background-image: url(@/assets/images/relic-list-bg-1-3.png);
  473. }
  474. }
  475. </style>