From 91f78c7a3c325b7627f269524cdf92f006948cdf Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期一, 20 十月 2025 17:37:35 +0800
Subject: [PATCH] 景宁电话接入

---
 src/views/followvisit/discharge/ClickCall copy.vue | 1258 +++++++++++++++++++++++++++++
 dist.zip                                           |    0 
 vue.config.js                                      |    4 
 src/api/AiCentre/phoneCall.js                      |    4 
 src/views/followvisit/record/detailpage/index.vue  |   62 
 src/views/followvisit/discharge/ClickCall.vue      |  622 ++++++++++---
 src/components/CallCenterLs/index.vue              |  614 ++++++++++++++
 7 files changed, 2,371 insertions(+), 193 deletions(-)

diff --git a/dist.zip b/dist.zip
index b6e6235..829d089 100644
--- a/dist.zip
+++ b/dist.zip
Binary files differ
diff --git a/src/api/AiCentre/phoneCall.js b/src/api/AiCentre/phoneCall.js
index 84769dc..f2f5124 100644
--- a/src/api/AiCentre/phoneCall.js
+++ b/src/api/AiCentre/phoneCall.js
@@ -1,11 +1,13 @@
 import request from "@/utils/request";
 
-
 // 鍒犻櫎澶栭儴鎮h�呰〃
 export function CallgetList() {
   return request({
     url: "/smartor/ServiceTelInfo/getList",
     method: "get",
+    params: {
+      orgid: localStorage.getItem("orgid"),
+    },
   });
 }
 // 鏌ヨ澶栭儴鎮h�呰〃
diff --git a/src/components/CallCenterLs/index.vue b/src/components/CallCenterLs/index.vue
new file mode 100644
index 0000000..2114df6
--- /dev/null
+++ b/src/components/CallCenterLs/index.vue
@@ -0,0 +1,614 @@
+<template>
+  <div class="call-center-container">
+    <!-- 涓绘帶鍒跺尯 -->
+    <div class="control-section">
+      <div class="phone-control-card">
+        <h3 class="section-title">鐢佃瘽鎺у埗</h3>
+        <div class="input-group">
+          <div class="form-field">
+            <label class="form-label">瀹㈡埛鐢佃瘽鍙风爜</label>
+            <input
+              v-model="customerPhone"
+              type="text"
+              placeholder="璇疯緭鍏ョ數璇濆彿鐮�"
+              :disabled="isCalling"
+              class="phone-input"
+            />
+          </div>
+
+          <div class="button-group">
+            <button
+              @click="handleCall"
+              :class="['call-btn', callButtonClass]"
+              :disabled="!canMakeCall"
+            >
+              <span class="btn-icon">馃摓</span>
+              {{ callButtonText }}
+            </button>
+
+            <button
+              @click="handleHangup"
+              class="hangup-btn"
+              :disabled="!canHangup"
+            >
+              <span class="btn-icon">馃摰</span>
+              鎸傛柇
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 鐘舵�佹樉绀哄尯 -->
+      <div class="status-card">
+        <h3 class="section-title">鐘舵�佺洃鎺�</h3>
+        <div class="status-grid">
+          <div class="status-item">
+            <span class="status-label">搴у腑鐘舵��:</span>
+            <span :class="['status-indicator', seatStatusClass]">
+              <span class="status-dot"></span>
+              {{ seatStatusText }}
+            </span>
+          </div>
+
+          <div class="status-item">
+            <span class="status-label">閫氳瘽鐘舵��:</span>
+            <span :class="['status-indicator', callStatusClass]">
+              <span class="status-dot"></span>
+              {{ callStatusText }}
+            </span>
+          </div>
+
+          <div class="status-item" v-if="callDuration">
+            <span class="status-label">閫氳瘽鏃堕暱:</span>
+            <span class="duration-display">
+              鈴憋笍 {{ callDuration }}
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 璋冭瘯闈㈡澘 -->
+    <div class="debug-section">
+      <el-collapse accordion>
+        <el-collapse-item name="debug">
+          <template slot="title">
+            <div class="debug-header">
+              <span class="debug-title">鍛煎彨璋冭瘯鏃ュ織</span>
+              <span class="debug-subtitle">鐐瑰嚮鏌ョ湅璇︾粏閫氳瘽淇℃伅</span>
+            </div>
+          </template>
+          <div class="debug-content">
+            <WebsocketDemo
+              ref="callComponent"
+              :customer-phone="customerPhone"
+              :auto-login="true"
+              @status-change="onSeatStatusChange"
+              @call-status="onCallStatusChange"
+              @error="onCallError"
+              class="call-component"
+            />
+          </div>
+        </el-collapse-item>
+      </el-collapse>
+    </div>
+  </div>
+</template>
+
+<script>
+import WebsocketDemo from "../../views/followvisit/discharge/ClickCall.vue";
+
+export default {
+  name: "CallCenterModal",
+  components: {
+    WebsocketDemo,
+  },
+
+  props: {
+    initialPhone: {
+      type: String,
+      default: "",
+    },
+  },
+
+  data() {
+    return {
+      customerPhone: "",
+      isSeatLoggedIn: false,
+      callStatus: "idle",
+      callStartTime: null,
+      callDuration: "00:00",
+      durationTimer: null,
+      lastError: null,
+    };
+  },
+
+  computed: {
+    isCalling() {
+      return this.callStatus === "calling";
+    },
+
+    isInCall() {
+      return this.callStatus === "connected";
+    },
+
+    canMakeCall() {
+      return (
+        this.isSeatLoggedIn &&
+        this.customerPhone &&
+        this.callStatus === "idle" &&
+        !this.lastError
+      );
+    },
+
+    canHangup() {
+      return this.isCalling || this.isInCall;
+    },
+
+    callButtonClass() {
+      if (!this.canMakeCall) return "disabled";
+      return this.isCalling ? "calling" : "idle";
+    },
+
+    callButtonText() {
+      if (this.isCalling) return "鍛煎彨涓�...";
+      if (this.isInCall) return "閫氳瘽涓�";
+      return "寮�濮嬪懠鍙�";
+    },
+
+    seatStatusClass() {
+      return this.isSeatLoggedIn ? "success" : "error";
+    },
+
+    seatStatusText() {
+      return this.isSeatLoggedIn ? "宸茬鍏�" : "鏈鍏�";
+    },
+
+    callStatusClass() {
+      switch (this.callStatus) {
+        case "connected":
+          return "success";
+        case "calling":
+          return "warning";
+        default:
+          return "idle";
+      }
+    },
+
+    callStatusText() {
+      switch (this.callStatus) {
+        case "connected":
+          return "閫氳瘽涓�";
+        case "calling":
+          return "鍛煎彨涓�";
+        default:
+          return "绌洪棽";
+      }
+    },
+  },
+
+  watch: {
+    initialPhone: {
+      immediate: true,
+      handler(newVal) {
+        if (newVal) {
+          this.customerPhone = newVal;
+        }
+      },
+    },
+  },
+
+  methods: {
+    handleCall() {
+      if (!this.canMakeCall) return;
+      this.$refs.callComponent.callout(this.customerPhone);
+      this.startCallTimer();
+    },
+
+    handleHangup() {
+      this.$refs.callComponent.hangup();
+      this.stopCallTimer();
+      this.callStatus = "idle";
+    },
+
+    onSeatStatusChange(status) {
+      this.isSeatLoggedIn = status.isLoggedIn;
+    },
+
+    onCallStatusChange(status) {
+      this.callStatus = status.status;
+      if (status.status === "connected") {
+        this.startCallTimer();
+      } else if (status.status === "idle") {
+        this.stopCallTimer();
+      }
+    },
+
+    onCallError(error) {
+      this.lastError = error;
+      this.$emit("error", error);
+    },
+
+    startCallTimer() {
+      this.callStartTime = new Date();
+      this.durationTimer = setInterval(() => {
+        if (this.callStartTime) {
+          const duration = Math.floor((new Date() - this.callStartTime) / 1000);
+          const minutes = Math.floor(duration / 60)
+            .toString()
+            .padStart(2, "0");
+          const seconds = (duration % 60).toString().padStart(2, "0");
+          this.callDuration = `${minutes}:${seconds}`;
+        }
+      }, 1000);
+    },
+
+    stopCallTimer() {
+      if (this.durationTimer) {
+        clearInterval(this.durationTimer);
+        this.durationTimer = null;
+      }
+      this.callDuration = "00:00";
+      this.callStartTime = null;
+    },
+
+    handleSeatBusy() {
+      this.$refs.callComponent.afk();
+    },
+
+    handleSeatReady() {
+      this.$refs.callComponent.online();
+    },
+
+    handleHold() {
+      this.$refs.callComponent.hold();
+    },
+
+    handleResume() {
+      this.$refs.callComponent.holdresume();
+    },
+
+    // 鎻愪緵缁欑埗缁勪欢璋冪敤鐨勬柟娉�
+    setPhoneNumber(phone) {
+      this.customerPhone = phone;
+    },
+
+    autoCall(phone) {
+      this.setPhoneNumber(phone);
+      this.$nextTick(() => {
+        this.handleCall();
+      });
+    },
+  },
+
+  beforeUnmount() {
+    this.stopCallTimer();
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.call-center-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  padding: 0;
+  background: #f8fafc;
+}
+
+// 鎺у埗鍖哄煙鏍峰紡
+.control-section {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 16px;
+  padding: 20px;
+    padding-bottom: 0;
+
+  @media (max-width: 1024px) {
+    grid-template-columns: 1fr;
+  }
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1e293b;
+  margin-bottom: 16px;
+  display: flex;
+  align-items: center;
+
+  &::before {
+    content: "";
+    width: 3px;
+    height: 16px;
+    background: #3b82f6;
+    margin-right: 8px;
+    border-radius: 2px;
+  }
+}
+
+// 鍗$墖閫氱敤鏍峰紡
+.phone-control-card,
+.status-card {
+  background: white;
+  padding: 20px;
+  border-radius: 12px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+  border: 1px solid #e2e8f0;
+}
+
+// 琛ㄥ崟鎺т欢鏍峰紡
+.form-field {
+  margin-bottom: 20px;
+}
+
+.form-label {
+  display: block;
+  font-weight: 500;
+  color: #475569;
+  margin-bottom: 8px;
+  font-size: 14px;
+}
+
+.phone-input {
+  width: 100%;
+  padding: 12px;
+  border: 2px solid #e2e8f0;
+  border-radius: 8px;
+  font-size: 14px;
+  transition: all 0.3s ease;
+
+  &:focus {
+    outline: none;
+    border-color: #3b82f6;
+    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+  }
+
+  &:disabled {
+    background-color: #f8fafc;
+    color: #94a3b8;
+    cursor: not-allowed;
+  }
+}
+
+.button-group {
+  display: flex;
+  gap: 12px;
+  flex-wrap: wrap;
+}
+
+// 鎸夐挳鍩虹鏍峰紡
+.call-btn,
+.hangup-btn {
+  padding: 12px 28px; /* 澧炲姞鍐呰竟璺濓紝浣挎寜閽洿澶ф柟 */
+  border: none;
+  border-radius: 12px; /* 浣跨敤鏇村ぇ鐨勫渾瑙掞紝鍒涢�犫�滆嵂涓糕�濆舰 */
+  cursor: pointer;
+  font-size: 14px;
+  font-weight: 600; /* 瀛椾綋鍔犵矖 */
+  transition: all 0.3s ease; /* 骞虫粦杩囨浮鎵�鏈夊睘鎬� */
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px; /* 鍥炬爣鍜屾枃瀛楃殑闂磋窛 */
+  min-width: 120px; /* 璁剧疆鏈�灏忓搴� */
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); /* 澶氬眰闃村奖澧炲己绔嬩綋鎰� */
+}
+
+.call-btn {
+  background: linear-gradient(135deg, #10b981, #059669); /* 缁胯壊娓愬彉 */
+  color: white;
+}
+
+.call-btn:hover:not(.disabled) {
+  transform: translateY(-2px); /* 鎮仠鏃惰交寰笂娴� */
+  box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4); /* 鎮仠鏃堕槾褰辨洿鏄庢樉 */
+}
+
+.call-btn.calling {
+  background: linear-gradient(135deg, #f59e0b, #d97706); /* 鍛煎彨涓姸鎬佹敼涓烘鑹叉笎鍙� */
+  animation: pulse 1.5s infinite; /* 鍛煎彨涓坊鍔犲懠鍚歌剦鍐插姩鐢� */
+}
+
+.hangup-btn {
+  background: linear-gradient(135deg, #ef4444, #dc2626); /* 绾㈣壊娓愬彉 */
+  color: white;
+}
+
+.hangup-btn:hover:not(:disabled) {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 16px rgba(239, 68, 68, 0.4);
+}
+
+/* 绂佺敤鐘舵�� */
+.call-btn.disabled,
+.hangup-btn:disabled {
+  background: #cbd5e1 !important;
+  cursor: not-allowed;
+  transform: none !important;
+  box-shadow: none !important;
+}
+
+/* 鑴夊啿鍔ㄧ敾瀹氫箟 */
+@keyframes pulse {
+  0% { box-shadow: 0 4px 12px rgba(245, 158, 11, 0.5); }
+  50% { box-shadow: 0 4px 20px rgba(245, 158, 11, 0.8); }
+  100% { box-shadow: 0 4px 12px rgba(245, 158, 11, 0.5); }
+}
+
+// 鐘舵�佹樉绀烘牱寮�
+.status-grid {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.status-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px;
+  background: #f8fafc;
+  border-radius: 8px;
+}
+
+.status-label {
+  font-size: 14px;
+  color: #475569;
+  font-weight: 500;
+}
+
+.status-indicator {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 13px;
+  font-weight: 500;
+  padding: 4px 12px;
+  border-radius: 20px;
+}
+
+.status-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  animation: pulse 2s infinite;
+}
+
+.status-indicator.success {
+  background: #f0fdf4;
+  color: #059669;
+
+  .status-dot {
+    background: #059669;
+  }
+}
+
+.status-indicator.error {
+  background: #fef2f2;
+  color: #dc2626;
+
+  .status-dot {
+    background: #dc2626;
+  }
+}
+
+.status-indicator.warning {
+  background: #fffbeb;
+  color: #d97706;
+
+  .status-dot {
+    background: #f59e0b;
+  }
+}
+
+.status-indicator.idle {
+  background: #f8fafc;
+  color: #64748b;
+
+  .status-dot {
+    background: #94a3b8;
+  }
+}
+
+.duration-display {
+  font-family: 'Courier New', monospace;
+  font-weight: 600;
+  color: #059669;
+  font-size: 14px;
+}
+
+// 璋冭瘯闈㈡澘鏍峰紡
+.debug-section {
+  background: white;
+  margin: 0 20px 20px;
+  padding: 20px;
+  border-radius: 12px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.debug-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  padding-right: 20px;
+}
+
+.debug-title {
+ font-size: 16px;
+  font-weight: 600;
+  color: #1e293b;
+  margin-bottom: 16px;
+  display: flex;
+  align-items: center;
+
+  &::before {
+    content: "";
+    width: 3px;
+    height: 16px;
+    background: #3b82f6;
+    margin-right: 8px;
+    border-radius: 2px;
+  }
+}
+
+.debug-subtitle {
+  font-size: 12px;
+  color: #64748b;
+}
+
+.debug-content {
+  padding: 0;
+}
+
+.call-component {
+  min-height: 200px;
+  border-top: 1px solid #e2e8f0;
+}
+
+// 鍔ㄧ敾瀹氫箟
+@keyframes pulse {
+  0% { opacity: 1; }
+  50% { opacity: 0.5; }
+  100% { opacity: 1; }
+}
+
+// 鍝嶅簲寮忚璁�
+@media (max-width: 768px) {
+  .control-section {
+    padding: 16px;
+    grid-template-columns: 1fr;
+  }
+
+  .phone-control-card,
+  .status-card {
+    padding: 16px;
+  }
+
+  .button-group {
+    flex-direction: column;
+  }
+
+  .status-item {
+    flex-direction: column;
+    gap: 8px;
+    align-items: flex-start;
+  }
+
+  .debug-section {
+    margin: 0 16px 16px;
+  }
+}
+
+@media (max-width: 480px) {
+  .call-center-container {
+    gap: 12px;
+  }
+
+  .control-section {
+    padding: 12px;
+  }
+}
+</style>
diff --git a/src/views/followvisit/discharge/ClickCall copy.vue b/src/views/followvisit/discharge/ClickCall copy.vue
new file mode 100644
index 0000000..b922b0c
--- /dev/null
+++ b/src/views/followvisit/discharge/ClickCall copy.vue
@@ -0,0 +1,1258 @@
+<template>
+  <div class="websocket-demo">
+    <div>
+      <h3>Websocket鎺ュ彛娴嬭瘯DEMO</h3>
+      <div class="config-area">
+        <div class="input-group">
+          <label>CTI_WS_URL</label>
+          <input
+            type="text"
+            v-model="config.cti_ws_url"
+            placeholder="ws://40.78.0.169:6688"
+          />
+
+          <label>鍧愬腑宸ュ彿</label>
+          <input type="text" v-model="config.seatname" placeholder="8000" />
+
+          <label>鍧愬腑鍒嗘満</label>
+          <input type="text" v-model="config.seatnum" placeholder="8000" />
+
+          <label>瀵嗙爜</label>
+          <input type="text" v-model="config.password" placeholder="123456" />
+        </div>
+
+        <div class="input-group">
+          <label>澶栫嚎鍙风爜</label>
+          <input type="text" v-model="config.phone" placeholder="10086" />
+
+          <label>UUID</label>
+          <input type="text" v-model="config.uuid" />
+
+          <label>鍏朵粬鍧愬腑</label>
+          <input type="text" v-model="config.other" placeholder="8001" />
+
+          <label>鎶�鑳界粍</label>
+          <input type="text" v-model="config.group" placeholder="a3" />
+
+          <label>澶栧懠鍙傛暟id</label>
+          <input type="text" v-model="config.paramid" placeholder="3" />
+        </div>
+      </div>
+
+      <!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
+      <div class="button-area">
+        <!-- 绗竴琛屾寜閽� -->
+        <div class="button-row">
+          <button @click="seatlogin">绛惧叆</button>
+          <button @click="seatlogout">绛惧嚭</button>
+          <button @click="afk">绀哄繖</button>
+          <button @click="online">绀洪棽</button>
+          <button @click="pickup">浠g瓟</button>
+        </div>
+
+        <!-- 绗簩琛屾寜閽� -->
+        <div class="button-row">
+          <button @click="hangup">鎸傛満</button>
+          <button @click="callout">澶栧懠</button>
+          <button @click="transfer">閫氳瘽杞Щ</button>
+          <button @click="transferresume">閫氳瘽杞Щ鏀跺洖</button>
+          <button @click="hold">閫氳瘽淇濇寔</button>
+          <button @click="holdresume">閫氳瘽淇濇寔鏀跺洖</button>
+          <button @click="remove">閫氳瘽寮烘媶</button>
+          <button @click="insert">閫氳瘽寮烘彃</button>
+          <button @click="monitor">鐩戝惉</button>
+          <button @click="monitor_to_talk">鐩戝惉杞�氳瘽</button>
+          <button @click="monitor_end">鐩戝惉缁撴潫</button>
+          <button @click="choosecall">閫夋嫨</button>
+          <button @click="replacecall">浠f帴</button>
+          <button @click="three">涓夋柟閫氳瘽</button>
+        </div>
+
+        <!-- 绗笁琛屾寜閽� -->
+        <div class="button-row">
+          <button @click="handoff_ready">鍜ㄨ寮�濮�</button>
+          <button @click="handoff_call">鍜ㄨ鍛煎彨</button>
+          <button @click="handoff_resume">鍜ㄨ鏀跺洖</button>
+          <button @click="handoff_transfer">鍜ㄨ杞Щ</button>
+          <button @click="handoff_three">鍜ㄨ涓夋柟</button>
+          <button @click="record_start">寮�濮嬮�氳瘽褰曢煶</button>
+          <button @click="record_stop">鍋滄閫氳瘽褰曢煶</button>
+        </div>
+
+        <!-- 绗洓琛屾寜閽� -->
+        <div class="button-row">
+          <button @click="openseatlist">鎵撳紑鍧愬腑鐘舵��</button>
+          <button @click="closeseatlist">鍏抽棴鍧愬腑鐘舵��</button>
+          <button @click="openqueues">鎵撳紑闃熷垪淇℃伅</button>
+          <button @click="closequeues">鍏抽棴闃熷垪淇℃伅</button>
+          <button @click="opencalllist">鎵撳紑閫氳瘽淇℃伅</button>
+          <button @click="closecalllist">鍏抽棴閫氳瘽淇℃伅</button>
+          <button @click="openroutelist">鎵撳紑璺敱淇℃伅</button>
+          <button @click="closeroutelist">鍏抽棴璺敱淇℃伅</button>
+        </div>
+
+        <!-- 绗簲琛屾寜閽� -->
+        <div class="button-row">
+          <button @click="seatlist">鑾峰彇鍧愬腑淇℃伅</button>
+          <button @click="queues">鑾峰彇闃熷垪淇℃伅</button>
+          <button @click="calllist">鑾峰彇閫氳瘽淇℃伅</button>
+          <button @click="routelist">鑾峰彇璺敱淇℃伅</button>
+          <button @click="batch">鑾峰彇澶栧懠鍙傛暟淇℃伅</button>
+          <button @click="batch_start">寮�濮嬪鍛间换鍔�</button>
+          <button @click="batch_stop">鍋滄澶栧懠浠诲姟</button>
+        </div>
+      </div>
+
+      <!-- 鏃ュ織鏄剧ず鍖哄煙 -->
+      <h3>鍗忚鏃ュ織鍖�<button @click="testclear">娓呴櫎</button></h3>
+      <div id="msg" class="log-area">{{ logs }}</div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { CallsetState, CallgetList } from "@/api/AiCentre/index";
+
+export default {
+  name: "WebsocketDemo",
+
+  data() {
+    return {
+      config: {
+        cti_ws_url: "wss://9.208.2.190:8092/cal-api/",
+        seatname: "8000",
+        seatnum: "8000",
+        password: "123456",
+        phone: "10086",
+        uuid: "",
+        other: "8001",
+        group: "a3",
+        paramid: "3",
+      },
+      randomNum: "",
+      randomID: "",
+      logs: "",
+      ws: null,
+      isConnected: false,
+    };
+  },
+
+  mounted() {
+    this.CallgetList();
+    this.initializeWebSocket();
+  },
+
+  beforeUnmount() {
+    this.disconnectWebSocket();
+  },
+
+  methods: {
+    // 鍒濆鍖朩ebSocket杩炴帴
+    initializeWebSocket() {
+      try {
+        // 鏍规嵁褰撳墠椤甸潰鍗忚鑷姩閫夋嫨WS鍗忚
+        const isHttps = window.location.protocol === "https:";
+        this.config.cti_ws_url = isHttps
+          ? "wss://9.208.2.190:8092/cal-api/"
+          : "ws://40.78.0.169:6688";
+
+        if (typeof window.WebSocket === "undefined") {
+          this.addLog("閿欒: 娴忚鍣ㄤ笉鏀寔WebSocket");
+          return;
+        }
+
+        this.connectWebSocket();
+      } catch (error) {
+        this.addLog(`鍒濆鍖朩ebSocket閿欒: ${error.message}`);
+        // 灏濊瘯浣跨敤澶囩敤鍦板潃
+        this.config.cti_ws_url = "wss://9.208.2.190:8092/cal-api/";
+        setTimeout(() => this.connectWebSocket(), 2000);
+      }
+    },
+    // 鏌ヨ鍙敤鍒嗘満鍙�
+    async CallgetList() {
+      try {
+        const res = await CallgetList();
+        this.randomNum = res.data[0].tel;
+        this.randomID = res.data[0].id;
+        // 姝g‘璁剧疆 sipUri
+        this.config.seatname = randomNum;
+        this.config.seatnum = randomNum;
+        this.startCallsetState();
+      } catch (error) {
+        console.error("鑾峰彇搴у腑鍙峰け璐�:", error);
+        this.updateStatus("failed", "鑾峰彇搴у腑鍙峰け璐�");
+      }
+    },
+    //浣跨敤搴у腑鍙�
+    async startCallsetState() {
+      try {
+        await CallsetState({ id: this.randomID, state: 1 });
+        console.log("搴у腑鍙风姸鎬佹洿鏂颁负浣跨敤涓�");
+      } catch (error) {
+        console.error("鏇存柊搴у腑鍙风姸鎬佸け璐�:", error);
+      }
+    },
+    //閲婃斁搴у腑鍙�
+    async overCallsetState() {
+      try {
+        if (this.randomID) {
+          await CallsetState({ id: this.randomID, state: 0 });
+          console.log("搴у腑鍙风姸鎬佹洿鏂颁负鍙敤");
+        }
+      } catch (error) {
+        console.error("閲婃斁搴у腑鍙峰け璐�:", error);
+      }
+    },
+    // 杩炴帴WebSocket
+    connectWebSocket() {
+      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
+        this.addLog("WebSocket宸茶繛鎺�");
+        return;
+      }
+
+      try {
+        let wsUrl = this.config.cti_ws_url;
+        // 纭繚HTTPS椤甸潰浣跨敤WSS
+        if (
+          window.location.protocol === "https:" &&
+          wsUrl.startsWith("ws://")
+        ) {
+          wsUrl = wsUrl.replace("ws://", "wss://");
+        }
+
+        this.ws = new WebSocket(wsUrl);
+
+        this.ws.onopen = () => {
+          this.isConnected = true;
+          this.addLog("WebSocket杩炴帴鎴愬姛");
+        };
+
+        this.ws.onmessage = (event) => {
+          this.handleWebSocketMessage(event);
+        };
+
+        this.ws.onclose = (event) => {
+          this.isConnected = false;
+          this.addLog(`WebSocket杩炴帴鍏抽棴: ${event.code} ${event.reason}`);
+          // 鑷姩閲嶈繛
+          setTimeout(() => this.connectWebSocket(), 3000);
+        };
+
+        this.ws.onerror = (error) => {
+          this.addLog(`WebSocket閿欒: ${error.message}`);
+          // 灏濊瘯澶囩敤URL
+          if (!wsUrl.includes("9.208.2.190")) {
+            this.config.cti_ws_url = "wss://9.208.2.190:8092/cal-api/";
+            setTimeout(() => this.connectWebSocket(), 3000);
+          }
+        };
+      } catch (error) {
+        this.addLog(`杩炴帴WebSocket澶辫触: ${error.message}`);
+        // 灏濊瘯澶囩敤URL
+        this.config.cti_ws_url = "wss://9.208.2.190:8092/cal-api/";
+        setTimeout(() => this.connectWebSocket(), 3000);
+      }
+    },
+
+    // 澶勭悊WebSocket娑堟伅
+    handleWebSocketMessage(event) {
+      const reader = new FileReader();
+      reader.onloadend = (e) => {
+        const message = reader.result;
+        this.addLog(`鏀跺埌娑堟伅: ${message}`);
+
+        try {
+          const obj = JSON.parse(message);
+
+          // 澶勭悊蹇冭烦鍖�
+          if (obj.cmd === "system" && obj.action === "keepalive") {
+            this.keepalive(obj.seatname, obj.seatnum);
+          }
+
+          // 鑷姩璁剧疆UUID
+          if (obj.cmd === "control" && obj.action === "tp_callin") {
+            this.config.uuid = obj.uuid;
+            this.addLog(`鑷姩璁剧疆UUID: ${obj.uuid}`);
+          }
+        } catch (error) {
+          this.addLog(`娑堟伅瑙f瀽閿欒: ${error.message}`);
+        }
+      };
+      reader.readAsText(event.data);
+    },
+
+    // 鏂紑WebSocket杩炴帴
+    disconnectWebSocket() {
+      if (this.ws) {
+        this.ws.close();
+        this.ws = null;
+        this.isConnected = false;
+        this.addLog("WebSocket宸叉柇寮�");
+      }
+    },
+
+    // 鍙戦�乄ebSocket娑堟伅
+    sendWebSocketMessage(message) {
+      if (!this.isConnected || !this.ws) {
+        this.addLog("閿欒: WebSocket鏈繛鎺�");
+        return false;
+      }
+
+      try {
+        const messageStr =
+          typeof message === "string" ? message : JSON.stringify(message);
+        this.ws.send(messageStr);
+        this.addLog(`鍙戦�佹秷鎭�: ${messageStr}`);
+        return true;
+      } catch (error) {
+        this.addLog(`鍙戦�佹秷鎭け璐�: ${error.message}`);
+        return false;
+      }
+    },
+
+    // 楠岃瘉鍙傛暟
+    validateParams(params, requiredFields) {
+      for (const field of requiredFields) {
+        if (!params[field] || params[field].toString().trim() === "") {
+          this.addLog(`閿欒: ${field} 涓嶈兘涓虹┖`);
+          return false;
+        }
+      }
+      return true;
+    },
+
+    // ==================== WebSocket.js 鍔熻兘鏁村悎 ====================
+
+    // 绛惧叆
+    seatlogin() {
+      const { seatname, seatnum, password, cti_ws_url } = this.config;
+
+      if (
+        !this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
+      ) {
+        return;
+      }
+
+      // 閲嶆柊杩炴帴WebSocket锛堝師js鏂囦欢涓殑閫昏緫锛�
+      this.connectWebSocket();
+      setTimeout(() => {
+        const protocol = {
+          cmd: "system",
+          action: "seatlogin",
+          seatname: seatname,
+          seatnum: seatnum,
+          password: password,
+          timestamp: Date.now(),
+        };
+        this.sendWebSocketMessage(protocol);
+      }, 1000);
+    },
+
+    // 绛惧嚭
+    seatlogout() {
+      const { seatname, seatnum } = this.config;
+
+      if (
+        !this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
+      ) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "system",
+        action: "seatlogout",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+      this.ws.close();
+    },
+
+    // 绀哄繖
+    afk() {
+      const { seatname, seatnum } = this.config;
+
+      if (
+        !this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
+      ) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "system",
+        action: "afk",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 绀洪棽
+    online() {
+      const { seatname, seatnum } = this.config;
+
+      if (
+        !this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
+      ) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "system",
+        action: "online",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 浠g瓟
+    pickup() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "pickup",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鎸傛満
+    hangup() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "hangup",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 澶栧懠
+    callout() {
+      const { seatname, seatnum, phone } = this.config;
+
+      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "callout",
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 閫氳瘽杞Щ
+    transfer() {
+      const { seatname, seatnum, phone, uuid } = this.config;
+
+      if (
+        !this.validateParams({ seatnum, phone, uuid }, [
+          "seatnum",
+          "phone",
+          "uuid",
+        ])
+      ) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "transfer",
+        uuid: uuid,
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 閫氳瘽杞Щ鏀跺洖
+    transferresume() {
+      const { seatname, seatnum, phone, uuid } = this.config;
+
+      if (
+        !this.validateParams({ seatnum, phone, uuid }, [
+          "seatnum",
+          "phone",
+          "uuid",
+        ])
+      ) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "transferresume",
+        uuid: uuid,
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 閫氳瘽淇濇寔
+    hold() {
+      const { seatname, seatnum, uuid } = this.config;
+
+      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "hold",
+        uuid: uuid,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 閫氳瘽淇濇寔鏀跺洖
+    holdresume() {
+      const { seatname, seatnum, uuid } = this.config;
+
+      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "holdresume",
+        uuid: uuid,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 閫氳瘽寮烘媶
+    remove() {
+      const { seatname, seatnum, phone } = this.config;
+
+      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "remove",
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 閫氳瘽寮烘彃
+    insert() {
+      const { seatname, seatnum, phone } = this.config;
+
+      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "insert",
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鐩戝惉
+    monitor() {
+      const { seatname, seatnum, phone } = this.config;
+
+      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "monitor",
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鐩戝惉杞�氳瘽
+    monitor_to_talk() {
+      const { seatname, seatnum, phone } = this.config;
+
+      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "monitor_to_talk",
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鐩戝惉缁撴潫
+    monitor_end() {
+      const { seatname, seatnum, phone } = this.config;
+
+      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "monitor_end",
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 閫夋嫨閫氳瘽
+    choosecall() {
+      const { seatname, seatnum, uuid } = this.config;
+
+      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "choosecall",
+        uuid: uuid,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 浠f帴
+    replacecall() {
+      const { seatname, seatnum, phone } = this.config;
+
+      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "replacecall",
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 涓夋柟閫氳瘽
+    three() {
+      const { seatname, seatnum, phone } = this.config;
+
+      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "three",
+        phone: phone,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍜ㄨ寮�濮�
+    handoff_ready() {
+      const { seatname, seatnum, uuid } = this.config;
+
+      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "handoff_ready",
+        uuid: uuid,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍜ㄨ鍛煎彨
+    handoff_call() {
+      const { seatname, seatnum, other, uuid } = this.config;
+
+      if (
+        !this.validateParams({ seatnum, other, uuid }, [
+          "seatnum",
+          "other",
+          "uuid",
+        ])
+      ) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "handoff_call",
+        uuid: uuid,
+        phone: other,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍜ㄨ鏀跺洖
+    handoff_resume() {
+      const { seatname, seatnum, uuid } = this.config;
+
+      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "handoff_resume",
+        uuid: uuid,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍜ㄨ杞Щ
+    handoff_transfer() {
+      const { seatname, seatnum, other, uuid } = this.config;
+
+      if (
+        !this.validateParams({ seatnum, other, uuid }, [
+          "seatnum",
+          "other",
+          "uuid",
+        ])
+      ) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "handoff_transfer",
+        uuid: uuid,
+        phone: other,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍜ㄨ涓夋柟
+    handoff_three() {
+      const { seatname, seatnum, uuid } = this.config;
+
+      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "handoff_three",
+        uuid: uuid,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 寮�濮嬮�氳瘽褰曢煶
+    record_start() {
+      const { seatname, seatnum, uuid } = this.config;
+
+      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "record_start",
+        uuid: uuid,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍋滄閫氳瘽褰曢煶
+    record_stop() {
+      const { seatname, seatnum, uuid } = this.config;
+
+      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "control",
+        action: "record_stop",
+        uuid: uuid,
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鎵撳紑鍧愬腑鐘舵��
+    openseatlist() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "openseatlist",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍏抽棴鍧愬腑鐘舵��
+    closeseatlist() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "closeseatlist",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鎵撳紑闃熷垪淇℃伅
+    openqueues() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "openqueues",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍏抽棴闃熷垪淇℃伅
+    closequeues() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "closequeues",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鎵撳紑閫氳瘽淇℃伅
+    opencalllist() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "opencalllist",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍏抽棴閫氳瘽淇℃伅
+    closecalllist() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "closecalllist",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鎵撳紑璺敱淇℃伅
+    openroutelist() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "openroutelist",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍏抽棴璺敱淇℃伅
+    closeroutelist() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "closeroutelist",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鑾峰彇鍧愬腑淇℃伅
+    seatlist() {
+      const { group } = this.config;
+
+      if (!this.validateParams({ group }, ["group"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "seatlist",
+        group: group,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鑾峰彇闃熷垪淇℃伅
+    queues() {
+      const protocol = {
+        cmd: "status",
+        action: "queues",
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鑾峰彇閫氳瘽淇℃伅
+    calllist() {
+      const protocol = {
+        cmd: "status",
+        action: "calllist",
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鑾峰彇璺敱淇℃伅
+    routelist() {
+      const protocol = {
+        cmd: "status",
+        action: "routelist",
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鑾峰彇澶栧懠鍙傛暟淇℃伅
+    batch() {
+      const { paramid } = this.config;
+
+      if (!this.validateParams({ paramid }, ["paramid"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "status",
+        action: "batch",
+        paramid: paramid,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 寮�濮嬪鍛间换鍔�
+    batch_start() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "system",
+        action: "batch_start",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 鍋滄澶栧懠浠诲姟
+    batch_stop() {
+      const { seatname, seatnum } = this.config;
+
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "system",
+        action: "batch_stop",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 蹇冭烦鍖�
+    keepalive(seatname, seatnum) {
+      if (!this.validateParams({ seatnum }, ["seatnum"])) {
+        return;
+      }
+
+      const protocol = {
+        cmd: "system",
+        action: "keepalive",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+      this.sendWebSocketMessage(protocol);
+    },
+
+    // 娓呴櫎鏃ュ織
+    testclear() {
+      this.logs = "";
+      this.addLog("鏃ュ織宸叉竻闄�");
+    },
+
+    // 娣诲姞鏃ュ織
+    addLog(message) {
+      const timestamp = new Date().toLocaleTimeString();
+      this.logs += `[${timestamp}] ${message}\n`;
+
+      // 闄愬埗鏃ュ織闀垮害锛岄槻姝㈠唴瀛樻孩鍑�
+      const logLines = this.logs.split("\n");
+      if (logLines.length > 100) {
+        this.logs = logLines.slice(-50).join("\n");
+      }
+    },
+  },
+};
+</script>
+
+<style scoped>
+.websocket-demo {
+  font-family: Arial, sans-serif;
+  padding: 20px;
+  max-width: 1200px;
+  margin: 0 auto;
+}
+
+.config-area {
+  margin-bottom: 20px;
+  padding: 15px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  background-color: #f9f9f9;
+}
+
+.input-group {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 10px;
+}
+
+.input-group label {
+  font-weight: bold;
+  min-width: 80px;
+}
+
+.input-group input {
+  padding: 5px 10px;
+  border: 1px solid #ccc;
+  border-radius: 3px;
+  width: 120px;
+}
+
+.button-area {
+  margin-bottom: 20px;
+}
+
+.button-row {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 5px;
+  margin-bottom: 10px;
+}
+
+.button-row button {
+  padding: 8px 15px;
+  border: 1px solid #ccc;
+  border-radius: 3px;
+  background-color: #f0f0f0;
+  cursor: pointer;
+  transition: background-color 0.3s;
+  font-size: 12px;
+}
+
+.button-row button:hover {
+  background-color: #e0e0e0;
+}
+
+.button-row button:active {
+  background-color: #d0d0d0;
+  transform: translateY(1px);
+}
+
+.log-area {
+  height: 300px;
+  overflow-y: auto;
+  border: 1px solid #ccc;
+  padding: 10px;
+  background-color: #f5f5f5;
+  white-space: pre-wrap;
+  font-family: "Courier New", monospace;
+  font-size: 12px;
+  line-height: 1.4;
+}
+
+h3 {
+  color: #333;
+  border-bottom: 2px solid #eee;
+  padding-bottom: 10px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+h3 button {
+  padding: 5px 10px;
+  font-size: 12px;
+  background-color: #f0f0f0;
+  border: 1px solid #ccc;
+  border-radius: 3px;
+  cursor: pointer;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+  .websocket-demo {
+    padding: 10px;
+  }
+
+  .input-group {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+
+  .input-group input {
+    width: 100%;
+  }
+
+  .button-row {
+    flex-direction: column;
+  }
+
+  .button-row button {
+    width: 100%;
+    margin-bottom: 5px;
+  }
+}
+</style>
diff --git a/src/views/followvisit/discharge/ClickCall.vue b/src/views/followvisit/discharge/ClickCall.vue
index 2896d7b..727d67e 100644
--- a/src/views/followvisit/discharge/ClickCall.vue
+++ b/src/views/followvisit/discharge/ClickCall.vue
@@ -1,21 +1,36 @@
 <template>
   <div class="websocket-demo">
     <div>
-      <h3>Websocket鎺ュ彛娴嬭瘯DEMO</h3>
+      <h3>Websocket鍛煎彨涓績鎺ュ彛</h3>
       <div class="config-area">
+        <div class="status-indicator">
+          <span :class="['status-dot', connectionStatus]"></span>
+          杩炴帴鐘舵��: {{ connectionText }}
+          <span :class="['status-dot', seatStatus]"></span>
+          搴у腑鐘舵��: {{ seatStatusText }}
+        </div>
+
         <div class="input-group">
           <label>CTI_WS_URL</label>
           <input
             type="text"
             v-model="config.cti_ws_url"
-            placeholder="ws://40.78.0.169:6688"
+            placeholder="wss://your-server.com"
           />
 
           <label>鍧愬腑宸ュ彿</label>
-          <input type="text" v-model="config.seatname" placeholder="8000" />
+          <input
+            type="text"
+            v-model="config.seatname"
+            :placeholder="randomNum"
+          />
 
           <label>鍧愬腑鍒嗘満</label>
-          <input type="text" v-model="config.seatnum" placeholder="8000" />
+          <input
+            type="text"
+            v-model="config.seatnum"
+            :placeholder="randomNum"
+          />
 
           <label>瀵嗙爜</label>
           <input type="text" v-model="config.password" placeholder="123456" />
@@ -23,129 +38,136 @@
 
         <div class="input-group">
           <label>澶栫嚎鍙风爜</label>
-          <input type="text" v-model="config.phone" placeholder="10086" />
-
-          <label>UUID</label>
-          <input type="text" v-model="config.uuid" />
-
-          <label>鍏朵粬鍧愬腑</label>
-          <input type="text" v-model="config.other" placeholder="8001" />
+          <input
+            type="text"
+            v-model="customerPhone"
+            placeholder="璇疯緭鍏ョ數璇濆彿鐮�"
+          />
 
           <label>鎶�鑳界粍</label>
           <input type="text" v-model="config.group" placeholder="a3" />
-
-          <label>澶栧懠鍙傛暟id</label>
-          <input type="text" v-model="config.paramid" placeholder="3" />
         </div>
       </div>
 
       <!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
       <div class="button-area">
-        <!-- 绗竴琛屾寜閽� -->
         <div class="button-row">
-          <button @click="seatlogin">绛惧叆</button>
-          <button @click="seatlogout">绛惧嚭</button>
-          <button @click="afk">绀哄繖</button>
-          <button @click="online">绀洪棽</button>
-          <button @click="pickup">浠g瓟</button>
+          <button
+            @click="handleSeatLogin"
+            :disabled="!isConnected || isSeatLoggedIn"
+          >
+            绛惧叆
+          </button>
+          <button @click="handleSeatLogout" :disabled="!isSeatLoggedIn">
+            绛惧嚭
+          </button>
+          <button @click="callout" :disabled="!isSeatLoggedIn">澶栧懠</button>
+          <button @click="hangup" :disabled="!isSeatLoggedIn">鎸傛満</button>
         </div>
 
-        <!-- 绗簩琛屾寜閽� -->
         <div class="button-row">
-          <button @click="hangup">鎸傛満</button>
-          <button @click="callout">澶栧懠</button>
-          <button @click="transfer">閫氳瘽杞Щ</button>
-          <button @click="transferresume">閫氳瘽杞Щ鏀跺洖</button>
-          <button @click="hold">閫氳瘽淇濇寔</button>
-          <button @click="holdresume">閫氳瘽淇濇寔鏀跺洖</button>
-          <button @click="remove">閫氳瘽寮烘媶</button>
-          <button @click="insert">閫氳瘽寮烘彃</button>
-          <button @click="monitor">鐩戝惉</button>
-          <button @click="monitor_to_talk">鐩戝惉杞�氳瘽</button>
-          <button @click="monitor_end">鐩戝惉缁撴潫</button>
-          <button @click="choosecall">閫夋嫨</button>
-          <button @click="replacecall">浠f帴</button>
-          <button @click="three">涓夋柟閫氳瘽</button>
-        </div>
-
-        <!-- 绗笁琛屾寜閽� -->
-        <div class="button-row">
-          <button @click="handoff_ready">鍜ㄨ寮�濮�</button>
-          <button @click="handoff_call">鍜ㄨ鍛煎彨</button>
-          <button @click="handoff_resume">鍜ㄨ鏀跺洖</button>
-          <button @click="handoff_transfer">鍜ㄨ杞Щ</button>
-          <button @click="handoff_three">鍜ㄨ涓夋柟</button>
-          <button @click="record_start">寮�濮嬮�氳瘽褰曢煶</button>
-          <button @click="record_stop">鍋滄閫氳瘽褰曢煶</button>
-        </div>
-
-        <!-- 绗洓琛屾寜閽� -->
-        <div class="button-row">
-          <button @click="openseatlist">鎵撳紑鍧愬腑鐘舵��</button>
-          <button @click="closeseatlist">鍏抽棴鍧愬腑鐘舵��</button>
-          <button @click="openqueues">鎵撳紑闃熷垪淇℃伅</button>
-          <button @click="closequeues">鍏抽棴闃熷垪淇℃伅</button>
-          <button @click="opencalllist">鎵撳紑閫氳瘽淇℃伅</button>
-          <button @click="closecalllist">鍏抽棴閫氳瘽淇℃伅</button>
-          <button @click="openroutelist">鎵撳紑璺敱淇℃伅</button>
-          <button @click="closeroutelist">鍏抽棴璺敱淇℃伅</button>
-        </div>
-
-        <!-- 绗簲琛屾寜閽� -->
-        <div class="button-row">
-          <button @click="seatlist">鑾峰彇鍧愬腑淇℃伅</button>
-          <button @click="queues">鑾峰彇闃熷垪淇℃伅</button>
-          <button @click="calllist">鑾峰彇閫氳瘽淇℃伅</button>
-          <button @click="routelist">鑾峰彇璺敱淇℃伅</button>
-          <button @click="batch">鑾峰彇澶栧懠鍙傛暟淇℃伅</button>
-          <button @click="batch_start">寮�濮嬪鍛间换鍔�</button>
-          <button @click="batch_stop">鍋滄澶栧懠浠诲姟</button>
+          <button @click="afk" :disabled="!isSeatLoggedIn">绀哄繖</button>
+          <button @click="online" :disabled="!isSeatLoggedIn">绀洪棽</button>
+          <button @click="hold" :disabled="!isSeatLoggedIn">淇濇寔</button>
+          <button @click="holdresume" :disabled="!isSeatLoggedIn">
+            鍙栨秷淇濇寔
+          </button>
         </div>
       </div>
 
       <!-- 鏃ュ織鏄剧ず鍖哄煙 -->
-      <h3>鍗忚鏃ュ織鍖�<button @click="testclear">娓呴櫎</button></h3>
-      <div id="msg" class="log-area">{{ logs }}</div>
+      <h3>鍗忚鏃ュ織鍖� <button @click="testclear">娓呴櫎</button></h3>
+      <div class="log-area">{{ logs }}</div>
     </div>
   </div>
 </template>
 
 <script>
+import { CallsetState, CallgetList } from "@/api/AiCentre/index";
+
 export default {
   name: "WebsocketDemo",
+  emits: ["status-change", "call-status", "error"],
+
+  props: {
+    customerPhone: {
+      type: String,
+      default: "",
+    },
+    autoLogin: {
+      type: Boolean,
+      default: true,
+    },
+  },
 
   data() {
     return {
       config: {
-        cti_ws_url: "wss://9.208.2.190:8092/cal-api/",
-        seatname: "8000",
-        seatnum: "8000",
+        cti_ws_url: "",
+        seatname: "",
+        seatnum: "",
         password: "123456",
-        phone: "10086",
+        phone: "",
         uuid: "",
         other: "8001",
         group: "a3",
         paramid: "3",
       },
+      randomNum: "",
+      randomID: "",
       logs: "",
       ws: null,
       isConnected: false,
+      isSeatLoggedIn: false,
+      currentCallStatus: "idle", // idle, calling, connected
+      seatResourceAcquired: false,
+      reconnectAttempts: 0,
+      maxReconnectAttempts: 5,
+      heartbeatTimer: null,
     };
   },
 
-  mounted() {
+  computed: {
+    connectionStatus() {
+      return this.isConnected ? "connected" : "disconnected";
+    },
+    connectionText() {
+      return this.isConnected ? "宸茶繛鎺�" : "鏈繛鎺�";
+    },
+    seatStatus() {
+      return this.isSeatLoggedIn ? "logged-in" : "logged-out";
+    },
+    seatStatusText() {
+      return this.isSeatLoggedIn ? "宸茬鍏�" : "鏈鍏�";
+    },
+  },
+
+  watch: {
+    customerPhone(newVal) {
+      this.config.phone = newVal;
+    },
+    isSeatLoggedIn(newVal) {
+      this.$emit("status-change", {
+        isLoggedIn: newVal,
+        seatNumber: this.config.seatnum,
+        status: newVal ? "ready" : "offline",
+      });
+    },
+  },
+
+  async mounted() {
+    await this.initializeSeatResource();
     this.initializeWebSocket();
   },
 
   beforeUnmount() {
-    this.disconnectWebSocket();
+    this.cleanup();
   },
 
   methods: {
     // 鍒濆鍖朩ebSocket杩炴帴
     initializeWebSocket() {
       try {
-        // 鏍规嵁褰撳墠椤甸潰鍗忚鑷姩閫夋嫨WS鍗忚
         const isHttps = window.location.protocol === "https:";
         this.config.cti_ws_url = isHttps
           ? "wss://9.208.2.190:8092/cal-api/"
@@ -159,22 +181,70 @@
         this.connectWebSocket();
       } catch (error) {
         this.addLog(`鍒濆鍖朩ebSocket閿欒: ${error.message}`);
-        // 灏濊瘯浣跨敤澶囩敤鍦板潃
-        this.config.cti_ws_url = "wss://9.208.2.190:8092/cal-api/";
         setTimeout(() => this.connectWebSocket(), 2000);
       }
     },
+    // 鍒濆鍖栧骇甯彿璧勬簮
+    async initializeSeatResource() {
+      try {
+        const res = await CallgetList();
+        if (res.data && res.data.length > 0) {
+          // this.randomNum = res.data[0].tel;
+          // this.randomID = res.data[0].id;
+          this.randomNum = 8000;
+          this.randomID = 8000;
+          // 璁剧疆榛樿搴у腑鍙�
+          this.config.seatname = this.randomNum;
+          this.config.seatnum = this.randomNum;
 
+          // 绔嬪嵆鍗犵敤搴у腑鍙疯祫婧�
+          await this.startCallsetState();
+          this.seatResourceAcquired = true;
+          this.addLog(`搴у腑鍙疯祫婧愯幏鍙栨垚鍔�: ${this.randomNum}`);
+        }
+      } catch (error) {
+        console.error("鑾峰彇搴у腑鍙峰け璐�:", error);
+        this.addLog("閿欒: 鑾峰彇搴у腑鍙疯祫婧愬け璐�");
+        this.$emit("error", { type: "seat_acquisition_failed", error });
+      }
+    },
+    // 鍗犵敤搴у腑鍙�
+    async startCallsetState() {
+      try {
+        await CallsetState({ id: this.randomID, state: 1 });
+        this.addLog("搴у腑鍙风姸鎬佹洿鏂颁负浣跨敤涓�");
+      } catch (error) {
+        console.error("鏇存柊搴у腑鍙风姸鎬佸け璐�:", error);
+        throw error;
+      }
+    },
+
+    // 閲婃斁搴у腑鍙�
+    async releaseSeatResource() {
+      if (this.seatResourceAcquired && this.randomID) {
+        try {
+          await CallsetState({ id: this.randomID, state: 0 });
+          this.addLog("搴у腑鍙疯祫婧愬凡閲婃斁");
+          this.seatResourceAcquired = false;
+        } catch (error) {
+          console.error("閲婃斁搴у腑鍙峰け璐�:", error);
+        }
+      }
+    },
+    // 杩炴帴WebSocket
     // 杩炴帴WebSocket
     connectWebSocket() {
       if (this.ws && this.ws.readyState === WebSocket.OPEN) {
-        this.addLog("WebSocket宸茶繛鎺�");
+        return;
+      }
+
+      if (this.reconnectAttempts >= this.maxReconnectAttempts) {
+        this.addLog("閿欒: 杈惧埌鏈�澶ч噸杩炴鏁帮紝鍋滄閲嶈繛");
         return;
       }
 
       try {
         let wsUrl = this.config.cti_ws_url;
-        // 纭繚HTTPS椤甸潰浣跨敤WSS
         if (
           window.location.protocol === "https:" &&
           wsUrl.startsWith("ws://")
@@ -186,7 +256,14 @@
 
         this.ws.onopen = () => {
           this.isConnected = true;
+          this.reconnectAttempts = 0;
           this.addLog("WebSocket杩炴帴鎴愬姛");
+          this.startHeartbeat();
+
+          // 杩炴帴鎴愬姛鍚庤嚜鍔ㄧ鍏�
+          if (this.autoLogin && this.seatResourceAcquired) {
+            setTimeout(() => this.handleSeatLogin(), 500);
+          }
         };
 
         this.ws.onmessage = (event) => {
@@ -195,54 +272,202 @@
 
         this.ws.onclose = (event) => {
           this.isConnected = false;
+          this.isSeatLoggedIn = false;
+          this.stopHeartbeat();
           this.addLog(`WebSocket杩炴帴鍏抽棴: ${event.code} ${event.reason}`);
+
           // 鑷姩閲嶈繛
-          setTimeout(() => this.connectWebSocket(), 3000);
+          if (this.reconnectAttempts < this.maxReconnectAttempts) {
+            this.reconnectAttempts++;
+            setTimeout(() => this.connectWebSocket(), 3000);
+          }
         };
 
         this.ws.onerror = (error) => {
           this.addLog(`WebSocket閿欒: ${error.message}`);
-          // 灏濊瘯澶囩敤URL
-          if (!wsUrl.includes("9.208.2.190")) {
-            this.config.cti_ws_url = "wss://9.208.2.190:8092/cal-api/";
-            setTimeout(() => this.connectWebSocket(), 3000);
-          }
         };
       } catch (error) {
         this.addLog(`杩炴帴WebSocket澶辫触: ${error.message}`);
-        // 灏濊瘯澶囩敤URL
-        this.config.cti_ws_url = "wss://9.208.2.190:8092/cal-api/";
         setTimeout(() => this.connectWebSocket(), 3000);
       }
     },
 
     // 澶勭悊WebSocket娑堟伅
     handleWebSocketMessage(event) {
-      const reader = new FileReader();
-      reader.onloadend = (e) => {
-        const message = reader.result;
-        this.addLog(`鏀跺埌娑堟伅: ${message}`);
-
-        try {
-          const obj = JSON.parse(message);
-
-          // 澶勭悊蹇冭烦鍖�
-          if (obj.cmd === "system" && obj.action === "keepalive") {
-            this.keepalive(obj.seatname, obj.seatnum);
-          }
-
-          // 鑷姩璁剧疆UUID
-          if (obj.cmd === "control" && obj.action === "tp_callin") {
-            this.config.uuid = obj.uuid;
-            this.addLog(`鑷姩璁剧疆UUID: ${obj.uuid}`);
-          }
-        } catch (error) {
-          this.addLog(`娑堟伅瑙f瀽閿欒: ${error.message}`);
+      try {
+        // 妫�鏌ユ暟鎹被鍨嬶細鍙兘鏄瓧绗︿覆鎴朆lob
+        if (event.data instanceof Blob) {
+          // 澶勭悊浜岃繘鍒舵暟鎹紙Blob锛�
+          const reader = new FileReader();
+          reader.onload = () => {
+            try {
+              const textData = reader.result;
+              this.addLog(`鏀跺埌Blob娑堟伅: ${textData}`);
+              this.processWebSocketData(textData);
+            } catch (error) {
+              this.addLog(`Blob鏁版嵁澶勭悊閿欒: ${error.message}`);
+            }
+          };
+          reader.readAsText(event.data);
+        } else if (typeof event.data === "string") {
+          // 鐩存帴澶勭悊鏂囨湰鏁版嵁
+          this.addLog(`鏀跺埌鏂囨湰娑堟伅: ${event.data}`);
+          this.processWebSocketData(event.data);
+        } else {
+          this.addLog(`鏈煡鏁版嵁绫诲瀷: ${typeof event.data}`);
         }
+      } catch (error) {
+        this.addLog(`娑堟伅澶勭悊閿欒: ${error.message}`);
+      }
+    },
+    // 涓撻棬澶勭悊瑙f瀽鍚庣殑WebSocket鏁版嵁
+    processWebSocketData(messageText) {
+      console.log(messageText,'娑堟伅1');
+
+      try {
+        const obj = JSON.parse(messageText);
+      console.log(obj,'娑堟伅2');
+
+        // 澶勭悊蹇冭烦鍖�
+        if (obj.cmd === "system" && obj.action === "keepalive") {
+          this.keepalive(obj.seatname, obj.seatnum);
+        }
+        // 澶勭悊鎸傛柇
+         if (obj.action === "calloutend") {
+          this.hangup();
+        }
+
+        // 澶勭悊绛惧叆鍝嶅簲
+        if (obj.cmd === "system" && obj.action === "seatlogin") {
+            this.isSeatLoggedIn = true;
+            this.addLog("搴у腑绛惧叆鎴愬姛");
+            this.$emit("status-change", {
+              isLoggedIn: true,
+              seatNumber: this.config.seatnum,
+              status: "ready",
+            });
+        }
+
+        // 澶勭悊绛惧嚭鍝嶅簲
+        if (obj.cmd === "system" && obj.action === "seatlogout") {
+          this.isSeatLoggedIn = false;
+          this.addLog("搴у腑绛惧嚭鎴愬姛");
+          this.$emit("status-change", {
+            isLoggedIn: false,
+            status: "offline",
+          });
+        }
+
+        // 鑷姩璁剧疆UUID锛堟潵鐢典簨浠讹級
+        if (obj.cmd === "control" && obj.action === "tp_callin") {
+          this.config.uuid = obj.uuid;
+          this.addLog(`鑷姩璁剧疆UUID: ${obj.uuid}`);
+          this.$emit("call-status", {
+            status: "incoming",
+            uuid: obj.uuid,
+            phone: obj.phone || "鏈煡鍙风爜",
+          });
+        }
+
+        // 澶勭悊澶栧懠鍝嶅簲
+        if (obj.cmd === "control" && obj.action === "callout") {
+            this.$emit("call-status", {
+              status: obj.status || "calling",
+              uuid: obj.uuid,
+              phone: this.config.phone,
+            });
+        }
+
+        // 澶勭悊鎸傛満鍝嶅簲
+        if (obj.cmd === "control" && obj.action === "hangup") {
+          this.$emit("call-status", {
+            status: "idle",
+            uuid: obj.uuid,
+          });
+        }
+
+        // 澶勭悊閫氳瘽鐘舵�佸彉鍖�
+        if (obj.cmd === "control" && obj.status) {
+          this.handleCallStatusChange(obj);
+        }
+      } catch (error) {
+        this.addLog(`JSON瑙f瀽閿欒: ${error.message}, 鍘熷鏁版嵁: ${messageText}`);
+      }
+    },
+    // 澶勭悊鍛煎彨鐘舵�佸彉鍖�
+    handleCallStatusChange(obj) {
+      const statusMap = {
+        ringing: "鎸搩涓�",
+        connected: "閫氳瘽涓�",
+        held: "宸蹭繚鎸�",
+        ended: "閫氳瘽缁撴潫",
       };
-      reader.readAsText(event.data);
+
+      this.addLog(`閫氳瘽鐘舵��: ${statusMap[obj.status] || obj.status}`);
+      this.$emit("call-status", {
+        status: obj.status,
+        uuid: obj.uuid,
+        phone: obj.phone || this.config.phone,
+      });
+    },
+    // 寮�濮嬪績璺虫娴�
+    startHeartbeat() {
+      this.heartbeatTimer = setInterval(() => {
+        if (this.isConnected && this.isSeatLoggedIn) {
+          this.keepalive(this.config.seatname, this.config.seatnum);
+        }
+      }, 30000); // 30绉掑績璺�
     },
 
+    // 鍋滄蹇冭烦妫�娴�
+    stopHeartbeat() {
+      if (this.heartbeatTimer) {
+        clearInterval(this.heartbeatTimer);
+        this.heartbeatTimer = null;
+      }
+    },
+    // 搴у腑绛惧叆
+    async handleSeatLogin() {
+      if (!this.seatResourceAcquired) {
+        this.addLog("閿欒: 鏈幏鍙栧骇甯彿璧勬簮锛屾棤娉曠鍏�");
+        return;
+      }
+
+      const { seatname, seatnum, password } = this.config;
+      if (!seatname || !seatnum) {
+        this.addLog("閿欒: 搴у腑宸ュ彿鍜屽垎鏈哄彿涓嶈兘涓虹┖");
+        return;
+      }
+
+      const protocol = {
+        cmd: "system",
+        action: "seatlogin",
+        seatname: seatname,
+        seatnum: seatnum,
+        password: password,
+        timestamp: Date.now(),
+      };
+
+      this.sendWebSocketMessage(protocol);
+    },
+    // 搴у腑绛惧嚭
+    async handleSeatLogout() {
+      const { seatname, seatnum } = this.config;
+
+      const protocol = {
+        cmd: "system",
+        action: "seatlogout",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+
+      if (this.sendWebSocketMessage(protocol)) {
+        this.isSeatLoggedIn = false;
+        // 寤惰繜閲婃斁璧勬簮锛岀‘淇濈鍑哄畬鎴�
+        setTimeout(() => this.releaseSeatResource(), 1000);
+      }
+    },
     // 鏂紑WebSocket杩炴帴
     disconnectWebSocket() {
       if (this.ws) {
@@ -333,19 +558,11 @@
 
     // 绀哄繖
     afk() {
-      const { seatname, seatnum } = this.config;
-
-      if (
-        !this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
-      ) {
-        return;
-      }
-
       const protocol = {
         cmd: "system",
         action: "afk",
-        seatname: seatname,
-        seatnum: seatnum,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
@@ -353,19 +570,11 @@
 
     // 绀洪棽
     online() {
-      const { seatname, seatnum } = this.config;
-
-      if (
-        !this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
-      ) {
-        return;
-      }
-
       const protocol = {
         cmd: "system",
         action: "online",
-        seatname: seatname,
-        seatnum: seatnum,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
@@ -391,27 +600,28 @@
 
     // 鎸傛満
     hangup() {
-      const { seatname, seatnum } = this.config;
-
-      if (!this.validateParams({ seatnum }, ["seatnum"])) {
-        return;
-      }
-
       const protocol = {
         cmd: "control",
         action: "hangup",
-        seatname: seatname,
-        seatnum: seatnum,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
     },
 
     // 澶栧懠
-    callout() {
-      const { seatname, seatnum, phone } = this.config;
+    // 澶栧懠鎿嶄綔
+    async callout(phoneNumber = null) {
+      const phone = phoneNumber || this.customerPhone || this.config.phone;
+      if (!phone) {
+        this.addLog("閿欒: 琚彨鍙风爜涓嶈兘涓虹┖");
+        this.$emit("error", { type: "phone_number_required" });
+        return;
+      }
 
-      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+      if (!this.isSeatLoggedIn) {
+        this.addLog("閿欒: 搴у腑鏈鍏ワ紝鏃犳硶澶栧懠");
         return;
       }
 
@@ -419,13 +629,23 @@
         cmd: "control",
         action: "callout",
         phone: phone,
-        seatname: seatname,
-        seatnum: seatnum,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
-      this.sendWebSocketMessage(protocol);
-    },
 
+      this.sendWebSocketMessage(protocol);
+      this.$emit("call-status", { status: "calling", phone });
+    },
+    // 娓呯悊璧勬簮
+    cleanup() {
+      this.stopHeartbeat();
+      if (this.ws) {
+        this.ws.close();
+        this.ws = null;
+      }
+      this.releaseSeatResource();
+    },
     // 閫氳瘽杞Щ
     transfer() {
       const { seatname, seatnum, phone, uuid } = this.config;
@@ -480,18 +700,12 @@
 
     // 閫氳瘽淇濇寔
     hold() {
-      const { seatname, seatnum, uuid } = this.config;
-
-      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
-        return;
-      }
-
       const protocol = {
         cmd: "control",
         action: "hold",
-        uuid: uuid,
-        seatname: seatname,
-        seatnum: seatnum,
+        uuid: this.config.uuid,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
@@ -499,18 +713,12 @@
 
     // 閫氳瘽淇濇寔鏀跺洖
     holdresume() {
-      const { seatname, seatnum, uuid } = this.config;
-
-      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
-        return;
-      }
-
       const protocol = {
         cmd: "control",
         action: "holdresume",
-        uuid: uuid,
-        seatname: seatname,
-        seatnum: seatnum,
+        uuid: this.config.uuid,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
@@ -1059,12 +1267,7 @@
       this.sendWebSocketMessage(protocol);
     },
 
-    // 蹇冭烦鍖�
     keepalive(seatname, seatnum) {
-      if (!this.validateParams({ seatnum }, ["seatnum"])) {
-        return;
-      }
-
       const protocol = {
         cmd: "system",
         action: "keepalive",
@@ -1097,6 +1300,87 @@
 </script>
 
 <style scoped>
+.status-indicator {
+  margin-bottom: 15px;
+  padding: 10px;
+  background: #f5f5f5;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.status-dot {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  display: inline-block;
+}
+
+.status-dot.connected {
+  background-color: #52c41a;
+}
+
+.status-dot.disconnected {
+  background-color: #f5222d;
+}
+
+.status-dot.logged-in {
+  background-color: #1890ff;
+}
+
+.status-dot.logged-out {
+  background-color: #d9d9d9;
+}
+
+.button-row button:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+.config-area {
+  margin-bottom: 20px;
+}
+
+.input-group {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  margin-bottom: 10px;
+}
+
+.input-group label {
+  font-weight: bold;
+  min-width: 80px;
+}
+
+.input-group input {
+  padding: 5px 10px;
+  border: 1px solid #ccc;
+  border-radius: 3px;
+  width: 120px;
+}
+
+.button-area {
+  margin-bottom: 20px;
+}
+
+.button-row {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 5px;
+  margin-bottom: 10px;
+}
+
+.log-area {
+  height: 200px;
+  overflow-y: auto;
+  border: 1px solid #ccc;
+  padding: 10px;
+  background: #f5f5f5;
+  white-space: pre-wrap;
+  font-family: monospace;
+}
 .websocket-demo {
   font-family: Arial, sans-serif;
   padding: 20px;
diff --git a/src/views/followvisit/record/detailpage/index.vue b/src/views/followvisit/record/detailpage/index.vue
index c5945bb..0525fec 100644
--- a/src/views/followvisit/record/detailpage/index.vue
+++ b/src/views/followvisit/record/detailpage/index.vue
@@ -1013,6 +1013,18 @@
         <el-button type="primary" @click="setupsubtask">纭鍒涘缓鏈嶅姟</el-button>
       </div>
     </el-dialog>
+    <div class="main-content" v-if="orgname == '鏅畞鐣叉棌鑷不鍘夸汉姘戝尰闄�'">
+      <!-- <el-button @click="CaldialogVisible = true">鎵撳紑寮规</el-button> -->
+
+      <!-- 寮规璋冪敤 -->
+      <el-dialog
+        title="鍛煎彨鍔熻兘妗�"
+        :visible.sync="CaldialogVisible"
+        width="60%"
+      >
+        <CallCenterLs ref="callCenterModal" :initial-phone="currentPhoneNumber" />
+      </el-dialog>
+    </div>
   </div>
 </template>
 
@@ -1038,10 +1050,12 @@
 } from "@/api/patient/homepage";
 import CallButton from "@/components/CallButton";
 import MergeAndModify from "./MergeAndModify.vue";
+import CallCenterLs from "@/components/CallCenterLs";
 export default {
   components: {
     CallButton,
     MergeAndModify,
+    CallCenterLs,
   },
   directives: {
     numericOnly: {
@@ -1125,6 +1139,7 @@
       // 宸叉湁鏁版嵁...
       callStatus: "idle", // idle, calling, connected, ended, failed
       isEndingCall: false,
+      CaldialogVisible: false,
       currentCall: null, // 褰撳墠閫氳瘽瀵硅薄
       input: "浠婂ぉ韬綋杩樹笉閿�",
       radio: "2",
@@ -1673,8 +1688,13 @@
         this.$message.error("璇疯緭鍏ユ纭殑鎵嬫満鍙风爜");
         return;
       }
-
       this.currentPhoneNumber = phone;
+      // 鍛煎彨鍒ゆ柇
+      if (this.orgname == "鏅畞鐣叉棌鑷不鍘夸汉姘戝尰闄�") {
+        this.CaldialogVisible = true;
+        return
+      }
+
       this.callType = type;
       this.callStatus = "calling";
 
@@ -1721,28 +1741,28 @@
       }, 3000);
     },
     yuyingetdetail() {
-     const dataToSubmit = JSON.parse(JSON.stringify(this.tableDatatop));
+      const dataToSubmit = JSON.parse(JSON.stringify(this.tableDatatop));
 
-  dataToSubmit.forEach((item, index) => {
-    // 瀵规嫹璐濈殑鏁版嵁杩涜鎿嶄綔锛屼笉褰卞搷鍘熷鐨� scriptResult 鏁扮粍
-    item.scriptResult = item.scriptResult.join("&");
-    item.templatequestionnum = index + 1;
-    item.subId = this.id;
-    item.taskid = this.taskid;
-    item.asrtext = item.matchedtext;
-    if (!item.id) {
-      item.isoperation = 1;
-    }
-    item.patid = this.patid;
-    item.templateid = item.templateID;
-  });
+      dataToSubmit.forEach((item, index) => {
+        // 瀵规嫹璐濈殑鏁版嵁杩涜鎿嶄綔锛屼笉褰卞搷鍘熷鐨� scriptResult 鏁扮粍
+        item.scriptResult = item.scriptResult.join("&");
+        item.templatequestionnum = index + 1;
+        item.subId = this.id;
+        item.taskid = this.taskid;
+        item.asrtext = item.matchedtext;
+        if (!item.id) {
+          item.isoperation = 1;
+        }
+        item.patid = this.patid;
+        item.templateid = item.templateID;
+      });
 
-  let obj = {
-    serviceSubtaskDetailList: dataToSubmit, // 鎻愪氦澶勭悊鍚庣殑鍓湰
-    param1: this.taskid,
-    param2: this.patid,
-    subId: this.id,
-  };
+      let obj = {
+        serviceSubtaskDetailList: dataToSubmit, // 鎻愪氦澶勭悊鍚庣殑鍓湰
+        param1: this.taskid,
+        param2: this.patid,
+        subId: this.id,
+      };
 
       addPersonVoices(obj).then((res) => {
         if (res.code == 200) {
diff --git a/vue.config.js b/vue.config.js
index e6f390f..5f97768 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -37,8 +37,8 @@
       [process.env.VUE_APP_BASE_API]: {
         // target: `https://www.health-y.cn/lssf`,
         // target: `http://192.168.100.129:8095`,
-        target: `http://192.168.100.10:8096`,
-        // target:`http://localhost:8095`,
+        // target: `http://192.168.100.10:8096`,
+        target:`http://localhost:8095`,
         // target:`http://35z1t16164.qicp.vip`,
         // target: `http://192.168.100.193:8095`,
         // target: `http://192.168.101.166:8093`,

--
Gitblit v1.9.3