<template>
  <div class="reader-container">
    <!-- 顶部工具栏 -->
    <div class="reader-header">
      <div class="header-left">
        <el-button link type="primary" @click="goBack" icon="ArrowLeft">返回</el-button>
        <span class="role-badge" :class="role === 'customer' ? 'customer' : 'consultant'">
          {{ role === 'customer' ? '👤 客户' : '👤 顾问' }}
        </span>
      </div>
      <div class="header-center">
        <span class="title">{{ currentArticle?.title || '加载中...' }}</span>
        <span class="page-info">第 {{ currentPage + 1 }} / {{ articles.length }} 篇</span>
      </div>
      <div class="header-right">
        <el-badge
            :value="recordingStatus === 'recording' ? '录制中' : recordingStatus === 'stopped' ? '已录制' : '未录制'"
            :type="recordingStatus === 'recording' ? 'danger' : recordingStatus === 'stopped' ? 'success' : 'info'"
        >
          <el-button link type="primary" icon="VideoCamera" @click="toggleRecording" />
        </el-badge>
        <el-badge :value="wsConnected ? '在线' : '离线'" :type="wsConnected ? 'success' : 'danger'">
          <el-button link type="primary" icon="Connection" />
        </el-badge>
      </div>
    </div>

    <!-- 文章内容区域 -->
    <div class="reader-body" ref="readerBodyRef">
      <!-- 顾问且正在接收屏幕流时显示视频 -->
      <div v-if="role === 'consultant' && isReceivingScreen" class="video-container">
        <video ref="remoteVideoRef" autoplay playsinline></video>
        <!-- 视频上方的控制覆盖层 -->
        <div class="controls-overlay">
          <div class="nav-controls">
            <button class="nav-btn" :class="{ disabled: !canOperate }" @click="prevPage" :disabled="!canOperate">
              <span>◀</span>
            </button>
            <span class="page-text">{{ currentPage + 1 }} / {{ articles.length }}</span>
            <button class="nav-btn" :class="{ disabled: !canOperate }" @click="nextPage" :disabled="!canOperate">
              <span>▶</span>
            </button>
          </div>
          <div class="zoom-controls-overlay">
            <button class="zoom-btn" @click="zoomOut" :disabled="!canOperate">−</button>
            <span>{{ Math.round(zoomLevel * 100) }}%</span>
            <button class="zoom-btn" @click="zoomIn" :disabled="!canOperate">+</button>
          </div>
          <div class="status-tip" v-if="!canOperate">
            <span>🔒 顾问正在操作</span>
          </div>
        </div>
      </div>
      <!-- 否则显示文章内容 -->
      <div v-else class="article-wrapper" :style="{ transform: `scale(${zoomLevel})`, transformOrigin: 'top left' }">
        <div class="article-content" ref="articleContentRef" v-html="currentArticle?.content || ''"></div>
      </div>
    </div>

    <!-- 底部翻页控制（客户可见，顾问在视频模式下隐藏） -->
    <div class="reader-footer" v-if="!(role === 'consultant' && isReceivingScreen)">
      <el-button type="primary" plain circle icon="ArrowLeft" @click="prevPage" :disabled="!canOperate" />
      <el-slider v-model="currentPage" :min="0" :max="articles.length - 1" :show-tooltip="false" style="flex:1; margin:0 16px;" @change="onSliderChange" :disabled="!canOperate" />
      <el-button type="primary" plain circle icon="ArrowRight" @click="nextPage" :disabled="!canOperate" />
    </div>

    <!-- 缩放控制（客户可见，顾问在视频模式下隐藏） -->
    <div class="zoom-controls" v-if="!(role === 'consultant' && isReceivingScreen)">
      <el-button type="primary" plain circle size="small" @click="zoomOut" :disabled="!canOperate || zoomLevel <= 0.5">−</el-button>
      <span class="zoom-level">{{ Math.round(zoomLevel * 100) }}%</span>
      <el-button type="primary" plain circle size="small" @click="zoomIn" :disabled="!canOperate || zoomLevel >= 2.0">+</el-button>
    </div>

    <!-- ★★★ 右下角悬浮按钮组 ★★★ -->
    <div class="fab-group">
      <!-- 客户按钮组 -->
      <template v-if="role === 'customer'">
        <!-- 开始共享 / 结束共享 -->
        <el-badge :value="sharingStatus === '讲解中' ? '讲解中' : '未开始'" :type="sharingStatus === '讲解中' ? 'success' : 'info'">
          <el-button
              v-if="sharingStatus !== '讲解中'"
              class="fab-btn fab-share"
              type="primary"
              circle
              size="large"
              @click="startSharing"
          >
            <span class="fab-btn-text">开始<br>共享</span>
          </el-button>
          <el-button
              v-else
              class="fab-btn fab-end"
              type="danger"
              circle
              size="large"
              @click="handleEndSharing"
          >
            <span class="fab-btn-text">结束<br>共享</span>
          </el-button>
        </el-badge>
        <!-- 脱敏开关 -->
        <el-badge :value="desensitizationEnabled ? '已脱敏' : '未脱敏'" :type="desensitizationEnabled ? 'success' : 'info'">
          <el-button
              class="fab-btn fab-desensitize"
              type="primary"
              circle
              size="large"
              @click="toggleDesensitization"
          >
            <span class="fab-btn-text">脱敏</span>
          </el-button>
        </el-badge>
      </template>

      <!-- 顾问按钮组 -->
      <template v-if="role === 'consultant'">
        <el-badge :value="sharingStatus === '讲解中' ? '已加入' : '未加入'" :type="sharingStatus === '讲解中' ? 'success' : 'info'">
          <!-- 未加入时显示“加入” -->
          <el-button
              v-if="sharingStatus !== '讲解中'"
              class="fab-btn fab-join"
              type="primary"
              circle
              size="large"
              @click="joinDialogVisible = true"
          >
            <span class="fab-btn-text">加入</span>
          </el-button>
          <!-- 已加入时显示“授权客户”或“收回授权” -->
          <el-button
              v-else
              class="fab-btn"
              :class="isController ? 'fab-authorize' : 'fab-revoke'"
              type="primary"
              circle
              size="large"
              @click="toggleControl"
          >
            <span class="fab-btn-text">{{ isController ? '授权\n客户' : '收回\n授权' }}</span>
          </el-button>
        </el-badge>
      </template>
    </div>

    <!-- 共享信息弹窗 -->
    <el-dialog title="大屏讲解" v-model="shareDialogVisible" width="400px" append-to-body>
      <div style="text-align: center; padding: 16px 0;">
        <el-alert title="请将以下信息分享给顾问" type="success" :closable="false" style="margin-bottom: 16px;" />
        <div style="margin-bottom: 16px;">
          <div style="font-size: 14px; color: #666;">共享码</div>
          <div style="font-size: 28px; font-weight: bold; letter-spacing: 6px; color: #1890ff;">
            {{ shareInfo.roomPwd || '------' }}
          </div>
          <el-button size="small" type="primary" plain @click="copyCode" style="margin-top: 8px;">复制</el-button>
        </div>
        <div>
          <div style="font-size: 14px; color: #666;">房间号</div>
          <div style="font-size: 16px; font-weight: 500; color: #333;">{{ shareInfo.roomId || '------' }}</div>
        </div>
      </div>
      <template #footer>
        <el-button type="primary" @click="shareDialogVisible = false; connectAsCustomer()">开始协同</el-button>
      </template>
    </el-dialog>

    <!-- 顾问加入对话框 -->
    <el-dialog title="加入协同" v-model="joinDialogVisible" width="400px" append-to-body>
      <el-form :model="joinForm" label-width="80px">
        <el-form-item label="共享码">
          <el-input v-model="joinForm.roomPwd" placeholder="请输入6位共享码" maxlength="6" />
        </el-form-item>
        <el-form-item label="房间号">
          <el-input v-model="joinForm.roomId" placeholder="请输入房间号" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button type="primary" @click="submitJoin">加入</el-button>
        <el-button @click="joinDialogVisible = false">取消</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup name="ReportReader">
import { ref, reactive, computed, onMounted, onBeforeUnmount, getCurrentInstance, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
  createCoSession,
  endCoSession,
  joinCoSession,
  startRecording,
  stopRecording,
  queryRecording,
  getDesensitizationRules
} from '@/api/system/report'
import useUserStore from '@/store/modules/user'
import { getToken } from '@/utils/auth'

console.log('【协同报告阅读器】组件开始加载')

const { proxy } = getCurrentInstance()
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()

const role = ref(route.query.role || 'customer')
console.log('【角色】当前身份:', role.value)

// ========== 文章数据 ==========
const articles = reactive([
  {
    title: '2026年宏观经济展望',
    content: `
      <h2>2026年宏观经济展望</h2>
      <p>2026年全球经济呈现复苏态势，主要经济体政策趋于宽松。国际货币基金组织（IMF）预测全球GDP增长3.2%，其中新兴市场表现亮眼。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">张三丰</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">13812345678</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E📈 经济走势图%3C/text%3E%3C/svg%3E" alt="经济走势" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>在通胀压力缓解的背景下，美联储预计将于2026年三季度启动降息周期。欧洲央行紧随其后，维持宽松立场。亚洲地区，中国和印度继续成为全球增长引擎。</p>
      <p>值得注意的是，地缘政治风险依然存在，能源价格波动可能对复苏构成扰动。企业需关注供应链多元化布局。</p>
    `
  },
  {
    title: '人工智能与产业变革',
    content: `
      <h2>人工智能与产业变革</h2>
      <p>人工智能（AI）正以前所未有的速度渗透各行各业。2026年，生成式AI已广泛应用于内容创作、客户服务和研发设计。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">李思思</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">15987654321</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E🤖 AI应用场景%3C/text%3E%3C/svg%3E" alt="AI应用" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>据Gartner预测，到2027年，70%的企业将部署AI助手。金融行业率先采用AI进行风险评估和智能投顾，效率提升超过40%。</p>
      <p>然而，AI也带来数据隐私和就业结构调整的挑战。监管部门正加紧制定AI伦理框架，确保技术向善。</p>
    `
  },
  {
    title: '绿色能源投资新机遇',
    content: `
      <h2>绿色能源投资新机遇</h2>
      <p>全球碳中和目标驱动绿色能源投资爆发式增长。2026年，可再生能源投资预计达到1.2万亿美元，创历史新高。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">王小明</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">13612349876</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E🌱 新能源投资%3C/text%3E%3C/svg%3E" alt="绿色能源" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>光伏和风电成本持续下降，储能技术突破使可再生能源稳定性大幅提升。氢能成为新的投资热点，多个国家公布氢能战略。</p>
      <p>在政策利好和技术成熟的双轮驱动下，绿色金融产品创新不断，ESG投资理念深入人心。</p>
    `
  },
  {
    title: '消费市场趋势洞察',
    content: `
      <h2>消费市场趋势洞察</h2>
      <p>2026年消费市场呈现"理性务实"和"体验升级"双重特征。Z世代成为消费主力，注重个性化与社交属性。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">赵丽颖</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">13800008888</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E🛍️ 消费趋势%3C/text%3E%3C/svg%3E" alt="消费趋势" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>电商领域，直播带货和社交电商持续增长，而线下零售通过数字化改造实现复苏。国潮品牌崛起，文化自信驱动国货消费。</p>
      <p>健康消费成为新蓝海，功能性食品、智能健身设备需求激增。同时，可持续消费理念影响购买决策。</p>
    `
  },
  {
    title: '数字化转型实战指南',
    content: `
      <h2>数字化转型实战指南</h2>
      <p>数字化转型已从"选修课"变为"必修课"。2026年，企业数字化成熟度成为核心竞争力。</p>
      <div class="customer-info">
        <p><strong>客户姓名：</strong><span class="desensitize-name">刘德华</span></p>
        <p><strong>联系电话：</strong><span class="desensitize-phone">18912348888</span></p>
      </div>
      <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' viewBox='0 0 400 200'%3E%3Crect width='400' height='200' fill='%23f0f2f5'/%3E%3Ctext x='50%25' y='50%25' font-size='20' text-anchor='middle' fill='%23999'%3E💻 数字化转型%3C/text%3E%3C/svg%3E" alt="数字化" style="max-width:100%; border-radius:8px; margin:12px 0;" />
      <p>成功转型的企业注重顶层设计、数据治理和人才梯队建设。云原生、AI和大数据分析成为核心技术底座。</p>
      <p>本指南提供从战略规划到实施落地的全流程框架，助您把握数字化时代的先机。</p>
    `
  }
])

// ========== 页面状态 ==========
const currentPage = ref(0)
const zoomLevel = ref(1.0)
const scrollRatio = ref(0)
const desensitizationEnabled = ref(false)
const readerBodyRef = ref(null)
const articleContentRef = ref(null)
const originalContentMap = new Map()

// ========== 协同状态 ==========
const sharingStatus = ref('未共享')
const shareDialogVisible = ref(false)
const joinDialogVisible = ref(false)
const wsConnected = ref(false)
const isController = ref(false)
const shareInfo = reactive({ roomId: '', roomPwd: '', sessionBizId: '', ownerId: '' })
const joinForm = reactive({ roomId: '', roomPwd: '' })

let ws = null
let wsReconnectTimer = null
let shouldReconnect = true

// ========== 录制相关状态 ==========
const recordingStatus = ref('idle')
const recordingTaskId = ref(null)
const mediaRecorder = ref(null)
const recordedChunks = ref([])
const recordingStream = ref(null)
const recordingStartTime = ref(null)
const recordingDuration = ref(0)
let durationTimer = null
const recordedFileUrl = ref('')
const uploading = ref(false)

// ========== WebRTC 相关 ==========
let pc = null
const remoteVideoRef = ref(null)
const isReceivingScreen = ref(false)
// ★ 共享流（全局）
let sharedStream = null

// ========== 计算属性 ==========
const currentArticle = computed(() => articles[currentPage.value] || articles[0])
const canOperate = computed(() => wsConnected.value && isController.value)

// ========== 页面导航 ==========
function goBack() {
  router.push('/system/report')
}

// ========== 翻页、滚动、缩放 ==========
function prevPage() {
  if (canOperate.value && currentPage.value > 0) {
    changePage(currentPage.value - 1)
  }
}
function nextPage() {
  if (canOperate.value && currentPage.value < articles.length - 1) {
    changePage(currentPage.value + 1)
  }
}
function onSliderChange(val) {
  if (!canOperate.value) return
  changePage(val)
  nextTick(() => {
    sendSyncAction('turn_the_page', 0)
  })
}
function zoomIn() {
  if (canOperate.value && zoomLevel.value < 2.0) {
    zoomLevel.value = Math.min(2.0, zoomLevel.value + 0.1)
    sendSyncAction('scaling', zoomLevel.value)
  }
}
function zoomOut() {
  if (canOperate.value && zoomLevel.value > 0.5) {
    zoomLevel.value = Math.max(0.5, zoomLevel.value - 0.1)
    sendSyncAction('scaling', zoomLevel.value)
  }
}

function changePage(newPage) {
  if (newPage < 0 || newPage >= articles.length) return
  currentPage.value = newPage
  if (desensitizationEnabled.value) {
    applyDesensitization()
  } else {
    restoreOriginalContent()
  }
}

// ========== 控制权切换 ==========
function toggleControl() {
  if (role.value !== 'consultant') {
    ElMessage.warning('只有顾问可以切换控制权')
    return
  }
  if (!wsConnected.value) {
    ElMessage.warning('未连接协同')
    return
  }
  const targetHolderType = isController.value ? 'owner' : 'participant'
  isController.value = !isController.value
  const msg = { action: 'control_transfer', holderType: targetHolderType }
  ws.send(JSON.stringify(msg))
  console.log('【控制权切换】发送:', msg)
}

// ========== 客户发起共享 ==========
async function startSharing() {
  const userId = userStore.userBizId || 'admin'
  const token = getToken() || 'demo-token'
  const ownerType = role.value === 'customer' ? 'customer' : 'consultant'
  try {
    const res = await createCoSession({
      scope: 'single',
      resourceType: 'report',
      resourceId: 'RPT-DEMO',
      resourceInit: JSON.stringify({
        url: window.location.href,
        title: currentArticle.value.title,
        page_type: 'article',
        data: {
          currentPage: currentPage.value,
          totalPages: articles.length,
          scrollRatio: scrollRatio.value,
          zoomLevel: zoomLevel.value,
          desensitizationEnabled: desensitizationEnabled.value,
          pageTitle: currentArticle.value.title,
          chartIndices: []
        }
      }),
      ownerId: userId,
      ownerType: ownerType,
      userId: userId,
      token: token
    })
    if (res.code === 200) {
      const data = res.data
      shareInfo.roomId = data.roomId
      shareInfo.roomPwd = data.roomPwd
      shareInfo.sessionBizId = data.sessionBizId
      shareInfo.ownerId = userId
      sharingStatus.value = '讲解中'
      shareDialogVisible.value = true
      ElMessage.success('大屏讲解已开启，请分享共享码')
    } else {
      ElMessage.error(res.msg || '开启失败')
    }
  } catch (e) {
    shareInfo.roomId = 'room_' + Math.random().toString(36).substr(2, 8)
    shareInfo.roomPwd = Math.random().toString().slice(2, 8)
    shareInfo.sessionBizId = 'sess_' + Date.now()
    shareInfo.ownerId = userId
    sharingStatus.value = '讲解中'
    shareDialogVisible.value = true
    ElMessage.success('（模拟）大屏讲解已开启')
  }
}

// ========== 客户连接 WebSocket ==========
function connectAsCustomer() {
  if (!shareInfo.roomId) return
  shouldReconnect = true
  connectWebSocket(shareInfo.roomId, userStore.userBizId || 'CUST-001', 'owner')
}

// ========== 顾问加入 ==========
async function submitJoin() {
  if (!joinForm.roomPwd || !joinForm.roomId) {
    ElMessage.warning('请填写完整信息')
    return
  }
  try {
    const res = await joinCoSession({
      roomId: joinForm.roomId,
      roomPwd: joinForm.roomPwd,
      participantId: userStore.userBizId || 'CONS-001',
      participantType: 'consultant'
    })
    if (res.code === 200) {
      if (res.data?.sessionBizId) shareInfo.sessionBizId = res.data.sessionBizId
      if (res.data?.ownerId) shareInfo.ownerId = res.data.ownerId
      sharingStatus.value = '讲解中'
      ElMessage.success('加入成功')
      joinDialogVisible.value = false
      shouldReconnect = true
      connectWebSocket(joinForm.roomId, userStore.userBizId || 'CONS-001', 'participant', true)
    } else {
      ElMessage.error(res.msg || '加入失败')
    }
  } catch (error) {
    console.error('【join】接口异常:', error)
    ElMessage.error('加入失败，请稍后重试')
  }
}

// ========== WebSocket 连接 ==========
function connectWebSocket(roomId, userId, userType, sendControl = false) {
  if (ws) { try { ws.close() } catch (e) {}; ws = null }
  if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
  const wsUrl = `ws://139.224.145.34:9482/communication/api/ws/${roomId}?userId=${userId}&userType=${userType}`
  console.log('【WebSocket】连接地址:', wsUrl)
  ws = new WebSocket(wsUrl)
  ws.onopen = () => {
    wsConnected.value = true
    ElMessage.success('WebSocket 已连接')
    if (role.value === 'customer') {
      startWebRTCScreenShare(roomId)
    }
    if (sendControl && role.value === 'consultant') {
      ws.send(JSON.stringify({ action: 'control_transfer', holderType: 'participant' }))
      console.log('【顾问】发送 control_transfer 同步消息')
    }
  }
  ws.onmessage = (event) => {
    try { handleWebSocketMessage(JSON.parse(event.data)) } catch (e) { console.error('解析消息失败:', e) }
  }
  ws.onclose = () => {
    wsConnected.value = false
    closeWebRTC()
    if (shouldReconnect) {
      ElMessage.warning('WebSocket 断开，尝试重连...')
      if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
      wsReconnectTimer = setTimeout(() => connectWebSocket(roomId, userId, userType), 3000)
    } else {
      console.log('【WebSocket】主动关闭，不再重连')
      shouldReconnect = true
    }
  }
  ws.onerror = (err) => { console.error('WebSocket 错误:', err); wsConnected.value = false }
}

// ========== WebRTC 屏幕共享（客户）并复用流给录制 ==========
async function startWebRTCScreenShare(roomId) {
  try {
    if (pc) { pc.close(); pc = null }
    // ★ 获取屏幕流（只弹一次）
    const stream = await navigator.mediaDevices.getDisplayMedia({
      video: {
        displaySurface: 'browser',
        frameRate: { ideal: 30 },
        cursor: 'always'
      },
      audio: true
    })
    // 保存全局流供录制复用
    sharedStream = stream
    // ★ 立即启动录制（复用同一个流）
    if (shareInfo.sessionBizId) {
      startRecordingWithStream(stream)
    } else {
      console.warn('【录制】sessionBizId 未就绪，稍后重试')
    }

    // 创建 PeerConnection
    pc = new RTCPeerConnection({
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
    })
    stream.getTracks().forEach(track => pc.addTrack(track, stream))
    pc.onicecandidate = (event) => {
      if (event.candidate) {
        ws.send(JSON.stringify({
          action: 'webrtc_ice_candidate',
          roomId,
          candidate: event.candidate
        }))
      }
    }
    pc.onconnectionstatechange = () => {
      console.log('【WebRTC】连接状态:', pc.connectionState)
      if (pc.connectionState === 'connected') {
        ElMessage.success('屏幕共享已连接')
      } else if (pc.connectionState === 'disconnected' || pc.connectionState === 'failed') {
        ElMessage.warning('屏幕共享连接中断')
      }
    }
    const offer = await pc.createOffer()
    await pc.setLocalDescription(offer)
    ws.send(JSON.stringify({
      action: 'webrtc_offer',
      roomId,
      sdp: offer
    }))
    console.log('【WebRTC】屏幕共享已启动')
  } catch (error) {
    console.error('【WebRTC】启动屏幕共享失败:', error)
    ElMessage.error('启动屏幕共享失败: ' + error.message)
    if (pc) { pc.close(); pc = null }
  }
}

// ========== 录制功能（复用共享流） ==========
async function startRecordingWithStream(stream) {
  try {
    if (recordingStatus.value === 'recording') return
    if (!shareInfo.sessionBizId) {
      ElMessage.warning('未获取到协同会话ID，无法开始录制')
      return
    }
    const startRes = await startRecording({ bizType: 'co_session', bizId: shareInfo.sessionBizId })
    if (startRes.code !== 200) {
      ElMessage.error(startRes.msg || '创建录制任务失败')
      return
    }
    recordingTaskId.value = startRes.data
    console.log('【录制】任务创建成功，taskId:', recordingTaskId.value)

    // 直接使用传入的流
    const mimeTypes = ['video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm', 'video/mp4']
    let mimeType = mimeTypes.find(type => MediaRecorder.isTypeSupported(type)) || ''
    const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 2500000 })
    recordedChunks.value = []
    recorder.ondataavailable = (event) => {
      if (event.data && event.data.size > 0) recordedChunks.value.push(event.data)
    }
    recorder.onstop = () => console.log('【录制】MediaRecorder 停止，共', recordedChunks.value.length, '个数据块')
    recorder.onerror = (event) => { console.error('【录制】MediaRecorder 错误:', event); ElMessage.error('录制发生错误') }

    // 监听流结束（用户点击停止共享时，自动停止录制）
    if (stream) {
      const videoTrack = stream.getVideoTracks()[0]
      if (videoTrack) {
        videoTrack.addEventListener('ended', () => {
          if (recordingStatus.value === 'recording') {
            ElMessage.warning('屏幕共享被中断，录制将停止')
            stopRecordingAndUpload()
          }
        })
      }
    }

    mediaRecorder.value = recorder
    recorder.start(1000)
    recordingStatus.value = 'recording'
    recordingStartTime.value = Date.now()
    recordingDuration.value = 0
    if (durationTimer) clearInterval(durationTimer)
    durationTimer = setInterval(() => {
      recordingDuration.value = Math.floor((Date.now() - recordingStartTime.value) / 1000)
    }, 1000)
    ElMessage.success('开始录制屏幕')
  } catch (error) {
    console.error('【录制】启动失败:', error)
    ElMessage.error('录制启动失败: ' + error.message)
    recordingStatus.value = 'idle'
    recordingTaskId.value = null
  }
}

// ========== 停止录制并上传 ==========
async function stopRecordingAndUpload() {
  if (recordingStatus.value !== 'recording') {
    ElMessage.warning('当前没有正在进行的录制')
    return
  }
  if (!mediaRecorder.value) {
    ElMessage.warning('录制器不存在')
    return
  }
  uploading.value = true
  try {
    mediaRecorder.value.stop()
    if (durationTimer) { clearInterval(durationTimer); durationTimer = null }
    await new Promise(resolve => setTimeout(resolve, 500))
    if (recordedChunks.value.length === 0) {
      ElMessage.warning('没有录制到任何数据')
      recordingStatus.value = 'idle'
      uploading.value = false
      return
    }
    const blob = new Blob(recordedChunks.value, { type: mediaRecorder.value.mimeType || 'video/webm' })
    console.log('【录制】文件大小:', (blob.size / 1024 / 1024).toFixed(2), 'MB')
    const fileExtension = blob.type.includes('mp4') ? 'mp4' : 'webm'
    const fileName = `recording_${recordingTaskId.value}_${Date.now()}.${fileExtension}`
    const file = new File([blob], fileName, { type: blob.type })
    const stopRes = await stopRecording(recordingTaskId.value, file)
    if (stopRes.code === 200) {
      const data = stopRes.data
      recordedFileUrl.value = data.fileUrl || ''
      recordingStatus.value = 'stopped'
      ElMessage.success('录制文件上传成功！')
      console.log('【录制】上传成功，fileUrl:', recordedFileUrl.value)
      try {
        const queryRes = await queryRecording(recordingTaskId.value)
        if (queryRes.code === 200) console.log('【录制】查询结果:', queryRes.data)
      } catch (e) { console.warn('查询录制信息失败:', e) }
    } else {
      ElMessage.error(stopRes.msg || '上传失败')
      recordingStatus.value = 'idle'
    }
  } catch (error) {
    console.error('【录制】停止并上传失败:', error)
    ElMessage.error('停止录制失败: ' + (error.message || '未知错误'))
    recordingStatus.value = 'idle'
  } finally {
    uploading.value = false
    mediaRecorder.value = null
    recordedChunks.value = []
    recordingTaskId.value = null
  }
}

// ========== 顾问接收屏幕（自动响应 Offer） ==========
async function handleWebRTCOffer(roomId, sdp) {
  try {
    if (pc) { pc.close(); pc = null }
    pc = new RTCPeerConnection({
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
    })
    pc.onicecandidate = (event) => {
      if (event.candidate) {
        ws.send(JSON.stringify({
          action: 'webrtc_ice_candidate',
          roomId,
          candidate: event.candidate
        }))
      }
    }
    pc.ontrack = (event) => {
      if (remoteVideoRef.value) {
        remoteVideoRef.value.srcObject = event.streams[0]
        remoteVideoRef.value.play()
        isReceivingScreen.value = true
        console.log('【WebRTC】开始接收屏幕流')
      }
    }
    pc.onconnectionstatechange = () => {
      console.log('【WebRTC】连接状态:', pc.connectionState)
      if (pc.connectionState === 'connected') {
        ElMessage.success('已接收屏幕共享')
      } else if (pc.connectionState === 'disconnected' || pc.connectionState === 'failed') {
        ElMessage.warning('屏幕共享连接中断')
        isReceivingScreen.value = false
      }
    }
    await pc.setRemoteDescription(new RTCSessionDescription(sdp))
    const answer = await pc.createAnswer()
    await pc.setLocalDescription(answer)
    ws.send(JSON.stringify({
      action: 'webrtc_answer',
      roomId,
      sdp: answer
    }))
    console.log('【WebRTC】回答已发送')
  } catch (error) {
    console.error('【WebRTC】处理 Offer 失败:', error)
    ElMessage.error('接收屏幕共享失败: ' + error.message)
  }
}

async function handleWebRTCAnswer(sdp) {
  if (!pc) return
  try {
    await pc.setRemoteDescription(new RTCSessionDescription(sdp))
    console.log('【WebRTC】远程描述已设置')
  } catch (error) {
    console.error('【WebRTC】设置 Answer 失败:', error)
  }
}

async function handleWebRTCIceCandidate(candidate) {
  if (!pc) return
  try {
    await pc.addIceCandidate(new RTCIceCandidate(candidate))
  } catch (error) {
    console.error('【WebRTC】添加 ICE 候选失败:', error)
  }
}

function closeWebRTC() {
  if (pc) { pc.close(); pc = null }
  isReceivingScreen.value = false
  if (remoteVideoRef.value) {
    remoteVideoRef.value.srcObject = null
  }
  console.log('【WebRTC】连接已关闭')
}

// ========== 处理 WebSocket 消息 ==========
async function handleWebSocketMessage(msg) {
  console.log('【WS消息】', msg)
  if (msg.action === 'control_transfer') {
    const currentUserType = role.value === 'customer' ? 'owner' : 'participant'
    const currentUserId = userStore.userBizId || 'admin'
    isController.value = (msg.holderType === currentUserType && msg.holderId === currentUserId)
    ElMessage.info(`控制权已切换至${msg.holderType === 'owner' ? '客户' : '顾问'}`)
    return
  }
  if (msg.action === 'init_sync') {
    const currentUserType = role.value === 'customer' ? 'owner' : 'participant'
    const currentUserId = userStore.userBizId || 'admin'
    isController.value = (msg.holderType === currentUserType && msg.holderId === currentUserId)
    if (msg.currentPage) {
      const data = msg.currentPage.data
      currentPage.value = data.currentPage || 0
      scrollRatio.value = data.scrollRatio || 0
      zoomLevel.value = data.zoomLevel || 1
      desensitizationEnabled.value = data.desensitizationEnabled || false
      applyScroll(scrollRatio.value)
      if (desensitizationEnabled.value) applyDesensitization()
      else restoreOriginalContent()
      ElMessage.success('状态已同步')
    }
    return
  }
  if (msg.action === 'turn_the_page' && msg.currentPage) {
    currentPage.value = msg.currentPage.data.currentPage
    scrollRatio.value = 0
    applyScroll(0)
    if (desensitizationEnabled.value) applyDesensitization()
    else restoreOriginalContent()
    return
  }
  if (msg.action === 'scroll' && msg.currentPage) {
    scrollRatio.value = msg.currentPage.data.scrollRatio
    applyScroll(scrollRatio.value)
    return
  }
  if (msg.action === 'scaling' && msg.currentPage) {
    zoomLevel.value = msg.currentPage.data.zoomLevel
    return
  }
  if (msg.action === 'desensitization_switch') {
    desensitizationEnabled.value = msg.enabled
    if (desensitizationEnabled.value) await applyDesensitization()
    else restoreOriginalContent()
    ElMessage.info(`脱敏${msg.enabled ? '开启' : '关闭'}`)
    return
  }
  if (msg.action === 'end_sharing') {
    console.log('【end_sharing】收到结束共享消息，当前录制状态:', recordingStatus.value)
    ElMessage.warning('共享已结束')
    if (recordingStatus.value === 'recording') {
      console.log('【end_sharing】正在录制，开始停止并上传...')
      await stopRecordingAndUpload()
    }
    shouldReconnect = false
    if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
    if (ws) { ws.close(); ws = null }
    wsConnected.value = false
    sharingStatus.value = '已结束'
    closeWebRTC()
    return
  }
  // WebRTC 信令
  if (msg.action === 'webrtc_offer') {
    if (role.value === 'consultant') {
      await handleWebRTCOffer(msg.roomId, msg.sdp)
    }
    return
  }
  if (msg.action === 'webrtc_answer') {
    if (role.value === 'customer') {
      await handleWebRTCAnswer(msg.sdp)
    }
    return
  }
  if (msg.action === 'webrtc_ice_candidate') {
    await handleWebRTCIceCandidate(msg.candidate)
    return
  }
}

// ========== 应用滚动 ==========
function applyScroll(ratio) {
  const el = articleContentRef.value
  if (el) {
    const wrapper = el.closest('.article-wrapper')
    if (wrapper) {
      const maxScroll = wrapper.scrollHeight - wrapper.clientHeight
      if (maxScroll > 0) wrapper.scrollTop = ratio * maxScroll
    }
  }
}

// ========== 发送同步操作 ==========
function sendSyncAction(action, value) {
  if (!wsConnected.value || !isController.value) {
    ElMessage.warning('无控制权或未连接')
    return
  }
  const pageData = {
    url: window.location.href,
    title: currentArticle.value.title,
    page_type: 'article',
    data: {
      currentPage: currentPage.value,
      totalPages: articles.length,
      scrollRatio: scrollRatio.value,
      zoomLevel: zoomLevel.value,
      desensitizationEnabled: desensitizationEnabled.value,
      pageTitle: currentArticle.value.title,
      chartIndices: []
    }
  }
  const sendMsg = { action, currentPage: pageData }
  if (action === 'desensitization_switch') {
    sendMsg.enabled = value
    pageData.data.desensitizationEnabled = value
  } else if (action === 'turn_the_page') {
    pageData.data.currentPage = currentPage.value
  } else if (action === 'scroll') {
    scrollRatio.value = value
    pageData.data.scrollRatio = value
  } else if (action === 'scaling') {
    zoomLevel.value = value
    pageData.data.zoomLevel = value
  }
  ws.send(JSON.stringify(sendMsg))
}

// ========== 客户结束共享 ==========
async function handleEndSharing() {
  if (role.value !== 'customer') {
    ElMessage.warning('只有客户可以结束共享')
    return
  }
  try {
    await proxy.$modal.confirm('确认结束共享？')
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ action: 'end_sharing' }))
      console.log('【客户】已发送 end_sharing 消息')
    }
    await endCoSession({ roomId: shareInfo.roomId })
    ElMessage.success('共享已结束')
    shouldReconnect = false
    if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
    if (ws) { ws.close(); ws = null }
    wsConnected.value = false
    sharingStatus.value = '已结束'
    closeWebRTC()
    if (sharedStream) {
      sharedStream.getTracks().forEach(track => track.stop())
      sharedStream = null
    }
  } catch (error) {
    if (error !== 'cancel') {
      console.error('结束共享失败:', error)
      ElMessage.error('结束共享失败，请重试')
    }
  }
}

// ========== 工具 ==========
function copyCode() {
  if (shareInfo.roomPwd) {
    navigator.clipboard?.writeText(shareInfo.roomPwd).then(() => ElMessage.success('已复制'))
        .catch(() => {
          const input = document.createElement('input')
          input.value = shareInfo.roomPwd
          document.body.appendChild(input)
          input.select()
          document.execCommand('copy')
          document.body.removeChild(input)
          ElMessage.success('已复制')
        })
  }
}

// ========== 录制 UI 切换 ==========
function toggleRecording() {
  if (recordingStatus.value === 'recording') {
    stopRecordingAndUpload()
  } else if (recordingStatus.value === 'idle') {
    ElMessage.info('录制将在共享屏幕时自动开始')
  } else if (recordingStatus.value === 'stopped') {
    if (recordedFileUrl.value) window.open(recordedFileUrl.value, '_blank')
  }
}

function formatDuration(seconds) {
  if (!seconds || seconds < 0) return '00:00'
  const mins = Math.floor(seconds / 60)
  const secs = seconds % 60
  return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`
}

// ==================== 脱敏核心逻辑 ====================
async function applyDesensitization() {
  if (!desensitizationEnabled.value) {
    restoreOriginalContent()
    return
  }
  const originalHtml = originalContentMap.get(currentPage.value)
  if (!originalHtml) return
  try {
    const resourceType = 'report'
    const resourceId = route.query.reportId || null
    const res = await getDesensitizationRules(resourceType, resourceId)
    if (res.code !== 200 || !res.data || res.data.length === 0) {
      ElMessage.warning('未获取到脱敏规则')
      return
    }
    let processedHtml = originalHtml
    res.data.forEach(rule => {
      const fieldPath = rule.fieldPath || ''
      if (fieldPath.includes('name')) {
        const tempDiv = document.createElement('div')
        tempDiv.innerHTML = processedHtml
        const nameEls = tempDiv.querySelectorAll('.desensitize-name')
        nameEls.forEach(el => {
          const originalText = el.textContent
          const maskedText = applyMask(originalText, rule.maskType, rule.maskConfig)
          el.textContent = maskedText
        })
        processedHtml = tempDiv.innerHTML
      } else if (fieldPath.includes('phone')) {
        const tempDiv = document.createElement('div')
        tempDiv.innerHTML = processedHtml
        const phoneEls = tempDiv.querySelectorAll('.desensitize-phone')
        phoneEls.forEach(el => {
          const originalText = el.textContent
          const maskedText = applyMask(originalText, rule.maskType, rule.maskConfig)
          el.textContent = maskedText
        })
        processedHtml = tempDiv.innerHTML
      }
    })
    const currentArticle = articles[currentPage.value]
    if (currentArticle) currentArticle.content = processedHtml
  } catch (error) {
    console.error('应用脱敏失败:', error)
    ElMessage.error('脱敏处理失败')
  }
}

function restoreOriginalContent() {
  const originalHtml = originalContentMap.get(currentPage.value)
  if (originalHtml) {
    const currentArticle = articles[currentPage.value]
    if (currentArticle) currentArticle.content = originalHtml
  }
}

function applyMask(text, maskType, maskConfigStr) {
  if (!text) return text
  let config = {}
  try { config = maskConfigStr ? JSON.parse(maskConfigStr) : {} } catch (e) {}
  const replaceChar = config.replace_char || '*'
  switch (maskType) {
    case 'mask': {
      const suffix = config.suffix || 1
      if (text.length <= suffix) return text
      const prefixLen = text.length - suffix
      return replaceChar.repeat(prefixLen) + text.slice(-suffix)
    }
    case 'hide': return replaceChar.repeat(text.length)
    case 'replace': return replaceChar.repeat(text.length)
    case 'partial': {
      const prefix = config.prefix || 0
      const suffix = config.suffix || 0
      if (text.length <= prefix + suffix) return text
      const middleLen = text.length - prefix - suffix
      return text.slice(0, prefix) + replaceChar.repeat(middleLen) + text.slice(-suffix)
    }
    default: return text
  }
}

async function toggleDesensitization() {
  desensitizationEnabled.value = !desensitizationEnabled.value
  await applyDesensitization()
  if (ws && ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ action: 'desensitization_switch', enabled: desensitizationEnabled.value }))
  }
}

// ========== 生命周期 ==========
onMounted(() => {
  articles.forEach((article, index) => {
    originalContentMap.set(index, article.content)
  })
  const wrapper = document.querySelector('.article-wrapper')
  if (wrapper) {
    wrapper.addEventListener('scroll', () => {
      if (wsConnected.value && isController.value) {
        const maxScroll = wrapper.scrollHeight - wrapper.clientHeight
        if (maxScroll > 0) {
          const ratio = wrapper.scrollTop / maxScroll
          if (Math.abs(ratio - scrollRatio.value) > 0.01) {
            scrollRatio.value = ratio
            sendSyncAction('scroll', ratio)
          }
        }
      }
    })
  }
})

onBeforeUnmount(() => {
  shouldReconnect = false
  if (wsReconnectTimer) clearTimeout(wsReconnectTimer)
  if (ws) { try { ws.close() } catch (e) {}; ws = null }
  closeWebRTC()
  if (durationTimer) clearInterval(durationTimer)
  if (mediaRecorder.value && recordingStatus.value === 'recording') {
    try { mediaRecorder.value.stop() } catch (e) {}
  }
  if (sharedStream) {
    sharedStream.getTracks().forEach(track => track.stop())
    sharedStream = null
  }
  if (recordingStream.value) {
    recordingStream.value.getTracks().forEach(track => track.stop())
    recordingStream.value = null
  }
  originalContentMap.clear()
})
</script>

<style scoped>
.reader-container {
  position: relative;
  height: 100vh;
  background: #f7f9fc;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.reader-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 20px;
  background: #fff;
  border-bottom: 1px solid #e8ecf1;
  flex-shrink: 0;
  z-index: 10;
}
.header-left { display: flex; align-items: center; gap: 12px; }
.role-badge {
  font-size: 13px;
  padding: 2px 10px;
  border-radius: 12px;
  font-weight: 500;
}
.role-badge.customer { background: #e6f7ff; color: #1890ff; }
.role-badge.consultant { background: #f6ffed; color: #52c41a; }
.header-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1;
}
.header-center .title {
  font-size: 16px;
  font-weight: 600;
  color: #1a1a2e;
  max-width: 300px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.header-center .page-info { font-size: 12px; color: #999; }
.header-right { display: flex; align-items: center; gap: 10px; }

.reader-body {
  flex: 1;
  overflow: hidden;
  padding: 16px 20px;
  position: relative;
}
.article-wrapper {
  height: 100%;
  overflow-y: auto;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.06);
  padding: 20px 24px;
  transition: transform 0.2s ease;
}
.article-content {
  line-height: 1.8;
  font-size: 15px;
  color: #333;
}
.article-content h2 {
  font-size: 20px;
  margin-bottom: 12px;
  color: #1a1a2e;
}
.article-content p { margin-bottom: 12px; }
.article-content img {
  max-width: 100%;
  border-radius: 8px;
  margin: 12px 0;
}
.reader-footer {
  display: flex;
  align-items: center;
  padding: 12px 24px 20px;
  background: #fff;
  border-top: 1px solid #e8ecf1;
  flex-shrink: 0;
}
.zoom-controls {
  position: fixed;
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  background: rgba(255,255,255,0.85);
  padding: 8px;
  border-radius: 24px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  backdrop-filter: blur(4px);
  z-index: 20;
}
.zoom-level { font-size: 12px; font-weight: 500; color: #333; }

/* 悬浮按钮组 */
.fab-group {
  position: fixed;
  bottom: 30px;
  right: 30px;
  z-index: 30;
  display: flex;
  flex-direction: column-reverse;
  gap: 12px;
  align-items: center;
}
.fab-btn {
  width: 60px;
  height: 60px;
  border: none;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
  transition: all 0.3s;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  padding: 0;
}
.fab-btn:hover { transform: scale(1.05); }
.fab-btn-text {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  white-space: pre-line;
  color: #fff;
}
.fab-share { background: #1890ff; }
.fab-share:hover { background: #40a9ff; }
.fab-end { background: #ff4d4f; }
.fab-end:hover { background: #ff7875; }
.fab-desensitize { background: #52c41a; }
.fab-desensitize:hover { background: #73d13d; }
.fab-join { background: #1890ff; }
.fab-join:hover { background: #40a9ff; }
.fab-authorize { background: #faad14; }
.fab-authorize:hover { background: #ffd666; }
.fab-revoke { background: #ff4d4f; }
.fab-revoke:hover { background: #ff7875; }

/* 视频容器 */
.video-container {
  position: relative;
  width: 100%;
  height: 100%;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.video-container video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* 视频覆盖层 */
.controls-overlay {
  position: absolute;
  bottom: 30px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 20px;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(8px);
  padding: 10px 24px;
  border-radius: 40px;
  border: 1px solid rgba(255,255,255,0.1);
  z-index: 10;
  color: white;
}
.nav-controls {
  display: flex;
  align-items: center;
  gap: 12px;
}
.nav-btn, .zoom-btn {
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.2);
  color: white;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  cursor: pointer;
  transition: all 0.2s;
  font-size: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.nav-btn:not(.disabled):hover, .zoom-btn:not(:disabled):hover {
  background: rgba(255,255,255,0.25);
}
.nav-btn.disabled, .zoom-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
.page-text {
  color: rgba(255,255,255,0.8);
  font-size: 14px;
  min-width: 60px;
  text-align: center;
}
.zoom-controls-overlay {
  display: flex;
  align-items: center;
  gap: 6px;
}
.zoom-controls-overlay span {
  font-size: 13px;
  min-width: 36px;
  text-align: center;
}
.status-tip {
  font-size: 13px;
  color: rgba(255,255,255,0.6);
  padding-left: 16px;
  border-left: 1px solid rgba(255,255,255,0.1);
}

.customer-info {
  background: #f6ffed;
  border: 1px solid #b7eb8f;
  border-radius: 8px;
  padding: 12px 16px;
  margin: 12px 0;
}
.customer-info p { margin: 4px 0; }

.article-wrapper::-webkit-scrollbar { width: 4px; }
.article-wrapper::-webkit-scrollbar-track { background: transparent; }
.article-wrapper::-webkit-scrollbar-thumb { background: #d0d5dd; border-radius: 4px; }
</style>