YinLiShi.vue 3.34 KB
<template>
  <div class="yinlishi-container">
    <section class="grid-two">
      <div
        class="item-card"
        v-for="(item, index) in guideList"
        :key="`${item.id}-${index}`"
        @click="goDetail(item)"
      >
        <div class="item-image">
          <img
            :src="item.image"
            :alt="`${item.role} ${item.name}`"
            :aria-label="`${item.role}:${item.name}`"
            loading="lazy"
          />
        </div>
        <div class="item-caption">
          <div class="item-role">{{ item.role }}</div>
          <div class="item-name" v-html="formatNameWithSuperscript(item.name)"></div>
        </div>
      </div>
    </section>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useTitle } from '@vueuse/core'
import { formatNameWithSuperscript } from '@utils/text'

import { getArticleListAPI } from '@/api/index.js'

const router = useRouter()

useTitle(router.currentRoute.value.query.title || '引礼师')

const cid = ref(router.currentRoute.value.query.cid || '')
const guideList = ref([])

/**
 * 处理图片地址,避免空图导致布局塌陷
 * @param {string} photo 原始图片地址
 * @returns {string} 处理后的图片地址
 */
const processImageUrl = (photo) => {
  if (!photo) {
    return ''
  }
  return `${photo}?imageMogr2/thumbnail/400x/strip/quality/70`
}

/**
 * 规范化引礼师列表项,统一页面渲染字段
 * @param {object} item 接口返回项
 * @returns {object} 页面展示项
 */
const processGuideItem = (item) => ({
  id: item?.id || '',
  role: item?.post_excerpt || '',
  name: item?.post_title || '未知',
  image: processImageUrl(item?.photo)
})

const goDetail = (item) => {
  router.push(`/yinlishi/${item.id}`)
}

/**
 * 加载引礼师文章列表
 * @returns {Promise<void>}
 */
const loadGuideList = async () => {
  try {
    const { code, list } = await getArticleListAPI({
      cid: cid.value,
      page: 0,
      limit: 9999
    })

    if (!code || !Array.isArray(list)) {
      console.error('Invalid guide list response')
      guideList.value = []
      return
    }

    guideList.value = list.map(processGuideItem)
  } catch (error) {
    console.error('Failed to fetch guide list:', error)
    guideList.value = []
  }
}

onMounted(() => {
  loadGuideList()
})
</script>

<style scoped>
.yinlishi-container {
  min-height: 100vh;
  padding: 1rem;
  background: #F2EBDB;
  box-sizing: border-box;
}

.grid-two {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.75rem;
}

.item-card {
  position: relative;
  border: 1px solid rgba(107, 65, 2, 0.8);
  overflow: hidden;
  background: #FFFFFF;
  transition: transform 0.2s ease;
}

.item-card:hover {
  transform: translateY(-0.125rem);
}

.item-image {
  width: 100%;
  aspect-ratio: 3 / 4;
  background: #f5f5f5;
}

.item-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.item-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0.75rem;
  text-align: center;
  color: #FFFFFF;
  background: rgba(107, 65, 2, 0.8);
}

.item-role {
  font-size: 0.75rem;
  opacity: 0.95;
}

.item-name {
  margin-top: 0.25rem;
  font-size: 1.25rem;
  font-weight: 600;
}

@media (max-width: 30rem) {
  .yinlishi-container {
    padding: 0.75rem;
  }

  .item-caption {
    padding: 0.5rem;
  }
}
</style>