| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197 |
- <template>
- <view class="content" :class="screenMode">
- <view class="split-layout" v-if="screenMode === 'heng'">
- <!-- 左侧联系人列表 -->
- <view class="left-panel">
- <view class="header">
- <view class="header-left">
- <image class="avatar" :src="studentAvatar"></image>
- <text class="greeting">{{ studentName }},你好!</text>
- </view>
- <view class="logout-btn" @click="killBackgroundApp">
- <image src="/static/icon/logout.png"></image>
- </view>
- </view>
- <view class="contact-list">
- <view class="contact-card" v-for="contact in contacts" :key="contact.id"
- :class="{ 'active': currentContact?.id === contact.id }">
- <view class="contact-info">
- <image class="contact-avatar" :src="contact.avatar"></image>
- <text class="contact-name">{{ contact.username }}</text>
- </view>
- <view class="action-buttons">
- <view class="action-btn" @click.stop="startAudioCall(contact)">
- <view class="action-btn-icon">
- <image src="/static/icon/audio.png"></image>
- </view>
- <text>音频</text>
- </view>
- <view class="action-btn" @click.stop="startVideoCall(contact)">
- <view class="action-btn-icon">
- <image src="/static/icon/video.png"></image>
- </view>
- <text>视频</text>
- </view>
- <view class="action-btn" :class="{ 'message-point': contact.hasPoints }"
- @click="selectContact(contact)">
- <view class="action-btn-icon">
- <image src="/static/icon/message.png"></image>
- </view>
- <text>留言</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 个人服务状态 -->
- <view class="service-status" v-if="serviceStatus">
- <view class="service-status-header">
- <text class="service-status-title">服务状态</text>
- </view>
- <view class="service-status-content">
- <view class="status-item" v-if="serviceStatus.sfmf === 1">
- <text class="status-label">免费服务</text>
- </view>
- <view class="status-item" v-if="serviceStatus.zdsc > 0">
- <text class="status-label">时长</text>
- <text class="status-value">{{ serviceStatus.ljsc || 0 }}/{{ serviceStatus.zdsc }}分钟</text>
- </view>
- <view class="status-item" v-if="serviceStatus.zdcs > 0">
- <text class="status-label">次数</text>
- <text class="status-value">{{ serviceStatus.ljcs || 0 }}/{{ serviceStatus.zdcs }}次</text>
- </view>
- <view class="status-item" v-if="serviceStatus.zdll > 0">
- <text class="status-label">流量</text>
- <text class="status-value">{{ serviceStatus.ljll || 0 }}/{{ serviceStatus.zdll }}MB</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 右侧消息面板 -->
- <view class="right-panel">
- <message-page v-if="currentContact" :contact-id="currentContact.id" role="device"></message-page>
- <!-- <view v-else class="empty-message">
- <text>请选择联系人开始聊天</text>
- </view> -->
- </view>
- </view>
- <view class="split-layout" v-else>
- <!-- 头部欢迎语 -->
- <view class="header">
- <view class="header-left">
- <image class="avatar" :src="studentAvatar"></image>
- <text class="greeting">{{ studentName }},你好!</text>
- </view>
- <view class="logout-btn" @click="killBackgroundApp">
- <image src="/static/icon/logout.png"></image>
- </view>
- </view>
- <!-- 调试信息 -->
- <!-- <view class="debug-info" @click="startCall">
- <text>Options1: {{ JSON.stringify(options) }}</text>
- </view> -->
- <!-- 联系人卡片列表 -->
- <view class="contact-list">
- <view class="contact-card" v-for="contact in contacts" :key="contact.id">
- <view class="contact-info">
- <image class="contact-avatar" :src="contact.avatar"></image>
- <text class="contact-name">{{ contact.username }}</text>
- </view>
- <view class="action-buttons">
- <view class="action-btn" @click="startAudioCall(contact)">
- <view class="action-btn-icon">
- <image src="/static/icon/audio.png"></image>
- </view>
- <text>音频</text>
- </view>
- <view class="action-btn" @click="startVideoCall(contact)">
- <view class="action-btn-icon">
- <image src="/static/icon/video.png"></image>
- </view>
- <text>视频</text>
- </view>
- <view class="action-btn" :class="{ 'message-point': contact.hasPoints }"
- @click="goToMessage(contact.id)">
- <view class="action-btn-icon">
- <image src="/static/icon/message.png"></image>
- </view>
- <text>留言</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 个人服务状态 -->
- <view class="service-status" v-if="serviceStatus">
- <view class="service-status-header">
- <text class="service-status-title">服务状态</text>
- </view>
- <view class="service-status-content">
- <view class="status-item" v-if="serviceStatus.sfmf === 1">
- <text class="status-label">免费服务</text>
- </view>
- <view class="status-item" v-if="serviceStatus.zdsc > 0">
- <text class="status-label">时长</text>
- <text class="status-value">{{ serviceStatus.ljsc || 0 }}/{{ serviceStatus.zdsc }}分钟</text>
- </view>
- <view class="status-item" v-if="serviceStatus.zdcs > 0">
- <text class="status-label">次数</text>
- <text class="status-value">{{ serviceStatus.ljcs || 0 }}/{{ serviceStatus.zdcs }}次</text>
- </view>
- <view class="status-item" v-if="serviceStatus.zdll > 0">
- <text class="status-label">流量</text>
- <text class="status-value">{{ serviceStatus.ljll || 0 }}/{{ serviceStatus.zdll }}MB</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 自定义Modal -->
- <custom-modal
- :visible="modalVisible"
- :title="modalTitle"
- :content="modalContent"
- :showCancel="modalShowCancel"
- :confirmText="modalConfirmText"
- @confirm="handleModalConfirm"
- @cancel="handleModalCancel"
- />
- </view>
- </template>
- <script>
- import messagePage from '../parent/message.vue'
- import customModal from '@/components/custom-modal.vue'
- import { deviceApi } from '@/api/device'
- import { getImageUrl } from '@/utils/util'
- const wmpfVoip = requirePlugin('wmpf-voip').default
- const isWmpf = (typeof wmpf !== 'undefined')
- // 指定接听方使用的小程序版本。formal/正式版(默认);trial/体验版;developer/开发版
- const miniprogramState = (() => {
- const accountInfo = wx.getAccountInfoSync();
- if (accountInfo && accountInfo.miniProgram) {
- const platform = { develop: 'developer', trial: 'trial', release: 'formal' }
- return platform[accountInfo.miniProgram.envVersion]
- }
- })()
- export default {
- components: {
- messagePage,
- customModal
- },
- data() {
- return {
- studentAvatar: 'https://thirdwx.qlogo.cn/mmopen/vi_32/POgEwh4mIHO4nibH0KlMECNjjGxQUq24ZEaGT4poC6icRiccVGKSyXwibcPq4BWmiaIGuG1icwxaQX6grC9VemZoJ8rg/132',
- studentName: '默认学生',
- title: '家庭联系',
- screenMode: 'heng',
- currentContact: null,
- contacts: [],
- serviceStatus: null, // 个人服务状态
- _voipEndPathSet: false, // 记录是否已设置通话结束跳转页面
- studentId: '1',
- sn: 'A1000006B624470',
- inactivityTimer: null,
- inactivityTimeout: 180000, // 3分钟
- options: null,
- // 自定义Modal相关
- modalVisible: false,
- modalTitle: '提示',
- modalContent: '',
- modalShowCancel: false,
- modalConfirmText: '确定',
- modalResolve: null, // 用于Promise
- }
- },
- async onLoad(options) {
- // console.log('Device onLoad', options)
- this.options = options
- // 🔧 关闭可能存在的旧Modal
- this.modalVisible = false
- if (this.modalResolve) {
- this.modalResolve(false) // 取消旧的Promise
- this.modalResolve = null
- }
- uni.getSystemInfo({
- success: (res) => {
- this.screenMode = res.windowWidth > res.windowHeight ? 'heng' : 'shu'
- }
- })
- // 🎯 检查是否从通话结束返回
- if (options && options.fromCall === 'true') {
- console.log('📞 检测到通话结束返回,准备记录通话')
- await this.recordCallAndRefresh(options)
- return // 处理完通话记录后直接返回
- }
- // 🎯 检查是否已有登录信息(从通话结束页面返回的情况)
- const userInfo = uni.getStorageSync('userInfo')
- if (userInfo && userInfo.devId && (!options || !options.sn)) {
- // 已登录且没有传递新的 sn,说明是从其他页面返回
- console.log('✅ 检测到已登录,使用缓存数据')
- // 恢复学生信息
- if (userInfo.xm) {
- this.studentName = userInfo.xm
- }
- if (userInfo.yszwj) {
- this.studentAvatar = getImageUrl(userInfo.yszwj)
- }
- // 恢复 sn
- this.sn = userInfo.devId
- // 获取联系人列表
- await this.getContactList()
- // 检查个人服务状态
- await this.checkServiceStatus(115)
- // 注册通话事件
- this.registerVoipEvent()
- this.setVoipEndPagePath()
- // 初始化无操作检测
- this.resetInactivityTimer()
-
- // 添加全局点击事件监听
- const pages = getCurrentPages();
- const currentPage = pages[pages.length - 1];
- if (currentPage.$getAppWebview) {
- const webview = currentPage.$getAppWebview();
- webview.addEventListener('click', () => {
- this.handleUserActivity();
- });
- }
- return // 直接返回,不需要重新登录
- }
- // 🆕 新登录流程:从 options 获取 sn 和 cardNo
- if (options && options.sn) {
- this.sn = options.sn
- }
- const cardNo = options && options.cardNo ? options.cardNo : ''
- // 可选的 studentId 参数
- if (options && options.studentId) {
- this.studentId = options.studentId
- }
- // 第一步: 设备登录
- const loginSuccess = await this.deviceLogin(this.sn, cardNo)
- // 登录成功后才继续后续操作
- if (!loginSuccess) {
- return // 登录失败,直接返回
- }
- this.registerVoipEvent()
- // 初始化无操作检测
- this.resetInactivityTimer()
- // 添加全局点击事件监听
- const pages = getCurrentPages();
- const currentPage = pages[pages.length - 1];
- if (currentPage.$getAppWebview) {
- const webview = currentPage.$getAppWebview();
- webview.addEventListener('click', () => {
- this.handleUserActivity();
- });
- }
- },
- onUnload() {
- // 清理定时器
- if (this.inactivityTimer) {
- clearTimeout(this.inactivityTimer);
- }
- },
- methods: {
- // 显示自定义Modal
- showCustomModal(options = {}) {
- return new Promise((resolve) => {
- this.modalTitle = options.title || '提示'
- this.modalContent = options.content || ''
- this.modalShowCancel = options.showCancel || false
- this.modalConfirmText = options.confirmText || '确定'
- this.modalResolve = resolve
- this.modalVisible = true
- })
- },
- // 处理Modal确认
- handleModalConfirm() {
- this.modalVisible = false
- if (this.modalResolve) {
- this.modalResolve(true)
- this.modalResolve = null
- }
- },
- // 处理Modal取消
- handleModalCancel() {
- this.modalVisible = false
- if (this.modalResolve) {
- this.modalResolve(false)
- this.modalResolve = null
- }
- },
- // 设备登录
- async deviceLogin(sn, cardNo) {
- try {
- const result = await deviceApi.login(sn, cardNo)
- if (result && result.data) {
- const loginData = result.data
- // 🚫 检查登录失败的情况(废卡/登录过期等)
- if (loginData.msg) {
- // 有 msg 字段说明登录失败
- // 弹出自定义确认框
- const confirmed = await this.showCustomModal({
- title: '登录失败',
- content: '登录失败,卡无登记',
- showCancel: false,
- confirmText: '确定'
- })
- // 用户确认后,调用退出
- if (confirmed) {
- await this.killBackgroundApp()
- }
- return false
- }
- // 🎯 参考 H5 login.html 的 userData 构建方式(304-314行)
- // 后端返回的数据没有 userInfo 层级,需要手动构建
- const userData = {
- devId: loginData.devId,
- sbmc: loginData.sbmc,
- sessId: loginData.sessId,
- xm: loginData.xm,
- yhsbToken: loginData.yhsbToken,
- onlineToken: loginData.onlineToken,
- syList: loginData.sylist || loginData.syList,
- yhid: loginData.yhid,
- yhm: loginData.yhm
- }
- // 可选字段:如果存在则添加
- if (loginData.yszwj) {
- userData.yszwj = loginData.yszwj
- }
- if (loginData.zjzwj) {
- userData.zjzwj = loginData.zjzwj
- }
- // 保存完整的用户信息
- uni.setStorageSync('userInfo', userData)
- // 保存 JSESSIONID
- if (userData.sessId) {
- uni.setStorageSync('JSESSIONID', userData.sessId)
- } else {
- console.warn('⚠️ 登录数据中没有 sessId')
- }
- // 更新页面显示的学生信息
- if (userData.xm) {
- this.studentName = userData.xm
- }
- if (userData.yszwj){
- this.studentAvatar = getImageUrl(userData.yszwj)
- }
- // 如果登录返回了学生ID,更新当前学生ID
- if (loginData.studentId) {
- this.studentId = loginData.studentId
- }
- // ✅ 登录成功,获取联系人列表
- await this.getContactList()
- // 检查个人服务状态
- await this.checkServiceStatus(115)
- console.log('✅ 设备登录成功:', userData.xm)
- return true
- } else {
- throw new Error('登录返回数据格式错误')
- }
- } catch (error) {
- console.error('❌ 设备登录失败:', error)
- // 弹出自定义确认框
- await this.showCustomModal({
- title: '登录失败',
- content: '网络错误或服务异常',
- showCancel: false,
- confirmText: '确定'
- })
- // 用户确认后,调用退出
- await this.killBackgroundApp()
- return false
- }
- },
- // 通用通话方法,供音频和视频通话共用
- async startCall(contact, roomType) {
- // 显示loading弹窗
- uni.showLoading({
- title: '呼叫中...',
- mask: true // 添加遮罩,防止触摸穿透
- });
- // 记录当前联系人
- this.currentContact = contact;
- // 清除可能的上次通话记录,确保本次通话信息独立
- if (uni.getStorageSync('currcall')) {
- uni.removeStorageSync('currcall');
- }
- // 先存储基本联系人信息
- uni.setStorageSync('lastCallInfo', {
- name: contact.username || '未知联系人',
- avatar: contact.avatar || '/static/logo.png',
- duration: 0,
- time: new Date().toLocaleString(),
- type: roomType,
- });
- this.setVoipEndPagePath()
- try {
- const res = await wmpfVoip.initByCaller({
- roomType: roomType,
- caller: {
- id: this.sn,
- },
- listener: {
- id: contact.openid,
- name: contact.username
- },
- businessType: 1,
- miniprogramState: miniprogramState
- })
- console.log("res:",res)
- if (res.isSuccess) {
- uni.hideLoading();
- uni.redirectTo({
- url: wmpfVoip.CALL_PAGE_PATH
- })
- return true
- } else {
- uni.hideLoading();
- console.error("呼叫失败,res:",res)
- uni.showToast({
- title: '呼叫失败1',
- icon: 'error'
- })
-
- return false
- }
- } catch (error) {
- uni.hideLoading();
- console.error('通话错误:', error)
- uni.showToast({
- title: '发起通话失败',
- icon: 'none'
- })
- return false
- }
- },
- // 设置通话结束跳转地址
- setVoipEndPagePath(forceUpdate) {
- if (this._voipEndPathSet && !forceUpdate) return;
- if (wmpfVoip) {
- const callInfo = uni.getStorageSync('lastCallInfo') || {};
- // 构建参数:如果需要记录通话,传递参数给首页
- let options = '';
- if (callInfo.needRecord && callInfo.duration > 0) {
- options = `fromCall=true&duration=${callInfo.duration}&callType=${callInfo.type || 'voice'}`;
- console.log('🔗 设置通话结束跳转参数:', options);
- }
- wmpfVoip.setVoipEndPagePath({
- url: '/pages/device/index', // 通话结束直接返回首页
- key: 'Call', // 设置业务类型为通话
- options: options, // 传递通话数据
- routeType: 'redirectTo' // 使用redirectTo方式跳转
- });
- this._voipEndPathSet = true; // 记录已设置状态
- }
- },
- registerVoipEvent() {
- wmpfVoip.onVoipEvent((event) => {
- const eventName = event.eventName;
- // 开始通话时清除无操作定时器
- if (eventName === 'startVoip') {
- clearTimeout(this.inactivityTimer);
- }
- // 定义不同类型的结束事件
- const hangupEvent = ['hangUpVoip', 'endVoip']; // 正常挂断
- const cancelEvent = ['cancelVoip']; // 主动取消
- const timeoutEvent = ['timeout']; // 超时未接听
- const rejectEvent = ['rejectVoip']; // 拒绝接听
- const callInfo = uni.getStorageSync('lastCallInfo') || {};
- // 处理正常通话结束
- if (hangupEvent.includes(eventName)) {
- callInfo.duration = event.data?.keepTime || 0;
- callInfo.status = callInfo.duration > 0 ? '通话已结束' : '未接通';
- // 如果有通话时长,标记需要记录到后端
- if (callInfo.duration > 0) {
- callInfo.needRecord = true;
- console.log('✅ 通话结束,时长:', callInfo.duration, '秒,类型:', callInfo.type);
- }
- }
- // 处理取消通话
- else if (cancelEvent.includes(eventName)) {
- callInfo.duration = 0;
- callInfo.status = '已取消';
- }
- // 处理超时未接听
- else if (timeoutEvent.includes(eventName)) {
- callInfo.duration = 0;
- callInfo.status = '未接听';
- }
- // 处理拒绝接听
- else if (rejectEvent.includes(eventName)) {
- callInfo.duration = 0;
- callInfo.status = '已拒绝';
- }
- // 如果是任何一种结束事件,保存信息并更新跳转页面
- if ([...hangupEvent, ...cancelEvent, ...timeoutEvent, ...rejectEvent].includes(eventName)) {
- callInfo.endType = eventName;
- callInfo.endTime = Date.now();
- uni.hideLoading();
- uni.setStorageSync('lastCallInfo', callInfo);
- this.setVoipEndPagePath(true);
- this.resetInactivityTimer();
- }
- })
- },
- async startAudioCall(contact) {
- await this.startCall(contact, 'voice')
- },
- async startVideoCall(contact) {
- await this.startCall(contact, 'video')
- },
- // 杀后台方法
- async killBackgroundApp() {
- try {
- // 🧹 清理登录信息
- uni.removeStorageSync('userInfo')
- uni.removeStorageSync('JSESSIONID')
- // 🧹 清理通话相关缓存
- uni.removeStorageSync('currcall')
- uni.removeStorageSync('lastCallInfo')
- // 判断是否在真机环境
- if (typeof wmpf !== 'undefined') {
- await wmpf.Channel.invoke({
- command: 'killBackgroundApp',
- data: {
- sn: this.sn
- },
- success: (res) => {
- uni.showToast({
- title: '已退出',
- icon: 'success'
- });
- }
- });
- } else {
- // 开发环境也要清理缓存
- uni.showToast({
- title: '已退出(开发环境)',
- icon: 'success'
- });
- }
- } catch (error) {
- console.error('❌ 退出失败:', error);
- uni.showToast({
- title: '退出失败',
- icon: 'error'
- });
- }
- },
- // 重置无操作计时器
- resetInactivityTimer() {
- if (this.inactivityTimer) {
- clearTimeout(this.inactivityTimer);
- }
- this.inactivityTimer = setTimeout(() => {
- this.killBackgroundApp();
- }, this.inactivityTimeout);
- },
- // 监听用户活动的方法
- handleUserActivity() {
- this.resetInactivityTimer();
- },
- selectContact(contact) {
- this.currentContact = contact
- },
- async getContactList() {
- try {
- // 不需要传参数,device-request.js 会自动从 userInfo 获取 devId 和 sbmc
- const result = await deviceApi.selParentInfo()
- if (result && result.data && result.data.ssData) {
- const parent = result.data.ssData
- // 处理头像:如果有 yszwj 就用 getImageUrl 转换,否则用默认头像
- const defaultAvatar = 'https://thirdwx.qlogo.cn/mmopen/vi_32/POgEwh4mIHO4nibH0KlMECNjjGxQUq24ZEaGT4poC6icRiccVGKSyXwibcPq4BWmiaIGuG1icwxaQX6grC9VemZoJ8rg/132'
- const avatar = parent.yszwj ? getImageUrl(parent.yszwj) : defaultAvatar
- // 现在只有一个家长,直接赋值为数组(兼容现有页面结构)
- this.contacts = [{
- id: parent.ryid, // 人员ID
- username: parent.xm, // 姓名
- avatar: avatar, // 艺术照(头像)
- openid: parent.wbid, // 外部ID(微信openid)
- hasPoints: false // 是否有未读留言(默认false)
- }]
- console.log('✅ 获取联系人成功:', parent)
- }
- } catch (error) {
- console.error('❌ 获取联系人失败:', error)
- }
- },
- goToMessage(contactId) {
- uni.navigateTo({
- url: '/pages/parent/message?contactId=' + contactId + "&role=device"
- })
- },
- // 检查个人服务状态
- async checkServiceStatus(grfwxmm = 115) {
- try {
- const result = await deviceApi.grfw_chkGrfw(grfwxmm)
- console.log('检查服务状态返回:', result)
- if (result && result.data) {
- const data = result.data
- if (data.ssCode === 0 && data.ssData) {
- // 成功获取服务状态
- this.serviceStatus = data.ssData
- console.log('✅ 服务状态:', this.serviceStatus)
- } else if (data.ssCode > 0) {
- // 服务异常
- console.warn('⚠️ 服务状态异常:', data.ssMsg)
- this.serviceStatus = null
- }
- }
- } catch (error) {
- console.error('❌ 检查服务状态失败:', error)
- this.serviceStatus = null
- }
- },
- // 记录通话并刷新服务状态
- async recordCallAndRefresh(callOptions) {
- try {
- const duration = parseInt(callOptions.duration) || 0 // 秒
- const callType = callOptions.callType || 'voice'
- if (duration <= 0) {
- console.log('⚠️ 通话时长为0,不记录')
- await this.loadPageAfterCall()
- return
- }
- // 转换为分钟,不满1分钟按1分钟算(向上取整)
- const minutes = Math.ceil(duration / 60)
- console.log(`📊 通话时长: ${duration}秒 → ${minutes}分钟(向上取整)`)
- // 根据通话类型确定服务项目码
- const grfwxmm = callType === 'video' ? 115 : 111 // 115=视频电话, 111=音频电话
- // 调用接口记录通话
- console.log(`📝 记录通话: grfwxmm=${grfwxmm}, 时长=${minutes}分钟`)
- await deviceApi.grfw_endGrfw({
- grfwxmm: grfwxmm,
- sc: minutes, // 时长(分钟)
- ll: 0, // 流量
- ms: `通话${duration}秒` // 备注
- })
- console.log('✅ 通话记录成功')
- // 重新加载服务状态
- await this.checkServiceStatus(grfwxmm)
- // 清理通话记录标记
- const callInfo = uni.getStorageSync('lastCallInfo') || {}
- delete callInfo.needRecord
- uni.setStorageSync('lastCallInfo', callInfo)
- } catch (error) {
- console.error('❌ 记录通话失败:', error)
- uni.showToast({
- title: '记录通话失败',
- icon: 'none'
- })
- } finally {
- await this.loadPageAfterCall()
- }
- },
- // 通话结束后加载页面内容
- async loadPageAfterCall() {
- const userInfo = uni.getStorageSync('userInfo')
- if (userInfo) {
- if (userInfo.xm) {
- this.studentName = userInfo.xm
- }
- if (userInfo.yszwj) {
- this.studentAvatar = getImageUrl(userInfo.yszwj)
- }
- this.sn = userInfo.devId
- }
- await this.getContactList()
- this.registerVoipEvent()
- this.setVoipEndPagePath()
- this.resetInactivityTimer()
- const pages = getCurrentPages();
- const currentPage = pages[pages.length - 1];
- if (currentPage.$getAppWebview) {
- const webview = currentPage.$getAppWebview();
- webview.addEventListener('click', () => {
- this.handleUserActivity();
- });
- }
- },
- }
- }
- </script>
- <style scoped>
- .heng .split-layout {
- display: flex;
- height: 100vh;
- }
- .heng .left-panel {
- width: 50%;
- border-right: 2rpx solid #dcdcdc;
- background: #f5f5f5;
- overflow-y: auto;
- }
- .heng .right-panel {
- flex: 1;
- background: #fff;
- height: 100%;
- overflow-y: auto;
- }
- .heng .empty-message {
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #999;
- font-size: 32rpx;
- }
- .heng .header {
- display: flex;
- align-items: center;
- padding: 16rpx 20rpx 10rpx;
- /* height: 200rpx; */
- border-bottom: 1rpx solid #d2d2d2;
- background: #f5f5f5;
- box-sizing: border-box;
- justify-content: space-between;
- }
- .heng .avatar {
- width: 42rpx;
- height: 42rpx;
- border-radius: 50%;
- margin-right: 8rpx;
- }
- .heng .header-left {
- display: flex;
- align-items: center;
- }
- .heng .logout-btn image {
- width: 20rpx;
- height: 20rpx;
- }
- .heng .greeting {
- font-size: 14rpx;
- color: #333;
- }
- .heng .contact-list {
- margin-top: 20rpx;
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 15rpx;
- padding: 0 12rpx;
- }
- .heng .contact-card {
- width: calc(100% - 20rpx);
- /* margin: 16rpx 20rpx; */
- padding: 8rpx;
- background-color: #fff;
- border-radius: 3rpx;
- display: flex;
- align-items: center;
- background: #f5f5f5;
- border: 1px solid #dddfe6;
- justify-content: space-between;
- position: relative;
- /* 添加相对定位 */
- }
- .heng .contact-card.active::after {
- content: '';
- position: absolute;
- right: -13rpx;
- top: 50%;
- transform: translateY(-50%);
- width: 0;
- height: 0;
- border-top: 8rpx solid transparent;
- border-bottom: 8rpx solid transparent;
- border-left: 8rpx solid #d2d2d2;
- }
- .heng .contact-info {
- display: flex;
- align-items: center;
- }
- .heng .contact-avatar {
- width: 52rpx;
- height: 52rpx;
- border: 1px solid #fff;
- margin-right: 8rpx;
- border-radius: 0rpx;
- }
- .heng .contact-name {
- font-size: 16rpx;
- color: #333;
- }
- .heng .action-buttons {
- display: flex;
- justify-content: space-around;
- gap: 12rpx;
- margin-right: 8rpx;
- }
- .heng .action-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .heng .action-btn-icon {
- width: 32rpx;
- height: 32rpx;
- margin-bottom: 2rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #ffffff;
- border: 1px solid #dddfe6;
- box-sizing: border-box;
- border-radius: 3rpx;
- }
- .heng .action-btn image {
- width: 60%;
- height: 60%;
- }
- .heng .action-btn text {
- font-size: 10rpx;
- color: #000000;
- }
- .heng .message-point {
- position: relative;
- }
- .heng .message-point::after {
- content: '';
- position: absolute;
- width: 8rpx;
- height: 8rpx;
- background-color: #eb6100;
- border-radius: 50%;
- top: -4rpx;
- right: -4rpx;
- }
- .shu .header {
- display: flex;
- align-items: center;
- padding: 40rpx 50rpx 25rpx;
- height: 200rpx;
- border-bottom: 2rpx solid #d2d2d2;
- background: #f5f5f5;
- box-sizing: border-box;
- justify-content: space-between;
- }
- .shu .avatar {
- width: 108rpx;
- height: 108rpx;
- border-radius: 50%;
- margin-right: 20rpx;
- }
- .shu .header-left {
- display: flex;
- align-items: center;
- }
- .shu .logout-btn image {
- width: 40rpx;
- height: 40rpx;
- }
- .shu .greeting {
- font-size: 38rpx;
- color: #333;
- }
- .shu .contact-list {
- margin-top: 30rpx;
- }
- .shu .contact-card {
- margin: 40rpx 50rpx;
- padding: 20rpx;
- background-color: #fff;
- border-radius: 8rpx;
- display: flex;
- align-items: center;
- background: #f5f5f5;
- border: 1px solid #dddfe6;
- justify-content: space-between;
- }
- .shu .contact-info {
- display: flex;
- align-items: center;
- }
- .shu .contact-avatar {
- width: 128rpx;
- height: 128rpx;
- border: 1px solid #fff;
- margin-right: 20rpx;
- border-radius: 0rpx;
- }
- .shu .contact-name {
- font-size: 32rpx;
- color: #333;
- }
- .shu .action-buttons {
- display: flex;
- justify-content: space-around;
- gap: 30rpx;
- /* margin-right: 20rpx; */
- }
- .shu .action-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .shu .action-btn-icon {
- width: 80rpx;
- height: 80rpx;
- margin-bottom: 5rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #ffffff;
- border: 1px solid #dddfe6;
- box-sizing: border-box;
- border-radius: 10rpx;
- }
- .shu .action-btn image {
- width: 60%;
- height: 60%;
- }
- .shu .action-btn text {
- font-size: 24rpx;
- color: #000000;
- }
- .shu .message-point {
- position: relative;
- }
- .shu .message-point::after {
- content: '';
- position: absolute;
- width: 18rpx;
- height: 18rpx;
- background-color: #eb6100;
- border-radius: 50%;
- top: -9rpx;
- right: -9rpx;
- }
- .debug-info {
- padding: 20rpx;
- background: #fff3cd;
- margin: 20rpx;
- border-radius: 8rpx;
- word-break: break-all;
- }
- .debug-info text {
- font-size: 24rpx;
- color: #856404;
- }
- /* 横屏服务状态样式 */
- .heng .service-status {
- margin: 12rpx;
- padding: 10rpx 12rpx;
- background: #fff;
- border-radius: 6rpx;
- border: 1px solid #dddfe6;
- }
- .heng .service-status-header {
- margin-bottom: 8rpx;
- }
- .heng .service-status-title {
- font-size: 12rpx;
- color: #666;
- font-weight: bold;
- }
- .heng .service-status-content {
- display: flex;
- flex-wrap: wrap;
- gap: 8rpx;
- }
- .heng .status-item {
- display: flex;
- align-items: center;
- gap: 4rpx;
- padding: 4rpx 8rpx;
- background: #f0f9ff;
- border-radius: 4rpx;
- }
- .heng .status-label {
- font-size: 10rpx;
- color: #666;
- }
- .heng .status-value {
- font-size: 10rpx;
- color: #07c160;
- font-weight: bold;
- }
- /* 竖屏服务状态样式 */
- .shu .service-status {
- margin: 30rpx 50rpx;
- padding: 24rpx;
- background: #fff;
- border-radius: 12rpx;
- border: 1px solid #dddfe6;
- }
- .shu .service-status-header {
- margin-bottom: 16rpx;
- }
- .shu .service-status-title {
- font-size: 28rpx;
- color: #666;
- font-weight: bold;
- }
- .shu .service-status-content {
- display: flex;
- flex-wrap: wrap;
- gap: 20rpx;
- }
- .shu .status-item {
- display: flex;
- align-items: center;
- gap: 10rpx;
- padding: 12rpx 20rpx;
- background: #f0f9ff;
- border-radius: 8rpx;
- }
- .shu .status-label {
- font-size: 26rpx;
- color: #666;
- }
- .shu .status-value {
- font-size: 26rpx;
- color: #07c160;
- font-weight: bold;
- }
- </style>
|