木邻有你前端代码仓库
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.

462 lines
12 KiB

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