GridList.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <template>
  2. <div :class="['grid-container']">
  3. <div
  4. v-for="(item, index) in list"
  5. :key="index"
  6. :class="['item', itemStyleType ]"
  7. :style="{
  8. height: height,
  9. backgroundImage: itemStyleType !== 'ugly-box' ?
  10. `url(${playAudio ? (item.playing ? Pause : Play) : (item.thumbnail || item.image || AppCofig.defaultImage)})` :
  11. undefined,
  12. }"
  13. :tabindex="1"
  14. @click="handleClick(item)"
  15. >
  16. <img
  17. v-if="itemStyleType === 'ugly-box'"
  18. :src="item.thumbnail || item.image || AppCofig.defaultImage"
  19. alt=""
  20. />
  21. <div class="title">{{ item.title }}</div>
  22. </div>
  23. <audio ref="audio" />
  24. </div>
  25. </template>
  26. <script setup lang="ts">
  27. import { nextTick, onMounted, ref, type PropType } from 'vue';
  28. import Play from '@/assets/images/Home/Play.png';
  29. import Pause from '@/assets/images/Home/Pause.png';
  30. import AppCofig from '@/common/config/AppCofig';
  31. import Swal from 'sweetalert2';
  32. interface Item {
  33. image: string;
  34. thumbnail?: string;
  35. title: string;
  36. audio?: string;
  37. playing?: boolean;
  38. }
  39. const props = defineProps({
  40. list: {
  41. type: Array as PropType<Item[]>,
  42. required: true,
  43. default: () => [],
  44. },
  45. height: {
  46. type: String,
  47. default: undefined,
  48. },
  49. defaultImage: {
  50. type: String,
  51. default: undefined,
  52. },
  53. playAudio: {
  54. type: Boolean,
  55. default: false,
  56. },
  57. itemStyleType: {
  58. type: String,
  59. default: 'main-card shadow',
  60. },
  61. });
  62. const emit = defineEmits(['itemClick']);
  63. const audio = ref<HTMLAudioElement>();
  64. onMounted(() => {
  65. nextTick(() => {
  66. if (!audio.value)
  67. return;
  68. audio.value.onended = () => {
  69. props.list.forEach(p => p.playing = false);
  70. };
  71. })
  72. });
  73. function handleClick(item: Item) {
  74. if (props.playAudio && audio.value) {
  75. if (item.audio) {
  76. if (item.playing) {
  77. audio.value.pause();
  78. item.playing = false;
  79. } else {
  80. audio.value.pause();
  81. props.list.forEach(p => p.playing = false);
  82. audio.value.src = item.audio;
  83. audio.value.play().catch(() => {
  84. Swal.fire({
  85. icon: 'error',
  86. title: '播放失败',
  87. });
  88. item.playing = false;
  89. });
  90. item.playing = true;
  91. }
  92. }
  93. return;
  94. }
  95. emit('itemClick', item)
  96. }
  97. </script>
  98. <style lang="scss" scoped>
  99. .grid-container {
  100. display: grid;
  101. grid-template-columns: repeat(4, 1fr);
  102. grid-template-rows: repeat(4, 1fr);
  103. row-gap: 2.5vh;
  104. column-gap: 1.2vw;
  105. width: 100%;
  106. .item {
  107. position: relative;
  108. background-size: cover;
  109. background-position: center;
  110. }
  111. .ugly-box {
  112. position: relative;
  113. background-size: 100% 100%;
  114. background-image: url('@/assets/images/Home/UglyItemBackground.png');
  115. padding: 20px;
  116. cursor: pointer;
  117. user-select: none;
  118. img {
  119. border-radius: 5px 25px 5px 25px;
  120. box-shadow: 0 3px 6px rgba(0, 0, 0, 0.56), -3px 0 6px rgba(0, 0, 0, 0.26);
  121. width: 100%;
  122. height: calc(100% - 20px);
  123. object-fit: cover;
  124. }
  125. .title {
  126. background: transparent;
  127. }
  128. &:hover {
  129. transform: scale(1.05);
  130. }
  131. &:focus {
  132. outline: 4px solid var(--color-primary);
  133. }
  134. }
  135. .title {
  136. position: absolute;
  137. bottom: 0;
  138. left: 0;
  139. width: 100%;
  140. text-align: center;
  141. color: var(--color-text-light);
  142. font-size: 0.8rem;
  143. padding: 8px 0;
  144. background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
  145. }
  146. }
  147. </style>