Procházet zdrojové kódy

Merge branch 'master_feat_h5preview_20250305'

xdd před 1 dnem
rodič
revize
bbb109bb37
1 změnil soubory, kde provedl 548 přidání a 189 odebrání
  1. 548 189
      pages/index/index.vue

+ 548 - 189
pages/index/index.vue

@@ -1,199 +1,558 @@
 <template>
-	<view class="content" :style="{paddingBottom: paddingBottom}">
-		<view class="item" v-for="(item, index) in json" :class="item.classText.join(' ')" @click="click(item)">
-			<p v-if="item.type == 'h5-text'" :style="item.style">{{item.content}}</p>
-			<img :ref="'myImage' + index" @load="handleImageLoad(index, item)" v-if="item.type == 'h5-image'" :src="item.url"></p>
-		</view>
-	</view>
+  <view class="content" :style="{paddingBottom: paddingBottom}">
+    <view class="item" v-for="(item, index) in json" :class="item.classText.join(' ')" @click="click(item)">
+      <p v-if="item.type === 'h5-text'" :style="item.style">{{ item.content }}</p>
+      <img :ref="'myImage' + index" @load="handleImageLoad(index, item)" v-if="item.type === 'h5-image'"
+           :src="item.url"/>
+      <div v-if="item.type === 'h5-sep'" :class="item.classText.join(' ')"></div>
+      <div class="countdown2-box"
+           :style="{ backgroundImage: item.bgImage && item.bgImage !== '#' ? `url(${item.bgImage})` : '' }"
+           v-if="item.type==='h5-countdown'"
+      >
+        距结束<span id="days" :style="{background: item.mainColor}">{{ item.days }}</span>天
+        <span id="hours" :style="{background: item.mainColor}">{{ item.hours }}</span>时
+        <span id="minutes" :style="{background: item.mainColor}">{{ item.minutes }}</span>分
+        <span id="seconds" :style="{background: item.mainColor}">{{ item.seconds }}</span>秒
+      </div>
+      <div :class="item.classText.join(' ')" v-if="item.type==='h5-chat'">
+        <div class="chat-container">
+          <div class="chat-messages">
+            <!-- Messages -->
+            <div v-for="(message, index) in item.messages" :key="index"
+                 :class="['message-wrapper', message.sender === 'user' ? 'user-message' : 'agent-message']"
+            >
+              <div class="avatar">
+                <img :src="message.sender === 'user' ? userAvatar : getAgentAvatar(item)" alt="Avatar"/>
+              </div>
+              <div class="message-content">
+                <div v-if="message.text" class="message-text" v-html="message.text"></div>
+                <!-- Options buttons -->
+                <div v-if="message.options && message.options.length > 0" class="options-container">
+                  <button
+                      v-for="(option, optIndex) in message.options"
+                      :key="optIndex"
+                      :style="{color: item.style.btnTextColor,backgroundColor: item.style.buttonColor}"
+                      class="option-button"
+                      :class="{ 'selected': isOptionSelected(message.id, option) }"
+                      @click="selectOption(message.id, option)"
+                  >
+                    {{ option.text }}
+                  </button>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </view>
+  </view>
 </template>
 <script>
-	import Clipboard from 'clipboard'; // 引入Clipboard
-	import {getTemplateByNo,getTemplateById,callback,youkuClickCallback, iqiyiClickCallback} from '../../api/api.js'
-	import {clicks} from '../../common/common.js'
-
-
-	export default {
-		data() {
-			return {
-        images: [],
-        params: {},
-				site: {},
-				data: {},
-				json: {},
-				vid: '',
-				click_id: '',
-				aid: '',
-				id: null,
-				no: null,
-				type: 0,
-				show: false,
-				name: '',
-				tel: '',
-				accountId: '',
-				ip: '',
-				paddingBottom: 0,
-				order_plan_id: "",
-				creative_id: "",
-				impress_id: "",
-				sign: "",
-			}
-		},
-		mounted() {
-			window.vueInstance = this;  // 将 Vue 实例暴露到全局
-		},
-		onLoad(option) {
-      this.params = option;
-			this.no = this.params.no;
-			this.id = this.params.tid;
-			if((this.id == null || this.id == "" || this.id == undefined) && (this.no == null || this.no == "" || this.no == undefined)){
-				uni.showToast({
-					icon:'none',
-					title: '推广链接错误',
-				});
-				return;
-			}
-			this.accountId = this.params.accountId;
-			this.type = this.params.type;
-			this.no = this.params.no;
-			if(this.id){
-				getTemplateById(this.id).then(e => {
-					this.data = e.data;
-					this.site = e.site;
-					this.type = e.site.type;
-					this.json = JSON.parse(e.data.json)
-          this.setData();
-				})
-			}else if(this.no){
-				getTemplateByNo(this.no).then(e => {
-					this.data = e.data;
-					this.json = JSON.parse(e.data.json)
-          this.setData();
-				})
-			}
-			// callback({vid: this.vid}).then(e => {
-			// 	console.info(e)
-			// })
-		},
-		methods: {
-      setData(){
-        // 百度
-        if(this.type == 0){
-          this.vid = this.params.bd_vid;
+import Clipboard from 'clipboard' // 引入Clipboard
+import {getTemplateByNo, getTemplateById, callback, youkuClickCallback, iqiyiClickCallback} from '../../api/api.js'
+import {clicks} from '../../common/common.js'
+import agentAvatar from '../../static/customer.png'
+import userAvatar from '../../static/profile.png'
+
+export default {
+  data() {
+    return {
+      userAvatar: userAvatar, // Placeholder for user avatar
+      agentAvatar: agentAvatar,
+      images: [],
+      params: {},
+      site: {},
+      data: {},
+      json: [],
+      vid: '',
+      click_id: '',
+      aid: '',
+      id: null,
+      no: null,
+      type: 0,
+      show: false,
+      name: '',
+      tel: '',
+      accountId: '',
+      ip: '',
+      paddingBottom: 0,
+      order_plan_id: '',
+      creative_id: '',
+      impress_id: '',
+      sign: ''
+    }
+  },
+  mounted() {
+    window.vueInstance = this  // 将 Vue 实例暴露到全局
+  },
+  onLoad(option) {
+    this.params = option;
+    this.no = this.params.no;
+    this.id = this.params.tid;
+    if ((this.id == null || this.id == '' || this.id == undefined) && (this.no == null || this.no == '' || this.no == undefined)) {
+      uni.showToast({
+        icon: 'none',
+        title: '推广链接错误'
+      })
+      return
+    }
+    this.accountId = this.params.accountId;
+    this.type = this.params.type;
+    this.no = this.params.no;
+    if (this.id) {
+      getTemplateById(this.id).then(e => {
+        this.data = e.data;
+        this.site = e.site;
+        this.type = e.site.type;
+        this.json = JSON.parse(e.data.json)
+      }).finally(() => {
+        this.setData();
+      })
+    } else if (this.no) {
+      getTemplateByNo(this.no).then(e => {
+        this.data = e.data;
+        this.json = JSON.parse(e.data.json)
+      }).finally(() => {
+        this.setData();
+      })
+    }
+  },
+  beforeDestroy() {
+    this.json.filter(e => e.type === 'h5-countdown').forEach(item => {
+      if (item.intervalId) {
+        clearInterval(item.intervalId)
+      }
+    })
+  },
+  methods: {
+    setData() {
+      // 百度
+      if (this.type == 0) {
+        this.vid = this.params.bd_vid;
+      }
+      // 优酷
+      if (this.type == 1) {
+        this.vid = this.params.bd_vid;
+        this.aid = this.params.aid;
+        this.click_id = this.params.click_id;
+        this.ip = this.params.ip;
+      }
+      // 爱奇艺
+      if (this.type == 2) {
+        this.order_plan_id = this.params.order_plan_id;
+        this.creative_id = this.params.creative_id;
+        this.impress_id = this.params.impress_id;
+        this.sign = this.params.sign;
+      }
+      this.loadCountdownData();
+      this.loadChatMessage();
+    },
+    getChatMsgIdMax(){
+      let h5ChatItem = this.json.filter(e => e.type === 'h5-chat')[0]
+      let maxId = Math.max(0,...h5ChatItem.messages.map(e=>e.id))
+      return maxId + 1
+    },
+    // 聊天组件选择了选项
+    selectOption(id,option){
+      let h5ChatItem = this.json.filter(e => e.type === 'h5-chat')[0]
+      h5ChatItem.messages.push({
+        id: this.getChatMsgIdMax(),
+        sender: 'user',
+        text: option.text,
+        options: []
+      })
+      // 选项存在回复就回复用户
+      if(option.answer) {
+        setTimeout(()=>{
+          h5ChatItem.messages.push({
+            id: this.getChatMsgIdMax(),
+            sender: 'agent',
+            text: option.answer,
+            options: []
+          })
+        },1000)
+      }
+      // 然后发送下一个客服消息
+      let nextMsg = h5ChatItem.agentMsg.shift()
+      if(nextMsg) {
+        setTimeout(()=>{
+          h5ChatItem.messages.push(nextMsg)
+        },1000)
+      } else {
+        setTimeout(()=>{
+          h5ChatItem.messages.push({
+            id: this.getChatMsgIdMax(),
+            sender: 'agent',
+            text: "好的,已经为您分配专业老师。<span style='color:red'>【点击下方按钮】</span>添加老师,获取免费上课链接。!",
+            options: []
+          })
+        },2000)
+      }
+    },
+
+    // 加载计时器数据
+    loadCountdownData() {
+      let countdownItems = this.json.filter(e => e.type === 'h5-countdown')
+      countdownItems.forEach(item => {
+        // 为每个item启动一个定时器
+        this.startCountdown(item)
+      })
+    },
+    // 加载聊天组件的消息
+    loadChatMessage() {
+      let h5ChatItem = this.json.filter(e => e.type === 'h5-chat')[0]
+      h5ChatItem.messages = []
+
+      // 先输出所有的欢迎信息,欢迎信息为没有options的数据,以及后面一条数据
+      let isOutputWelcome = 0;
+
+      while (h5ChatItem.agentMsg.length > 0) {
+        let msg = h5ChatItem.agentMsg.shift()
+
+        if (!msg.options || msg.options.length === 0) {
+          isOutputWelcome = 1
+          setTimeout(() => {
+            h5ChatItem.messages.push(msg)
+          }, 1000)
+        } else {
+          if ((isOutputWelcome === 1) || (isOutputWelcome === 0)) {
+            isOutputWelcome = 2
+            setTimeout(() => {
+              h5ChatItem.messages.push(msg)
+            }, 3000)
+            break;
+          }
         }
-        // 优酷
-        if(this.type == 1){
-          this.vid = this.params.bd_vid;
-          this.aid = this.params.aid;
-          this.click_id = this.params.click_id;
-          this.ip = this.params.ip;
+      }
+    },
+    startCountdown(item) {
+      if (!item.active) {
+        return
+      }
+
+      let intervalId = setInterval(() => {
+        let timeLeft
+
+        if (item.countdownMode === '1') {
+          // 模式1:now() - endDateTime 的时间差
+          timeLeft = this.calculateTimePassed(item)
+        } else if (item.countdownMode === '2') {
+          // 模式2:timeDisplay 分钟数倒计时
+          timeLeft = this.calculateTimeLeftFromDisplay(item)
         }
-        // 爱奇艺
-        if(this.type == 2){
-          this.order_plan_id = this.params.order_plan_id;
-          this.creative_id = this.params.creative_id;
-          this.impress_id = this.params.impress_id;
-          this.sign = this.params.sign;
+        // 不再有 total <= 0 的判断, 因为模式1是不断增加的
+        item.days = timeLeft.days
+        item.hours = timeLeft.hours
+        item.minutes = timeLeft.minutes
+        item.seconds = timeLeft.seconds
+
+      }, 1000)
+      item.intervalId = intervalId // 存储 intervalId
+    },
+
+    calculateTimePassed(item) {
+      // 如果已经结束,直接返回
+      if (item.isFinished) {
+        return {
+          days: 0,
+          hours: 0,
+          minutes: 0,
+          seconds: 0
         }
-      },
-			handleImageLoad(index, item){
-				if(item.classText.indexOf("footer") != -1){
-					const imgElement  = this.$refs["myImage" + index];
-					// 获取真实高度(包含以下两种方式)
-					const naturalHeight = imgElement[0].naturalHeight; // 原始高度
-					const clientHeight = imgElement[0].clientHeight;   // 渲染高度
-					this.paddingBottom = clientHeight + "px";
-				}
-			},
-			confirm() {
-				this.show = false;
-			},
-			clickToWx(workUrl){
-				// 百度
-				if(this.type == 0){
-					let data = {
-						id: this.id,
-						url: window.location.href,
-						vid: this.vid,
-						clickType: 67,
-					};
-					baiduClickCallback(data).then(e => {
-						window.location.href = workUrl + "?customer_channel=" + this.vid;
-					})
-				}
-				// 优酷
-				if(this.type == 1){
-					let data = {
-						id: this.id,
-						url: window.location.href,
-						vid: this.vid,
-						aid: this.aid,
-						clickId: this.click_id,
-						ip: this.ip,
-					};
-					youkuClickCallback(data).then(e => {
-						window.location.href = workUrl + "?customer_channel=" + this.vid;
-					})
-				}
-				// 爱奇艺
-				if(this.type == 2){
-          if(this.id){
-            let data = {
-              url: window.location.href,
-              id: this.id,
-              planId: this.order_plan_id,
-              creativeId: this.creative_id,
-              vid: this.impress_id,
-              sign: this.sign,
-              clickType: "200"
-            };
-            iqiyiClickCallback(data).then(e => {
-              window.location.href = workUrl + "?customer_channel=" + this.impress_id;
-            })
-          }else{
-            let data = {
-              accountId: this.accountId,
-              url: window.location.href,
-              no: this.no,
-              planId: this.order_plan_id,
-              creativeId: this.creative_id,
-              vid: this.impress_id,
-              sign: this.sign,
-              clickType: "200"
-            };
-            iqiyiClickCallback(data).then(e => {
-              window.location.href = workUrl + "?customer_channel=" + this.impress_id;
-            })
-          }
-				}
-
-			},
-			click(item){
-				if(!item.addWxFun){
-					return;
-				}
-				this.clickToWx(item.workUrl);
-			},
-		}
-	}
+      }
+
+      let endTime = new Date(item.endDateTime)
+      let now = new Date()
+      let diff = endTime.getTime() - now.getTime()   // 毫秒差, end - now
+
+      if (diff <= 0) {
+        // 如果 endDateTime 比当前时间晚, 则显示 0
+        clearInterval(item.intervalId)
+        item.intervalId = null
+        item.isFinished = true
+        return {
+          days: 0,
+          hours: 0,
+          minutes: 0,
+          seconds: 0
+        }
+      }
+
+      return this.millisecondsToTimeObject(diff)
+    },
+
+    calculateTimeLeftFromDisplay(item) {
+      if (!item.startTime) {
+        item.startTime = Date.now() // 记录开始时间
+      }
+      let totalSeconds = item.timeDisplay * 60 // timeDisplay 是分钟数
+      let passedSeconds = Math.floor((Date.now() - item.startTime) / 1000)
+      let timeLeft = totalSeconds - passedSeconds
+
+      if (timeLeft <= 0) {
+        clearInterval(item.intervalId)
+        item.intervalId = null//清除id
+        return {
+          days: 0,
+          hours: 0,
+          minutes: 0,
+          seconds: 0
+        }
+      }
+
+      return this.secondsToTimeObject(timeLeft)
+    },
+
+    millisecondsToTimeObject(milliseconds) {
+      let totalSeconds = Math.floor(milliseconds / 1000)
+      return this.secondsToTimeObject(totalSeconds)
+    },
+    secondsToTimeObject(totalSeconds) {
+      let days = Math.floor(totalSeconds / (3600 * 24))
+      let hours = Math.floor((totalSeconds % (3600 * 24)) / 3600)
+      let minutes = Math.floor((totalSeconds % 3600) / 60)
+      let seconds = Math.floor(totalSeconds % 60)
+      return {
+        days: days,
+        hours: hours,
+        minutes: minutes,
+        seconds: seconds
+      }
+    },
+    getAgentAvatar(item) {
+      if (item.style.avatar) {
+        return item.style.avatar
+      }
+      return this.agentAvatar
+    },
+    isOptionSelected(messageId, option) {
+      let messages = this.json.find(t => t.type === 'h5-chat').messages
+      const message = messages.find(m => m.id === messageId)
+      return message && message.userSelection && message.userSelection.id === option.id
+    },
+    handleImageLoad(index, item) {
+      if (item.classText.indexOf("footer") != -1) {
+        const imgElement = this.$refs["myImage" + index];
+        // 获取真实高度(包含以下两种方式)
+        const naturalHeight = imgElement[0].naturalHeight; // 原始高度
+        const clientHeight = imgElement[0].clientHeight;   // 渲染高度
+        this.paddingBottom = clientHeight + "px";
+      }
+    },
+    confirm() {
+      this.show = false
+    },
+    clickToWx(workUrl) {
+      // 百度
+      if (this.type == 0) {
+        let data = {
+          id: this.id,
+          url: window.location.href,
+          vid: this.vid,
+          clickType: 67,
+        };
+        baiduClickCallback(data).then(e => {
+          window.location.href = workUrl + "?customer_channel=" + this.vid;
+        })
+      }
+      // 优酷
+      if (this.type == 1) {
+        let data = {
+          id: this.id,
+          url: window.location.href,
+          vid: this.vid,
+          aid: this.aid,
+          clickId: this.click_id,
+          ip: this.ip,
+        };
+        youkuClickCallback(data).then(e => {
+          window.location.href = workUrl + "?customer_channel=" + this.vid;
+        })
+      }
+      // 爱奇艺
+      if (this.type == 2) {
+        if (this.id) {
+          let data = {
+            url: window.location.href,
+            id: this.id,
+            planId: this.order_plan_id,
+            creativeId: this.creative_id,
+            vid: this.impress_id,
+            sign: this.sign,
+            clickType: "200"
+          };
+          iqiyiClickCallback(data).then(e => {
+            window.location.href = workUrl + "?customer_channel=" + this.impress_id;
+          })
+        } else {
+          let data = {
+            accountId: this.accountId,
+            url: window.location.href,
+            no: this.no,
+            planId: this.order_plan_id,
+            creativeId: this.creative_id,
+            vid: this.impress_id,
+            sign: this.sign,
+            clickType: "200"
+          };
+          iqiyiClickCallback(data).then(e => {
+            window.location.href = workUrl + "?customer_channel=" + this.impress_id;
+          })
+        }
+      }
+
+    },
+    click(item) {
+      if (!item.addWxFun) {
+        return
+      }
+      this.clickToWx(item.workUrl)
+    }
+  }
+}
 </script>
 
 <style lang="scss" scoped>
-	.item{
-		display: flex;
-		flex-direction: column;
-		flex: 1;
-		margin: 0;
-		width: 100%;
-		img{
-			width: 100%;
-		}
-		p{
-			white-space: pre-line;
-		}
-	}
-	.footer{
-		position: fixed;
-		bottom: 0;
-		left: 0;
-	}
+.chat-container {
+  width: 100%;
+  // max-width: 500px;
+  display: flex;
+  flex-direction: column;
+  background-color: #f8f8f8;
+  font-family: Arial, sans-serif;
+  max-height: 500px;
+}
+
+.chat-messages {
+  flex: 1;
+  overflow-y: auto;
+  padding: 16px;
+  display: flex;
+  flex-direction: column;
+}
+
+.message-wrapper {
+  display: flex;
+  margin-bottom: 16px;
+  max-width: 80%;
+}
+
+.agent-message {
+  align-self: flex-start;
+}
+
+.user-message {
+  align-self: flex-end;
+  flex-direction: row-reverse;
+}
+
+.avatar {
+  font-size: 14px;
+  display: flex;
+  padding-right: 5px;
+  padding-left: 0;
+  margin-bottom: 15px;
+}
+
+.avatar img {
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+}
+
+.message-content {
+  display: flex;
+  flex-direction: column;
+}
+
+.message-text {
+  padding: 12px;
+  border-radius: 18px;
+  font-size: 14px;
+  line-height: 1.4;
+  word-break: break-word;
+}
+
+.agent-message .message-text {
+  background-color: white;
+  color: #333;
+  border-top-left-radius: 4px;
+}
+
+.user-message .message-text {
+  background-color: #4a90e2;
+  color: white;
+  border-top-right-radius: 4px;
+}
+
+.options-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-top: 8px;
+}
+
+.option-button {
+  background-color: #4a90e2;
+  color: white;
+  border: none;
+  border-radius: 18px;
+  padding: 8px 16px;
+  font-size: 14px;
+  cursor: pointer;
+  transition: background-color 0.2s;
+}
+
+.option-button:hover {
+  background-color: #045dba;
+}
+
+.option-button.selected {
+  background-color: #2a70c2;
+}
+
+.item {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  margin: 0;
+  width: 100%;
+
+  img {
+    width: 100%;
+  }
+
+  p {
+    white-space: pre-line;
+  }
+}
+
+.footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+}
+
+.countdown2-box {
+  background: url('#') no-repeat center;
+  background-size: 100%;
+  width: 100%;
+  height: 54px;
+  line-height: 54px;
+  color: #515A6E;
+  font-size: 14px;
+  text-align: center;
+}
+
+.countdown2-box span {
+  display: inline-block;
+  color: #fff;
+  width: 22px;
+  line-height: 20px;
+  text-align: center;
+  height: 20px;
+  border-radius: 3px;
+  margin: 7px;
+  background: #FF5A29;
+}
 </style>