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