木邻有你前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

486 lines
12 KiB

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
  1. <template>
  2. <view class="activity-detail">
  3. <!-- 轮播图 -->
  4. <view class="banner-container">
  5. <swiper class="banner-swiper" height="450rpx" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="500">
  6. <!-- 如果没有,分割 -->
  7. <swiper-item v-for="(image, index) in imageList" :key="index">
  8. <image class="banner-image" :src="image" mode="aspectFill"></image>
  9. </swiper-item>
  10. </swiper>
  11. </view>
  12. <!-- 活动信息 -->
  13. <view class="activity-info">
  14. <!-- 活动标题和标签 -->
  15. <view class="title-section">
  16. <view class="activity-badge">
  17. <text class="badge-text">{{ activityData.score }}积分</text>
  18. </view>
  19. <text class="activity-title">{{ activityData.title }}</text>
  20. </view>
  21. <!-- 活动详细信息 -->
  22. <view class="info-section">
  23. <view class="info-item">
  24. <uv-icon name="calendar" size="16" color="#666"></uv-icon>
  25. <text class="info-label">活动时间</text>
  26. <text class="info-value">{{ activityData.activityTime }}</text>
  27. </view>
  28. <view class="info-item">
  29. <uv-icon name="clock" size="16" color="#666"></uv-icon>
  30. <text class="info-label">报名时间</text>
  31. <text class="info-value">{{ activityData.startTime }}</text>
  32. </view>
  33. <view class="info-item">
  34. <uv-icon name="account-fill" size="16" color="#666"></uv-icon>
  35. <text class="info-label">联系人</text>
  36. <text class="info-value">{{ activityData.contact }}</text>
  37. </view>
  38. <view class="info-item">
  39. <uv-icon name="phone" size="16" color="#666"></uv-icon>
  40. <text class="info-label">取消规则</text>
  41. <text class="info-value">{{ activityData.rule }}</text>
  42. </view>
  43. <view class="info-item">
  44. <uv-icon name="map-fill" size="16" color="#666"></uv-icon>
  45. <text class="info-label">活动地点</text>
  46. <text class="info-value">{{ activityData.address }}</text>
  47. </view>
  48. </view>
  49. <!-- 活动详情 -->
  50. <view class="detail-section">
  51. <view class="section-title">
  52. <text class="title-text">活动详情</text>
  53. </view>
  54. <view class="detail-content">
  55. <!-- <text class="detail-text"> -->
  56. <rich-text :nodes="activityData.details"></rich-text>
  57. <!-- </text> -->
  58. </view>
  59. </view>
  60. <!-- 活动图集 -->
  61. <view class="gallery-section">
  62. <view class="section-title">
  63. <text class="title-text">活动图集</text>
  64. </view>
  65. <view class="gallery-grid">
  66. <image
  67. v-for="(image, index) in atlas"
  68. :key="index"
  69. class="gallery-image"
  70. :src="image"
  71. mode="aspectFill"
  72. @click="previewImage(image, atlas)"
  73. ></image>
  74. <!-- <uv-album :urls="activityData.gallery"></uv-album> -->
  75. </view>
  76. </view>
  77. </view>
  78. <!-- 固定底部操作栏 -->
  79. <view class="bottom-action">
  80. <view class="action-left">
  81. <button open-type="share">
  82. <view class="action-item">
  83. <uv-icon name="share" size="24" color="#000"></uv-icon>
  84. <text class="action-text">分享</text>
  85. </view>
  86. </button>
  87. <view class="action-item" @click="collectActivity">
  88. <uv-icon name="heart-fill" class="collection-icon" size="24" :color="activityData.isCollection === 1 ? '#ff4757' : '#999'"></uv-icon>
  89. <text class="action-text">收藏</text>
  90. </view>
  91. <view class="action-item">
  92. <text class="participants-count">
  93. <text :style="{'color': activityData.numActivity >= activityData.numLimit ? '#999' : '#1488DB'}">{{ activityData.numActivity }}</text>
  94. /{{ activityData.numLimit }}</text>
  95. <text class="action-text">已报名</text>
  96. </view>
  97. </view>
  98. <view class="action-right">
  99. <uv-button
  100. v-if="activityData.status === '1'"
  101. type="primary"
  102. size="normal"
  103. text="已结束"
  104. shape="circle"
  105. @click="signUpActivity"
  106. :disabled="true"
  107. ></uv-button>
  108. <uv-button
  109. v-else-if="activityData.isApply === 1"
  110. type="primary"
  111. size="normal"
  112. text="您已报名"
  113. shape="circle"
  114. @click="signUpActivity"
  115. :disabled="true"
  116. ></uv-button>
  117. <uv-button
  118. v-else
  119. type="primary"
  120. size="normal"
  121. text="我要报名"
  122. shape="circle"
  123. @click="signUpActivity"
  124. :disabled="activityData.numActivity >= activityData.numLimit "
  125. ></uv-button>
  126. </view>
  127. </view>
  128. <SignUpForm
  129. ref="signUpFormRef"
  130. @close="onSignUpFormClose"
  131. @submit="onSignUpFormSubmit"
  132. />
  133. <GlobalPopup ref="globalPopupRef"></GlobalPopup>
  134. </view>
  135. <!-- 报名表单弹窗 -->
  136. </template>
  137. <script>
  138. import SignUpForm from '@/subPages/index/components/SignUpForm.vue'
  139. export default {
  140. components: {
  141. SignUpForm
  142. },
  143. data() {
  144. return {
  145. // isCollected: false,
  146. showSignUpForm: false,
  147. activityData: {
  148. title: '关爱自闭症儿童活动',
  149. duration: '30积分',
  150. time: '2025-06-12 14:30',
  151. registrationTime: '2025-06-01 14:30——2025-09-01 14:30',
  152. contact: '柳老师 (13256484512)',
  153. cancelRule: '报名随时可取消',
  154. location: '长沙市雨花区时代阳光大夏国际大厅2145',
  155. registeredCount: 9,
  156. maxCount: 30,
  157. details: [
  158. '身体健康,热爱志愿服务工作,富有责任感和奉献精神',
  159. '遵纪守法,思想上进,作风正派,服从安排',
  160. '年龄在60岁以下,具备广告宣传理能力'
  161. ],
  162. gallery: [
  163. '/static/bannerImage.png',
  164. '/static/bannerImage.png',
  165. '/static/bannerImage.png',
  166. '/static/bannerImage.png'
  167. ]
  168. },
  169. activityId: null
  170. }
  171. },
  172. computed:{
  173. imageList(){
  174. if (this.activityData.image) {
  175. return this.activityData.image.split(',')
  176. }
  177. return []
  178. },
  179. atlas(){
  180. if (this.activityData.atlas) {
  181. return this.activityData.atlas.split(',')
  182. }
  183. return []
  184. }
  185. },
  186. onLoad(options) {
  187. if (options.id) {
  188. this.activityId = options.id
  189. this.loadActivityDetail(options.id)
  190. }else {
  191. uni.showToast({
  192. title: '没有给活动id',
  193. icon: 'none'
  194. })
  195. }
  196. },
  197. methods: {
  198. // 自定义分享内容
  199. mixinCustomShare() {
  200. return {
  201. desc: '',
  202. title: `邀请您参加${this.activityData.title || ''}`,
  203. imageUrl: this.imageList[0],
  204. path: '/subPages/index/activityDetail?id=' + this.activityId
  205. }
  206. },
  207. async loadActivityDetail(id) {
  208. let params = {}
  209. if (uni.getStorageSync('token')) {
  210. params.token = uni.getStorageSync('token')
  211. }
  212. // 根据ID加载活动详情
  213. const res = await this.$api.activity.queryActivityById({
  214. activityId: id,
  215. ...params
  216. })
  217. this.activityData = res.result
  218. },
  219. previewImage(current, urls) {
  220. uni.previewImage({
  221. current: current,
  222. urls: urls
  223. })
  224. },
  225. async collectActivity() {
  226. const res = await this.$api.activity.collectionActivity({
  227. activityId: this.activityId
  228. })
  229. await this.loadActivityDetail(this.activityId)
  230. uni.showToast({
  231. title: `${res.message}`,
  232. icon: 'none'
  233. })
  234. },
  235. signUpActivity() {
  236. if (this.activityData.numActivity >= this.activityData.numLimit) {
  237. uni.showToast({
  238. title: '报名人数已满',
  239. icon: 'none'
  240. })
  241. return
  242. }
  243. this.$refs.signUpFormRef.open()
  244. },
  245. onSignUpFormClose() {
  246. this.$refs.signUpFormRef.close()
  247. },
  248. async onSignUpFormSubmit(formData) {
  249. console.log('报名表单数据:', formData)
  250. // 这里可以调用API提交报名数据
  251. const res = await this.$api.activity.applyActivity({
  252. activityId: this.activityId,
  253. ...formData
  254. })
  255. if (res.code === 200) {
  256. this.$refs.globalPopupRef.open({
  257. content: '恭喜您报名成功',
  258. subContent: '别忘了准时参与活动哦!',
  259. titleType: 'submit',
  260. popupType: 'success',
  261. closefn: () => {
  262. setTimeout(() => {
  263. uni.navigateBack()
  264. }, 300);
  265. }
  266. })
  267. // 更新状态
  268. // this.loadActivityDetail(this.activityId)
  269. }else {
  270. uni.showToast({
  271. title: `${res.message}`,
  272. icon: 'none'
  273. })
  274. }
  275. }
  276. },
  277. async onPullDownRefresh() {
  278. await this.loadActivityDetail(this.activityId)
  279. uni.stopPullDownRefresh()
  280. }
  281. }
  282. </script>
  283. <style lang="scss" scoped>
  284. .activity-detail {
  285. min-height: 100vh;
  286. background: #f8f8f8;
  287. padding-bottom: 120rpx;
  288. .banner-container {
  289. width: 100%;
  290. height: 450rpx;
  291. .banner-swiper {
  292. width: 100%;
  293. height: 100%;
  294. .banner-image {
  295. width: 100%;
  296. height: 100%;
  297. }
  298. }
  299. }
  300. .activity-info {
  301. background: #ffffff;
  302. // border: 1rpx dashed #F3F7F8;
  303. margin: 20rpx;
  304. border-radius: 16rpx;
  305. padding: 30rpx;
  306. .title-section {
  307. display: flex;
  308. align-items: center;
  309. margin-bottom: 30rpx;
  310. .activity-badge {
  311. background: #218CDD;
  312. border-radius: 8rpx;
  313. height: 40rpx;
  314. padding: 0 5rpx 10rpx;
  315. line-height: 40rpx;
  316. text-align: center;
  317. margin-right: 16rpx;
  318. .badge-text {
  319. color: #ffffff;
  320. font-size: 24rpx;
  321. font-weight: 500;
  322. }
  323. }
  324. .activity-title {
  325. font-size: 36rpx;
  326. font-weight: bold;
  327. color: #333333;
  328. flex: 1;
  329. }
  330. }
  331. .info-section {
  332. background: #F3F7F8;
  333. margin-bottom: 40rpx;
  334. // 虚线属性
  335. border: 2rpx dashed #F3F7F8;
  336. .info-item {
  337. display: flex;
  338. align-items: center;
  339. margin-bottom: 20rpx;
  340. &:last-child {
  341. margin-bottom: 0;
  342. }
  343. .info-label {
  344. font-size: 28rpx;
  345. color: #999999;
  346. margin-left: 12rpx;
  347. margin-right: 8rpx;
  348. }
  349. .info-value {
  350. font-size: 28rpx;
  351. color: #999999;
  352. flex: 1;
  353. }
  354. }
  355. }
  356. .detail-section {
  357. margin-bottom: 40rpx;
  358. .section-title {
  359. margin-bottom: 20rpx;
  360. .title-text {
  361. font-size: 32rpx;
  362. font-weight: bold;
  363. color: #333333;
  364. }
  365. }
  366. .detail-content {
  367. .detail-text {
  368. display: block;
  369. font-size: 28rpx;
  370. color: #666666;
  371. line-height: 1.6;
  372. margin-bottom: 16rpx;
  373. &:last-child {
  374. margin-bottom: 0;
  375. }
  376. }
  377. }
  378. }
  379. .gallery-section {
  380. .section-title {
  381. margin-bottom: 20rpx;
  382. .title-text {
  383. font-size: 32rpx;
  384. font-weight: bold;
  385. color: #333333;
  386. }
  387. }
  388. .gallery-grid {
  389. display: grid;
  390. grid-template-columns: repeat(2, 1fr);
  391. gap: 16rpx;
  392. .gallery-image {
  393. width: 100%;
  394. height: 200rpx;
  395. border-radius: 12rpx;
  396. }
  397. }
  398. }
  399. }
  400. .bottom-action {
  401. position: fixed;
  402. bottom: 0;
  403. left: 0;
  404. right: 0;
  405. background: #ffffff;
  406. padding: 20rpx 30rpx;
  407. border-top: 1rpx solid #eeeeee;
  408. display: flex;
  409. align-items: center;
  410. justify-content: space-between;
  411. z-index: 100;
  412. .action-left {
  413. display: flex;
  414. align-items: center;
  415. gap: 100rpx;
  416. .action-item {
  417. display: flex;
  418. flex-direction: column;
  419. align-items: center;
  420. gap: 8rpx;
  421. .action-text {
  422. font-size: 22rpx;
  423. color: #000;
  424. }
  425. .participants-count {
  426. font-size: 24rpx;
  427. color: #333333;
  428. // font-weight: bold;
  429. }
  430. }
  431. }
  432. .action-right {
  433. flex-shrink: 0;
  434. }
  435. }
  436. }
  437. </style>