<template>
  <div class="uvm-detail">
    <div class="page-header">
      <div class="header-left">
        <el-button link @click="goBack"><el-icon><ArrowLeft /></el-icon>返回列表</el-button>
        <el-divider direction="vertical" />
        <span class="page-title">用户虚拟机详情</span>
      </div>
      <el-button :icon="Refresh" plain @click="loadDetail" :loading="loading">刷新</el-button>
    </div>

    <div class="main-content" v-loading="loading">
      <!-- 概览卡片 -->
      <el-card shadow="never" class="overview-card" v-if="userGoods">
        <div class="overview-header">
          <div class="overview-left">
            <el-icon :size="44" color="#409eff"><Monitor /></el-icon>
            <div class="overview-info">
              <div class="name-row">
                <h2 class="vm-name">{{ vm?.name || userGoods.good?.name || `用户虚拟机 #${userGoodsId}` }}</h2>
                <el-tag v-if="vm?.status" :type="vmStatusType(vm.status)" size="small" style="margin-left:8px">{{ vmStatusLabel(vm.status) }}</el-tag>
                <el-tag v-if="vm?.rescue" size="small" type="danger" effect="dark" style="margin-left:4px">救援模式</el-tag>
                <el-tag v-if="userGoods.tag" size="small" type="info" style="margin-left:4px">{{ userGoods.tag }}</el-tag>
              </div>
              <div class="meta-row">
                <span>用户商品ID: <b>{{ userGoods.id }}</b></span>
                <el-divider direction="vertical" />
                <span>虚拟机ID: <b>{{ userGoods.itemId || '-' }}</b></span>
                <el-divider direction="vertical" />
                <span>用户ID: <b>{{ userGoods.userId }}</b></span>
                <el-divider direction="vertical" />
                <span>到期: <b>{{ formatExpireTime(userGoods.expireTime) }}</b></span>
              </div>
            </div>
          </div>
          <div class="overview-actions">
            <el-button v-if="isVmGoods" size="small" type="primary" @click="handleVnc">VNC</el-button>
            <el-button v-if="isVmGoods" size="small" type="success" @click="handlePower('start')" :disabled="vm?.status === 'running'">启动</el-button>
            <el-button v-if="isVmGoods" size="small" type="warning" @click="handlePower('reboot')">重启</el-button>
            <el-button v-if="isVmGoods" size="small" type="danger" @click="handlePower('stop')" :disabled="vm?.status === 'stopped' || vm?.status === 'stop'">关机</el-button>
            <el-dropdown v-if="isVmGoods" trigger="click" @command="handleMoreCmd">
              <el-button size="small">更多<el-icon class="el-icon--right"><ArrowDown /></el-icon></el-button>
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item command="suspend">暂停</el-dropdown-item>
                  <el-dropdown-item command="resume">恢复</el-dropdown-item>
                  <el-dropdown-item command="rescue" :disabled="!!vm?.rescue">救援模式</el-dropdown-item>
                  <el-dropdown-item command="exitRescue" :disabled="!vm?.rescue">退出救援</el-dropdown-item>
                  <el-dropdown-item divided command="rebuild">重装系统</el-dropdown-item>
                  <el-dropdown-item command="updateVm">编辑虚拟机</el-dropdown-item>
                  <el-dropdown-item command="refactorVm">重构虚拟机</el-dropdown-item>
                  <el-dropdown-item command="updateTraffic">修改带宽</el-dropdown-item>
                  <el-dropdown-item divided command="migrate">迁移</el-dropdown-item>
                  <el-dropdown-item command="transfer">转移用户</el-dropdown-item>
                  <el-dropdown-item divided command="editGoods">编辑商品信息</el-dropdown-item>
                  <el-dropdown-item divided command="delete" style="color:#f56c6c">删除</el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
          </div>
        </div>
        <el-divider style="margin:12px 0 8px" />
        <!-- VM 配置信息 -->
        <div class="vm-config-grid" v-if="vm">
          <div class="config-row">
            <div class="config-cell"><span class="config-label">vCPU</span><span class="config-value">{{ vm.vcpu || '-' }} 核</span></div>
            <div class="config-cell"><span class="config-label">内存</span><span class="config-value">{{ formatMemory(vm.memory) }}</span></div>
            <div class="config-cell"><span class="config-label">下行带宽</span><span class="config-value">{{ vm.rx_bandwidth || 0 }} Mbps</span></div>
            <div class="config-cell"><span class="config-label">上行带宽</span><span class="config-value">{{ vm.tx_bandwidth || 0 }} Mbps</span></div>
          </div>
          <div class="config-row">
            <div class="config-cell"><span class="config-label">用户名</span><span class="config-value" style="font-weight:500">{{ isWindows ? 'Administrator' : 'root' }}</span></div>
            <div class="config-cell"><span class="config-label">远程端口</span><span class="config-value">{{ isWindows ? (vm.ssh_port && vm.ssh_port !== 22 ? vm.ssh_port : 3389) : (vm.ssh_port || 22) }}</span></div>
            <div class="config-cell">
              <span class="config-label">外网IP</span>
              <span class="config-value ip-value" v-if="vmPublicIpList.length">
                <span class="ip-main" style="color:#165dff">{{ vmPublicIpList[0] }}</span>
                <el-popover v-if="vmPublicIpList.length > 1" trigger="hover" placement="bottom" :width="360" popper-class="ip-popover-panel">
                  <template #reference>
                    <el-tag size="small" type="primary" class="ip-more-tag">+{{ vmPublicIpList.length - 1 }}</el-tag>
                  </template>
                  <div class="ip-popover-header">
                    <span>全部外网IP（{{ vmPublicIpList.length }}）</span>
                    <el-button link type="primary" size="small" @click="copyAllIps(vmPublicIpList)">复制全部</el-button>
                  </div>
                  <div class="ip-popover-list">
                    <div v-for="(ip, idx) in vmPublicIpList" :key="idx" class="ip-popover-item">
                      <span class="ip-text">{{ ip }}</span>
                      <el-button link type="primary" size="small" class="ip-copy-btn" @click="copyText(ip)">复制</el-button>
                    </div>
                  </div>
                </el-popover>
              </span>
              <span class="config-value" v-else>-</span>
            </div>
            <div class="config-cell">
              <span class="config-label">内网IP</span>
              <span class="config-value ip-value" v-if="vmPrivateIpList.length">
                <span class="ip-main" style="color:#67c23a">{{ vmPrivateIpList[0] }}</span>
                <el-popover v-if="vmPrivateIpList.length > 1" trigger="hover" placement="bottom" :width="360" popper-class="ip-popover-panel">
                  <template #reference>
                    <el-tag size="small" type="success" class="ip-more-tag">+{{ vmPrivateIpList.length - 1 }}</el-tag>
                  </template>
                  <div class="ip-popover-header">
                    <span>全部内网IP（{{ vmPrivateIpList.length }}）</span>
                    <el-button link type="primary" size="small" @click="copyAllIps(vmPrivateIpList)">复制全部</el-button>
                  </div>
                  <div class="ip-popover-list">
                    <div v-for="(ip, idx) in vmPrivateIpList" :key="idx" class="ip-popover-item">
                      <span class="ip-text">{{ ip }}</span>
                      <el-button link type="primary" size="small" class="ip-copy-btn" @click="copyText(ip)">复制</el-button>
                    </div>
                  </div>
                </el-popover>
              </span>
              <span class="config-value" v-else>-</span>
            </div>
          </div>
          <div class="config-row">
            <div class="config-cell">
              <span class="config-label">Root密码</span>
              <span class="config-value pwd-value">
                <code class="pwd-text">{{ showPassword ? (vm.root_password || '-') : '••••••••' }}</code>
                <el-button link size="small" @click="showPassword = !showPassword" class="pwd-btn">
                  <el-icon :size="14"><View v-if="!showPassword" /><Hide v-else /></el-icon>
                </el-button>
                <el-button link size="small" type="primary" @click="copyPassword" class="pwd-btn">
                  <el-icon :size="14"><CopyDocument /></el-icon>
                </el-button>
              </span>
            </div>
            <div class="config-cell">
              <span class="config-label">流量上限</span>
              <span class="config-value">
                {{ formatTraffic(vm.traffic_max) }}
                <el-button link type="primary" size="small" class="cfg-edit-btn" @click="handleCommand('updateTraffic')">
                  <el-icon :size="14"><Edit /></el-icon>修改
                </el-button>
              </span>
            </div>
            <div class="config-cell"><span class="config-label">续费价格</span><span class="config-value">¥{{ (userGoods.renewPrice / 100).toFixed(2) }}</span></div>
            <div class="config-cell"><span class="config-label">基础价格</span><span class="config-value">¥{{ (userGoods.basePrice / 100).toFixed(2) }}</span></div>
          </div>
          <div class="config-row">
            <div class="config-cell"><span class="config-label">商品</span><span class="config-value">{{ userGoods.good?.name || '-' }}</span></div>
            <div class="config-cell"><span class="config-label">备注</span><span class="config-value">{{ userGoods.note || '-' }}</span></div>
            <div class="config-cell">
              <span class="config-label">入站安全组</span>
              <span class="config-value">
                <el-tag v-if="inPortGroup" size="small" type="success">{{ inPortGroup.name }} (ID:{{ inPortGroup.id }})</el-tag>
                <span v-else style="color:#c0c4cc">未绑定</span>
              </span>
            </div>
            <div class="config-cell">
              <span class="config-label">出站安全组</span>
              <span class="config-value">
                <el-tag v-if="outPortGroup" size="small" type="warning">{{ outPortGroup.name }} (ID:{{ outPortGroup.id }})</el-tag>
                <span v-else style="color:#c0c4cc">未绑定</span>
              </span>
            </div>
            <div class="config-cell" v-if="vmImage">
              <span class="config-label">镜像</span>
              <span class="config-value">{{ vmImage.name }} <el-tag size="small" :type="vmImage.os_type === 'linux' ? 'success' : 'primary'" style="margin-left:4px">{{ vmImage.os_type }}</el-tag></span>
            </div>
          </div>
        </div>
        <el-descriptions :column="3" border size="small" v-else>
          <el-descriptions-item label="商品">{{ userGoods.good?.name || '-' }}</el-descriptions-item>
          <el-descriptions-item label="商品标签">{{ userGoods.tag || userGoods.good?.tag || '-' }}</el-descriptions-item>
          <el-descriptions-item label="套餐ID">{{ userGoods.goodPlanId || '-' }}</el-descriptions-item>
          <el-descriptions-item label="续费价格">¥{{ (userGoods.renewPrice / 100).toFixed(2) }}</el-descriptions-item>
          <el-descriptions-item label="基础价格">¥{{ (userGoods.basePrice / 100).toFixed(2) }}</el-descriptions-item>
          <el-descriptions-item label="订单">{{ userGoods.order?.name || (userGoods.orderId ? `订单 #${userGoods.orderId}` : '-') }}</el-descriptions-item>
          <el-descriptions-item label="创建时间">{{ formatTime(userGoods.CreatedAt) }}</el-descriptions-item>
          <el-descriptions-item label="更新时间">{{ formatTime(userGoods.UpdatedAt) }}</el-descriptions-item>
          <el-descriptions-item label="备注">{{ userGoods.note || '-' }}</el-descriptions-item>
          <el-descriptions-item label="操作" :span="3">
            <el-button size="small" type="primary" plain @click="openEditGoods">编辑商品信息</el-button>
            <el-button size="small" type="danger" plain @click="handleMoreCmd('delete')" style="margin-left:8px">删除</el-button>
          </el-descriptions-item>
        </el-descriptions>
      </el-card>

      <!-- 标签页 -->
      <el-card shadow="never" class="tabs-card" v-if="userGoods && isVmGoods">
        <el-tabs v-model="activeTab" @tab-change="handleTabChange">

          <!-- 数据卷 -->
          <el-tab-pane v-if="isVmGoods" label="数据卷" name="volume">
            <div class="tab-toolbar">
              <el-button size="small" type="primary" @click="showVolumeSelector = true">挂载数据卷</el-button>
              <el-button size="small" :icon="Refresh" @click="loadDetail">刷新</el-button>
            </div>
            <el-table :data="vmVolumes" stripe size="small">
              <el-table-column prop="id" label="ID" width="70" />
              <el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip />
              <el-table-column label="大小" width="100">
                <template #default="{ row }">{{ row.size ? (row.size + ' GB') : '-' }}</template>
              </el-table-column>
              <el-table-column label="类型" width="80">
                <template #default="{ row }"><el-tag :type="row.is_system ? 'danger' : ''" size="small">{{ row.is_system ? '系统盘' : '数据盘' }}</el-tag></template>
              </el-table-column>
              <el-table-column label="状态" width="90">
                <template #default="{ row }"><el-tag :type="volumeStatusType(row.status)" size="small">{{ volumeStatusLabel(row.status) }}</el-tag></template>
              </el-table-column>
              <el-table-column label="挂载" width="90">
                <template #default="{ row }">
                  <el-tag :type="isVolumeMounted(row) ? 'success' : 'info'" size="small">{{ isVolumeMounted(row) ? '已挂载' : '未挂载' }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="path" label="路径" min-width="200" show-overflow-tooltip><template #default="{ row }"><span style="font-family:monospace;font-size:12px">{{ row.path || '-' }}</span></template></el-table-column>
              <el-table-column label="操作" width="220" fixed="right">
                <template #default="{ row }">
                  <el-button link type="primary" size="small" @click="handleResizeVolume(row)">扩容</el-button>
                  <el-button link type="success" size="small" @click="handleMountVolume(row)" v-if="!isVolumeMounted(row)">挂载</el-button>
                  <el-button link type="warning" size="small" @click="handleUnmountVolume(row)" v-if="isVolumeMounted(row)">卸载</el-button>
                  <el-button link type="danger" size="small" @click="handleDeleteVolume(row)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-if="!vmVolumes.length" :image-size="60" description="暂无数据卷" />
          </el-tab-pane>

          <!-- 快照 -->
          <el-tab-pane v-if="isVmGoods" label="快照" name="snapshot">
            <div class="tab-toolbar">
              <el-tag v-if="snapshotQuota" size="small" effect="plain">{{ snapshotQuota.count || 0}} / {{ snapshotQuota.limit }}</el-tag>
              <el-button size="small" @click="handleSetSnapshotLimit">设置上限</el-button>
              <el-button size="small" type="primary" @click="handleCreateSnapshot">创建快照</el-button>
              <el-button size="small" :icon="Refresh" @click="loadSnapshots">刷新</el-button>
            </div>
            <el-table :data="snapshots" v-loading="snapshotLoading" stripe size="small">
              <el-table-column prop="id" label="ID" width="70" />
              <el-table-column prop="name" label="名称" min-width="140" />
              <el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="taskStatusType(row.status)" size="small">{{ row.status || '-' }}</el-tag></template></el-table-column>
              <el-table-column label="操作" width="180" fixed="right">
                <template #default="{ row }">
                  <el-button link type="primary" size="small" @click="handleRestoreSnapshot(row)">恢复</el-button>
                  <el-button link type="info" size="small" @click="handleSnapshotProgress(row)" v-if="row.status === 'running' || row.status === 'pending'">进度</el-button>
                  <el-button link type="danger" size="small" @click="handleDeleteSnapshot(row)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-if="!snapshots.length && !snapshotLoading" :image-size="60" description="暂无快照" />
          </el-tab-pane>

          <!-- 备份 -->
          <el-tab-pane v-if="isVmGoods" label="备份" name="backup">
            <div class="tab-toolbar">
              <el-tag v-if="backupQuota" size="small" effect="plain">{{ backupQuota.count || 0 }} / {{ backupQuota.limit }}</el-tag>
              <el-button size="small" @click="handleSetBackupLimit">设置上限</el-button>
              <el-button size="small" type="primary" @click="handleCreateBackup">创建备份</el-button>
              <el-button size="small" :icon="Refresh" @click="loadBackups">刷新</el-button>
            </div>
            <el-table :data="backups" v-loading="backupLoading" stripe size="small">
              <el-table-column prop="id" label="ID" width="70" />
              <el-table-column prop="name" label="名称" min-width="140" />
              <el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="taskStatusType(row.status)" size="small">{{ row.status || '-' }}</el-tag></template></el-table-column>
              <el-table-column label="操作" width="180" fixed="right">
                <template #default="{ row }">
                  <el-button link type="primary" size="small" @click="handleRestoreBackup(row)">恢复</el-button>
                  <el-button link type="info" size="small" @click="handleBackupProgress(row)" v-if="row.status === 'running' || row.status === 'pending'">进度</el-button>
                  <el-button link type="danger" size="small" @click="handleDeleteBackup(row)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-if="!backups.length && !backupLoading" :image-size="60" description="暂无备份" />
          </el-tab-pane>

          <!-- 安全组 -->
          <el-tab-pane v-if="isVmGoods" label="安全组" name="security">
            <div class="tab-toolbar">
              <el-button size="small" type="primary" @click="showSgBindSelector = true">绑定安全组</el-button>
              <el-button size="small" :icon="Refresh" @click="async () => { await loadDetail(); loadSgLockInfo() }">刷新</el-button>
            </div>
            <el-table :data="inPortGroupList" stripe size="small">
              <el-table-column prop="id" label="ID" width="70" />
              <el-table-column prop="name" label="名称" min-width="140" />
              <el-table-column label="方向" width="80">
                <template #default="{ row }"><el-tag :type="row.direction === 'in' ? 'success' : 'warning'" size="small">{{ row.direction === 'in' ? '入站' : '出站' }}</el-tag></template>
              </el-table-column>
              <el-table-column label="白名单" width="80">
                <template #default="{ row }"><el-tag :type="row.drop_all ? 'warning' : 'info'" size="small">{{ row.drop_all ? '开启' : '关闭' }}</el-tag></template>
              </el-table-column>
              <el-table-column label="锁定" width="70">
                <template #default="{ row }"><el-tag :type="row.lock ? 'danger' : 'info'" size="small">{{ row.lock ? '是' : '否' }}</el-tag></template>
              </el-table-column>
              <el-table-column label="应用时间" min-width="160">
                <template #default="{ row }">{{ row.apply_time ? formatTime(row.apply_time) : '-' }}</template>
              </el-table-column>
              <el-table-column label="操作" width="320" fixed="right">
                <template #default="{ row }">
                  <el-button link type="primary" size="small" @click="handleSgDetail(row)">规则</el-button>
                  <el-button link type="info" size="small" @click="handleEditSg(row)">编辑</el-button>
                  <el-button link type="warning" size="small" @click="handleApplySg(row)">应用</el-button>
                  <el-button link type="primary" size="small" @click="handleSgWhitelist(row)">{{ row.drop_all ? '关闭白名单' : '开启白名单' }}</el-button>
                  <el-button link type="danger" size="small" @click="handleUnbindSg(row)">解绑</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-if="!inPortGroupList.length" :image-size="60" description="暂无绑定的安全组" />
          </el-tab-pane>

          <!-- 网络 -->
          <el-tab-pane v-if="isVmGoods" label="网络" name="network">
            <div class="tab-toolbar">
              <el-button size="small" type="primary" @click="showBindNetworkSelector = true">绑定网络</el-button>
              <el-button size="small" :icon="Refresh" @click="loadDetail">刷新</el-button>
            </div>
            <el-table :data="vmNetworks" stripe size="small">
              <el-table-column prop="id" label="ID" width="70" />
              <el-table-column prop="name" label="名称" min-width="120" />
              <el-table-column prop="address" label="地址(CIDR)" min-width="150" />
              <el-table-column prop="gateway" label="网关" min-width="120" />
              <el-table-column prop="mac_address" label="MAC" min-width="150" show-overflow-tooltip />
              <el-table-column label="类型" width="80"><template #default="{ row }"><el-tag :type="row.type === 'bridge' ? 'success' : 'warning'" size="small">{{ row.type === 'bridge' ? '网桥' : 'NAT' }}</el-tag></template></el-table-column>
              <el-table-column label="操作" width="90" fixed="right">
                <template #default="{ row }">
                  <el-button link type="danger" size="small" :loading="deletingNetworkId === row.id" @click="handleDeleteVmNetwork(row)">
                    <el-icon :size="14"><Delete /></el-icon>删除
                  </el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-if="!vmNetworks.length" :image-size="60" description="暂无网络" />
          </el-tab-pane>

          <!-- 组网 -->
          <el-tab-pane v-if="isVmGoods" label="组网" name="networking">
            <div class="tab-toolbar">
              <el-button size="small" type="primary" @click="handleCreateNetworking">创建组网</el-button>
              <el-button size="small" :icon="Refresh" @click="loadNetworkings">刷新</el-button>
            </div>
            <el-table :data="networkingRows" v-loading="networkingLoading" stripe size="small">
              <el-table-column label="组网" min-width="160">
                <template #default="{ row }">
                  <span>{{ row.name }} <span style="color:#909399">(ID: {{ row.id }})</span></span>
                </template>
              </el-table-column>
              <el-table-column label="状态" width="90">
                <template #default="{ row }">
                  <el-tag :type="row._joined ? 'success' : 'info'" size="small">{{ row._joined ? '已加入' : '未加入' }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="网桥" min-width="120">
                <template #default="{ row }">{{ row.bridge_name || '-' }}</template>
              </el-table-column>
              <el-table-column label="IP地址" min-width="160">
                <template #default="{ row }">
                  <span v-if="row._network" class="mono-text">{{ row._network.address || '-' }}</span>
                  <span v-else style="color:#c0c4cc">-</span>
                </template>
              </el-table-column>
              <el-table-column label="网关" min-width="130">
                <template #default="{ row }">
                  <span class="mono-text">{{ row._network?.gateway || row.gateway || '-' }}</span>
                </template>
              </el-table-column>
              <el-table-column label="操作" width="120" fixed="right">
                <template #default="{ row }">
                  <el-button v-if="!row._joined" link type="primary" size="small" @click="handleJoinNetworking(row)">加入</el-button>
                  <el-button v-else link type="danger" size="small" @click="handleLeaveNetworking(row)">移除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-if="!networkings.length && !networkingLoading" :image-size="60" description="暂无组网" />
            <div class="pagination-wrapper" v-if="networkingTotal > 0">
              <el-pagination v-model:current-page="networkingPage" v-model:page-size="networkingPageSize" :page-sizes="[10,20]" :total="networkingTotal" layout="total,sizes,prev,pager,next" small
                @size-change="s => { networkingPageSize = s; networkingPage = 1; loadNetworkings() }" @current-change="p => { networkingPage = p; loadNetworkings() }" />
            </div>
          </el-tab-pane>

          <!-- 监控 -->
          <el-tab-pane v-if="isVmGoods" label="监控" name="monitor">
            <div class="section-block">
              <div class="section-header">
                <h3 class="section-title">监控指标</h3>
                <div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
                  <el-date-picker
                    v-model="monitorDateRange"
                    type="datetimerange"
                    range-separator="至"
                    start-placeholder="开始时间"
                    end-placeholder="结束时间"
                    size="small"
                    style="width: 360px"
                    :shortcuts="monitorShortcuts"
                    @change="loadMetricsHistory"
                  />
                  <span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
                  <el-button size="small" :icon="Refresh" @click="loadMetricsHistory" :loading="metricsLoading">刷新</el-button>
                </div>
              </div>
              <template v-if="latestMetrics">
                <div class="metric-summary-row">
                  <div class="metric-summary-card">
                    <div class="metric-summary-label">CPU 使用率</div>
                    <div class="metric-summary-value">{{ latestMetrics.cpu_usage?.toFixed(1) }}%</div>
                    <div class="metric-summary-sub">&nbsp;</div>
                  </div>
                  <div class="metric-summary-card">
                    <div class="metric-summary-label">内存</div>
                    <div class="metric-summary-value">{{ vmMemPercent(latestMetrics) }}%</div>
                    <div class="metric-summary-sub">{{ formatMemKB(latestMetrics.mem_used) }} / {{ formatMemKB(latestMetrics.mem_total) }}</div>
                  </div>
                  <div class="metric-summary-card">
                    <div class="metric-summary-label">磁盘 I/O</div>
                    <div class="metric-summary-value">读 {{ formatBytesRaw(latestMetrics.disk_read) }}</div>
                    <div class="metric-summary-sub">写 {{ formatBytesRaw(latestMetrics.disk_write) }}</div>
                  </div>
                  <div class="metric-summary-card">
                    <div class="metric-summary-label">网络流量</div>
                    <div class="metric-summary-value">↓{{ formatNetLabel(latestMetrics.net_rx) }}</div>
                    <div class="metric-summary-sub">↑{{ formatNetLabel(latestMetrics.net_tx) }}</div>
                  </div>
                </div>
              </template>
              <template v-if="metricsData">
                <el-row :gutter="16">
                  <el-col :span="12">
                    <el-card shadow="hover" class="metrics-card">
                      <template #header><span class="metrics-title"><el-icon><Monitor /></el-icon> CPU 使用率</span></template>
                      <div ref="cpuChartRef" class="chart-container"></div>
                    </el-card>
                  </el-col>
                  <el-col :span="12">
                    <el-card shadow="hover" class="metrics-card">
                      <template #header><span class="metrics-title"><el-icon><Monitor /></el-icon> 内存使用率</span></template>
                      <div ref="memChartRef" class="chart-container"></div>
                    </el-card>
                  </el-col>
                </el-row>
                <el-row :gutter="16" style="margin-top: 16px">
                  <el-col :span="12">
                    <el-card shadow="hover" class="metrics-card">
                      <template #header><span class="metrics-title"><el-icon><Monitor /></el-icon> 磁盘 I/O</span></template>
                      <div ref="diskChartRef" class="chart-container"></div>
                    </el-card>
                  </el-col>
                  <el-col :span="12">
                    <el-card shadow="hover" class="metrics-card">
                      <template #header><span class="metrics-title"><el-icon><Monitor /></el-icon> 网络流量</span></template>
                      <div ref="netChartRef" class="chart-container"></div>
                    </el-card>
                  </el-col>
                </el-row>
              </template>
              <el-empty v-else-if="!metricsLoading" description="暂无监控数据" :image-size="80" />
            </div>
          </el-tab-pane>

          <!-- 流量策略 -->
          <el-tab-pane v-if="isVmGoods" label="流量策略" name="trafficPolicy">
            <div class="section-block">
              <div class="section-header">
                <h3 class="section-title">流量策略</h3>
                <div style="display:flex;gap:8px">
                  <el-button size="small" type="primary" @click="openTrafficPolicyDialog">修改流量策略</el-button>
                  <el-button size="small" type="success" @click="openAddTrafficDialog('fixed')">增加固定流量</el-button>
                  <el-button size="small" type="warning" @click="openAddTrafficDialog('temporary')">增加临时流量</el-button>
                  <el-button size="small" :icon="Refresh" @click="loadTrafficPolicy" :loading="trafficPolicyLoading">刷新</el-button>
                </div>
              </div>
              <el-descriptions v-if="trafficPolicy" :column="3" border size="small" style="margin-top:12px">
                <el-descriptions-item label="流量上限">{{ trafficPolicy.traffic_max_mb != null ? (trafficPolicy.traffic_max_mb === 0 ? '不限' : trafficPolicy.traffic_max_mb + ' MB') : '-' }}</el-descriptions-item>
                <el-descriptions-item label="耗尽下行限速">{{ trafficPolicy.exhausted_rx_mbps != null ? (trafficPolicy.exhausted_rx_mbps === 0 ? '不限' : trafficPolicy.exhausted_rx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
                <el-descriptions-item label="耗尽上行限速">{{ trafficPolicy.exhausted_tx_mbps != null ? (trafficPolicy.exhausted_tx_mbps === 0 ? '不限' : trafficPolicy.exhausted_tx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
              </el-descriptions>
              <el-empty v-else-if="!trafficPolicyLoading" description="暂无流量策略数据" :image-size="60" />
            </div>
          </el-tab-pane>

        </el-tabs>
      </el-card>
    </div>

    <!-- 弹窗 -->
    <!-- VNC -->
    <el-dialog v-model="vncVisible" title="VNC连接" width="480px" destroy-on-close class="vnc-dialog">
      <div v-loading="vncLoading">
        <el-descriptions :column="1" border v-if="vncResult">
          <el-descriptions-item label="VNC地址">
            <el-link type="primary" :href="vncResult.url" target="_blank" class="vnc-url-link">{{ vncResult.url }}</el-link>
          </el-descriptions-item>
          <el-descriptions-item label="过期时间">{{ formatVncExpire(vncResult.expire_at) }}</el-descriptions-item>
        </el-descriptions>
        <el-empty v-else-if="!vncLoading" description="获取失败" :image-size="60" />
      </div>
      <template #footer>
        <el-button @click="vncVisible = false">关闭</el-button>
        <el-button v-if="vncResult?.url" type="primary" @click="openVncUrl">打开连接</el-button>
      </template>
    </el-dialog>

    <!-- 电源操作 -->
    <el-dialog v-model="powerVisible" :title="`${powerLabels[powerAction]}虚拟机`" width="400px" destroy-on-close>
      <div style="display:flex;align-items:flex-start;gap:12px;padding:8px 0">
        <el-icon :size="22" :style="{ color: powerAction === 'stop' ? '#F56C6C' : powerAction === 'reboot' ? '#E6A23C' : '#409EFF', flexShrink: 0, marginTop: '0px' }">
          <WarningFilled />
        </el-icon>
        <div>
          <div style="font-size:15px;font-weight:500;color:#303133">
            确定要{{ powerLabels[powerAction] }}虚拟机「{{ vm?.name || userGoods?.good?.name }}」吗？
          </div>
        </div>
      </div>
      <template #footer>
        <el-button @click="powerVisible = false">取消</el-button>
        <el-button :type="powerAction === 'stop' ? 'danger' : 'primary'" :loading="actionLoading" @click="submitPower">确定{{ powerLabels[powerAction] }}</el-button>
      </template>
    </el-dialog>

    <!-- 创建数据卷 -->
    <el-dialog v-model="volCreateVisible" title="创建数据卷" width="440px" destroy-on-close>
      <el-form :model="volCreateForm" label-width="100px">
        <el-form-item label="名称" required><el-input v-model="volCreateForm.name" /></el-form-item>
        <el-form-item label="大小">
          <div class="unit-input-row">
            <el-input-number v-model="volCreateForm.size" :min="1" controls-position="right" style="flex:1" />
            <span class="unit-text">GB</span>
          </div>
        </el-form-item>
        <el-form-item label="目标设备名"><el-input v-model="volCreateForm.target_device" placeholder="不填自动生成" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="volCreateVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitCreateVolume">确定</el-button>
      </template>
    </el-dialog>

    <!-- 扩容数据卷 -->
    <el-dialog v-model="volResizeVisible" title="扩容数据卷" width="400px" destroy-on-close>
      <el-form label-width="100px">
        <el-form-item label="当前大小">{{ volResizeTarget?.size || 0 }} GB</el-form-item>
        <el-form-item label="新大小">
          <div class="unit-input-row">
            <el-input-number v-model="volNewSize" :min="volResizeTarget?.size || 1" controls-position="right" style="flex:1" />
            <span class="unit-text">GB</span>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="volResizeVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitResizeVolume">确定</el-button>
      </template>
    </el-dialog>

    <!-- 创建快照 -->
    <el-dialog v-model="snapshotCreateVisible" title="创建快照" width="400px" destroy-on-close>
      <el-form label-width="90px">
        <el-form-item label="快照名称" required><el-input v-model="snapshotForm.name" /></el-form-item>
        <el-form-item label="描述"><el-input v-model="snapshotForm.description" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="snapshotCreateVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitCreateSnapshot">创建</el-button>
      </template>
    </el-dialog>

    <!-- 创建备份 -->
    <el-dialog v-model="backupCreateVisible" title="创建备份" width="400px" destroy-on-close>
      <el-form label-width="90px">
        <el-form-item label="备份名称" required><el-input v-model="backupForm.name" /></el-form-item>
        <el-form-item label="描述"><el-input v-model="backupForm.description" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="backupCreateVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitCreateBackup">创建</el-button>
      </template>
    </el-dialog>

    <!-- 创建安全组 -->
    <el-dialog v-model="sgCreateVisible" title="创建安全组" width="440px" destroy-on-close>
      <el-form :model="sgCreateForm" label-width="90px">
        <el-form-item label="名称" required><el-input v-model="sgCreateForm.name" /></el-form-item>
        <el-form-item label="方向">
          <el-select v-model="sgCreateForm.direction" style="width:100%">
            <el-option label="入站 (in)" value="in" /><el-option label="出站 (out)" value="out" />
          </el-select>
        </el-form-item>
        <el-form-item label="锁定"><el-switch v-model="sgCreateForm.lock" /></el-form-item>
        <el-form-item label="白名单"><el-switch v-model="sgCreateForm.drop_all" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="sgCreateVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitCreateSg">创建</el-button>
      </template>
    </el-dialog>

    <!-- 创建组网 -->
    <el-dialog v-model="networkingCreateVisible" title="创建组网" width="440px" destroy-on-close>
      <el-form :model="networkingCreateForm" label-width="90px">
        <el-form-item label="名称"><el-input v-model="networkingCreateForm.name" placeholder="不填则随机生成" /></el-form-item>
        <el-form-item label="网桥名称"><el-input v-model="networkingCreateForm.bridge_name" placeholder="不填则随机生成" /></el-form-item>
        <el-form-item label="网关"><el-input v-model="networkingCreateForm.gateway" placeholder="可选" /></el-form-item>
        <el-form-item label="描述"><el-input v-model="networkingCreateForm.description" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="networkingCreateVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitCreateNetworking">创建</el-button>
      </template>
    </el-dialog>

    <!-- 分配IP -->
    <el-dialog v-model="assignVisible" title="加入组网" width="400px" destroy-on-close>
      <el-form label-width="90px">
        <el-form-item label="组网">{{ assignTarget?.name }}</el-form-item>
        <el-form-item label="指定IP"><el-input v-model="assignIp" placeholder="留空自动分配" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="assignVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitAssign">分配</el-button>
      </template>
    </el-dialog>

    <!-- 重装系统 -->
    <el-dialog v-model="rebuildVisible" title="重装系统" width="480px" destroy-on-close>
      <el-alert type="warning" :closable="false" style="margin-bottom:16px">重装会清除当前系统数据，请谨慎操作！</el-alert>
      <el-form label-width="80px">
        <el-form-item label="镜像" required>
          <div class="selector-row">
            <el-input :model-value="rebuildImageName || (rebuildImageId ? `镜像 #${rebuildImageId}` : '')"
              readonly placeholder="请选择镜像" style="flex:1" />
            <el-button type="primary" @click="showRebuildImageSelector = true" style="margin-left:8px">选择</el-button>
            <el-button v-if="rebuildImageId" @click="rebuildImageId = 0; rebuildImageName = ''" style="margin-left:4px">清除</el-button>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="rebuildVisible = false">取消</el-button>
        <el-button type="danger" :loading="actionLoading" :disabled="!rebuildImageId" @click="submitRebuild">确定重装</el-button>
      </template>
    </el-dialog>

    <!-- 重装镜像选择器 -->
    <el-dialog v-model="showRebuildImageSelector" title="选择镜像" width="640px" append-to-body destroy-on-close>
      <div class="selector-toolbar">
        <el-button :icon="Refresh" @click="loadRebuildImages" :loading="rebuildImagesLoading">刷新</el-button>
        <span style="color:#909399;font-size:13px">仅显示宿主机上已同步完成的镜像</span>
      </div>
      <el-table :data="rebuildImages" v-loading="rebuildImagesLoading" highlight-current-row
        @current-change="row => rebuildSelectedImage = row" :height="320" stripe size="small">
        <el-table-column prop="id" label="ID" width="70" />
        <el-table-column prop="name" label="名称" min-width="200" show-overflow-tooltip />
        <el-table-column label="系统类型" width="100">
          <template #default="{ row }"><el-tag :type="row.os_type === 'linux' ? 'success' : 'primary'" size="small">{{ row.os_type }}</el-tag></template>
        </el-table-column>
        <el-table-column label="类型" width="80">
          <template #default="{ row }"><el-tag size="small">{{ row.type === 'system' ? '系统' : '数据' }}</el-tag></template>
        </el-table-column>
      </el-table>
      <el-empty v-if="!rebuildImages.length && !rebuildImagesLoading" :image-size="60" description="暂无可用镜像" />
      <template #footer>
        <el-button @click="showRebuildImageSelector = false">取消</el-button>
        <el-button type="primary" :disabled="!rebuildSelectedImage" @click="confirmRebuildImage">确定选择</el-button>
      </template>
    </el-dialog>

    <!-- 修改带宽 -->
    <el-dialog v-model="trafficVisible" title="修改带宽" width="440px" destroy-on-close>
      <el-form :model="trafficForm" label-width="100px">
        <el-form-item label="下行带宽">
          <div class="unit-input-row">
            <el-input-number v-model="trafficForm.rx_bandwidth" :min="0" controls-position="right" style="flex:1" />
            <el-select v-model="trafficForm._rxUnit" class="unit-select" style="width:100px">
              <el-option label="Mbps" value="Mbps" /><el-option label="Gbps" value="Gbps" />
            </el-select>
          </div>
        </el-form-item>
        <el-form-item label="上行带宽">
          <div class="unit-input-row">
            <el-input-number v-model="trafficForm.tx_bandwidth" :min="0" controls-position="right" style="flex:1" />
            <el-select v-model="trafficForm._txUnit" class="unit-select" style="width:100px">
              <el-option label="Mbps" value="Mbps" /><el-option label="Gbps" value="Gbps" />
            </el-select>
          </div>
        </el-form-item>
        <el-form-item label="流量上限">
          <div class="unit-input-row">
            <el-input-number v-model="trafficForm._trafficValue" :min="0" :precision="2" controls-position="right" style="flex:1" />
            <el-select v-model="trafficForm._trafficUnit" class="unit-select" style="width:100px">
              <el-option label="MB" value="MB" /><el-option label="GB" value="GB" /><el-option label="TB" value="TB" />
            </el-select>
          </div>
        </el-form-item>
        <el-form-item label="耗尽下行限速">
          <div class="unit-input-row">
            <el-input-number v-model="trafficForm.traffic_exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
            <span class="unit-text" style="margin-left:8px">Mbps（0 不限）</span>
          </div>
        </el-form-item>
        <el-form-item label="耗尽上行限速">
          <div class="unit-input-row">
            <el-input-number v-model="trafficForm.traffic_exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
            <span class="unit-text" style="margin-left:8px">Mbps（0 不限）</span>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="trafficVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitUpdateTraffic">确定</el-button>
      </template>
    </el-dialog>

    <!-- 流量策略管理 -->
    <el-dialog v-model="trafficPolicyVisible" title="修改流量策略" width="440px" destroy-on-close>
      <el-form :model="trafficPolicyForm" label-width="120px">
        <el-form-item label="流量上限">
          <div class="unit-input-row">
            <el-input-number v-model="trafficPolicyForm.traffic_max_mb" :min="0" controls-position="right" style="flex:1" />
            <span class="unit-text" style="margin-left:8px">MB（0 不限）</span>
          </div>
        </el-form-item>
        <el-form-item label="耗尽下行限速">
          <div class="unit-input-row">
            <el-input-number v-model="trafficPolicyForm.exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
            <span class="unit-text" style="margin-left:8px">Mbps（0 不限）</span>
          </div>
        </el-form-item>
        <el-form-item label="耗尽上行限速">
          <div class="unit-input-row">
            <el-input-number v-model="trafficPolicyForm.exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
            <span class="unit-text" style="margin-left:8px">Mbps（0 不限）</span>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="trafficPolicyVisible = false">取消</el-button>
        <el-button type="primary" :loading="trafficPolicyLoading" @click="submitUpdateTrafficPolicy">确定</el-button>
      </template>
    </el-dialog>

    <!-- 增加固定/临时流量 -->
    <el-dialog v-model="addTrafficVisible" :title="addTrafficType === 'fixed' ? '增加固定流量上限' : '增加一次性临时流量'" width="400px" destroy-on-close>
      <el-form :model="addTrafficForm" label-width="110px">
        <el-form-item label="流量数量">
          <div class="unit-input-row">
            <el-input-number v-model="addTrafficForm.traffic_mb" :min="1" controls-position="right" style="flex:1" />
            <span class="unit-text" style="margin-left:8px">MB</span>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="addTrafficVisible = false">取消</el-button>
        <el-button type="primary" :loading="trafficPolicyLoading" @click="submitAddTraffic">确定</el-button>
      </template>
    </el-dialog>

    <!-- 转移用户 -->
    <el-dialog v-model="transferVisible" title="转移虚拟机" width="440px" destroy-on-close>
      <el-form label-width="100px">
        <el-form-item label="目标用户">
          <div class="selector-row">
            <el-input :model-value="transferForm._userName || (transferForm.target_user_id ? `用户 #${transferForm.target_user_id}` : '')" readonly placeholder="请选择目标用户" style="flex:1" />
            <el-button type="primary" @click="showTransferUserSelector = true" style="margin-left:8px">选择</el-button>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="transferVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitTransfer">确定转移</el-button>
      </template>
    </el-dialog>
    <UserSelector v-model:visible="showTransferUserSelector" @select="u => { transferForm.target_user_id = u.user_id; transferForm._userName = u.user_name }" />

    <!-- 迁移虚拟机弹窗 -->
    <el-dialog v-model="migrateVisible" title="迁移虚拟机（更换宿主机）" width="560px" destroy-on-close>
      <el-alert type="warning" :closable="false" style="margin-bottom:16px">将虚拟机迁移到其他宿主机或宿主机组，请谨慎操作！</el-alert>
      <el-form label-width="120px" v-loading="migrateOptionsLoading">
        <el-form-item label="迁移方式">
          <el-radio-group v-model="migrateMode">
            <el-radio value="host">指定宿主机</el-radio>
            <el-radio value="group">指定宿主机组</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="目标宿主机" v-if="migrateMode === 'host'" required>
          <el-select v-model="migrateForm.target_host_id" placeholder="选择目标宿主机" filterable style="width:100%">
            <el-option v-for="h in migrateHostOptions" :key="h.id" :label="`${h.name} (${h.ip || h.id})`" :value="h.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="目标宿主机组" v-if="migrateMode === 'group'" required>
          <el-select v-model="migrateForm.target_host_group_id" placeholder="选择目标宿主机组" filterable style="width:100%">
            <el-option v-for="g in migrateGroupOptions" :key="g.id" :label="`${g.name} (ID: ${g.id})`" :value="g.id" />
          </el-select>
        </el-form-item>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="IPv4数量">
              <el-input-number v-model="migrateForm.ipv4_num" :min="0" controls-position="right" style="width:100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="IPv6数量">
              <el-input-number v-model="migrateForm.ipv6_num" :min="0" controls-position="right" style="width:100%" />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button @click="migrateVisible = false">取消</el-button>
        <el-button type="warning" :loading="actionLoading" @click="submitMigrateVm">确定迁移</el-button>
      </template>
    </el-dialog>
    <!-- 编辑安全组弹窗 -->
    <el-dialog v-model="sgEditVisible" title="编辑安全组" width="440px" destroy-on-close>
      <el-form :model="sgEditForm" label-width="90px">
        <el-form-item label="名称"><el-input v-model="sgEditForm.name" /></el-form-item>
        <el-form-item label="方向">
          <el-select v-model="sgEditForm.direction" style="width:100%">
            <el-option label="入站 (in)" value="in" /><el-option label="出站 (out)" value="out" />
          </el-select>
        </el-form-item>
        <el-form-item label="锁定"><el-switch v-model="sgEditForm.lock" /></el-form-item>
        <el-form-item label="白名单"><el-switch v-model="sgEditForm.drop_all" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="sgEditVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitEditSg">保存</el-button>
      </template>
    </el-dialog>

    <!-- 安全组规则管理弹窗 -->
    <el-dialog v-model="sgRuleVisible" :title="`安全组「${sgRuleTarget?.name}」规则管理`" width="760px" destroy-on-close>
      <div class="tab-toolbar" style="margin-bottom:12px">
        <el-button size="small" type="primary" @click="handleAddSgRule">新增规则</el-button>
        <el-button size="small" :icon="Refresh" @click="loadSgDetail(sgRuleTarget?.id)" :loading="sgDetailLoading">刷新</el-button>
        <el-button size="small" @click="handleSgWhitelist(sgRuleTarget)">{{ sgRuleTarget?.drop_all ? '关闭白名单' : '开启白名单' }}</el-button>
      </div>
      <el-table :data="sgRules" v-loading="sgDetailLoading" stripe size="small">
        <el-table-column prop="id" label="ID" width="70" />
        <el-table-column prop="protocol" label="协议" width="80"><template #default="{ row }"><el-tag size="small">{{ (row.protocol || '-').toUpperCase() }}</el-tag></template></el-table-column>
        <el-table-column label="动作" width="70"><template #default="{ row }"><el-tag :type="row.action === 'allow' ? 'success' : 'danger'" size="small">{{ row.action === 'allow' ? '允许' : '拒绝' }}</el-tag></template></el-table-column>
        <el-table-column prop="port_range" label="端口范围" min-width="100" />
        <el-table-column prop="ip_range" label="IP范围" min-width="130" />
        <el-table-column prop="priority" label="优先级" width="80" />
        <el-table-column label="操作" width="120" fixed="right">
          <template #default="{ row }">
            <el-button link type="primary" size="small" @click="handleEditSgRule(row)">编辑</el-button>
            <el-button link type="danger" size="small" @click="handleDeleteSgRule(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-empty v-if="!sgRules.length && !sgDetailLoading" :image-size="60" description="暂无规则" />
      <template #footer><el-button @click="sgRuleVisible = false">关闭</el-button></template>
    </el-dialog>

    <!-- 新增/编辑规则弹窗 -->
    <el-dialog v-model="sgRuleFormVisible" :title="sgRuleFormType === 'add' ? '新增规则' : '编辑规则'" width="480px" destroy-on-close>
      <el-form :model="sgRuleForm" label-width="90px">
        <el-form-item label="协议">
          <el-select v-model="sgRuleForm.protocol" style="width:100%">
            <el-option label="TCP" value="tcp" /><el-option label="UDP" value="udp" />
          </el-select>
        </el-form-item>
        <el-form-item label="动作">
          <el-select v-model="sgRuleForm.action" style="width:100%">
            <el-option label="允许 (allow)" value="allow" /><el-option label="拒绝 (deny)" value="deny" />
          </el-select>
        </el-form-item>
        <el-form-item label="端口范围"><el-input v-model="sgRuleForm.port_range" placeholder="如 80 或 80-90" /></el-form-item>
        <el-form-item label="IP范围"><el-input v-model="sgRuleForm.ip_range" placeholder="如 0.0.0.0/0" /></el-form-item>
        <el-form-item label="优先级"><el-input-number v-model="sgRuleForm.priority" :min="0" :max="9999" controls-position="right" style="width:100%" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="sgRuleFormVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitSgRule">确定</el-button>
      </template>
    </el-dialog>

    <!-- 绑定安全组选择器 -->
    <UserVmSecurityGroupSelector v-model="showSgBindSelector" :user-goods-id="userGoodsId"
      @confirm="sg => handleBindSg(sg)" />
    <!-- 挂载数据卷选择器 -->
    <UserVmVolumeSelector v-model="showVolumeSelector" :user-goods-id="userGoodsId"
      @confirm="vol => handleMountVolume(vol)" />

    <!-- 编辑虚拟机弹窗（对接 /user_vm/update） -->
    <el-dialog v-model="editVmVisible" title="编辑虚拟机配置" width="680px" destroy-on-close class="scrollable-dialog">
      <el-form :model="editVmForm" label-width="90px" v-loading="editVmLoading">
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="下行带宽">
              <div class="unit-input-row">
                <el-input-number v-model="editVmForm.rx_bandwidth" :min="0" controls-position="right" style="flex:1" />
                <el-select v-model="editVmForm._rxUnit" class="unit-select" style="width:100px">
                  <el-option label="Mbps" value="Mbps" /><el-option label="Gbps" value="Gbps" />
                </el-select>
              </div>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="上行带宽">
              <div class="unit-input-row">
                <el-input-number v-model="editVmForm.tx_bandwidth" :min="0" controls-position="right" style="flex:1" />
                <el-select v-model="editVmForm._txUnit" class="unit-select" style="width:100px">
                  <el-option label="Mbps" value="Mbps" /><el-option label="Gbps" value="Gbps" />
                </el-select>
              </div>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="Root密码">
          <el-input v-model="editVmForm.root_password" placeholder="留空则不修改" show-password />
        </el-form-item>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="SSH端口">
              <el-input-number v-model="editVmForm.ssh_port" :min="1" :max="65535" controls-position="right" style="width:100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="快照上限">
              <el-input-number v-model="editVmForm.snapshot_num" :min="0" controls-position="right" style="width:100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="备份上限">
              <el-input-number v-model="editVmForm.backup_num" :min="0" controls-position="right" style="width:100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="安全组">
          <div class="selector-row">
            <el-input :model-value="editVmForm._sgName || (editVmForm.port_group_id ? `安全组 #${editVmForm.port_group_id}` : '')"
              readonly placeholder="可选" style="flex:1" />
            <el-button type="primary" @click="showEditVmSgSelector = true" style="margin-left:8px">选择</el-button>
            <el-button v-if="editVmForm.port_group_id" @click="editVmForm.port_group_id = 0; editVmForm._sgName = ''" style="margin-left:4px">清除</el-button>
          </div>
        </el-form-item>
        <el-form-item label="公网网络">
          <div class="selector-row">
            <el-input :model-value="editVmForm._networkName || (editVmForm.internet_network_id ? `网络 #${editVmForm.internet_network_id}` : '')"
              readonly placeholder="可选，仅网桥类型" style="flex:1" />
            <el-button type="primary" @click="showEditVmNetSelector = true" style="margin-left:8px">选择</el-button>
            <el-button v-if="editVmForm.internet_network_id" @click="editVmForm.internet_network_id = 0; editVmForm._networkName = ''" style="margin-left:4px">清除</el-button>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="editVmVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitEditVm">保存</el-button>
      </template>
    </el-dialog>
    <UserVmSecurityGroupSelector v-model="showEditVmSgSelector" :user-goods-id="userGoodsId"
      @confirm="sg => { editVmForm.port_group_id = sg.id; editVmForm._sgName = sg.name }" />
    <UserVmNetworkSelector v-model="showEditVmNetSelector" :user-goods-id="userGoodsId"
      @confirm="net => { editVmForm.internet_network_id = net.id; editVmForm._networkName = net.name }" />

    <!-- 重构虚拟机弹窗 -->
    <el-dialog v-model="refactorVisible" title="重构虚拟机" width="720px" destroy-on-close class="scrollable-dialog">
      <el-alert type="warning" :closable="false" style="margin-bottom:12px">重构会修改虚拟机底层配置，请谨慎操作！</el-alert>
      <el-form :model="refactorForm" label-width="90px">
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="内存">
              <div class="unit-input-row">
                <el-input-number v-model="refactorForm._memoryValue" :min="0" controls-position="right" style="flex:1" />
                <el-select v-model="refactorForm._memoryUnit" class="unit-select" style="width:85px">
                  <el-option label="MB" value="MB" /><el-option label="GB" value="GB" />
                </el-select>
              </div>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="vCPU">
              <div class="unit-input-row">
                <el-input-number v-model="refactorForm.vcpu" :min="0" controls-position="right" style="flex:1" />
                <span class="unit-text">核</span>
              </div>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="下行带宽">
              <div class="unit-input-row">
                <el-input-number v-model="refactorForm.rx_bandwidth" :min="0" controls-position="right" style="flex:1" />
                <el-select v-model="refactorForm._rxUnit" class="unit-select" style="width:100px">
                  <el-option label="Mbps" value="Mbps" /><el-option label="Gbps" value="Gbps" />
                </el-select>
              </div>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="上行带宽">
              <div class="unit-input-row">
                <el-input-number v-model="refactorForm.tx_bandwidth" :min="0" controls-position="right" style="flex:1" />
                <el-select v-model="refactorForm._txUnit" class="unit-select" style="width:100px">
                  <el-option label="Mbps" value="Mbps" /><el-option label="Gbps" value="Gbps" />
                </el-select>
              </div>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="Root密码"><el-input v-model="refactorForm.root_password" placeholder="不修改留空" show-password /></el-form-item>
        <el-form-item label="UUID"><el-input v-model="refactorForm.uuid" placeholder="虚拟机UUID" /></el-form-item>
        <el-form-item label="元数据ID"><el-input v-model="refactorForm.mate_data_id" placeholder="元数据ID" /></el-form-item>
        <el-form-item label="物理名称"><el-input v-model="refactorForm.physical_name" placeholder="不修改留空" /></el-form-item>
        <el-form-item label="配置路径"><el-input v-model="refactorForm.config_path" placeholder="不修改留空" /></el-form-item>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="SSH端口"><el-input-number v-model="refactorForm.ssh_port" :min="0" :max="65535" controls-position="right" style="width:100%" /></el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="VNC端口"><el-input-number v-model="refactorForm.vnc_port" :min="0" :max="65535" controls-position="right" style="width:100%" /></el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="VNC密码"><el-input v-model="refactorForm.vnc_password" placeholder="不填随机" show-password /></el-form-item>
        <el-form-item label="安全组">
          <div class="selector-row">
            <el-input :model-value="refactorForm._sgName || (refactorForm.port_group_id ? `安全组 #${refactorForm.port_group_id}` : '')" readonly placeholder="可选" style="flex:1" />
            <el-button type="primary" @click="showRefactorSgSelector = true" style="margin-left:8px">选择</el-button>
            <el-button v-if="refactorForm.port_group_id" @click="refactorForm.port_group_id = 0; refactorForm._sgName = ''" style="margin-left:4px">清除</el-button>
          </div>
        </el-form-item>
        <el-form-item label="公网网络">
          <div class="selector-row">
            <el-input :model-value="refactorForm._networkName || (refactorForm.internet_network_id ? `网络 #${refactorForm.internet_network_id}` : '')" readonly placeholder="可选" style="flex:1" />
            <el-button type="primary" @click="showRefactorNetSelector = true" style="margin-left:8px">选择</el-button>
            <el-button v-if="refactorForm.internet_network_id" @click="refactorForm.internet_network_id = 0; refactorForm._networkName = ''" style="margin-left:4px">清除</el-button>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="refactorVisible = false">取消</el-button>
        <el-button type="warning" :loading="actionLoading" @click="submitRefactorVm">确定重构</el-button>
      </template>
    </el-dialog>
    <UserVmSecurityGroupSelector v-model="showRefactorSgSelector" :user-goods-id="userGoodsId"
      @confirm="sg => { refactorForm.port_group_id = sg.id; refactorForm._sgName = sg.name }" />
    <UserVmNetworkSelector v-model="showRefactorNetSelector" :user-goods-id="userGoodsId"
      @confirm="net => { refactorForm.internet_network_id = net.id; refactorForm._networkName = net.name }" />

    <!-- 绑定网络选择器 -->
    <UserVmNetworkSelector v-model="showBindNetworkSelector" :user-goods-id="userGoodsId"
      :show-create-button="false" @confirm="handleBindNetworkConfirm" />

    <!-- 编辑商品信息弹窗 -->    <el-dialog v-model="editGoodsVisible" title="编辑商品信息" width="480px" destroy-on-close>
      <el-form :model="editGoodsForm" label-width="110px">
        <el-form-item label="备注"><el-input v-model="editGoodsForm.note" /></el-form-item>
        <el-form-item label="续费价格">
          <div class="unit-input-row">
            <el-input-number v-model="editGoodsForm.renew_price" :min="0" :precision="2" controls-position="right" style="flex:1" />
            <span class="unit-text">元</span>
          </div>
        </el-form-item>
        <el-form-item label="基础价格">
          <div class="unit-input-row">
            <el-input-number v-model="editGoodsForm.base_price" :min="0" :precision="2" controls-position="right" style="flex:1" />
            <span class="unit-text">元</span>
          </div>
        </el-form-item>
        <el-form-item label="到期时间">
          <el-date-picker v-model="editGoodsForm.expire_time" type="datetime" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="editGoodsVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitEditGoods">保存</el-button>
      </template>
    </el-dialog>

    <!-- 任务进度弹窗 -->
    <el-dialog v-model="progressVisible" :title="progressTitle" width="480px" destroy-on-close>
      <div v-loading="progressLoading">
        <el-descriptions :column="1" border size="small" v-if="progressData">
          <el-descriptions-item label="任务ID">
            <span style="font-family:monospace;font-size:12px">{{ progressData.task_id || '-' }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="状态">
            <el-tag :type="taskStatusType(progressData.status)" size="small">{{ progressData.status || '-' }}</el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="元数据" v-if="progressData.meta && progressData.meta.length > 2">
            <span style="word-break:break-all;font-size:12px">{{ progressData.meta }}</span>
          </el-descriptions-item>
        </el-descriptions>
        <el-empty v-else-if="!progressLoading" description="暂无进度信息" :image-size="60" />
      </div>
      <template #footer><el-button @click="progressVisible = false">关闭</el-button></template>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted, onBeforeUnmount, onActivated, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowLeft, Refresh, ArrowDown, Monitor, WarningFilled, View, Hide, CopyDocument, Edit, Delete } from '@element-plus/icons-vue'
import {
  getUserVmDetail, getUserVmVnc, getUserVmHostImages,
  startUserVm, stopUserVm, rebootUserVm, suspendUserVm, resumeUserVm, rescueUserVm, exitRescueUserVm, rebuildUserVm, deleteUserVm,
  transferUserVm, migrateUserVm, updateUserVmTraffic, updateUserVm, refactorUserVm,
  getUserVmVolumeList, createUserVmVolume, resizeUserVmVolume, mountUserVmVolume, unmountUserVmVolume, deleteUserVmVolume,
  getUserVmSnapshotList, getUserVmSnapshotCount, createUserVmSnapshot, restoreUserVmSnapshot, deleteUserVmSnapshot, getUserVmSnapshotProgress, setUserVmSnapshotLimit,
  getUserVmBackupList, getUserVmBackupCount, createUserVmBackup, restoreUserVmBackup, deleteUserVmBackup, getUserVmBackupProgress, setUserVmBackupLimit,
  getUserVmPostGroupList, createUserVmPostGroup, updateUserVmPostGroup, bindUserVmPostGroup, unbindUserVmPostGroup, applyUserVmPostGroup, deleteUserVmPostGroup, enableUserVmPostGroupWhitelist, disableUserVmPostGroupWhitelist,
  createUserVmPostGroupRule, updateUserVmPostGroupRule, deleteUserVmPostGroupRule,
  getUserVmPostGroupDetail,
  getUserVmNetworkList, getUserVmNetworkDetail, getUserVmNetworkingList, createUserVmNetworking, assignUserVmNetworking, removeUserVmNetworkingNetwork, deleteUserVmNetworking,
  getUserGoodsDetail,
  getUserVmMetricsHistory,
  getUserVmTrafficPolicy, updateUserVmTrafficPolicy, addUserVmFixedTraffic, addUserVmTemporaryTraffic
} from '@/api/admin/userVm'
import { deleteNetwork as deletePointNetwork } from '@/api/admin/kvmService'
import { extractApiError } from '@/utils/kvmErrorUtil'
import { vmStatusLabel as vmStatusLabelUtil, vmStatusType as vmStatusTypeUtil, volumeStatusLabel, volumeStatusType } from '@/utils/tool'
import UserSelector from '@/components/UserSelector/index.vue'
import UserVmSecurityGroupSelector from '@/components/admin/UserVmSecurityGroupSelector.vue'
import UserVmVolumeSelector from '@/components/admin/UserVmVolumeSelector.vue'
import UserVmNetworkSelector from '@/components/admin/UserVmNetworkSelector.vue'
import dayjs from 'dayjs'
import * as echarts from 'echarts'

const route = useRoute()
const router = useRouter()
const userGoodsId = ref(parseInt(route.query.id) || 0)

const loading = ref(false)
const actionLoading = ref(false)
const activeTab = ref('volume')

// 详情数据
const userGoods = ref(null)
const vm = ref(null)
const vmNetworks = ref([])
const vmVolumes = ref([])
const vmImage = ref(null)
const inPortGroup = ref(null)
const outPortGroup = ref(null)
const isVmGoods = ref(false)
const showPassword = ref(false)

const copyPassword = async () => {
  const pwd = vm.value?.root_password
  if (!pwd) { ElMessage.warning('暂无密码'); return }
  try {
    if (navigator.clipboard && window.isSecureContext) {
      await navigator.clipboard.writeText(pwd)
    } else {
      const ta = document.createElement('textarea')
      ta.value = pwd
      ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0'
      document.body.appendChild(ta)
      ta.focus()
      ta.select()
      document.execCommand('copy')
      document.body.removeChild(ta)
    }
    ElMessage.success('密码已复制到剪贴板')
  } catch {
    ElMessage.error('复制失败，请手动复制')
  }
}

const clipCopy = async (text) => {
  if (navigator.clipboard && window.isSecureContext) {
    await navigator.clipboard.writeText(text)
  } else {
    const ta = document.createElement('textarea')
    ta.value = text
    ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0'
    document.body.appendChild(ta)
    ta.focus(); ta.select()
    document.execCommand('copy')
    document.body.removeChild(ta)
  }
}

const copyText = async (text) => {
  if (!text) return
  try { await clipCopy(text); ElMessage.success('已复制') } catch { ElMessage.error('复制失败') }
}

const copyAllIps = async (ipList) => {
  if (!ipList?.length) return
  try { await clipCopy(ipList.join('\n')); ElMessage.success(`已复制 ${ipList.length} 个IP`) } catch { ElMessage.error('复制失败') }
}

const isWindows = computed(() => vmImage.value?.os_type === 'windows')

const vmPublicIpList = computed(() => {
  return vmNetworks.value.filter(n => n.type === 'bridge').map(n => n.address ? n.address.split('/')[0] : n.name).filter(Boolean)
})
const vmPrivateIpList = computed(() => {
  return vmNetworks.value.filter(n => n.type === 'nat').map(n => n.address ? n.address.split('/')[0] : n.name).filter(Boolean)
})

// 安全组列表（来自详情接口，入站+出站）
const inPortGroupList = computed(() => {
  const list = []
  if (inPortGroup.value) list.push(inPortGroup.value)
  if (outPortGroup.value) list.push(outPortGroup.value)
  return list
})
const showSgBindSelector = ref(false)

const formatTime = (t) => t ? dayjs(t).format('YYYY-MM-DD HH:mm:ss') : '-'
const formatVncExpire = (t) => {
  if (!t) return '-'
  // 处理 {seconds: xxx} 格式
  const sec = typeof t === 'object' ? t.seconds : null
  if (sec) return dayjs(sec * 1000).format('YYYY-MM-DD HH:mm:ss')
  return dayjs(t).format('YYYY-MM-DD HH:mm:ss')
}
const formatExpireTime = (t) => { if (!t) return '-'; const d = dayjs(t); return d.year() < 2000 ? '永久' : d.format('YYYY-MM-DD HH:mm') }
const formatMemory = (kb) => { if (!kb) return '-'; const n = Number(kb); if (n >= 1048576) return (n / 1048576).toFixed(1) + ' GB'; if (n >= 1024) return (n / 1024).toFixed(0) + ' MB'; return n + ' KB' }
const formatTraffic = (mb) => { if (!mb) return '-'; const n = Number(mb); if (n >= 1024) return (n / 1024).toFixed(2) + ' GB'; return n + ' Mb' }
const vmStatusType = (s) => vmStatusTypeUtil(s)
const vmStatusLabel = (s) => vmStatusLabelUtil(s)
const taskStatusType = (s) => ({ running: 'primary', completed: 'success', ready: 'success', failed: 'danger', error: 'danger', pending: 'info' }[s] || 'info')

const goBack = () => router.back()

const loadDetail = async () => {
  if (!userGoodsId.value) return
  loading.value = true
  try {
    // 并行请求虚拟机详情和用户商品详情
    const [vmRes, goodsRes] = await Promise.allSettled([
      getUserVmDetail({ user_goods_id: userGoodsId.value }),
      getUserGoodsDetail({ id: userGoodsId.value })
    ])

    // 优先用虚拟机详情里的 user_goods
    if (vmRes.status === 'fulfilled' && vmRes.value?.data?.code === 200 && vmRes.value?.data?.data) {
      const d = vmRes.value.data.data
      userGoods.value = d.user_goods
      isVmGoods.value = true
      const vmData = d.vm
      if (vmData) {
        vm.value = vmData.data
        vmNetworks.value = vmData.networks || []
        vmVolumes.value = vmData.volumes || []
        vmImage.value = vmData.image || null
        inPortGroup.value = vmData.in_port_group || null
        outPortGroup.value = vmData.out_port_group || null
        if (activeTab.value === 'security') loadSgLockInfo()
      } else {
        // vm 为空，降级：用用户商品详情补充数据
        vm.value = null
        vmNetworks.value = []
        vmVolumes.value = []
        vmImage.value = null
        inPortGroup.value = null
        outPortGroup.value = null
      }
    } else if (goodsRes.status === 'fulfilled' && goodsRes.value?.data?.code === 200 && goodsRes.value?.data?.data) {
      // 虚拟机详情失败（非虚拟机商品），降级用用户商品详情
      const d = goodsRes.value.data.data?.data ?? goodsRes.value.data.data
      userGoods.value = d
      isVmGoods.value = false
      vm.value = null
      vmNetworks.value = []
      vmVolumes.value = []
      vmImage.value = null
      inPortGroup.value = null
      outPortGroup.value = null
    } else {
      ElMessage.error('加载失败')
    }

    // 如果 userGoods 还没有，尝试从用户商品详情补充
    if (userGoods.value && goodsRes.status === 'fulfilled' && goodsRes.value?.data?.code === 200) {
      const goodsData = goodsRes.value.data.data?.data ?? goodsRes.value.data.data
      // 补充 user 信息（虚拟机详情里 user 字段可能为空）
      if (goodsData?.user?.UserName && !userGoods.value.user?.UserName) {
        userGoods.value = { ...userGoods.value, user: goodsData.user }
      }
    }
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '加载失败')) } finally { loading.value = false }
}

const handleTabChange = (tab) => {
  if (tab === 'snapshot') { loadSnapshots(); loadSnapshotQuota() }
  if (tab === 'backup') { loadBackups(); loadBackupQuota() }
  if (tab === 'security') loadSgLockInfo()
  if (tab === 'networking') loadNetworkings()
  if (tab === 'monitor' && !metricsData.value) loadMetricsHistory()
  if (tab === 'trafficPolicy') loadTrafficPolicy()
}

// 请求安全组详情补充 lock 字段（使用用户虚拟机安全组详情接口）
const loadSgLockInfo = async () => {
  const groups = [inPortGroup.value, outPortGroup.value].filter(Boolean)
  for (const sg of groups) {
    try {
      const res = await getUserVmPostGroupDetail({ user_goods_id: userGoodsId.value, id: sg.id })
      if (res?.data?.code === 200 && res?.data?.data) {
        const d = res.data.data.group || res.data.data.data || res.data.data
        if (inPortGroup.value?.id === sg.id) inPortGroup.value = { ...inPortGroup.value, lock: d.lock ?? sg.lock }
        if (outPortGroup.value?.id === sg.id) outPortGroup.value = { ...outPortGroup.value, lock: d.lock ?? sg.lock }
      }
    } catch { /* */ }
  }
}

// ---- VNC ----
const vncVisible = ref(false)
const vncLoading = ref(false)
const vncResult = ref(null)
const handleVnc = async () => {
  vncVisible.value = true; vncLoading.value = true; vncResult.value = null
  try {
    const res = await getUserVmVnc({ user_goods_id: userGoodsId.value })
    if (res?.data?.code === 200) vncResult.value = res.data.data
    else ElMessage.error(extractApiError(res?.data, '获取VNC失败'))
  } catch { /* */ } finally { vncLoading.value = false }
}
const openVncUrl = () => { if (vncResult.value?.url) window.open(vncResult.value.url, '_blank') }

// ---- 电源 ----
const powerVisible = ref(false)
const powerAction = ref('')
const powerLabels = { start: '启动', stop: '停止', reboot: '重启', suspend: '暂停', resume: '恢复', rescue: '救援', exitRescue: '退出救援' }
const powerApis = { start: startUserVm, stop: stopUserVm, reboot: rebootUserVm, suspend: suspendUserVm, resume: resumeUserVm, rescue: rescueUserVm, exitRescue: exitRescueUserVm }

const handlePower = (action) => { powerAction.value = action; powerVisible.value = true }
const submitPower = async () => {
  actionLoading.value = true
  try {
    const res = await powerApis[powerAction.value]({ user_goods_id: userGoodsId.value })
    if (res?.data?.code === 200) { ElMessage.success(`${powerLabels[powerAction.value]}成功`); powerVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '操作失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) } finally { actionLoading.value = false }
}

const handleMoreCmd = (cmd) => {
  if (powerLabels[cmd]) { handlePower(cmd); return }
  if (cmd === 'rebuild') { rebuildImageId.value = 0; rebuildImageName.value = ''; rebuildImages.value = []; rebuildVisible.value = true; loadRebuildImages() }
  if (cmd === 'updateTraffic') { Object.assign(trafficForm, { rx_bandwidth: vm.value?.rx_bandwidth || 0, tx_bandwidth: vm.value?.tx_bandwidth || 0, _trafficValue: +((vm.value?.traffic_max || 0) / 1024).toFixed(2), _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB', traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0 }); trafficVisible.value = true }
  if (cmd === 'transfer') { Object.assign(transferForm, { target_user_id: 0, _userName: '' }); transferVisible.value = true }
  if (cmd === 'updateVm') openEditVm()
  if (cmd === 'refactorVm') openRefactorVm()
  if (cmd === 'migrate') openMigrateVm()
  if (cmd === 'editGoods') openEditGoods()
  if (cmd === 'delete') {
    ElMessageBox.confirm('确定删除该用户虚拟机吗？', '删除确认', { type: 'error' }).then(async () => {
      try {
        const res = await deleteUserVm({ user_goods_id: userGoodsId.value })
        if (res?.data?.code === 200) { ElMessage.success('删除成功'); goBack() }
        else ElMessage.error(extractApiError(res?.data, '删除失败'))
      } catch { /* */ }
    }).catch(() => {})
  }
}

// ---- 数据卷 ----
const isVolumeMounted = (row) => {
  if (row.is_mount !== undefined) return !!row.is_mount
  return row.status === 'ready'
}
const showVolumeSelector = ref(false)
const volumes = ref([])
const volumeLoading = ref(false)
const volumePage = ref(1)
const volumePageSize = ref(10)
const volumeTotal = ref(0)
const volCreateVisible = ref(false)
const volResizeVisible = ref(false)
const volResizeTarget = ref(null)
const volNewSize = ref(1)
const volCreateForm = reactive({ name: '', size: 10, target_device: '' })

const loadVolumes = async () => {
  volumeLoading.value = true
  try {
    const res = await getUserVmVolumeList({ user_goods_id: userGoodsId.value, page: volumePage.value, count: volumePageSize.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data; volumes.value = d.data || (Array.isArray(d) ? d : [])
      volumeTotal.value = d.all_count ?? d.total ?? volumes.value.length
    }
  } catch { /* */ } finally { volumeLoading.value = false }
}
const handleCreateVolume = () => { Object.assign(volCreateForm, { name: '', size: 10, target_device: '' }); volCreateVisible.value = true }
const submitCreateVolume = async () => {
  if (!volCreateForm.name) { ElMessage.warning('请输入名称'); return }
  actionLoading.value = true
  try {
    const res = await createUserVmVolume({ user_goods_id: userGoodsId.value, ...volCreateForm })
    if (res?.data?.code === 200) { ElMessage.success('创建成功'); volCreateVisible.value = false; loadVolumes() }
    else ElMessage.error(extractApiError(res?.data, '创建失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { actionLoading.value = false }
}
const handleResizeVolume = (row) => { volResizeTarget.value = row; volNewSize.value = row.size || 1; volResizeVisible.value = true }
const submitResizeVolume = async () => {
  actionLoading.value = true
  try {
    const res = await resizeUserVmVolume({ user_goods_id: userGoodsId.value, volume_id: volResizeTarget.value.id, size: volNewSize.value })
    if (res?.data?.code === 200) { ElMessage.success('扩容成功'); volResizeVisible.value = false; loadVolumes() }
    else ElMessage.error(extractApiError(res?.data, '扩容失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '扩容失败')) } finally { actionLoading.value = false }
}
const handleMountVolume = (row) => {
  ElMessageBox.confirm('确定挂载该数据卷吗？', '挂载', { type: 'info' }).then(async () => {
    try { const res = await mountUserVmVolume({ user_goods_id: userGoodsId.value, volume_id: row.id }); if (res?.data?.code === 200) { ElMessage.success('挂载成功'); loadVolumes() } else ElMessage.error(extractApiError(res?.data, '挂载失败')) } catch { /* */ }
  }).catch(() => {})
}
const handleUnmountVolume = (row) => {
  ElMessageBox.confirm('确定卸载该数据卷吗？', '卸载', { type: 'warning' }).then(async () => {
    try { const res = await unmountUserVmVolume({ user_goods_id: userGoodsId.value, volume_id: row.id }); if (res?.data?.code === 200) { ElMessage.success('卸载成功'); loadDetail() } else ElMessage.error(extractApiError(res?.data, '卸载失败')) } catch { /* */ }
  }).catch(() => {})
}
const handleDeleteVolume = (row) => {
  ElMessageBox.confirm('确定删除该数据卷吗？', '删除', { type: 'error' }).then(async () => {
    try { const res = await deleteUserVmVolume({ user_goods_id: userGoodsId.value, volume_id: row.id }); if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadVolumes() } else ElMessage.error(extractApiError(res?.data, '删除失败')) } catch { /* */ }
  }).catch(() => {})
}

// ---- 任务进度 ----
const progressVisible = ref(false)
const progressLoading = ref(false)
const progressTitle = ref('')
const progressData = ref(null)

// ---- 快照 ----
const snapshots = ref([])
const snapshotLoading = ref(false)
const snapshotQuota = ref(null)
const snapshotCreateVisible = ref(false)
const snapshotForm = reactive({ name: '', description: '' })

const loadSnapshots = async () => {
  snapshotLoading.value = true
  try {
    const res = await getUserVmSnapshotList({ user_goods_id: userGoodsId.value })
    if (res?.data?.code === 200 && res?.data?.data) { const d = res.data.data; snapshots.value = d.data || (Array.isArray(d) ? d : []) }
  } catch { /* */ } finally { snapshotLoading.value = false }
}
const loadSnapshotQuota = async () => {
  try { const res = await getUserVmSnapshotCount({ user_goods_id: userGoodsId.value }); if (res?.data?.code === 200) snapshotQuota.value = res.data.data } catch { /* */ }
}
const handleCreateSnapshot = () => { Object.assign(snapshotForm, { name: '', description: '' }); snapshotCreateVisible.value = true }
const submitCreateSnapshot = async () => {
  if (!snapshotForm.name) { ElMessage.warning('请输入名称'); return }
  actionLoading.value = true
  try {
    const res = await createUserVmSnapshot({ user_goods_id: userGoodsId.value, ...snapshotForm })
    if (res?.data?.code === 200) { ElMessage.success('创建成功'); snapshotCreateVisible.value = false; loadSnapshots(); loadSnapshotQuota() }
    else ElMessage.error(extractApiError(res?.data, '创建失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { actionLoading.value = false }
}
const handleRestoreSnapshot = (row) => {
  ElMessageBox.confirm(`确定恢复快照「${row.name}」吗？`, '恢复', { type: 'warning' }).then(async () => {
    try { const res = await restoreUserVmSnapshot({ user_goods_id: userGoodsId.value, snapshot_id: row.id }); if (res?.data?.code === 200) ElMessage.success('恢复操作已提交'); else ElMessage.error(extractApiError(res?.data, '恢复失败')) } catch { /* */ }
  }).catch(() => {})
}
const handleDeleteSnapshot = (row) => {
  ElMessageBox.confirm(`确定删除快照「${row.name}」吗？`, '删除', { type: 'warning' }).then(async () => {
    try { const res = await deleteUserVmSnapshot({ user_goods_id: userGoodsId.value, snapshot_id: row.id }); if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadSnapshots(); loadSnapshotQuota() } else ElMessage.error(extractApiError(res?.data, '删除失败')) } catch { /* */ }
  }).catch(() => {})
}
const handleSnapshotProgress = async (row) => {
  progressTitle.value = '快照任务进度'
  progressData.value = null
  progressVisible.value = true
  progressLoading.value = true
  try {
    const res = await getUserVmSnapshotProgress({ user_goods_id: userGoodsId.value, task_id: String(row.task_id || row.id) })
    if (res?.data?.code === 200) progressData.value = res.data.data?.data ?? res.data.data
    else ElMessage.warning('暂无进度信息')
  } catch { ElMessage.warning('获取进度失败') } finally { progressLoading.value = false }
}
const handleSetSnapshotLimit = () => {  ElMessageBox.prompt('请输入快照上限', '设置快照上限', { inputPattern: /^[1-9]\d*$/, inputErrorMessage: '请输入正整数', inputValue: String(snapshotQuota.value?.limit || 10) })
    .then(async ({ value }) => { try { const res = await setUserVmSnapshotLimit({ user_goods_id: userGoodsId.value, limit: value }); if (res?.data?.code === 200) { ElMessage.success('设置成功'); loadSnapshotQuota() } else ElMessage.error(extractApiError(res?.data, '设置失败')) } catch { /* */ } }).catch(() => {})
}

// ---- 备份 ----
const backups = ref([])
const backupLoading = ref(false)
const backupQuota = ref(null)
const backupCreateVisible = ref(false)
const backupForm = reactive({ name: '', description: '' })

const loadBackups = async () => {
  backupLoading.value = true
  try {
    const res = await getUserVmBackupList({ user_goods_id: userGoodsId.value })
    if (res?.data?.code === 200 && res?.data?.data) { const d = res.data.data; backups.value = d.data || (Array.isArray(d) ? d : []) }
  } catch { /* */ } finally { backupLoading.value = false }
}
const loadBackupQuota = async () => {
  try { const res = await getUserVmBackupCount({ user_goods_id: userGoodsId.value }); if (res?.data?.code === 200) backupQuota.value = res.data.data } catch { /* */ }
}
const handleCreateBackup = () => { Object.assign(backupForm, { name: '', description: '' }); backupCreateVisible.value = true }
const submitCreateBackup = async () => {
  if (!backupForm.name) { ElMessage.warning('请输入名称'); return }
  actionLoading.value = true
  try {
    const res = await createUserVmBackup({ user_goods_id: userGoodsId.value, ...backupForm })
    if (res?.data?.code === 200) { ElMessage.success('创建成功'); backupCreateVisible.value = false; loadBackups(); loadBackupQuota() }
    else ElMessage.error(extractApiError(res?.data, '创建失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { actionLoading.value = false }
}
const handleRestoreBackup = (row) => {
  ElMessageBox.confirm(`确定恢复备份「${row.name}」吗？`, '恢复', { type: 'warning' }).then(async () => {
    try { const res = await restoreUserVmBackup({ user_goods_id: userGoodsId.value, backup_id: row.id }); if (res?.data?.code === 200) ElMessage.success('恢复操作已提交'); else ElMessage.error(extractApiError(res?.data, '恢复失败')) } catch { /* */ }
  }).catch(() => {})
}
const handleDeleteBackup = (row) => {
  ElMessageBox.confirm(`确定删除备份「${row.name}」吗？`, '删除', { type: 'warning' }).then(async () => {
    try { const res = await deleteUserVmBackup({ user_goods_id: userGoodsId.value, backup_id: row.id }); if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadBackups(); loadBackupQuota() } else ElMessage.error(extractApiError(res?.data, '删除失败')) } catch { /* */ }
  }).catch(() => {})
}
const handleBackupProgress = async (row) => {
  progressTitle.value = '备份任务进度'
  progressData.value = null
  progressVisible.value = true
  progressLoading.value = true
  try {
    const res = await getUserVmBackupProgress({ user_goods_id: userGoodsId.value, task_id: String(row.task_id || row.id) })
    if (res?.data?.code === 200) progressData.value = res.data.data?.data ?? res.data.data
    else ElMessage.warning('暂无进度信息')
  } catch { ElMessage.warning('获取进度失败') } finally { progressLoading.value = false }
}
const handleSetBackupLimit = () => {
  ElMessageBox.prompt('请输入备份上限', '设置备份上限', { inputPattern: /^[1-9]\d*$/, inputErrorMessage: '请输入正整数', inputValue: String(backupQuota.value?.limit || 10) })
    .then(async ({ value }) => { try { const res = await setUserVmBackupLimit({ user_goods_id: userGoodsId.value, limit: value }); if (res?.data?.code === 200) { ElMessage.success('设置成功'); loadBackupQuota() } else ElMessage.error(extractApiError(res?.data, '设置失败')) } catch { /* */ } }).catch(() => {})
}

// ---- 安全组 ----
const sgs = ref([])
const sgLoading = ref(false)
const sgPage = ref(1)
const sgPageSize = ref(10)
const sgTotal = ref(0)
const sgCreateVisible = ref(false)
const sgCreateForm = reactive({ name: '', direction: 'in', lock: false, drop_all: false })

const loadSecurityGroups = async () => {
  sgLoading.value = true
  try {
    const res = await getUserVmPostGroupList({ user_goods_id: userGoodsId.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data; sgs.value = d.groups || d.data || (Array.isArray(d) ? d : [])
      sgTotal.value = d.total ?? sgs.value.length
    }
  } catch { /* */ } finally { sgLoading.value = false }
}
const handleCreateSg = () => { Object.assign(sgCreateForm, { name: '', direction: 'in', lock: false, drop_all: false }); sgCreateVisible.value = true }
const submitCreateSg = async () => {
  if (!sgCreateForm.name) { ElMessage.warning('请输入名称'); return }
  actionLoading.value = true
  try {
    const res = await createUserVmPostGroup({ user_goods_id: userGoodsId.value, ...sgCreateForm })
    if (res?.data?.code === 200) { ElMessage.success('创建成功'); sgCreateVisible.value = false; loadSecurityGroups() }
    else ElMessage.error(extractApiError(res?.data, '创建失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { actionLoading.value = false }
}
const handleBindSg = async (row) => { try { const res = await bindUserVmPostGroup({ user_goods_id: userGoodsId.value, id: row.id }); if (res?.data?.code === 200) { ElMessage.success('绑定成功'); loadDetail() } else ElMessage.error(extractApiError(res?.data, '绑定失败')) } catch { /* */ } }
const handleUnbindSg = async (row) => { try { const res = await unbindUserVmPostGroup({ user_goods_id: userGoodsId.value, id: row.id }); if (res?.data?.code === 200) { ElMessage.success('解绑成功'); loadDetail() } else ElMessage.error(extractApiError(res?.data, '解绑失败')) } catch { /* */ } }
const handleApplySg = async (row) => { try { const res = await applyUserVmPostGroup({ user_goods_id: userGoodsId.value, id: row.id }); if (res?.data?.code === 200) ElMessage.success('应用成功'); else ElMessage.error(extractApiError(res?.data, '应用失败')) } catch { /* */ } }

// ---- 编辑安全组 ----
const sgEditVisible = ref(false)
const sgEditTarget = ref(null)
const sgEditForm = reactive({ name: '', direction: 'in', lock: false, drop_all: false })

const handleEditSg = (row) => {
  sgEditTarget.value = row
  Object.assign(sgEditForm, { name: row.name || '', direction: row.direction || 'in', lock: !!row.lock, drop_all: !!row.drop_all })
  sgEditVisible.value = true
}
const submitEditSg = async () => {
  actionLoading.value = true
  try {
    const res = await updateUserVmPostGroup({ user_goods_id: userGoodsId.value, id: sgEditTarget.value.id, ...sgEditForm })
    if (res?.data?.code === 200) { ElMessage.success('修改成功'); sgEditVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '修改失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { actionLoading.value = false }
}

// ---- 安全组规则管理 ----
const sgRuleVisible = ref(false)
const sgRuleTarget = ref(null)
const sgRules = ref([])
const sgDetailLoading = ref(false)
const sgRuleFormVisible = ref(false)
const sgRuleFormType = ref('add')
const sgRuleForm = reactive({ id: undefined, group_id: 0, protocol: 'tcp', action: 'allow', port_range: '', ip_range: '', priority: 0 })

const loadSgDetail = async (id) => {
  if (!id) return
  sgDetailLoading.value = true
  try {
    const res = await getUserVmPostGroupDetail({ user_goods_id: userGoodsId.value, id })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data
      const sg = d.group || d.data || d
      sgRuleTarget.value = { ...sgRuleTarget.value, ...sg }
      sgRules.value = sg.rules || []
    }
  } catch { /* */ } finally { sgDetailLoading.value = false }
}

const handleSgDetail = (row) => {
  sgRuleTarget.value = row
  sgRules.value = []
  sgRuleVisible.value = true
  loadSgDetail(row.id)
}

const handleAddSgRule = () => {
  sgRuleFormType.value = 'add'
  Object.assign(sgRuleForm, { id: undefined, group_id: sgRuleTarget.value?.id || 0, protocol: 'tcp', action: 'allow', port_range: '', ip_range: '', priority: 0 })
  sgRuleFormVisible.value = true
}
const handleEditSgRule = (rule) => {
  sgRuleFormType.value = 'edit'
  Object.assign(sgRuleForm, { id: rule.id, group_id: sgRuleTarget.value?.id || 0, protocol: rule.protocol || 'tcp', action: rule.action || 'allow', port_range: rule.port_range || '', ip_range: rule.ip_range || '', priority: rule.priority || 0 })
  sgRuleFormVisible.value = true
}
const submitSgRule = async () => {
  actionLoading.value = true
  try {
    const payload = { user_goods_id: userGoodsId.value, group_id: sgRuleForm.group_id, protocol: sgRuleForm.protocol, action: sgRuleForm.action, priority: sgRuleForm.priority }
    if (sgRuleForm.port_range) payload.port_range = sgRuleForm.port_range
    if (sgRuleForm.ip_range) payload.ip_range = sgRuleForm.ip_range
    if (sgRuleFormType.value === 'edit') { payload.id = sgRuleForm.id; payload.port_group_id = sgRuleForm.group_id }
    const res = sgRuleFormType.value === 'add' ? await createUserVmPostGroupRule(payload) : await updateUserVmPostGroupRule(payload)
    if (res?.data?.code === 200) { ElMessage.success(sgRuleFormType.value === 'add' ? '创建成功' : '修改成功'); sgRuleFormVisible.value = false; loadSgDetail(sgRuleTarget.value?.id) }
    else ElMessage.error(extractApiError(res?.data, '操作失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) } finally { actionLoading.value = false }
}
const handleDeleteSgRule = (rule) => {
  ElMessageBox.confirm('确定删除该规则吗？', '删除', { type: 'warning' }).then(async () => {
    try {
      const res = await deleteUserVmPostGroupRule({ user_goods_id: userGoodsId.value, id: rule.id })
      if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadSgDetail(sgRuleTarget.value?.id) }
      else ElMessage.error(extractApiError(res?.data, '删除失败'))
    } catch { /* */ }
  }).catch(() => {})
}
const handleSgWhitelist = async (row) => {
  const api = row.drop_all ? disableUserVmPostGroupWhitelist : enableUserVmPostGroupWhitelist
  try { const res = await api({ user_goods_id: userGoodsId.value, id: row.id }); if (res?.data?.code === 200) { ElMessage.success('操作成功'); loadDetail() } else ElMessage.error(extractApiError(res?.data, '操作失败')) } catch { /* */ }
}
const handleDeleteSg = (row) => {
  ElMessageBox.confirm(`确定删除安全组「${row.name}」吗？`, '删除', { type: 'warning' }).then(async () => {
    try { const res = await deleteUserVmPostGroup({ user_goods_id: userGoodsId.value, id: row.id }); if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadSecurityGroups() } else ElMessage.error(extractApiError(res?.data, '删除失败')) } catch { /* */ }
  }).catch(() => {})
}

// ---- 网络 ----
const networks = ref([])
const networkLoading = ref(false)
const networkPage = ref(1)
const networkPageSize = ref(10)
const networkTotal = ref(0)

const loadNetworks = async () => {
  networkLoading.value = true
  try {
    const res = await getUserVmNetworkList({ user_goods_id: userGoodsId.value, page: networkPage.value, count: networkPageSize.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data; networks.value = d.data || (Array.isArray(d) ? d : [])
      networkTotal.value = d.all_count ?? d.total ?? networks.value.length
    }
  } catch { /* */ } finally { networkLoading.value = false }
}

// ---- 删除网络 ----
// 走 host_service/point/network/delete：删除底层物理网络（破坏性，会影响其他绑定该网络的 VM）
// row 字段可能不完整，service_id / host_id 通过 getUserVmNetworkDetail 兜底反查
const deletingNetworkId = ref(0)
const resolveNetworkServiceHost = async (row) => {
  let serviceId = row.service_id ?? row.host_service_id ?? row.kvm_service_id ?? 0
  let hostId = row.host_id ?? 0
  if (!serviceId || !hostId) {
    try {
      const res = await getUserVmNetworkDetail({ user_goods_id: userGoodsId.value, id: row.id })
      if (res?.data?.code === 200 && res?.data?.data) {
        const d = res.data.data
        const n = d.data || d.network || d
        if (!serviceId) serviceId = n?.service_id ?? n?.host_service_id ?? n?.kvm_service_id ?? 0
        if (!hostId) hostId = n?.host_id ?? 0
      }
    } catch { /* 兜底失败时返回原值，由调用方判断 */ }
  }
  return { serviceId, hostId }
}
const handleDeleteVmNetwork = (row) => {
  ElMessageBox.confirm(
    `将删除底层网络「${row.name}」(ID:${row.id})，该操作会影响所有绑定该网络的虚拟机，是否继续？`,
    '删除网络',
    { confirmButtonText: '确定删除', cancelButtonText: '取消', type: 'warning' }
  ).then(async () => {
    deletingNetworkId.value = row.id
    try {
      const { serviceId, hostId } = await resolveNetworkServiceHost(row)
      if (!serviceId) { ElMessage.error('无法获取该网络所属服务ID，删除失败'); return }
      const params = { service_id: serviceId, network_id: row.id }
      if (hostId) params.host_id = hostId
      const res = await deletePointNetwork(params)
      if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '删除失败'))
    } catch (e) {
      ElMessage.error(extractApiError(e?.response?.data, '删除失败'))
    } finally {
      deletingNetworkId.value = 0
    }
  }).catch(() => {})
}

// ---- 绑定网络 ----
const showBindNetworkSelector = ref(false)

const handleBindNetworkConfirm = async (selectedNetwork) => {
  const existingIds = vmNetworks.value.map(n => n.id)
  if (existingIds.includes(selectedNetwork.id)) {
    ElMessage.warning('该网络已绑定')
    return
  }
  actionLoading.value = true
  try {
    const payload = { user_goods_id: userGoodsId.value }
    const bridgeIds = vmNetworks.value.filter(n => n.type === 'bridge').map(n => n.id)
    const natNet = vmNetworks.value.find(n => n.type === 'nat')
    if (selectedNetwork.type === 'nat') {
      payload.internet_network_id = selectedNetwork.id
      if (bridgeIds.length) payload.network_ids = bridgeIds
    } else {
      payload.network_ids = [...bridgeIds, selectedNetwork.id]
      if (natNet) payload.internet_network_id = natNet.id
    }
    if (vm.value?.rx_bandwidth) payload.rx_bandwidth = vm.value.rx_bandwidth
    if (vm.value?.tx_bandwidth) payload.tx_bandwidth = vm.value.tx_bandwidth
    if (vm.value?.ssh_port) payload.ssh_port = vm.value.ssh_port
    if (inPortGroup.value?.id) payload.port_group_id = inPortGroup.value.id
    const res = await updateUserVm(payload)
    if (res?.data?.code === 200) {
      ElMessage.success('绑定网络成功')
      loadDetail()
    } else {
      ElMessage.error(extractApiError(res?.data, '绑定网络失败'))
    }
  } catch (e) {
    ElMessage.error(extractApiError(e?.response?.data, '绑定网络失败'))
  } finally {
    actionLoading.value = false
  }
}

// ---- 组网 ----
const networkings = ref([])
const networkingLoading = ref(false)
const networkingPage = ref(1)
const networkingPageSize = ref(10)
const networkingTotal = ref(0)
const networkingCreateVisible = ref(false)

// 组网行数据：匹配 vmNetworks 判断是否已加入
const networkingRows = computed(() => {
  const bridgeMap = {}
  for (const n of vmNetworks.value) {
    if (n.bridge_name) bridgeMap[n.bridge_name] = n
  }
  return networkings.value.map(nw => {
    const matched = nw.bridge_name ? bridgeMap[nw.bridge_name] : null
    return { ...nw, _joined: !!matched, _network: matched || null }
  })
})
const networkingCreateForm = reactive({ name: '', bridge_name: '', gateway: '', description: '' })
const assignVisible = ref(false)
const assignTarget = ref(null)
const assignIp = ref('')

const loadNetworkings = async () => {
  networkingLoading.value = true
  try {
    const res = await getUserVmNetworkingList({ user_goods_id: userGoodsId.value, page: networkingPage.value, count: networkingPageSize.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data; networkings.value = d.data || (Array.isArray(d) ? d : [])
      networkingTotal.value = d.all_count ?? d.total ?? networkings.value.length
    }
  } catch { /* */ } finally { networkingLoading.value = false }
}
const handleCreateNetworking = () => { Object.assign(networkingCreateForm, { name: '', bridge_name: '', gateway: '', description: '' }); networkingCreateVisible.value = true }
const submitCreateNetworking = async () => {
  
  actionLoading.value = true
  try {
    const res = await createUserVmNetworking({ user_goods_id: userGoodsId.value, ...networkingCreateForm })
    if (res?.data?.code === 200) { ElMessage.success('创建成功'); networkingCreateVisible.value = false; loadNetworkings() }
    else ElMessage.error(extractApiError(res?.data, '创建失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { actionLoading.value = false }
}
const handleAssignNetworking = (row) => { assignTarget.value = row; assignIp.value = ''; assignVisible.value = true }

// 加入组网（弹窗指定IP）
const handleJoinNetworking = (row) => { assignTarget.value = row; assignIp.value = ''; assignVisible.value = true }

// 移除组网（通过 remove_network 接口）
const handleLeaveNetworking = (row) => {
  const networkId = row._network?.id
  if (!networkId) { ElMessage.warning('未找到对应网络ID'); return }
  ElMessageBox.confirm(`确定将虚拟机从组网「${row.name}」中移除吗？`, '移除确认', { type: 'warning' }).then(async () => {
    try {
      const res = await removeUserVmNetworkingNetwork({ user_goods_id: userGoodsId.value, networking_id: row.id, network_id: networkId })
      if (res?.data?.code === 200) { ElMessage.success('移除成功'); loadDetail(); loadNetworkings() }
      else ElMessage.error(extractApiError(res?.data, '移除失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '移除失败')) }
  }).catch(() => {})
}
const submitAssign = async () => {
  actionLoading.value = true
  try {
    const payload = { user_goods_id: userGoodsId.value, networking_id: assignTarget.value.id }
    if (assignIp.value.trim()) payload.ip = assignIp.value.trim()
    const res = await assignUserVmNetworking(payload)
    if (res?.data?.code === 200) { ElMessage.success('加入成功'); assignVisible.value = false; loadDetail(); loadNetworkings() }
    else ElMessage.error(extractApiError(res?.data, '分配失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '分配失败')) } finally { actionLoading.value = false }
}
const handleDeleteNetworking = (row) => {
  ElMessageBox.confirm(`确定删除组网「${row.name}」吗？`, '删除', { type: 'warning' }).then(async () => {
    try { const res = await deleteUserVmNetworking({ user_goods_id: userGoodsId.value, networking_id: row.id }); if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadNetworkings() } else ElMessage.error(extractApiError(res?.data, '删除失败')) } catch { /* */ }
  }).catch(() => {})
}

// ---- 重装 ----
const rebuildVisible = ref(false)
const rebuildImageId = ref(0)
const rebuildImageName = ref('')
const showRebuildImageSelector = ref(false)
const rebuildImages = ref([])
const rebuildImagesLoading = ref(false)
const rebuildSelectedImage = ref(null)

const loadRebuildImages = async () => {
  rebuildImagesLoading.value = true
  try {
    const res = await getUserVmHostImages({ user_goods_id: userGoodsId.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data
      rebuildImages.value = (d.data || []).map(item => item.image || item).filter(Boolean)
    }
  } catch { /* */ } finally { rebuildImagesLoading.value = false }
}

const confirmRebuildImage = () => {
  if (rebuildSelectedImage.value) {
    rebuildImageId.value = rebuildSelectedImage.value.id
    rebuildImageName.value = rebuildSelectedImage.value.name
    showRebuildImageSelector.value = false
    rebuildSelectedImage.value = null
  }
}

const submitRebuild = async () => {
  if (!rebuildImageId.value) { ElMessage.warning('请选择镜像'); return }
  actionLoading.value = true
  try {
    const res = await rebuildUserVm({ user_goods_id: userGoodsId.value, image_id: rebuildImageId.value })
    if (res?.data?.code === 200) { ElMessage.success('重装成功'); rebuildVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '重装失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '重装失败')) } finally { actionLoading.value = false }
}

// ---- 迁移虚拟机 ----
const migrateVisible = ref(false)
const migrateOptionsLoading = ref(false)
const migrateMode = ref('host')
const migrateHostOptions = ref([])
const migrateGroupOptions = ref([])
const migrateForm = reactive({ target_host_id: null, target_host_group_id: null, ipv4_num: 0, ipv6_num: 0 })

const openMigrateVm = async () => {
  Object.assign(migrateForm, { target_host_id: null, target_host_group_id: null, ipv4_num: 0, ipv6_num: 0 })
  migrateMode.value = 'host'
  migrateVisible.value = true
  migrateOptionsLoading.value = true
  try {
    // 需要通过 service_id 获取宿主机列表，从 userGoods 的 good.table 判断服务
    // 这里通过 KvmServiceSelector 选择，或直接加载所有服务下的宿主机
    // 简化处理：加载用户商品关联的主控服务的宿主机
    const { getRemoteHostList, getRemoteHostGroupList } = await import('@/api/admin/kvmService')
    // 获取所有主控服务列表，让用户先选服务再选宿主机
    const { getKvmServiceList } = await import('@/api/admin/kvmService')
    const svcRes = await getKvmServiceList({ page: 1, count: 10 })
    if (svcRes?.data?.code === 200 && svcRes?.data?.data) {
      const inner = svcRes.data.data
      const svcs = inner.data || inner.list || (Array.isArray(inner) ? inner : [])
      // 取第一个服务加载宿主机（实际应让用户选服务）
      if (svcs.length > 0) {
        const sid = svcs[0].id ?? svcs[0].Id
        const [hostRes, groupRes] = await Promise.all([
          getRemoteHostList({ service_id: sid, page: 1, page_size: 10 }),
          getRemoteHostGroupList({ service_id: sid, page: 1, page_size: 10 })
        ])
        if (hostRes?.data?.code === 200 && hostRes?.data?.data) {
          const d = hostRes.data.data
          migrateHostOptions.value = Array.isArray(d) ? d : (d.hosts || d.data || [])
        }
        if (groupRes?.data?.code === 200 && groupRes?.data?.data) {
          const d = groupRes.data.data
          migrateGroupOptions.value = Array.isArray(d) ? d : (d.host_groups || d.data || [])
        }
      }
    }
  } catch { /* */ } finally { migrateOptionsLoading.value = false }
}

const submitMigrateVm = async () => {
  if (migrateMode.value === 'host' && !migrateForm.target_host_id) { ElMessage.warning('请选择目标宿主机'); return }
  if (migrateMode.value === 'group' && !migrateForm.target_host_group_id) { ElMessage.warning('请选择目标宿主机组'); return }
  actionLoading.value = true
  try {
    const payload = { user_goods_id: userGoodsId.value }
    if (migrateMode.value === 'host') payload.target_host_id = migrateForm.target_host_id
    else payload.target_host_group_id = migrateForm.target_host_group_id
    if (migrateForm.ipv4_num) payload.ipv4_num = migrateForm.ipv4_num
    if (migrateForm.ipv6_num) payload.ipv6_num = migrateForm.ipv6_num
    const res = await migrateUserVm(payload)
    if (res?.data?.code === 200) { ElMessage.success('迁移成功'); migrateVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '迁移失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '迁移失败')) } finally { actionLoading.value = false }
}

// ---- 重构虚拟机 ----
const refactorVisible = ref(false)
const showRefactorSgSelector = ref(false)
const showRefactorNetSelector = ref(false)
const refactorForm = reactive({
  _memoryValue: 0, _memoryUnit: 'MB', vcpu: 0,
  rx_bandwidth: 0, tx_bandwidth: 0, _rxUnit: 'Mbps', _txUnit: 'Mbps',
  root_password: '', uuid: '', mate_data_id: '', physical_name: '', config_path: '',
  ssh_port: 0, vnc_port: 0, vnc_password: '',
  port_group_id: 0, _sgName: '', internet_network_id: 0, _networkName: ''
})

const openRefactorVm = () => {
  Object.assign(refactorForm, {
    _memoryValue: vm.value ? Math.round((vm.value.memory || 0) / 1024) : 0,
    _memoryUnit: 'MB',
    vcpu: vm.value?.vcpu || 0,
    rx_bandwidth: vm.value?.rx_bandwidth || 0,
    tx_bandwidth: vm.value?.tx_bandwidth || 0,
    _rxUnit: 'Mbps', _txUnit: 'Mbps',
    root_password: '', uuid: vm.value?.uuid || '',
    mate_data_id: vm.value?.mate_data_id || '',
    physical_name: '', config_path: '',
    ssh_port: vm.value?.ssh_port || 0, vnc_port: 0, vnc_password: '',
    port_group_id: inPortGroup.value?.id || 0, _sgName: inPortGroup.value?.name || '',
    internet_network_id: 0, _networkName: ''
  })
  const bridgeNet = vmNetworks.value.find(n => n.type === 'bridge')
  if (bridgeNet) { refactorForm.internet_network_id = bridgeNet.id; refactorForm._networkName = bridgeNet.name || bridgeNet.address }
  refactorVisible.value = true
}

const submitRefactorVm = async () => {
  actionLoading.value = true
  try {
    const payload = { user_goods_id: userGoodsId.value }
    if (refactorForm._memoryValue) {
      const memKB = refactorForm._memoryUnit === 'GB' ? refactorForm._memoryValue * 1024 * 1024 : refactorForm._memoryValue * 1024
      payload.memory = Math.round(memKB)
    }
    if (refactorForm.vcpu) payload.vcpu = refactorForm.vcpu
    const refRx = refactorForm._rxUnit === 'Gbps' ? refactorForm.rx_bandwidth * 1000 : refactorForm.rx_bandwidth
    const refTx = refactorForm._txUnit === 'Gbps' ? refactorForm.tx_bandwidth * 1000 : refactorForm.tx_bandwidth
    if (refRx) payload.rx_bandwidth = Math.round(refRx)
    if (refTx) payload.tx_bandwidth = Math.round(refTx)
    if (refactorForm.root_password) payload.root_password = refactorForm.root_password
    if (refactorForm.uuid) payload.uuid = refactorForm.uuid
    if (refactorForm.mate_data_id) payload.mate_data_id = refactorForm.mate_data_id
    if (refactorForm.physical_name) payload.physical_name = refactorForm.physical_name
    if (refactorForm.config_path) payload.config_path = refactorForm.config_path
    if (refactorForm.ssh_port) payload.ssh_port = refactorForm.ssh_port
    if (refactorForm.vnc_port) payload.vnc_port = refactorForm.vnc_port
    if (refactorForm.vnc_password) payload.vnc_password = refactorForm.vnc_password
    if (refactorForm.port_group_id) payload.port_group_id = refactorForm.port_group_id
    if (refactorForm.internet_network_id) payload.internet_network_id = refactorForm.internet_network_id
    const res = await refactorUserVm(payload)
    if (res?.data?.code === 200) { ElMessage.success('重构成功'); refactorVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '重构失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '重构失败')) } finally { actionLoading.value = false }
}

// ---- 编辑虚拟机 ----
const editVmVisible = ref(false)
const editVmLoading = ref(false)
const showEditVmSgSelector = ref(false)
const showEditVmNetSelector = ref(false)
const editVmForm = reactive({
  rx_bandwidth: 0, tx_bandwidth: 0,
  _rxUnit: 'Mbps', _txUnit: 'Mbps',
  root_password: '',
  ssh_port: 22,
  port_group_id: 0, _sgName: '',
  snapshot_num: 0, backup_num: 0,
  internet_network_id: 0, _networkName: ''
})

const openEditVm = async () => {
  Object.assign(editVmForm, {
    rx_bandwidth: vm.value?.rx_bandwidth || 0,
    tx_bandwidth: vm.value?.tx_bandwidth || 0,
    _rxUnit: 'Mbps', _txUnit: 'Mbps',
    root_password: '',
    ssh_port: vm.value?.ssh_port || 22,
    port_group_id: inPortGroup.value?.id || 0,
    _sgName: inPortGroup.value?.name || '',
    snapshot_num: vm.value?.snapshot_num || 0,
    backup_num: vm.value?.backup_num || 0,
    internet_network_id: 0, _networkName: ''
  })
  // 回填公网网络（取第一个 bridge 类型）
  const bridgeNet = vmNetworks.value.find(n => n.type === 'bridge')
  if (bridgeNet) { editVmForm.internet_network_id = bridgeNet.id; editVmForm._networkName = bridgeNet.name || bridgeNet.address }
  editVmVisible.value = true
}

const submitEditVm = async () => {
  actionLoading.value = true
  try {
    const payload = { user_goods_id: userGoodsId.value }
    const editRx = editVmForm._rxUnit === 'Gbps' ? editVmForm.rx_bandwidth * 1000 : editVmForm.rx_bandwidth
    const editTx = editVmForm._txUnit === 'Gbps' ? editVmForm.tx_bandwidth * 1000 : editVmForm.tx_bandwidth
    if (editRx) payload.rx_bandwidth = Math.round(editRx)
    if (editTx) payload.tx_bandwidth = Math.round(editTx)
    if (editVmForm.root_password) payload.root_password = editVmForm.root_password
    if (editVmForm.ssh_port) payload.ssh_port = editVmForm.ssh_port
    if (editVmForm.port_group_id) payload.port_group_id = editVmForm.port_group_id
    if (editVmForm.snapshot_num) payload.snapshot_num = editVmForm.snapshot_num
    if (editVmForm.backup_num) payload.backup_num = editVmForm.backup_num
    if (editVmForm.internet_network_id) payload.internet_network_id = editVmForm.internet_network_id
    const res = await updateUserVm(payload)
    if (res?.data?.code === 200) { ElMessage.success('保存成功'); editVmVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '保存失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '保存失败')) } finally { actionLoading.value = false }
}

// ---- 修改带宽 ----
const trafficVisible = ref(false)
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficValue: 0, _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB', traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0 })
const submitUpdateTraffic = async () => {
  actionLoading.value = true
  try {
    const rxBw = trafficForm._rxUnit === 'Gbps' ? trafficForm.rx_bandwidth * 1000 : trafficForm.rx_bandwidth
    const txBw = trafficForm._txUnit === 'Gbps' ? trafficForm.tx_bandwidth * 1000 : trafficForm.tx_bandwidth
    const trafficMb = trafficForm._trafficUnit === 'TB' ? (trafficForm._trafficValue || 0) * 1024 * 1024
      : trafficForm._trafficUnit === 'GB' ? (trafficForm._trafficValue || 0) * 1024
      : (trafficForm._trafficValue || 0)
    const payload = {
      user_goods_id: userGoodsId.value,
      rx_bandwidth: Math.round(rxBw),
      tx_bandwidth: Math.round(txBw),
      traffic_max: Math.round(trafficMb)
    }
    if (trafficForm.traffic_exhausted_rx_mbps > 0) payload.traffic_exhausted_rx_mbps = trafficForm.traffic_exhausted_rx_mbps
    if (trafficForm.traffic_exhausted_tx_mbps > 0) payload.traffic_exhausted_tx_mbps = trafficForm.traffic_exhausted_tx_mbps
    const res = await updateUserVmTraffic(payload)
    if (res?.data?.code === 200) { ElMessage.success('修改成功'); trafficVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '修改失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { actionLoading.value = false }
}

// ---- 流量策略 ----
// 测试未通过（接口新增，待联调）
const trafficPolicy = ref(null)
const trafficPolicyLoading = ref(false)
const trafficPolicyVisible = ref(false)
const addTrafficVisible = ref(false)
const addTrafficType = ref('fixed')
const trafficPolicyForm = reactive({ traffic_max_mb: 0, exhausted_rx_mbps: 0, exhausted_tx_mbps: 0 })
const addTrafficForm = reactive({ traffic_mb: 1 })

const loadTrafficPolicy = async () => {
  if (!userGoodsId.value) return
  trafficPolicyLoading.value = true
  try {
    const res = await getUserVmTrafficPolicy({ user_goods_id: userGoodsId.value })
    if (res?.data?.code === 200) trafficPolicy.value = res.data.data
  } catch { /* ignore */ } finally { trafficPolicyLoading.value = false }
}

const openTrafficPolicyDialog = () => {
  Object.assign(trafficPolicyForm, {
    traffic_max_mb: trafficPolicy.value?.traffic_max_mb || 0,
    exhausted_rx_mbps: trafficPolicy.value?.exhausted_rx_mbps || 0,
    exhausted_tx_mbps: trafficPolicy.value?.exhausted_tx_mbps || 0
  })
  trafficPolicyVisible.value = true
}

const submitUpdateTrafficPolicy = async () => {
  trafficPolicyLoading.value = true
  try {
    const res = await updateUserVmTrafficPolicy({
      user_goods_id: userGoodsId.value,
      traffic_max_mb: trafficPolicyForm.traffic_max_mb,
      exhausted_rx_mbps: trafficPolicyForm.exhausted_rx_mbps,
      exhausted_tx_mbps: trafficPolicyForm.exhausted_tx_mbps
    })
    if (res?.data?.code === 200) { ElMessage.success('修改成功'); trafficPolicyVisible.value = false; loadTrafficPolicy() }
    else ElMessage.error(extractApiError(res?.data, '修改失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { trafficPolicyLoading.value = false }
}

const openAddTrafficDialog = (type) => {
  addTrafficType.value = type
  addTrafficForm.traffic_mb = 1
  addTrafficVisible.value = true
}

const submitAddTraffic = async () => {
  if (!addTrafficForm.traffic_mb || addTrafficForm.traffic_mb < 1) { ElMessage.warning('请输入有效的流量数量'); return }
  trafficPolicyLoading.value = true
  try {
    const apiFn = addTrafficType.value === 'fixed' ? addUserVmFixedTraffic : addUserVmTemporaryTraffic
    const res = await apiFn({ user_goods_id: userGoodsId.value, traffic_mb: addTrafficForm.traffic_mb })
    if (res?.data?.code === 200) { ElMessage.success('操作成功'); addTrafficVisible.value = false; loadTrafficPolicy() }
    else ElMessage.error(extractApiError(res?.data, '操作失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) } finally { trafficPolicyLoading.value = false }
}

// ---- 转移 ----
const transferVisible = ref(false)
const showTransferUserSelector = ref(false)
const transferForm = reactive({ target_user_id: 0, _userName: '' })
const submitTransfer = async () => {
  if (!transferForm.target_user_id) { ElMessage.warning('请选择目标用户'); return }
  actionLoading.value = true
  try {
    const res = await transferUserVm({ user_goods_id: userGoodsId.value, target_user_id: transferForm.target_user_id })
    if (res?.data?.code === 200) { ElMessage.success('转移成功'); transferVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '转移失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '转移失败')) } finally { actionLoading.value = false }
}

// ---- 编辑商品信息 ----
const editGoodsVisible = ref(false)
const editGoodsForm = reactive({ note: '', renew_price: 0, base_price: 0, expire_time: '' })

const openEditGoods = () => {
  Object.assign(editGoodsForm, {
    note: userGoods.value?.note || '',
    renew_price: (userGoods.value?.renewPrice || 0) / 100,
    base_price: (userGoods.value?.basePrice || 0) / 100,
    expire_time: (() => {
      const t = userGoods.value?.expireTime
      if (!t) return ''
      const d = dayjs(t)
      return d.year() < 2000 ? '' : d.format('YYYY-MM-DD HH:mm:ss')
    })()
  })
  editGoodsVisible.value = true
}

const submitEditGoods = async () => {
  actionLoading.value = true
  try {
    const { updateUserGoods } = await import('@/api/admin/userVm')
    const payload = { id: userGoodsId.value }
    if (editGoodsForm.note !== undefined) payload.note = editGoodsForm.note
    if (editGoodsForm.renew_price) payload.renew_price = Math.round(editGoodsForm.renew_price * 100)
    if (editGoodsForm.base_price) payload.base_price = Math.round(editGoodsForm.base_price * 100)
    if (editGoodsForm.expire_time) payload.expire_time = editGoodsForm.expire_time
    const res = await updateUserGoods(payload)
    if (res?.data?.code === 200) { ElMessage.success('保存成功'); editGoodsVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '保存失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '保存失败')) } finally { actionLoading.value = false }
}

// ---- 监控指标 ----
const cpuChartRef = ref(null)
const memChartRef = ref(null)
const diskChartRef = ref(null)
const netChartRef = ref(null)
let cpuChart = null
let memChart = null
let diskChart = null
let netChart = null

const metricsData = ref(null)
const metricsLoading = ref(false)

const makeDefaultRange = () => {
  const now = new Date()
  return [new Date(now.getTime() - 10 * 60 * 1000), now]
}
const monitorDateRange = ref(makeDefaultRange())

const monitorShortcuts = [
  { text: '最近10分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 10 * 60000), n] } },
  { text: '最近30分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 30 * 60000), n] } },
  { text: '最近1小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 3600000), n] } },
  { text: '最近6小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 6 * 3600000), n] } },
  { text: '最近12小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 12 * 3600000), n] } },
  { text: '最近1天', value: () => { const n = new Date(); return [new Date(n.getTime() - 86400000), n] } },
  { text: '最近7天', value: () => { const n = new Date(); return [new Date(n.getTime() - 7 * 86400000), n] } },
]

function calcInterval(startTime, endTime) {
  const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
  if (spanMin < 30) return '1m'
  if (spanMin < 60) return '3m'
  if (spanMin < 360) return '5m'
  if (spanMin < 720) return '15m'
  if (spanMin < 1440) return '30m'
  if (spanMin < 4320) return '1h'
  if (spanMin < 10080) return '2h'
  if (spanMin < 43200) return '6h'
  if (spanMin < 129600) return '12h'
  return '1d'
}

const intervalLabelMap = { '1m': '1分钟', '3m': '3分钟', '5m': '5分钟', '15m': '15分钟', '30m': '30分钟', '1h': '1小时', '2h': '2小时', '6h': '6小时', '12h': '12小时', '1d': '1天' }
const currentIntervalLabel = computed(() => {
  if (!monitorDateRange.value || monitorDateRange.value.length < 2) return '-'
  const iv = calcInterval(new Date(monitorDateRange.value[0]), new Date(monitorDateRange.value[1]))
  return intervalLabelMap[iv] || iv
})

const latestMetrics = computed(() => {
  const arr = metricsData.value
  if (!Array.isArray(arr) || !arr.length) return null
  return arr[arr.length - 1]
})

const formatBytesRaw = (val) => {
  if (!val && val !== 0) return '-'
  val = Number(val)
  if (val >= 1099511627776) return (val / 1099511627776).toFixed(2) + ' TB'
  if (val >= 1073741824) return (val / 1073741824).toFixed(2) + ' GB'
  if (val >= 1048576) return (val / 1048576).toFixed(2) + ' MB'
  if (val >= 1024) return (val / 1024).toFixed(1) + ' KB'
  return val + ' B'
}

const formatNetLabel = (v) => {
  if (!v) return '0 B/s'
  v = Number(v)
  if (v >= 1073741824) return (v / 1073741824).toFixed(1) + ' GB/s'
  if (v >= 1048576) return (v / 1048576).toFixed(1) + ' MB/s'
  if (v >= 1024) return (v / 1024).toFixed(1) + ' KB/s'
  return v + ' B/s'
}

const formatMemKB = (kb) => {
  if (!kb && kb !== 0) return '-'
  kb = Number(kb)
  if (kb >= 1073741824) return (kb / 1073741824).toFixed(1) + ' TB'
  if (kb >= 1048576) return (kb / 1048576).toFixed(1) + ' GB'
  if (kb >= 1024) return (kb / 1024).toFixed(0) + ' MB'
  return kb + ' KB'
}

const vmMemPercent = (m) => {
  if (!m || !m.mem_total) return '0.0'
  return ((m.mem_used / m.mem_total) * 100).toFixed(1)
}

const loadMetricsHistory = async () => {
  if (!userGoodsId.value) return
  if (!monitorDateRange.value || monitorDateRange.value.length < 2) return
  metricsLoading.value = true
  try {
    const startTime = new Date(monitorDateRange.value[0])
    const endTime = new Date(monitorDateRange.value[1])
    const interval = calcInterval(startTime, endTime)
    const params = {
      user_goods_id: userGoodsId.value,
      start: startTime.toISOString(),
      end_time: endTime.toISOString(),
      interval
    }
    const res = await getUserVmMetricsHistory(params)
    const body = res?.data
    if (body?.code === 200 && body?.data) {
      metricsData.value = Array.isArray(body.data) ? body.data : (body.data.data || [])
      await nextTick()
      renderMetricsCharts()
    } else {
      ElMessage.error(extractApiError(body, '加载监控数据失败'))
    }
  } catch (e) {
    ElMessage.error(extractApiError(e?.response?.data, '加载监控数据失败'))
  } finally {
    metricsLoading.value = false
  }
}

const renderMetricsCharts = () => {
  const metrics = metricsData.value
  if (!Array.isArray(metrics) || !metrics.length) return

  const spanMs = monitorDateRange.value ? (new Date(monitorDateRange.value[1]).getTime() - new Date(monitorDateRange.value[0]).getTime()) : 600000
  const showDate = spanMs >= 86400000
  const symbolType = metrics.length < 30 ? 'circle' : 'none'
  const labelRotate = showDate ? 45 : 0

  const times = metrics.map(m => {
    const date = new Date(m.bucket)
    if (showDate) return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
    return date.toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' })
  })

  const cpuData = metrics.map(m => m.cpu_usage ?? 0)
  const memData = metrics.map(m => m.mem_total ? ((m.mem_used / m.mem_total) * 100) : 0)
  const diskReadData = metrics.map(m => m.disk_read ?? 0)
  const diskWriteData = metrics.map(m => m.disk_write ?? 0)
  const netRxData = metrics.map(m => m.net_rx ?? 0)
  const netTxData = metrics.map(m => m.net_tx ?? 0)

  const baseGrid = { top: 10, right: 16, bottom: showDate ? 40 : 24, left: 50 }
  const makeXAxis = () => ({ type: 'category', data: times, boundaryGap: false, axisLabel: { fontSize: 10, rotate: labelRotate } })
  const makeSeries = (name, data, color) => ({ name, type: 'line', smooth: true, symbol: symbolType, areaStyle: { opacity: 0.15 }, lineStyle: { width: 2, color }, itemStyle: { color }, data })

  if (cpuChartRef.value) {
    if (!cpuChart) cpuChart = echarts.init(cpuChartRef.value)
    cpuChart.setOption({
      tooltip: { trigger: 'axis', formatter: (p) => `${p[0].axisValue}<br/>${p[0].marker} CPU: ${p[0].value.toFixed(1)}%` },
      grid: baseGrid, xAxis: makeXAxis(),
      yAxis: { type: 'value', min: 0, max: 100, axisLabel: { fontSize: 10, formatter: v => v + '%' } },
      series: [makeSeries('CPU', cpuData, '#409eff')]
    }, true)
  }

  if (memChartRef.value) {
    if (!memChart) memChart = echarts.init(memChartRef.value)
    memChart.setOption({
      tooltip: { trigger: 'axis', formatter: (p) => `${p[0].axisValue}<br/>${p[0].marker} 内存: ${p[0].value.toFixed(1)}%` },
      grid: baseGrid, xAxis: makeXAxis(),
      yAxis: { type: 'value', min: 0, max: 100, axisLabel: { fontSize: 10, formatter: v => v + '%' } },
      series: [makeSeries('内存', memData, '#67c23a')]
    }, true)
  }

  if (diskChartRef.value) {
    if (!diskChart) diskChart = echarts.init(diskChartRef.value)
    diskChart.setOption({
      tooltip: { trigger: 'axis', formatter: (params) => {
        let s = params[0].axisValue
        params.forEach(p => { s += `<br/>${p.marker} ${p.seriesName}: ${formatBytesRaw(p.value)}` })
        return s
      }},
      grid: baseGrid, xAxis: makeXAxis(),
      yAxis: { type: 'value', min: 0, axisLabel: { fontSize: 10, formatter: v => formatBytesRaw(v) } },
      series: [makeSeries('读取', diskReadData, '#409eff'), makeSeries('写入', diskWriteData, '#e6a23c')]
    }, true)
  }

  if (netChartRef.value) {
    if (!netChart) netChart = echarts.init(netChartRef.value)
    netChart.setOption({
      tooltip: { trigger: 'axis', formatter: (params) => {
        let s = params[0].axisValue
        params.forEach(p => { s += `<br/>${p.marker} ${p.seriesName}: ${formatNetLabel(p.value)}` })
        return s
      }},
      grid: baseGrid, xAxis: makeXAxis(),
      yAxis: { type: 'value', min: 0, axisLabel: { fontSize: 10, formatter: v => formatNetLabel(v) } },
      series: [makeSeries('接收', netRxData, '#409eff'), makeSeries('发送', netTxData, '#e6a23c')]
    }, true)
  }
}

const disposeCharts = () => {
  cpuChart?.dispose(); cpuChart = null
  memChart?.dispose(); memChart = null
  diskChart?.dispose(); diskChart = null
  netChart?.dispose(); netChart = null
}

onMounted(() => { loadDetail() })

onActivated(() => {
  const newId = parseInt(route.query.id) || 0
  if (newId && newId !== userGoodsId.value) {
    userGoodsId.value = newId
    userGoods.value = null
    vm.value = null
    vmNetworks.value = []
    vmVolumes.value = []
    inPortGroup.value = null
    outPortGroup.value = null
    isVmGoods.value = false
    metricsData.value = null
    disposeCharts()
    loadDetail()
  }
})

onBeforeUnmount(() => { disposeCharts() })
</script>

<style scoped>
.uvm-detail { padding: 0; }
.page-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 20px; background: #fff; border-bottom: 1px solid #ebeef5; }
.header-left { display: flex; align-items: center; gap: 0; }
.page-title { font-size: 16px; font-weight: 600; color: #303133; }
.main-content { padding: 16px 20px; }
.overview-card { margin-bottom: 16px; }
.overview-header { display: flex; justify-content: space-between; align-items: flex-start; }
.overview-left { display: flex; align-items: center; gap: 16px; }
.overview-info { display: flex; flex-direction: column; gap: 8px; }
.name-row { display: flex; align-items: center; }
.vm-name { margin: 0; font-size: 20px; font-weight: 600; color: #303133; }
.meta-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #606266; flex-wrap: wrap; }
.overview-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.tabs-card { }
.tab-toolbar { display: flex; gap: 8px; align-items: center; margin: 12px 0; }
.selector-row { display: flex; align-items: center; width: 100%; }

/* VM 配置网格 */
.vm-config-grid { border: 1px solid #e8e8e8; border-radius: 4px; overflow: hidden; }
.config-row { display: flex; border-bottom: 1px solid #e8e8e8; }
.config-row:last-child { border-bottom: none; }
.config-cell { flex: 1; padding: 10px 14px; display: flex; flex-direction: column; gap: 4px; border-right: 1px solid #e8e8e8; }
.config-cell:last-child { border-right: none; }
.config-label { font-size: 12px; color: #86909c; line-height: 1; }
.config-value { font-size: 13px; color: #1d2129; line-height: 1.4; word-break: break-all; }
.cfg-edit-btn { margin-left: 8px; padding: 0 4px; height: 18px; vertical-align: middle; }
.cfg-edit-btn .el-icon { margin-right: 2px; vertical-align: -2px; }
.pwd-value { display: inline-flex; align-items: center; gap: 4px; }
.pwd-text { font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; background: #f5f7fa; padding: 2px 8px; border-radius: 3px; letter-spacing: .5px; user-select: all; }
.pwd-btn { padding: 0 !important; height: auto !important; min-height: auto !important; }

/* 单位输入行 */
.unit-input-row { display: flex; align-items: center; gap: 6px; width: 100%; }
.unit-select :deep(.el-input__wrapper) { padding: 0 8px; }
.unit-text { flex-shrink: 0; font-size: 13px; color: #606266; padding: 0 4px; min-width: 36px; text-align: center; line-height: 32px; background: #f5f7fa; border: 1px solid #dcdfe6; border-radius: 4px; }

/* 监控指标 */
.section-block { padding: 0 4px; }
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.section-title { margin: 0; font-size: 15px; font-weight: 600; color: #303133; }
.metrics-card { margin-bottom: 0; }
.metrics-title { font-weight: 600; font-size: 13px; display: inline-flex; align-items: center; gap: 6px; }
.metrics-title .el-icon { font-size: 16px; color: #409eff; }
.chart-container { width: 100%; height: 220px; }
.metric-summary-row { display: flex; gap: 16px; margin-bottom: 16px; }
.metric-summary-card { flex: 1; min-width: 0; background: #f7f8fa; border-radius: 6px; padding: 14px 16px; border: 1px solid #e8e8e8; display: flex; flex-direction: column; }
.metric-summary-label { font-size: 12px; color: #86909c; margin-bottom: 8px; }
.metric-summary-value { font-size: 22px; font-weight: 600; color: #1d2129; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.metric-summary-sub { font-size: 12px; color: #86909c; margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ip-value { display: inline-flex; align-items: center; gap: 4px; }
.ip-main { font-weight: 500; font-family: 'SF Mono', Consolas, monospace; font-size: 13px; }
.ip-more-tag { cursor: pointer; vertical-align: middle; flex-shrink: 0; }
.ip-popover-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; margin-bottom: 4px; font-size: 13px; color: #606266; }
.ip-popover-list { max-height: 240px; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; }
.ip-popover-list::-webkit-scrollbar { display: none; }
.ip-popover-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid #f2f3f5; }
.ip-popover-item:last-child { border-bottom: none; }
.ip-popover-item .ip-text { font-family: 'SF Mono', Consolas, monospace; font-size: 13px; color: #303133; word-break: break-all; }
.ip-popover-item .ip-copy-btn { flex-shrink: 0; margin-left: 8px; }

/* VNC 弹窗 */
.vnc-url-link { word-break: break-all; white-space: normal; }
.vnc-dialog :deep(.el-descriptions__cell) { word-break: break-all; }
</style>
