hookehuyr

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

重构路由配置,新增MastersDetail页面
实现戒子页面的瀑布流图片展示功能
优化新闻详情页的样式和布局
移除不再使用的Teachers相关组件
添加Vant组件类型声明和mock数据
...@@ -19,14 +19,17 @@ declare module 'vue' { ...@@ -19,14 +19,17 @@ declare module 'vue' {
19 VanCheckbox: typeof import('vant/es')['Checkbox'] 19 VanCheckbox: typeof import('vant/es')['Checkbox']
20 VanCollapse: typeof import('vant/es')['Collapse'] 20 VanCollapse: typeof import('vant/es')['Collapse']
21 VanCollapseItem: typeof import('vant/es')['CollapseItem'] 21 VanCollapseItem: typeof import('vant/es')['CollapseItem']
22 + VanEmpty: typeof import('vant/es')['Empty']
22 VanField: typeof import('vant/es')['Field'] 23 VanField: typeof import('vant/es')['Field']
23 VanForm: typeof import('vant/es')['Form'] 24 VanForm: typeof import('vant/es')['Form']
24 VanGrid: typeof import('vant/es')['Grid'] 25 VanGrid: typeof import('vant/es')['Grid']
25 VanGridItem: typeof import('vant/es')['GridItem'] 26 VanGridItem: typeof import('vant/es')['GridItem']
26 VanIcon: typeof import('vant/es')['Icon'] 27 VanIcon: typeof import('vant/es')['Icon']
27 VanImage: typeof import('vant/es')['Image'] 28 VanImage: typeof import('vant/es')['Image']
29 + VanList: typeof import('vant/es')['List']
28 VanNavBar: typeof import('vant/es')['NavBar'] 30 VanNavBar: typeof import('vant/es')['NavBar']
29 VanNoticeBar: typeof import('vant/es')['NoticeBar'] 31 VanNoticeBar: typeof import('vant/es')['NoticeBar']
32 + VanOverlay: typeof import('vant/es')['Overlay']
30 VanProgress: typeof import('vant/es')['Progress'] 33 VanProgress: typeof import('vant/es')['Progress']
31 VanRate: typeof import('vant/es')['Rate'] 34 VanRate: typeof import('vant/es')['Rate']
32 VanSidebar: typeof import('vant/es')['Sidebar'] 35 VanSidebar: typeof import('vant/es')['Sidebar']
...@@ -42,5 +45,6 @@ declare module 'vue' { ...@@ -42,5 +45,6 @@ declare module 'vue' {
42 VanTabs: typeof import('vant/es')['Tabs'] 45 VanTabs: typeof import('vant/es')['Tabs']
43 VanTag: typeof import('vant/es')['Tag'] 46 VanTag: typeof import('vant/es')['Tag']
44 VideoPlayer: typeof import('./components/VideoPlayer.vue')['default'] 47 VideoPlayer: typeof import('./components/VideoPlayer.vue')['default']
48 + WaterfallGallery: typeof import('./components/WaterfallGallery.vue')['default']
45 } 49 }
46 } 50 }
......
1 /* 1 /*
2 * @Date: 2025-10-30 10:29:15 2 * @Date: 2025-10-30 10:29:15
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-10-30 10:29:24 4 + * @LastEditTime: 2025-10-30 20:53:14
5 - * @FilePath: /itomix/h5_vite_template/src/router/index.js 5 + * @FilePath: /stdj_h5/src/router/index.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 import { createRouter, createWebHistory } from 'vue-router' 8 import { createRouter, createWebHistory } from 'vue-router'
...@@ -20,34 +20,29 @@ const routes = [ ...@@ -20,34 +20,29 @@ const routes = [
20 component: Home 20 component: Home
21 }, 21 },
22 { 22 {
23 - path: '/teachers', 23 + path: '/masters',
24 - name: 'Teachers', 24 + name: '三師七證',
25 - component: () => import('../views/Teachers.vue') 25 + component: () => import('../views/Masters.vue')
26 }, 26 },
27 { 27 {
28 - path: '/teachers/:id', 28 + path: '/masters/:id',
29 - name: 'TeacherDetail', 29 + name: '三師七證详情',
30 - component: () => import('../views/TeacherDetail.vue') 30 + component: () => import('../views/MastersDetail.vue')
31 }, 31 },
32 { 32 {
33 - path: '/volunteers', 33 + path: '/news/:id',
34 - name: 'Volunteers', 34 + name: 'NewsDetail',
35 - component: () => import('../views/Volunteers.vue') 35 + component: () => import('../views/NewsDetail.vue')
36 }, 36 },
37 { 37 {
38 path: '/students', 38 path: '/students',
39 - name: 'Students', 39 + name: '戒子',
40 component: () => import('../views/Students.vue') 40 component: () => import('../views/Students.vue')
41 }, 41 },
42 { 42 {
43 - path: '/students/:id', 43 + path: '/volunteers',
44 - name: 'StudentDetail', 44 + name: '义工',
45 - component: () => import('../views/StudentDetail.vue') 45 + component: () => import('../views/Volunteers.vue')
46 - },
47 - {
48 - path: '/news/:id',
49 - name: 'NewsDetail',
50 - component: () => import('../views/NewsDetail.vue')
51 }, 46 },
52 { 47 {
53 path: '/:pathMatch(.*)*', 48 path: '/:pathMatch(.*)*',
...@@ -74,7 +69,7 @@ router.beforeEach((to, from, next) => { ...@@ -74,7 +69,7 @@ router.beforeEach((to, from, next) => {
74 if (to.meta.title) { 69 if (to.meta.title) {
75 document.title = to.meta.title 70 document.title = to.meta.title
76 } 71 }
77 - 72 +
78 // 这里可以添加权限验证逻辑 73 // 这里可以添加权限验证逻辑
79 next() 74 next()
80 }) 75 })
...@@ -83,4 +78,4 @@ router.afterEach(() => { ...@@ -83,4 +78,4 @@ router.afterEach(() => {
83 // 路由跳转后的逻辑 78 // 路由跳转后的逻辑
84 }) 79 })
85 80
86 -export default router
...\ No newline at end of file ...\ No newline at end of file
81 +export default router
......
...@@ -240,3 +240,87 @@ export const communityPosts = [ ...@@ -240,3 +240,87 @@ export const communityPosts = [
240 createdAt: '2023-03-14T15:45:00Z' 240 createdAt: '2023-03-14T15:45:00Z'
241 } 241 }
242 ]; 242 ];
243 +
244 +// 瀑布流图片数据 - 用于义工和戒子页面
245 +export const waterfallImages = [
246 + {
247 + id: 1,
248 + url: 'https://cdn.ipadbiz.cn/mlaj/images/zMRLZh40kms.jpg',
249 + height: 300,
250 + title: '义工活动'
251 + },
252 + {
253 + id: 2,
254 + url: 'https://cdn.ipadbiz.cn/mlaj/images/27kCu7bXGEI.jpg',
255 + height: 400,
256 + title: '学习交流'
257 + },
258 + {
259 + id: 3,
260 + url: 'https://cdn.ipadbiz.cn/mlaj/images/jbwr0qZvpD4.jpg',
261 + height: 350,
262 + title: '禅修体验'
263 + },
264 + {
265 + id: 4,
266 + url: 'https://cdn.ipadbiz.cn/mlaj/images/GGCP6vshpPY.jpg',
267 + height: 280,
268 + title: '传统文化'
269 + },
270 + {
271 + id: 5,
272 + url: 'https://cdn.ipadbiz.cn/mlaj/images/2JIvboGLeho.jpg',
273 + height: 320,
274 + title: '法会现场'
275 + },
276 + {
277 + id: 6,
278 + url: 'https://cdn.ipadbiz.cn/mlaj/images/_6HzPU9Hyfg (2).jpg',
279 + height: 380,
280 + title: '诵经学习'
281 + },
282 + {
283 + id: 7,
284 + url: 'https://cdn.ipadbiz.cn/mlaj/images/2Juj2cXWB7U.jpg',
285 + height: 290,
286 + title: '义工服务'
287 + },
288 + {
289 + id: 8,
290 + url: 'https://cdn.ipadbiz.cn/mlaj/images/Y17FE9Fuw4Y.jpg',
291 + height: 360,
292 + title: '戒子生活'
293 + },
294 + {
295 + id: 9,
296 + url: 'https://cdn.ipadbiz.cn/mlaj/images/-G3rw6Y02D0.jpg',
297 + height: 310,
298 + title: '修行日常'
299 + },
300 + {
301 + id: 10,
302 + url: 'https://cdn.ipadbiz.cn/mlaj/images/Oalh2MojUuk.jpg',
303 + height: 340,
304 + title: '集体活动'
305 + }
306 +];
307 +
308 +// 生成更多瀑布流数据的函数
309 +export const generateWaterfallData = (page = 1, pageSize = 10) => {
310 + const baseImages = waterfallImages;
311 + const startIndex = (page - 1) * pageSize;
312 + const data = [];
313 +
314 + for (let i = 0; i < pageSize; i++) {
315 + const baseIndex = i % baseImages.length;
316 + const baseImage = baseImages[baseIndex];
317 + data.push({
318 + ...baseImage,
319 + id: startIndex + i + 1,
320 + height: Math.floor(Math.random() * 200) + 250, // 随机高度 250-450px
321 + title: `${baseImage.title} ${Math.floor((startIndex + i) / baseImages.length) + 1}`
322 + });
323 + }
324 +
325 + return data;
326 +};
......
...@@ -187,6 +187,9 @@ ...@@ -187,6 +187,9 @@
187 import { ref, computed, nextTick, onMounted, watch } from 'vue' 187 import { ref, computed, nextTick, onMounted, watch } from 'vue'
188 import VideoPlayer from '@/components/VideoPlayer.vue' 188 import VideoPlayer from '@/components/VideoPlayer.vue'
189 import { useTitle } from '@vueuse/core'; 189 import { useTitle } from '@vueuse/core';
190 +import { useRouter } from 'vue-router'
191 +
192 +const router = useRouter()
190 193
191 // 页面标题 194 // 页面标题
192 useTitle('西园戒幢律寺三坛大戒法会'); 195 useTitle('西园戒幢律寺三坛大戒法会');
...@@ -322,14 +325,17 @@ const viewMore = (type) => { ...@@ -322,14 +325,17 @@ const viewMore = (type) => {
322 case 'masters': 325 case 'masters':
323 console.log('跳转到三师七证页面') 326 console.log('跳转到三师七证页面')
324 // 这里可以添加跳转到三师七证页面的逻辑 327 // 这里可以添加跳转到三师七证页面的逻辑
328 + router.push('/masters')
325 break 329 break
326 case 'students': 330 case 'students':
327 console.log('跳转到戒子页面') 331 console.log('跳转到戒子页面')
328 // 这里可以添加跳转到戒子页面的逻辑 332 // 这里可以添加跳转到戒子页面的逻辑
333 + router.push('/students')
329 break 334 break
330 case 'volunteers': 335 case 'volunteers':
331 console.log('跳转到义工页面') 336 console.log('跳转到义工页面')
332 // 这里可以添加跳转到义工页面的逻辑 337 // 这里可以添加跳转到义工页面的逻辑
338 + router.push('/volunteers')
333 break 339 break
334 } 340 }
335 } else { 341 } else {
...@@ -467,6 +473,7 @@ const attachTransitionEndOnce = (looping) => { ...@@ -467,6 +473,7 @@ const attachTransitionEndOnce = (looping) => {
467 const handleNewsClick = (item) => { 473 const handleNewsClick = (item) => {
468 console.log('点击新闻:', item) 474 console.log('点击新闻:', item)
469 // 这里可以添加跳转到新闻详情页面的逻辑 475 // 这里可以添加跳转到新闻详情页面的逻辑
476 + router.push({ name: 'NewsDetail', params: { id: item.date } })
470 } 477 }
471 </script> 478 </script>
472 479
......
1 +<template>
2 + <div class="masters-container">
3 + <!-- 一行一个 Item(单列) -->
4 + <section class="single-list">
5 + <div
6 + class="item-card"
7 + v-for="(item, i) in singleItems"
8 + :key="`single-${i}`"
9 + @click="goDetail(item)"
10 + >
11 + <div class="item-image">
12 + <img :src="item.image" :alt="item.name" />
13 + </div>
14 + <div class="item-caption">
15 + <div class="item-role">{{ item.role }}</div>
16 + <div class="item-name">{{ item.name }}</div>
17 + </div>
18 + </div>
19 + </section>
20 +
21 + <!-- 一行两个 Item(双列) -->
22 + <section class="grid-two">
23 + <div
24 + class="item-card small"
25 + v-for="(item, i) in gridItems"
26 + :key="`grid-${i}`"
27 + @click="goDetail(item)"
28 + >
29 + <div class="item-image">
30 + <img :src="item.image" :alt="item.name" />
31 + </div>
32 + <div class="item-caption">
33 + <div class="item-role">{{ item.role }}</div>
34 + <div class="item-name">{{ item.name }}</div>
35 + </div>
36 + </div>
37 + </section>
38 + </div>
39 +</template>
40 +
41 +<script setup>
42 +import { ref } from 'vue'
43 +import { useRouter } from 'vue-router'
44 +import { useTitle } from '@vueuse/core';
45 +
46 +const router = useRouter()
47 +
48 +useTitle('三師七證')
49 +
50 +// 单列数据(示例)
51 +const singleItems = ref([
52 + {
53 + id: 101,
54 + role: '中国·戒幢律寺',
55 + name: '上明仁传师',
56 + image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
57 + },
58 + {
59 + id: 102,
60 + role: '中国·戒幢律寺',
61 + name: '上明仁传师',
62 + image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
63 + },
64 + {
65 + id: 103,
66 + role: '中国·戒幢律寺',
67 + name: '上明仁传师',
68 + image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
69 + }
70 +])
71 +
72 +// 双列数据(示例)
73 +const gridItems = ref([
74 + {
75 + id: 201,
76 + role: '传戒师',
77 + name: '上智和法师',
78 + image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
79 + },
80 + {
81 + id: 202,
82 + role: '教授师',
83 + name: '上德弘法师',
84 + image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
85 + },
86 + {
87 + id: 203,
88 + role: '羯磨师',
89 + name: '上寂明法师',
90 + image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
91 + },
92 + {
93 + id: 204,
94 + role: '得戒和尚',
95 + name: '上慧觉法师',
96 + image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
97 + },
98 + {
99 + id: 205,
100 + role: '引礼师',
101 + name: '上圆道法师',
102 + image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
103 + },
104 + {
105 + id: 206,
106 + role: '开导师',
107 + name: '上演真法师',
108 + image: '/src/assets/images/02 西园戒幢律寺三坛大戒法会/截图/全家福3_副本.jpg'
109 + }
110 +])
111 +
112 +const goDetail = (item) => {
113 + router.push(`/masters/${item.id}`)
114 +}
115 +</script>
116 +
117 +<style scoped>
118 +/* 页面容器 */
119 +.masters-container {
120 + padding: 1.5rem;
121 + background: #F2EBDB;
122 +}
123 +
124 +/* 单列列表区 */
125 +.single-list {
126 + display: flex;
127 + flex-direction: column;
128 + gap: 1rem;
129 + margin-bottom: 1rem;
130 +}
131 +
132 +/* 双列网格区 */
133 +.grid-two {
134 + display: grid;
135 + grid-template-columns: repeat(2, 1fr);
136 + gap: 0.75rem;
137 +}
138 +
139 +/* 卡片 */
140 +.item-card {
141 + position: relative;
142 + background: #fff;
143 + border: 2px solid #6B4102;
144 + overflow: hidden;
145 + transition: transform 0.2s ease;
146 + padding: 0.5rem;
147 +}
148 +
149 +.item-card:hover {
150 + transform: translateY(-0.125rem);
151 +}
152 +
153 +.item-card.small {
154 + /* 双列卡片视觉上更紧凑 */
155 + border: 1px solid rgba(107, 65, 2, 0.8);
156 +}
157 +
158 +/* 图片区域 - 采用固定纵向比例 */
159 +.item-image {
160 + width: 100%;
161 + aspect-ratio: 3 / 4;
162 + background: #f5f5f5;
163 +}
164 +
165 +.item-image img {
166 + width: 100%;
167 + height: 100%;
168 + object-fit: cover;
169 + display: block;
170 +}
171 +
172 +/* 底部说明条 */
173 +.item-caption {
174 + position: absolute;
175 + left: 0;
176 + right: 0;
177 + bottom: 0;
178 + background: rgba(107, 65, 2, 0.8);
179 + color: #fff;
180 + padding: 0.75rem;
181 + text-align: center;
182 +}
183 +
184 +.item-role {
185 + font-size: 0.75rem;
186 + opacity: 0.95;
187 +}
188 +
189 +.item-name {
190 + font-size: 0.875rem;
191 + font-weight: 600;
192 + margin-top: 0.25rem;
193 +}
194 +
195 +/* 响应式调整 */
196 +@media (max-width: 48rem) {
197 + .item-caption {
198 + padding: 0.625rem;
199 + margin: 0.5rem;
200 + }
201 +}
202 +
203 +@media (max-width: 30rem) {
204 + .masters-container {
205 + padding: 0.75rem;
206 + }
207 + .item-caption {
208 + padding: 0.5rem;
209 + margin: 0.5rem;
210 + }
211 + .item-name {
212 + font-size: 0.8125rem;
213 + }
214 +}
215 +
216 +@media (max-width: 20rem) {
217 + .grid-two {
218 + gap: 0.5rem;
219 + }
220 + .item-caption {
221 + padding: 0.375rem;
222 + margin: 0.5rem;
223 + }
224 + .item-name {
225 + font-size: 0.75rem;
226 + }
227 +}
228 +</style>
1 +<!--
2 + * @Date: 2025-10-30 20:00:25
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-10-30 20:50:36
5 + * @FilePath: /stdj_h5/src/views/MastersDetail.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <div class="masters-detail-container">
10 + <section class="single-list">
11 + <div class="item-card">
12 +
13 + </div>
14 + </section>
15 + </div>
16 +</template>
17 +
18 +<script setup>
19 +import { ref } from 'vue'
20 +import { useTitle } from '@vueuse/core';
21 +
22 +useTitle('三師七證 - 詳情')
23 +</script>
24 +
25 +
26 +<style scoped>
27 +.masters-detail-container {
28 + padding: 1.5rem;
29 + background: #F2EBDB;
30 + min-height: 100vh; /* 背景至少覆盖整个视口高度 */
31 + width: 100%;
32 + box-sizing: border-box;
33 +}
34 +
35 +.single-list {
36 + display: flex;
37 + flex-direction: column;
38 + gap: 1rem;
39 + margin-bottom: 1rem;
40 +}
41 +
42 +.item-card {
43 + position: relative;
44 + background: #fff;
45 + border: 2px solid #6B4102;
46 + overflow: hidden;
47 + transition: transform 0.2s ease;
48 + padding: 0.5rem;
49 +}
50 +
51 +</style>
1 <template> 1 <template>
2 <div class="page-container"> 2 <div class="page-container">
3 - <!-- 导航栏 -->
4 - <van-nav-bar title="新闻详情" left-arrow @click-left="$router.back()" class="custom-nav">
5 - <template #right>
6 - <van-icon name="share-o" size="18" @click="handleShare" />
7 - </template>
8 - </van-nav-bar>
9 -
10 <!-- 内容区域 --> 3 <!-- 内容区域 -->
11 <div class="content-container"> 4 <div class="content-container">
12 <!-- 文章头部 --> 5 <!-- 文章头部 -->
...@@ -14,152 +7,21 @@ ...@@ -14,152 +7,21 @@
14 <h1 class="article-title">{{ article.title }}</h1> 7 <h1 class="article-title">{{ article.title }}</h1>
15 <div class="article-meta"> 8 <div class="article-meta">
16 <div class="meta-info"> 9 <div class="meta-info">
17 - <span class="publish-date">{{ article.publishDate }}</span> 10 + <van-icon name="clock-o" /><span class="publish-date">{{ article.publishDate }}</span>
18 - <span class="author">{{ article.author }}</span> 11 + <van-icon name="manager-o" /><span class="author">{{ article.author }}</span>
19 </div> 12 </div>
20 - <div class="article-stats">
21 - <span class="view-count">
22 - <van-icon name="eye-o" />
23 - {{ article.viewCount }}
24 - </span>
25 - <span class="like-count" @click="handleLike">
26 - <van-icon :name="article.isLiked ? 'like' : 'like-o'" :color="article.isLiked ? '#ff6b6b' : '#999'" />
27 - {{ article.likeCount }}
28 - </span>
29 - </div>
30 - </div>
31 -
32 - <!-- 标签 -->
33 - <div class="article-tags" v-if="article.tags && article.tags.length">
34 - <van-tag
35 - v-for="tag in article.tags"
36 - :key="tag"
37 - type="primary"
38 - size="small"
39 - class="article-tag"
40 - >
41 - {{ tag }}
42 - </van-tag>
43 </div> 13 </div>
44 </div> 14 </div>
45 - 15 +
46 <!-- 文章封面 --> 16 <!-- 文章封面 -->
47 <div class="article-cover" v-if="article.coverImage"> 17 <div class="article-cover" v-if="article.coverImage">
48 <img :src="article.coverImage" :alt="article.title" /> 18 <img :src="article.coverImage" :alt="article.title" />
49 </div> 19 </div>
50 - 20 +
51 <!-- 文章内容 --> 21 <!-- 文章内容 -->
52 <div class="article-content"> 22 <div class="article-content">
53 <div class="content-text" v-html="article.content"></div> 23 <div class="content-text" v-html="article.content"></div>
54 </div> 24 </div>
55 -
56 - <!-- 文章底部 -->
57 - <div class="article-footer">
58 - <div class="footer-actions">
59 - <div class="action-item" @click="handleLike">
60 - <van-icon :name="article.isLiked ? 'like' : 'like-o'" :color="article.isLiked ? '#ff6b6b' : '#666'" size="20" />
61 - <span>{{ article.isLiked ? '已赞' : '点赞' }}</span>
62 - </div>
63 - <div class="action-item" @click="handleCollect">
64 - <van-icon :name="article.isCollected ? 'star' : 'star-o'" :color="article.isCollected ? '#fbbf24' : '#666'" size="20" />
65 - <span>{{ article.isCollected ? '已收藏' : '收藏' }}</span>
66 - </div>
67 - <div class="action-item" @click="handleShare">
68 - <van-icon name="share-o" color="#666" size="20" />
69 - <span>分享</span>
70 - </div>
71 - <div class="action-item" @click="handleComment">
72 - <van-icon name="chat-o" color="#666" size="20" />
73 - <span>评论</span>
74 - </div>
75 - </div>
76 - </div>
77 -
78 - <!-- 相关文章 -->
79 - <div class="related-articles" v-if="relatedArticles.length">
80 - <h3 class="section-title">相关文章</h3>
81 - <div class="related-list">
82 - <div
83 - v-for="related in relatedArticles"
84 - :key="related.id"
85 - class="related-item"
86 - @click="handleRelatedClick(related)"
87 - >
88 - <div class="related-cover">
89 - <img v-if="related.coverImage" :src="related.coverImage" :alt="related.title" />
90 - <div v-else class="cover-placeholder">
91 - <van-icon name="photo-o" size="24" color="#ccc" />
92 - </div>
93 - </div>
94 - <div class="related-info">
95 - <h4 class="related-title">{{ related.title }}</h4>
96 - <div class="related-meta">
97 - <span class="related-date">{{ related.publishDate }}</span>
98 - <span class="related-views">{{ related.viewCount }}阅读</span>
99 - </div>
100 - </div>
101 - </div>
102 - </div>
103 - </div>
104 -
105 - <!-- 评论区 -->
106 - <div class="comments-section">
107 - <h3 class="section-title">评论 ({{ comments.length }})</h3>
108 -
109 - <!-- 评论输入 -->
110 - <div class="comment-input">
111 - <van-field
112 - v-model="commentText"
113 - type="textarea"
114 - placeholder="写下你的评论..."
115 - rows="3"
116 - maxlength="500"
117 - show-word-limit
118 - />
119 - <van-button
120 - type="primary"
121 - size="small"
122 - @click="handleSubmitComment"
123 - :disabled="!commentText.trim()"
124 - class="submit-btn"
125 - >
126 - 发表
127 - </van-button>
128 - </div>
129 -
130 - <!-- 评论列表 -->
131 - <div class="comments-list">
132 - <div
133 - v-for="comment in comments"
134 - :key="comment.id"
135 - class="comment-item"
136 - >
137 - <div class="comment-avatar">
138 - <img v-if="comment.avatar" :src="comment.avatar" :alt="comment.author" />
139 - <div v-else class="avatar-placeholder">
140 - <span>{{ comment.author.charAt(0) }}</span>
141 - </div>
142 - </div>
143 - <div class="comment-content">
144 - <div class="comment-header">
145 - <span class="comment-author">{{ comment.author }}</span>
146 - <span class="comment-date">{{ comment.date }}</span>
147 - </div>
148 - <p class="comment-text">{{ comment.content }}</p>
149 - <div class="comment-actions">
150 - <span class="comment-like" @click="handleCommentLike(comment)">
151 - <van-icon :name="comment.isLiked ? 'like' : 'like-o'" :color="comment.isLiked ? '#ff6b6b' : '#999'" size="14" />
152 - {{ comment.likeCount || '' }}
153 - </span>
154 - <span class="comment-reply" @click="handleCommentReply(comment)">回复</span>
155 - </div>
156 - </div>
157 - </div>
158 - </div>
159 -
160 - <!-- 空状态 -->
161 - <van-empty v-if="comments.length === 0" description="暂无评论,快来抢沙发吧~" />
162 - </div>
163 </div> 25 </div>
164 </div> 26 </div>
165 </template> 27 </template>
...@@ -177,158 +39,26 @@ const commentText = ref('') ...@@ -177,158 +39,26 @@ const commentText = ref('')
177 // 文章数据 39 // 文章数据
178 const article = ref({ 40 const article = ref({
179 id: 1, 41 id: 1,
180 - title: '三坛大戒传戒法会圆满举行', 42 + title: '三坛大戒 | 护戒胜福田 成就最上因',
181 content: ` 43 content: `
182 - <p>2024年春季三坛大戒传戒法会于今日在本寺圆满举行。此次法会历时21天,共有来自全国各地的68位戒子参加,在诸位戒师的慈悲教导下,圆满受持三坛大戒。</p> 44 + <p>为弘承世尊遗教,光大如来戒法,绍隆佛种,续佛慧命,经研究并报中国佛教协会批复同意(中佛会〔2025〕35号),由江苏省佛教协会主办,苏州市佛教协会、苏州市姑苏区佛教协会、苏州工业园区佛教协会协办,苏州西园戒幢律寺、苏州工业园区积善寺承办的传戒三坛大戒法会定于2025年10月15日至11月15日举办。</p>
183 - 45 +
184 - <p>三坛大戒是佛教出家众必须受持的重要戒律,包括沙弥戒、比丘戒和菩萨戒三个层次。通过严格的戒律学习和实践,戒子们不仅在戒律知识上有了深入的理解,更在修行品格上得到了显著提升。</p> 46 + <h3>天增上三学,以戒为首</h3>
185 - 47 + <h3>佛灭度后,以戒为师</h3>
186 - <p>在传戒期间,戒子们每日早起晚睡,精进修学。从戒律理论的学习到实际生活中的践行,从个人修持到集体共修,每一个环节都体现了佛教戒律的庄严与神圣。</p> 48 +
187 - 49 + <p>《梵网经》云:"众生受佛戒,即入诸佛位。位同大觉已,真是诸佛子。"传戒法会,具称"千佛三坛大戒法会",三坛大戒是成就僧人最重要的增上缘仪式,谓授千佛共同所制戒。戒场分非同一般之法会场所,庄妙标有,无过之者。</p>
188 - <p>本次传戒法会得到了十方善信的大力护持,义工菩萨们日夜辛劳,为法会的顺利进行提供了有力保障。在此向所有护持法会的善信表示衷心感谢。</p> 50 +
189 - 51 + <p>今苏州西园戒幢律寺作为主戒场传戒三坛大戒,正值因时,其事庄严,此意成就道场通达。</p>
190 - <p>愿诸位新戒比丘能够严持净戒,精进修行,早证菩提,广度众生。愿佛法久住,法轮常转,众生离苦得乐。</p>
191 `, 52 `,
192 - author: '释智慧法师', 53 + author: '戒幢律寺编辑部',
193 - publishDate: '2024-01-15', 54 + publishDate: '2025-10-1',
194 - viewCount: 1256, 55 + viewCount: 2156,
195 - likeCount: 89, 56 + likeCount: 156,
196 isLiked: false, 57 isLiked: false,
197 isCollected: false, 58 isCollected: false,
198 - coverImage: null, 59 + coverImage: 'https://images.unsplash.com/photo-1545558014-8692077e9b5c?w=800&h=600&fit=crop',
199 - tags: ['三坛大戒', '传戒法会', '戒律', '修行']
200 }) 60 })
201 61
202 -// 相关文章
203 -const relatedArticles = ref([
204 - {
205 - id: 2,
206 - title: '戒律学习的重要性与方法',
207 - publishDate: '2024-01-10',
208 - viewCount: 856,
209 - coverImage: null
210 - },
211 - {
212 - id: 3,
213 - title: '沙弥戒的基本要求与实践',
214 - publishDate: '2024-01-08',
215 - viewCount: 642,
216 - coverImage: null
217 - },
218 - {
219 - id: 4,
220 - title: '比丘戒的深层含义解析',
221 - publishDate: '2024-01-05',
222 - viewCount: 789,
223 - coverImage: null
224 - }
225 -])
226 -
227 -// 评论数据
228 -const comments = ref([
229 - {
230 - id: 1,
231 - author: '慧心居士',
232 - content: '随喜赞叹!三坛大戒的传承是佛教的重要传统,愿新戒比丘们都能严持净戒,精进修行。',
233 - date: '2024-01-15 14:30',
234 - likeCount: 12,
235 - isLiked: false,
236 - avatar: null
237 - },
238 - {
239 - id: 2,
240 - author: '觉悟行者',
241 - content: '感恩诸位法师的慈悲教导,戒律是修行的基础,希望能有更多这样的法会。',
242 - date: '2024-01-15 15:45',
243 - likeCount: 8,
244 - isLiked: false,
245 - avatar: null
246 - },
247 - {
248 - id: 3,
249 - author: '清净莲花',
250 - content: '阿弥陀佛!看到这样的法会真是法喜充满,愿佛法久住世间。',
251 - date: '2024-01-15 16:20',
252 - likeCount: 5,
253 - isLiked: false,
254 - avatar: null
255 - }
256 -])
257 -
258 -// 处理点赞
259 -const handleLike = () => {
260 - article.value.isLiked = !article.value.isLiked
261 - if (article.value.isLiked) {
262 - article.value.likeCount++
263 - Toast('点赞成功')
264 - } else {
265 - article.value.likeCount--
266 - Toast('取消点赞')
267 - }
268 -}
269 -
270 -// 处理收藏
271 -const handleCollect = () => {
272 - article.value.isCollected = !article.value.isCollected
273 - if (article.value.isCollected) {
274 - Toast('收藏成功')
275 - } else {
276 - Toast('取消收藏')
277 - }
278 -}
279 -
280 -// 处理分享
281 -const handleShare = () => {
282 - Toast('分享功能开发中...')
283 -}
284 -
285 -// 处理评论
286 -const handleComment = () => {
287 - document.querySelector('.comment-input').scrollIntoView({ behavior: 'smooth' })
288 -}
289 -
290 -// 处理相关文章点击
291 -const handleRelatedClick = (related) => {
292 - router.push(`/news/${related.id}`)
293 -}
294 -
295 -// 提交评论
296 -const handleSubmitComment = () => {
297 - if (!commentText.value.trim()) {
298 - Toast('请输入评论内容')
299 - return
300 - }
301 -
302 - const newComment = {
303 - id: Date.now(),
304 - author: '当前用户',
305 - content: commentText.value.trim(),
306 - date: new Date().toLocaleString('zh-CN'),
307 - likeCount: 0,
308 - isLiked: false,
309 - avatar: null
310 - }
311 -
312 - comments.value.unshift(newComment)
313 - commentText.value = ''
314 - Toast('评论发表成功')
315 -}
316 -
317 -// 处理评论点赞
318 -const handleCommentLike = (comment) => {
319 - comment.isLiked = !comment.isLiked
320 - if (comment.isLiked) {
321 - comment.likeCount = (comment.likeCount || 0) + 1
322 - } else {
323 - comment.likeCount = Math.max(0, (comment.likeCount || 0) - 1)
324 - }
325 -}
326 -
327 -// 处理评论回复
328 -const handleCommentReply = (comment) => {
329 - Toast('回复功能开发中...')
330 -}
331 -
332 // 组件挂载时加载数据 62 // 组件挂载时加载数据
333 onMounted(() => { 63 onMounted(() => {
334 const articleId = route.params.id 64 const articleId = route.params.id
...@@ -340,339 +70,89 @@ onMounted(() => { ...@@ -340,339 +70,89 @@ onMounted(() => {
340 <style scoped> 70 <style scoped>
341 .page-container { 71 .page-container {
342 min-height: 100vh; 72 min-height: 100vh;
343 - background: #fafafa; 73 + background: #f5f5f5;
344 -}
345 -
346 -.custom-nav {
347 - background: linear-gradient(135deg, #3b82f6, #1d4ed8);
348 - color: white;
349 -}
350 -
351 -.custom-nav :deep(.van-nav-bar__title) {
352 - color: white;
353 - font-weight: 600;
354 -}
355 -
356 -.custom-nav :deep(.van-icon) {
357 - color: white;
358 } 74 }
359 75
360 .content-container { 76 .content-container {
361 - padding-top: 46px; 77 + background-color: #F2EBDB;
362 } 78 }
363 79
364 .article-header { 80 .article-header {
365 - background: white; 81 + padding: 1rem;
366 - padding: 20px;
367 - margin-bottom: 12px;
368 } 82 }
369 83
370 .article-title { 84 .article-title {
371 - font-size: 24px; 85 + font-size: 1.25rem;
372 font-weight: 700; 86 font-weight: 700;
373 - color: #333; 87 + color: #8b4513;
374 line-height: 1.4; 88 line-height: 1.4;
375 - margin: 0 0 16px 0; 89 + margin: 0 0 1rem 0;
90 + text-align: center;
376 } 91 }
377 92
378 .article-meta { 93 .article-meta {
379 display: flex; 94 display: flex;
380 - justify-content: space-between; 95 + justify-content: center;
381 align-items: center; 96 align-items: center;
382 - margin-bottom: 16px; 97 + margin-bottom: 1rem;
98 + gap: 1rem;
383 } 99 }
384 100
385 .meta-info { 101 .meta-info {
386 display: flex; 102 display: flex;
387 - gap: 16px; 103 + gap: 1rem;
104 + align-items: center;
388 } 105 }
389 106
390 .publish-date, 107 .publish-date,
391 .author { 108 .author {
392 - font-size: 14px; 109 + font-size: 0.875rem;
393 - color: #666; 110 + color: #8b4513;
394 -}
395 -
396 -.article-stats {
397 - display: flex;
398 - gap: 16px;
399 -}
400 -
401 -.view-count,
402 -.like-count {
403 display: flex; 111 display: flex;
404 align-items: center; 112 align-items: center;
405 - gap: 4px; 113 + gap: 0.25rem;
406 - font-size: 14px;
407 - color: #666;
408 - cursor: pointer;
409 -}
410 -
411 -.article-tags {
412 - display: flex;
413 - gap: 8px;
414 - flex-wrap: wrap;
415 -}
416 -
417 -.article-tag {
418 - background: #e0f2fe !important;
419 - color: #0277bd !important;
420 - border: 1px solid #81d4fa !important;
421 } 114 }
422 115
423 .article-cover { 116 .article-cover {
424 - background: white; 117 + margin: 1rem 0;
425 - padding: 0 20px 20px;
426 - margin-bottom: 12px;
427 } 118 }
428 119
429 .article-cover img { 120 .article-cover img {
430 width: 100%; 121 width: 100%;
431 - border-radius: 8px; 122 + border-radius: 0.5rem;
123 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
432 } 124 }
433 125
434 .article-content { 126 .article-content {
435 - background: white; 127 + background: #f9f7f4;
436 - padding: 20px; 128 + padding: 1.5rem;
437 - margin-bottom: 12px; 129 + margin: 1rem 0;
130 + border-radius: 1rem;
131 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
132 + border: 1px solid #e8e3dc;
438 } 133 }
439 134
440 .content-text { 135 .content-text {
441 - font-size: 16px; 136 + font-size: 1rem;
442 line-height: 1.8; 137 line-height: 1.8;
443 - color: #333; 138 + color: #5d4e37;
444 } 139 }
445 140
446 .content-text :deep(p) { 141 .content-text :deep(p) {
447 - margin: 0 0 16px 0; 142 + margin: 0 0 1rem 0;
448 text-indent: 2em; 143 text-indent: 2em;
449 } 144 }
450 145
451 -.content-text :deep(p:last-child) { 146 +.content-text :deep(h3) {
452 - margin-bottom: 0; 147 + font-size: 1.125rem;
453 -}
454 -
455 -.article-footer {
456 - background: white;
457 - padding: 20px;
458 - margin-bottom: 12px;
459 - border-top: 1px solid #eee;
460 -}
461 -
462 -.footer-actions {
463 - display: flex;
464 - justify-content: space-around;
465 -}
466 -
467 -.action-item {
468 - display: flex;
469 - flex-direction: column;
470 - align-items: center;
471 - gap: 8px;
472 - cursor: pointer;
473 - padding: 12px;
474 - border-radius: 8px;
475 - transition: background-color 0.2s;
476 -}
477 -
478 -.action-item:active {
479 - background: #f5f5f5;
480 -}
481 -
482 -.action-item span {
483 - font-size: 12px;
484 - color: #666;
485 -}
486 -
487 -.related-articles {
488 - background: white;
489 - padding: 20px;
490 - margin-bottom: 12px;
491 -}
492 -
493 -.section-title {
494 - font-size: 18px;
495 font-weight: 600; 148 font-weight: 600;
496 - color: #333; 149 + color: #8b4513;
497 - margin: 0 0 16px 0; 150 + text-align: center;
498 - padding-left: 4px; 151 + margin: 1.5rem 0 1rem 0;
499 - border-left: 4px solid #3b82f6; 152 + text-indent: 0;
500 } 153 }
501 154
502 -.related-list { 155 +.content-text :deep(p:last-child) {
503 - space-y: 12px; 156 + margin-bottom: 0;
504 -}
505 -
506 -.related-item {
507 - display: flex;
508 - gap: 12px;
509 - padding: 12px;
510 - border-radius: 8px;
511 - cursor: pointer;
512 - transition: background-color 0.2s;
513 - margin-bottom: 12px;
514 -}
515 -
516 -.related-item:active {
517 - background: #f5f5f5;
518 -}
519 -
520 -.related-cover {
521 - width: 80px;
522 - height: 60px;
523 - border-radius: 6px;
524 - overflow: hidden;
525 - flex-shrink: 0;
526 - background: #f5f5f5;
527 -}
528 -
529 -.related-cover img {
530 - width: 100%;
531 - height: 100%;
532 - object-fit: cover;
533 -}
534 -
535 -.cover-placeholder {
536 - width: 100%;
537 - height: 100%;
538 - display: flex;
539 - align-items: center;
540 - justify-content: center;
541 - background: #f5f5f5;
542 -}
543 -
544 -.related-info {
545 - flex: 1;
546 -}
547 -
548 -.related-title {
549 - font-size: 16px;
550 - font-weight: 500;
551 - color: #333;
552 - margin: 0 0 8px 0;
553 - line-height: 1.4;
554 - display: -webkit-box;
555 - -webkit-line-clamp: 2;
556 - -webkit-box-orient: vertical;
557 - overflow: hidden;
558 -}
559 -
560 -.related-meta {
561 - display: flex;
562 - gap: 12px;
563 -}
564 -
565 -.related-date,
566 -.related-views {
567 - font-size: 12px;
568 - color: #999;
569 -}
570 -
571 -.comments-section {
572 - background: white;
573 - padding: 20px;
574 - margin-bottom: 20px;
575 -}
576 -
577 -.comment-input {
578 - margin-bottom: 20px;
579 - position: relative;
580 -}
581 -
582 -.submit-btn {
583 - position: absolute;
584 - bottom: 12px;
585 - right: 12px;
586 - background: #3b82f6 !important;
587 - border-color: #3b82f6 !important;
588 -}
589 -
590 -.comments-list {
591 - space-y: 16px;
592 -}
593 -
594 -.comment-item {
595 - display: flex;
596 - gap: 12px;
597 - padding: 16px 0;
598 - border-bottom: 1px solid #f0f0f0;
599 -}
600 -
601 -.comment-item:last-child {
602 - border-bottom: none;
603 -}
604 -
605 -.comment-avatar {
606 - width: 40px;
607 - height: 40px;
608 - border-radius: 50%;
609 - overflow: hidden;
610 - flex-shrink: 0;
611 -}
612 -
613 -.comment-avatar img {
614 - width: 100%;
615 - height: 100%;
616 - object-fit: cover;
617 -}
618 -
619 -.avatar-placeholder {
620 - width: 100%;
621 - height: 100%;
622 - background: linear-gradient(135deg, #3b82f6, #1d4ed8);
623 - display: flex;
624 - align-items: center;
625 - justify-content: center;
626 - color: white;
627 - font-size: 16px;
628 - font-weight: 600;
629 -}
630 -
631 -.comment-content {
632 - flex: 1;
633 -}
634 -
635 -.comment-header {
636 - display: flex;
637 - justify-content: space-between;
638 - align-items: center;
639 - margin-bottom: 8px;
640 -}
641 -
642 -.comment-author {
643 - font-size: 14px;
644 - font-weight: 500;
645 - color: #333;
646 -}
647 -
648 -.comment-date {
649 - font-size: 12px;
650 - color: #999;
651 -}
652 -
653 -.comment-text {
654 - font-size: 14px;
655 - color: #666;
656 - line-height: 1.6;
657 - margin: 0 0 12px 0;
658 -}
659 -
660 -.comment-actions {
661 - display: flex;
662 - gap: 16px;
663 -}
664 -
665 -.comment-like,
666 -.comment-reply {
667 - display: flex;
668 - align-items: center;
669 - gap: 4px;
670 - font-size: 12px;
671 - color: #999;
672 - cursor: pointer;
673 -}
674 -
675 -.comment-reply:hover {
676 - color: #3b82f6;
677 } 157 }
678 -</style>
...\ No newline at end of file ...\ No newline at end of file
158 +</style>
......
1 -<template>
2 - <div class="page-container">
3 - <!-- 导航栏 -->
4 - <van-nav-bar title="戒子详情" left-arrow @click-left="$router.back()" class="custom-nav">
5 - <template #right>
6 - <van-icon name="edit" size="18" @click="handleEdit" />
7 - </template>
8 - </van-nav-bar>
9 -
10 - <!-- 内容区域 -->
11 - <div class="content-container">
12 - <!-- 戒子基本信息 -->
13 - <div class="profile-section">
14 - <div class="profile-header">
15 - <div class="avatar-container">
16 - <img v-if="student.avatar" :src="student.avatar" :alt="student.name" class="avatar" />
17 - <div v-else class="avatar-placeholder">
18 - <span>{{ student.name.charAt(0) }}</span>
19 - </div>
20 - <div class="precept-badge" :class="getPreceptClass(student.preceptType)">
21 - {{ getPreceptText(student.preceptType) }}
22 - </div>
23 - </div>
24 -
25 - <div class="profile-info">
26 - <h2 class="student-name">{{ student.name }}</h2>
27 - <p class="student-dharma-name">法名:{{ student.dharmaName }}</p>
28 - <p class="student-temple">{{ student.temple }}</p>
29 - <div class="status-badge" :class="getStatusClass(student.status)">
30 - {{ getStatusText(student.status) }}
31 - </div>
32 - </div>
33 - </div>
34 -
35 - <!-- 统计信息 -->
36 - <div class="stats-grid">
37 - <div class="stat-item">
38 - <div class="stat-value">{{ student.studyDays }}</div>
39 - <div class="stat-label">学习天数</div>
40 - </div>
41 - <div class="stat-item">
42 - <div class="stat-value">{{ student.progress }}%</div>
43 - <div class="stat-label">学习进度</div>
44 - </div>
45 - <div class="stat-item">
46 - <div class="stat-value">{{ student.completedCourses }}</div>
47 - <div class="stat-label">完成课程</div>
48 - </div>
49 - <div class="stat-item">
50 - <div class="stat-value">{{ student.merits }}</div>
51 - <div class="stat-label">功德积分</div>
52 - </div>
53 - </div>
54 - </div>
55 -
56 - <!-- 详细信息 -->
57 - <div class="details-section">
58 - <!-- 基本信息 -->
59 - <van-cell-group title="基本信息" class="info-group">
60 - <van-cell title="戒师" :value="student.teacher" />
61 - <van-cell title="入学时间" :value="student.entryDate" />
62 - <van-cell title="预计毕业" :value="student.expectedGraduation" />
63 - <van-cell title="籍贯" :value="student.hometown" />
64 - <van-cell title="年龄" :value="student.age + '岁'" />
65 - </van-cell-group>
66 -
67 - <!-- 学习进度 -->
68 - <div class="progress-section">
69 - <h3 class="section-title">学习进度</h3>
70 - <div class="progress-card">
71 - <div class="progress-header">
72 - <span class="progress-text">总体进度</span>
73 - <span class="progress-percent">{{ student.progress }}%</span>
74 - </div>
75 - <van-progress :percentage="student.progress" color="#8b5cf6" />
76 - </div>
77 -
78 - <div class="course-list">
79 - <div
80 - v-for="course in student.courses"
81 - :key="course.id"
82 - class="course-item"
83 - >
84 - <div class="course-info">
85 - <h4 class="course-name">{{ course.name }}</h4>
86 - <p class="course-teacher">授课法师:{{ course.teacher }}</p>
87 - </div>
88 - <div class="course-progress">
89 - <div class="course-status" :class="getCourseStatusClass(course.status)">
90 - {{ getCourseStatusText(course.status) }}
91 - </div>
92 - <div class="course-score" v-if="course.score">
93 - {{ course.score }}分
94 - </div>
95 - </div>
96 - </div>
97 - </div>
98 - </div>
99 -
100 - <!-- 戒律修学记录 -->
101 - <div class="records-section">
102 - <h3 class="section-title">修学记录</h3>
103 - <van-timeline>
104 - <van-timeline-item
105 - v-for="record in student.studyRecords"
106 - :key="record.id"
107 - :time="record.date"
108 - >
109 - <div class="record-content">
110 - <h4 class="record-title">{{ record.title }}</h4>
111 - <p class="record-description">{{ record.description }}</p>
112 - <div class="record-tags">
113 - <van-tag
114 - v-for="tag in record.tags"
115 - :key="tag"
116 - type="primary"
117 - size="small"
118 - class="record-tag"
119 - >
120 - {{ tag }}
121 - </van-tag>
122 - </div>
123 - </div>
124 - </van-timeline-item>
125 - </van-timeline>
126 - </div>
127 -
128 - <!-- 联系方式 -->
129 - <van-cell-group title="联系方式" class="info-group">
130 - <van-cell title="手机号码" :value="student.phone" />
131 - <van-cell title="紧急联系人" :value="student.emergencyContact" />
132 - <van-cell title="紧急联系电话" :value="student.emergencyPhone" />
133 - </van-cell-group>
134 - </div>
135 - </div>
136 - </div>
137 -</template>
138 -
139 -<script setup>
140 -import { ref, onMounted } from 'vue'
141 -import { useRoute, useRouter } from 'vue-router'
142 -import { Toast } from 'vant'
143 -
144 -const route = useRoute()
145 -const router = useRouter()
146 -
147 -// 戒子详细信息
148 -const student = ref({
149 - id: 1,
150 - name: '释慧明',
151 - dharmaName: '慧明',
152 - temple: '大雄宝殿',
153 - teacher: '释智慧法师',
154 - preceptType: 'bhiksu',
155 - status: 'studying',
156 - entryDate: '2023-03-15',
157 - expectedGraduation: '2024-03-15',
158 - hometown: '江苏南京',
159 - age: 28,
160 - studyDays: 285,
161 - progress: 75,
162 - completedCourses: 12,
163 - merits: 1580,
164 - phone: '138****8888',
165 - emergencyContact: '张居士',
166 - emergencyPhone: '139****9999',
167 - avatar: null,
168 - courses: [
169 - {
170 - id: 1,
171 - name: '沙弥律仪',
172 - teacher: '释智慧法师',
173 - status: 'completed',
174 - score: 95
175 - },
176 - {
177 - id: 2,
178 - name: '比丘戒本',
179 - teacher: '释慈悲法师',
180 - status: 'studying',
181 - score: null
182 - },
183 - {
184 - id: 3,
185 - name: '戒律学纲要',
186 - teacher: '释般若法师',
187 - status: 'completed',
188 - score: 88
189 - },
190 - {
191 - id: 4,
192 - name: '四分律删繁补阙行事钞',
193 - teacher: '释智慧法师',
194 - status: 'pending',
195 - score: null
196 - }
197 - ],
198 - studyRecords: [
199 - {
200 - id: 1,
201 - date: '2024-01-15',
202 - title: '完成沙弥律仪考试',
203 - description: '以优异成绩通过沙弥律仪课程考试,获得95分',
204 - tags: ['考试', '沙弥律仪', '优秀']
205 - },
206 - {
207 - id: 2,
208 - date: '2024-01-10',
209 - title: '参加戒律研讨会',
210 - description: '积极参与戒律研讨,发表见解,获得法师好评',
211 - tags: ['研讨会', '戒律', '积极参与']
212 - },
213 - {
214 - id: 3,
215 - date: '2024-01-05',
216 - title: '开始比丘戒本学习',
217 - description: '正式开始比丘戒本课程的学习,制定详细学习计划',
218 - tags: ['比丘戒', '学习计划']
219 - },
220 - {
221 - id: 4,
222 - date: '2023-12-20',
223 - title: '戒律学纲要结业',
224 - description: '完成戒律学纲要课程,掌握戒律基本理论',
225 - tags: ['结业', '戒律学', '理论']
226 - }
227 - ]
228 -})
229 -
230 -// 获取戒律类型样式类
231 -const getPreceptClass = (type) => {
232 - const classes = {
233 - novice: 'precept-novice',
234 - bhiksu: 'precept-bhiksu',
235 - bodhisattva: 'precept-bodhisattva'
236 - }
237 - return classes[type] || 'precept-novice'
238 -}
239 -
240 -// 获取戒律类型文本
241 -const getPreceptText = (type) => {
242 - const texts = {
243 - novice: '沙弥戒',
244 - bhiksu: '比丘戒',
245 - bodhisattva: '菩萨戒'
246 - }
247 - return texts[type] || '沙弥戒'
248 -}
249 -
250 -// 获取状态样式类
251 -const getStatusClass = (status) => {
252 - const classes = {
253 - studying: 'status-studying',
254 - graduated: 'status-graduated',
255 - suspended: 'status-suspended'
256 - }
257 - return classes[status] || 'status-studying'
258 -}
259 -
260 -// 获取状态文本
261 -const getStatusText = (status) => {
262 - const texts = {
263 - studying: '在学',
264 - graduated: '毕业',
265 - suspended: '暂停'
266 - }
267 - return texts[status] || '在学'
268 -}
269 -
270 -// 获取课程状态样式类
271 -const getCourseStatusClass = (status) => {
272 - const classes = {
273 - completed: 'course-completed',
274 - studying: 'course-studying',
275 - pending: 'course-pending'
276 - }
277 - return classes[status] || 'course-pending'
278 -}
279 -
280 -// 获取课程状态文本
281 -const getCourseStatusText = (status) => {
282 - const texts = {
283 - completed: '已完成',
284 - studying: '学习中',
285 - pending: '未开始'
286 - }
287 - return texts[status] || '未开始'
288 -}
289 -
290 -// 处理编辑
291 -const handleEdit = () => {
292 - Toast('编辑功能开发中...')
293 -}
294 -
295 -// 组件挂载时加载数据
296 -onMounted(() => {
297 - // 这里可以根据路由参数加载具体的戒子数据
298 - const studentId = route.params.id
299 - console.log('Loading student data for ID:', studentId)
300 -})
301 -</script>
302 -
303 -<style scoped>
304 -.page-container {
305 - min-height: 100vh;
306 - background: #fafafa;
307 -}
308 -
309 -.custom-nav {
310 - background: linear-gradient(135deg, #8b5cf6, #7c3aed);
311 - color: white;
312 -}
313 -
314 -.custom-nav :deep(.van-nav-bar__title) {
315 - color: white;
316 - font-weight: 600;
317 -}
318 -
319 -.custom-nav :deep(.van-icon) {
320 - color: white;
321 -}
322 -
323 -.content-container {
324 - padding-top: 46px;
325 -}
326 -
327 -.profile-section {
328 - background: white;
329 - padding: 20px;
330 - margin-bottom: 12px;
331 -}
332 -
333 -.profile-header {
334 - display: flex;
335 - align-items: center;
336 - margin-bottom: 20px;
337 -}
338 -
339 -.avatar-container {
340 - position: relative;
341 - width: 80px;
342 - height: 80px;
343 - border-radius: 50%;
344 - overflow: hidden;
345 - margin-right: 20px;
346 - flex-shrink: 0;
347 -}
348 -
349 -.avatar {
350 - width: 100%;
351 - height: 100%;
352 - object-fit: cover;
353 -}
354 -
355 -.avatar-placeholder {
356 - width: 100%;
357 - height: 100%;
358 - background: linear-gradient(135deg, #8b5cf6, #7c3aed);
359 - display: flex;
360 - align-items: center;
361 - justify-content: center;
362 - color: white;
363 - font-size: 32px;
364 - font-weight: 600;
365 -}
366 -
367 -.precept-badge {
368 - position: absolute;
369 - bottom: -2px;
370 - right: -2px;
371 - padding: 4px 8px;
372 - border-radius: 12px;
373 - font-size: 10px;
374 - font-weight: 500;
375 - border: 2px solid white;
376 -}
377 -
378 -.precept-novice {
379 - background: #10b981;
380 - color: white;
381 -}
382 -
383 -.precept-bhiksu {
384 - background: #3b82f6;
385 - color: white;
386 -}
387 -
388 -.precept-bodhisattva {
389 - background: #f59e0b;
390 - color: white;
391 -}
392 -
393 -.profile-info {
394 - flex: 1;
395 -}
396 -
397 -.student-name {
398 - font-size: 24px;
399 - font-weight: 700;
400 - color: #333;
401 - margin: 0 0 8px 0;
402 -}
403 -
404 -.student-dharma-name {
405 - font-size: 16px;
406 - color: #666;
407 - margin: 0 0 4px 0;
408 -}
409 -
410 -.student-temple {
411 - font-size: 16px;
412 - color: #666;
413 - margin: 0 0 12px 0;
414 -}
415 -
416 -.status-badge {
417 - display: inline-block;
418 - padding: 6px 12px;
419 - border-radius: 16px;
420 - font-size: 12px;
421 - font-weight: 500;
422 -}
423 -
424 -.status-studying {
425 - background: #dbeafe;
426 - color: #1d4ed8;
427 - border: 1px solid #93c5fd;
428 -}
429 -
430 -.status-graduated {
431 - background: #dcfce7;
432 - color: #166534;
433 - border: 1px solid #86efac;
434 -}
435 -
436 -.status-suspended {
437 - background: #fef3c7;
438 - color: #92400e;
439 - border: 1px solid #fcd34d;
440 -}
441 -
442 -.stats-grid {
443 - display: grid;
444 - grid-template-columns: repeat(4, 1fr);
445 - gap: 16px;
446 -}
447 -
448 -.stat-item {
449 - text-align: center;
450 -}
451 -
452 -.stat-value {
453 - font-size: 20px;
454 - font-weight: 700;
455 - color: #8b5cf6;
456 - margin-bottom: 4px;
457 -}
458 -
459 -.stat-label {
460 - font-size: 12px;
461 - color: #666;
462 -}
463 -
464 -.details-section {
465 - padding: 0 16px 20px;
466 -}
467 -
468 -.info-group {
469 - margin-bottom: 16px;
470 -}
471 -
472 -.info-group :deep(.van-cell-group__title) {
473 - color: #333;
474 - font-weight: 600;
475 -}
476 -
477 -.section-title {
478 - font-size: 18px;
479 - font-weight: 600;
480 - color: #333;
481 - margin: 20px 0 16px 0;
482 - padding-left: 4px;
483 - border-left: 4px solid #8b5cf6;
484 -}
485 -
486 -.progress-section {
487 - background: white;
488 - border-radius: 12px;
489 - padding: 20px;
490 - margin-bottom: 16px;
491 -}
492 -
493 -.progress-card {
494 - background: #f8fafc;
495 - border-radius: 8px;
496 - padding: 16px;
497 - margin-bottom: 20px;
498 -}
499 -
500 -.progress-header {
501 - display: flex;
502 - justify-content: space-between;
503 - align-items: center;
504 - margin-bottom: 12px;
505 -}
506 -
507 -.progress-text {
508 - font-size: 16px;
509 - font-weight: 500;
510 - color: #333;
511 -}
512 -
513 -.progress-percent {
514 - font-size: 18px;
515 - font-weight: 700;
516 - color: #8b5cf6;
517 -}
518 -
519 -.course-list {
520 - space-y: 12px;
521 -}
522 -
523 -.course-item {
524 - display: flex;
525 - justify-content: space-between;
526 - align-items: center;
527 - padding: 16px;
528 - background: #f8fafc;
529 - border-radius: 8px;
530 - margin-bottom: 12px;
531 -}
532 -
533 -.course-info {
534 - flex: 1;
535 -}
536 -
537 -.course-name {
538 - font-size: 16px;
539 - font-weight: 600;
540 - color: #333;
541 - margin: 0 0 4px 0;
542 -}
543 -
544 -.course-teacher {
545 - font-size: 14px;
546 - color: #666;
547 - margin: 0;
548 -}
549 -
550 -.course-progress {
551 - display: flex;
552 - flex-direction: column;
553 - align-items: flex-end;
554 - gap: 4px;
555 -}
556 -
557 -.course-status {
558 - padding: 4px 8px;
559 - border-radius: 12px;
560 - font-size: 12px;
561 - font-weight: 500;
562 -}
563 -
564 -.course-completed {
565 - background: #dcfce7;
566 - color: #166534;
567 -}
568 -
569 -.course-studying {
570 - background: #dbeafe;
571 - color: #1d4ed8;
572 -}
573 -
574 -.course-pending {
575 - background: #f3f4f6;
576 - color: #6b7280;
577 -}
578 -
579 -.course-score {
580 - font-size: 14px;
581 - font-weight: 600;
582 - color: #8b5cf6;
583 -}
584 -
585 -.records-section {
586 - background: white;
587 - border-radius: 12px;
588 - padding: 20px;
589 - margin-bottom: 16px;
590 -}
591 -
592 -.record-content {
593 - padding-left: 16px;
594 -}
595 -
596 -.record-title {
597 - font-size: 16px;
598 - font-weight: 600;
599 - color: #333;
600 - margin: 0 0 8px 0;
601 -}
602 -
603 -.record-description {
604 - font-size: 14px;
605 - color: #666;
606 - margin: 0 0 12px 0;
607 - line-height: 1.5;
608 -}
609 -
610 -.record-tags {
611 - display: flex;
612 - gap: 8px;
613 - flex-wrap: wrap;
614 -}
615 -
616 -.record-tag {
617 - background: #f3e8ff !important;
618 - color: #8b5cf6 !important;
619 - border: 1px solid #c4b5fd !important;
620 -}
621 -</style>
...\ No newline at end of file ...\ No newline at end of file
1 +<!--
2 + * @Date: 2025-10-30 20:52:19
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-10-30 21:22:42
5 + * @FilePath: /stdj_h5/src/views/Students.vue
6 + * @Description: 戒子页面 - 图片瀑布流展示
7 +-->
8 +
1 <template> 9 <template>
2 - <div class="page-container"> 10 + <div class="students-container">
3 - <!-- 导航栏 --> 11 + <!-- 瀑布流内容 -->
4 - <van-nav-bar title="戒子管理" left-arrow @click-left="$router.back()" class="custom-nav"> 12 + <div class="waterfall-content">
5 - <template #right> 13 + <van-list
6 - <van-icon name="search" size="18" @click="handleSearch" /> 14 + v-model:loading="loading"
7 - </template> 15 + :finished="finished"
8 - </van-nav-bar> 16 + finished-text="没有更多了"
9 - 17 + @load="onLoad"
10 - <!-- 内容区域 --> 18 + >
11 - <div class="content-container"> 19 + <div class="waterfall-container">
12 - <!-- 顶部统计 --> 20 + <div class="waterfall-column" v-for="(column, index) in columns" :key="index">
13 - <div class="stats-section"> 21 + <div
14 - <div class="stat-card"> 22 + class="waterfall-item"
15 - <div class="stat-icon">🙏</div> 23 + v-for="item in column"
16 - <div class="stat-info"> 24 + :key="item.id"
17 - <div class="stat-number">{{ totalStudents }}</div> 25 + @click="onImageClick(item)"
18 - <div class="stat-label">总戒子数</div> 26 + >
27 + <div class="image-wrapper">
28 + <img
29 + :src="item.url"
30 + :alt="item.title"
31 + :style="{ height: item.height + 'px' }"
32 + @load="onImageLoad"
33 + @error="onImageError"
34 + />
35 + <div class="image-overlay">
36 + <span class="image-title">{{ item.title }}</span>
37 + </div>
38 + </div>
39 + </div>
19 </div> 40 </div>
20 </div> 41 </div>
21 - <div class="stat-card"> 42 + </van-list>
22 - <div class="stat-icon">📚</div> 43 + </div>
23 - <div class="stat-info"> 44 +
24 - <div class="stat-number">{{ studyingStudents }}</div> 45 + <!-- 遮罩层弹窗 -->
25 - <div class="stat-label">在学戒子</div> 46 + <van-overlay :show="showOverlay" @click="closeOverlay">
26 - </div> 47 + <div class="overlay-content" @click.stop>
48 + <!-- 关闭按钮 -->
49 + <div class="close-btn" @click="closeOverlay">
50 + <van-icon name="cross" size="1.5rem" color="#fff" />
27 </div> 51 </div>
28 - <div class="stat-card"> 52 +
29 - <div class="stat-icon">🎓</div> 53 + <!-- 图片展示 -->
30 - <div class="stat-info"> 54 + <div class="overlay-image-wrapper">
31 - <div class="stat-number">{{ graduatedStudents }}</div> 55 + <img
32 - <div class="stat-label">已毕业</div> 56 + :src="selectedImage?.url"
33 - </div> 57 + :alt="selectedImage?.title"
58 + class="overlay-image"
59 + />
34 </div> 60 </div>
35 - </div> 61 +
36 - 62 + <!-- 描述内容 -->
37 - <!-- 筛选栏 --> 63 + <div class="overlay-description">
38 - <div class="filter-section"> 64 + <h3 class="overlay-title">{{ selectedImage?.title }}</h3>
39 - <van-tabs v-model:active="activeTab" @change="handleTabChange" class="custom-tabs"> 65 + <p class="overlay-text">{{ selectedImage?.description }}</p>
40 - <van-tab title="全部" name="all"></van-tab>
41 - <van-tab title="沙弥戒" name="novice"></van-tab>
42 - <van-tab title="比丘戒" name="bhiksu"></van-tab>
43 - <van-tab title="菩萨戒" name="bodhisattva"></van-tab>
44 - </van-tabs>
45 - </div>
46 -
47 - <!-- 戒子列表 -->
48 - <div class="students-list">
49 - <div
50 - v-for="student in filteredStudents"
51 - :key="student.id"
52 - class="student-card"
53 - @click="handleStudentClick(student)"
54 - >
55 - <div class="student-avatar">
56 - <img v-if="student.avatar" :src="student.avatar" :alt="student.name" />
57 - <div v-else class="avatar-placeholder">
58 - <span>{{ student.name.charAt(0) }}</span>
59 - </div>
60 - <div class="precept-badge" :class="getPreceptClass(student.preceptType)">
61 - {{ getPreceptText(student.preceptType) }}
62 - </div>
63 - </div>
64 -
65 - <div class="student-info">
66 - <div class="student-header">
67 - <h4 class="student-name">{{ student.name }}</h4>
68 - <div class="student-status" :class="getStatusClass(student.status)">
69 - {{ getStatusText(student.status) }}
70 - </div>
71 - </div>
72 -
73 - <div class="student-details">
74 - <p class="student-temple">{{ student.temple }}</p>
75 - <p class="student-teacher">戒师:{{ student.teacher }}</p>
76 - <div class="student-meta">
77 - <span class="entry-date">{{ student.entryDate }}入学</span>
78 - <span class="progress">进度:{{ student.progress }}%</span>
79 - </div>
80 - </div>
81 - </div>
82 -
83 - <div class="student-actions">
84 - <van-icon name="arrow" />
85 - </div>
86 </div> 66 </div>
87 </div> 67 </div>
88 - 68 + </van-overlay>
89 - <!-- 空状态 -->
90 - <van-empty v-if="filteredStudents.length === 0" description="暂无戒子信息" />
91 - </div>
92 </div> 69 </div>
93 </template> 70 </template>
94 71
95 <script setup> 72 <script setup>
96 -import { ref, computed } from 'vue' 73 +import { ref, reactive, onMounted } from 'vue'
97 -import { useRouter } from 'vue-router' 74 +import { generateWaterfallData } from '@/utils/mockData'
98 -import { Toast } from 'vant' 75 +import { useTitle } from '@vueuse/core';
99 - 76 +
100 -const router = useRouter() 77 +useTitle('同戒录')
101 -const activeTab = ref('all') 78 +
102 - 79 +// 响应式数据
103 -// 统计数据 80 +const loading = ref(false)
104 -const totalStudents = ref(68) 81 +const finished = ref(false)
105 -const studyingStudents = ref(52) 82 +const currentPage = ref(1)
106 -const graduatedStudents = ref(16) 83 +const pageSize = 10
107 - 84 +const allImages = ref([])
108 -// 戒子数据 85 +const columns = reactive([[], []])
109 -const students = ref([ 86 +
110 - { 87 +// 遮罩层相关状态
111 - id: 1, 88 +const showOverlay = ref(false)
112 - name: '释慧明', 89 +const selectedImage = ref(null)
113 - temple: '大雄宝殿', 90 +
114 - teacher: '释智慧法师', 91 +// 加载数据
115 - preceptType: 'bhiksu', 92 +const onLoad = async () => {
116 - status: 'studying', 93 + loading.value = true
117 - entryDate: '2023-03-15', 94 +
118 - progress: 75, 95 + // 模拟网络延迟
119 - avatar: null 96 + await new Promise(resolve => setTimeout(resolve, 1000))
120 - }, 97 +
121 - { 98 + try {
122 - id: 2, 99 + const newData = generateWaterfallData(currentPage.value, pageSize)
123 - name: '释德行', 100 +
124 - temple: '观音殿', 101 + if (newData.length === 0) {
125 - teacher: '释慈悲法师', 102 + finished.value = true
126 - preceptType: 'novice', 103 + } else {
127 - status: 'studying', 104 + allImages.value.push(...newData)
128 - entryDate: '2023-06-20', 105 + distributeImages(newData)
129 - progress: 45, 106 + currentPage.value++
130 - avatar: null 107 + }
131 - }, 108 + } catch (error) {
132 - { 109 + console.error('加载数据失败:', error)
133 - id: 3, 110 + } finally {
134 - name: '释觉悟', 111 + loading.value = false
135 - temple: '文殊殿',
136 - teacher: '释般若法师',
137 - preceptType: 'bodhisattva',
138 - status: 'graduated',
139 - entryDate: '2022-09-10',
140 - progress: 100,
141 - avatar: null
142 - },
143 - {
144 - id: 4,
145 - name: '释持戒',
146 - temple: '地藏殿',
147 - teacher: '释智慧法师',
148 - preceptType: 'bhiksu',
149 - status: 'studying',
150 - entryDate: '2023-01-05',
151 - progress: 85,
152 - avatar: null
153 - },
154 - {
155 - id: 5,
156 - name: '释精进',
157 - temple: '药师殿',
158 - teacher: '释慈悲法师',
159 - preceptType: 'novice',
160 - status: 'studying',
161 - entryDate: '2023-08-12',
162 - progress: 30,
163 - avatar: null
164 - },
165 - {
166 - id: 6,
167 - name: '释忍辱',
168 - temple: '弥勒殿',
169 - teacher: '释般若法师',
170 - preceptType: 'bodhisattva',
171 - status: 'studying',
172 - entryDate: '2023-04-18',
173 - progress: 60,
174 - avatar: null
175 - },
176 - {
177 - id: 7,
178 - name: '释禅定',
179 - temple: '韦陀殿',
180 - teacher: '释智慧法师',
181 - preceptType: 'bhiksu',
182 - status: 'graduated',
183 - entryDate: '2022-11-30',
184 - progress: 100,
185 - avatar: null
186 - },
187 - {
188 - id: 8,
189 - name: '释智慧',
190 - temple: '伽蓝殿',
191 - teacher: '释慈悲法师',
192 - preceptType: 'novice',
193 - status: 'studying',
194 - entryDate: '2023-07-08',
195 - progress: 40,
196 - avatar: null
197 - }
198 -])
199 -
200 -// 过滤后的戒子列表
201 -const filteredStudents = computed(() => {
202 - if (activeTab.value === 'all') {
203 - return students.value
204 - }
205 - return students.value.filter(student => student.preceptType === activeTab.value)
206 -})
207 -
208 -// 获取戒律类型样式类
209 -const getPreceptClass = (type) => {
210 - const classes = {
211 - novice: 'precept-novice',
212 - bhiksu: 'precept-bhiksu',
213 - bodhisattva: 'precept-bodhisattva'
214 } 112 }
215 - return classes[type] || 'precept-novice'
216 } 113 }
217 114
218 -// 获取戒律类型文本 115 +// 分配图片到两列
219 -const getPreceptText = (type) => { 116 +const distributeImages = (images) => {
220 - const texts = { 117 + images.forEach(image => {
221 - novice: '沙弥', 118 + // 计算两列的当前高度
222 - bhiksu: '比丘', 119 + const leftHeight = columns[0].reduce((sum, item) => sum + item.height + 20, 0)
223 - bodhisattva: '菩萨' 120 + const rightHeight = columns[1].reduce((sum, item) => sum + item.height + 20, 0)
224 - }
225 - return texts[type] || '沙弥'
226 -}
227 121
228 -// 获取状态样式类 122 + // 将图片添加到高度较小的列
229 -const getStatusClass = (status) => { 123 + if (leftHeight <= rightHeight) {
230 - const classes = { 124 + columns[0].push(image)
231 - studying: 'status-studying', 125 + } else {
232 - graduated: 'status-graduated', 126 + columns[1].push(image)
233 - suspended: 'status-suspended' 127 + }
234 - } 128 + })
235 - return classes[status] || 'status-studying'
236 } 129 }
237 130
238 -// 获取状态文本 131 +// 图片点击事件
239 -const getStatusText = (status) => { 132 +const onImageClick = (item) => {
240 - const texts = { 133 + console.log('点击图片:', item)
241 - studying: '在学', 134 + selectedImage.value = item
242 - graduated: '毕业', 135 + showOverlay.value = true
243 - suspended: '暂停'
244 - }
245 - return texts[status] || '在学'
246 } 136 }
247 137
248 -// 处理标签切换 138 +// 关闭遮罩层
249 -const handleTabChange = (name) => { 139 +const closeOverlay = () => {
250 - activeTab.value = name 140 + showOverlay.value = false
141 + selectedImage.value = null
251 } 142 }
252 143
253 -// 处理戒子点击 144 +// 图片加载成功
254 -const handleStudentClick = (student) => { 145 +const onImageLoad = (event) => {
255 - router.push(`/students/${student.id}`) 146 + console.log('图片加载成功:', event.target.src)
256 } 147 }
257 148
258 -// 处理搜索 149 +// 图片加载失败
259 -const handleSearch = () => { 150 +const onImageError = (event) => {
260 - Toast('搜索功能开发中...') 151 + console.error('图片加载失败:', event.target.src)
152 + // 可以设置默认图片
153 + event.target.src = 'https://via.placeholder.com/300x400?text=加载失败'
261 } 154 }
155 +
156 +// 组件挂载时初始化
157 +onMounted(() => {
158 + // 初始加载第一页数据
159 + onLoad()
160 +})
262 </script> 161 </script>
263 162
264 <style scoped> 163 <style scoped>
265 -.page-container { 164 +.students-container {
165 + background-color: #F2EBDB;
266 min-height: 100vh; 166 min-height: 100vh;
267 - background: #fafafa;
268 -}
269 -
270 -.custom-nav {
271 - background: linear-gradient(135deg, #8b5cf6, #7c3aed);
272 - color: white;
273 -}
274 -
275 -.custom-nav :deep(.van-nav-bar__title) {
276 - color: white;
277 - font-weight: 600;
278 } 167 }
279 168
280 -.custom-nav :deep(.van-icon) { 169 +.header {
281 - color: white; 170 + position: sticky;
171 + top: 0;
172 + z-index: 100;
173 + background-color: #fff;
282 } 174 }
283 175
284 -.content-container { 176 +.waterfall-content {
285 - padding-top: 46px; 177 + padding: 1rem;
286 } 178 }
287 179
288 -.stats-section { 180 +.waterfall-container {
289 display: flex; 181 display: flex;
290 - gap: 12px; 182 + gap: 0.75rem;
291 - padding: 16px; 183 + align-items: flex-start;
292 } 184 }
293 185
294 -.stat-card { 186 +.waterfall-column {
295 flex: 1; 187 flex: 1;
296 - background: white;
297 - border-radius: 12px;
298 - padding: 16px;
299 display: flex; 188 display: flex;
300 - align-items: center; 189 + flex-direction: column;
301 - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 190 + gap: 0.75rem;
302 } 191 }
303 192
304 -.stat-icon { 193 +.waterfall-item {
305 - font-size: 24px; 194 + background-color: #fff;
306 - margin-right: 12px; 195 + border-radius: 0.5rem;
196 + overflow: hidden;
197 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
198 + transition: transform 0.2s ease, box-shadow 0.2s ease;
199 + cursor: pointer;
307 } 200 }
308 201
309 -.stat-info { 202 +.waterfall-item:hover {
310 - flex: 1; 203 + transform: translateY(-2px);
204 + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
311 } 205 }
312 206
313 -.stat-number { 207 +.image-wrapper {
314 - font-size: 20px; 208 + position: relative;
315 - font-weight: 700; 209 + overflow: hidden;
316 - color: #8b5cf6;
317 - margin-bottom: 2px;
318 } 210 }
319 211
320 -.stat-label { 212 +.image-wrapper img {
321 - font-size: 12px; 213 + width: 100%;
322 - color: #666; 214 + display: block;
215 + object-fit: cover;
216 + transition: transform 0.3s ease;
323 } 217 }
324 218
325 -.filter-section { 219 +.waterfall-item:hover .image-wrapper img {
326 - background: white; 220 + transform: scale(1.05);
327 - border-bottom: 1px solid #eee;
328 } 221 }
329 222
330 -.custom-tabs :deep(.van-tab) { 223 +.image-overlay {
331 - font-weight: 500; 224 + position: absolute;
225 + bottom: 0;
226 + left: 0;
227 + right: 0;
228 + background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
229 + padding: 1rem 0.75rem 0.75rem;
230 + transform: translateY(100%);
231 + transition: transform 0.3s ease;
332 } 232 }
333 233
334 -.custom-tabs :deep(.van-tab--active) { 234 +.waterfall-item:hover .image-overlay {
335 - color: #8b5cf6; 235 + transform: translateY(0);
336 } 236 }
337 237
338 -.custom-tabs :deep(.van-tabs__line) { 238 +.image-title {
339 - background: #8b5cf6; 239 + color: #fff;
240 + font-size: 0.875rem;
241 + font-weight: 500;
242 + line-height: 1.4;
340 } 243 }
341 244
342 -.students-list { 245 +/* 加载状态样式 */
343 - padding: 16px; 246 +:deep(.van-list__loading) {
247 + padding: 1rem;
248 + text-align: center;
249 + color: #969799;
344 } 250 }
345 251
346 -.student-card { 252 +:deep(.van-list__finished-text) {
347 - background: white; 253 + padding: 1rem;
348 - border-radius: 12px; 254 + text-align: center;
349 - padding: 16px; 255 + color: #969799;
350 - margin-bottom: 12px; 256 + font-size: 0.875rem;
351 - display: flex;
352 - align-items: center;
353 - gap: 16px;
354 - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
355 - cursor: pointer;
356 - transition: all 0.3s ease;
357 } 257 }
358 258
359 -.student-card:hover { 259 +/* 响应式设计 */
360 - transform: translateY(-3px); 260 +@media (max-width: 480px) {
361 - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); 261 + .waterfall-content {
362 -} 262 + padding: 0.75rem;
263 + }
363 264
364 -.student-card:active { 265 + .waterfall-container {
365 - transform: translateY(-1px); 266 + gap: 0.5rem;
366 - box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12); 267 + }
268 +
269 + .waterfall-column {
270 + gap: 0.5rem;
271 + }
272 +
273 + .image-title {
274 + font-size: 0.8125rem;
275 + }
367 } 276 }
368 277
369 -.student-avatar { 278 +/* 骨架屏效果 */
370 - position: relative; 279 +.waterfall-item.loading {
371 - width: 60px; 280 + background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
372 - height: 60px; 281 + background-size: 200% 100%;
373 - border-radius: 50%; 282 + animation: loading 1.5s infinite;
374 - overflow: hidden;
375 - margin-right: 16px;
376 - flex-shrink: 0;
377 } 283 }
378 284
379 -.student-avatar img { 285 +@keyframes loading {
380 - width: 100%; 286 + 0% {
381 - height: 100%; 287 + background-position: 200% 0;
382 - object-fit: cover; 288 + }
289 + 100% {
290 + background-position: -200% 0;
291 + }
383 } 292 }
384 293
385 -.avatar-placeholder { 294 +/* 遮罩层样式 */
386 - width: 100%; 295 +:deep(.van-overlay) {
387 - height: 100%;
388 - background: linear-gradient(135deg, #8b5cf6, #7c3aed);
389 display: flex; 296 display: flex;
390 align-items: center; 297 align-items: center;
391 justify-content: center; 298 justify-content: center;
392 - color: white; 299 + padding: 1rem;
393 - font-size: 24px;
394 - font-weight: 600;
395 } 300 }
396 301
397 -.precept-badge { 302 +.overlay-content {
398 - position: absolute; 303 + background-color: #fff;
399 - bottom: -2px; 304 + border-radius: 1rem;
400 - right: -2px; 305 + max-width: 90vw;
401 - padding: 2px 6px; 306 + max-height: 80vh;
402 - border-radius: 8px; 307 + overflow: hidden;
403 - font-size: 10px; 308 + position: relative;
404 - font-weight: 500; 309 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
405 - border: 2px solid white;
406 -}
407 -
408 -.precept-novice {
409 - background: #10b981;
410 - color: white;
411 -}
412 -
413 -.precept-bhiksu {
414 - background: #3b82f6;
415 - color: white;
416 -}
417 -
418 -.precept-bodhisattva {
419 - background: #f59e0b;
420 - color: white;
421 -}
422 -
423 -.student-info {
424 - flex: 1;
425 } 310 }
426 311
427 -.student-header { 312 +.close-btn {
313 + position: absolute;
314 + top: 1rem;
315 + right: 1rem;
316 + width: 2.5rem;
317 + height: 2.5rem;
318 + background-color: rgba(0, 0, 0, 0.5);
319 + border-radius: 50%;
428 display: flex; 320 display: flex;
429 align-items: center; 321 align-items: center;
430 - justify-content: space-between; 322 + justify-content: center;
431 - margin-bottom: 8px; 323 + cursor: pointer;
432 -} 324 + z-index: 10;
433 - 325 + transition: background-color 0.2s ease;
434 -.student-name {
435 - font-size: 18px;
436 - font-weight: 600;
437 - color: #333;
438 - margin: 0;
439 } 326 }
440 327
441 -.student-status { 328 +.close-btn:hover {
442 - padding: 4px 8px; 329 + background-color: rgba(0, 0, 0, 0.7);
443 - border-radius: 12px;
444 - font-size: 12px;
445 - font-weight: 500;
446 } 330 }
447 331
448 -.status-studying { 332 +.overlay-image-wrapper {
449 - background: #dbeafe; 333 + width: 100%;
450 - color: #1d4ed8; 334 + max-height: 60vh;
451 - border: 1px solid #93c5fd; 335 + overflow: hidden;
336 + display: flex;
337 + align-items: center;
338 + justify-content: center;
339 + background-color: #f5f5f5;
452 } 340 }
453 341
454 -.status-graduated { 342 +.overlay-image {
455 - background: #dcfce7; 343 + width: 100%;
456 - color: #166534; 344 + height: auto;
457 - border: 1px solid #86efac; 345 + max-height: 60vh;
346 + object-fit: contain;
347 + display: block;
458 } 348 }
459 349
460 -.status-suspended { 350 +.overlay-description {
461 - background: #fef3c7; 351 + padding: 1.5rem;
462 - color: #92400e; 352 + background-color: #fff;
463 - border: 1px solid #fcd34d;
464 } 353 }
465 354
466 -.student-details { 355 +.overlay-title {
467 - space-y: 4px; 356 + font-size: 1.25rem;
357 + font-weight: 600;
358 + color: #333;
359 + margin: 0 0 1rem 0;
360 + line-height: 1.4;
468 } 361 }
469 362
470 -.student-temple { 363 +.overlay-text {
471 - font-size: 16px; 364 + font-size: 1rem;
472 color: #666; 365 color: #666;
473 - margin: 0 0 4px 0; 366 + line-height: 1.6;
367 + margin: 0;
368 + white-space: pre-wrap;
474 } 369 }
475 370
476 -.student-teacher { 371 +/* 移动端适配 */
477 - font-size: 14px; 372 +@media (max-width: 480px) {
478 - color: #999; 373 + .overlay-content {
479 - margin: 0 0 8px 0; 374 + max-width: 95vw;
480 -} 375 + max-height: 85vh;
376 + border-radius: 0.75rem;
377 + }
481 378
482 -.student-meta { 379 + .close-btn {
483 - display: flex; 380 + top: 0.75rem;
484 - gap: 16px; 381 + right: 0.75rem;
485 -} 382 + width: 2rem;
383 + height: 2rem;
384 + }
486 385
487 -.entry-date, 386 + .overlay-description {
488 -.progress { 387 + padding: 1rem;
489 - font-size: 12px; 388 + }
490 - color: #999;
491 - background: #f5f5f5;
492 - padding: 2px 6px;
493 - border-radius: 4px;
494 -}
495 389
496 -.student-actions { 390 + .overlay-title {
497 - margin-left: 12px; 391 + font-size: 1.125rem;
498 - color: #ccc; 392 + }
393 +
394 + .overlay-text {
395 + font-size: 0.875rem;
396 + }
499 } 397 }
500 -</style>
...\ No newline at end of file ...\ No newline at end of file
398 +</style>
......
1 -<template>
2 - <div class="page-container">
3 - <!-- 导航栏 -->
4 - <van-nav-bar title="法师详情" left-arrow @click-left="$router.back()" class="custom-nav">
5 - <template #right>
6 - <van-icon name="share" size="18" />
7 - </template>
8 - </van-nav-bar>
9 -
10 - <!-- 内容区域 -->
11 - <div class="content-container">
12 - <!-- 法师基本信息 -->
13 - <div class="teacher-profile">
14 - <div class="profile-header">
15 - <div class="teacher-avatar">
16 - <img v-if="teacher.avatar" :src="teacher.avatar" :alt="teacher.name" />
17 - <div v-else class="avatar-placeholder">
18 - <span>{{ teacher.name.charAt(0) }}</span>
19 - </div>
20 - </div>
21 -
22 - <div class="profile-info">
23 - <h2 class="teacher-name">{{ teacher.name }}</h2>
24 - <div class="teacher-role" :class="getRoleClass(teacher.role)">
25 - {{ teacher.role }}
26 - </div>
27 - <p class="teacher-title">{{ teacher.title }} · {{ teacher.temple }}</p>
28 - </div>
29 - </div>
30 -
31 - <!-- 统计信息 -->
32 - <div class="stats-section">
33 - <div class="stat-item">
34 - <div class="stat-number">{{ teacher.experience }}</div>
35 - <div class="stat-label">戒腊年数</div>
36 - </div>
37 - <div class="stat-divider"></div>
38 - <div class="stat-item">
39 - <div class="stat-number">{{ teacher.ordinationYear }}</div>
40 - <div class="stat-label">受戒年份</div>
41 - </div>
42 - <div class="stat-divider"></div>
43 - <div class="stat-item">
44 - <div class="stat-number">{{ teacher.disciples || 0 }}</div>
45 - <div class="stat-label">弟子人数</div>
46 - </div>
47 - </div>
48 - </div>
49 -
50 - <!-- 详细信息 -->
51 - <div class="detail-sections">
52 - <!-- 个人简介 -->
53 - <div class="detail-section">
54 - <h3 class="section-title">
55 - <van-icon name="user-o" />
56 - 个人简介
57 - </h3>
58 - <div class="section-content">
59 - <p>{{ teacher.biography || '暂无个人简介信息' }}</p>
60 - </div>
61 - </div>
62 -
63 - <!-- 修学经历 -->
64 - <div class="detail-section">
65 - <h3 class="section-title">
66 - <van-icon name="certificate" />
67 - 修学经历
68 - </h3>
69 - <div class="section-content">
70 - <div class="timeline">
71 - <div v-for="(experience, index) in teacher.experiences" :key="index" class="timeline-item">
72 - <div class="timeline-dot"></div>
73 - <div class="timeline-content">
74 - <div class="timeline-year">{{ experience.year }}</div>
75 - <div class="timeline-event">{{ experience.event }}</div>
76 - <div class="timeline-location">{{ experience.location }}</div>
77 - </div>
78 - </div>
79 - </div>
80 - </div>
81 - </div>
82 -
83 - <!-- 弘法活动 -->
84 - <div class="detail-section">
85 - <h3 class="section-title">
86 - <van-icon name="fire-o" />
87 - 弘法活动
88 - </h3>
89 - <div class="section-content">
90 - <div v-if="teacher.activities && teacher.activities.length > 0" class="activities-list">
91 - <div v-for="activity in teacher.activities" :key="activity.id" class="activity-item">
92 - <div class="activity-date">{{ activity.date }}</div>
93 - <div class="activity-title">{{ activity.title }}</div>
94 - <div class="activity-location">{{ activity.location }}</div>
95 - </div>
96 - </div>
97 - <p v-else class="no-data">暂无弘法活动记录</p>
98 - </div>
99 - </div>
100 -
101 - <!-- 联系方式 -->
102 - <div class="detail-section">
103 - <h3 class="section-title">
104 - <van-icon name="phone-o" />
105 - 联系方式
106 - </h3>
107 - <div class="section-content">
108 - <div class="contact-info">
109 - <div class="contact-item">
110 - <span class="contact-label">所在寺院:</span>
111 - <span class="contact-value">{{ teacher.temple }}</span>
112 - </div>
113 - <div class="contact-item" v-if="teacher.phone">
114 - <span class="contact-label">联系电话:</span>
115 - <span class="contact-value">{{ teacher.phone }}</span>
116 - </div>
117 - <div class="contact-item" v-if="teacher.email">
118 - <span class="contact-label">电子邮箱:</span>
119 - <span class="contact-value">{{ teacher.email }}</span>
120 - </div>
121 - </div>
122 - </div>
123 - </div>
124 - </div>
125 - </div>
126 - </div>
127 -</template>
128 -
129 -<script setup>
130 -import { ref, onMounted } from 'vue'
131 -import { useRoute, useRouter } from 'vue-router'
132 -
133 -const route = useRoute()
134 -const router = useRouter()
135 -
136 -// 法师详细信息
137 -const teacher = ref({
138 - id: 1,
139 - name: '慧明法师',
140 - title: '方丈',
141 - role: '得戒和尚',
142 - temple: '大觉寺',
143 - ordinationYear: 1985,
144 - experience: 39,
145 - disciples: 156,
146 - avatar: null,
147 - biography: '慧明法师,俗姓李,1960年生于江苏南京。1985年在大觉寺依止上慧下觉老和尚剃度出家,同年在宝华山隆昌寺受具足戒。法师戒行清净,学修并重,深得四众弟子敬仰。现任大觉寺方丈,致力于佛法弘扬和寺院建设。',
148 - experiences: [
149 - {
150 - year: '1985年',
151 - event: '在大觉寺剃度出家',
152 - location: '大觉寺'
153 - },
154 - {
155 - year: '1985年',
156 - event: '在宝华山隆昌寺受具足戒',
157 - location: '宝华山隆昌寺'
158 - },
159 - {
160 - year: '1990年',
161 - event: '任大觉寺知客',
162 - location: '大觉寺'
163 - },
164 - {
165 - year: '1995年',
166 - event: '任大觉寺监院',
167 - location: '大觉寺'
168 - },
169 - {
170 - year: '2000年',
171 - event: '升座为大觉寺方丈',
172 - location: '大觉寺'
173 - }
174 - ],
175 - activities: [
176 - {
177 - id: 1,
178 - date: '2024-01-15',
179 - title: '三坛大戒传戒法会',
180 - location: '大觉寺'
181 - },
182 - {
183 - id: 2,
184 - date: '2023-12-08',
185 - title: '佛成道日法会',
186 - location: '大觉寺'
187 - },
188 - {
189 - id: 3,
190 - date: '2023-11-20',
191 - title: '佛学讲座:戒律的现代意义',
192 - location: '大觉寺讲堂'
193 - }
194 - ],
195 - phone: '025-12345678',
196 - email: 'huiming@dajuesi.org'
197 -})
198 -
199 -// 获取角色样式类
200 -const getRoleClass = (role) => {
201 - if (role.includes('和尚') || role.includes('阿阇梨')) {
202 - return 'role-teacher'
203 - }
204 - return 'role-witness'
205 -}
206 -
207 -// 加载法师详情
208 -const loadTeacherDetail = async () => {
209 - const teacherId = route.params.id
210 - // 这里应该根据 teacherId 从 API 获取法师详情
211 - // 现在使用模拟数据
212 - console.log('Loading teacher detail for ID:', teacherId)
213 -}
214 -
215 -onMounted(() => {
216 - loadTeacherDetail()
217 -})
218 -</script>
219 -
220 -<style scoped>
221 -.page-container {
222 - min-height: 100vh;
223 - background: #fafafa;
224 -}
225 -
226 -.custom-nav {
227 - background: linear-gradient(135deg, #fbbf24, #f97316);
228 - color: white;
229 -}
230 -
231 -.custom-nav :deep(.van-nav-bar__title) {
232 - color: white;
233 - font-weight: 600;
234 -}
235 -
236 -.custom-nav :deep(.van-icon) {
237 - color: white;
238 -}
239 -
240 -.content-container {
241 - padding-top: 46px;
242 -}
243 -
244 -.teacher-profile {
245 - background: white;
246 - margin: 16px;
247 - border-radius: 12px;
248 - padding: 24px;
249 - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
250 -}
251 -
252 -.profile-header {
253 - display: flex;
254 - align-items: center;
255 - margin-bottom: 24px;
256 -}
257 -
258 -.teacher-avatar {
259 - width: 80px;
260 - height: 80px;
261 - border-radius: 50%;
262 - overflow: hidden;
263 - margin-right: 20px;
264 - flex-shrink: 0;
265 -}
266 -
267 -.teacher-avatar img {
268 - width: 100%;
269 - height: 100%;
270 - object-fit: cover;
271 -}
272 -
273 -.avatar-placeholder {
274 - width: 100%;
275 - height: 100%;
276 - background: linear-gradient(135deg, #fbbf24, #f97316);
277 - display: flex;
278 - align-items: center;
279 - justify-content: center;
280 - color: white;
281 - font-size: 32px;
282 - font-weight: 600;
283 -}
284 -
285 -.profile-info {
286 - flex: 1;
287 -}
288 -
289 -.teacher-name {
290 - font-size: 24px;
291 - font-weight: 700;
292 - color: #333;
293 - margin: 0 0 8px 0;
294 -}
295 -
296 -.teacher-role {
297 - display: inline-block;
298 - padding: 6px 12px;
299 - border-radius: 16px;
300 - font-size: 14px;
301 - font-weight: 500;
302 - margin-bottom: 8px;
303 -}
304 -
305 -.role-teacher {
306 - background: linear-gradient(135deg, #fbbf24, #f97316);
307 - color: white;
308 -}
309 -
310 -.role-witness {
311 - background: #f0f9ff;
312 - color: #0369a1;
313 - border: 1px solid #bae6fd;
314 -}
315 -
316 -.teacher-title {
317 - font-size: 16px;
318 - color: #666;
319 - margin: 0;
320 -}
321 -
322 -.stats-section {
323 - display: flex;
324 - align-items: center;
325 - justify-content: space-around;
326 - padding-top: 24px;
327 - border-top: 1px solid #f0f0f0;
328 -}
329 -
330 -.stat-item {
331 - text-align: center;
332 -}
333 -
334 -.stat-number {
335 - font-size: 24px;
336 - font-weight: 700;
337 - color: #f59e0b;
338 - margin-bottom: 4px;
339 -}
340 -
341 -.stat-label {
342 - font-size: 12px;
343 - color: #999;
344 -}
345 -
346 -.stat-divider {
347 - width: 1px;
348 - height: 40px;
349 - background: #f0f0f0;
350 -}
351 -
352 -.detail-sections {
353 - padding: 0 16px 16px;
354 -}
355 -
356 -.detail-section {
357 - background: white;
358 - border-radius: 12px;
359 - margin-bottom: 16px;
360 - overflow: hidden;
361 - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
362 -}
363 -
364 -.section-title {
365 - display: flex;
366 - align-items: center;
367 - padding: 16px 20px;
368 - margin: 0;
369 - font-size: 16px;
370 - font-weight: 600;
371 - color: #333;
372 - background: #fafafa;
373 - border-bottom: 1px solid #f0f0f0;
374 -}
375 -
376 -.section-title .van-icon {
377 - margin-right: 8px;
378 - color: #f59e0b;
379 -}
380 -
381 -.section-content {
382 - padding: 20px;
383 -}
384 -
385 -.section-content p {
386 - font-size: 14px;
387 - line-height: 1.6;
388 - color: #666;
389 - margin: 0;
390 -}
391 -
392 -.timeline {
393 - position: relative;
394 -}
395 -
396 -.timeline::before {
397 - content: '';
398 - position: absolute;
399 - left: 8px;
400 - top: 0;
401 - bottom: 0;
402 - width: 2px;
403 - background: #f0f0f0;
404 -}
405 -
406 -.timeline-item {
407 - position: relative;
408 - padding-left: 32px;
409 - margin-bottom: 20px;
410 -}
411 -
412 -.timeline-item:last-child {
413 - margin-bottom: 0;
414 -}
415 -
416 -.timeline-dot {
417 - position: absolute;
418 - left: 0;
419 - top: 4px;
420 - width: 16px;
421 - height: 16px;
422 - border-radius: 50%;
423 - background: #f59e0b;
424 - border: 3px solid white;
425 - box-shadow: 0 0 0 2px #f59e0b;
426 -}
427 -
428 -.timeline-year {
429 - font-size: 14px;
430 - font-weight: 600;
431 - color: #f59e0b;
432 - margin-bottom: 4px;
433 -}
434 -
435 -.timeline-event {
436 - font-size: 16px;
437 - font-weight: 500;
438 - color: #333;
439 - margin-bottom: 4px;
440 -}
441 -
442 -.timeline-location {
443 - font-size: 14px;
444 - color: #999;
445 -}
446 -
447 -.activities-list {
448 - space-y: 12px;
449 -}
450 -
451 -.activity-item {
452 - padding: 16px;
453 - background: #fafafa;
454 - border-radius: 8px;
455 - margin-bottom: 12px;
456 -}
457 -
458 -.activity-date {
459 - font-size: 12px;
460 - color: #f59e0b;
461 - font-weight: 500;
462 - margin-bottom: 4px;
463 -}
464 -
465 -.activity-title {
466 - font-size: 16px;
467 - font-weight: 500;
468 - color: #333;
469 - margin-bottom: 4px;
470 -}
471 -
472 -.activity-location {
473 - font-size: 14px;
474 - color: #666;
475 -}
476 -
477 -.contact-info {
478 - space-y: 12px;
479 -}
480 -
481 -.contact-item {
482 - display: flex;
483 - align-items: center;
484 - margin-bottom: 12px;
485 -}
486 -
487 -.contact-label {
488 - font-size: 14px;
489 - color: #666;
490 - width: 80px;
491 - flex-shrink: 0;
492 -}
493 -
494 -.contact-value {
495 - font-size: 14px;
496 - color: #333;
497 - flex: 1;
498 -}
499 -
500 -.no-data {
501 - text-align: center;
502 - color: #999;
503 - font-size: 14px;
504 - margin: 0;
505 -}
506 -</style>
...\ No newline at end of file ...\ No newline at end of file
1 -<template>
2 - <div class="page-container">
3 - <!-- 导航栏 -->
4 - <van-nav-bar title="三师七证" left-arrow @click-left="$router.back()" class="custom-nav">
5 - <template #right>
6 - <van-icon name="search" size="18" />
7 - </template>
8 - </van-nav-bar>
9 -
10 - <!-- 内容区域 -->
11 - <div class="content-container">
12 - <!-- 顶部说明 -->
13 - <div class="intro-section">
14 - <div class="intro-card">
15 - <div class="intro-icon">📜</div>
16 - <div class="intro-content">
17 - <h3>三师七证</h3>
18 - <p>三师:得戒和尚、羯磨阿阇梨、教授阿阇梨<br>七证:七位证明师</p>
19 - </div>
20 - </div>
21 - </div>
22 -
23 - <!-- 筛选栏 -->
24 - <div class="filter-section">
25 - <van-tabs v-model:active="activeTab" @change="handleTabChange" class="custom-tabs">
26 - <van-tab title="全部" name="all"></van-tab>
27 - <van-tab title="三师" name="teachers"></van-tab>
28 - <van-tab title="七证" name="witnesses"></van-tab>
29 - </van-tabs>
30 - </div>
31 -
32 - <!-- 法师列表 -->
33 - <div class="teachers-list">
34 - <div
35 - v-for="teacher in filteredTeachers"
36 - :key="teacher.id"
37 - class="teacher-card"
38 - @click="handleTeacherClick(teacher)"
39 - >
40 - <div class="teacher-avatar">
41 - <img v-if="teacher.avatar" :src="teacher.avatar" :alt="teacher.name" />
42 - <div v-else class="avatar-placeholder">
43 - <span>{{ teacher.name.charAt(0) }}</span>
44 - </div>
45 - </div>
46 -
47 - <div class="teacher-info">
48 - <div class="teacher-header">
49 - <h4 class="teacher-name">{{ teacher.name }}</h4>
50 - <div class="teacher-role" :class="getRoleClass(teacher.role)">
51 - {{ teacher.role }}
52 - </div>
53 - </div>
54 -
55 - <div class="teacher-details">
56 - <p class="teacher-title">{{ teacher.title }}</p>
57 - <p class="teacher-temple">{{ teacher.temple }}</p>
58 - <div class="teacher-meta">
59 - <span class="ordination-year">{{ teacher.ordinationYear }}年受戒</span>
60 - <span class="experience">{{ teacher.experience }}年戒腊</span>
61 - </div>
62 - </div>
63 - </div>
64 -
65 - <div class="teacher-actions">
66 - <van-icon name="arrow" />
67 - </div>
68 - </div>
69 - </div>
70 -
71 - <!-- 空状态 -->
72 - <van-empty v-if="filteredTeachers.length === 0" description="暂无相关法师信息" />
73 - </div>
74 - </div>
75 -</template>
76 -
77 -<script setup>
78 -import { ref, computed } from 'vue'
79 -import { useRouter } from 'vue-router'
80 -
81 -const router = useRouter()
82 -const activeTab = ref('all')
83 -
84 -// 法师数据
85 -const teachers = ref([
86 - {
87 - id: 1,
88 - name: '慧明法师',
89 - title: '方丈',
90 - role: '得戒和尚',
91 - temple: '大觉寺',
92 - ordinationYear: 1985,
93 - experience: 39,
94 - avatar: null,
95 - type: 'teacher'
96 - },
97 - {
98 - id: 2,
99 - name: '智慧法师',
100 - title: '首座',
101 - role: '羯磨阿阇梨',
102 - temple: '大觉寺',
103 - ordinationYear: 1990,
104 - experience: 34,
105 - avatar: null,
106 - type: 'teacher'
107 - },
108 - {
109 - id: 3,
110 - name: '觉悟法师',
111 - title: '监院',
112 - role: '教授阿阇梨',
113 - temple: '大觉寺',
114 - ordinationYear: 1992,
115 - experience: 32,
116 - avatar: null,
117 - type: 'teacher'
118 - },
119 - {
120 - id: 4,
121 - name: '慈悲法师',
122 - title: '知客',
123 - role: '证明师',
124 - temple: '大觉寺',
125 - ordinationYear: 1995,
126 - experience: 29,
127 - avatar: null,
128 - type: 'witness'
129 - },
130 - {
131 - id: 5,
132 - name: '般若法师',
133 - title: '维那',
134 - role: '证明师',
135 - temple: '大觉寺',
136 - ordinationYear: 1998,
137 - experience: 26,
138 - avatar: null,
139 - type: 'witness'
140 - },
141 - {
142 - id: 6,
143 - name: '禅定法师',
144 - title: '典座',
145 - role: '证明师',
146 - temple: '大觉寺',
147 - ordinationYear: 2000,
148 - experience: 24,
149 - avatar: null,
150 - type: 'witness'
151 - },
152 - {
153 - id: 7,
154 - name: '精进法师',
155 - title: '书记',
156 - role: '证明师',
157 - temple: '大觉寺',
158 - ordinationYear: 2002,
159 - experience: 22,
160 - avatar: null,
161 - type: 'witness'
162 - },
163 - {
164 - id: 8,
165 - name: '持戒法师',
166 - title: '库头',
167 - role: '证明师',
168 - temple: '大觉寺',
169 - ordinationYear: 2005,
170 - experience: 19,
171 - avatar: null,
172 - type: 'witness'
173 - },
174 - {
175 - id: 9,
176 - name: '忍辱法师',
177 - title: '僧值',
178 - role: '证明师',
179 - temple: '大觉寺',
180 - ordinationYear: 2008,
181 - experience: 16,
182 - avatar: null,
183 - type: 'witness'
184 - },
185 - {
186 - id: 10,
187 - name: '布施法师',
188 - title: '衣钵',
189 - role: '证明师',
190 - temple: '大觉寺',
191 - ordinationYear: 2010,
192 - experience: 14,
193 - avatar: null,
194 - type: 'witness'
195 - }
196 -])
197 -
198 -// 过滤后的法师列表
199 -const filteredTeachers = computed(() => {
200 - if (activeTab.value === 'all') {
201 - return teachers.value
202 - } else if (activeTab.value === 'teachers') {
203 - return teachers.value.filter(teacher => teacher.type === 'teacher')
204 - } else if (activeTab.value === 'witnesses') {
205 - return teachers.value.filter(teacher => teacher.type === 'witness')
206 - }
207 - return teachers.value
208 -})
209 -
210 -// 获取角色样式类
211 -const getRoleClass = (role) => {
212 - if (role.includes('和尚') || role.includes('阿阇梨')) {
213 - return 'role-teacher'
214 - }
215 - return 'role-witness'
216 -}
217 -
218 -// 处理标签切换
219 -const handleTabChange = (name) => {
220 - activeTab.value = name
221 -}
222 -
223 -// 处理法师点击
224 -const handleTeacherClick = (teacher) => {
225 - router.push(`/teachers/${teacher.id}`)
226 -}
227 -</script>
228 -
229 -<style scoped>
230 -.page-container {
231 - min-height: 100vh;
232 - background: #fafafa;
233 -}
234 -
235 -.custom-nav {
236 - background: linear-gradient(135deg, #fbbf24, #f97316);
237 - color: white;
238 -}
239 -
240 -.custom-nav :deep(.van-nav-bar__title) {
241 - color: white;
242 - font-weight: 600;
243 -}
244 -
245 -.custom-nav :deep(.van-icon) {
246 - color: white;
247 -}
248 -
249 -.content-container {
250 - padding-top: 46px;
251 -}
252 -
253 -.intro-section {
254 - padding: 16px;
255 -}
256 -
257 -.intro-card {
258 - background: white;
259 - border-radius: 12px;
260 - padding: 20px;
261 - display: flex;
262 - align-items: center;
263 - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
264 -}
265 -
266 -.intro-icon {
267 - font-size: 32px;
268 - margin-right: 16px;
269 -}
270 -
271 -.intro-content h3 {
272 - font-size: 18px;
273 - font-weight: 600;
274 - color: #333;
275 - margin: 0 0 8px 0;
276 -}
277 -
278 -.intro-content p {
279 - font-size: 14px;
280 - color: #666;
281 - margin: 0;
282 - line-height: 1.5;
283 -}
284 -
285 -.filter-section {
286 - background: white;
287 - border-bottom: 1px solid #eee;
288 -}
289 -
290 -.custom-tabs :deep(.van-tab) {
291 - font-weight: 500;
292 -}
293 -
294 -.custom-tabs :deep(.van-tab--active) {
295 - color: #f59e0b;
296 -}
297 -
298 -.custom-tabs :deep(.van-tabs__line) {
299 - background: #f59e0b;
300 -}
301 -
302 -.teachers-list {
303 - padding: 16px;
304 -}
305 -
306 -.teacher-card {
307 - background: white;
308 - border-radius: 12px;
309 - padding: 16px;
310 - margin-bottom: 12px;
311 - display: flex;
312 - align-items: center;
313 - gap: 16px;
314 - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
315 - cursor: pointer;
316 - transition: all 0.3s ease;
317 -}
318 -
319 -.teacher-card:hover {
320 - transform: translateY(-3px);
321 - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
322 -}
323 -
324 -.teacher-card:active {
325 - transform: translateY(-1px);
326 - box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
327 -}
328 -
329 -.teacher-avatar {
330 - width: 60px;
331 - height: 60px;
332 - border-radius: 50%;
333 - overflow: hidden;
334 - margin-right: 16px;
335 - flex-shrink: 0;
336 -}
337 -
338 -.teacher-avatar img {
339 - width: 100%;
340 - height: 100%;
341 - object-fit: cover;
342 -}
343 -
344 -.avatar-placeholder {
345 - width: 100%;
346 - height: 100%;
347 - background: linear-gradient(135deg, #fbbf24, #f97316);
348 - display: flex;
349 - align-items: center;
350 - justify-content: center;
351 - color: white;
352 - font-size: 24px;
353 - font-weight: 600;
354 -}
355 -
356 -.teacher-info {
357 - flex: 1;
358 -}
359 -
360 -.teacher-header {
361 - display: flex;
362 - align-items: center;
363 - justify-content: space-between;
364 - margin-bottom: 8px;
365 -}
366 -
367 -.teacher-name {
368 - font-size: 18px;
369 - font-weight: 600;
370 - color: #333;
371 - margin: 0;
372 -}
373 -
374 -.teacher-role {
375 - padding: 4px 8px;
376 - border-radius: 12px;
377 - font-size: 12px;
378 - font-weight: 500;
379 -}
380 -
381 -.role-teacher {
382 - background: linear-gradient(135deg, #fbbf24, #f97316);
383 - color: white;
384 -}
385 -
386 -.role-witness {
387 - background: #f0f9ff;
388 - color: #0369a1;
389 - border: 1px solid #bae6fd;
390 -}
391 -
392 -.teacher-details {
393 - space-y: 4px;
394 -}
395 -
396 -.teacher-title {
397 - font-size: 16px;
398 - color: #666;
399 - margin: 0 0 4px 0;
400 -}
401 -
402 -.teacher-temple {
403 - font-size: 14px;
404 - color: #999;
405 - margin: 0 0 8px 0;
406 -}
407 -
408 -.teacher-meta {
409 - display: flex;
410 - gap: 16px;
411 -}
412 -
413 -.ordination-year,
414 -.experience {
415 - font-size: 12px;
416 - color: #999;
417 - background: #f5f5f5;
418 - padding: 2px 6px;
419 - border-radius: 4px;
420 -}
421 -
422 -.teacher-actions {
423 - margin-left: 12px;
424 - color: #ccc;
425 -}
426 -</style>
...\ No newline at end of file ...\ No newline at end of file
1 -<template> 1 +<!--
2 - <div class="page-container"> 2 + * @Date: 2025-01-01 15:20:00
3 - <!-- 导航栏 --> 3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - <van-nav-bar title="义工服务" left-arrow @click-left="$router.back()" class="custom-nav"> 4 + * @LastEditTime: 2025-10-30 21:15:28
5 - <template #right> 5 + * @FilePath: /stdj_h5/src/views/Volunteers.vue
6 - <van-icon name="plus" size="18" @click="handleAddVolunteer" /> 6 + * @Description: 义工页面 - 图片瀑布流展示
7 - </template> 7 +-->
8 - </van-nav-bar>
9 -
10 - <!-- 内容区域 -->
11 - <div class="content-container">
12 - <!-- 顶部统计 -->
13 - <div class="stats-section">
14 - <div class="stat-card">
15 - <div class="stat-icon">👥</div>
16 - <div class="stat-info">
17 - <div class="stat-number">{{ totalVolunteers }}</div>
18 - <div class="stat-label">总义工数</div>
19 - </div>
20 - </div>
21 - <div class="stat-card">
22 - <div class="stat-icon">✅</div>
23 - <div class="stat-info">
24 - <div class="stat-number">{{ activeVolunteers }}</div>
25 - <div class="stat-label">在岗义工</div>
26 - </div>
27 - </div>
28 - <div class="stat-card">
29 - <div class="stat-icon">📅</div>
30 - <div class="stat-info">
31 - <div class="stat-number">{{ todayTasks }}</div>
32 - <div class="stat-label">今日任务</div>
33 - </div>
34 - </div>
35 - </div>
36 8
37 - <!-- 筛选栏 --> 9 +<template>
38 - <div class="filter-section"> 10 + <div class="volunteers-container">
39 - <van-tabs v-model:active="activeTab" @change="handleTabChange" class="custom-tabs"> 11 + <!-- 瀑布流内容 -->
40 - <van-tab title="全部" name="all"></van-tab> 12 + <div class="waterfall-content">
41 - <van-tab title="在岗" name="active"></van-tab> 13 + <van-list
42 - <van-tab title="休息" name="rest"></van-tab> 14 + v-model:loading="loading"
43 - <van-tab title="请假" name="leave"></van-tab> 15 + :finished="finished"
44 - </van-tabs> 16 + finished-text="没有更多了"
45 - </div> 17 + @load="onLoad"
46 - 18 + >
47 - <!-- 义工列表 --> 19 + <div class="waterfall-container">
48 - <div class="volunteers-list"> 20 + <div class="waterfall-column" v-for="(column, index) in columns" :key="index">
49 - <div 21 + <div
50 - v-for="volunteer in filteredVolunteers" 22 + class="waterfall-item"
51 - :key="volunteer.id" 23 + v-for="item in column"
52 - class="volunteer-card" 24 + :key="item.id"
53 - @click="handleVolunteerClick(volunteer)" 25 + @click="onImageClick(item)"
54 - > 26 + >
55 - <div class="volunteer-avatar"> 27 + <div class="image-wrapper">
56 - <img v-if="volunteer.avatar" :src="volunteer.avatar" :alt="volunteer.name" /> 28 + <img
57 - <div v-else class="avatar-placeholder"> 29 + :src="item.url"
58 - <span>{{ volunteer.name.charAt(0) }}</span> 30 + :alt="item.title"
59 - </div> 31 + :style="{ height: item.height + 'px' }"
60 - <div class="status-badge" :class="getStatusClass(volunteer.status)"> 32 + @load="onImageLoad"
61 - {{ getStatusText(volunteer.status) }} 33 + @error="onImageError"
62 - </div> 34 + />
63 - </div> 35 + <div class="image-overlay">
64 - 36 + <span class="image-title">{{ item.title }}</span>
65 - <div class="volunteer-info"> 37 + </div>
66 - <div class="volunteer-header">
67 - <h4 class="volunteer-name">{{ volunteer.name }}</h4>
68 - <div class="volunteer-level" :class="getLevelClass(volunteer.level)">
69 - {{ volunteer.level }}
70 - </div>
71 - </div>
72 -
73 - <div class="volunteer-details">
74 - <p class="volunteer-department">{{ volunteer.department }}</p>
75 - <p class="volunteer-task">当前任务:{{ volunteer.currentTask || '暂无' }}</p>
76 - <div class="volunteer-meta">
77 - <span class="join-date">{{ volunteer.joinDate }}加入</span>
78 - <span class="service-hours">{{ volunteer.serviceHours }}小时</span>
79 </div> 38 </div>
80 </div> 39 </div>
81 </div> 40 </div>
82 -
83 - <div class="volunteer-actions">
84 - <van-icon name="arrow" />
85 - </div>
86 </div> 41 </div>
87 - </div> 42 + </van-list>
88 -
89 - <!-- 空状态 -->
90 - <van-empty v-if="filteredVolunteers.length === 0" description="暂无义工信息" />
91 </div> 43 </div>
92 44
93 - <!-- 浮动按钮 --> 45 + <!-- 遮罩层弹窗 -->
94 - <van-floating-bubble 46 + <van-overlay :show="showOverlay" @click="closeOverlay">
95 - axis="xy" 47 + <div class="overlay-content" @click.stop>
96 - icon="plus" 48 + <!-- 关闭按钮 -->
97 - @click="handleAddVolunteer" 49 + <div class="close-btn" @click="closeOverlay">
98 - class="add-button" 50 + <van-icon name="cross" size="1.5rem" color="#fff" />
99 - /> 51 + </div>
52 +
53 + <!-- 图片展示 -->
54 + <div class="overlay-image-wrapper">
55 + <img
56 + :src="selectedImage?.url"
57 + :alt="selectedImage?.title"
58 + class="overlay-image"
59 + />
60 + </div>
61 +
62 + <!-- 描述内容 -->
63 + <div class="overlay-description">
64 + <h3 class="overlay-title">{{ selectedImage?.title }}</h3>
65 + <p class="overlay-text">{{ selectedImage?.description }}</p>
66 + </div>
67 + </div>
68 + </van-overlay>
100 </div> 69 </div>
101 </template> 70 </template>
102 71
103 <script setup> 72 <script setup>
104 -import { ref, computed } from 'vue' 73 +import { ref, reactive, onMounted } from 'vue'
105 -import { useRouter } from 'vue-router' 74 +import { generateWaterfallData } from '@/utils/mockData'
106 -import { Toast } from 'vant' 75 +import { useTitle } from '@vueuse/core';
107 - 76 +
108 -const router = useRouter() 77 +useTitle('义工')
109 -const activeTab = ref('all') 78 +
110 - 79 +// 响应式数据
111 -// 统计数据 80 +const loading = ref(false)
112 -const totalVolunteers = ref(45) 81 +const finished = ref(false)
113 -const activeVolunteers = ref(32) 82 +const currentPage = ref(1)
114 -const todayTasks = ref(18) 83 +const pageSize = 10
115 - 84 +const allImages = ref([])
116 -// 义工数据 85 +const columns = reactive([[], []])
117 -const volunteers = ref([ 86 +
118 - { 87 +// 遮罩层相关状态
119 - id: 1, 88 +const showOverlay = ref(false)
120 - name: '张慧敏', 89 +const selectedImage = ref(null)
121 - department: '客堂组', 90 +
122 - level: '资深义工', 91 +// 加载数据
123 - status: 'active', 92 +const onLoad = async () => {
124 - currentTask: '接待来访信众', 93 + loading.value = true
125 - joinDate: '2022-03-15', 94 +
126 - serviceHours: 520, 95 + // 模拟网络延迟
127 - avatar: null 96 + await new Promise(resolve => setTimeout(resolve, 1000))
128 - }, 97 +
129 - { 98 + try {
130 - id: 2, 99 + const newData = generateWaterfallData(currentPage.value, pageSize)
131 - name: '李明德', 100 +
132 - department: '维护组', 101 + if (newData.length === 0) {
133 - level: '普通义工', 102 + finished.value = true
134 - status: 'active', 103 + } else {
135 - currentTask: '大殿清洁维护', 104 + allImages.value.push(...newData)
136 - joinDate: '2023-01-20', 105 + distributeImages(newData)
137 - serviceHours: 280, 106 + currentPage.value++
138 - avatar: null 107 + }
139 - }, 108 + } catch (error) {
140 - { 109 + console.error('加载数据失败:', error)
141 - id: 3, 110 + } finally {
142 - name: '王慈悲', 111 + loading.value = false
143 - department: '斋堂组',
144 - level: '组长',
145 - status: 'active',
146 - currentTask: '午斋准备工作',
147 - joinDate: '2021-08-10',
148 - serviceHours: 750,
149 - avatar: null
150 - },
151 - {
152 - id: 4,
153 - name: '陈智慧',
154 - department: '法务组',
155 - level: '资深义工',
156 - status: 'rest',
157 - currentTask: null,
158 - joinDate: '2022-06-05',
159 - serviceHours: 420,
160 - avatar: null
161 - },
162 - {
163 - id: 5,
164 - name: '刘精进',
165 - department: '安保组',
166 - level: '普通义工',
167 - status: 'leave',
168 - currentTask: null,
169 - joinDate: '2023-04-12',
170 - serviceHours: 150,
171 - avatar: null
172 - },
173 - {
174 - id: 6,
175 - name: '赵般若',
176 - department: '文宣组',
177 - level: '资深义工',
178 - status: 'active',
179 - currentTask: '活动摄影记录',
180 - joinDate: '2022-11-30',
181 - serviceHours: 380,
182 - avatar: null
183 - },
184 - {
185 - id: 7,
186 - name: '孙持戒',
187 - department: '客堂组',
188 - level: '普通义工',
189 - status: 'active',
190 - currentTask: '登记来访信息',
191 - joinDate: '2023-07-08',
192 - serviceHours: 95,
193 - avatar: null
194 - },
195 - {
196 - id: 8,
197 - name: '周忍辱',
198 - department: '斋堂组',
199 - level: '普通义工',
200 - status: 'rest',
201 - currentTask: null,
202 - joinDate: '2023-02-14',
203 - serviceHours: 220,
204 - avatar: null
205 } 112 }
206 -]) 113 +}
207 114
208 -// 过滤后的义工列表 115 +// 分配图片到两列
209 -const filteredVolunteers = computed(() => { 116 +const distributeImages = (images) => {
210 - if (activeTab.value === 'all') { 117 + images.forEach(image => {
211 - return volunteers.value 118 + // 计算两列的当前高度
212 - } 119 + const leftHeight = columns[0].reduce((sum, item) => sum + item.height + 20, 0)
213 - return volunteers.value.filter(volunteer => volunteer.status === activeTab.value) 120 + const rightHeight = columns[1].reduce((sum, item) => sum + item.height + 20, 0)
214 -})
215 121
216 -// 获取状态样式类 122 + // 将图片添加到高度较小的列
217 -const getStatusClass = (status) => { 123 + if (leftHeight <= rightHeight) {
218 - const classes = { 124 + columns[0].push(image)
219 - active: 'status-active', 125 + } else {
220 - rest: 'status-rest', 126 + columns[1].push(image)
221 - leave: 'status-leave' 127 + }
222 - } 128 + })
223 - return classes[status] || 'status-rest'
224 } 129 }
225 130
226 -// 获取状态文本 131 +// 图片点击事件
227 -const getStatusText = (status) => { 132 +const onImageClick = (item) => {
228 - const texts = { 133 + console.log('点击图片:', item)
229 - active: '在岗', 134 + selectedImage.value = item
230 - rest: '休息', 135 + showOverlay.value = true
231 - leave: '请假'
232 - }
233 - return texts[status] || '休息'
234 } 136 }
235 137
236 -// 获取等级样式类 138 +// 关闭遮罩层
237 -const getLevelClass = (level) => { 139 +const closeOverlay = () => {
238 - if (level === '组长') return 'level-leader' 140 + showOverlay.value = false
239 - if (level === '资深义工') return 'level-senior' 141 + selectedImage.value = null
240 - return 'level-normal'
241 } 142 }
242 143
243 -// 处理标签切换 144 +// 图片加载成功
244 -const handleTabChange = (name) => { 145 +const onImageLoad = (event) => {
245 - activeTab.value = name 146 + console.log('图片加载成功:', event.target.src)
246 } 147 }
247 148
248 -// 处理义工点击 149 +// 图片加载失败
249 -const handleVolunteerClick = (volunteer) => { 150 +const onImageError = (event) => {
250 - router.push(`/volunteers/${volunteer.id}`) 151 + console.error('图片加载失败:', event.target.src)
152 + // 可以设置默认图片
153 + event.target.src = 'https://via.placeholder.com/300x400?text=加载失败'
251 } 154 }
252 155
253 -// 处理添加义工 156 +// 组件挂载时初始化
254 -const handleAddVolunteer = () => { 157 +onMounted(() => {
255 - Toast('添加义工功能开发中...') 158 + // 初始加载第一页数据
256 -} 159 + onLoad()
160 +})
257 </script> 161 </script>
258 162
259 <style scoped> 163 <style scoped>
260 -.page-container { 164 +.volunteers-container {
165 + background-color: #F2EBDB;
261 min-height: 100vh; 166 min-height: 100vh;
262 - background: #fafafa; 167 + background-color: #f5f5f5;
263 } 168 }
264 169
265 -.custom-nav { 170 +.header {
266 - background: linear-gradient(135deg, #fbbf24, #f97316); 171 + position: sticky;
267 - color: white; 172 + top: 0;
173 + z-index: 100;
174 + background-color: #fff;
268 } 175 }
269 176
270 -.custom-nav :deep(.van-nav-bar__title) { 177 +.waterfall-content {
271 - color: white; 178 + padding: 1rem;
272 - font-weight: 600;
273 -}
274 -
275 -.custom-nav :deep(.van-icon) {
276 - color: white;
277 } 179 }
278 180
279 -.content-container { 181 +.waterfall-container {
280 - padding-top: 46px;
281 -}
282 -
283 -.stats-section {
284 display: flex; 182 display: flex;
285 - gap: 12px; 183 + gap: 0.75rem;
286 - padding: 16px; 184 + align-items: flex-start;
287 } 185 }
288 186
289 -.stat-card { 187 +.waterfall-column {
290 flex: 1; 188 flex: 1;
291 - background: white;
292 - border-radius: 12px;
293 - padding: 16px;
294 display: flex; 189 display: flex;
295 - align-items: center; 190 + flex-direction: column;
296 - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 191 + gap: 0.75rem;
297 -}
298 -
299 -.stat-icon {
300 - font-size: 24px;
301 - margin-right: 12px;
302 -}
303 -
304 -.stat-info {
305 - flex: 1;
306 -}
307 -
308 -.stat-number {
309 - font-size: 20px;
310 - font-weight: 700;
311 - color: #f59e0b;
312 - margin-bottom: 2px;
313 -}
314 -
315 -.stat-label {
316 - font-size: 12px;
317 - color: #666;
318 -}
319 -
320 -.filter-section {
321 - background: white;
322 - border-bottom: 1px solid #eee;
323 -}
324 -
325 -.custom-tabs :deep(.van-tab) {
326 - font-weight: 500;
327 -}
328 -
329 -.custom-tabs :deep(.van-tab--active) {
330 - color: #f59e0b;
331 -}
332 -
333 -.custom-tabs :deep(.van-tabs__line) {
334 - background: #f59e0b;
335 -}
336 -
337 -.volunteers-list {
338 - padding: 16px;
339 } 192 }
340 193
341 -.volunteer-card { 194 +.waterfall-item {
342 - background: white; 195 + background-color: #fff;
343 - border-radius: 12px; 196 + border-radius: 0.5rem;
344 - padding: 16px; 197 + overflow: hidden;
345 - margin-bottom: 12px;
346 - display: flex;
347 - align-items: center;
348 - gap: 16px;
349 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 198 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
199 + transition: transform 0.2s ease, box-shadow 0.2s ease;
350 cursor: pointer; 200 cursor: pointer;
351 - transition: all 0.3s ease;
352 -}
353 -
354 -.volunteer-card:hover {
355 - transform: translateY(-3px);
356 - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
357 } 201 }
358 202
359 -.volunteer-card:active { 203 +.waterfall-item:hover {
360 - transform: translateY(-1px); 204 + transform: translateY(-2px);
361 - box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12); 205 + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
362 } 206 }
363 207
364 -.volunteer-avatar { 208 +.image-wrapper {
365 position: relative; 209 position: relative;
366 - width: 60px;
367 - height: 60px;
368 - border-radius: 50%;
369 overflow: hidden; 210 overflow: hidden;
370 - margin-right: 16px;
371 - flex-shrink: 0;
372 } 211 }
373 212
374 -.volunteer-avatar img { 213 +.image-wrapper img {
375 width: 100%; 214 width: 100%;
376 - height: 100%; 215 + display: block;
377 object-fit: cover; 216 object-fit: cover;
217 + transition: transform 0.3s ease;
378 } 218 }
379 219
380 -.avatar-placeholder { 220 +.waterfall-item:hover .image-wrapper img {
381 - width: 100%; 221 + transform: scale(1.05);
382 - height: 100%;
383 - background: linear-gradient(135deg, #fbbf24, #f97316);
384 - display: flex;
385 - align-items: center;
386 - justify-content: center;
387 - color: white;
388 - font-size: 24px;
389 - font-weight: 600;
390 } 222 }
391 223
392 -.status-badge { 224 +.image-overlay {
393 position: absolute; 225 position: absolute;
394 - bottom: -2px; 226 + bottom: 0;
395 - right: -2px; 227 + left: 0;
396 - padding: 2px 6px; 228 + right: 0;
397 - border-radius: 8px; 229 + background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
398 - font-size: 10px; 230 + padding: 1rem 0.75rem 0.75rem;
399 - font-weight: 500; 231 + transform: translateY(100%);
400 - border: 2px solid white; 232 + transition: transform 0.3s ease;
401 } 233 }
402 234
403 -.status-active { 235 +.waterfall-item:hover .image-overlay {
404 - background: #10b981; 236 + transform: translateY(0);
405 - color: white;
406 } 237 }
407 238
408 -.status-rest { 239 +.image-title {
409 - background: #6b7280; 240 + color: #fff;
410 - color: white; 241 + font-size: 0.875rem;
242 + font-weight: 500;
243 + line-height: 1.4;
411 } 244 }
412 245
413 -.status-leave { 246 +/* 加载状态样式 */
414 - background: #ef4444; 247 +:deep(.van-list__loading) {
415 - color: white; 248 + padding: 1rem;
249 + text-align: center;
250 + color: #969799;
416 } 251 }
417 252
418 -.volunteer-info { 253 +:deep(.van-list__finished-text) {
419 - flex: 1; 254 + padding: 1rem;
255 + text-align: center;
256 + color: #969799;
257 + font-size: 0.875rem;
420 } 258 }
421 259
422 -.volunteer-header { 260 +/* 响应式设计 */
423 - display: flex; 261 +@media (max-width: 480px) {
424 - align-items: center; 262 + .waterfall-content {
425 - justify-content: space-between; 263 + padding: 0.75rem;
426 - margin-bottom: 8px; 264 + }
427 -}
428 -
429 -.volunteer-name {
430 - font-size: 18px;
431 - font-weight: 600;
432 - color: #333;
433 - margin: 0;
434 -}
435 265
436 -.volunteer-level { 266 + .waterfall-container {
437 - padding: 4px 8px; 267 + gap: 0.5rem;
438 - border-radius: 12px; 268 + }
439 - font-size: 12px;
440 - font-weight: 500;
441 -}
442 269
443 -.level-leader { 270 + .waterfall-column {
444 - background: linear-gradient(135deg, #fbbf24, #f97316); 271 + gap: 0.5rem;
445 - color: white; 272 + }
446 -}
447 273
448 -.level-senior { 274 + .image-title {
449 - background: #dbeafe; 275 + font-size: 0.8125rem;
450 - color: #1d4ed8; 276 + }
451 - border: 1px solid #93c5fd;
452 } 277 }
453 278
454 -.level-normal { 279 +/* 骨架屏效果 */
455 - background: #f3f4f6; 280 +.waterfall-item.loading {
456 - color: #6b7280; 281 + background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
457 - border: 1px solid #d1d5db; 282 + background-size: 200% 100%;
283 + animation: loading 1.5s infinite;
458 } 284 }
459 285
460 -.volunteer-details { 286 +@keyframes loading {
461 - space-y: 4px; 287 + 0% {
288 + background-position: 200% 0;
289 + }
290 + 100% {
291 + background-position: -200% 0;
292 + }
462 } 293 }
463 294
464 -.volunteer-department { 295 +/* 遮罩层样式 */
465 - font-size: 16px; 296 +:deep(.van-overlay) {
466 - color: #666; 297 + display: flex;
467 - margin: 0 0 4px 0; 298 + align-items: center;
299 + justify-content: center;
300 + padding: 1rem;
468 } 301 }
469 302
470 -.volunteer-task { 303 +.overlay-content {
471 - font-size: 14px; 304 + background-color: #fff;
472 - color: #999; 305 + border-radius: 1rem;
473 - margin: 0 0 8px 0; 306 + max-width: 90vw;
307 + max-height: 80vh;
308 + overflow: hidden;
309 + position: relative;
310 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
474 } 311 }
475 312
476 -.volunteer-meta { 313 +.close-btn {
314 + position: absolute;
315 + top: 1rem;
316 + right: 1rem;
317 + width: 2.5rem;
318 + height: 2.5rem;
319 + background-color: rgba(0, 0, 0, 0.5);
320 + border-radius: 50%;
477 display: flex; 321 display: flex;
478 - gap: 16px; 322 + align-items: center;
323 + justify-content: center;
324 + cursor: pointer;
325 + z-index: 10;
326 + transition: background-color 0.2s ease;
479 } 327 }
480 328
481 -.join-date, 329 +.close-btn:hover {
482 -.service-hours { 330 + background-color: rgba(0, 0, 0, 0.7);
483 - font-size: 12px;
484 - color: #999;
485 - background: #f5f5f5;
486 - padding: 2px 6px;
487 - border-radius: 4px;
488 } 331 }
489 332
490 -.volunteer-actions { 333 +.overlay-image-wrapper {
491 - margin-left: 12px; 334 + width: 100%;
492 - color: #ccc; 335 + max-height: 60vh;
336 + overflow: hidden;
337 + display: flex;
338 + align-items: center;
339 + justify-content: center;
340 + background-color: #f5f5f5;
493 } 341 }
494 342
495 -.add-button { 343 +.overlay-image {
496 - background: linear-gradient(135deg, #fbbf24, #f97316) !important; 344 + width: 100%;
497 - transition: all 0.3s ease; 345 + height: auto;
498 - animation: float 3s ease-in-out infinite; 346 + max-height: 60vh;
347 + object-fit: contain;
348 + display: block;
499 } 349 }
500 350
501 -.add-button:hover { 351 +.overlay-description {
502 - transform: translateY(-3px) scale(1.1); 352 + padding: 1.5rem;
503 - box-shadow: 0 8px 25px rgba(251, 191, 36, 0.6); 353 + background-color: #fff;
504 } 354 }
505 355
506 -.add-button:active { 356 +.overlay-title {
507 - transform: translateY(-1px) scale(1.05); 357 + font-size: 1.25rem;
508 - box-shadow: 0 4px 16px rgba(251, 191, 36, 0.4); 358 + font-weight: 600;
359 + color: #333;
360 + margin: 0 0 1rem 0;
361 + line-height: 1.4;
509 } 362 }
510 363
511 -.add-button :deep(.van-floating-bubble__icon) { 364 +.overlay-text {
512 - color: white; 365 + font-size: 1rem;
366 + color: #666;
367 + line-height: 1.6;
368 + margin: 0;
369 + white-space: pre-wrap;
513 } 370 }
514 371
515 -@keyframes float { 372 +/* 移动端适配 */
516 - 0%, 100% { 373 +@media (max-width: 480px) {
517 - transform: translateY(0px); 374 + .overlay-content {
375 + max-width: 95vw;
376 + max-height: 85vh;
377 + border-radius: 0.75rem;
378 + }
379 +
380 + .close-btn {
381 + top: 0.75rem;
382 + right: 0.75rem;
383 + width: 2rem;
384 + height: 2rem;
385 + }
386 +
387 + .overlay-description {
388 + padding: 1rem;
389 + }
390 +
391 + .overlay-title {
392 + font-size: 1.125rem;
518 } 393 }
519 - 50% { 394 +
520 - transform: translateY(-10px); 395 + .overlay-text {
396 + font-size: 0.875rem;
521 } 397 }
522 } 398 }
523 -</style>
...\ No newline at end of file ...\ No newline at end of file
399 +</style>
......