449c6d4875
## H5 员工端 v4 (2026-07-13 00:48 已部署)
- 人工按钮三态文案统一为"人工坐席"
- 按钮位置移至发送键和语音按钮上方(垂直堆叠)
- 点按钮直接调 store.shakeAgent(),删除 CallAgentModal 弹窗动画
- 截图快捷键提示改为"截图->粘贴:Alt+Shift+A-Ctrl+V ---> Ctrl+V"
- 移动端隐藏截图提示(CSS 媒体查询)
- AI转人工提示改为"已为您呼叫人工坐席,请稍等!"
- 坐席接入提示改为"坐席正在查看您的信息,请等待处理回复!"
- 删除"摇铃呼叫坐席"入口和文案
- 删除孤儿组件 MessageList.vue + shake 动画 CSS
## H5 员工端 v5 (2026-07-13 02:08 已部署)
- RightPanel v2.1:删除"软件安装"和"资源权限"标签页
- 移除标签栏,智能推荐(DynamicRecommend)直接展示
- 删除 SoftwareDownloads/ApprovalLinks 引用和相关 CSS
## AI 对话链路全栈改造 Phase 1-6 (已部署)
- Phase 1: Dify JSON输出 + 后端blocking解析 + 双WS推送 + 错误降级
- Phase 2: 关键词收窄(~25强意图词) + 两级分类Prompt + 删除前端checkApprovalIntent
- Phase 3: WS扩展(ai_thinking+dynamic_recommend) + ai_structured气泡 + RightPanel v2 + 选项回传
- Phase 4: VisionService接入 + 图片消息融合(5秒窗口) + 降级策略
- Phase 5: 坐席端ai_thinking指示器 + ai_structured/byod_card渲染 + handleNewMessage修复
- Phase 6: diagnosis_stage(6值) + response_time_ms计时 + 慢响应告警(>10s)
## 坐席端 v5 (2026-07-13 01:38 已部署)
- ai_structured/byod_card 只读渲染
- AI思考指示器 UI
- handleNewMessage 透传 msg_type/extra_data 修复
- 布局优化v2.0: QuickReplyBar L1+L2悬浮 + ReplyBox左右分区 + 右栏260/560px切换
- 键盘快捷键v2.3: 纯数字路由 + ESC分层撤销 + Shift+Space用event.code
## 上下文感知智能诊断闭环 (2026-07-12 已部署)
- 三层诊断(API→Script→AI) + 三段排队(VIP→info_locked→not locked)
- 答题插队 + 五场景关闭
- 迁移052(6表+6列) + queue_service + quiz_service + closing_service
- H5前端: QueueWaiting + RightPanel双Tab + InputBar三态 + ResolveConfirmCard
- 坐席前端: pending_close结单流程 + 信息锁定(Dify步骤完成+有效回答率≥70%)
## 知识库迭代3 (2026-07-12 已部署)
- 分诊交互(H5+坐席+Dify独立应用)
- 拓扑预览(ECharts只读)
- 代答排除(4种匹配器: keyword/regex/intent/category)
- 迁移051 + 44文件43测试通过
## 后端变更
- 6个Python文件改造(h5_ai_task.py/h5.py/ai_service.py/closing_service.py等)
- funny_phrase_service.py: shake/connected/keyword 默认文案更新
- session_service.py: 企微消息文案同步
- 新增: queue.py/quiz.py/triage.py/exclusion_rules.py 等API端点
- 新增: diagnostic.py/quiz.py/triage_session.py 等模型
- 新增: closing_service/queue_service/quiz_service/triage_service 等服务
## 文档更新
- CHANGELOG.md: 新增 [未发布] 区全部变更记录
- 项目管理主文档 v2.5: 新增v0.7.3版本 + 已完成看板 + 最近搞定
- 版本记录: 新增v0.7.3条目
- AI对话链路实施计划: Phase 1-6 全部标记✅已实施
- 新增架构图/时序图/类图(mermaid)
## 部署路径修正
- 服务器项目根路径: /opt/wecom-it-desk/
- 所有前端dist均为ro bind mount,只能在宿主机源路径操作
- 服务器nginx /h5/ 是静态文件服务(非proxy_pass)
- elFinder上传二进制不可靠(MD5不匹配),改用base64分块上传
346 lines
13 KiB
Vue
346 lines
13 KiB
Vue
<!--
|
||
=============================================================================
|
||
企微IT智能服务台 — 终端设备报修页面
|
||
=============================================================================
|
||
说明:小鱼终端大屏设备报修页面
|
||
- 设备类型选择(投影仪/视频会议设备/空调/桌椅/网络/其他)
|
||
- 故障描述文本输入(支持快捷选项)
|
||
- 匿名报修(未登录时也可提交)
|
||
- 提交后创建 IT 工单 + 通知管理员
|
||
- 成功/失败反馈,自动返回状态页
|
||
- 1920×1080 横屏深色主题适配
|
||
=============================================================================
|
||
-->
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { useTerminalStore } from '@/stores/terminal'
|
||
import { submitRepair } from '@/api/meetingroom'
|
||
import { useTerminal } from '@/composables/useTerminal'
|
||
import type { DeviceTypeOption } from '@/types/meetingroom'
|
||
|
||
// ==========================================================================
|
||
// 路由 & Store & 终端
|
||
// ==========================================================================
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const store = useTerminalStore()
|
||
const { close: closePage } = useTerminal()
|
||
|
||
// ==========================================================================
|
||
// 设备类型选项
|
||
// ==========================================================================
|
||
|
||
/** 设备类型列表(与后端 repair_service.py 的 DEVICE_TYPE_LABELS 对应) */
|
||
const deviceTypeOptions: DeviceTypeOption[] = [
|
||
{ value: 'projector', label: '投影仪', icon: '📽️' },
|
||
{ value: 'video_conf', label: '视频会议设备', icon: '📹' },
|
||
{ value: 'aircon', label: '空调', icon: '❄️' },
|
||
{ value: 'desk_chair', label: '桌椅', icon: '🪑' },
|
||
{ value: 'network', label: '网络', icon: '🌐' },
|
||
{ value: 'other', label: '其他设备', icon: '🔧' },
|
||
]
|
||
|
||
// ==========================================================================
|
||
// 表单状态
|
||
// ==========================================================================
|
||
|
||
/** 选中的设备类型 */
|
||
const selectedDeviceType = ref<string>('')
|
||
/** 故障描述 */
|
||
const faultDescription = ref('')
|
||
/** 提交中 */
|
||
const submitting = ref(false)
|
||
/** 提交结果 */
|
||
const submitResult = ref<{ success: boolean; repairId?: number; message: string } | null>(null)
|
||
|
||
/** 快捷故障描述选项 */
|
||
const quickFaultOptions = [
|
||
'无法开机',
|
||
'画面模糊',
|
||
'声音异常',
|
||
'连接失败',
|
||
'设备老化',
|
||
'其他问题',
|
||
]
|
||
|
||
// ==========================================================================
|
||
// 计算属性
|
||
// ==========================================================================
|
||
|
||
/** 终端SN */
|
||
const terminalSn = computed(() => route.params.sn as string || store.terminalSn)
|
||
|
||
/** 会议室信息 */
|
||
const meetingroomId = computed(() => store.meetingroomInfo?.meetingroom_id ?? null)
|
||
const meetingroomName = computed(() => store.meetingroomName)
|
||
|
||
/** 是否可提交 */
|
||
const canSubmit = computed(() => {
|
||
return selectedDeviceType.value !== '' && faultDescription.value.trim().length > 0 && !submitting.value
|
||
})
|
||
|
||
/** 选中设备类型的显示信息 */
|
||
const selectedDeviceInfo = computed(() => {
|
||
return deviceTypeOptions.find(d => d.value === selectedDeviceType.value)
|
||
})
|
||
|
||
// ==========================================================================
|
||
// 方法
|
||
// ==========================================================================
|
||
|
||
/** 选择设备类型 */
|
||
function selectDeviceType(value: string) {
|
||
selectedDeviceType.value = value
|
||
}
|
||
|
||
/** 快速填充故障描述 */
|
||
function quickFillFault(text: string) {
|
||
// 如果已有内容,追加而不是覆盖
|
||
if (faultDescription.value && !faultDescription.value.includes(text)) {
|
||
faultDescription.value = `${faultDescription.value};${text}`
|
||
} else {
|
||
faultDescription.value = text
|
||
}
|
||
}
|
||
|
||
/** 提交报修 */
|
||
async function handleSubmit() {
|
||
if (!canSubmit.value) return
|
||
if (!meetingroomId.value) {
|
||
submitResult.value = { success: false, message: '会议室信息缺失,无法提交报修' }
|
||
return
|
||
}
|
||
|
||
submitting.value = true
|
||
submitResult.value = null
|
||
|
||
try {
|
||
const result = await submitRepair({
|
||
terminal_sn: terminalSn.value,
|
||
meetingroom_id: meetingroomId.value,
|
||
meetingroom_name: meetingroomName.value,
|
||
device_type: selectedDeviceType.value,
|
||
fault_description: faultDescription.value.trim(),
|
||
// 如果已登录,附带用户信息;未登录则匿名提交
|
||
reporter_name: store.isLoggedIn ? store.loginUser?.name : undefined,
|
||
reporter_userid: store.isLoggedIn ? store.loginUser?.userid : undefined,
|
||
})
|
||
|
||
submitResult.value = {
|
||
success: true,
|
||
repairId: result.repair_id,
|
||
message: '报修已提交,IT人员将尽快处理',
|
||
}
|
||
|
||
// 3秒后自动返回状态页
|
||
setTimeout(() => {
|
||
goBack()
|
||
}, 3000)
|
||
} catch (e: any) {
|
||
submitResult.value = {
|
||
success: false,
|
||
message: e.message || '提交失败,请稍后重试或联系IT管理员',
|
||
}
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
/** 返回状态页 */
|
||
function goBack() {
|
||
const sn = route.params.sn as string
|
||
router.replace(`/${sn}`)
|
||
}
|
||
|
||
/** 关闭页面(小鱼终端返回主页) */
|
||
function handleClose() {
|
||
closePage()
|
||
}
|
||
|
||
// ==========================================================================
|
||
// 生命周期
|
||
// ==========================================================================
|
||
|
||
onMounted(() => {
|
||
// 如果终端绑定信息未加载,尝试加载
|
||
if (!store.meetingroomInfo && terminalSn.value) {
|
||
store.loadBinding(terminalSn.value)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="w-full h-screen flex items-center justify-center bg-bg-primary p-4 md:p-8">
|
||
<div class="bg-bg-card rounded-2xl border border-border-subtle shadow-2xl w-full max-w-[900px] max-h-[92vh] overflow-y-auto animate-slide-up">
|
||
<!-- ================================================================ -->
|
||
<!-- 标题栏 -->
|
||
<!-- ================================================================ -->
|
||
<div class="flex items-center justify-between p-6 border-b border-border-subtle">
|
||
<div>
|
||
<h2 class="text-2xl font-bold text-text-primary">🔧 设备报修</h2>
|
||
<p class="text-sm text-text-secondary mt-1">
|
||
{{ meetingroomName }}
|
||
<span v-if="store.location"> · {{ store.location }}</span>
|
||
</p>
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<button
|
||
class="px-4 py-2 rounded-lg text-base text-text-secondary border border-border-subtle hover:text-text-primary transition-colors"
|
||
@click="handleClose"
|
||
>
|
||
关闭
|
||
</button>
|
||
<button
|
||
class="text-text-secondary hover:text-text-primary text-2xl"
|
||
@click="goBack"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================================================================ -->
|
||
<!-- 提交成功状态 -->
|
||
<!-- ================================================================ -->
|
||
<div v-if="submitResult?.success" class="p-12 text-center">
|
||
<div class="text-6xl mb-4">✅</div>
|
||
<h3 class="text-3xl font-bold text-status-free mb-3">报修已提交</h3>
|
||
<p class="text-lg text-text-secondary mb-2">
|
||
{{ submitResult.message }}
|
||
</p>
|
||
<p v-if="submitResult.repairId" class="text-base text-text-muted">
|
||
报修编号: #{{ submitResult.repairId }}
|
||
</p>
|
||
<p class="text-sm text-text-muted mt-4">
|
||
3 秒后自动返回...
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ================================================================ -->
|
||
<!-- 报修表单 -->
|
||
<!-- ================================================================ -->
|
||
<div v-else class="p-6 space-y-6">
|
||
<!-- 报修人信息 -->
|
||
<div class="flex items-center gap-4">
|
||
<span class="text-lg text-text-secondary min-w-[100px]">报修人</span>
|
||
<div class="px-4 py-2 bg-bg-input rounded-lg text-lg text-text-primary">
|
||
<span v-if="store.isLoggedIn">
|
||
{{ store.loginUser?.name || store.loginUser?.userid }}
|
||
</span>
|
||
<span v-else class="text-text-muted">匿名报修(终端提交)</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 设备类型选择 -->
|
||
<div>
|
||
<div class="flex items-center gap-4 mb-3">
|
||
<span class="text-lg text-text-secondary min-w-[100px]">设备类型</span>
|
||
<span class="text-sm text-text-muted">请选择故障设备</span>
|
||
</div>
|
||
<div class="grid grid-cols-2 md:grid-cols-3 gap-3 md:gap-4 ml-0 md:ml-[116px]">
|
||
<button
|
||
v-for="opt in deviceTypeOptions"
|
||
:key="opt.value"
|
||
class="flex items-center gap-3 px-5 py-4 rounded-xl border-2 transition-all"
|
||
:class="selectedDeviceType === opt.value
|
||
? 'border-status-free bg-status-free/10 text-text-primary'
|
||
: 'border-border-subtle bg-bg-input text-text-secondary hover:text-text-primary hover:border-border-subtle'"
|
||
@click="selectDeviceType(opt.value)"
|
||
>
|
||
<span class="text-3xl">{{ opt.icon }}</span>
|
||
<span class="text-lg font-medium">{{ opt.label }}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 故障描述 -->
|
||
<div>
|
||
<div class="flex items-center gap-4 mb-3">
|
||
<span class="text-lg text-text-secondary min-w-[100px]">故障描述</span>
|
||
<span class="text-sm text-text-muted">请描述故障现象</span>
|
||
</div>
|
||
<div class="ml-0 md:ml-[116px]">
|
||
<textarea
|
||
v-model="faultDescription"
|
||
placeholder="请输入故障描述,例如:投影仪无法开机,指示灯不亮..."
|
||
rows="4"
|
||
maxlength="500"
|
||
class="w-full px-4 py-3 bg-bg-input rounded-xl text-lg text-text-primary border border-border-subtle focus:border-status-free focus:outline-none resize-none"
|
||
/>
|
||
<div class="flex items-center justify-between mt-2">
|
||
<!-- 快捷选项 -->
|
||
<div class="flex gap-2 flex-wrap">
|
||
<button
|
||
v-for="quick in quickFaultOptions"
|
||
:key="quick"
|
||
class="px-3 py-1 text-sm bg-bg-input text-text-secondary rounded border border-border-subtle hover:text-text-primary hover:border-status-free transition-colors"
|
||
@click="quickFillFault(quick)"
|
||
>
|
||
{{ quick }}
|
||
</button>
|
||
</div>
|
||
<!-- 字数统计 -->
|
||
<span class="text-sm text-text-muted">
|
||
{{ faultDescription.length }} / 500
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 选中设备预览 -->
|
||
<div
|
||
v-if="selectedDeviceInfo"
|
||
class="flex items-center gap-3 p-4 bg-bg-input rounded-xl"
|
||
>
|
||
<span class="text-3xl">{{ selectedDeviceInfo.icon }}</span>
|
||
<div>
|
||
<div class="text-lg text-text-primary font-medium">
|
||
{{ selectedDeviceInfo.label }}
|
||
</div>
|
||
<div class="text-sm text-text-secondary">
|
||
将创建IT工单并通知管理员
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 提交失败提示 -->
|
||
<div
|
||
v-if="submitResult && !submitResult.success"
|
||
class="p-4 bg-status-busy/10 border border-status-busy/30 rounded-xl text-status-busy text-lg"
|
||
>
|
||
⚠ {{ submitResult.message }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================================================================ -->
|
||
<!-- 底部操作栏 -->
|
||
<!-- ================================================================ -->
|
||
<div
|
||
v-if="!submitResult?.success"
|
||
class="flex items-center justify-end gap-4 p-6 border-t border-border-subtle"
|
||
>
|
||
<button
|
||
class="px-8 py-3 rounded-xl text-button font-medium bg-bg-input text-text-secondary border border-border-subtle hover:text-text-primary transition-colors"
|
||
@click="goBack"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
class="px-10 py-3 rounded-xl text-button font-bold transition-all"
|
||
:class="[
|
||
canSubmit
|
||
? 'bg-status-free text-white hover:scale-105'
|
||
: 'bg-text-muted text-text-secondary cursor-not-allowed',
|
||
]"
|
||
:disabled="!canSubmit"
|
||
@click="handleSubmit"
|
||
>
|
||
{{ submitting ? '提交中...' : '提交报修' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|