GuardInfoBoard.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <template>
  2. <div class="guard-info-board">
  3. <div class="board-wrap" :style="{ backgroundImage: `url(${boardImg})` }">
  4. <div class="board-text">
  5. <div class="board-title">{{ title }}</div>
  6. <div class="board-sub">{{ subtitle }}</div>
  7. </div>
  8. <div class="edit-btn" aria-label="编辑" @click.stop="onEdit">
  9. <Icon icon="solar:pen-new-square-linear" />
  10. </div>
  11. </div>
  12. <div class="info-cards" :style="{ '--card-border-bg': `url(${borderBgImg})` }">
  13. <div v-for="(card, index) in cards" :key="index" class="info-card">
  14. <template v-if="index === 3">
  15. <img class="badge-icon" :src="resolvedBadgeIcon" alt="" />
  16. <span class="badge-label">{{ card.bottom }}</span>
  17. </template>
  18. <template v-else>
  19. <div class="card-top">{{ card.top }}</div>
  20. <div class="card-bottom">{{ card.bottom }}</div>
  21. </template>
  22. </div>
  23. </div>
  24. </div>
  25. </template>
  26. <script setup lang="ts">
  27. import { computed } from 'vue'
  28. import { Icon } from '@iconify/vue'
  29. import boardImg from '@/assets/img/guard/board.png'
  30. import borderBgImg from '@/assets/img/guard/border-bg.png'
  31. import level1Img from '@/assets/img/guard/level-1.png'
  32. export type GuardInfoCard = {
  33. top?: string
  34. bottom: string
  35. }
  36. const props = withDefaults(
  37. defineProps<{
  38. title?: string
  39. subtitle?: string
  40. badgeIcon?: string
  41. cards?: GuardInfoCard[]
  42. }>(),
  43. {
  44. title: '【茜茜荔荔】',
  45. subtitle: '用户昵称 2025.06.22',
  46. cards: () => [
  47. { top: '2025.05.16', bottom: '从化荔博园' },
  48. { top: '16年老树', bottom: '妃子笑荔枝' },
  49. { top: '成熟期', bottom: '温度适宜-梢期杀虫' },
  50. { top: '', bottom: '润土行者' },
  51. ],
  52. },
  53. )
  54. const resolvedBadgeIcon = computed(() => props.badgeIcon || level1Img)
  55. const emit = defineEmits<{ edit: [] }>()
  56. function onEdit() {
  57. emit('edit')
  58. }
  59. </script>
  60. <style scoped lang="less">
  61. .guard-info-board {
  62. position: relative;
  63. width: 100%;
  64. pointer-events: none;
  65. .board-wrap {
  66. position: relative;
  67. z-index: 1;
  68. width: 200rem;
  69. height: 335rem;
  70. background-repeat: no-repeat;
  71. background-position: center;
  72. background-size: 100% 100%;
  73. pointer-events: none;
  74. .board-text {
  75. position: absolute;
  76. top: 0;
  77. left: 0;
  78. width: 100%;
  79. height: 146rem;
  80. display: flex;
  81. flex-direction: column;
  82. align-items: center;
  83. justify-content: center;
  84. box-sizing: border-box;
  85. text-align: center;
  86. color: #fff;
  87. pointer-events: none;
  88. .board-title {
  89. font-family: jiangxizhuokai, 'Songti SC', 'STSong', serif;
  90. font-size: 26rem;
  91. text-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.25);
  92. }
  93. .board-sub {
  94. font-family: jiangxizhuokai, 'Songti SC', 'STSong', serif;
  95. margin-top: 5rem;
  96. font-size: 10rem;
  97. }
  98. }
  99. .edit-btn {
  100. position: absolute;
  101. right: 0rem;
  102. top: 120rem;
  103. width: 23rem;
  104. height: 23rem;
  105. border-radius: 50%;
  106. display: flex;
  107. align-items: center;
  108. justify-content: center;
  109. background: rgba(0, 0, 0, 0.6);
  110. color: #fff;
  111. pointer-events: auto;
  112. svg {
  113. font-size: 10rem;
  114. }
  115. }
  116. }
  117. .info-cards {
  118. position: absolute;
  119. left: 50%;
  120. bottom: calc(var(--footer-bottom-offset) + var(--footer-height) + 10rem);
  121. z-index: 2;
  122. width: calc(100vw - 20rem);
  123. transform: translateX(-50%);
  124. display: grid;
  125. grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.3fr) minmax(0, 1fr);
  126. gap: 4rem;
  127. .info-card {
  128. width: 100%;
  129. min-width: 0;
  130. height: 58rem;
  131. background: var(--card-border-bg) no-repeat center / 100% 100%;
  132. display: flex;
  133. flex-direction: column;
  134. align-items: center;
  135. justify-content: center;
  136. text-align: center;
  137. color: #fff;
  138. .card-top {
  139. width: 100%;
  140. font-family: SmileySans, sans-serif;
  141. font-size: 16rem;
  142. margin-bottom: 3rem;
  143. }
  144. .card-bottom {
  145. width: 100%;
  146. font-size: 10rem;
  147. }
  148. .badge-icon {
  149. width: 24rem;
  150. height: 31rem;
  151. object-fit: contain;
  152. margin-bottom: 2rem;
  153. }
  154. .badge-label {
  155. width: 100%;
  156. font-size: 10rem;
  157. font-weight: 600;
  158. color: #ffd2a7;
  159. }
  160. }
  161. }
  162. }
  163. </style>