From de147dda682f8ac597bbcc8555b57acbdf45dba2 Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期四, 13 十一月 2025 16:55:51 +0800
Subject: [PATCH] 测试完成

---
 src/components/CallCenterLs/index.vue |  717 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 717 insertions(+), 0 deletions(-)

diff --git a/src/components/CallCenterLs/index.vue b/src/components/CallCenterLs/index.vue
new file mode 100644
index 0000000..1ffc805
--- /dev/null
+++ b/src/components/CallCenterLs/index.vue
@@ -0,0 +1,717 @@
+<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="handleSeatLogout"
+              :class="[
+                'seat-btn',
+                'logout',
+                { 'in-call': isInCall || isCalling },
+              ]"
+              :disabled="!canLogout"
+            >
+              <span class="btn-icon">馃毆</span>
+              {{ isInCall || isCalling ? "寮哄埗绛惧嚭" : "绛惧嚭" }}
+            </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";
+    },
+    canLogout() {
+      // 鍙湁鍦ㄥ凡绛惧叆鐘舵�佹墠鍏佽绛惧嚭锛堟棤璁烘槸鍚﹀湪閫氳瘽涓級
+      return this.isSeatLoggedIn && !this.isSeatLoggingOut;
+    },
+    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();
+    },
+    async handleSeatLogout() {
+      if (!this.canLogout) return;
+
+      try {
+        // 濡傛灉姝e湪閫氳瘽涓紝鍏堟寕鏂�
+        if (this.isInCall || this.isCalling) {
+          // 鏄剧ず纭瀵硅瘽妗�
+          const confirm = await this.$confirm(
+            "纭畾瑕佺鍑哄悧锛熺鍑哄皢缁撴潫褰撳墠閫氳瘽",
+            "鎻愮ず",
+            {
+              confirmButtonText: "纭畾",
+              cancelButtonText: "鍙栨秷",
+              type: "warning",
+            }
+          );
+          await this.handleHangup();
+        }
+        console.log(2);
+
+        // 鎵ц绛惧嚭
+        await this.$refs.callComponent.handleSeatLogout();
+        console.log(3);
+        this.isSeatLoggedIn = false;
+
+        this.$message.success("搴у腑绛惧嚭鎴愬姛");
+      } catch (error) {
+        if (error !== "cancel") {
+          // 蹇界暐鐢ㄦ埛鍙栨秷鐨勬儏鍐�
+          console.error("绛惧嚭澶辫触:", error);
+          this.$message.error("搴у腑绛惧嚭澶辫触");
+        }
+      }
+    },
+    handleHangup() {
+      this.$refs.callComponent.hangup();
+      this.stopCallTimer();
+      this.callStatus = "idle";
+    },
+
+    onSeatStatusChange(status) {
+      this.isSeatLoggedIn = status.isLoggedIn;
+
+      // 濡傛灉搴у腑绛惧嚭锛岄噸缃�氳瘽鐘舵��
+      if (!status.isLoggedIn) {
+        this.callStatus = "idle";
+        this.stopCallTimer();
+      }
+    },
+
+    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();
+
+    // 缁勪欢閿�姣佸墠灏濊瘯绛惧嚭
+    if (this.isSeatLoggedIn) {
+      this.handleSeatLogout().catch(console.error);
+    }
+  },
+};
+</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;
+  }
+}
+
+.seat-btn.logout {
+  background: linear-gradient(135deg, #f97316, #ea580c);
+  color: white;
+
+  &:hover:not(:disabled) {
+    box-shadow: 0 6px 16px rgba(249, 115, 22, 0.4);
+  }
+
+  // 閫氳瘽涓殑鐗规畩鏍峰紡
+  &.in-call {
+    background: linear-gradient(135deg, #ef4444, #dc2626);
+    animation: pulse 1.5s infinite;
+
+    &:hover:not(:disabled) {
+      box-shadow: 0 6px 16px rgba(239, 68, 68, 0.4);
+    }
+  }
+}
+
+// 閫氳瘽涓鍑烘寜閽殑鐗规畩鍔ㄧ敾
+@keyframes pulse-red {
+  0% {
+    box-shadow: 0 4px 12px rgba(239, 68, 68, 0.5);
+  }
+  50% {
+    box-shadow: 0 4px 20px rgba(239, 68, 68, 0.8);
+  }
+  100% {
+    box-shadow: 0 4px 12px rgba(239, 68, 68, 0.5);
+  }
+}
+.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>

--
Gitblit v1.9.3