From 0cf1e9bc1e8fc6ac9b6ff0d81acf93307f91bad0 Mon Sep 17 00:00:00 2001
From: yfx <1249361928@.com>
Date: Thu, 19 Mar 2026 11:19:05 +0800
Subject: [PATCH] 137弹框协议

---
 components/textBox/index.vue |  329 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 329 insertions(+), 0 deletions(-)

diff --git a/components/textBox/index.vue b/components/textBox/index.vue
new file mode 100644
index 0000000..b988f61
--- /dev/null
+++ b/components/textBox/index.vue
@@ -0,0 +1,329 @@
+<script>
+export default {
+  name: "ProtocolPopup",
+
+  props:{
+    code:{ type:String, default:'' },
+    title:{ type:String, default:'' },
+    getPlatformProtocolNewList:{
+      type:Array,
+      default:() => []
+    }
+  },
+
+  data(){
+    return {
+      list: [],
+      text:'',
+      titleText:'',
+      flag:false,
+      ing:0,
+      scrollTop: 0,
+
+      reachBottom:false,
+      readingTimer:null,
+      readTime:500,
+
+      tiems:5,
+      timer:null, // 倒计时
+    }
+  },
+
+  created() {
+    const _this = this;
+
+    // 多协议
+    if(this.getPlatformProtocolNewList.length){
+
+      const cache = uni.getStorageSync('protocol_read_cache') || {};
+
+      this.list = this.getPlatformProtocolNewList.map((item, index) => ({
+        ...item,
+        // isRead: cache[index]?.isRead || false
+        isRead:  false
+      }));
+
+      this.ing = cache.ing || 0;
+
+      this.updateCurrent();
+      this.checkAllRead();
+      return;
+    }
+
+    // 单协议
+    uni.request({
+      url: this.$webHost + `/api/v2/protocolInfo/getProtocol?appId=xxx&code=${this.code}`,
+      success: (res) => {
+        _this.text = res.data.data.content;
+        _this.titleText = res.data.data.name;
+        _this.flag = false;
+      }
+    })
+  },
+
+  methods:{
+    open(){
+      this.$refs.boxCnt.open();
+      this.startCountDown();
+    },
+
+    closePopup() {
+      this.$refs.boxCnt.close()
+    },
+
+    // ✅ 倒计时
+    startCountDown(){
+      this.tiems = 5;
+
+      if(this.timer){
+        clearInterval(this.timer);
+      }
+
+      this.timer = setInterval(() => {
+        if(this.tiems > 0){
+          this.tiems--;
+        }else{
+          clearInterval(this.timer);
+          this.timer = null;
+        }
+      },1000);
+    },
+
+    // 更新当前协议
+    updateCurrent(){
+      this.text = this.list[this.ing].content;
+      this.titleText = this.list[this.ing].name;
+      this.scrollTop = 0;
+
+      this.reachBottom = false;
+
+      if(this.readingTimer){
+        clearTimeout(this.readingTimer);
+      }
+
+      // 每个协议重新倒计时
+      this.startCountDown();
+    },
+
+    // 滚动到底
+    scrolltolower(){
+      if(!this.list.length){
+        this.flag = true;
+        return;
+      }
+
+      if(this.reachBottom) return;
+
+      this.reachBottom = true;
+
+      if(this.readingTimer){
+        clearTimeout(this.readingTimer);
+      }
+
+      this.readingTimer = setTimeout(() => {
+
+        this.list[this.ing].isRead = true;
+
+        this.saveCache();
+
+        const allRead = this.list.every(item => item.isRead);
+
+        if (allRead) {
+          this.flag = true;
+        } else {
+
+        }
+
+      }, this.readTime);
+    },
+
+    // scroll 兜底
+    onScroll(e){
+      const { scrollTop, scrollHeight, deltaY } = e.detail;
+
+      if(scrollTop + deltaY >= scrollHeight - 100){
+        this.scrolltolower();
+      }
+    },
+
+    // 缓存
+    saveCache(){
+      const cache = {
+        ing: this.ing,
+        ...this.list.reduce((acc, item, index) => {
+          acc[index] = { isRead: item.isRead };
+          return acc;
+        }, {})
+      };
+
+      uni.setStorageSync('protocol_read_cache', cache);
+    },
+
+    // 判断是否全部读完
+    checkAllRead(){
+      this.flag = this.list.every(item => item.isRead);
+    },
+
+    // 点击确认
+    loadPopup(){
+      console.log(this.flag);
+      if(!this.flag){
+        if(this.getPlatformProtocolNewList.length && this.reachBottom){
+          if(this.readingTimer){
+            clearTimeout(this.readingTimer);
+          }
+          this.scrollTop = 0;
+
+          this.ing++;
+          if(this.ing === this.getPlatformProtocolNewList.length){
+            this.flag = true;
+
+            uni.removeStorageSync('protocol_read_cache');
+
+            this.$refs.boxCnt.close();
+            this.$emit('confirm');
+            return;
+          }
+          this.updateCurrent();
+        } else {
+          uni.showToast({
+            title: '请先阅读完本协议内容',
+            icon: 'none',
+          })
+        }
+
+        return;
+      }
+      if(!this.flag || this.tiems > 0) return;
+
+      uni.removeStorageSync('protocol_read_cache');
+
+      this.$refs.boxCnt.close();
+      this.$emit('confirm');
+    }
+  },
+
+  beforeDestroy(){
+    if(this.timer){
+      clearInterval(this.timer);
+    }
+    if(this.readingTimer){
+      clearTimeout(this.readingTimer);
+    }
+  }
+}
+</script>
+
+<template>
+  <uv-popup
+      ref="boxCnt"
+      z-index="999"
+      :round="16"
+      :safe-area-inset-bottom="true"
+      :closeOnClickOverlay="false"
+  >
+    <view class="box">
+
+      <!-- 标题 -->
+      <view class="title">
+        {{ titleText }}
+      </view>
+
+      <!-- 进度 -->
+      <view v-if="list.length" class="progress">
+        {{ ing + 1 }} / {{ list.length }}
+      </view>
+      <view v-if="!reachBottom" class="scroll-tip">
+        ↓ 请滑动阅读全部内容
+      </view>
+      <!-- 内容 -->
+      <scroll-view
+          :key="ing"
+          scroll-y
+          :scroll-top="scrollTop"
+          @scroll="onScroll"
+          @scrolltolower="scrolltolower"
+          lower-threshold="50"
+          style="height: 70vh;"
+          :show-scrollbar="true"
+      >
+        <view class="text-container">
+          <rich-text :nodes="text"></rich-text>
+        </view>
+      </scroll-view>
+
+      <!-- 按钮 -->
+      <view style="margin-top: 20rpx;">
+        <uv-button
+
+            :disabled="!flag || tiems > 0"
+            @click="loadPopup"
+            v-if="code"
+            :custom-style="{ color: '#FF1472',border:'none' }"
+        >
+          确定
+          <text v-show="tiems">({{ tiems }}S)</text>
+        </uv-button>
+        <uv-button
+
+            :disabled="tiems > 0 || !reachBottom "
+            @click="loadPopup"
+            v-if="getPlatformProtocolNewList.length"
+            :custom-style="{ color: '#FF1472',border:'none' }"
+        >
+          {{ flag ||  getPlatformProtocolNewList.length === ing + 1 ? '确定' : '下一步' }}
+          <text v-show="tiems">({{ tiems }}S)</text>
+        </uv-button>
+      </view>
+
+    </view>
+  </uv-popup>
+</template>
+
+<style scoped lang="scss">
+.box{
+  padding: 40rpx;
+  width:80vw;
+  margin: auto;
+  border-radius: 40rpx;
+  background: white;
+}
+
+.title{
+  text-align: center;
+  font-weight: 800;
+  margin-bottom: 10rpx;
+  font-size: 34rpx;
+}
+
+.progress{
+  text-align: center;
+  font-size: 24rpx;
+  color: #999;
+  margin-bottom: 10rpx;
+}
+
+.text-container {
+  font-size: 28rpx;
+  line-height: 1.8;
+  color: #666;
+}
+.uni-scroll-view::-webkit-scrollbar {
+  width: 6px;
+}
+
+.uni-scroll-view::-webkit-scrollbar-thumb {
+  background: rgba(0,0,0,0.3);
+  border-radius: 10px;
+}
+
+.uni-scroll-view::-webkit-scrollbar-track {
+  background: transparent;
+}
+.scroll-tip{
+  text-align:center;
+  color:#999;
+  font-size:24rpx;
+  margin:10rpx 0;
+}
+</style>
\ No newline at end of file

--
Gitblit v1.9.3