hookehuyr

feat: 添加三师七证详情页和瀑布流展示功能

重构路由配置,新增MastersDetail页面
实现戒子页面的瀑布流图片展示功能
优化新闻详情页的样式和布局
移除不再使用的Teachers相关组件
添加Vant组件类型声明和mock数据
......@@ -19,14 +19,17 @@ declare module 'vue' {
VanCheckbox: typeof import('vant/es')['Checkbox']
VanCollapse: typeof import('vant/es')['Collapse']
VanCollapseItem: typeof import('vant/es')['CollapseItem']
VanEmpty: typeof import('vant/es')['Empty']
VanField: typeof import('vant/es')['Field']
VanForm: typeof import('vant/es')['Form']
VanGrid: typeof import('vant/es')['Grid']
VanGridItem: typeof import('vant/es')['GridItem']
VanIcon: typeof import('vant/es')['Icon']
VanImage: typeof import('vant/es')['Image']
VanList: typeof import('vant/es')['List']
VanNavBar: typeof import('vant/es')['NavBar']
VanNoticeBar: typeof import('vant/es')['NoticeBar']
VanOverlay: typeof import('vant/es')['Overlay']
VanProgress: typeof import('vant/es')['Progress']
VanRate: typeof import('vant/es')['Rate']
VanSidebar: typeof import('vant/es')['Sidebar']
......@@ -42,5 +45,6 @@ declare module 'vue' {
VanTabs: typeof import('vant/es')['Tabs']
VanTag: typeof import('vant/es')['Tag']
VideoPlayer: typeof import('./components/VideoPlayer.vue')['default']
WaterfallGallery: typeof import('./components/WaterfallGallery.vue')['default']
}
}
......
/*
* @Date: 2025-10-30 10:29:15
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-10-30 10:29:24
* @FilePath: /itomix/h5_vite_template/src/router/index.js
* @LastEditTime: 2025-10-30 20:53:14
* @FilePath: /stdj_h5/src/router/index.js
* @Description: 文件描述
*/
import { createRouter, createWebHistory } from 'vue-router'
......@@ -20,34 +20,29 @@ const routes = [
component: Home
},
{
path: '/teachers',
name: 'Teachers',
component: () => import('../views/Teachers.vue')
path: '/masters',
name: '三師七證',
component: () => import('../views/Masters.vue')
},
{
path: '/teachers/:id',
name: 'TeacherDetail',
component: () => import('../views/TeacherDetail.vue')
path: '/masters/:id',
name: '三師七證详情',
component: () => import('../views/MastersDetail.vue')
},
{
path: '/volunteers',
name: 'Volunteers',
component: () => import('../views/Volunteers.vue')
path: '/news/:id',
name: 'NewsDetail',
component: () => import('../views/NewsDetail.vue')
},
{
path: '/students',
name: 'Students',
name: '戒子',
component: () => import('../views/Students.vue')
},
{
path: '/students/:id',
name: 'StudentDetail',
component: () => import('../views/StudentDetail.vue')
},
{
path: '/news/:id',
name: 'NewsDetail',
component: () => import('../views/NewsDetail.vue')
path: '/volunteers',
name: '义工',
component: () => import('../views/Volunteers.vue')
},
{
path: '/:pathMatch(.*)*',
......
......@@ -240,3 +240,87 @@ export const communityPosts = [
createdAt: '2023-03-14T15:45:00Z'
}
];
// 瀑布流图片数据 - 用于义工和戒子页面
export const waterfallImages = [
{
id: 1,
url: 'https://cdn.ipadbiz.cn/mlaj/images/zMRLZh40kms.jpg',
height: 300,
title: '义工活动'
},
{
id: 2,
url: 'https://cdn.ipadbiz.cn/mlaj/images/27kCu7bXGEI.jpg',
height: 400,
title: '学习交流'
},
{
id: 3,
url: 'https://cdn.ipadbiz.cn/mlaj/images/jbwr0qZvpD4.jpg',
height: 350,
title: '禅修体验'
},
{
id: 4,
url: 'https://cdn.ipadbiz.cn/mlaj/images/GGCP6vshpPY.jpg',
height: 280,
title: '传统文化'
},
{
id: 5,
url: 'https://cdn.ipadbiz.cn/mlaj/images/2JIvboGLeho.jpg',
height: 320,
title: '法会现场'
},
{
id: 6,
url: 'https://cdn.ipadbiz.cn/mlaj/images/_6HzPU9Hyfg (2).jpg',
height: 380,
title: '诵经学习'
},
{
id: 7,
url: 'https://cdn.ipadbiz.cn/mlaj/images/2Juj2cXWB7U.jpg',
height: 290,
title: '义工服务'
},
{
id: 8,
url: 'https://cdn.ipadbiz.cn/mlaj/images/Y17FE9Fuw4Y.jpg',
height: 360,
title: '戒子生活'
},
{
id: 9,
url: 'https://cdn.ipadbiz.cn/mlaj/images/-G3rw6Y02D0.jpg',
height: 310,
title: '修行日常'
},
{
id: 10,
url: 'https://cdn.ipadbiz.cn/mlaj/images/Oalh2MojUuk.jpg',
height: 340,
title: '集体活动'
}
];
// 生成更多瀑布流数据的函数
export const generateWaterfallData = (page = 1, pageSize = 10) => {
const baseImages = waterfallImages;
const startIndex = (page - 1) * pageSize;
const data = [];
for (let i = 0; i < pageSize; i++) {
const baseIndex = i % baseImages.length;
const baseImage = baseImages[baseIndex];
data.push({
...baseImage,
id: startIndex + i + 1,
height: Math.floor(Math.random() * 200) + 250, // 随机高度 250-450px
title: `${baseImage.title} ${Math.floor((startIndex + i) / baseImages.length) + 1}`
});
}
return data;
};
......
......@@ -187,6 +187,9 @@
import { ref, computed, nextTick, onMounted, watch } from 'vue'
import VideoPlayer from '@/components/VideoPlayer.vue'
import { useTitle } from '@vueuse/core';
import { useRouter } from 'vue-router'
const router = useRouter()
// 页面标题
useTitle('西园戒幢律寺三坛大戒法会');
......@@ -322,14 +325,17 @@ const viewMore = (type) => {
case 'masters':
console.log('跳转到三师七证页面')
// 这里可以添加跳转到三师七证页面的逻辑
router.push('/masters')
break
case 'students':
console.log('跳转到戒子页面')
// 这里可以添加跳转到戒子页面的逻辑
router.push('/students')
break
case 'volunteers':
console.log('跳转到义工页面')
// 这里可以添加跳转到义工页面的逻辑
router.push('/volunteers')
break
}
} else {
......@@ -467,6 +473,7 @@ const attachTransitionEndOnce = (looping) => {
const handleNewsClick = (item) => {
console.log('点击新闻:', item)
// 这里可以添加跳转到新闻详情页面的逻辑
router.push({ name: 'NewsDetail', params: { id: item.date } })
}
</script>
......
<template>
<div class="masters-container">
<!-- 一行一个 Item(单列) -->
<section class="single-list">
<div
class="item-card"
v-for="(item, i) in singleItems"
:key="`single-${i}`"
@click="goDetail(item)"
>
<div class="item-image">
<img :src="item.image" :alt="item.name" />
</div>
<div class="item-caption">
<div class="item-role">{{ item.role }}</div>
<div class="item-name">{{ item.name }}</div>
</div>
</div>
</section>
<!-- 一行两个 Item(双列) -->
<section class="grid-two">
<div
class="item-card small"
v-for="(item, i) in gridItems"
:key="`grid-${i}`"
@click="goDetail(item)"
>
<div class="item-image">
<img :src="item.image" :alt="item.name" />
</div>
<div class="item-caption">
<div class="item-role">{{ item.role }}</div>
<div class="item-name">{{ item.name }}</div>
</div>
</div>
</section>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useTitle } from '@vueuse/core';
const router = useRouter()
useTitle('三師七證')
// 单列数据(示例)
const singleItems = ref([
{
id: 101,
role: '中国·戒幢律寺',
name: '上明仁传师',
image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
},
{
id: 102,
role: '中国·戒幢律寺',
name: '上明仁传师',
image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
},
{
id: 103,
role: '中国·戒幢律寺',
name: '上明仁传师',
image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
}
])
// 双列数据(示例)
const gridItems = ref([
{
id: 201,
role: '传戒师',
name: '上智和法师',
image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
},
{
id: 202,
role: '教授师',
name: '上德弘法师',
image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
},
{
id: 203,
role: '羯磨师',
name: '上寂明法师',
image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
},
{
id: 204,
role: '得戒和尚',
name: '上慧觉法师',
image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
},
{
id: 205,
role: '引礼师',
name: '上圆道法师',
image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
},
{
id: 206,
role: '开导师',
name: '上演真法师',
image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
}
])
const goDetail = (item) => {
router.push(`/masters/${item.id}`)
}
</script>
<style scoped>
/* 页面容器 */
.masters-container {
padding: 1.5rem;
background: #F2EBDB;
}
/* 单列列表区 */
.single-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1rem;
}
/* 双列网格区 */
.grid-two {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
}
/* 卡片 */
.item-card {
position: relative;
background: #fff;
border: 2px solid #6B4102;
overflow: hidden;
transition: transform 0.2s ease;
padding: 0.5rem;
}
.item-card:hover {
transform: translateY(-0.125rem);
}
.item-card.small {
/* 双列卡片视觉上更紧凑 */
border: 1px solid rgba(107, 65, 2, 0.8);
}
/* 图片区域 - 采用固定纵向比例 */
.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;
background: rgba(107, 65, 2, 0.8);
color: #fff;
padding: 0.75rem;
text-align: center;
}
.item-role {
font-size: 0.75rem;
opacity: 0.95;
}
.item-name {
font-size: 0.875rem;
font-weight: 600;
margin-top: 0.25rem;
}
/* 响应式调整 */
@media (max-width: 48rem) {
.item-caption {
padding: 0.625rem;
margin: 0.5rem;
}
}
@media (max-width: 30rem) {
.masters-container {
padding: 0.75rem;
}
.item-caption {
padding: 0.5rem;
margin: 0.5rem;
}
.item-name {
font-size: 0.8125rem;
}
}
@media (max-width: 20rem) {
.grid-two {
gap: 0.5rem;
}
.item-caption {
padding: 0.375rem;
margin: 0.5rem;
}
.item-name {
font-size: 0.75rem;
}
}
</style>
<!--
* @Date: 2025-10-30 20:00:25
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-10-30 20:50:36
* @FilePath: /stdj_h5/src/views/MastersDetail.vue
* @Description: 文件描述
-->
<template>
<div class="masters-detail-container">
<section class="single-list">
<div class="item-card">
</div>
</section>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useTitle } from '@vueuse/core';
useTitle('三師七證 - 詳情')
</script>
<style scoped>
.masters-detail-container {
padding: 1.5rem;
background: #F2EBDB;
min-height: 100vh; /* 背景至少覆盖整个视口高度 */
width: 100%;
box-sizing: border-box;
}
.single-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1rem;
}
.item-card {
position: relative;
background: #fff;
border: 2px solid #6B4102;
overflow: hidden;
transition: transform 0.2s ease;
padding: 0.5rem;
}
</style>
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
<template>
<div class="page-container">
<!-- 导航栏 -->
<van-nav-bar title="三师七证" left-arrow @click-left="$router.back()" class="custom-nav">
<template #right>
<van-icon name="search" size="18" />
</template>
</van-nav-bar>
<!-- 内容区域 -->
<div class="content-container">
<!-- 顶部说明 -->
<div class="intro-section">
<div class="intro-card">
<div class="intro-icon">📜</div>
<div class="intro-content">
<h3>三师七证</h3>
<p>三师:得戒和尚、羯磨阿阇梨、教授阿阇梨<br>七证:七位证明师</p>
</div>
</div>
</div>
<!-- 筛选栏 -->
<div class="filter-section">
<van-tabs v-model:active="activeTab" @change="handleTabChange" class="custom-tabs">
<van-tab title="全部" name="all"></van-tab>
<van-tab title="三师" name="teachers"></van-tab>
<van-tab title="七证" name="witnesses"></van-tab>
</van-tabs>
</div>
<!-- 法师列表 -->
<div class="teachers-list">
<div
v-for="teacher in filteredTeachers"
:key="teacher.id"
class="teacher-card"
@click="handleTeacherClick(teacher)"
>
<div class="teacher-avatar">
<img v-if="teacher.avatar" :src="teacher.avatar" :alt="teacher.name" />
<div v-else class="avatar-placeholder">
<span>{{ teacher.name.charAt(0) }}</span>
</div>
</div>
<div class="teacher-info">
<div class="teacher-header">
<h4 class="teacher-name">{{ teacher.name }}</h4>
<div class="teacher-role" :class="getRoleClass(teacher.role)">
{{ teacher.role }}
</div>
</div>
<div class="teacher-details">
<p class="teacher-title">{{ teacher.title }}</p>
<p class="teacher-temple">{{ teacher.temple }}</p>
<div class="teacher-meta">
<span class="ordination-year">{{ teacher.ordinationYear }}年受戒</span>
<span class="experience">{{ teacher.experience }}年戒腊</span>
</div>
</div>
</div>
<div class="teacher-actions">
<van-icon name="arrow" />
</div>
</div>
</div>
<!-- 空状态 -->
<van-empty v-if="filteredTeachers.length === 0" description="暂无相关法师信息" />
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const activeTab = ref('all')
// 法师数据
const teachers = ref([
{
id: 1,
name: '慧明法师',
title: '方丈',
role: '得戒和尚',
temple: '大觉寺',
ordinationYear: 1985,
experience: 39,
avatar: null,
type: 'teacher'
},
{
id: 2,
name: '智慧法师',
title: '首座',
role: '羯磨阿阇梨',
temple: '大觉寺',
ordinationYear: 1990,
experience: 34,
avatar: null,
type: 'teacher'
},
{
id: 3,
name: '觉悟法师',
title: '监院',
role: '教授阿阇梨',
temple: '大觉寺',
ordinationYear: 1992,
experience: 32,
avatar: null,
type: 'teacher'
},
{
id: 4,
name: '慈悲法师',
title: '知客',
role: '证明师',
temple: '大觉寺',
ordinationYear: 1995,
experience: 29,
avatar: null,
type: 'witness'
},
{
id: 5,
name: '般若法师',
title: '维那',
role: '证明师',
temple: '大觉寺',
ordinationYear: 1998,
experience: 26,
avatar: null,
type: 'witness'
},
{
id: 6,
name: '禅定法师',
title: '典座',
role: '证明师',
temple: '大觉寺',
ordinationYear: 2000,
experience: 24,
avatar: null,
type: 'witness'
},
{
id: 7,
name: '精进法师',
title: '书记',
role: '证明师',
temple: '大觉寺',
ordinationYear: 2002,
experience: 22,
avatar: null,
type: 'witness'
},
{
id: 8,
name: '持戒法师',
title: '库头',
role: '证明师',
temple: '大觉寺',
ordinationYear: 2005,
experience: 19,
avatar: null,
type: 'witness'
},
{
id: 9,
name: '忍辱法师',
title: '僧值',
role: '证明师',
temple: '大觉寺',
ordinationYear: 2008,
experience: 16,
avatar: null,
type: 'witness'
},
{
id: 10,
name: '布施法师',
title: '衣钵',
role: '证明师',
temple: '大觉寺',
ordinationYear: 2010,
experience: 14,
avatar: null,
type: 'witness'
}
])
// 过滤后的法师列表
const filteredTeachers = computed(() => {
if (activeTab.value === 'all') {
return teachers.value
} else if (activeTab.value === 'teachers') {
return teachers.value.filter(teacher => teacher.type === 'teacher')
} else if (activeTab.value === 'witnesses') {
return teachers.value.filter(teacher => teacher.type === 'witness')
}
return teachers.value
})
// 获取角色样式类
const getRoleClass = (role) => {
if (role.includes('和尚') || role.includes('阿阇梨')) {
return 'role-teacher'
}
return 'role-witness'
}
// 处理标签切换
const handleTabChange = (name) => {
activeTab.value = name
}
// 处理法师点击
const handleTeacherClick = (teacher) => {
router.push(`/teachers/${teacher.id}`)
}
</script>
<style scoped>
.page-container {
min-height: 100vh;
background: #fafafa;
}
.custom-nav {
background: linear-gradient(135deg, #fbbf24, #f97316);
color: white;
}
.custom-nav :deep(.van-nav-bar__title) {
color: white;
font-weight: 600;
}
.custom-nav :deep(.van-icon) {
color: white;
}
.content-container {
padding-top: 46px;
}
.intro-section {
padding: 16px;
}
.intro-card {
background: white;
border-radius: 12px;
padding: 20px;
display: flex;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.intro-icon {
font-size: 32px;
margin-right: 16px;
}
.intro-content h3 {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0 0 8px 0;
}
.intro-content p {
font-size: 14px;
color: #666;
margin: 0;
line-height: 1.5;
}
.filter-section {
background: white;
border-bottom: 1px solid #eee;
}
.custom-tabs :deep(.van-tab) {
font-weight: 500;
}
.custom-tabs :deep(.van-tab--active) {
color: #f59e0b;
}
.custom-tabs :deep(.van-tabs__line) {
background: #f59e0b;
}
.teachers-list {
padding: 16px;
}
.teacher-card {
background: white;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.3s ease;
}
.teacher-card:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.teacher-card:active {
transform: translateY(-1px);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
}
.teacher-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
margin-right: 16px;
flex-shrink: 0;
}
.teacher-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #fbbf24, #f97316);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
font-weight: 600;
}
.teacher-info {
flex: 1;
}
.teacher-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.teacher-name {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
}
.teacher-role {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.role-teacher {
background: linear-gradient(135deg, #fbbf24, #f97316);
color: white;
}
.role-witness {
background: #f0f9ff;
color: #0369a1;
border: 1px solid #bae6fd;
}
.teacher-details {
space-y: 4px;
}
.teacher-title {
font-size: 16px;
color: #666;
margin: 0 0 4px 0;
}
.teacher-temple {
font-size: 14px;
color: #999;
margin: 0 0 8px 0;
}
.teacher-meta {
display: flex;
gap: 16px;
}
.ordination-year,
.experience {
font-size: 12px;
color: #999;
background: #f5f5f5;
padding: 2px 6px;
border-radius: 4px;
}
.teacher-actions {
margin-left: 12px;
color: #ccc;
}
</style>
\ No newline at end of file
This diff is collapsed. Click to expand it.