478 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
			
		
		
	
	
			478 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
| <template>
 | |
|   <view class="message-board-component">
 | |
|     <!-- 一个列表一个swipe-action容器更符合官方推荐用法 -->
 | |
|     <view v-for="(item, index) in answerList" :key="item.id">
 | |
|       <uni-swipe-action ref="swipeActionRef">
 | |
|         <!-- :right-options="swipeOptions" -->
 | |
|         <uni-swipe-action-item
 | |
|           :disabled="!canSwipe"
 | |
|           :threshold="0.3"
 | |
|           :auto-close="false"
 | |
|           @change="handleSwipeChange($event, index)"
 | |
|         >
 | |
|           <template v-slot:right>
 | |
|             <view
 | |
|               class="right-btn-box"
 | |
|               @touchend.stop="handleSwipeClick(item, index)"
 | |
|             >
 | |
|               <image
 | |
|                 class="right-btn"
 | |
|                 src="/static/common/images/icon_delete.png"
 | |
|               ></image>
 | |
|               <text>撤回</text>
 | |
|             </view>
 | |
|           </template>
 | |
| 
 | |
|           <view class="answer-item-box">
 | |
|             <view
 | |
|               class="answer-item"
 | |
|               :class="{
 | |
|                 'no-right-radius': openedItems[index] === 'right',
 | |
|                 'is-web-mode': isWebMode,
 | |
|               }"
 | |
|             >
 | |
|               <view class="student-info">
 | |
|                 <image
 | |
|                   class="student-avatar"
 | |
|                   :src="item.studentAvatar || AvatarDefault"
 | |
|                 ></image>
 | |
| 
 | |
|                 <view class="student-info-content">
 | |
|                   <text class="student-name">{{ item.studentName }}</text>
 | |
|                   <text class="answer-time">{{ item.time }}</text>
 | |
|                 </view>
 | |
|               </view>
 | |
| 
 | |
|               <view class="question">
 | |
|                 <image
 | |
|                   class="question-tag"
 | |
|                   src="/static/common/images/icon_ask.png"
 | |
|                 ></image>
 | |
|                 <view class="question-content">{{ item.question }}</view>
 | |
|               </view>
 | |
| 
 | |
|               <view v-if="item.isReply">
 | |
|                 <view class="line"></view>
 | |
| 
 | |
|                 <view class="student-info">
 | |
|                   <image
 | |
|                     class="student-avatar"
 | |
|                     :src="item.teacherAvatar || AvatarDefault"
 | |
|                   ></image>
 | |
| 
 | |
|                   <view class="student-info-content">
 | |
|                     <text class="student-name">{{ item.teacherName }}</text>
 | |
|                     <text class="answer-time">{{ item.replyTime }}</text>
 | |
|                   </view>
 | |
|                 </view>
 | |
| 
 | |
|                 <view class="answer">
 | |
|                   <image
 | |
|                     class="answer-tag"
 | |
|                     src="/static/common/images/icon_answer.png"
 | |
|                   ></image>
 | |
|                   <view class="answer-content">
 | |
|                     <!-- <view class="answer-text">{{ item.fullAnswer }}</view> -->
 | |
|                     <xzj-readMore>{{ item.fullAnswer }}</xzj-readMore>
 | |
|                   </view>
 | |
|                 </view>
 | |
|               </view>
 | |
| 
 | |
|               <!-- 回复按钮-老师 -->
 | |
|               <view class="reply-btn-box" v-if="isTeacher">
 | |
|                 <button
 | |
|                   class="reply-btn uni-btn-reset"
 | |
|                   :class="{ 'disabled-reply': item.isReply }"
 | |
|                   @click.stop="handleReplyClick(item)"
 | |
|                 >
 | |
|                   <image
 | |
|                     class="reply-btn-icon"
 | |
|                     src="/static/common/images/icon_reply.png"
 | |
|                   ></image
 | |
|                   >回复
 | |
|                 </button>
 | |
|               </view>
 | |
|             </view>
 | |
| 
 | |
|             <view
 | |
|               v-if="isWebMode"
 | |
|               class="right-btn-box"
 | |
|               :class="{
 | |
|                 'web-mode-cursor': isWebMode,
 | |
|               }"
 | |
|               @click.stop="handleWebModeClick(item)"
 | |
|             >
 | |
|               <image
 | |
|                 class="right-btn"
 | |
|                 src="/static/common/images/icon_delete.png"
 | |
|               ></image>
 | |
|               <text>撤回</text>
 | |
|             </view>
 | |
|           </view>
 | |
|         </uni-swipe-action-item>
 | |
|       </uni-swipe-action>
 | |
|     </view>
 | |
| 
 | |
|     <!-- 模态弹窗 -->
 | |
|     <u-popup
 | |
|       v-model="modalShow"
 | |
|       mode="center"
 | |
|       width="70%"
 | |
|       border-radius="16"
 | |
|       :mask-close-able="false"
 | |
|     >
 | |
|       <view class="custom-modal">
 | |
|         <view class="modal-title">提示</view>
 | |
|         <view class="modal-content">{{ content }}</view>
 | |
|         <view class="modal-footer">
 | |
|           <view class="modal-btn cancel" @click="handleCancel">取消</view>
 | |
|           <view class="modal-btn confirm" @click="handleConfirm">确认</view>
 | |
|         </view>
 | |
|       </view>
 | |
|     </u-popup>
 | |
|   </view>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| import AvatarDefault from "@/static/common/images/avatar_default.jpg";
 | |
| import UniSwipeAction from "@/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue";
 | |
| import XzjReadMore from "@/components/xzj-readMore/xzj-readMore.vue";
 | |
| 
 | |
| export default {
 | |
|   name: "MessageBoard",
 | |
|   components: {
 | |
|     UniSwipeAction,
 | |
|     XzjReadMore,
 | |
|   },
 | |
|   props: {
 | |
|     answerList: {
 | |
|       type: Array,
 | |
|       default: () => [],
 | |
|     },
 | |
|     isTeacher: {
 | |
|       type: Boolean,
 | |
|       default: false,
 | |
|     },
 | |
|     // 是否允许左滑
 | |
|     canSwipe: {
 | |
|       type: Boolean,
 | |
|       default: true,
 | |
|     },
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       AvatarDefault, // 默认头像
 | |
|       currentItem: {}, // 当前操作的item
 | |
|       currentIndex: 0, // 当前操作的item索引
 | |
|       openedItems: {}, // 记录每个项目的滑动状态
 | |
|       isWebMode: false, // 是否是web模式
 | |
| 
 | |
|       // 模态弹窗
 | |
|       modalShow: false,
 | |
|       content: "确定要撤回该留言吗?",
 | |
|       modalOpenTime: 0,
 | |
|       preventCloseOnce: false, // 添加防误触标记
 | |
|     };
 | |
|   },
 | |
|   mounted() {
 | |
|     // #ifdef H5
 | |
|     const userAgent = navigator.userAgent.toLowerCase();
 | |
|     this.isWebMode = !/mobile|android|iphone|ipod|ipad/.test(userAgent);
 | |
|     // #endif
 | |
| 
 | |
|     console.log("isWebMode", this.isWebMode);
 | |
|   },
 | |
|   methods: {
 | |
|     // 监听滑动状态变化
 | |
|     handleSwipeChange(e, index) {
 | |
|       // 使用Vue的响应式更新方法来更新对象
 | |
|       this.$set(this.openedItems, index, e);
 | |
|       // console.log("滑动状态", this.openedItems);
 | |
|     },
 | |
|     // 处理左滑按钮点击
 | |
|     handleSwipeClick(item, index) {
 | |
|       // console.log("swipeClick", item, index);
 | |
|       // console.log("swipeActionRef", this.$refs.swipeActionRef);
 | |
|       this.currentItem = item;
 | |
|       this.currentIndex = index;
 | |
|       this.modalShow = true;
 | |
|       this.modalOpenTime = Date.now(); // 记录模态框打开时间
 | |
|     },
 | |
| 
 | |
|     // 处理web模式点击
 | |
|     handleWebModeClick(item) {
 | |
|       console.log("web模式点击", item);
 | |
|     },
 | |
| 
 | |
|     // 处理回复按钮点击
 | |
|     handleReplyClick(item) {
 | |
|       if (item.isReply) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       this.$emit("reply", item);
 | |
|     },
 | |
| 
 | |
|     // 处理取消按钮点击
 | |
|     handleCancel() {
 | |
|       this.modalShow = false;
 | |
|     },
 | |
| 
 | |
|     // 处理确认按钮点击
 | |
|     handleConfirm() {
 | |
|       // 检查是否是快速点击(防止误触)
 | |
|       if (this.modalOpenTime && Date.now() - this.modalOpenTime < 300) {
 | |
|         console.log("防止误触,请重新点击");
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // 发送删除事件,并传递回调函数
 | |
|       this.$emit("delete", this.currentItem, (success) => {
 | |
|         // 只有当操作成功时才关闭模态框
 | |
|         console.log("callback success", success);
 | |
| 
 | |
|         if (success) {
 | |
|           this.$refs.swipeActionRef[this.currentIndex].closeAll();
 | |
|           this.modalShow = false;
 | |
|         }
 | |
|       });
 | |
|     },
 | |
|   },
 | |
| };
 | |
| </script>
 | |
| 
 | |
| <style lang="scss" scoped>
 | |
| .message-board-component {
 | |
|   .right-btn-box {
 | |
|     width: 120rpx;
 | |
|     // height: inherit;
 | |
|     color: #ffffff;
 | |
|     background-color: #ff5252;
 | |
|     display: flex;
 | |
|     flex-direction: column;
 | |
|     gap: 10rpx;
 | |
|     align-items: center;
 | |
|     justify-content: center;
 | |
|     border-radius: 0 16rpx 16rpx 0;
 | |
|     border-left: 1px solid #fff; /* 防止初始状态下出现红线 */
 | |
|     margin-bottom: 32rpx;
 | |
|     .right-btn {
 | |
|       width: 32rpx;
 | |
|       height: 32rpx;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   .answer-item-box {
 | |
|     display: flex;
 | |
|   }
 | |
| 
 | |
|   .answer-item {
 | |
|     flex: 1;
 | |
|     background-color: #ffffff;
 | |
|     border-radius: 16rpx;
 | |
|     padding: 32rpx;
 | |
|     margin-bottom: 32rpx;
 | |
|     position: relative;
 | |
|     /* 删除 overflow: hidden; 可能妨碍滑动 */
 | |
|     transition: border-radius 0.3s; /* 添加过渡效果使圆角变化更平滑 */
 | |
| 
 | |
|     /* 滑动时移除右侧圆角 */
 | |
|     &.no-right-radius {
 | |
|       border-top-right-radius: 0;
 | |
|       border-bottom-right-radius: 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   .is-web-mode {
 | |
|     cursor: default;
 | |
|   }
 | |
| 
 | |
|   .web-mode-cursor {
 | |
|     cursor: pointer;
 | |
|   }
 | |
| 
 | |
|   .student-info {
 | |
|     display: flex;
 | |
|     align-items: center;
 | |
|     margin-bottom: 32rpx;
 | |
| 
 | |
|     .student-avatar {
 | |
|       width: 60rpx;
 | |
|       height: 60rpx;
 | |
|       border-radius: 50%;
 | |
|       margin-right: 16rpx;
 | |
|     }
 | |
| 
 | |
|     .student-info-content {
 | |
|       display: flex;
 | |
|       flex-direction: column;
 | |
| 
 | |
|       .student-name {
 | |
|         font-size: 28rpx;
 | |
|         color: #333333;
 | |
|         font-weight: 500;
 | |
|       }
 | |
| 
 | |
|       .answer-time {
 | |
|         font-size: 24rpx;
 | |
|         color: #666666;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   .line {
 | |
|     width: 100%;
 | |
|     height: 1rpx;
 | |
|     background-color: rgba(238, 238, 238, 0.8);
 | |
|     margin: 28rpx 0;
 | |
|   }
 | |
| 
 | |
|   .question {
 | |
|     display: flex;
 | |
|     margin-bottom: 24rpx;
 | |
| 
 | |
|     .question-tag {
 | |
|       width: 42rpx;
 | |
|       height: 42rpx;
 | |
|       margin-left: 6rpx;
 | |
|       margin-right: 26rpx;
 | |
|       flex-shrink: 0;
 | |
|     }
 | |
| 
 | |
|     .question-content {
 | |
|       font-size: 32rpx;
 | |
|       color: #333333;
 | |
|       flex: 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   .answer {
 | |
|     display: flex;
 | |
|     align-items: flex-start;
 | |
| 
 | |
|     .answer-tag {
 | |
|       width: 42rpx;
 | |
|       height: 42rpx;
 | |
|       margin-left: 6rpx;
 | |
|       margin-right: 26rpx;
 | |
|       flex-shrink: 0;
 | |
|     }
 | |
| 
 | |
|     .answer-content {
 | |
|       font-size: 24rpx;
 | |
|       line-height: 40rpx;
 | |
|       color: #666666;
 | |
|       flex: 1;
 | |
| 
 | |
|       .answer-text {
 | |
|         display: -webkit-box;
 | |
|         -webkit-line-clamp: 3;
 | |
|         -webkit-box-orient: vertical;
 | |
|         overflow: hidden;
 | |
|         text-overflow: ellipsis;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   .reply-btn-box {
 | |
|     width: 100%;
 | |
|     margin-top: 28rpx;
 | |
| 
 | |
|     .uni-btn-reset::after {
 | |
|       border: none !important;
 | |
|     }
 | |
| 
 | |
|     .reply-btn {
 | |
|       display: flex;
 | |
|       align-items: center;
 | |
|       justify-content: center;
 | |
|       width: 280rpx;
 | |
|       height: 64rpx;
 | |
|       background: #4f6aff;
 | |
|       border-radius: 16rpx;
 | |
|       font-size: 28rpx;
 | |
|       color: #ffffff;
 | |
|       margin-right: 0;
 | |
| 
 | |
|       .reply-btn-icon {
 | |
|         width: 28rpx;
 | |
|         height: 28rpx;
 | |
|         margin-right: 12rpx;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     .disabled-reply {
 | |
|       background: #cccccc;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* 自定义滑动按钮样式,保持简洁 */
 | |
| ::v-deep .uni-swipe-action__button {
 | |
|   display: flex;
 | |
|   flex-direction: row;
 | |
|   align-items: center;
 | |
|   padding: 0 32rpx;
 | |
| }
 | |
| 
 | |
| /* 自定义图标样式 - 如果使用默认图标系统 */
 | |
| ::v-deep .uni-swipe-action__button-icon {
 | |
|   width: 32rpx;
 | |
|   height: 32rpx;
 | |
| }
 | |
| 
 | |
| /* 如果需要使用自定义图标 */
 | |
| ::v-deep .uni-swipe-action__button-image {
 | |
|   width: 32rpx;
 | |
|   height: 32rpx;
 | |
|   margin-right: 8rpx;
 | |
| }
 | |
| 
 | |
| .custom-modal {
 | |
|   background-color: #fff;
 | |
|   overflow: hidden;
 | |
| 
 | |
|   .modal-title {
 | |
|     font-size: 32rpx;
 | |
|     font-weight: 500;
 | |
|     text-align: center;
 | |
|     padding: 30rpx 0;
 | |
|     color: #333;
 | |
|   }
 | |
| 
 | |
|   .modal-content {
 | |
|     padding: 30rpx;
 | |
|     font-size: 28rpx;
 | |
|     color: #666;
 | |
|     text-align: center;
 | |
|     min-height: 80rpx;
 | |
|     display: flex;
 | |
|     align-items: center;
 | |
|     justify-content: center;
 | |
|   }
 | |
| 
 | |
|   .modal-footer {
 | |
|     display: flex;
 | |
|     border-top: 1px solid #eee;
 | |
| 
 | |
|     .modal-btn {
 | |
|       flex: 1;
 | |
|       height: 90rpx;
 | |
|       display: flex;
 | |
|       align-items: center;
 | |
|       justify-content: center;
 | |
|       font-size: 30rpx;
 | |
| 
 | |
|       &.cancel {
 | |
|         color: #666;
 | |
|         border-right: 1px solid #eee;
 | |
|       }
 | |
| 
 | |
|       &.confirm {
 | |
|         color: #4f6aff;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| </style>
 |