skyline пре 1 дан
родитељ
комит
9815cf2c7f

+ 3 - 0
car-wash-entity/src/main/java/com/kym/entity/vo/NoticeStationVo.java

@@ -2,6 +2,7 @@ package com.kym.entity.vo;
 
 import com.kym.entity.BaseEntity;
 import com.kym.entity.SystemNotice;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
@@ -44,11 +45,13 @@ public class NoticeStationVo extends BaseEntity {
     /**
      * 开始时间
      */
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime startTime;
 
     /**
      * 结束时间
      */
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime endTime;
 
     /**

+ 180 - 9
car-wash-mp/src/pages/index/index.vue

@@ -139,15 +139,41 @@
         <view class="popup-content">
           <text class="popup-desc">长按识别二维码获取停车优惠</text>
           <text class="popup-warning">未满最低消费无法领取停车优惠</text>
-          <image 
-            class="qrcode-image" 
-            show-menu-by-longpress 
+          <image
+            class="qrcode-image"
+            show-menu-by-longpress
             src="/static/parking-qrcode.jpg"
           />
         </view>
       </view>
     </uv-popup>
 
+    <!-- 公告详情弹窗 -->
+    <view class="notice-popup-mask" v-if="noticePopupVisible" @click="closeNoticePopup">
+      <view class="notice-popup" @click.stop>
+        <view class="notice-popup-head">
+          <view class="notice-popup-head-icon">
+            <text class="head-icon-inner">!</text>
+          </view>
+          <text class="notice-popup-head-title">公告</text>
+          <view class="notice-popup-close" @click="closeNoticePopup">
+            <text class="close-icon">✕</text>
+          </view>
+        </view>
+        <scroll-view class="notice-popup-body" scroll-y>
+          <view class="notice-card" v-for="(item, idx) in state.noticeList" :key="idx">
+            <text class="notice-card-title">{{ item.title }}</text>
+            <view class="notice-card-divider"></view>
+            <text class="notice-card-content">{{ item.content }}</text>
+            <text class="notice-card-time" v-if="item.startTime">{{ item.startTime }} — {{ item.endTime }}</text>
+          </view>
+        </scroll-view>
+        <view class="notice-popup-foot">
+          <view class="notice-popup-btn" @click="closeNoticePopup">我知道了</view>
+        </view>
+      </view>
+    </view>
+
     <!-- 底部导航栏 -->
     <tab-bar :index="0"></tab-bar>
   </view>
@@ -163,6 +189,7 @@ import {calcMapDistance} from "@/utils/common"
 import {checkLogin, fetchToken, loadUserInfo, tryLogin} from "@/utils/auth";
 
 const parking_popup_ref = ref();
+const noticePopupVisible = ref(false);
 const isLogin = ref(false)
 const isLoading = ref(true)
 const statusBarHeight = ref(0)
@@ -308,12 +335,11 @@ const loadNoticeList = () => {
 }
 
 const handleNoticeClick = () => {
-  uni.showModal({
-    title: '公告详情',
-    content: state.noticeList.map((n: any, i: number) => `${i + 1}. ${n.title}\n${n.content || ''}`).join('\n\n'),
-    showCancel: false,
-    confirmText: '我知道了'
-  })
+  noticePopupVisible.value = true;
+}
+
+const closeNoticePopup = () => {
+  noticePopupVisible.value = false;
 }
 
 const handleBannerClick = (index: number) => {
@@ -762,4 +788,149 @@ page {
     }
   }
 }
+
+// 公告详情弹窗
+.notice-popup-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.45);
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.notice-popup {
+  width: 630rpx;
+  max-height: 78vh;
+  background: #fff;
+  border-radius: 24rpx;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.12);
+}
+
+.notice-popup-head {
+  display: flex;
+  align-items: center;
+  padding: 36rpx 32rpx 28rpx;
+  flex-shrink: 0;
+}
+
+.notice-popup-head-icon {
+  width: 44rpx;
+  height: 44rpx;
+  border-radius: 12rpx;
+  background: linear-gradient(135deg, #C6171E, #E84545);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+
+  .head-icon-inner {
+    color: #fff;
+    font-size: 28rpx;
+    font-weight: 800;
+    font-style: italic;
+  }
+}
+
+.notice-popup-head-title {
+  font-size: 34rpx;
+  font-weight: 700;
+  color: #1a1a1a;
+  flex: 1;
+}
+
+.notice-popup-close {
+  width: 52rpx;
+  height: 52rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 50%;
+  background: #f2f2f2;
+
+  .close-icon {
+    font-size: 28rpx;
+    color: #999;
+    font-weight: 500;
+  }
+}
+
+.notice-popup-body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 0 28rpx;
+}
+
+.notice-card {
+  padding: 28rpx 28rpx 24rpx;
+  margin-bottom: 20rpx;
+  border-radius: 16rpx;
+  background: #fff;
+  border: 1rpx solid #eee;
+  border-left: 6rpx solid #C6171E;
+}
+
+.notice-card-title {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #1a1a1a;
+  line-height: 1.5;
+  display: block;
+}
+
+.notice-card-divider {
+  width: 48rpx;
+  height: 4rpx;
+  background: #C6171E;
+  border-radius: 2rpx;
+  margin: 18rpx 0;
+  opacity: 0.45;
+}
+
+.notice-card-content {
+  font-size: 27rpx;
+  color: #666;
+  line-height: 1.9;
+  display: block;
+  letter-spacing: 0.5rpx;
+}
+
+.notice-card-time {
+  display: block;
+  margin-top: 20rpx;
+  font-size: 22rpx;
+  color: #bbb;
+  letter-spacing: 0.3rpx;
+}
+
+.notice-popup-foot {
+  padding: 24rpx 32rpx 36rpx;
+  flex-shrink: 0;
+}
+
+.notice-popup-btn {
+  width: 100%;
+  height: 88rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(135deg, #C6171E, #E84545);
+  border-radius: 44rpx;
+  color: #fff;
+  font-size: 30rpx;
+  font-weight: 600;
+  letter-spacing: 2rpx;
+
+  &:active {
+    opacity: 0.85;
+    transform: scale(0.98);
+  }
+}
 </style>