Эх сурвалжийг харах

设备智能管控页面重命名

zengy 2 өдөр өмнө
parent
commit
d443d1f2a2

+ 0 - 514
src/views/subSystem/pipeNetwork/pinteControl/PremoteControl.vue

@@ -1,514 +0,0 @@
-<!-- 终端设备远程测控 -->
-<template>
-  <div class="app-container remote-page" v-loading="loading">
-    <div class="hero-panel">
-      <div>
-        <div class="hero-title">终端设备远程测控</div>
-        <div class="hero-subtitle">基于区域控制、手动控制和事故联动预案,模拟值班中心对终端设备的远程控制管理</div>
-      </div>
-      <el-button type="primary" icon="Refresh" @click="loadDashboard">刷新区域</el-button>
-    </div>
-
-    <el-row :gutter="16">
-      <el-col :xs="24" :lg="6">
-        <el-card shadow="never" class="panel-card">
-          <template #header>
-            <div class="card-header-row">
-              <div class="card-title">区域控制器</div>
-              <el-tag>{{ regions.length }} 个区域</el-tag>
-            </div>
-          </template>
-
-          <div class="region-list">
-            <div
-              v-for="item in regions"
-              :key="item.id"
-              class="region-item"
-              :class="{ active: selectedRegionId === item.id }"
-              @click="selectRegion(item.id)"
-            >
-              <div class="region-name">{{ item.name }}</div>
-              <div class="region-meta">{{ item.location }}</div>
-              <div class="region-status">
-                <span class="status-dot" :class="item.controlMode === 'auto' ? 'dot-auto' : 'dot-manual'"></span>
-                {{ item.controlMode === "auto" ? "自动控制" : "手动控制" }}
-              </div>
-            </div>
-          </div>
-        </el-card>
-
-        <el-card v-if="currentRegion" shadow="never" class="panel-card">
-          <template #header>
-            <div class="card-title">自动控制参数</div>
-          </template>
-          <el-form label-width="80px" size="small">
-            <el-form-item label="上限阈值">
-              <el-input-number v-model="autoParams.upperThreshold" :step="0.1" style="width: 100%" />
-            </el-form-item>
-            <el-form-item label="下限阈值">
-              <el-input-number v-model="autoParams.lowerThreshold" :step="0.1" style="width: 100%" />
-            </el-form-item>
-            <el-form-item label="目标值">
-              <el-input-number v-model="autoParams.targetValue" :step="0.1" style="width: 100%" />
-            </el-form-item>
-            <el-form-item label="采样周期">
-              <el-input-number v-model="autoParams.samplePeriod" :step="1" style="width: 100%" />
-            </el-form-item>
-            <el-button type="primary" style="width: 100%" @click="saveAutoParams">保存参数</el-button>
-          </el-form>
-        </el-card>
-      </el-col>
-
-      <el-col :xs="24" :lg="11">
-        <el-card shadow="never" class="panel-card">
-          <template #header>
-            <div class="card-header-row">
-              <div>
-                <div class="card-title">中控远程手动控制</div>
-                <div class="card-subtitle">{{ currentRegion?.name || "请选择区域" }}</div>
-              </div>
-              <el-switch
-                v-model="manualMode"
-                active-text="手动模式"
-                inactive-text="自动模式"
-                @change="toggleControlMode"
-              />
-            </div>
-          </template>
-
-          <div v-if="currentDevices.length" class="device-list">
-            <div v-for="item in currentDevices" :key="item.id" class="device-item">
-              <div class="device-main">
-                <div class="device-name">{{ item.name }}</div>
-                <div class="device-meta">{{ item.type }} · {{ item.deviceId }}</div>
-                <div class="device-value">目标值:{{ item.currentValue }} {{ item.unit }}</div>
-              </div>
-              <div class="device-actions">
-                <el-tag :type="item.status === 'running' ? 'success' : 'info'">
-                  {{ item.status === "running" ? "运行中" : "已停止" }}
-                </el-tag>
-                <el-button type="success" plain size="small" :disabled="!manualMode || item.status === 'running'" @click="sendControlCommand(item, 'start')">
-                  启动
-                </el-button>
-                <el-button type="danger" plain size="small" :disabled="!manualMode || item.status === 'stopped'" @click="sendControlCommand(item, 'stop')">
-                  停止
-                </el-button>
-                <el-button size="small" :disabled="!manualMode" @click="openSetValueDialog(item)">设置参数</el-button>
-              </div>
-            </div>
-          </div>
-          <el-empty v-else description="当前区域暂无可控设备" />
-
-          <div class="quick-actions">
-            <el-button type="primary" plain :disabled="!manualMode" @click="quickCommand('全部启动')">全部启动</el-button>
-            <el-button type="danger" plain :disabled="!manualMode" @click="quickCommand('全部停止')">全部停止</el-button>
-            <el-button type="warning" plain :disabled="!manualMode" @click="quickCommand('紧急停止')">紧急停止</el-button>
-          </div>
-        </el-card>
-
-        <el-card shadow="never" class="panel-card">
-          <template #header>
-            <div class="card-title">设备运行方案</div>
-          </template>
-          <div class="scheme-list">
-            <div v-for="item in operationSchemes" :key="item.id" class="scheme-item">
-              <div class="scheme-name">{{ item.name }}</div>
-              <div class="scheme-desc">{{ item.description }}</div>
-              <div class="scheme-footer">
-                <span>设备数量:{{ item.deviceIds.length }}</span>
-                <el-button type="primary" link @click="applyScheme(item)">应用方案</el-button>
-              </div>
-            </div>
-          </div>
-        </el-card>
-      </el-col>
-
-      <el-col :xs="24" :lg="7">
-        <el-card shadow="never" class="panel-card">
-          <template #header>
-            <div class="card-title">事故联动控制预案</div>
-          </template>
-          <div class="plan-list">
-            <div v-for="item in incidentPlans" :key="item.id" class="plan-item">
-              <div class="plan-name">{{ item.name }}</div>
-              <div class="plan-desc">{{ item.description }}</div>
-              <div class="plan-devices">联动设备:{{ item.devices.map((d) => d.name).join("、") }}</div>
-              <el-button type="warning" plain size="small" @click="triggerIncident(item)">快速启动</el-button>
-            </div>
-          </div>
-        </el-card>
-
-        <el-card shadow="never" class="panel-card">
-          <template #header>
-            <div class="card-title">控制日志</div>
-          </template>
-          <div class="log-list">
-            <div v-for="item in controlLogs" :key="item.id" class="log-item">
-              <div class="log-time">{{ item.time }}</div>
-              <div class="log-action">{{ item.action }}</div>
-              <div class="log-target">{{ item.target }}</div>
-            </div>
-          </div>
-        </el-card>
-      </el-col>
-    </el-row>
-
-    <el-dialog title="设置设备参数" v-model="setValueOpen" width="420px" append-to-body>
-      <el-form label-width="90px">
-        <el-form-item label="设备名称">
-          <span>{{ currentSetDevice?.name || "-" }}</span>
-        </el-form-item>
-        <el-form-item label="目标值">
-          <el-input-number v-model="setValue" :step="0.1" style="width: 100%" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitSetValue">下发指令</el-button>
-          <el-button @click="setValueOpen = false">取 消</el-button>
-        </div>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-import { computed, onMounted, reactive, ref } from "vue";
-import { getMonitorPointDetail, listMonitorPointAll, listPipeNetworkAll } from "@/api/pipeNetwork/pdeviceInform";
-
-const loading = ref(false);
-const networkOptions = ref([]);
-const regions = ref([]);
-const selectedRegionId = ref();
-const manualMode = ref(false);
-const controlLogs = ref([]);
-const setValueOpen = ref(false);
-const currentSetDevice = ref();
-const setValue = ref(0);
-
-const autoParams = reactive({
-  upperThreshold: 1.2,
-  lowerThreshold: 0.3,
-  targetValue: 0.8,
-  samplePeriod: 10
-});
-
-const operationSchemes = ref([
-  { id: 1, name: "高峰供给方案", description: "高峰时段优先启用区域内主要设备", deviceIds: [] },
-  { id: 2, name: "低谷节能方案", description: "低谷时段保留关键设备,其余设备停机待命", deviceIds: [] },
-  { id: 3, name: "夜间值守方案", description: "仅维持核心设备运行,降低夜间能耗", deviceIds: [] }
-]);
-
-const incidentPlans = ref([
-  { id: 1, name: "泄漏事故联动", description: "快速停机并通知值班中心介入处理", devices: [] },
-  { id: 2, name: "压力异常联动", description: "立即停止相关设备,降低区域运行风险", devices: [] },
-  { id: 3, name: "养护维护预案", description: "进入维护模式,按计划逐项停机确认", devices: [] }
-]);
-
-const currentRegion = computed(() => {
-  return regions.value.find((item) => item.id === selectedRegionId.value);
-});
-
-const currentDevices = computed(() => {
-  return currentRegion.value?.devices || [];
-});
-
-function addLog(action, target) {
-  controlLogs.value.unshift({
-    id: Date.now() + Math.random(),
-    time: new Date().toLocaleTimeString(),
-    action,
-    target
-  });
-  controlLogs.value = controlLogs.value.slice(0, 10);
-}
-
-function buildRegions(networks, points, detailMap) {
-  return networks.map((network, index) => {
-    const relatedPoints = points.filter((item) => item.networkId === network.networkId);
-    const devices = relatedPoints.flatMap((point) => {
-      const detail = detailMap[point.pointId] || {};
-      return (detail.equipmentList || []).map((equipment, equipmentIndex) => ({
-        id: `${network.networkId}-${equipment.equipmentId}-${equipmentIndex}`,
-        equipmentId: equipment.equipmentId,
-        name: equipment.equipmentName,
-        deviceId: equipment.equipmentCode,
-        type: equipment.equipmentModel || "设备",
-        status: ["正常", "启用"].includes(point.status) ? "running" : "stopped",
-        unit: point.pointType?.includes("压力") ? "MPa" : point.pointType?.includes("流量") ? "m3/h" : "值",
-        currentValue: equipmentIndex + 1,
-        pointName: point.pointName
-      }));
-    });
-    return {
-      id: index + 1,
-      networkId: network.networkId,
-      name: network.networkName,
-      location: network.location || "未维护位置",
-      controlMode: index % 2 === 0 ? "auto" : "manual",
-      devices
-    };
-  });
-}
-
-function loadDashboard() {
-  loading.value = true;
-  return Promise.all([listPipeNetworkAll(), listMonitorPointAll()])
-    .then(async ([networkRes, pointRes]) => {
-      const networks = networkRes.data || [];
-      const points = pointRes.data || [];
-      networkOptions.value = networks;
-      const details = await Promise.all(points.map((item) => getMonitorPointDetail(item.pointId)));
-      const detailMap = {};
-      details.forEach((detail, index) => {
-        detailMap[points[index].pointId] = detail.data || {};
-      });
-      regions.value = buildRegions(networks, points, detailMap);
-      selectedRegionId.value = regions.value[0]?.id;
-
-      const allDeviceIds = regions.value.flatMap((item) => item.devices.map((device) => device.equipmentId));
-      operationSchemes.value = operationSchemes.value.map((item, index) => ({
-        ...item,
-        deviceIds: allDeviceIds.slice(0, Math.max(1, index + 1))
-      }));
-      incidentPlans.value = incidentPlans.value.map((item, index) => ({
-        ...item,
-        devices: regions.value.flatMap((region) => region.devices).slice(index, index + 2)
-      }));
-      addLog("刷新远程测控页面", `共加载 ${regions.value.length} 个区域`);
-    })
-    .finally(() => {
-      loading.value = false;
-    });
-}
-
-function selectRegion(id) {
-  selectedRegionId.value = id;
-  manualMode.value = currentRegion.value?.controlMode === "manual";
-  addLog("切换区域控制器", currentRegion.value?.name || "-");
-}
-
-function saveAutoParams() {
-  addLog("保存自动控制参数", `${currentRegion.value?.name || "-"} 阈值已更新`);
-}
-
-function toggleControlMode() {
-  if (!currentRegion.value) {
-    return;
-  }
-  currentRegion.value.controlMode = manualMode.value ? "manual" : "auto";
-  addLog("切换控制模式", `${currentRegion.value.name} -> ${manualMode.value ? "手动模式" : "自动模式"}`);
-}
-
-function sendControlCommand(device, command) {
-  if (!manualMode.value) {
-    addLog("控制拦截", "当前为自动模式,无法手动控制");
-    return;
-  }
-  device.status = command === "start" ? "running" : "stopped";
-  addLog(command === "start" ? "启动设备" : "停止设备", device.name);
-}
-
-function openSetValueDialog(device) {
-  currentSetDevice.value = device;
-  setValue.value = device.currentValue || 0;
-  setValueOpen.value = true;
-}
-
-function submitSetValue() {
-  if (currentSetDevice.value) {
-    currentSetDevice.value.currentValue = setValue.value;
-    addLog("下发参数设置", `${currentSetDevice.value.name} -> ${setValue.value}`);
-  }
-  setValueOpen.value = false;
-}
-
-function quickCommand(command) {
-  if (!manualMode.value) {
-    addLog("快捷指令拦截", "请先切换到手动模式");
-    return;
-  }
-  currentDevices.value.forEach((device) => {
-    device.status = command === "全部启动" ? "running" : "stopped";
-  });
-  addLog("执行快捷指令", command);
-}
-
-function applyScheme(scheme) {
-  const targetIds = new Set(scheme.deviceIds);
-  regions.value.forEach((region) => {
-    region.devices.forEach((device) => {
-      if (targetIds.has(device.equipmentId)) {
-        device.status = scheme.name.includes("节能") ? "stopped" : "running";
-      }
-    });
-  });
-  addLog("应用设备运行方案", scheme.name);
-}
-
-function triggerIncident(plan) {
-  plan.devices.forEach((device) => {
-    const found = regions.value.flatMap((item) => item.devices).find((item) => item.id === device.id);
-    if (found) {
-      found.status = "stopped";
-    }
-  });
-  addLog("启动事故联动预案", plan.name);
-}
-
-onMounted(() => {
-  loadDashboard();
-});
-</script>
-
-<style scoped>
-.remote-page {
-  background: linear-gradient(180deg, #f3f7fd 0%, #f8fafc 100%);
-}
-
-.hero-panel {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  gap: 16px;
-  margin-bottom: 16px;
-  padding: 20px 24px;
-  border-radius: 18px;
-  background: linear-gradient(135deg, #0f2742, #1d4f7d);
-  color: #fff;
-}
-
-.hero-title {
-  font-size: 24px;
-  font-weight: 700;
-}
-
-.hero-subtitle {
-  margin-top: 8px;
-  font-size: 13px;
-  color: rgba(255, 255, 255, 0.84);
-}
-
-.panel-card {
-  border-radius: 16px;
-  margin-bottom: 16px;
-}
-
-.card-header-row {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  gap: 12px;
-}
-
-.card-title {
-  font-size: 16px;
-  font-weight: 600;
-  color: #1f2937;
-}
-
-.card-subtitle {
-  margin-top: 4px;
-  font-size: 12px;
-  color: #6b7280;
-}
-
-.region-list,
-.scheme-list,
-.plan-list,
-.log-list,
-.device-list {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-}
-
-.region-item,
-.scheme-item,
-.plan-item,
-.log-item,
-.device-item {
-  padding: 12px;
-  border-radius: 12px;
-  background: #f8fafc;
-  border: 1px solid #edf2f7;
-}
-
-.region-item {
-  cursor: pointer;
-}
-
-.region-item.active {
-  border-color: #60a5fa;
-  background: #eef6ff;
-}
-
-.region-name,
-.device-name,
-.scheme-name,
-.plan-name,
-.log-action {
-  font-size: 14px;
-  font-weight: 600;
-  color: #111827;
-}
-
-.region-meta,
-.region-status,
-.device-meta,
-.device-value,
-.scheme-desc,
-.scheme-footer,
-.plan-desc,
-.plan-devices,
-.log-time,
-.log-target {
-  margin-top: 4px;
-  font-size: 12px;
-  color: #6b7280;
-}
-
-.status-dot {
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
-  display: inline-block;
-  margin-right: 6px;
-}
-
-.dot-auto {
-  background: #16a34a;
-}
-
-.dot-manual {
-  background: #2563eb;
-}
-
-.device-actions {
-  display: flex;
-  gap: 8px;
-  flex-wrap: wrap;
-  margin-top: 10px;
-}
-
-.quick-actions {
-  display: flex;
-  gap: 8px;
-  margin-top: 16px;
-  flex-wrap: wrap;
-}
-
-.scheme-footer {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-@media (max-width: 768px) {
-  .hero-panel,
-  .card-header-row {
-    flex-direction: column;
-    align-items: flex-start;
-  }
-}
-</style>

+ 0 - 1094
src/views/subSystem/pipeNetwork/pinteControl/PtelephoneorRadio.vue

@@ -1,1094 +0,0 @@
-<!-- 紧急电话或广播管理-->
-<template>
-  <div class="prototype-container">
-    <!-- 头部 -->
-    <div class="header">
-      <div class="logo">
-        <h2>📢 紧急电话与广播路侧分机监控系统</h2>
-        <p>设备状态实时监控 · 分区/全线广播 · 应急通信管理</p>
-      </div>
-      <div class="header-stats">
-        <div class="stat-item">
-          <span class="stat-value">{{ onlineCount }}</span>
-          <span class="stat-label">设备在线</span>
-        </div>
-        <div class="stat-item warning">
-          <span class="stat-value">{{ alarmCount }}</span>
-          <span class="stat-label">报警/故障</span>
-        </div>
-        <div class="stat-item">
-          <span class="stat-value">{{ activeCallCount }}</span>
-          <span class="stat-label">通话中</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="main-layout">
-      <!-- 左侧:设备图元监控面板 -->
-      <div class="left-panel">
-        <div class="panel-title">
-          📡 路侧分机设备状态
-          <span class="live-badge">● 实时监控</span>
-        </div>
-        <div class="device-grid">
-          <div
-              v-for="device in devices"
-              :key="device.id"
-              class="device-card"
-              :class="{
-              'status-online': device.status === 'online',
-              'status-calling': device.status === 'calling',
-              'status-broadcast': device.status === 'broadcast',
-              'status-fault': device.status === 'fault',
-              'status-alarm': device.status === 'alarm'
-            }"
-              @click="showDeviceDetail(device)"
-          >
-            <div class="device-icon" :class="{ 'pulse-animation': device.status === 'alarm' || device.status === 'calling' }">
-              <span v-if="device.type === 'emergency'">📞</span>
-              <span v-else>📢</span>
-            </div>
-            <div class="device-info">
-              <div class="device-name">{{ device.name }}</div>
-              <div class="device-location">{{ device.location }}</div>
-              <div class="device-status">
-                <span class="status-dot" :class="getStatusDotClass(device.status)"></span>
-                {{ getStatusText(device.status) }}
-              </div>
-              <div class="device-call-info" v-if="device.status === 'calling' || device.status === 'alarm'">
-                <div class="call-time" v-if="device.callTime">呼叫时间: {{ device.callTime }}</div>
-                <div class="answer-time" v-if="device.answerTime">接警时间: {{ device.answerTime }}</div>
-              </div>
-            </div>
-            <div class="device-actions">
-              <button v-if="device.status === 'calling'" @click.stop="answerCall(device)" class="btn-answer">接听</button>
-              <button v-if="device.status === 'calling'" @click.stop="rejectCall(device)" class="btn-reject">拒接</button>
-              <button @click.stop="startBroadcast(device)" class="btn-broadcast">📢 广播</button>
-            </div>
-            <div v-if="device.status === 'alarm' || device.status === 'fault'" class="alarm-wave">
-              <div class="wave"></div><div class="wave"></div><div class="wave"></div>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- 右侧:广播控制与呼叫记录 -->
-      <div class="right-panel">
-        <!-- 广播控制面板 -->
-        <div class="card">
-          <div class="card-header">
-            🎙️ 广播控制系统
-            <span class="broadcast-status" :class="{ active: isBroadcasting }">
-              {{ isBroadcasting ? '广播中' : '待机' }}
-            </span>
-          </div>
-          <div class="card-body">
-            <!-- 分区选择 -->
-            <div class="zone-section">
-              <div class="section-title">选择广播分区</div>
-              <div class="zone-buttons">
-                <button
-                    v-for="zone in zones"
-                    :key="zone.id"
-                    class="zone-btn"
-                    :class="{ active: selectedZones.includes(zone.id) }"
-                    @click="toggleZone(zone.id)"
-                >
-                  {{ zone.name }}
-                </button>
-                <button class="zone-btn select-all" @click="selectAllZones">全选</button>
-                <button class="zone-btn clear-all" @click="clearAllZones">清空</button>
-              </div>
-            </div>
-
-            <!-- 广播模式选择 -->
-            <div class="mode-section">
-              <div class="section-title">广播模式</div>
-              <div class="mode-buttons">
-                <button class="mode-btn" :class="{ active: broadcastMode === 'zone' }" @click="broadcastMode = 'zone'">
-                  🎯 分区广播
-                </button>
-                <button class="mode-btn" :class="{ active: broadcastMode === 'all' }" @click="broadcastMode = 'all'">
-                  🌐 全线广播
-                </button>
-                <button class="mode-btn" :class="{ active: broadcastMode === 'background' }" @click="broadcastMode = 'background'">
-                  🎵 背景音乐
-                </button>
-              </div>
-            </div>
-
-            <!-- 广播内容 -->
-            <div class="broadcast-section">
-              <div class="section-title">广播内容</div>
-              <div class="broadcast-input-area">
-                <textarea
-                    v-model="broadcastText"
-                    class="broadcast-input"
-                    placeholder="输入广播内容或选择音频文件..."
-                    rows="2"
-                ></textarea>
-                <div class="file-upload-area">
-                  <label class="upload-btn">
-                    📁 上传音频文件
-                    <input type="file" accept="audio/*" @change="handleFileUpload" hidden>
-                  </label>
-                  <span v-if="uploadedFileName" class="file-name">{{ uploadedFileName }}</span>
-                  <button @click="playPreview" class="preview-btn" :disabled="!uploadedFileName && !broadcastText">▶ 试听</button>
-                </div>
-              </div>
-            </div>
-
-            <!-- 广播控制按钮 -->
-            <div class="control-buttons">
-              <button @click="startBroadcastAll" class="btn-broadcast-start" :disabled="!canBroadcast">
-                📢 开始广播
-              </button>
-              <button @click="stopBroadcast" class="btn-broadcast-stop" :disabled="!isBroadcasting">
-                ⏹️ 停止广播
-              </button>
-            </div>
-
-            <!-- 快捷应急广播 -->
-            <div class="emergency-section">
-              <div class="section-title">应急广播快捷指令</div>
-              <div class="emergency-buttons">
-                <button @click="emergencyBroadcast('火灾警报')" class="emergency-btn fire">🔥 火灾警报</button>
-                <button @click="emergencyBroadcast('泄漏事故')" class="emergency-btn leak">💧 泄漏事故</button>
-                <button @click="emergencyBroadcast('紧急疏散')" class="emergency-btn evacuate">🚨 紧急疏散</button>
-                <button @click="emergencyBroadcast('天气预警')" class="emergency-btn weather">🌧️ 天气预警</button>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <!-- 呼叫记录与事件日志 -->
-        <div class="card">
-          <div class="card-header">
-            📋 呼叫记录与接警日志
-            <button @click="clearLogs" class="btn-clear">清空</button>
-          </div>
-          <div class="card-body">
-            <div class="call-log-list">
-              <div v-for="log in callLogs" :key="log.id" class="log-item" :class="log.type">
-                <div class="log-time">{{ log.time }}</div>
-                <div class="log-device">{{ log.deviceName }}</div>
-                <div class="log-location">{{ log.location }}</div>
-                <div class="log-action">{{ log.action }}</div>
-                <div class="log-duration" v-if="log.duration">{{ log.duration }}</div>
-              </div>
-              <div v-if="callLogs.length === 0" class="empty-log">暂无呼叫记录</div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <!-- 设备详情模态框 -->
-    <div v-if="detailModalVisible" class="modal-overlay" @click.self="closeDetailModal">
-      <div class="modal-content">
-        <h3>📟 设备详细信息</h3>
-        <div class="detail-row"><span class="detail-label">设备名称:</span>{{ currentDevice?.name }}</div>
-        <div class="detail-row"><span class="detail-label">设备ID:</span>{{ currentDevice?.deviceId }}</div>
-        <div class="detail-row"><span class="detail-label">安装位置:</span>{{ currentDevice?.location }}</div>
-        <div class="detail-row"><span class="detail-label">设备类型:</span>{{ currentDevice?.type === 'emergency' ? '紧急电话' : '广播分机' }}</div>
-        <div class="detail-row"><span class="detail-label">工作状态:</span>{{ getStatusText(currentDevice?.status) }}</div>
-        <div class="detail-row" v-if="currentDevice?.callTime"><span class="detail-label">呼叫时间:</span>{{ currentDevice?.callTime }}</div>
-        <div class="detail-row" v-if="currentDevice?.answerTime"><span class="detail-label">接警时间:</span>{{ currentDevice?.answerTime }}</div>
-        <div class="detail-row"><span class="detail-label">最后在线:</span>{{ currentDevice?.lastOnline || '-' }}</div>
-        <button @click="closeDetailModal" class="btn-primary" style="margin-top:16px;">关闭</button>
-      </div>
-    </div>
-
-    <!-- 隐藏音频播放器 -->
-    <audio ref="audioPlayer" style="display: none;"></audio>
-  </div>
-</template>
-
-<script setup>
-import { ref, computed, onMounted, onUnmounted } from 'vue'
-
-// ==================== 设备数据 ====================
-const devices = ref([
-  { id: 1, name: "紧急电话-001", deviceId: "ET-001", type: "emergency", location: "K2+300 东侧", zone: "zone_a", status: "online", lastOnline: "2025-05-08 14:23:12" },
-  { id: 2, name: "紧急电话-002", deviceId: "ET-002", type: "emergency", location: "K5+600 南侧", zone: "zone_a", status: "online", lastOnline: "2025-05-08 14:22:45" },
-  { id: 3, name: "广播分机-001", deviceId: "SP-001", type: "broadcast", location: "服务区东区", zone: "zone_b", status: "online", lastOnline: "2025-05-08 14:23:00" },
-  { id: 4, name: "紧急电话-003", deviceId: "ET-003", type: "emergency", location: "K12+800 隧道口", zone: "zone_b", status: "calling", callTime: "14:22:30", answerTime: null, lastOnline: "2025-05-08 14:22:30" },
-  { id: 5, name: "广播分机-002", deviceId: "SP-002", type: "broadcast", location: "收费站广场", zone: "zone_c", status: "broadcast", lastOnline: "2025-05-08 14:20:00" },
-  { id: 6, name: "紧急电话-004", deviceId: "ET-004", type: "emergency", location: "K20+500 西侧", zone: "zone_c", status: "alarm", callTime: "14:15:20", answerTime: "14:16:45", lastOnline: "2025-05-08 14:15:20" },
-  { id: 7, name: "广播分机-003", deviceId: "SP-003", type: "broadcast", location: "观景台", zone: "zone_d", status: "fault", lastOnline: "2025-05-08 13:50:22" },
-  { id: 8, name: "紧急电话-005", deviceId: "ET-005", type: "emergency", location: "K25+100 北侧", zone: "zone_d", status: "online", lastOnline: "2025-05-08 14:21:33" }
-])
-
-// 分区定义
-const zones = ref([
-  { id: "zone_a", name: "A区(东段)", devices: [1, 2] },
-  { id: "zone_b", name: "B区(隧道段)", devices: [3, 4] },
-  { id: "zone_c", name: "C区(收费站)", devices: [5, 6] },
-  { id: "zone_d", name: "D区(西段)", devices: [7, 8] }
-])
-
-// ==================== 广播状态 ====================
-const selectedZones = ref([])
-const broadcastMode = ref('zone') // zone, all, background
-const broadcastText = ref('')
-const uploadedFile = ref(null)
-const uploadedFileName = ref('')
-const isBroadcasting = ref(false)
-let broadcastInterval = null
-
-// ==================== 呼叫记录 ====================
-const callLogs = ref([
-  { id: 1, time: "14:22:30", deviceName: "紧急电话-003", location: "K12+800 隧道口", action: "呼入等待中", type: "incoming", duration: null },
-  { id: 2, time: "14:16:45", deviceName: "紧急电话-004", location: "K20+500 西侧", action: "接警处理", type: "answered", duration: "2分15秒" },
-  { id: 3, time: "14:10:22", deviceName: "广播分机-002", location: "收费站广场", action: "应急广播", type: "broadcast", duration: "30秒" },
-  { id: 4, time: "13:58:30", deviceName: "紧急电话-001", location: "K2+300 东侧", action: "测试通话", type: "test", duration: "45秒" }
-])
-
-// ==================== 计算属性 ====================
-const onlineCount = computed(() => devices.value.filter(d => d.status === 'online').length)
-const alarmCount = computed(() => devices.value.filter(d => d.status === 'alarm' || d.status === 'fault').length)
-const activeCallCount = computed(() => devices.value.filter(d => d.status === 'calling').length)
-
-const canBroadcast = computed(() => {
-  if (broadcastMode.value === 'zone') return selectedZones.value.length > 0 && (broadcastText.value.trim() || uploadedFile.value)
-  if (broadcastMode.value === 'all') return broadcastText.value.trim() || uploadedFile.value
-  if (broadcastMode.value === 'background') return uploadedFile.value
-  return false
-})
-
-// ==================== 辅助函数 ====================
-const getStatusText = (status) => {
-  const map = {
-    online: '设备在线',
-    calling: '对讲中',
-    broadcast: '广播中',
-    fault: '故障',
-    alarm: '报警中'
-  }
-  return map[status] || status
-}
-
-const getStatusDotClass = (status) => {
-  const map = {
-    online: 'dot-online',
-    calling: 'dot-calling',
-    broadcast: 'dot-broadcast',
-    fault: 'dot-fault',
-    alarm: 'dot-alarm'
-  }
-  return map[status] || 'dot-online'
-}
-
-// ==================== 设备交互 ====================
-const detailModalVisible = ref(false)
-const currentDevice = ref(null)
-
-const showDeviceDetail = (device) => {
-  currentDevice.value = device
-  detailModalVisible.value = true
-}
-
-const closeDetailModal = () => {
-  detailModalVisible.value = false
-  currentDevice.value = null
-}
-
-// 接听呼叫
-const answerCall = (device) => {
-  device.status = 'online'
-  device.answerTime = new Date().toLocaleTimeString()
-  addCallLog(device.name, device.location, '接警处理', 'answered')
-  alert(`已接听 ${device.name} 的呼叫,开始通话`)
-}
-
-// 拒接呼叫
-const rejectCall = (device) => {
-  device.status = 'online'
-  device.callTime = null
-  addCallLog(device.name, device.location, '未接来电', 'missed')
-  alert(`已拒绝 ${device.name} 的呼叫`)
-}
-
-// 单设备广播
-const startBroadcast = (device) => {
-  broadcastText.value = `【广播通知】${device.location} 请注意安全`
-  selectedZones.value = []
-  broadcastMode.value = 'zone'
-  // 找到设备所在分区
-  for (const zone of zones.value) {
-    if (zone.devices.includes(device.id)) {
-      selectedZones.value = [zone.id]
-      break
-    }
-  }
-  startBroadcastAll()
-}
-
-// ==================== 广播功能 ====================
-const toggleZone = (zoneId) => {
-  if (selectedZones.value.includes(zoneId)) {
-    selectedZones.value = selectedZones.value.filter(id => id !== zoneId)
-  } else {
-    selectedZones.value.push(zoneId)
-  }
-}
-
-const selectAllZones = () => {
-  selectedZones.value = zones.value.map(z => z.id)
-}
-
-const clearAllZones = () => {
-  selectedZones.value = []
-}
-
-// 处理文件上传
-const handleFileUpload = (event) => {
-  const file = event.target.files[0]
-  if (file) {
-    uploadedFile.value = file
-    uploadedFileName.value = file.name
-    // 创建预览URL
-    const url = URL.createObjectURL(file)
-    const audio = new Audio()
-    audio.src = url
-  }
-}
-
-// 播放预览
-const playPreview = () => {
-  if (uploadedFile.value) {
-    const url = URL.createObjectURL(uploadedFile.value)
-    const audio = new Audio(url)
-    audio.play().catch(e => console.log('播放失败'))
-  } else if (broadcastText.value) {
-    // 使用语音合成预览
-    const utterance = new SpeechSynthesisUtterance(broadcastText.value)
-    speechSynthesis.speak(utterance)
-  } else {
-    alert('请先输入广播内容或上传音频文件')
-  }
-}
-
-// 开始广播
-const startBroadcastAll = () => {
-  if (!canBroadcast.value) {
-    alert('请选择广播分区并输入内容或上传音频文件')
-    return
-  }
-
-  let targetDevices = []
-  if (broadcastMode.value === 'all') {
-    targetDevices = devices.value
-  } else if (broadcastMode.value === 'zone') {
-    const targetZoneIds = selectedZones.value
-    const targetDeviceIds = zones.value.filter(z => targetZoneIds.includes(z.id)).flatMap(z => z.devices)
-    targetDevices = devices.value.filter(d => targetDeviceIds.includes(d.id))
-  } else if (broadcastMode.value === 'background') {
-    targetDevices = devices.value.filter(d => d.type === 'broadcast')
-  }
-
-  isBroadcasting.value = true
-
-  // 更新设备广播状态
-  targetDevices.forEach(device => {
-    if (device.status === 'online') {
-      device.status = 'broadcast'
-    }
-  })
-
-  // 模拟广播播放
-  if (broadcastText.value) {
-    const utterance = new SpeechSynthesisUtterance(broadcastText.value)
-    utterance.onend = () => {
-      if (!isBroadcasting.value) return
-      // 广播结束后恢复状态
-      setTimeout(() => {
-        stopBroadcast()
-      }, 1000)
-    }
-    speechSynthesis.speak(utterance)
-  } else if (uploadedFile.value) {
-    const url = URL.createObjectURL(uploadedFile.value)
-    const audio = new Audio(url)
-    audio.onended = () => {
-      setTimeout(() => {
-        stopBroadcast()
-      }, 1000)
-    }
-    audio.play().catch(e => console.log('播放失败'))
-  }
-
-  // 记录广播日志
-  const zoneNames = broadcastMode.value === 'zone'
-      ? zones.value.filter(z => selectedZones.value.includes(z.id)).map(z => z.name).join(', ')
-      : (broadcastMode.value === 'all' ? '全线' : '背景音乐')
-
-  addCallLog('系统广播', zoneNames, `广播: ${broadcastText.value || uploadedFileName.value}`, 'broadcast')
-  alert(`广播已发送到${broadcastMode.value === 'zone' ? `分区: ${zoneNames}` : (broadcastMode.value === 'all' ? '全线' : '广播分机')}`)
-}
-
-// 停止广播
-const stopBroadcast = () => {
-  isBroadcasting.value = false
-  // 恢复设备状态
-  devices.value.forEach(device => {
-    if (device.status === 'broadcast') {
-      device.status = 'online'
-    }
-  })
-  if (broadcastInterval) {
-    clearInterval(broadcastInterval)
-    broadcastInterval = null
-  }
-}
-
-// 应急广播
-const emergencyBroadcast = (type) => {
-  const messages = {
-    '火灾警报': '【紧急广播】火灾警报,请所有人员迅速撤离至安全区域!',
-    '泄漏事故': '【紧急广播】泄漏事故警报,请所有人员远离泄漏区域!',
-    '紧急疏散': '【紧急广播】紧急疏散指令,请所有人员按照指示有序撤离!',
-    '天气预警': '【天气预警】恶劣天气即将到来,请注意安全防范!'
-  }
-  broadcastText.value = messages[type]
-  broadcastMode.value = 'all'
-  startBroadcastAll()
-}
-
-// ==================== 呼叫记录管理 ====================
-const addCallLog = (deviceName, location, action, type) => {
-  callLogs.value.unshift({
-    id: Date.now(),
-    time: new Date().toLocaleTimeString(),
-    deviceName: deviceName,
-    location: location,
-    action: action,
-    type: type,
-    duration: null
-  })
-  if (callLogs.value.length > 50) callLogs.value.pop()
-}
-
-const clearLogs = () => {
-  if (confirm('确定清空所有呼叫记录吗?')) {
-    callLogs.value = []
-  }
-}
-
-// ==================== 模拟实时设备状态更新 ====================
-let simulationInterval = null
-
-const simulateDeviceUpdates = () => {
-  // 模拟随机呼叫
-  if (!isBroadcasting.value && Math.random() < 0.05) {
-    const onlineDevices = devices.value.filter(d => d.status === 'online' && d.type === 'emergency')
-    if (onlineDevices.length > 0) {
-      const randomDevice = onlineDevices[Math.floor(Math.random() * onlineDevices.length)]
-      if (randomDevice.status === 'online') {
-        randomDevice.status = 'calling'
-        randomDevice.callTime = new Date().toLocaleTimeString()
-        randomDevice.answerTime = null
-        addCallLog(randomDevice.name, randomDevice.location, '呼入等待中', 'incoming')
-      }
-    }
-  }
-
-  // 自动接听模拟(20秒后自动接听)
-  const callingDevices = devices.value.filter(d => d.status === 'calling')
-  callingDevices.forEach(device => {
-    if (device.callTime && !device.answerTime) {
-      const callTime = new Date(`2000/01/01 ${device.callTime}`)
-      const now = new Date()
-      const diff = (now - callTime) / 1000
-      if (diff > 20) {
-        answerCall(device)
-      }
-    }
-  })
-}
-
-// ==================== 生命周期 ====================
-onMounted(() => {
-  simulationInterval = setInterval(simulateDeviceUpdates, 5000)
-})
-
-onUnmounted(() => {
-  if (simulationInterval) clearInterval(simulationInterval)
-  if (broadcastInterval) clearInterval(broadcastInterval)
-  speechSynthesis.cancel()
-})
-</script>
-
-<style scoped>
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-}
-
-.prototype-container {
-  max-width: 1600px;
-  margin: 0 auto;
-  background: #f0f4f8;
-  border-radius: 28px;
-  box-shadow: 0 20px 35px -12px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
-}
-
-/* 头部 */
-.header {
-  background: #0a2b3c;
-  padding: 16px 28px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 16px;
-  border-bottom: 3px solid #e74c3c;
-}
-
-.logo h2 {
-  color: white;
-  font-weight: 600;
-  font-size: 1.3rem;
-}
-
-.logo p {
-  color: #bdd8e8;
-  font-size: 0.7rem;
-}
-
-.header-stats {
-  display: flex;
-  gap: 20px;
-}
-
-.stat-item {
-  background: rgba(255, 255, 255, 0.15);
-  padding: 6px 16px;
-  border-radius: 40px;
-  text-align: center;
-  color: white;
-}
-
-.stat-item.warning {
-  background: #e74c3c;
-  animation: pulse 1s infinite;
-}
-
-.stat-value {
-  font-weight: 700;
-  font-size: 1.2rem;
-  margin-right: 6px;
-}
-
-.stat-label {
-  font-size: 0.7rem;
-  opacity: 0.9;
-}
-
-/* 主体布局 */
-.main-layout {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-.left-panel {
-  flex: 1.5;
-  background: #f9fbfd;
-  padding: 20px;
-  min-width: 500px;
-}
-
-.right-panel {
-  flex: 1;
-  background: #ffffff;
-  padding: 20px;
-  min-width: 400px;
-  border-left: 1px solid #e2edf2;
-}
-
-.panel-title {
-  font-size: 1rem;
-  font-weight: 700;
-  color: #1d5a78;
-  margin-bottom: 20px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.live-badge {
-  font-size: 0.7rem;
-  background: #e74c3c;
-  color: white;
-  padding: 4px 12px;
-  border-radius: 20px;
-  animation: blink 1s infinite;
-}
-
-/* 设备网格 */
-.device-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
-  gap: 16px;
-  max-height: 600px;
-  overflow-y: auto;
-}
-
-.device-card {
-  background: white;
-  border-radius: 20px;
-  padding: 16px;
-  display: flex;
-  gap: 12px;
-  position: relative;
-  cursor: pointer;
-  transition: all 0.2s;
-  border: 2px solid transparent;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
-}
-
-.device-card:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
-}
-
-.device-card.status-online {
-  border-left: 4px solid #27ae60;
-}
-
-.device-card.status-calling {
-  border-left: 4px solid #f39c12;
-  background: #fef9e8;
-  animation: callingPulse 1s infinite;
-}
-
-.device-card.status-broadcast {
-  border-left: 4px solid #3498db;
-  background: #e8f4fd;
-}
-
-.device-card.status-fault {
-  border-left: 4px solid #e74c3c;
-  background: #fef0ef;
-}
-
-.device-card.status-alarm {
-  border-left: 4px solid #e74c3c;
-  background: #ffefef;
-  animation: alarmPulse 0.8s infinite;
-}
-
-.device-icon {
-  font-size: 2rem;
-  width: 50px;
-  height: 50px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: #f0f4f8;
-  border-radius: 16px;
-}
-
-.device-icon.pulse-animation {
-  animation: iconPulse 0.6s infinite;
-}
-
-.device-info {
-  flex: 1;
-}
-
-.device-name {
-  font-weight: 700;
-  font-size: 0.85rem;
-}
-
-.device-location {
-  font-size: 0.65rem;
-  color: #6f8ea8;
-  margin: 2px 0;
-}
-
-.device-status {
-  font-size: 0.7rem;
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  margin-top: 4px;
-}
-
-.status-dot {
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
-  display: inline-block;
-}
-
-.dot-online { background: #27ae60; box-shadow: 0 0 4px #27ae60; }
-.dot-calling { background: #f39c12; box-shadow: 0 0 4px #f39c12; animation: blink 0.8s infinite; }
-.dot-broadcast { background: #3498db; box-shadow: 0 0 4px #3498db; }
-.dot-fault { background: #e74c3c; box-shadow: 0 0 4px #e74c3c; }
-.dot-alarm { background: #e74c3c; box-shadow: 0 0 8px #e74c3c; animation: blink 0.5s infinite; }
-
-.device-call-info {
-  font-size: 0.65rem;
-  color: #e67e22;
-  margin-top: 4px;
-}
-
-.device-actions {
-  display: flex;
-  flex-direction: column;
-  gap: 6px;
-}
-
-.btn-answer, .btn-reject, .btn-broadcast {
-  padding: 4px 10px;
-  border-radius: 20px;
-  font-size: 0.65rem;
-  border: none;
-  cursor: pointer;
-}
-
-.btn-answer { background: #27ae60; color: white; }
-.btn-reject { background: #e74c3c; color: white; }
-.btn-broadcast { background: #3498db; color: white; }
-
-.alarm-wave {
-  position: absolute;
-  top: 8px;
-  right: 8px;
-  display: flex;
-  gap: 3px;
-}
-
-.wave {
-  width: 4px;
-  height: 12px;
-  background: #e74c3c;
-  border-radius: 2px;
-  animation: waveAnim 0.6s infinite ease-in-out;
-}
-
-.wave:nth-child(2) { animation-delay: 0.2s; }
-.wave:nth-child(3) { animation-delay: 0.4s; }
-
-/* 卡片样式 */
-.card {
-  background: white;
-  border-radius: 20px;
-  border: 1px solid #e6edf3;
-  margin-bottom: 20px;
-  overflow: hidden;
-}
-
-.card-header {
-  background: #f9fafc;
-  padding: 14px 18px;
-  border-bottom: 1px solid #eef2f6;
-  font-weight: 700;
-  font-size: 0.85rem;
-  color: #1d5a78;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.card-body {
-  padding: 16px;
-}
-
-/* 广播区域 */
-.section-title {
-  font-size: 0.7rem;
-  font-weight: 600;
-  color: #2c5a72;
-  margin-bottom: 10px;
-}
-
-.zone-buttons {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 8px;
-  margin-bottom: 16px;
-}
-
-.zone-btn {
-  padding: 6px 14px;
-  border-radius: 30px;
-  border: 1px solid #cbdde6;
-  background: white;
-  font-size: 0.7rem;
-  cursor: pointer;
-  transition: 0.2s;
-}
-
-.zone-btn.active {
-  background: #3498db;
-  color: white;
-  border-color: #3498db;
-}
-
-.zone-btn.select-all {
-  background: #27ae60;
-  color: white;
-  border-color: #27ae60;
-}
-
-.zone-btn.clear-all {
-  background: #e74c3c;
-  color: white;
-  border-color: #e74c3c;
-}
-
-.mode-buttons {
-  display: flex;
-  gap: 12px;
-  flex-wrap: wrap;
-  margin-bottom: 16px;
-}
-
-.mode-btn {
-  padding: 6px 16px;
-  border-radius: 30px;
-  border: 1px solid #cbdde6;
-  background: white;
-  font-size: 0.7rem;
-  cursor: pointer;
-}
-
-.mode-btn.active {
-  background: #3498db;
-  color: white;
-  border-color: #3498db;
-}
-
-.broadcast-input-area {
-  margin-bottom: 16px;
-}
-
-.broadcast-input {
-  width: 100%;
-  padding: 10px;
-  border-radius: 12px;
-  border: 1px solid #cbdde6;
-  font-size: 0.75rem;
-  resize: vertical;
-}
-
-.file-upload-area {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-  margin-top: 10px;
-  flex-wrap: wrap;
-}
-
-.upload-btn {
-  background: #3498db;
-  color: white;
-  padding: 6px 14px;
-  border-radius: 30px;
-  font-size: 0.7rem;
-  cursor: pointer;
-}
-
-.file-name {
-  font-size: 0.65rem;
-  color: #6f8ea8;
-}
-
-.preview-btn {
-  background: #f39c12;
-  color: white;
-  border: none;
-  padding: 6px 14px;
-  border-radius: 30px;
-  font-size: 0.7rem;
-  cursor: pointer;
-}
-
-.control-buttons {
-  display: flex;
-  gap: 12px;
-  margin-bottom: 20px;
-}
-
-.btn-broadcast-start {
-  background: #27ae60;
-  color: white;
-  border: none;
-  padding: 10px 20px;
-  border-radius: 30px;
-  font-weight: 600;
-  cursor: pointer;
-  flex: 1;
-}
-
-.btn-broadcast-start:disabled {
-  background: #95a5a6;
-  cursor: not-allowed;
-}
-
-.btn-broadcast-stop {
-  background: #e74c3c;
-  color: white;
-  border: none;
-  padding: 10px 20px;
-  border-radius: 30px;
-  font-weight: 600;
-  cursor: pointer;
-  flex: 1;
-}
-
-.emergency-buttons {
-  display: flex;
-  gap: 10px;
-  flex-wrap: wrap;
-}
-
-.emergency-btn {
-  padding: 8px 16px;
-  border-radius: 30px;
-  border: none;
-  font-size: 0.7rem;
-  font-weight: 600;
-  cursor: pointer;
-  color: white;
-}
-
-.emergency-btn.fire { background: #e74c3c; }
-.emergency-btn.leak { background: #f39c12; }
-.emergency-btn.evacuate { background: #e67e22; }
-.emergency-btn.weather { background: #3498db; }
-
-.broadcast-status {
-  font-size: 0.65rem;
-  padding: 2px 10px;
-  border-radius: 20px;
-  background: #95a5a6;
-  color: white;
-}
-
-.broadcast-status.active {
-  background: #27ae60;
-  animation: blink 1s infinite;
-}
-
-/* 呼叫记录 */
-.call-log-list {
-  max-height: 300px;
-  overflow-y: auto;
-}
-
-.log-item {
-  display: flex;
-  gap: 12px;
-  padding: 10px 0;
-  border-bottom: 1px solid #f0f4f7;
-  font-size: 0.7rem;
-  flex-wrap: wrap;
-}
-
-.log-item.incoming { background: #fef9e8; margin: 0 -8px; padding: 8px 8px; border-radius: 8px; }
-.log-item.answered { background: #e8f8f0; margin: 0 -8px; padding: 8px 8px; border-radius: 8px; }
-.log-item.broadcast { background: #e8f4fd; margin: 0 -8px; padding: 8px 8px; border-radius: 8px; }
-
-.log-time { color: #6f8ea8; width: 70px; font-family: monospace; }
-.log-device { font-weight: 600; min-width: 100px; }
-.log-location { color: #2c5a72; flex: 1; }
-.log-action { color: #3498db; }
-.log-duration { color: #27ae60; }
-
-.empty-log {
-  text-align: center;
-  padding: 40px;
-  color: #8aaec2;
-  font-size: 0.8rem;
-}
-
-.btn-clear {
-  background: none;
-  border: none;
-  color: #e74c3c;
-  font-size: 0.65rem;
-  cursor: pointer;
-}
-
-/* 模态框 */
-.modal-overlay {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0, 0, 0, 0.5);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 1000;
-}
-
-.modal-content {
-  background: white;
-  border-radius: 24px;
-  width: 450px;
-  max-width: 90%;
-  padding: 24px;
-}
-
-.detail-row {
-  margin-bottom: 12px;
-  font-size: 0.75rem;
-}
-
-.detail-label {
-  font-weight: 600;
-  width: 100px;
-  display: inline-block;
-  color: #2c5a72;
-}
-
-.btn-primary {
-  background: #3498db;
-  border: none;
-  color: white;
-  padding: 8px 16px;
-  border-radius: 30px;
-  cursor: pointer;
-  width: 100%;
-}
-
-/* 动画 */
-@keyframes pulse {
-  0%, 100% { opacity: 1; }
-  50% { opacity: 0.7; }
-}
-
-@keyframes blink {
-  0%, 100% { opacity: 1; }
-  50% { opacity: 0.4; }
-}
-
-@keyframes callingPulse {
-  0%, 100% { background: #fef9e8; border-left-color: #f39c12; }
-  50% { background: #fef0d8; border-left-color: #e67e22; }
-}
-
-@keyframes alarmPulse {
-  0%, 100% { background: #ffefef; border-left-color: #e74c3c; }
-  50% { background: #ffe0e0; border-left-color: #c0392b; }
-}
-
-@keyframes iconPulse {
-  0%, 100% { transform: scale(1); }
-  50% { transform: scale(1.05); background: #ffe0e0; }
-}
-
-@keyframes waveAnim {
-  0%, 100% { height: 8px; opacity: 0.5; }
-  50% { height: 16px; opacity: 1; }
-}
-</style>

+ 0 - 0
src/views/subSystem/pipeNetwork/pinteControl/PrealTimeShow.vue → src/views/subSystem/pipeNetwork/pinteControl/realTimeShow.vue