4052 lines
142 KiB
Vue
4052 lines
142 KiB
Vue
<template>
|
||
<div class="admin-system">
|
||
<!-- 内容区域 -->
|
||
<div class="admin-container">
|
||
<!-- 左侧主导航 -->
|
||
<aside class="admin-sidebar-main">
|
||
<div class="sidebar-header">
|
||
<div class="logo" @click="backToMain">
|
||
<img src="/logo.jpg" alt="Logo" class="logo-img" />
|
||
<span class="logo-text">管理后台</span>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="main-nav">
|
||
<div class="section-title">系统管理</div>
|
||
<div
|
||
v-for="item in navItems"
|
||
:key="item.key"
|
||
class="main-nav-item"
|
||
:class="{ active: activeNav === item.key }"
|
||
@click="activeNav = item.key"
|
||
>
|
||
<el-icon>
|
||
<Setting v-if="item.key === 'platform'" />
|
||
<Document v-else-if="item.key === 'bidding'" />
|
||
<Service v-else-if="item.key === 'service'" />
|
||
</el-icon>
|
||
<span>{{ item.label }}</span>
|
||
</div>
|
||
|
||
<div class="nav-divider"></div>
|
||
|
||
<div class="section-title">个人设置</div>
|
||
<div
|
||
class="main-nav-item"
|
||
:class="{ active: activeNav === 'profile' }"
|
||
@click="activeNav = 'profile'"
|
||
>
|
||
<el-icon><User /></el-icon>
|
||
<span>个人中心</span>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- 用户信息和返回按钮 -->
|
||
<div class="sidebar-footer">
|
||
<div class="user-section">
|
||
<div class="user-avatar" @click="goToProfile">
|
||
<img src="/avatar.svg" alt="User" />
|
||
</div>
|
||
<span class="user-name">李志鹏</span>
|
||
<el-tooltip content="返回主系统" placement="top">
|
||
<div class="back-icon" @click="backToMain">
|
||
<el-icon><Back /></el-icon>
|
||
</div>
|
||
</el-tooltip>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- 左侧子级导航(根据不同系统显示不同内容) -->
|
||
<aside class="admin-sidebar-sub">
|
||
<!-- 平台管理后台的子导航 -->
|
||
<nav v-if="activeNav === 'platform'" class="sub-nav">
|
||
<div class="sub-nav-title">平台管理</div>
|
||
<div
|
||
v-for="item in platformSubNav"
|
||
:key="item.key"
|
||
class="sub-nav-item"
|
||
:class="{ active: activeSubNav === item.key }"
|
||
@click="activeSubNav = item.key"
|
||
>
|
||
<el-icon>
|
||
<DataLine v-if="item.key === 'dashboard'" />
|
||
<User v-else-if="item.key === 'users'" />
|
||
<Document v-else-if="item.key === 'knowledge'" />
|
||
<Setting v-else-if="item.key === 'settings'" />
|
||
</el-icon>
|
||
<span>{{ item.label }}</span>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- 智能标书管理后台的子导航 -->
|
||
<nav v-if="activeNav === 'bidding'" class="sub-nav">
|
||
<div class="sub-nav-title">标书管理</div>
|
||
<div
|
||
v-for="item in biddingSubNav"
|
||
:key="item.key"
|
||
class="sub-nav-item"
|
||
:class="{ active: activeSubNav === item.key }"
|
||
@click="activeSubNav = item.key"
|
||
>
|
||
<el-icon>
|
||
<DataLine v-if="item.key === 'dashboard'" />
|
||
<Document v-else-if="item.key === 'projects'" />
|
||
<Files v-else-if="item.key === 'templates'" />
|
||
<Setting v-else-if="item.key === 'settings'" />
|
||
</el-icon>
|
||
<span>{{ item.label }}</span>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- 泰豪小电管理后台的子导航 -->
|
||
<nav v-if="activeNav === 'service'" class="sub-nav">
|
||
<div class="sub-nav-title">客服管理</div>
|
||
<template v-for="item in serviceSubNav" :key="item.key">
|
||
<!-- 普通导航项 -->
|
||
<div
|
||
v-if="!item.children"
|
||
class="sub-nav-item"
|
||
:class="{ active: activeSubNav === item.key }"
|
||
@click="activeSubNav = item.key"
|
||
>
|
||
<el-icon>
|
||
<DataLine v-if="item.icon === 'DataLine'" />
|
||
<Document v-else-if="item.icon === 'Document'" />
|
||
<Tickets v-else-if="item.icon === 'Tickets'" />
|
||
<ChatDotRound v-else-if="item.icon === 'ChatDotRound'" />
|
||
<Service v-else-if="item.icon === 'Service'" />
|
||
<List v-else-if="item.icon === 'List'" />
|
||
<User v-else-if="item.icon === 'User'" />
|
||
</el-icon>
|
||
<span>{{ item.label }}</span>
|
||
</div>
|
||
|
||
<!-- 带子级的导航项 -->
|
||
<div v-else class="sub-nav-group">
|
||
<div
|
||
class="sub-nav-item has-children"
|
||
:class="{ expanded: logNavExpanded }"
|
||
@click="logNavExpanded = !logNavExpanded"
|
||
>
|
||
<el-icon><List /></el-icon>
|
||
<span>{{ item.label }}</span>
|
||
<el-icon class="arrow-icon"><ArrowDown /></el-icon>
|
||
</div>
|
||
<div v-show="logNavExpanded" class="sub-nav-children">
|
||
<div
|
||
v-for="child in item.children"
|
||
:key="child.key"
|
||
class="sub-nav-child-item"
|
||
:class="{ active: activeSubNav === child.key }"
|
||
@click="activeSubNav = child.key"
|
||
>
|
||
<span>{{ child.label }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</nav>
|
||
</aside>
|
||
|
||
<!-- 主内容区 -->
|
||
<div class="admin-main">
|
||
<!-- 平台管理后台 -->
|
||
<div v-if="activeNav === 'platform'" class="admin-content">
|
||
<div class="content-header">
|
||
<h1>平台管理后台</h1>
|
||
<p class="subtitle">AI数智化平台系统管理</p>
|
||
</div>
|
||
|
||
<div class="dashboard-cards">
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background: #409eff;">
|
||
<el-icon><User /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-label">用户总数</div>
|
||
<div class="stat-value">1,248</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background: #67c23a;">
|
||
<el-icon><Document /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-label">知识库文档</div>
|
||
<div class="stat-value">856</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background: #e6a23c;">
|
||
<el-icon><ChatDotRound /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-label">今日对话</div>
|
||
<div class="stat-value">342</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background: #f56c6c;">
|
||
<el-icon><Warning /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-label">待处理告警</div>
|
||
<div class="stat-value">12</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<el-card style="margin-top: 24px;">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>系统配置</span>
|
||
<el-button type="primary" size="small">保存配置</el-button>
|
||
</div>
|
||
</template>
|
||
<el-form label-width="150px">
|
||
<el-form-item label="系统名称">
|
||
<el-input v-model="platformConfig.systemName" />
|
||
</el-form-item>
|
||
<el-form-item label="AI模型">
|
||
<el-select v-model="platformConfig.aiModel" style="width: 100%;">
|
||
<el-option label="GPT-4" value="gpt-4" />
|
||
<el-option label="Claude-3" value="claude-3" />
|
||
<el-option label="文心一言" value="ernie" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="最大并发数">
|
||
<el-input-number v-model="platformConfig.maxConcurrency" :min="1" :max="1000" />
|
||
</el-form-item>
|
||
<el-form-item label="会话超时(分钟)">
|
||
<el-input-number v-model="platformConfig.sessionTimeout" :min="5" :max="120" />
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
</div>
|
||
|
||
<!-- 智能标书管理后台 -->
|
||
<div v-if="activeNav === 'bidding'" class="admin-content">
|
||
<div class="content-header">
|
||
<h1>智能标书管理后台</h1>
|
||
<p class="subtitle">招标助手系统管理</p>
|
||
</div>
|
||
|
||
<div class="dashboard-cards">
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background: #409eff;">
|
||
<el-icon><Document /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-label">标书总数</div>
|
||
<div class="stat-value">486</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background: #67c23a;">
|
||
<el-icon><Select /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-label">中标率</div>
|
||
<div class="stat-value">78.5%</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background: #e6a23c;">
|
||
<el-icon><Clock /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-label">进行中项目</div>
|
||
<div class="stat-value">23</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background: #909399;">
|
||
<el-icon><Files /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-label">模板库</div>
|
||
<div class="stat-value">156</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<el-card style="margin-top: 24px;">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>标书模板管理</span>
|
||
<el-button type="primary" size="small">新增模板</el-button>
|
||
</div>
|
||
</template>
|
||
<el-table :data="biddingTemplates" style="width: 100%">
|
||
<el-table-column prop="name" label="模板名称" />
|
||
<el-table-column prop="category" label="类别" width="120" />
|
||
<el-table-column prop="usage" label="使用次数" width="100" align="center" />
|
||
<el-table-column prop="updateTime" label="更新时间" width="180" />
|
||
<el-table-column label="操作" width="150">
|
||
<template>
|
||
<el-button type="primary" link size="small">编辑</el-button>
|
||
<el-button type="danger" link size="small">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-card>
|
||
</div>
|
||
|
||
<!-- 泰豪小电管理后台 -->
|
||
<div v-if="activeNav === 'service'" class="admin-content">
|
||
<!-- 运营看板 -->
|
||
<template v-if="activeSubNav === 'dashboard'">
|
||
<div class="content-header">
|
||
<h1>数据概览</h1>
|
||
<p class="subtitle">泰豪小电智能客服运营数据</p>
|
||
</div>
|
||
|
||
<!-- 核心指标卡片 -->
|
||
<div class="dashboard-stats">
|
||
<div class="stat-card clickable" @click="activeSubNav = 'conversation'">
|
||
<div class="stat-icon" style="background: linear-gradient(135deg, #409eff, #66b1ff);">
|
||
<el-icon><ChatDotRound /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-value">1,258</div>
|
||
<div class="stat-label">咨询次数</div>
|
||
<div class="stat-trend up">
|
||
<el-icon><Top /></el-icon>
|
||
<span>较昨日 +12.5%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card clickable" @click="goToTickets('pending')">
|
||
<div class="stat-icon" style="background: linear-gradient(135deg, #e6a23c, #f5c06a);">
|
||
<el-icon><Clock /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-value">{{ dashboardData.pendingTickets }}</div>
|
||
<div class="stat-label">待处理工单</div>
|
||
<div class="stat-trend">
|
||
<span>点击查看详情</span>
|
||
<el-icon><Right /></el-icon>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card clickable" @click="goToTickets('completed')">
|
||
<div class="stat-icon" style="background: linear-gradient(135deg, #67c23a, #95d475);">
|
||
<el-icon><Select /></el-icon>
|
||
</div>
|
||
<div class="stat-info">
|
||
<div class="stat-value">{{ dashboardData.completedTickets }}</div>
|
||
<div class="stat-label">已处理工单</div>
|
||
<div class="stat-trend">
|
||
<span>点击查看详情</span>
|
||
<el-icon><Right /></el-icon>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 问题分类统计和产品词云 -->
|
||
<div class="dashboard-charts">
|
||
<el-card class="chart-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>问题分类统计</span>
|
||
<el-radio-group v-model="questionStatPeriod" size="small">
|
||
<el-radio-button label="today">今日</el-radio-button>
|
||
<el-radio-button label="week">本周</el-radio-button>
|
||
<el-radio-button label="month">本月</el-radio-button>
|
||
</el-radio-group>
|
||
</div>
|
||
</template>
|
||
<div class="question-stats">
|
||
<div
|
||
v-for="item in questionCategories"
|
||
:key="item.name"
|
||
class="question-stat-item clickable"
|
||
@click="goToConversationByCategory(item.name)"
|
||
>
|
||
<div class="stat-bar-header">
|
||
<span class="stat-name">{{ item.name }}</span>
|
||
<span class="stat-count">{{ item.count }} 次</span>
|
||
</div>
|
||
<div class="stat-bar">
|
||
<div
|
||
class="stat-bar-fill"
|
||
:style="{ width: item.percent + '%', background: item.color }"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card class="chart-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>涉及产品词云</span>
|
||
<el-button type="primary" link size="small" @click="activeSubNav = 'conversation'">
|
||
查看详情
|
||
<el-icon><Right /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
<div class="product-cloud">
|
||
<span
|
||
v-for="(product, index) in productCloudData"
|
||
:key="index"
|
||
class="cloud-tag"
|
||
:style="{
|
||
fontSize: product.size + 'px',
|
||
color: product.color,
|
||
opacity: 0.7 + product.weight * 0.3
|
||
}"
|
||
@click="goToConversationByProduct(product.name)"
|
||
>
|
||
{{ product.name }}
|
||
</span>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
|
||
<!-- 快捷入口 -->
|
||
<el-card class="quick-entry-card">
|
||
<template #header>
|
||
<span>快捷入口</span>
|
||
</template>
|
||
<div class="quick-entries">
|
||
<div class="quick-entry-item" @click="activeSubNav = 'knowledge'">
|
||
<el-icon class="entry-icon" style="background: #409eff;"><Document /></el-icon>
|
||
<span>知识库管理</span>
|
||
</div>
|
||
<div class="quick-entry-item" @click="activeSubNav = 'tickets'">
|
||
<el-icon class="entry-icon" style="background: #e6a23c;"><Tickets /></el-icon>
|
||
<span>工单管理</span>
|
||
</div>
|
||
<div class="quick-entry-item" @click="activeSubNav = 'conversation'">
|
||
<el-icon class="entry-icon" style="background: #67c23a;"><ChatDotRound /></el-icon>
|
||
<span>对话数据</span>
|
||
</div>
|
||
<div class="quick-entry-item" @click="activeSubNav = 'agent'">
|
||
<el-icon class="entry-icon" style="background: #909399;"><Service /></el-icon>
|
||
<span>智能体管理</span>
|
||
</div>
|
||
<div class="quick-entry-item" @click="activeSubNav = 'crm'">
|
||
<el-icon class="entry-icon" style="background: #f56c6c;"><User /></el-icon>
|
||
<span>CRM管理</span>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</template>
|
||
|
||
<!-- 知识库管理 -->
|
||
<template v-if="activeSubNav === 'knowledge'">
|
||
<div class="content-header">
|
||
<div class="header-title">
|
||
<h1>知识库管理</h1>
|
||
<p class="subtitle">管理外部和内部知识库文档</p>
|
||
</div>
|
||
<el-button type="primary" @click="showKbUploadDialog = true">
|
||
<el-icon><Upload /></el-icon>
|
||
上传文档
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-card>
|
||
<el-tabs v-model="activeKbType">
|
||
<el-tab-pane label="外部知识库" name="external">
|
||
<p class="tab-desc">面向客户的知识库内容,包含设备操作指南、故障解决方案等</p>
|
||
|
||
<div class="kb-categories">
|
||
<div
|
||
v-for="cat in externalKbCategories"
|
||
:key="cat.key"
|
||
class="kb-category-card"
|
||
:class="{ active: activeExternalKbCat === cat.key }"
|
||
@click="activeExternalKbCat = cat.key"
|
||
>
|
||
<el-icon :style="{ color: cat.color }"><component :is="cat.icon" /></el-icon>
|
||
<span class="cat-name">{{ cat.name }}</span>
|
||
<span class="cat-count">{{ cat.count }} 个文件</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="kb-files-section">
|
||
<div class="section-toolbar">
|
||
<h3>{{ currentExternalCatName }}</h3>
|
||
<el-input
|
||
v-model="externalKbSearch"
|
||
placeholder="搜索文件名"
|
||
style="width: 240px;"
|
||
:prefix-icon="Search"
|
||
clearable
|
||
/>
|
||
</div>
|
||
|
||
<el-table :data="filteredExternalKbFiles" style="width: 100%">
|
||
<el-table-column prop="name" label="文件名" min-width="280">
|
||
<template #default="{ row }">
|
||
<div class="file-name-cell">
|
||
<el-icon class="file-icon"><Document /></el-icon>
|
||
<span>{{ row.name }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="uploader" label="上传人员" width="120" />
|
||
<el-table-column prop="uploadTime" label="上传时间" width="180" />
|
||
<el-table-column label="操作" width="180" align="center">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link size="small" @click="previewKbFile(row)">
|
||
<el-icon><View /></el-icon>预览
|
||
</el-button>
|
||
<el-button type="success" link size="small" @click="downloadKbFile(row)">
|
||
<el-icon><Download /></el-icon>下载
|
||
</el-button>
|
||
<el-button type="danger" link size="small" @click="deleteKbFile(row, 'external')">
|
||
<el-icon><Delete /></el-icon>删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane label="内部知识库" name="internal">
|
||
<p class="tab-desc">内部技术资料与服务规范,仅供内部员工使用</p>
|
||
|
||
<div class="kb-categories">
|
||
<div
|
||
v-for="cat in internalKbCategories"
|
||
:key="cat.key"
|
||
class="kb-category-card"
|
||
:class="{ active: activeInternalKbCat === cat.key }"
|
||
@click="activeInternalKbCat = cat.key"
|
||
>
|
||
<el-icon :style="{ color: cat.color }"><component :is="cat.icon" /></el-icon>
|
||
<span class="cat-name">{{ cat.name }}</span>
|
||
<span class="cat-count">{{ cat.count }} 个文件</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="kb-files-section">
|
||
<div class="section-toolbar">
|
||
<h3>{{ currentInternalCatName }}</h3>
|
||
<el-input
|
||
v-model="internalKbSearch"
|
||
placeholder="搜索文件名"
|
||
style="width: 240px;"
|
||
:prefix-icon="Search"
|
||
clearable
|
||
/>
|
||
</div>
|
||
|
||
<el-table :data="filteredInternalKbFiles" style="width: 100%">
|
||
<el-table-column prop="name" label="文件名" min-width="280">
|
||
<template #default="{ row }">
|
||
<div class="file-name-cell">
|
||
<el-icon class="file-icon"><Document /></el-icon>
|
||
<span>{{ row.name }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="uploader" label="上传人员" width="120" />
|
||
<el-table-column prop="uploadTime" label="上传时间" width="180" />
|
||
<el-table-column label="操作" width="180" align="center">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link size="small" @click="previewKbFile(row)">
|
||
<el-icon><View /></el-icon>预览
|
||
</el-button>
|
||
<el-button type="success" link size="small" @click="downloadKbFile(row)">
|
||
<el-icon><Download /></el-icon>下载
|
||
</el-button>
|
||
<el-button type="danger" link size="small" @click="deleteKbFile(row, 'internal')">
|
||
<el-icon><Delete /></el-icon>删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</el-card>
|
||
</template>
|
||
|
||
<!-- 工单管理 -->
|
||
<template v-if="activeSubNav === 'tickets'">
|
||
<div class="content-header">
|
||
<div class="header-title">
|
||
<h1>工单管理</h1>
|
||
<p class="subtitle">查看和处理客户服务工单</p>
|
||
</div>
|
||
<el-button type="primary" @click="showCreateTicketDialog = true">
|
||
<el-icon><Plus /></el-icon>
|
||
创建工单
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 筛选区 -->
|
||
<el-card class="filter-card">
|
||
<div class="ticket-filters">
|
||
<el-radio-group v-model="ticketStatusFilter" @change="filterTickets">
|
||
<el-radio-button label="all">全部</el-radio-button>
|
||
<el-radio-button label="pending">
|
||
待处理 <el-badge :value="ticketCounts.pending" :max="99" />
|
||
</el-radio-button>
|
||
<el-radio-button label="processing">处理中</el-radio-button>
|
||
<el-radio-button label="completed">已完成</el-radio-button>
|
||
<el-radio-button label="closed">已关闭</el-radio-button>
|
||
</el-radio-group>
|
||
|
||
<div class="filter-right">
|
||
<el-select v-model="ticketTypeFilter" placeholder="故障类型" clearable style="width: 140px;">
|
||
<el-option label="电气系统故障" value="electrical" />
|
||
<el-option label="机械故障" value="mechanical" />
|
||
<el-option label="控制系统故障" value="control" />
|
||
<el-option label="配件更换" value="parts" />
|
||
<el-option label="安装调试" value="install" />
|
||
<el-option label="其他" value="other" />
|
||
</el-select>
|
||
<el-select v-model="ticketUrgencyFilter" placeholder="紧急程度" clearable style="width: 120px;">
|
||
<el-option label="紧急" value="urgent" />
|
||
<el-option label="普通" value="normal" />
|
||
<el-option label="低" value="low" />
|
||
</el-select>
|
||
<el-input
|
||
v-model="ticketSearchKeyword"
|
||
placeholder="搜索工单号/客户/设备"
|
||
style="width: 200px;"
|
||
:prefix-icon="Search"
|
||
clearable
|
||
/>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 工单列表 -->
|
||
<el-card>
|
||
<el-table :data="filteredTicketList" style="width: 100%">
|
||
<el-table-column prop="ticketNo" label="工单号" width="140">
|
||
<template #default="{ row }">
|
||
<span class="ticket-no">{{ row.ticketNo }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="customerName" label="客户信息" width="150">
|
||
<template #default="{ row }">
|
||
<div class="customer-info">
|
||
<span class="name">{{ row.customerName }}</span>
|
||
<span class="phone">{{ row.customerPhone }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="faultType" label="故障类型" width="120">
|
||
<template #default="{ row }">
|
||
<el-tag size="small">{{ row.faultTypeName }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="deviceModel" label="设备型号" width="120" />
|
||
<el-table-column prop="urgency" label="紧急程度" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
:type="row.urgency === 'urgent' ? 'danger' : row.urgency === 'normal' ? 'warning' : 'info'"
|
||
size="small"
|
||
>
|
||
{{ row.urgencyName }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
:type="getTicketStatusType(row.status)"
|
||
size="small"
|
||
>
|
||
{{ row.statusName }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="assignee" label="处理人" width="100">
|
||
<template #default="{ row }">
|
||
<span v-if="row.assignee">{{ row.assignee }}</span>
|
||
<span v-else class="unassigned">未指派</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="source" label="来源" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.source === 'chat' ? 'success' : ''" size="small" effect="plain">
|
||
{{ row.source === 'chat' ? '小电对话' : '手动创建' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||
<el-table-column label="操作" width="200" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link size="small" @click="viewTicketDetail(row)">
|
||
详情
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.source === 'chat'"
|
||
type="success"
|
||
link
|
||
size="small"
|
||
@click="viewTicketConversation(row)"
|
||
>
|
||
对话
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.status === 'pending'"
|
||
type="warning"
|
||
link
|
||
size="small"
|
||
@click="showAssignDialog(row)"
|
||
>
|
||
指派
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.status === 'processing'"
|
||
type="success"
|
||
link
|
||
size="small"
|
||
@click="completeTicket(row)"
|
||
>
|
||
完成
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<div class="table-pagination">
|
||
<el-pagination
|
||
v-model:current-page="ticketCurrentPage"
|
||
:page-size="10"
|
||
:total="ticketTotal"
|
||
layout="total, prev, pager, next"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</template>
|
||
|
||
<!-- 创建工单弹窗 -->
|
||
<el-dialog v-model="showCreateTicketDialog" title="创建工单" width="650px">
|
||
<el-form :model="ticketForm" label-width="90px">
|
||
<el-row :gutter="20">
|
||
<el-col :span="12">
|
||
<el-form-item label="客户姓名" required>
|
||
<el-input v-model="ticketForm.customerName" placeholder="请输入客户姓名" :prefix-icon="User" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="联系电话" required>
|
||
<el-input v-model="ticketForm.customerPhone" placeholder="请输入联系电话" :prefix-icon="Phone" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-form-item label="设备型号" required>
|
||
<el-select v-model="ticketForm.deviceModel" placeholder="请选择设备型号" style="width: 100%;">
|
||
<el-option label="TH-500GF" value="TH-500GF" />
|
||
<el-option label="TH-300D" value="TH-300D" />
|
||
<el-option label="TH-800GF" value="TH-800GF" />
|
||
<el-option label="S-200X" value="S-200X" />
|
||
<el-option label="S-150X" value="S-150X" />
|
||
<el-option label="G-100S" value="G-100S" />
|
||
<el-option label="G-200S" value="G-200S" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="故障类型" required>
|
||
<el-select v-model="ticketForm.faultType" placeholder="请选择故障类型" style="width: 100%;">
|
||
<el-option label="电气系统故障" value="electrical" />
|
||
<el-option label="机械故障" value="mechanical" />
|
||
<el-option label="控制系统故障" value="control" />
|
||
<el-option label="配件更换" value="parts" />
|
||
<el-option label="安装调试" value="install" />
|
||
<el-option label="其他" value="other" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="故障描述" required>
|
||
<el-input
|
||
v-model="ticketForm.faultDesc"
|
||
type="textarea"
|
||
:rows="4"
|
||
placeholder="请详细描述故障现象..."
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="紧急程度" required>
|
||
<el-select v-model="ticketForm.urgency" placeholder="请选择紧急程度" style="width: 100%;">
|
||
<el-option label="紧急" value="urgent" />
|
||
<el-option label="普通" value="normal" />
|
||
<el-option label="低" value="low" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="现场地址">
|
||
<el-input v-model="ticketForm.address" placeholder="请输入现场详细地址" />
|
||
</el-form-item>
|
||
<el-form-item label="故障照片">
|
||
<el-upload
|
||
action="#"
|
||
list-type="picture-card"
|
||
:auto-upload="false"
|
||
:limit="9"
|
||
>
|
||
<el-icon><Plus /></el-icon>
|
||
<template #tip>
|
||
<div class="el-upload__tip">支持jpg、png格式,最多上传9张</div>
|
||
</template>
|
||
</el-upload>
|
||
</el-form-item>
|
||
<el-form-item label="设备铭牌">
|
||
<el-input v-model="ticketForm.nameplate" placeholder="请输入设备铭牌信息" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="showCreateTicketDialog = false">取消</el-button>
|
||
<el-button type="primary" @click="submitCreateTicket">提交工单</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 工单详情弹窗 -->
|
||
<el-dialog v-model="showTicketDetailDialog" title="工单详情" width="750px">
|
||
<div class="ticket-detail" v-if="currentTicket">
|
||
<div class="detail-header">
|
||
<div class="ticket-info">
|
||
<span class="ticket-no">{{ currentTicket.ticketNo }}</span>
|
||
<el-tag :type="getTicketStatusType(currentTicket.status)">{{ currentTicket.statusName }}</el-tag>
|
||
<el-tag :type="currentTicket.urgency === 'urgent' ? 'danger' : currentTicket.urgency === 'normal' ? 'warning' : 'info'">
|
||
{{ currentTicket.urgencyName }}
|
||
</el-tag>
|
||
</div>
|
||
<div class="detail-actions">
|
||
<el-button
|
||
v-if="currentTicket.source === 'chat'"
|
||
type="primary"
|
||
size="small"
|
||
@click="viewTicketConversation(currentTicket)"
|
||
>
|
||
<el-icon><ChatDotRound /></el-icon>
|
||
查看对话
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item label="客户姓名">{{ currentTicket.customerName }}</el-descriptions-item>
|
||
<el-descriptions-item label="联系电话">{{ currentTicket.customerPhone }}</el-descriptions-item>
|
||
<el-descriptions-item label="设备型号">{{ currentTicket.deviceModel }}</el-descriptions-item>
|
||
<el-descriptions-item label="故障类型">{{ currentTicket.faultTypeName }}</el-descriptions-item>
|
||
<el-descriptions-item label="现场地址" :span="2">{{ currentTicket.address || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="故障描述" :span="2">{{ currentTicket.faultDesc }}</el-descriptions-item>
|
||
<el-descriptions-item label="设备铭牌" :span="2">{{ currentTicket.nameplate || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="工单来源">
|
||
<el-tag :type="currentTicket.source === 'chat' ? 'success' : ''" size="small">
|
||
{{ currentTicket.source === 'chat' ? '小电对话' : '手动创建' }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="处理人">{{ currentTicket.assignee || '未指派' }}</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">{{ currentTicket.createTime }}</el-descriptions-item>
|
||
<el-descriptions-item label="更新时间">{{ currentTicket.updateTime }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<!-- 故障照片 -->
|
||
<div class="detail-section" v-if="currentTicket.images && currentTicket.images.length > 0">
|
||
<h4>
|
||
<el-icon><Picture /></el-icon>
|
||
故障照片
|
||
</h4>
|
||
<div class="ticket-images">
|
||
<el-image
|
||
v-for="(img, index) in currentTicket.images"
|
||
:key="index"
|
||
:src="img"
|
||
:preview-src-list="currentTicket.images"
|
||
:initial-index="index"
|
||
fit="cover"
|
||
class="ticket-image-item"
|
||
>
|
||
<template #error>
|
||
<div class="image-error">
|
||
<el-icon><Picture /></el-icon>
|
||
</div>
|
||
</template>
|
||
</el-image>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-section">
|
||
<h4>处理记录</h4>
|
||
<el-timeline>
|
||
<el-timeline-item
|
||
v-for="(log, index) in currentTicket.logs"
|
||
:key="index"
|
||
:timestamp="log.time"
|
||
:type="log.type"
|
||
>
|
||
<div class="log-content">
|
||
<span class="log-operator">{{ log.operator }}</span>
|
||
<span class="log-action">{{ log.action }}</span>
|
||
</div>
|
||
<div v-if="log.remark" class="log-remark">{{ log.remark }}</div>
|
||
</el-timeline-item>
|
||
</el-timeline>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="showTicketDetailDialog = false">关闭</el-button>
|
||
<el-button v-if="currentTicket?.status === 'pending'" type="warning" @click="showAssignDialogFromDetail">
|
||
指派工程师
|
||
</el-button>
|
||
<el-button v-if="currentTicket?.status === 'processing'" type="success" @click="completeTicket(currentTicket)">
|
||
完成工单
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 指派工程师弹窗 -->
|
||
<el-dialog v-model="showAssignTicketDialog" title="指派工程师" width="600px">
|
||
<div class="assign-dialog">
|
||
<div class="ai-recommend" v-if="aiRecommendEngineer">
|
||
<div class="recommend-header">
|
||
<el-icon><MagicStick /></el-icon>
|
||
<span>智能推荐</span>
|
||
</div>
|
||
<div class="recommend-content">
|
||
<div class="engineer-card recommended" @click="selectedEngineer = aiRecommendEngineer.id">
|
||
<el-radio :model-value="selectedEngineer" :value="aiRecommendEngineer.id">
|
||
<div class="engineer-info">
|
||
<el-avatar :size="40">{{ aiRecommendEngineer.name.charAt(0) }}</el-avatar>
|
||
<div class="info-text">
|
||
<div class="name">{{ aiRecommendEngineer.name }}</div>
|
||
<div class="title">{{ aiRecommendEngineer.title }}</div>
|
||
</div>
|
||
<div class="match-score">
|
||
<span class="score">{{ aiRecommendEngineer.matchScore }}%</span>
|
||
<span class="label">匹配度</span>
|
||
</div>
|
||
</div>
|
||
</el-radio>
|
||
</div>
|
||
<div class="recommend-reason">
|
||
<el-icon><InfoFilled /></el-icon>
|
||
<span>{{ aiRecommendEngineer.reason }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="engineer-list">
|
||
<div class="list-header">
|
||
<span>全部工程师</span>
|
||
<el-input
|
||
v-model="engineerSearch"
|
||
placeholder="搜索工程师"
|
||
style="width: 180px;"
|
||
size="small"
|
||
clearable
|
||
/>
|
||
</div>
|
||
<div class="list-content">
|
||
<div
|
||
v-for="eng in filteredEngineers"
|
||
:key="eng.id"
|
||
class="engineer-card"
|
||
:class="{ selected: selectedEngineer === eng.id }"
|
||
@click="selectedEngineer = eng.id"
|
||
>
|
||
<el-radio :model-value="selectedEngineer" :value="eng.id">
|
||
<div class="engineer-info">
|
||
<el-avatar :size="36">{{ eng.name.charAt(0) }}</el-avatar>
|
||
<div class="info-text">
|
||
<div class="name">{{ eng.name }}</div>
|
||
<div class="title">{{ eng.title }} | 当前工单: {{ eng.currentTickets }}个</div>
|
||
</div>
|
||
</div>
|
||
</el-radio>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="showAssignTicketDialog = false">取消</el-button>
|
||
<el-button type="primary" @click="confirmAssignTicket" :disabled="!selectedEngineer">确认指派</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 对话记录弹窗 -->
|
||
<el-dialog v-model="showConversationDialog" title="对话记录" width="700px">
|
||
<el-tabs v-model="conversationTab">
|
||
<el-tab-pane label="对话记录" name="record">
|
||
<div class="conversation-record">
|
||
<div
|
||
v-for="(msg, index) in currentConversation"
|
||
:key="index"
|
||
class="message-item"
|
||
:class="msg.role"
|
||
>
|
||
<div class="message-avatar">
|
||
<el-avatar v-if="msg.role === 'user'" :size="36">客</el-avatar>
|
||
<el-avatar v-else :size="36" style="background: #7c3aed;">电</el-avatar>
|
||
</div>
|
||
<div class="message-content">
|
||
<div class="message-header">
|
||
<span class="sender">{{ msg.role === 'user' ? '客户' : '小电' }}</span>
|
||
<span class="time">{{ msg.time }}</span>
|
||
</div>
|
||
<div class="message-text">{{ msg.content }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="对话纪要" name="summary">
|
||
<div class="conversation-summary">
|
||
<div class="summary-section">
|
||
<h4><el-icon><Document /></el-icon> 问题概述</h4>
|
||
<p>{{ conversationSummary.overview }}</p>
|
||
</div>
|
||
<div class="summary-section">
|
||
<h4><el-icon><Warning /></el-icon> 客户诉求</h4>
|
||
<ul>
|
||
<li v-for="(item, index) in conversationSummary.demands" :key="index">{{ item }}</li>
|
||
</ul>
|
||
</div>
|
||
<div class="summary-section">
|
||
<h4><el-icon><InfoFilled /></el-icon> 关键信息</h4>
|
||
<el-descriptions :column="1" border size="small">
|
||
<el-descriptions-item label="设备型号">{{ conversationSummary.deviceModel }}</el-descriptions-item>
|
||
<el-descriptions-item label="故障现象">{{ conversationSummary.faultSymptom }}</el-descriptions-item>
|
||
<el-descriptions-item label="发生时间">{{ conversationSummary.occurTime }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</el-dialog>
|
||
|
||
<!-- 对话数据管理 -->
|
||
<template v-if="activeSubNav === 'conversation'">
|
||
<div class="content-header">
|
||
<div class="header-title">
|
||
<h1>对话数据管理</h1>
|
||
<p class="subtitle">管理和分析客服对话数据</p>
|
||
</div>
|
||
<el-button type="primary" @click="exportConversations">
|
||
<el-icon><Download /></el-icon>
|
||
导出数据
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 筛选区 -->
|
||
<el-card class="filter-card">
|
||
<div class="conversation-filters">
|
||
<el-date-picker
|
||
v-model="conversationDateRange"
|
||
type="daterange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
style="width: 260px;"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
<el-select v-model="conversationCategoryFilter" placeholder="问题分类" clearable style="width: 140px;">
|
||
<el-option label="设备操作咨询" value="device" />
|
||
<el-option label="故障报修" value="fault" />
|
||
<el-option label="配件购买" value="parts" />
|
||
<el-option label="保修政策" value="warranty" />
|
||
<el-option label="安装指导" value="install" />
|
||
<el-option label="其他问题" value="other" />
|
||
</el-select>
|
||
<el-select v-model="conversationStatusFilter" placeholder="对话状态" clearable style="width: 120px;">
|
||
<el-option label="已完成" value="completed" />
|
||
<el-option label="已转工单" value="ticket" />
|
||
<el-option label="未解决" value="unresolved" />
|
||
</el-select>
|
||
<el-input
|
||
v-model="conversationSearchKeyword"
|
||
placeholder="搜索客户/设备/内容"
|
||
style="width: 200px;"
|
||
:prefix-icon="Search"
|
||
clearable
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 对话列表 -->
|
||
<el-card>
|
||
<el-table :data="filteredConversationList" style="width: 100%">
|
||
<el-table-column prop="sessionId" label="会话ID" width="140">
|
||
<template #default="{ row }">
|
||
<span class="session-id">{{ row.sessionId }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="customerName" label="客户信息" width="140">
|
||
<template #default="{ row }">
|
||
<div class="customer-info">
|
||
<span class="name">{{ row.customerName }}</span>
|
||
<span class="phone">{{ row.customerPhone }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="category" label="问题分类" width="120">
|
||
<template #default="{ row }">
|
||
<el-tag size="small">{{ row.categoryName }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="deviceModel" label="涉及产品" width="110" />
|
||
<el-table-column prop="summary" label="对话摘要" min-width="200">
|
||
<template #default="{ row }">
|
||
<span class="conversation-summary-text">{{ row.summary }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="messageCount" label="消息数" width="80" align="center" />
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
:type="row.status === 'completed' ? 'success' : row.status === 'ticket' ? 'warning' : 'danger'"
|
||
size="small"
|
||
>
|
||
{{ row.statusName }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="satisfaction" label="满意度" width="100">
|
||
<template #default="{ row }">
|
||
<div class="satisfaction-stars" v-if="row.satisfaction">
|
||
<el-icon v-for="n in row.satisfaction" :key="n" class="star-filled"><StarFilled /></el-icon>
|
||
<el-icon v-for="n in (5 - row.satisfaction)" :key="'empty-' + n" class="star-empty"><Star /></el-icon>
|
||
</div>
|
||
<span v-else class="no-rating">未评价</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="startTime" label="对话时间" width="160" />
|
||
<el-table-column label="操作" width="120" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link size="small" @click="viewConversationDetail(row)">
|
||
<el-icon><View /></el-icon>
|
||
查看
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<div class="table-pagination">
|
||
<el-pagination
|
||
v-model:current-page="conversationCurrentPage"
|
||
:page-size="10"
|
||
:total="conversationTotal"
|
||
layout="total, prev, pager, next"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</template>
|
||
|
||
<!-- 对话详情弹窗 -->
|
||
<el-dialog v-model="showConversationDetailDialog" title="对话详情" width="800px">
|
||
<div class="conversation-detail-header" v-if="currentConversationDetail">
|
||
<div class="detail-info">
|
||
<span class="session-id">{{ currentConversationDetail.sessionId }}</span>
|
||
<el-tag :type="currentConversationDetail.status === 'completed' ? 'success' : currentConversationDetail.status === 'ticket' ? 'warning' : 'danger'" size="small">
|
||
{{ currentConversationDetail.statusName }}
|
||
</el-tag>
|
||
<el-tag size="small">{{ currentConversationDetail.categoryName }}</el-tag>
|
||
</div>
|
||
<div class="detail-meta">
|
||
<span>客户:{{ currentConversationDetail.customerName }}</span>
|
||
<span>设备:{{ currentConversationDetail.deviceModel }}</span>
|
||
<span>时间:{{ currentConversationDetail.startTime }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<el-tabs v-model="conversationDetailTab">
|
||
<el-tab-pane label="对话记录" name="record">
|
||
<div class="conversation-record">
|
||
<div
|
||
v-for="(msg, index) in currentConversationMessages"
|
||
:key="index"
|
||
class="message-item"
|
||
:class="msg.role"
|
||
>
|
||
<div class="message-avatar">
|
||
<el-avatar v-if="msg.role === 'user'" :size="36">客</el-avatar>
|
||
<el-avatar v-else :size="36" style="background: #7c3aed;">电</el-avatar>
|
||
</div>
|
||
<div class="message-content">
|
||
<div class="message-header">
|
||
<span class="sender">{{ msg.role === 'user' ? '客户' : '小电' }}</span>
|
||
<span class="time">{{ msg.time }}</span>
|
||
</div>
|
||
<div class="message-text">{{ msg.content }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="对话概要" name="summary">
|
||
<div class="conversation-summary" v-if="currentConversationDetail">
|
||
<div class="summary-section">
|
||
<h4><el-icon><Document /></el-icon> 问题概述</h4>
|
||
<p>{{ currentConversationDetail.summaryDetail?.overview }}</p>
|
||
</div>
|
||
<div class="summary-section">
|
||
<h4><el-icon><Warning /></el-icon> 客户诉求</h4>
|
||
<ul>
|
||
<li v-for="(item, index) in currentConversationDetail.summaryDetail?.demands" :key="index">{{ item }}</li>
|
||
</ul>
|
||
</div>
|
||
<div class="summary-section">
|
||
<h4><el-icon><Select /></el-icon> 解决方案</h4>
|
||
<p>{{ currentConversationDetail.summaryDetail?.solution }}</p>
|
||
</div>
|
||
<div class="summary-section">
|
||
<h4><el-icon><InfoFilled /></el-icon> 关键信息</h4>
|
||
<el-descriptions :column="2" border size="small">
|
||
<el-descriptions-item label="涉及设备">{{ currentConversationDetail.deviceModel }}</el-descriptions-item>
|
||
<el-descriptions-item label="问题分类">{{ currentConversationDetail.categoryName }}</el-descriptions-item>
|
||
<el-descriptions-item label="对话时长">{{ currentConversationDetail.duration }}</el-descriptions-item>
|
||
<el-descriptions-item label="消息数量">{{ currentConversationDetail.messageCount }} 条</el-descriptions-item>
|
||
<el-descriptions-item label="是否转工单">
|
||
<el-tag :type="currentConversationDetail.status === 'ticket' ? 'warning' : 'info'" size="small">
|
||
{{ currentConversationDetail.status === 'ticket' ? '是' : '否' }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="客户满意度">
|
||
<div class="satisfaction-stars" v-if="currentConversationDetail.satisfaction">
|
||
<el-icon v-for="n in currentConversationDetail.satisfaction" :key="n" class="star-filled"><StarFilled /></el-icon>
|
||
</div>
|
||
<span v-else>未评价</span>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</el-dialog>
|
||
|
||
<!-- 智能体管理 -->
|
||
<template v-if="activeSubNav === 'agent'">
|
||
<div class="content-header">
|
||
<div class="header-title">
|
||
<h1>智能体管理</h1>
|
||
<p class="subtitle">配置和管理AI智能客服</p>
|
||
</div>
|
||
</div>
|
||
|
||
<el-card class="agent-config-card">
|
||
<!-- 智能体头部信息 -->
|
||
<div class="agent-header">
|
||
<div class="agent-avatar">
|
||
<el-avatar :size="56" style="background: linear-gradient(135deg, #7c3aed, #a855f7);">
|
||
<el-icon :size="28"><Service /></el-icon>
|
||
</el-avatar>
|
||
</div>
|
||
<div class="agent-info">
|
||
<h2>泰豪小电</h2>
|
||
<span class="agent-type">智能问答助手</span>
|
||
</div>
|
||
<el-tag type="success" class="agent-status">已发布</el-tag>
|
||
</div>
|
||
|
||
<el-divider />
|
||
|
||
<!-- 配置表单 -->
|
||
<el-form :model="agentConfig" label-position="top" class="agent-form">
|
||
<el-form-item label="助手名称">
|
||
<el-input v-model="agentConfig.name" placeholder="请输入助手名称" />
|
||
</el-form-item>
|
||
|
||
<el-form-item label="对话风格">
|
||
<el-radio-group v-model="agentConfig.style">
|
||
<el-radio value="professional">简洁专业</el-radio>
|
||
<el-radio value="friendly">友好亲切</el-radio>
|
||
<el-radio value="detailed">详细解答</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="助手描述" class="full-width">
|
||
<el-input
|
||
v-model="agentConfig.description"
|
||
type="textarea"
|
||
:rows="2"
|
||
placeholder="请输入助手描述,将作为开场白展示"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="功能开关" class="full-width">
|
||
<div class="feature-switches">
|
||
<el-checkbox v-model="agentConfig.features.autoGreeting">自动问候</el-checkbox>
|
||
<el-checkbox v-model="agentConfig.features.smartRecommend">智能推荐</el-checkbox>
|
||
<el-checkbox v-model="agentConfig.features.conversationRecord">对话记录</el-checkbox>
|
||
<el-checkbox v-model="agentConfig.features.voiceChat">语音对话</el-checkbox>
|
||
<el-checkbox v-model="agentConfig.features.ticketGuide">工单引导</el-checkbox>
|
||
<el-checkbox v-model="agentConfig.features.satisfaction">满意度评价</el-checkbox>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="响应延迟(毫秒)">
|
||
<el-input-number
|
||
v-model="agentConfig.responseDelay"
|
||
:min="0"
|
||
:max="5000"
|
||
:step="100"
|
||
style="width: 100%;"
|
||
/>
|
||
<div class="form-tip">模拟AI思考时间,建议设置为800-1500ms</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="最大回复长度(字符)">
|
||
<el-input-number
|
||
v-model="agentConfig.maxReplyLength"
|
||
:min="100"
|
||
:max="2000"
|
||
:step="100"
|
||
style="width: 100%;"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item class="form-actions">
|
||
<el-button type="primary" @click="saveAgentConfig">保存配置</el-button>
|
||
<el-button @click="resetAgentConfig">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
</template>
|
||
|
||
<!-- 知识库日志 -->
|
||
<template v-if="activeSubNav === 'log-knowledge'">
|
||
<div class="content-header">
|
||
<div class="header-title">
|
||
<h1>知识库日志</h1>
|
||
<p class="subtitle">查看知识库操作记录</p>
|
||
</div>
|
||
<el-button @click="exportKnowledgeLogs">
|
||
<el-icon><Download /></el-icon>
|
||
导出日志
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-card class="filter-card">
|
||
<div class="log-filters">
|
||
<el-date-picker
|
||
v-model="knowledgeLogDateRange"
|
||
type="daterange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
style="width: 260px;"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
<el-select v-model="knowledgeLogTypeFilter" placeholder="操作类型" clearable style="width: 120px;">
|
||
<el-option label="上传" value="upload" />
|
||
<el-option label="更新" value="update" />
|
||
<el-option label="删除" value="delete" />
|
||
<el-option label="重新索引" value="reindex" />
|
||
</el-select>
|
||
<el-select v-model="knowledgeLogOperatorFilter" placeholder="操作人" clearable style="width: 120px;">
|
||
<el-option label="李志鹏" value="李志鹏" />
|
||
<el-option label="张伟" value="张伟" />
|
||
<el-option label="系统" value="系统" />
|
||
</el-select>
|
||
<el-input
|
||
v-model="knowledgeLogSearch"
|
||
placeholder="搜索文档名称"
|
||
style="width: 180px;"
|
||
:prefix-icon="Search"
|
||
clearable
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card>
|
||
<el-table :data="filteredKnowledgeLogs" style="width: 100%">
|
||
<el-table-column prop="time" label="操作时间" width="170" />
|
||
<el-table-column prop="type" label="操作类型" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
:type="row.type === 'upload' ? 'success' : row.type === 'delete' ? 'danger' : row.type === 'update' ? 'warning' : 'info'"
|
||
size="small"
|
||
>
|
||
{{ row.typeName }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="docName" label="文档名称" min-width="200" />
|
||
<el-table-column prop="category" label="文档分类" width="120" />
|
||
<el-table-column prop="operator" label="操作人" width="100" />
|
||
<el-table-column prop="result" label="操作结果" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.result === 'success' ? 'success' : 'danger'" size="small">
|
||
{{ row.result === 'success' ? '成功' : '失败' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="remark" label="备注" min-width="150" />
|
||
</el-table>
|
||
|
||
<div class="table-pagination">
|
||
<el-pagination
|
||
v-model:current-page="knowledgeLogPage"
|
||
:page-size="10"
|
||
:total="knowledgeLogTotal"
|
||
layout="total, prev, pager, next"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</template>
|
||
|
||
<!-- 工单日志 -->
|
||
<template v-if="activeSubNav === 'log-ticket'">
|
||
<div class="content-header">
|
||
<div class="header-title">
|
||
<h1>工单日志</h1>
|
||
<p class="subtitle">查看工单操作记录</p>
|
||
</div>
|
||
<el-button @click="exportTicketLogs">
|
||
<el-icon><Download /></el-icon>
|
||
导出日志
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-card class="filter-card">
|
||
<div class="log-filters">
|
||
<el-date-picker
|
||
v-model="ticketLogDateRange"
|
||
type="daterange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
style="width: 260px;"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
<el-input
|
||
v-model="ticketLogNoFilter"
|
||
placeholder="工单号"
|
||
style="width: 160px;"
|
||
clearable
|
||
/>
|
||
<el-select v-model="ticketLogTypeFilter" placeholder="操作类型" clearable style="width: 120px;">
|
||
<el-option label="创建" value="create" />
|
||
<el-option label="指派" value="assign" />
|
||
<el-option label="转派" value="transfer" />
|
||
<el-option label="完成" value="complete" />
|
||
<el-option label="关闭" value="close" />
|
||
</el-select>
|
||
<el-select v-model="ticketLogOperatorFilter" placeholder="操作人" clearable style="width: 120px;">
|
||
<el-option label="系统" value="系统" />
|
||
<el-option label="李志鹏" value="李志鹏" />
|
||
<el-option label="小李" value="小李" />
|
||
<el-option label="小王" value="小王" />
|
||
</el-select>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card>
|
||
<el-table :data="filteredTicketLogs" style="width: 100%">
|
||
<el-table-column prop="time" label="操作时间" width="170" />
|
||
<el-table-column prop="ticketNo" label="工单号" width="160">
|
||
<template #default="{ row }">
|
||
<span class="ticket-no">{{ row.ticketNo }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="type" label="操作类型" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
:type="row.type === 'create' ? 'primary' : row.type === 'assign' ? 'warning' : row.type === 'complete' ? 'success' : row.type === 'close' ? 'info' : ''"
|
||
size="small"
|
||
>
|
||
{{ row.typeName }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="operator" label="操作人" width="100" />
|
||
<el-table-column prop="content" label="操作内容" min-width="250" />
|
||
<el-table-column prop="ip" label="IP地址" width="140" />
|
||
</el-table>
|
||
|
||
<div class="table-pagination">
|
||
<el-pagination
|
||
v-model:current-page="ticketLogPage"
|
||
:page-size="10"
|
||
:total="ticketLogTotal"
|
||
layout="total, prev, pager, next"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</template>
|
||
|
||
<!-- 系统日志 -->
|
||
<template v-if="activeSubNav === 'log-system'">
|
||
<div class="content-header">
|
||
<div class="header-title">
|
||
<h1>系统日志</h1>
|
||
<p class="subtitle">查看系统配置变更与异常信息</p>
|
||
</div>
|
||
<el-button @click="exportSystemLogs">
|
||
<el-icon><Download /></el-icon>
|
||
导出日志
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-card class="filter-card">
|
||
<div class="log-filters">
|
||
<el-date-picker
|
||
v-model="systemLogDateRange"
|
||
type="daterange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
style="width: 260px;"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
<el-select v-model="systemLogTypeFilter" placeholder="日志类型" clearable style="width: 120px;">
|
||
<el-option label="配置变更" value="config" />
|
||
<el-option label="系统异常" value="error" />
|
||
</el-select>
|
||
<el-select v-model="systemLogLevelFilter" placeholder="日志级别" clearable style="width: 120px;">
|
||
<el-option label="INFO" value="info" />
|
||
<el-option label="WARN" value="warn" />
|
||
<el-option label="ERROR" value="error" />
|
||
</el-select>
|
||
<el-select v-model="systemLogModuleFilter" placeholder="模块" clearable style="width: 140px;">
|
||
<el-option label="智能体配置" value="agent" />
|
||
<el-option label="知识库索引" value="knowledge" />
|
||
<el-option label="对话服务" value="chat" />
|
||
<el-option label="API调用" value="api" />
|
||
</el-select>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card>
|
||
<el-table :data="filteredSystemLogs" style="width: 100%">
|
||
<el-table-column prop="time" label="时间" width="170" />
|
||
<el-table-column prop="logType" label="类型" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.logType === 'config' ? 'primary' : 'danger'" size="small">
|
||
{{ row.logType === 'config' ? '配置变更' : '系统异常' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="level" label="级别" width="80">
|
||
<template #default="{ row }">
|
||
<span
|
||
class="log-level"
|
||
:class="row.level"
|
||
>
|
||
{{ row.level.toUpperCase() }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="module" label="模块" width="120">
|
||
<template #default="{ row }">
|
||
<el-tag size="small" type="info">{{ row.moduleName }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="operator" label="操作人" width="100" />
|
||
<el-table-column prop="content" label="内容" min-width="300" />
|
||
</el-table>
|
||
|
||
<div class="table-pagination">
|
||
<el-pagination
|
||
v-model:current-page="systemLogPage"
|
||
:page-size="10"
|
||
:total="systemLogTotal"
|
||
layout="total, prev, pager, next"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</template>
|
||
|
||
<!-- CRM管理 -->
|
||
<template v-if="activeSubNav === 'crm'">
|
||
<div class="content-header">
|
||
<div class="header-title">
|
||
<h1>CRM管理</h1>
|
||
<p class="subtitle">客户关系管理系统</p>
|
||
</div>
|
||
<el-button type="primary">
|
||
<el-icon><User /></el-icon>
|
||
新建客户
|
||
</el-button>
|
||
</div>
|
||
<el-card>
|
||
<div class="placeholder-content">
|
||
<el-icon class="placeholder-icon"><User /></el-icon>
|
||
<h3>CRM管理功能开发中</h3>
|
||
<p>此功能正在开发中,敬请期待...</p>
|
||
</div>
|
||
</el-card>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- 知识库上传对话框 -->
|
||
<el-dialog v-model="showKbUploadDialog" title="上传知识库文件" width="600px">
|
||
<el-form :model="kbUploadForm" label-width="100px">
|
||
<el-form-item label="选择文件" required>
|
||
<el-upload
|
||
:auto-upload="false"
|
||
:on-change="handleKbFileChange"
|
||
:limit="1"
|
||
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
|
||
>
|
||
<template #trigger>
|
||
<el-button type="primary">
|
||
<el-icon><Upload /></el-icon>
|
||
选择文件
|
||
</el-button>
|
||
</template>
|
||
<template #tip>
|
||
<div class="el-upload__tip">支持 PDF、Word、Excel、PPT 格式</div>
|
||
</template>
|
||
</el-upload>
|
||
</el-form-item>
|
||
<el-form-item label="文件名称" required>
|
||
<el-input v-model="kbUploadForm.name" placeholder="上传时自动识别" />
|
||
</el-form-item>
|
||
<el-form-item label="知识库类型" required>
|
||
<el-radio-group v-model="kbUploadForm.type" @change="kbUploadForm.category = ''">
|
||
<el-radio label="external">外部知识库</el-radio>
|
||
<el-radio label="internal">内部知识库</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="文件分类" required>
|
||
<el-select v-model="kbUploadForm.category" placeholder="请选择分类" style="width: 100%;">
|
||
<template v-if="kbUploadForm.type === 'external'">
|
||
<el-option label="设备操作指南" value="device-guide" />
|
||
<el-option label="常见故障解决方案" value="fault-solution" />
|
||
<el-option label="三包外服务政策" value="service-policy" />
|
||
<el-option label="配件咨询话术" value="parts-consult" />
|
||
</template>
|
||
<template v-else>
|
||
<el-option label="技术维修手册" value="tech-manual" />
|
||
<el-option label="产品参数明细" value="product-params" />
|
||
<el-option label="内部服务流程规范" value="service-process" />
|
||
<el-option label="客户服务话术模板" value="service-script" />
|
||
</template>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="上传人员" required>
|
||
<el-input v-model="kbUploadForm.uploader" placeholder="自动匹配当前用户" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="aiRecognizeKbFile" :loading="kbAiRecognizing">
|
||
<el-icon><MagicStick /></el-icon>
|
||
AI智能识别
|
||
</el-button>
|
||
<span style="margin-left: 12px; color: #909399; font-size: 13px;">自动识别并填充分类信息</span>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="showKbUploadDialog = false">取消</el-button>
|
||
<el-button type="primary" @click="confirmKbUpload">确认上传</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 个人中心 -->
|
||
<div v-if="activeNav === 'profile'" class="admin-content profile-content">
|
||
<div class="content-header">
|
||
<h1>个人中心</h1>
|
||
<p class="subtitle">管理您的个人信息和账户设置</p>
|
||
</div>
|
||
|
||
<div class="profile-main">
|
||
<el-tabs v-model="activeProfileTab">
|
||
<el-tab-pane label="基本信息" name="basic">
|
||
<el-card>
|
||
<div class="profile-section">
|
||
<div class="avatar-section">
|
||
<el-avatar :size="100" src="/avatar.svg">李志鹏</el-avatar>
|
||
<el-button type="primary" size="small" style="margin-top: 16px;">
|
||
<el-icon><Upload /></el-icon>
|
||
更换头像
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-form :model="profileForm" label-width="100px" style="max-width: 600px;">
|
||
<el-form-item label="姓名">
|
||
<el-input v-model="profileForm.name" />
|
||
</el-form-item>
|
||
<el-form-item label="工号">
|
||
<el-input v-model="profileForm.employeeId" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="部门">
|
||
<el-input v-model="profileForm.department" />
|
||
</el-form-item>
|
||
<el-form-item label="职位">
|
||
<el-input v-model="profileForm.position" />
|
||
</el-form-item>
|
||
<el-form-item label="手机号">
|
||
<el-input v-model="profileForm.phone" />
|
||
</el-form-item>
|
||
<el-form-item label="邮箱">
|
||
<el-input v-model="profileForm.email" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="saveProfile">保存修改</el-button>
|
||
<el-button @click="resetProfile">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</el-card>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane label="账户安全" name="security">
|
||
<el-card>
|
||
<div class="security-section">
|
||
<div class="security-item">
|
||
<div class="security-info">
|
||
<h3>登录密码</h3>
|
||
<p>定期更换密码可以提高账户安全性</p>
|
||
</div>
|
||
<el-button type="primary" link @click="showPasswordDialog = true">修改密码</el-button>
|
||
</div>
|
||
|
||
<el-divider />
|
||
|
||
<div class="security-item">
|
||
<div class="security-info">
|
||
<h3>双因素认证</h3>
|
||
<p>开启后登录需要验证码,更加安全</p>
|
||
</div>
|
||
<el-switch v-model="twoFactorEnabled" />
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane label="操作日志" name="logs">
|
||
<el-card>
|
||
<el-table :data="operationLogs" style="width: 100%">
|
||
<el-table-column prop="time" label="时间" width="180" />
|
||
<el-table-column prop="action" label="操作" width="200" />
|
||
<el-table-column prop="ip" label="IP地址" width="150" />
|
||
<el-table-column prop="device" label="设备" />
|
||
</el-table>
|
||
</el-card>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import {
|
||
Setting,
|
||
Document,
|
||
Service,
|
||
Back,
|
||
User,
|
||
Warning,
|
||
Select,
|
||
Clock,
|
||
Files,
|
||
DArrowRight,
|
||
DataLine,
|
||
Tickets,
|
||
Upload,
|
||
View,
|
||
Download,
|
||
Delete,
|
||
Search,
|
||
MagicStick,
|
||
Tools,
|
||
QuestionFilled,
|
||
ChatDotRound,
|
||
List,
|
||
ArrowDown,
|
||
Top,
|
||
Right,
|
||
Plus,
|
||
Phone,
|
||
InfoFilled,
|
||
Picture,
|
||
StarFilled,
|
||
Star
|
||
} from '@element-plus/icons-vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
|
||
const router = useRouter()
|
||
const activeNav = ref('platform')
|
||
const activeSubNav = ref('dashboard')
|
||
|
||
const navItems = [
|
||
{ key: 'platform', label: '平台管理后台', icon: 'Setting' },
|
||
{ key: 'bidding', label: '智能标书管理后台', icon: 'Document' },
|
||
{ key: 'service', label: '泰豪小电管理后台', icon: 'Service' }
|
||
]
|
||
|
||
// 平台管理后台的子导航
|
||
const platformSubNav = [
|
||
{ key: 'dashboard', label: '数据概览', icon: 'DataLine' },
|
||
{ key: 'users', label: '用户管理', icon: 'User' },
|
||
{ key: 'knowledge', label: '知识库', icon: 'Document' },
|
||
{ key: 'settings', label: '系统配置', icon: 'Setting' }
|
||
]
|
||
|
||
// 智能标书管理后台的子导航
|
||
const biddingSubNav = [
|
||
{ key: 'dashboard', label: '数据概览', icon: 'DataLine' },
|
||
{ key: 'projects', label: '项目管理', icon: 'Document' },
|
||
{ key: 'templates', label: '模板库', icon: 'Files' },
|
||
{ key: 'settings', label: '系统配置', icon: 'Setting' }
|
||
]
|
||
|
||
// 泰豪小电管理后台的子导航
|
||
const serviceSubNav = [
|
||
{ key: 'dashboard', label: '数据概览', icon: 'DataLine' },
|
||
{ key: 'knowledge', label: '知识库', icon: 'Document' },
|
||
{ key: 'tickets', label: '工单管理', icon: 'Tickets' },
|
||
{ key: 'conversation', label: '对话数据管理', icon: 'ChatDotRound' },
|
||
{ key: 'agent', label: '智能体管理', icon: 'Service' },
|
||
{
|
||
key: 'logs',
|
||
label: '日志管理',
|
||
icon: 'List',
|
||
children: [
|
||
{ key: 'log-knowledge', label: '知识库日志' },
|
||
{ key: 'log-ticket', label: '工单日志' },
|
||
{ key: 'log-system', label: '系统日志' }
|
||
]
|
||
},
|
||
{ key: 'crm', label: 'CRM管理', icon: 'User' }
|
||
]
|
||
|
||
// 日志导航展开状态
|
||
const logNavExpanded = ref(false)
|
||
|
||
// 数据概览
|
||
const dashboardData = ref({
|
||
consultations: 1258,
|
||
pendingTickets: 23,
|
||
completedTickets: 156
|
||
})
|
||
|
||
const questionStatPeriod = ref('today')
|
||
|
||
const questionCategories = ref([
|
||
{ name: '设备操作咨询', count: 386, percent: 85, color: '#409eff' },
|
||
{ name: '故障报修', count: 298, percent: 65, color: '#e6a23c' },
|
||
{ name: '配件购买', count: 215, percent: 47, color: '#67c23a' },
|
||
{ name: '保修政策', count: 189, percent: 42, color: '#909399' },
|
||
{ name: '安装指导', count: 112, percent: 25, color: '#f56c6c' },
|
||
{ name: '其他问题', count: 58, percent: 13, color: '#b3d8ff' }
|
||
])
|
||
|
||
const productCloudData = ref([
|
||
{ name: 'TH-500GF', size: 24, weight: 1, color: '#409eff' },
|
||
{ name: 'S-200X', size: 20, weight: 0.8, color: '#67c23a' },
|
||
{ name: 'TH-300D', size: 18, weight: 0.7, color: '#e6a23c' },
|
||
{ name: 'G-100S', size: 22, weight: 0.9, color: '#f56c6c' },
|
||
{ name: 'TH-800GF', size: 16, weight: 0.6, color: '#909399' },
|
||
{ name: 'S-150X', size: 19, weight: 0.75, color: '#7c3aed' },
|
||
{ name: 'TH-1000D', size: 15, weight: 0.5, color: '#00d4ff' },
|
||
{ name: 'G-200S', size: 17, weight: 0.65, color: '#ff6b6b' },
|
||
{ name: 'TH-600GF', size: 21, weight: 0.85, color: '#ffa940' },
|
||
{ name: 'S-300X', size: 14, weight: 0.45, color: '#36cfc9' },
|
||
{ name: 'TH-400D', size: 16, weight: 0.55, color: '#b37feb' },
|
||
{ name: 'G-150S', size: 18, weight: 0.7, color: '#ff85c0' }
|
||
])
|
||
|
||
// 跳转方法
|
||
const goToTickets = (status) => {
|
||
activeSubNav.value = 'tickets'
|
||
// 可以存储筛选状态供工单管理页面使用
|
||
ElMessage.info(`跳转至工单管理 - ${status === 'pending' ? '待处理' : '已处理'}工单`)
|
||
}
|
||
|
||
const goToConversationByCategory = (category) => {
|
||
activeSubNav.value = 'conversation'
|
||
ElMessage.info(`跳转至对话数据 - 分类:${category}`)
|
||
}
|
||
|
||
const goToConversationByProduct = (product) => {
|
||
activeSubNav.value = 'conversation'
|
||
ElMessage.info(`跳转至对话数据 - 产品:${product}`)
|
||
}
|
||
|
||
// 工单管理
|
||
const ticketStatusFilter = ref('all')
|
||
const ticketTypeFilter = ref('')
|
||
const ticketUrgencyFilter = ref('')
|
||
const ticketSearchKeyword = ref('')
|
||
const ticketCurrentPage = ref(1)
|
||
const ticketTotal = ref(45)
|
||
|
||
const ticketCounts = ref({
|
||
pending: 23,
|
||
processing: 8,
|
||
completed: 12,
|
||
closed: 2
|
||
})
|
||
|
||
const showCreateTicketDialog = ref(false)
|
||
const showTicketDetailDialog = ref(false)
|
||
const showAssignTicketDialog = ref(false)
|
||
const showConversationDialog = ref(false)
|
||
const currentTicket = ref(null)
|
||
const selectedEngineer = ref('')
|
||
const engineerSearch = ref('')
|
||
const conversationTab = ref('record')
|
||
|
||
const ticketForm = ref({
|
||
customerName: '',
|
||
customerPhone: '',
|
||
deviceModel: '',
|
||
faultType: '',
|
||
faultDesc: '',
|
||
urgency: 'normal',
|
||
address: '',
|
||
nameplate: ''
|
||
})
|
||
|
||
const ticketList = ref([
|
||
{
|
||
id: 1,
|
||
ticketNo: 'WO202501130001',
|
||
customerName: '张伟',
|
||
customerPhone: '138****5678',
|
||
deviceModel: 'TH-500GF',
|
||
faultType: 'electrical',
|
||
faultTypeName: '电气系统故障',
|
||
faultDesc: '发电机启动后电压不稳定,波动范围较大,影响正常使用',
|
||
urgency: 'urgent',
|
||
urgencyName: '紧急',
|
||
status: 'pending',
|
||
statusName: '待处理',
|
||
source: 'chat',
|
||
assignee: null,
|
||
address: '江西省南昌市红谷滩区xxx路xxx号',
|
||
nameplate: 'TH-500GF-2023-001',
|
||
images: [
|
||
'https://picsum.photos/400/300?random=1',
|
||
'https://picsum.photos/400/300?random=2',
|
||
'https://picsum.photos/400/300?random=3'
|
||
],
|
||
createTime: '2025-01-13 09:30:00',
|
||
updateTime: '2025-01-13 09:30:00',
|
||
logs: [
|
||
{ time: '2025-01-13 09:30:00', operator: '系统', action: '工单创建', type: 'primary', remark: '客户通过小电对话提交' }
|
||
]
|
||
},
|
||
{
|
||
id: 2,
|
||
ticketNo: 'WO202501130002',
|
||
customerName: '李明',
|
||
customerPhone: '139****1234',
|
||
deviceModel: 'S-200X',
|
||
faultType: 'mechanical',
|
||
faultTypeName: '机械故障',
|
||
faultDesc: '静音发电机运行时有异常噪音,疑似轴承磨损',
|
||
urgency: 'normal',
|
||
urgencyName: '普通',
|
||
status: 'processing',
|
||
statusName: '处理中',
|
||
source: 'chat',
|
||
assignee: '小李',
|
||
address: '江西省九江市浔阳区xxx街xxx号',
|
||
nameplate: 'S-200X-2022-156',
|
||
images: [
|
||
'https://picsum.photos/400/300?random=4',
|
||
'https://picsum.photos/400/300?random=5'
|
||
],
|
||
createTime: '2025-01-12 14:20:00',
|
||
updateTime: '2025-01-13 08:15:00',
|
||
logs: [
|
||
{ time: '2025-01-12 14:20:00', operator: '系统', action: '工单创建', type: 'primary', remark: '客户通过小电对话提交' },
|
||
{ time: '2025-01-12 15:00:00', operator: '管理员', action: '指派工程师', type: 'warning', remark: '指派给小李工程师处理' },
|
||
{ time: '2025-01-13 08:15:00', operator: '小李', action: '开始处理', type: 'success', remark: '已联系客户,计划今日上门检修' }
|
||
]
|
||
},
|
||
{
|
||
id: 3,
|
||
ticketNo: 'WO202501120003',
|
||
customerName: '王芳',
|
||
customerPhone: '137****9876',
|
||
deviceModel: 'TH-300D',
|
||
faultType: 'control',
|
||
faultTypeName: '控制系统故障',
|
||
faultDesc: '控制面板显示屏不亮,无法查看运行状态',
|
||
urgency: 'urgent',
|
||
urgencyName: '紧急',
|
||
status: 'pending',
|
||
statusName: '待处理',
|
||
source: 'manual',
|
||
assignee: null,
|
||
address: '江西省赣州市章贡区xxx路xxx号',
|
||
images: [
|
||
'https://picsum.photos/400/300?random=6'
|
||
],
|
||
nameplate: 'TH-300D-2023-089',
|
||
createTime: '2025-01-12 10:45:00',
|
||
updateTime: '2025-01-12 10:45:00',
|
||
logs: [
|
||
{ time: '2025-01-12 10:45:00', operator: '客服小张', action: '工单创建', type: 'primary', remark: '电话接报后手动创建' }
|
||
]
|
||
},
|
||
{
|
||
id: 4,
|
||
ticketNo: 'WO202501110004',
|
||
customerName: '赵强',
|
||
customerPhone: '136****5432',
|
||
deviceModel: 'G-100S',
|
||
faultType: 'parts',
|
||
faultTypeName: '配件更换',
|
||
faultDesc: '需要更换空气滤芯和机油滤芯',
|
||
urgency: 'low',
|
||
urgencyName: '低',
|
||
status: 'completed',
|
||
statusName: '已完成',
|
||
source: 'chat',
|
||
assignee: '小王',
|
||
address: '江西省上饶市信州区xxx街xxx号',
|
||
nameplate: 'G-100S-2021-234',
|
||
createTime: '2025-01-11 16:30:00',
|
||
updateTime: '2025-01-12 17:00:00',
|
||
logs: [
|
||
{ time: '2025-01-11 16:30:00', operator: '系统', action: '工单创建', type: 'primary', remark: '客户通过小电对话提交' },
|
||
{ time: '2025-01-11 17:00:00', operator: '管理员', action: '指派工程师', type: 'warning', remark: '指派给小王工程师处理' },
|
||
{ time: '2025-01-12 09:00:00', operator: '小王', action: '开始处理', type: 'success', remark: '已准备配件,前往客户现场' },
|
||
{ time: '2025-01-12 17:00:00', operator: '小王', action: '完成工单', type: 'success', remark: '已完成滤芯更换,设备运行正常' }
|
||
]
|
||
},
|
||
{
|
||
id: 5,
|
||
ticketNo: 'WO202501100005',
|
||
customerName: '孙丽',
|
||
customerPhone: '135****8765',
|
||
deviceModel: 'TH-800GF',
|
||
faultType: 'install',
|
||
faultTypeName: '安装调试',
|
||
faultDesc: '新购发电机组需要安装调试',
|
||
urgency: 'normal',
|
||
urgencyName: '普通',
|
||
status: 'processing',
|
||
statusName: '处理中',
|
||
source: 'manual',
|
||
assignee: '小张',
|
||
address: '江西省宜春市袁州区xxx路xxx号',
|
||
nameplate: 'TH-800GF-2025-003',
|
||
createTime: '2025-01-10 11:00:00',
|
||
updateTime: '2025-01-13 10:00:00',
|
||
logs: [
|
||
{ time: '2025-01-10 11:00:00', operator: '销售小刘', action: '工单创建', type: 'primary', remark: '新机销售后创建安装工单' },
|
||
{ time: '2025-01-10 14:00:00', operator: '管理员', action: '指派工程师', type: 'warning', remark: '指派给小张工程师处理' },
|
||
{ time: '2025-01-13 10:00:00', operator: '小张', action: '进行中', type: 'success', remark: '安装基本完成,正在进行调试' }
|
||
]
|
||
}
|
||
])
|
||
|
||
const engineerList = ref([
|
||
{ id: 1, name: '小李', title: '高级工程师', currentTickets: 2, skills: ['电气系统', '控制系统'] },
|
||
{ id: 2, name: '小王', title: '工程师', currentTickets: 1, skills: ['机械维修', '配件更换'] },
|
||
{ id: 3, name: '小张', title: '高级工程师', currentTickets: 1, skills: ['安装调试', '电气系统'] },
|
||
{ id: 4, name: '小陈', title: '工程师', currentTickets: 0, skills: ['机械维修', '控制系统'] },
|
||
{ id: 5, name: '小刘', title: '技术专家', currentTickets: 3, skills: ['电气系统', '机械维修', '控制系统'] }
|
||
])
|
||
|
||
const aiRecommendEngineer = ref(null)
|
||
|
||
const currentConversation = ref([])
|
||
const conversationSummary = ref({
|
||
overview: '',
|
||
demands: [],
|
||
deviceModel: '',
|
||
faultSymptom: '',
|
||
occurTime: ''
|
||
})
|
||
|
||
// 工单筛选
|
||
const filteredTicketList = computed(() => {
|
||
return ticketList.value.filter(ticket => {
|
||
if (ticketStatusFilter.value !== 'all' && ticket.status !== ticketStatusFilter.value) return false
|
||
if (ticketTypeFilter.value && ticket.faultType !== ticketTypeFilter.value) return false
|
||
if (ticketUrgencyFilter.value && ticket.urgency !== ticketUrgencyFilter.value) return false
|
||
if (ticketSearchKeyword.value) {
|
||
const keyword = ticketSearchKeyword.value.toLowerCase()
|
||
if (!ticket.ticketNo.toLowerCase().includes(keyword) &&
|
||
!ticket.customerName.toLowerCase().includes(keyword) &&
|
||
!ticket.deviceModel.toLowerCase().includes(keyword)) {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
})
|
||
})
|
||
|
||
const filteredEngineers = computed(() => {
|
||
if (!engineerSearch.value) return engineerList.value
|
||
return engineerList.value.filter(eng =>
|
||
eng.name.includes(engineerSearch.value) || eng.title.includes(engineerSearch.value)
|
||
)
|
||
})
|
||
|
||
// 工单状态类型
|
||
const getTicketStatusType = (status) => {
|
||
const typeMap = {
|
||
pending: 'warning',
|
||
processing: 'primary',
|
||
completed: 'success',
|
||
closed: 'info'
|
||
}
|
||
return typeMap[status] || 'info'
|
||
}
|
||
|
||
const filterTickets = () => {
|
||
ticketCurrentPage.value = 1
|
||
}
|
||
|
||
// 查看工单详情
|
||
const viewTicketDetail = (ticket) => {
|
||
currentTicket.value = ticket
|
||
showTicketDetailDialog.value = true
|
||
}
|
||
|
||
// 查看对话记录
|
||
const viewTicketConversation = (ticket) => {
|
||
currentConversation.value = [
|
||
{ role: 'user', content: '你好,我的发电机出问题了', time: '09:25:30' },
|
||
{ role: 'assistant', content: '您好!我是泰豪小电,很高兴为您服务。请问您的发电机是什么型号?出现了什么问题?', time: '09:25:32' },
|
||
{ role: 'user', content: '型号是TH-500GF,启动后电压一直跳,不稳定', time: '09:26:15' },
|
||
{ role: 'assistant', content: '了解了。TH-500GF电压不稳定可能有以下几种原因:\n1. AVR(自动电压调节器)故障\n2. 发电机转速不稳\n3. 负载过大或不平衡\n请问这个问题是一直存在还是最近才出现的?', time: '09:26:18' },
|
||
{ role: 'user', content: '最近两天才开始的,之前用着挺好的', time: '09:27:05' },
|
||
{ role: 'assistant', content: '明白了。建议您先检查一下负载情况,如果负载正常,可能需要专业工程师上门检查AVR和调速系统。您需要我帮您创建一个维修工单吗?', time: '09:27:08' },
|
||
{ role: 'user', content: '好的,帮我创建工单吧,比较着急', time: '09:28:00' },
|
||
{ role: 'assistant', content: '好的,我已为您创建紧急维修工单。请问您方便提供一下现场地址和联系电话吗?我们会尽快安排工程师上门服务。', time: '09:28:03' }
|
||
]
|
||
conversationSummary.value = {
|
||
overview: '客户反映TH-500GF发电机组启动后电压不稳定,波动范围较大,问题最近两天才出现,之前运行正常。可能原因包括AVR故障、转速不稳或负载问题。客户希望尽快安排上门维修。',
|
||
demands: [
|
||
'尽快安排工程师上门检修',
|
||
'解决电压不稳定问题',
|
||
'确保设备正常运行'
|
||
],
|
||
deviceModel: 'TH-500GF',
|
||
faultSymptom: '启动后电压不稳定,波动范围较大',
|
||
occurTime: '最近两天'
|
||
}
|
||
conversationTab.value = 'record'
|
||
showConversationDialog.value = true
|
||
}
|
||
|
||
// 显示指派弹窗
|
||
const showAssignDialog = (ticket) => {
|
||
currentTicket.value = ticket
|
||
selectedEngineer.value = ''
|
||
// 生成AI推荐
|
||
generateAIRecommend(ticket)
|
||
showAssignTicketDialog.value = true
|
||
}
|
||
|
||
const showAssignDialogFromDetail = () => {
|
||
selectedEngineer.value = ''
|
||
generateAIRecommend(currentTicket.value)
|
||
showAssignTicketDialog.value = true
|
||
}
|
||
|
||
// AI智能推荐工程师
|
||
const generateAIRecommend = (ticket) => {
|
||
// 模拟AI推荐逻辑
|
||
const faultTypeMap = {
|
||
electrical: { engId: 1, skill: '电气系统' },
|
||
mechanical: { engId: 2, skill: '机械维修' },
|
||
control: { engId: 1, skill: '控制系统' },
|
||
parts: { engId: 2, skill: '配件更换' },
|
||
install: { engId: 3, skill: '安装调试' },
|
||
other: { engId: 4, skill: '综合维修' }
|
||
}
|
||
|
||
const recommend = faultTypeMap[ticket.faultType] || faultTypeMap.other
|
||
const engineer = engineerList.value.find(e => e.id === recommend.engId)
|
||
|
||
if (engineer) {
|
||
aiRecommendEngineer.value = {
|
||
...engineer,
|
||
matchScore: 85 + Math.floor(Math.random() * 10),
|
||
reason: `客户问题涉及${ticket.deviceModel}${ticket.faultTypeName},${engineer.name}工程师在${recommend.skill}方面有丰富项目经验,历史工单解决率98%,平均响应时间2小时。`
|
||
}
|
||
}
|
||
}
|
||
|
||
// 确认指派
|
||
const confirmAssignTicket = () => {
|
||
const engineer = engineerList.value.find(e => e.id === selectedEngineer.value)
|
||
if (engineer && currentTicket.value) {
|
||
currentTicket.value.assignee = engineer.name
|
||
currentTicket.value.status = 'processing'
|
||
currentTicket.value.statusName = '处理中'
|
||
currentTicket.value.logs.push({
|
||
time: new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-'),
|
||
operator: '管理员',
|
||
action: '指派工程师',
|
||
type: 'warning',
|
||
remark: `指派给${engineer.name}工程师处理`
|
||
})
|
||
engineer.currentTickets++
|
||
ticketCounts.value.pending--
|
||
showAssignTicketDialog.value = false
|
||
ElMessage.success(`已成功指派给${engineer.name}工程师`)
|
||
}
|
||
}
|
||
|
||
// 完成工单
|
||
const completeTicket = (ticket) => {
|
||
ElMessageBox.confirm('确认完成该工单?', '完成确认', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'success'
|
||
}).then(() => {
|
||
ticket.status = 'completed'
|
||
ticket.statusName = '已完成'
|
||
ticket.logs.push({
|
||
time: new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-'),
|
||
operator: ticket.assignee,
|
||
action: '完成工单',
|
||
type: 'success',
|
||
remark: '工单已处理完成'
|
||
})
|
||
ticketCounts.value.completed++
|
||
showTicketDetailDialog.value = false
|
||
ElMessage.success('工单已完成')
|
||
}).catch(() => {})
|
||
}
|
||
|
||
// 创建工单
|
||
const submitCreateTicket = () => {
|
||
if (!ticketForm.value.customerName || !ticketForm.value.customerPhone ||
|
||
!ticketForm.value.deviceModel || !ticketForm.value.faultType || !ticketForm.value.faultDesc) {
|
||
ElMessage.warning('请填写必填信息')
|
||
return
|
||
}
|
||
|
||
const faultTypeNames = {
|
||
electrical: '电气系统故障',
|
||
mechanical: '机械故障',
|
||
control: '控制系统故障',
|
||
parts: '配件更换',
|
||
install: '安装调试',
|
||
other: '其他'
|
||
}
|
||
const urgencyNames = { urgent: '紧急', normal: '普通', low: '低' }
|
||
|
||
const newTicket = {
|
||
id: Date.now(),
|
||
ticketNo: 'WO' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + String(ticketList.value.length + 1).padStart(4, '0'),
|
||
customerName: ticketForm.value.customerName,
|
||
customerPhone: ticketForm.value.customerPhone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'),
|
||
deviceModel: ticketForm.value.deviceModel,
|
||
faultType: ticketForm.value.faultType,
|
||
faultTypeName: faultTypeNames[ticketForm.value.faultType],
|
||
faultDesc: ticketForm.value.faultDesc,
|
||
urgency: ticketForm.value.urgency,
|
||
urgencyName: urgencyNames[ticketForm.value.urgency],
|
||
status: 'pending',
|
||
statusName: '待处理',
|
||
source: 'manual',
|
||
assignee: null,
|
||
address: ticketForm.value.address,
|
||
nameplate: ticketForm.value.nameplate,
|
||
createTime: new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-'),
|
||
updateTime: new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-'),
|
||
logs: [
|
||
{
|
||
time: new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-'),
|
||
operator: '李志鹏',
|
||
action: '工单创建',
|
||
type: 'primary',
|
||
remark: '手动创建工单'
|
||
}
|
||
]
|
||
}
|
||
|
||
ticketList.value.unshift(newTicket)
|
||
ticketCounts.value.pending++
|
||
ticketTotal.value++
|
||
showCreateTicketDialog.value = false
|
||
ticketForm.value = {
|
||
customerName: '',
|
||
customerPhone: '',
|
||
deviceModel: '',
|
||
faultType: '',
|
||
faultDesc: '',
|
||
urgency: 'normal',
|
||
address: '',
|
||
nameplate: ''
|
||
}
|
||
ElMessage.success('工单创建成功')
|
||
}
|
||
|
||
// 对话数据管理
|
||
const conversationDateRange = ref(null)
|
||
const conversationCategoryFilter = ref('')
|
||
const conversationStatusFilter = ref('')
|
||
const conversationSearchKeyword = ref('')
|
||
const conversationCurrentPage = ref(1)
|
||
const conversationTotal = ref(86)
|
||
const showConversationDetailDialog = ref(false)
|
||
const conversationDetailTab = ref('record')
|
||
const currentConversationDetail = ref(null)
|
||
const currentConversationMessages = ref([])
|
||
|
||
const conversationList = ref([
|
||
{
|
||
id: 1,
|
||
sessionId: 'CONV20250113001',
|
||
customerName: '张伟',
|
||
customerPhone: '138****5678',
|
||
category: 'fault',
|
||
categoryName: '故障报修',
|
||
deviceModel: 'TH-500GF',
|
||
summary: '发电机启动后电压不稳定,需要工程师上门检修',
|
||
messageCount: 12,
|
||
status: 'ticket',
|
||
statusName: '已转工单',
|
||
satisfaction: 5,
|
||
startTime: '2025-01-13 09:25:00',
|
||
duration: '8分32秒',
|
||
summaryDetail: {
|
||
overview: '客户反映TH-500GF发电机组启动后电压不稳定,波动范围较大,问题最近两天才出现,之前运行正常。经过诊断,可能原因包括AVR故障、转速不稳或负载问题。',
|
||
demands: ['尽快安排工程师上门检修', '解决电压不稳定问题', '确保设备正常运行'],
|
||
solution: '已为客户创建紧急维修工单,安排工程师上门检查AVR和调速系统。'
|
||
},
|
||
messages: [
|
||
{ role: 'user', content: '你好,我的发电机出问题了', time: '09:25:30' },
|
||
{ role: 'assistant', content: '您好!我是泰豪小电,很高兴为您服务。请问您的发电机是什么型号?出现了什么问题?', time: '09:25:32' },
|
||
{ role: 'user', content: '型号是TH-500GF,启动后电压一直跳,不稳定', time: '09:26:15' },
|
||
{ role: 'assistant', content: '了解了。TH-500GF电压不稳定可能有以下几种原因:\n1. AVR(自动电压调节器)故障\n2. 发电机转速不稳\n3. 负载过大或不平衡\n请问这个问题是一直存在还是最近才出现的?', time: '09:26:18' },
|
||
{ role: 'user', content: '最近两天才开始的,之前用着挺好的', time: '09:27:05' },
|
||
{ role: 'assistant', content: '明白了。建议您先检查一下负载情况,如果负载正常,可能需要专业工程师上门检查AVR和调速系统。您需要我帮您创建一个维修工单吗?', time: '09:27:08' },
|
||
{ role: 'user', content: '好的,帮我创建工单吧,比较着急', time: '09:28:00' },
|
||
{ role: 'assistant', content: '好的,我已为您创建紧急维修工单。请问您方便提供一下现场地址和联系电话吗?我们会尽快安排工程师上门服务。', time: '09:28:03' }
|
||
]
|
||
},
|
||
{
|
||
id: 2,
|
||
sessionId: 'CONV20250113002',
|
||
customerName: '李娜',
|
||
customerPhone: '139****2345',
|
||
category: 'device',
|
||
categoryName: '设备操作咨询',
|
||
deviceModel: 'S-200X',
|
||
summary: '咨询静音发电机的操作方法和保养周期',
|
||
messageCount: 8,
|
||
status: 'completed',
|
||
statusName: '已完成',
|
||
satisfaction: 5,
|
||
startTime: '2025-01-13 10:15:00',
|
||
duration: '5分18秒',
|
||
summaryDetail: {
|
||
overview: '客户咨询S-200X静音发电机的日常操作方法和保养周期,包括启动流程、停机注意事项和定期保养项目。',
|
||
demands: ['了解设备操作方法', '了解保养周期和保养项目'],
|
||
solution: '详细介绍了设备的启动/停机流程,以及建议每200小时或3个月进行一次常规保养的计划。'
|
||
},
|
||
messages: [
|
||
{ role: 'user', content: '请问S-200X发电机怎么操作?', time: '10:15:20' },
|
||
{ role: 'assistant', content: '您好!S-200X静音发电机的操作非常简单,我来为您介绍一下:\n\n【启动流程】\n1. 检查燃油和机油液位\n2. 打开燃油开关\n3. 将启动开关转到"ON"位置\n4. 按下启动按钮\n\n请问您还有其他问题吗?', time: '10:15:25' },
|
||
{ role: 'user', content: '保养周期是多久?需要保养哪些项目?', time: '10:17:00' },
|
||
{ role: 'assistant', content: 'S-200X建议每运行200小时或每3个月进行一次常规保养,保养项目包括:\n1. 更换机油和机油滤芯\n2. 检查/更换空气滤芯\n3. 检查燃油滤芯\n4. 检查火花塞(汽油机)\n5. 检查蓄电池电量\n\n如需专业保养服务,可以预约我们的工程师上门。', time: '10:17:05' }
|
||
]
|
||
},
|
||
{
|
||
id: 3,
|
||
sessionId: 'CONV20250112003',
|
||
customerName: '王强',
|
||
customerPhone: '137****8765',
|
||
category: 'parts',
|
||
categoryName: '配件购买',
|
||
deviceModel: 'G-100S',
|
||
summary: '咨询空气滤芯和机油滤芯的价格和购买方式',
|
||
messageCount: 6,
|
||
status: 'completed',
|
||
statusName: '已完成',
|
||
satisfaction: 4,
|
||
startTime: '2025-01-12 14:30:00',
|
||
duration: '3分45秒',
|
||
summaryDetail: {
|
||
overview: '客户需要购买G-100S发电机的空气滤芯和机油滤芯,咨询配件价格和购买渠道。',
|
||
demands: ['了解配件价格', '了解购买方式'],
|
||
solution: '提供了配件价格信息,并引导客户通过官方渠道或授权经销商购买。'
|
||
},
|
||
messages: [
|
||
{ role: 'user', content: 'G-100S的滤芯多少钱?', time: '14:30:15' },
|
||
{ role: 'assistant', content: '您好!G-100S发电机的滤芯价格如下:\n- 空气滤芯:¥85/个\n- 机油滤芯:¥45/个\n\n您可以通过以下方式购买:\n1. 拨打售后热线订购\n2. 联系当地授权经销商\n3. 官方商城在线购买\n\n请问您需要哪种滤芯?', time: '14:30:20' }
|
||
]
|
||
},
|
||
{
|
||
id: 4,
|
||
sessionId: 'CONV20250112004',
|
||
customerName: '赵敏',
|
||
customerPhone: '136****4321',
|
||
category: 'warranty',
|
||
categoryName: '保修政策',
|
||
deviceModel: 'TH-300D',
|
||
summary: '咨询设备保修期限和保修范围',
|
||
messageCount: 5,
|
||
status: 'completed',
|
||
statusName: '已完成',
|
||
satisfaction: null,
|
||
startTime: '2025-01-12 16:45:00',
|
||
duration: '2分58秒',
|
||
summaryDetail: {
|
||
overview: '客户咨询TH-300D柴油发电机的保修政策,包括保修期限和保修范围。',
|
||
demands: ['了解保修期限', '了解保修范围'],
|
||
solution: '告知客户整机保修2年或2000小时,核心部件保修3年,人为损坏和易损件不在保修范围内。'
|
||
},
|
||
messages: [
|
||
{ role: 'user', content: '我买的TH-300D保修多久?', time: '16:45:10' },
|
||
{ role: 'assistant', content: '您好!TH-300D柴油发电机的保修政策如下:\n\n【保修期限】\n- 整机保修:2年或累计运行2000小时(以先到者为准)\n- 核心部件(发动机、发电机):3年\n\n【保修范围】\n- 因产品质量问题导致的故障\n- 不包含人为损坏、使用不当\n- 不包含易损件(滤芯、皮带等)\n\n请问您的设备遇到什么问题了吗?', time: '16:45:15' }
|
||
]
|
||
},
|
||
{
|
||
id: 5,
|
||
sessionId: 'CONV20250111005',
|
||
customerName: '孙丽',
|
||
customerPhone: '135****9876',
|
||
category: 'install',
|
||
categoryName: '安装指导',
|
||
deviceModel: 'TH-800GF',
|
||
summary: '咨询新购发电机的安装要求和注意事项',
|
||
messageCount: 10,
|
||
status: 'ticket',
|
||
statusName: '已转工单',
|
||
satisfaction: 5,
|
||
startTime: '2025-01-11 11:00:00',
|
||
duration: '12分15秒',
|
||
summaryDetail: {
|
||
overview: '客户新购TH-800GF大型发电机组,咨询安装场地要求、电气接入规范和通风散热要求。由于安装较为复杂,已创建安装调试工单。',
|
||
demands: ['了解安装场地要求', '了解电气接入规范', '安排专业安装服务'],
|
||
solution: '详细介绍了安装要求后,已创建安装调试工单,安排工程师上门进行专业安装。'
|
||
},
|
||
messages: [
|
||
{ role: 'user', content: '我刚买了TH-800GF,安装有什么要求?', time: '11:00:30' },
|
||
{ role: 'assistant', content: '恭喜您购买TH-800GF发电机组!这款设备功率较大,安装需要注意以下几点:\n\n【场地要求】\n1. 安装场地需平整坚固,承重不小于2吨/平米\n2. 预留设备四周各1米以上的维护空间\n3. 地面需做防震处理\n\n【通风要求】\n1. 进风口面积不小于2平方米\n2. 排风口面积不小于1.5平方米\n\n您的安装场地是否具备这些条件?', time: '11:00:35' }
|
||
]
|
||
}
|
||
])
|
||
|
||
// 对话数据筛选
|
||
const filteredConversationList = computed(() => {
|
||
return conversationList.value.filter(conv => {
|
||
if (conversationCategoryFilter.value && conv.category !== conversationCategoryFilter.value) return false
|
||
if (conversationStatusFilter.value && conv.status !== conversationStatusFilter.value) return false
|
||
if (conversationSearchKeyword.value) {
|
||
const keyword = conversationSearchKeyword.value.toLowerCase()
|
||
if (!conv.customerName.toLowerCase().includes(keyword) &&
|
||
!conv.deviceModel.toLowerCase().includes(keyword) &&
|
||
!conv.summary.toLowerCase().includes(keyword)) {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
})
|
||
})
|
||
|
||
// 查看对话详情
|
||
const viewConversationDetail = (conv) => {
|
||
currentConversationDetail.value = conv
|
||
currentConversationMessages.value = conv.messages || []
|
||
conversationDetailTab.value = 'record'
|
||
showConversationDetailDialog.value = true
|
||
}
|
||
|
||
// 导出对话数据
|
||
const exportConversations = () => {
|
||
ElMessage.success('对话数据导出中,请稍候...')
|
||
}
|
||
|
||
// 智能体配置
|
||
const agentConfig = ref({
|
||
name: '泰豪小电',
|
||
description: '您好!我是泰豪小电,有什么可以帮助您的吗?',
|
||
style: 'friendly',
|
||
features: {
|
||
autoGreeting: true,
|
||
smartRecommend: true,
|
||
conversationRecord: true,
|
||
voiceChat: false,
|
||
ticketGuide: true,
|
||
satisfaction: true
|
||
},
|
||
responseDelay: 1000,
|
||
maxReplyLength: 500
|
||
})
|
||
|
||
const defaultAgentConfig = {
|
||
name: '泰豪小电',
|
||
description: '您好!我是泰豪小电,有什么可以帮助您的吗?',
|
||
style: 'friendly',
|
||
features: {
|
||
autoGreeting: true,
|
||
smartRecommend: true,
|
||
conversationRecord: true,
|
||
voiceChat: false,
|
||
ticketGuide: true,
|
||
satisfaction: true
|
||
},
|
||
responseDelay: 1000,
|
||
maxReplyLength: 500
|
||
}
|
||
|
||
const saveAgentConfig = () => {
|
||
ElMessage.success('智能体配置保存成功')
|
||
}
|
||
|
||
const resetAgentConfig = () => {
|
||
agentConfig.value = { ...defaultAgentConfig, features: { ...defaultAgentConfig.features } }
|
||
ElMessage.info('配置已重置')
|
||
}
|
||
|
||
// 知识库日志
|
||
const knowledgeLogDateRange = ref(null)
|
||
const knowledgeLogTypeFilter = ref('')
|
||
const knowledgeLogOperatorFilter = ref('')
|
||
const knowledgeLogSearch = ref('')
|
||
const knowledgeLogPage = ref(1)
|
||
const knowledgeLogTotal = ref(45)
|
||
|
||
const knowledgeLogs = ref([
|
||
{ id: 1, time: '2025-01-13 14:30:00', type: 'upload', typeName: '上传', docName: 'TH-800GF安装指南.pdf', category: '产品文档', operator: '李志鹏', result: 'success', remark: '文件大小3.2MB' },
|
||
{ id: 2, time: '2025-01-13 11:20:00', type: 'update', typeName: '更新', docName: 'S-200X操作手册.pdf', category: '产品文档', operator: '李志鹏', result: 'success', remark: '更新第3章内容' },
|
||
{ id: 3, time: '2025-01-13 10:15:00', type: 'reindex', typeName: '重新索引', docName: '常见故障处理指南.docx', category: '维修知识', operator: '系统', result: 'success', remark: '自动触发重新索引' },
|
||
{ id: 4, time: '2025-01-12 16:45:00', type: 'delete', typeName: '删除', docName: '旧版产品目录.pdf', category: '产品文档', operator: '张伟', result: 'success', remark: '文档已过期' },
|
||
{ id: 5, time: '2025-01-12 14:30:00', type: 'upload', typeName: '上传', docName: '2025年配件价格表.xlsx', category: '配件信息', operator: '李志鹏', result: 'success', remark: '文件大小1.5MB' },
|
||
{ id: 6, time: '2025-01-12 10:00:00', type: 'upload', typeName: '上传', docName: '发电机保养规范.pdf', category: '维修知识', operator: '李志鹏', result: 'fail', remark: '文件格式不支持' },
|
||
{ id: 7, time: '2025-01-11 15:20:00', type: 'update', typeName: '更新', docName: 'TH-500GF技术参数.pdf', category: '产品文档', operator: '张伟', result: 'success', remark: '更新技术参数表' }
|
||
])
|
||
|
||
const filteredKnowledgeLogs = computed(() => {
|
||
return knowledgeLogs.value.filter(log => {
|
||
if (knowledgeLogTypeFilter.value && log.type !== knowledgeLogTypeFilter.value) return false
|
||
if (knowledgeLogOperatorFilter.value && log.operator !== knowledgeLogOperatorFilter.value) return false
|
||
if (knowledgeLogSearch.value && !log.docName.toLowerCase().includes(knowledgeLogSearch.value.toLowerCase())) return false
|
||
return true
|
||
})
|
||
})
|
||
|
||
const exportKnowledgeLogs = () => {
|
||
ElMessage.success('知识库日志导出中...')
|
||
}
|
||
|
||
// 工单日志
|
||
const ticketLogDateRange = ref(null)
|
||
const ticketLogNoFilter = ref('')
|
||
const ticketLogTypeFilter = ref('')
|
||
const ticketLogOperatorFilter = ref('')
|
||
const ticketLogPage = ref(1)
|
||
const ticketLogTotal = ref(128)
|
||
|
||
const ticketLogs = ref([
|
||
{ id: 1, time: '2025-01-13 15:00:00', ticketNo: 'WO202501130001', type: 'assign', typeName: '指派', operator: '李志鹏', content: '将工单指派给小李工程师', ip: '192.168.1.100' },
|
||
{ id: 2, time: '2025-01-13 09:30:00', ticketNo: 'WO202501130001', type: 'create', typeName: '创建', operator: '系统', content: '客户通过小电对话创建工单', ip: '-' },
|
||
{ id: 3, time: '2025-01-13 08:15:00', ticketNo: 'WO202501130002', type: 'complete', typeName: '完成', operator: '小李', content: '工单处理完成,故障已修复', ip: '192.168.1.105' },
|
||
{ id: 4, time: '2025-01-12 15:00:00', ticketNo: 'WO202501130002', type: 'assign', typeName: '指派', operator: '李志鹏', content: '将工单指派给小李工程师', ip: '192.168.1.100' },
|
||
{ id: 5, time: '2025-01-12 14:20:00', ticketNo: 'WO202501130002', type: 'create', typeName: '创建', operator: '系统', content: '客户通过小电对话创建工单', ip: '-' },
|
||
{ id: 6, time: '2025-01-12 10:45:00', ticketNo: 'WO202501120003', type: 'create', typeName: '创建', operator: '李志鹏', content: '手动创建工单', ip: '192.168.1.100' },
|
||
{ id: 7, time: '2025-01-11 16:30:00', ticketNo: 'WO202501110004', type: 'close', typeName: '关闭', operator: '李志鹏', content: '客户确认问题已解决,关闭工单', ip: '192.168.1.100' }
|
||
])
|
||
|
||
const filteredTicketLogs = computed(() => {
|
||
return ticketLogs.value.filter(log => {
|
||
if (ticketLogNoFilter.value && !log.ticketNo.includes(ticketLogNoFilter.value)) return false
|
||
if (ticketLogTypeFilter.value && log.type !== ticketLogTypeFilter.value) return false
|
||
if (ticketLogOperatorFilter.value && log.operator !== ticketLogOperatorFilter.value) return false
|
||
return true
|
||
})
|
||
})
|
||
|
||
const exportTicketLogs = () => {
|
||
ElMessage.success('工单日志导出中...')
|
||
}
|
||
|
||
// 系统日志
|
||
const systemLogDateRange = ref(null)
|
||
const systemLogTypeFilter = ref('')
|
||
const systemLogLevelFilter = ref('')
|
||
const systemLogModuleFilter = ref('')
|
||
const systemLogPage = ref(1)
|
||
const systemLogTotal = ref(89)
|
||
|
||
const systemLogs = ref([
|
||
{ id: 1, time: '2025-01-13 14:00:00', logType: 'config', level: 'info', module: 'agent', moduleName: '智能体配置', operator: '李志鹏', content: '修改响应延迟:1000ms -> 1200ms' },
|
||
{ id: 2, time: '2025-01-13 11:30:00', logType: 'error', level: 'warn', module: 'api', moduleName: 'API调用', operator: '系统', content: 'Dify API响应超时,已自动重试(耗时3.2s)' },
|
||
{ id: 3, time: '2025-01-13 10:00:00', logType: 'config', level: 'info', module: 'agent', moduleName: '智能体配置', operator: '李志鹏', content: '开启功能:工单引导' },
|
||
{ id: 4, time: '2025-01-12 18:45:00', logType: 'error', level: 'error', module: 'knowledge', moduleName: '知识库索引', operator: '系统', content: '文档索引失败:发电机保养规范.pdf,原因:文件格式不支持' },
|
||
{ id: 5, time: '2025-01-12 15:30:00', logType: 'config', level: 'info', module: 'agent', moduleName: '智能体配置', operator: '李志鹏', content: '修改对话风格:简洁专业 -> 友好亲切' },
|
||
{ id: 6, time: '2025-01-12 09:00:00', logType: 'error', level: 'warn', module: 'chat', moduleName: '对话服务', operator: '系统', content: '对话会话超时自动关闭,会话ID:CONV20250111008' },
|
||
{ id: 7, time: '2025-01-11 16:20:00', logType: 'config', level: 'info', module: 'agent', moduleName: '智能体配置', operator: '张伟', content: '修改助手描述' },
|
||
{ id: 8, time: '2025-01-11 11:00:00', logType: 'error', level: 'error', module: 'api', moduleName: 'API调用', operator: '系统', content: 'Dify API连接失败,错误码:503,已切换备用节点' }
|
||
])
|
||
|
||
const filteredSystemLogs = computed(() => {
|
||
return systemLogs.value.filter(log => {
|
||
if (systemLogTypeFilter.value && log.logType !== systemLogTypeFilter.value) return false
|
||
if (systemLogLevelFilter.value && log.level !== systemLogLevelFilter.value) return false
|
||
if (systemLogModuleFilter.value && log.module !== systemLogModuleFilter.value) return false
|
||
return true
|
||
})
|
||
})
|
||
|
||
const exportSystemLogs = () => {
|
||
ElMessage.success('系统日志导出中...')
|
||
}
|
||
|
||
const platformConfig = ref({
|
||
systemName: 'AI数智化平台',
|
||
aiModel: 'gpt-4',
|
||
maxConcurrency: 100,
|
||
sessionTimeout: 30
|
||
})
|
||
|
||
const biddingTemplates = ref([
|
||
{ name: '工程类标书模板', category: '工程建设', usage: 45, updateTime: '2024-12-01 10:30' },
|
||
{ name: '服务类标书模板', category: '服务采购', usage: 32, updateTime: '2024-11-28 14:20' },
|
||
{ name: '货物采购模板', category: '货物采购', usage: 28, updateTime: '2024-11-25 09:15' }
|
||
])
|
||
|
||
const knowledgeBase = ref([
|
||
{ title: '设备操作手册_TH-500GF', category: '产品文档', size: '2.3MB', status: '已索引', uploadTime: '2024-11-15 10:20' },
|
||
{ title: '常见故障排查指南', category: '技术文档', size: '856KB', status: '已索引', uploadTime: '2024-11-14 14:30' },
|
||
{ title: '维修案例汇总2023Q3', category: '案例库', size: '1.2MB', status: '处理中', uploadTime: '2024-11-10 09:15' }
|
||
])
|
||
|
||
// 知识库管理
|
||
const activeKbType = ref('external')
|
||
const activeExternalKbCat = ref('device-guide')
|
||
const activeInternalKbCat = ref('tech-manual')
|
||
const externalKbSearch = ref('')
|
||
const internalKbSearch = ref('')
|
||
const showKbUploadDialog = ref(false)
|
||
const kbAiRecognizing = ref(false)
|
||
const kbUploadForm = ref({
|
||
name: '',
|
||
type: 'external',
|
||
category: '',
|
||
uploader: '李志鹏'
|
||
})
|
||
|
||
// 外部知识库分类
|
||
const externalKbCategories = ref([
|
||
{ key: 'device-guide', name: '设备操作指南', count: 24, icon: 'Tools', color: '#409eff' },
|
||
{ key: 'fault-solution', name: '常见故障解决方案', count: 36, icon: 'QuestionFilled', color: '#e6a23c' },
|
||
{ key: 'service-policy', name: '三包外服务政策', count: 12, icon: 'Tickets', color: '#67c23a' },
|
||
{ key: 'parts-consult', name: '配件咨询话术', count: 18, icon: 'ChatDotRound', color: '#909399' }
|
||
])
|
||
|
||
// 外部知识库文件
|
||
const externalKbFiles = ref([
|
||
{ id: 1, name: 'TH-500GF发电机组操作手册.pdf', category: 'device-guide', uploader: '张三', uploadTime: '2025-01-10 10:30' },
|
||
{ id: 2, name: 'S-200X静音发电机使用指南.pdf', category: 'device-guide', uploader: '李四', uploadTime: '2025-01-08 14:20' },
|
||
{ id: 3, name: '发电机无法启动故障排查指南.pdf', category: 'fault-solution', uploader: '王五', uploadTime: '2025-01-05 09:15' },
|
||
{ id: 4, name: '电压不稳定常见原因及解决方案.docx', category: 'fault-solution', uploader: '张三', uploadTime: '2025-01-03 16:45' },
|
||
{ id: 5, name: '三包服务政策说明书(2025版).pdf', category: 'service-policy', uploader: '李四', uploadTime: '2024-12-20 11:00' },
|
||
{ id: 6, name: '延保服务套餐介绍.pdf', category: 'service-policy', uploader: '张三', uploadTime: '2024-12-15 15:30' },
|
||
{ id: 7, name: '配件价格查询话术模板.docx', category: 'parts-consult', uploader: '王五', uploadTime: '2024-12-10 09:00' },
|
||
{ id: 8, name: '滤芯更换周期咨询话术.docx', category: 'parts-consult', uploader: '李四', uploadTime: '2024-12-08 14:00' }
|
||
])
|
||
|
||
// 内部知识库分类
|
||
const internalKbCategories = ref([
|
||
{ key: 'tech-manual', name: '技术维修手册', count: 45, icon: 'Tools', color: '#409eff' },
|
||
{ key: 'product-params', name: '产品参数明细', count: 28, icon: 'Document', color: '#67c23a' },
|
||
{ key: 'service-process', name: '内部服务流程规范', count: 15, icon: 'Tickets', color: '#e6a23c' },
|
||
{ key: 'service-script', name: '客户服务话术模板', count: 22, icon: 'ChatDotRound', color: '#909399' }
|
||
])
|
||
|
||
// 内部知识库文件
|
||
const internalKbFiles = ref([
|
||
{ id: 1, name: 'TH系列发电机维修手册(内部版).pdf', category: 'tech-manual', uploader: '技术部', uploadTime: '2025-01-12 09:00' },
|
||
{ id: 2, name: 'S系列静音发电机拆装指南.pdf', category: 'tech-manual', uploader: '技术部', uploadTime: '2025-01-10 11:30' },
|
||
{ id: 3, name: '控制系统故障诊断流程.pdf', category: 'tech-manual', uploader: '技术部', uploadTime: '2025-01-08 14:00' },
|
||
{ id: 4, name: 'TH-500GF产品参数表.xlsx', category: 'product-params', uploader: '产品部', uploadTime: '2025-01-05 10:00' },
|
||
{ id: 5, name: 'S-200X技术规格书.pdf', category: 'product-params', uploader: '产品部', uploadTime: '2025-01-03 15:30' },
|
||
{ id: 6, name: '售后服务标准流程(内部).pdf', category: 'service-process', uploader: '服务部', uploadTime: '2024-12-28 09:00' },
|
||
{ id: 7, name: '工单处理规范V2.0.docx', category: 'service-process', uploader: '服务部', uploadTime: '2024-12-25 11:00' },
|
||
{ id: 8, name: '电话接待话术模板(内部).docx', category: 'service-script', uploader: '培训部', uploadTime: '2024-12-20 14:00' },
|
||
{ id: 9, name: '投诉处理话术指南.docx', category: 'service-script', uploader: '培训部', uploadTime: '2024-12-18 16:30' }
|
||
])
|
||
|
||
// 当前分类名称
|
||
const currentExternalCatName = computed(() => {
|
||
const cat = externalKbCategories.value.find(c => c.key === activeExternalKbCat.value)
|
||
return cat ? cat.name : ''
|
||
})
|
||
|
||
const currentInternalCatName = computed(() => {
|
||
const cat = internalKbCategories.value.find(c => c.key === activeInternalKbCat.value)
|
||
return cat ? cat.name : ''
|
||
})
|
||
|
||
// 过滤后的文件列表
|
||
const filteredExternalKbFiles = computed(() => {
|
||
return externalKbFiles.value.filter(f => {
|
||
if (f.category !== activeExternalKbCat.value) return false
|
||
if (externalKbSearch.value && !f.name.includes(externalKbSearch.value)) return false
|
||
return true
|
||
})
|
||
})
|
||
|
||
const filteredInternalKbFiles = computed(() => {
|
||
return internalKbFiles.value.filter(f => {
|
||
if (f.category !== activeInternalKbCat.value) return false
|
||
if (internalKbSearch.value && !f.name.includes(internalKbSearch.value)) return false
|
||
return true
|
||
})
|
||
})
|
||
|
||
// 知识库操作方法
|
||
const handleKbFileChange = (file) => {
|
||
kbUploadForm.value.name = file.name.replace(/\.[^.]+$/, '')
|
||
}
|
||
|
||
const aiRecognizeKbFile = () => {
|
||
kbAiRecognizing.value = true
|
||
setTimeout(() => {
|
||
const externalMap = { '操作': 'device-guide', '指南': 'device-guide', '故障': 'fault-solution', '解决': 'fault-solution', '三包': 'service-policy', '服务': 'service-policy', '配件': 'parts-consult', '话术': 'parts-consult' }
|
||
const internalMap = { '维修': 'tech-manual', '手册': 'tech-manual', '参数': 'product-params', '规格': 'product-params', '流程': 'service-process', '规范': 'service-process', '话术': 'service-script', '模板': 'service-script' }
|
||
const categoryMap = kbUploadForm.value.type === 'external' ? externalMap : internalMap
|
||
|
||
for (const [keyword, category] of Object.entries(categoryMap)) {
|
||
if (kbUploadForm.value.name.includes(keyword)) {
|
||
kbUploadForm.value.category = category
|
||
break
|
||
}
|
||
}
|
||
if (!kbUploadForm.value.category) {
|
||
kbUploadForm.value.category = kbUploadForm.value.type === 'external' ? 'device-guide' : 'tech-manual'
|
||
}
|
||
kbAiRecognizing.value = false
|
||
ElMessage.success('AI识别完成,已自动填充分类信息')
|
||
}, 1500)
|
||
}
|
||
|
||
const confirmKbUpload = () => {
|
||
if (!kbUploadForm.value.name || !kbUploadForm.value.category) {
|
||
ElMessage.warning('请填写完整信息')
|
||
return
|
||
}
|
||
const newFile = {
|
||
id: Date.now(),
|
||
name: kbUploadForm.value.name + '.pdf',
|
||
category: kbUploadForm.value.category,
|
||
uploader: kbUploadForm.value.uploader,
|
||
uploadTime: new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-')
|
||
}
|
||
if (kbUploadForm.value.type === 'external') {
|
||
externalKbFiles.value.unshift(newFile)
|
||
const cat = externalKbCategories.value.find(c => c.key === kbUploadForm.value.category)
|
||
if (cat) cat.count++
|
||
} else {
|
||
internalKbFiles.value.unshift(newFile)
|
||
const cat = internalKbCategories.value.find(c => c.key === kbUploadForm.value.category)
|
||
if (cat) cat.count++
|
||
}
|
||
showKbUploadDialog.value = false
|
||
kbUploadForm.value = { name: '', type: 'external', category: '', uploader: '李志鹏' }
|
||
ElMessage.success('文件上传成功')
|
||
}
|
||
|
||
const previewKbFile = (file) => {
|
||
ElMessage.info(`正在预览:${file.name}`)
|
||
}
|
||
|
||
const downloadKbFile = (file) => {
|
||
ElMessage.success(`开始下载:${file.name}`)
|
||
}
|
||
|
||
const deleteKbFile = (file, type) => {
|
||
ElMessageBox.confirm(`确定要删除文件"${file.name}"吗?`, '删除确认', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
if (type === 'external') {
|
||
const idx = externalKbFiles.value.findIndex(f => f.id === file.id)
|
||
if (idx > -1) {
|
||
externalKbFiles.value.splice(idx, 1)
|
||
const cat = externalKbCategories.value.find(c => c.key === file.category)
|
||
if (cat) cat.count--
|
||
}
|
||
} else {
|
||
const idx = internalKbFiles.value.findIndex(f => f.id === file.id)
|
||
if (idx > -1) {
|
||
internalKbFiles.value.splice(idx, 1)
|
||
const cat = internalKbCategories.value.find(c => c.key === file.category)
|
||
if (cat) cat.count--
|
||
}
|
||
}
|
||
ElMessage.success('删除成功')
|
||
}).catch(() => {})
|
||
}
|
||
|
||
const backToMain = () => {
|
||
router.push('/')
|
||
}
|
||
|
||
const goToProfile = () => {
|
||
activeNav.value = 'profile'
|
||
}
|
||
|
||
// 个人中心数据
|
||
const activeProfileTab = ref('basic')
|
||
const showPasswordDialog = ref(false)
|
||
const twoFactorEnabled = ref(false)
|
||
|
||
const profileForm = ref({
|
||
name: '李志鹏',
|
||
employeeId: 'TH20230001',
|
||
department: '技术研发部',
|
||
position: '高级工程师',
|
||
phone: '13800138000',
|
||
email: 'lizhipeng@taihao.com'
|
||
})
|
||
|
||
const operationLogs = ref([
|
||
{ time: '2024-12-06 18:30:00', action: '登录系统', ip: '192.168.1.100', device: 'Windows 11 / Chrome' },
|
||
{ time: '2024-12-06 14:20:00', action: '修改个人信息', ip: '192.168.1.100', device: 'Windows 11 / Chrome' },
|
||
{ time: '2024-12-05 09:15:00', action: '登录系统', ip: '192.168.1.100', device: 'Windows 11 / Chrome' },
|
||
{ time: '2024-12-04 16:45:00', action: '创建工单', ip: '192.168.1.100', device: 'Windows 11 / Chrome' }
|
||
])
|
||
|
||
const saveProfile = () => {
|
||
ElMessage.success('个人信息保存成功!')
|
||
}
|
||
|
||
const resetProfile = () => {
|
||
ElMessage.info('已重置为原始信息')
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.admin-system {
|
||
display: flex;
|
||
height: 100vh;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
// 内容容器
|
||
.admin-container {
|
||
flex: 1;
|
||
display: flex;
|
||
overflow: hidden;
|
||
}
|
||
|
||
// 左侧主导航
|
||
.admin-sidebar-main {
|
||
width: 200px;
|
||
background: #F0EAF4;
|
||
border-right: 1px solid #e4e7ed;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.sidebar-header {
|
||
padding: 20px;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||
|
||
.logo {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
cursor: pointer;
|
||
transition: opacity 0.2s;
|
||
|
||
&:hover {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.logo-img {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 6px;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.logo-text {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
}
|
||
}
|
||
|
||
.main-nav {
|
||
flex: 1;
|
||
padding: 16px 0;
|
||
overflow-y: auto;
|
||
|
||
.section-title {
|
||
padding: 8px 20px;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.main-nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 12px 20px;
|
||
cursor: pointer;
|
||
color: #606266;
|
||
font-size: 14px;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
background: rgba(124, 58, 237, 0.1);
|
||
color: #7c3aed;
|
||
}
|
||
|
||
&.active {
|
||
background: rgba(124, 58, 237, 0.15);
|
||
color: #7c3aed;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.el-icon {
|
||
font-size: 18px;
|
||
}
|
||
}
|
||
|
||
.nav-divider {
|
||
height: 1px;
|
||
background: rgba(0, 0, 0, 0.08);
|
||
margin: 12px 20px;
|
||
}
|
||
}
|
||
|
||
.sidebar-footer {
|
||
margin-top: auto;
|
||
padding: 16px;
|
||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||
|
||
.user-section {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
|
||
.user-avatar {
|
||
width: 36px;
|
||
height: 36px;
|
||
cursor: pointer;
|
||
transition: transform 0.2s;
|
||
|
||
&:hover {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
}
|
||
|
||
.user-name {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
}
|
||
|
||
.back-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
color: #606266;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
color: #7c3aed;
|
||
background: rgba(124, 58, 237, 0.1);
|
||
}
|
||
|
||
.el-icon {
|
||
font-size: 18px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 左侧子导航
|
||
.admin-sidebar-sub {
|
||
width: 180px;
|
||
background: #fff;
|
||
border-right: 1px solid #e4e7ed;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow-y: auto;
|
||
|
||
.sub-nav {
|
||
padding: 16px 0;
|
||
|
||
.sub-nav-title {
|
||
padding: 8px 20px;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.sub-nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 20px;
|
||
cursor: pointer;
|
||
color: #606266;
|
||
font-size: 14px;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
background: rgba(124, 58, 237, 0.1);
|
||
color: #7c3aed;
|
||
}
|
||
|
||
&.active {
|
||
background: rgba(124, 58, 237, 0.15);
|
||
color: #7c3aed;
|
||
border-right: 3px solid #7c3aed;
|
||
}
|
||
|
||
.el-icon {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.admin-main {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.admin-content {
|
||
.content-header {
|
||
padding: 32px 40px;
|
||
background: #fff;
|
||
border-bottom: 1px solid #e4e7ed;
|
||
|
||
h1 {
|
||
font-size: 28px;
|
||
margin: 0 0 8px 0;
|
||
color: #303133;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
.dashboard-cards {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 20px;
|
||
padding: 24px 40px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: #fff;
|
||
padding: 24px;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
gap: 16px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||
|
||
.stat-icon {
|
||
width: 56px;
|
||
height: 56px;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 28px;
|
||
color: #fff;
|
||
}
|
||
|
||
.stat-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 28px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
}
|
||
|
||
:deep(.el-card) {
|
||
margin: 0 40px 24px;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
// 个人中心样式
|
||
.profile-content {
|
||
.profile-main {
|
||
padding: 24px 40px;
|
||
}
|
||
|
||
.profile-section {
|
||
.avatar-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-bottom: 32px;
|
||
padding: 24px;
|
||
background: #f5f7fa;
|
||
border-radius: 8px;
|
||
}
|
||
}
|
||
|
||
.security-section {
|
||
.security-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20px 0;
|
||
|
||
.security-info {
|
||
h3 {
|
||
font-size: 16px;
|
||
margin: 0 0 8px 0;
|
||
color: #303133;
|
||
}
|
||
|
||
p {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 知识库管理样式
|
||
.content-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 24px;
|
||
|
||
.header-title {
|
||
h1 {
|
||
font-size: 24px;
|
||
margin: 0 0 8px 0;
|
||
color: #303133;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.tab-desc {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin: 0 0 20px 0;
|
||
}
|
||
|
||
.kb-categories {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 16px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.kb-category-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 20px 16px;
|
||
background: #f5f7fa;
|
||
border: 2px solid transparent;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
border-color: #409eff;
|
||
background: #ecf5ff;
|
||
}
|
||
|
||
&.active {
|
||
border-color: #409eff;
|
||
background: #ecf5ff;
|
||
}
|
||
|
||
.el-icon {
|
||
font-size: 28px;
|
||
}
|
||
|
||
.cat-name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
text-align: center;
|
||
}
|
||
|
||
.cat-count {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
.kb-files-section {
|
||
background: #fafafa;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.section-toolbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
|
||
h3 {
|
||
font-size: 16px;
|
||
margin: 0;
|
||
color: #303133;
|
||
}
|
||
}
|
||
|
||
.file-name-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
|
||
.file-icon {
|
||
color: #409eff;
|
||
font-size: 18px;
|
||
}
|
||
}
|
||
|
||
// 日志导航样式
|
||
.sub-nav-group {
|
||
.sub-nav-item.has-children {
|
||
justify-content: flex-start;
|
||
|
||
.arrow-icon {
|
||
margin-left: auto;
|
||
transition: transform 0.2s;
|
||
}
|
||
|
||
&.expanded .arrow-icon {
|
||
transform: rotate(180deg);
|
||
}
|
||
}
|
||
}
|
||
|
||
.sub-nav-children {
|
||
background: rgba(124, 58, 237, 0.05);
|
||
|
||
.sub-nav-child-item {
|
||
padding: 8px 20px 8px 44px;
|
||
cursor: pointer;
|
||
color: #606266;
|
||
font-size: 13px;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
background: rgba(124, 58, 237, 0.1);
|
||
color: #7c3aed;
|
||
}
|
||
|
||
&.active {
|
||
background: rgba(124, 58, 237, 0.15);
|
||
color: #7c3aed;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 数据概览样式
|
||
.dashboard-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20px;
|
||
padding: 0 40px;
|
||
margin-bottom: 24px;
|
||
|
||
.stat-card {
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 20px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||
transition: all 0.3s;
|
||
|
||
&.clickable {
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||
}
|
||
}
|
||
|
||
.stat-icon {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 28px;
|
||
color: #fff;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.stat-info {
|
||
flex: 1;
|
||
|
||
.stat-value {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
color: #303133;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.stat-trend {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-top: 8px;
|
||
|
||
&.up {
|
||
color: #67c23a;
|
||
}
|
||
|
||
&.down {
|
||
color: #f56c6c;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.dashboard-charts {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 20px;
|
||
padding: 0 40px;
|
||
margin-bottom: 24px;
|
||
|
||
.chart-card {
|
||
margin: 0 !important;
|
||
}
|
||
}
|
||
|
||
.question-stats {
|
||
.question-stat-item {
|
||
padding: 12px 0;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
&.clickable {
|
||
cursor: pointer;
|
||
padding: 12px;
|
||
margin: 0 -12px;
|
||
border-radius: 8px;
|
||
|
||
&:hover {
|
||
background: #f5f7fa;
|
||
}
|
||
}
|
||
|
||
.stat-bar-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
|
||
.stat-name {
|
||
font-size: 14px;
|
||
color: #303133;
|
||
}
|
||
|
||
.stat-count {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.stat-bar {
|
||
height: 8px;
|
||
background: #f0f0f0;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
|
||
.stat-bar-fill {
|
||
height: 100%;
|
||
border-radius: 4px;
|
||
transition: width 0.6s ease;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.product-cloud {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16px 24px;
|
||
padding: 20px 0;
|
||
justify-content: center;
|
||
align-items: center;
|
||
min-height: 180px;
|
||
|
||
.cloud-tag {
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
font-weight: 500;
|
||
|
||
&:hover {
|
||
transform: scale(1.15);
|
||
opacity: 1 !important;
|
||
}
|
||
}
|
||
}
|
||
|
||
.quick-entry-card {
|
||
margin: 0 40px 24px !important;
|
||
}
|
||
|
||
.quick-entries {
|
||
display: flex;
|
||
gap: 24px;
|
||
justify-content: center;
|
||
|
||
.quick-entry-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 20px 32px;
|
||
cursor: pointer;
|
||
border-radius: 12px;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
background: #f5f7fa;
|
||
|
||
.entry-icon {
|
||
transform: scale(1.1);
|
||
}
|
||
}
|
||
|
||
.entry-icon {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 24px;
|
||
color: #fff;
|
||
transition: transform 0.2s;
|
||
}
|
||
|
||
span {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 工单管理样式
|
||
.filter-card {
|
||
margin: 0 40px 16px !important;
|
||
}
|
||
|
||
.ticket-filters {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
|
||
.el-radio-group {
|
||
.el-badge {
|
||
margin-left: 4px;
|
||
}
|
||
}
|
||
|
||
.filter-right {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
}
|
||
|
||
.ticket-no {
|
||
font-family: monospace;
|
||
color: #409eff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.customer-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
|
||
.name {
|
||
font-weight: 500;
|
||
color: #303133;
|
||
}
|
||
|
||
.phone {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
.unassigned {
|
||
color: #c0c4cc;
|
||
font-style: italic;
|
||
}
|
||
|
||
.table-pagination {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
// 工单详情样式
|
||
.ticket-detail {
|
||
.detail-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
|
||
.ticket-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
|
||
.ticket-no {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-section {
|
||
margin-top: 24px;
|
||
|
||
h4 {
|
||
font-size: 15px;
|
||
margin: 0 0 16px 0;
|
||
color: #303133;
|
||
}
|
||
}
|
||
|
||
.log-content {
|
||
.log-operator {
|
||
font-weight: 500;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.log-action {
|
||
color: #606266;
|
||
}
|
||
}
|
||
|
||
.log-remark {
|
||
margin-top: 4px;
|
||
font-size: 13px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
.ticket-images {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
|
||
.ticket-image-item {
|
||
width: 120px;
|
||
height: 90px;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
border: 1px solid #e4e7ed;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
border-color: #409eff;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
}
|
||
|
||
.image-error {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #f5f7fa;
|
||
color: #c0c4cc;
|
||
font-size: 24px;
|
||
}
|
||
}
|
||
|
||
.detail-section h4 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
|
||
.el-icon {
|
||
color: #7c3aed;
|
||
}
|
||
}
|
||
|
||
// 对话数据管理样式
|
||
.conversation-filters {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.session-id {
|
||
font-family: monospace;
|
||
color: #7c3aed;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.conversation-summary-text {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
color: #606266;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.satisfaction-stars {
|
||
display: flex;
|
||
gap: 2px;
|
||
|
||
.star-filled {
|
||
color: #f7ba2a;
|
||
}
|
||
|
||
.star-empty {
|
||
color: #e4e7ed;
|
||
}
|
||
}
|
||
|
||
.no-rating {
|
||
color: #c0c4cc;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.conversation-detail-header {
|
||
margin-bottom: 20px;
|
||
padding-bottom: 16px;
|
||
border-bottom: 1px solid #e4e7ed;
|
||
|
||
.detail-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
|
||
.session-id {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.detail-meta {
|
||
display: flex;
|
||
gap: 24px;
|
||
font-size: 13px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
// 智能体管理样式
|
||
.agent-config-card {
|
||
margin: 0 40px !important;
|
||
}
|
||
|
||
.agent-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
|
||
.agent-info {
|
||
flex: 1;
|
||
|
||
h2 {
|
||
margin: 0 0 4px 0;
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.agent-type {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
}
|
||
|
||
.agent-form {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 0 40px;
|
||
|
||
.el-form-item {
|
||
margin-bottom: 24px;
|
||
|
||
&.full-width {
|
||
grid-column: 1 / -1;
|
||
}
|
||
}
|
||
|
||
.feature-switches {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 12px 24px;
|
||
|
||
.el-checkbox {
|
||
margin-right: 0;
|
||
}
|
||
}
|
||
|
||
.form-tip {
|
||
margin-top: 8px;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
|
||
.form-actions {
|
||
grid-column: 1 / -1;
|
||
margin-top: 16px;
|
||
}
|
||
}
|
||
|
||
// 日志管理样式
|
||
.log-filters {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.log-level {
|
||
display: inline-block;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
|
||
&.info {
|
||
background: #e6f7ff;
|
||
color: #1890ff;
|
||
}
|
||
|
||
&.warn {
|
||
background: #fff7e6;
|
||
color: #fa8c16;
|
||
}
|
||
|
||
&.error {
|
||
background: #fff2f0;
|
||
color: #ff4d4f;
|
||
}
|
||
}
|
||
|
||
// 指派工程师弹窗样式
|
||
.assign-dialog {
|
||
.ai-recommend {
|
||
background: linear-gradient(135deg, #f0e6ff, #e6f0ff);
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
margin-bottom: 20px;
|
||
|
||
.recommend-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: #7c3aed;
|
||
font-weight: 600;
|
||
margin-bottom: 12px;
|
||
|
||
.el-icon {
|
||
font-size: 18px;
|
||
}
|
||
}
|
||
|
||
.engineer-card.recommended {
|
||
background: #fff;
|
||
border: 2px solid #7c3aed;
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
|
||
.match-score {
|
||
text-align: center;
|
||
|
||
.score {
|
||
display: block;
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #7c3aed;
|
||
}
|
||
|
||
.label {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
}
|
||
|
||
.recommend-reason {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
margin-top: 12px;
|
||
padding: 12px;
|
||
background: rgba(255, 255, 255, 0.6);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
color: #606266;
|
||
line-height: 1.6;
|
||
|
||
.el-icon {
|
||
color: #7c3aed;
|
||
margin-top: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.engineer-list {
|
||
.list-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
|
||
span {
|
||
font-weight: 500;
|
||
color: #303133;
|
||
}
|
||
}
|
||
|
||
.list-content {
|
||
max-height: 240px;
|
||
overflow-y: auto;
|
||
|
||
.engineer-card {
|
||
padding: 12px;
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 8px;
|
||
margin-bottom: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
border-color: #409eff;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
&.selected {
|
||
border-color: #409eff;
|
||
background: #ecf5ff;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.engineer-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
|
||
.info-text {
|
||
flex: 1;
|
||
|
||
.name {
|
||
font-weight: 500;
|
||
color: #303133;
|
||
}
|
||
|
||
.title {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-top: 2px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 对话记录弹窗样式
|
||
.conversation-record {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
padding: 16px;
|
||
background: #f5f7fa;
|
||
border-radius: 8px;
|
||
|
||
.message-item {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
|
||
&.user {
|
||
flex-direction: row-reverse;
|
||
|
||
.message-content {
|
||
align-items: flex-end;
|
||
|
||
.message-text {
|
||
background: #409eff;
|
||
color: #fff;
|
||
}
|
||
}
|
||
}
|
||
|
||
&.assistant {
|
||
.message-content {
|
||
.message-text {
|
||
background: #fff;
|
||
}
|
||
}
|
||
}
|
||
|
||
.message-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-width: 70%;
|
||
|
||
.message-header {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-bottom: 4px;
|
||
font-size: 12px;
|
||
|
||
.sender {
|
||
color: #606266;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.time {
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
.message-text {
|
||
padding: 12px 16px;
|
||
border-radius: 12px;
|
||
line-height: 1.6;
|
||
white-space: pre-wrap;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.conversation-summary {
|
||
.summary-section {
|
||
margin-bottom: 24px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
h4 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 15px;
|
||
margin: 0 0 12px 0;
|
||
color: #303133;
|
||
|
||
.el-icon {
|
||
color: #7c3aed;
|
||
}
|
||
}
|
||
|
||
p {
|
||
margin: 0;
|
||
line-height: 1.8;
|
||
color: #606266;
|
||
}
|
||
|
||
ul {
|
||
margin: 0;
|
||
padding-left: 20px;
|
||
|
||
li {
|
||
line-height: 1.8;
|
||
color: #606266;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 占位页面样式
|
||
.placeholder-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 80px 20px;
|
||
text-align: center;
|
||
|
||
.placeholder-icon {
|
||
font-size: 64px;
|
||
color: #dcdfe6;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
h3 {
|
||
font-size: 18px;
|
||
color: #606266;
|
||
margin: 0 0 12px 0;
|
||
}
|
||
|
||
p {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
</style>
|