zgTel.vue 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230
  1. <template>
  2. <div class="phoneControls">
  3. <el-popover :width="240" trigger="hover" v-model:visible="showPop">
  4. <template #reference>
  5. <div class="mr20 status-box">
  6. <span class="color-primary">{{ currentStatusText }}</span>
  7. <el-text tag="b" v-if="['301', '303'].includes(m_strTelState)" type="danger">{{ formatDuration(talkTime) }}</el-text>
  8. <el-text tag="b" v-else-if="m_strTelState === '200'">{{ formatDuration(idleTime) }}</el-text>
  9. <el-text tag="b" v-else-if="m_strTelState === '201'">{{ formatDuration(busyTime) }}</el-text>
  10. <el-text tag="b" v-else-if="m_strTelState === '320'">{{ formatDuration(conferenceTime) }}</el-text>
  11. <el-text tag="b" v-else-if="m_strTelState === '900'">{{ formatDuration(arrangeTime) }}</el-text>
  12. <SvgIcon name="ele-CaretBottom" class="arrow" :class="showPop ? 'is-reverse' : ''" />
  13. </div>
  14. </template>
  15. <template #default>
  16. <template v-if="m_bLogin">
  17. <div class="flex-center-between" v-if="m_strUserNo">
  18. <span class="ml10">分机号</span>
  19. <el-text tag="b" type="danger">{{ m_strUserNo }}</el-text>
  20. </div>
  21. <div class="flex-center-between mt10" v-if="signTime">
  22. <span class="ml10">签入时长</span>
  23. <el-text tag="b" type="primary">{{ formatDuration(signTime) }}</el-text>
  24. </div>
  25. <div class="flex-center-between mt10" v-if="talkTime && ['301', '303', '310'].includes(m_strTelState)">
  26. <span class="ml10">通话时长</span>
  27. <el-text tag="b" type="danger">{{ formatDuration(talkTime) }}</el-text>
  28. </div>
  29. <div class="flex-center-between mt10" v-if="idleTime && ['0'].includes(m_strTelState)">
  30. <span class="ml10">空闲时长</span>
  31. <el-text tag="b">{{ formatDuration(idleTime) }}</el-text>
  32. </div>
  33. <div class="flex-center-between mt10" v-if="busyTime && ['201'].includes(m_strTelState)">
  34. <span class="ml10">示忙时长</span>
  35. <el-text tag="b">{{ formatDuration(busyTime) }}</el-text>
  36. </div>
  37. </template>
  38. <template v-else> <span class="color-info flex flex-center-center">请签入</span> </template>
  39. </template>
  40. </el-popover>
  41. <div class="btn-container">
  42. <!-- 签入 -->
  43. <template v-if="m_bLogin">
  44. <!-- 签出可用 -->
  45. <div class="item active" @click="onEvent('signOut')" v-if="activeArr.includes('dutyOff')" title="签出">
  46. <SvgIcon name="iconfont icon-dutyOff" class="icon mr3" size="18px" />签出
  47. </div>
  48. <!-- 签出不可用 -->
  49. <div class="item disabled" v-else title="签出"><SvgIcon name="iconfont icon-dutyOff" class="icon mr3" size="18px" />签出</div>
  50. </template>
  51. <!-- 灰色签入不可用 -->
  52. <template v-else>
  53. <div class="item active" @click="onEvent('signIn')" title="签入">
  54. <SvgIcon name="iconfont icon-dutyOn" class="icon mr3" size="18px" />签入
  55. </div>
  56. </template>
  57. <!-- 呼叫 可用-->
  58. <template v-if="m_bLogin && activeArr.includes('outbound')">
  59. <div class="item active" title="呼叫" @click="onEvent('callOut')">
  60. <SvgIcon name="iconfont icon-outbound" class="icon mr3" size="18px" />
  61. <span>呼叫</span>
  62. </div>
  63. </template>
  64. <!-- 呼叫 不可用 -->
  65. <template v-else>
  66. <div class="item disabled" title="呼叫">
  67. <SvgIcon name="iconfont icon-outbound" class="icon mr3" size="18px" />
  68. <span>呼叫</span>
  69. </div>
  70. </template>
  71. <!-- 可用挂断 -->
  72. <template v-if="m_bLogin && activeArr.includes('hangup')">
  73. <div class="item active" @click="onEvent('hangup')" title="挂断">
  74. <SvgIcon name="iconfont icon-hangup" class="icon mr3" size="16px" />挂断
  75. </div>
  76. </template>
  77. <!-- 灰色挂断 不可用 -->
  78. <template v-else>
  79. <div class="item disabled" title="挂断"><SvgIcon name="iconfont icon-hangup" class="icon mr3" size="16px" />挂断</div>
  80. </template>
  81. <!-- 小休和结束休息 可用 -->
  82. <template v-if="m_bLogin && activeArr.includes('rest')">
  83. <div class="item active" :title="m_bTelBusy ? '示闲' : '示忙'" @click="onEvent(m_bTelBusy ? 'idle' : 'busy')">
  84. <SvgIcon name="iconfont icon-rest" class="icon mr3" size="16px" />
  85. {{ m_bTelBusy ? '示闲' : '示忙' }}
  86. </div>
  87. </template>
  88. <!-- 灰色小休不可用 -->
  89. <template v-else>
  90. <div class="item disabled" title="示忙"><SvgIcon name="iconfont icon-rest" class="icon mr3" size="18px" />示忙</div>
  91. </template>
  92. <!-- 保持和取消保持 可用 -->
  93. <template v-if="m_bLogin && activeArr.includes('hold')">
  94. <div class="item active" :title="m_IsHold ? '恢复' : '保持'" @click="onEvent(m_IsHold ? 'reHold' : 'hold')">
  95. <SvgIcon name="iconfont icon-hold" class="icon mr3" size="16px" />
  96. {{ m_IsHold ? '恢复' : '保持' }}
  97. </div>
  98. </template>
  99. <!-- 灰色保持不可用 -->
  100. <template v-else>
  101. <div class="item disabled" title="保持"><SvgIcon name="iconfont icon-hold" class="icon mr3" size="16px" />保持</div>
  102. </template>
  103. <!-- 咨询 可用 -->
  104. <template v-if="m_bLogin && activeArr.includes('consult')">
  105. <div class="item active" title="咨询" @click="onEvent('consult')">
  106. <SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />
  107. 咨询
  108. </div>
  109. </template>
  110. <!-- 灰色咨询不可用 -->
  111. <template v-else>
  112. <div class="item disabled" title="咨询"><SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />咨询</div>
  113. </template>
  114. <!-- 盲转可用 -->
  115. <template v-if="m_bLogin && activeArr.includes('transferMz')">
  116. <div class="item active" title="盲转" @click="onEvent('transferMz')">
  117. <SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />
  118. 盲转
  119. </div>
  120. </template>
  121. <!-- 灰色盲转不可用 -->
  122. <template v-else>
  123. <div class="item disabled" title="盲转"><SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />盲转</div>
  124. </template>
  125. <!-- 转接可用 -->
  126. <template v-if="m_bLogin && activeArr.includes('transfer')">
  127. <div class="item active" title="转接" @click="onEvent('transfer')">
  128. <SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />
  129. 转接
  130. </div>
  131. </template>
  132. <!-- 灰色转接不可用 -->
  133. <template v-else>
  134. <div class="item disabled" title="转接"><SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />转接</div>
  135. </template>
  136. <!-- 三方会议可用 -->
  137. <template v-if="m_bLogin && activeArr.includes('conference')">
  138. <div class="item active" title="三方会议" @click="onEvent('conference')">
  139. <SvgIcon name="iconfont icon-conference" class="icon mr3" size="16px" />
  140. 三方会议
  141. </div>
  142. </template>
  143. <!-- 三方会议不可用 -->
  144. <template v-else>
  145. <div class="item disabled" title="三方会议"><SvgIcon name="iconfont icon-conference" class="icon mr3" size="16px" />三方会议</div>
  146. </template>
  147. <!-- 整理和取消整理 可用 -->
  148. <!-- <template v-if="m_bLogin && activeArr.includes('talkingDeal')">
  149. <div class="item active" :title="m_IsTalkingDeal ? '取消整理' : '话后整理'" @click="onEvent(m_IsTalkingDeal ? 'idle' : '')">
  150. <SvgIcon name="iconfont icon-talkingDeal" class="icon mr3" size="16px" />
  151. {{ m_IsTalkingDeal ? '取消整理' : '话后整理' }}
  152. </div>
  153. </template>
  154. &lt;!&ndash; 灰色整理不可用 &ndash;&gt;
  155. <template v-else>
  156. <div class="item disabled" title="话后整理"><SvgIcon name="iconfont icon-talkingDeal" class="icon mr3" size="16px" />话后整理</div>
  157. </template>-->
  158. <!-- 评价可用 登录并且是呼入 -->
  159. <template v-if="m_bLogin && m_IsCallIn && activeArr.includes('evaluate')">
  160. <div class="item active" title="评价" @click="onEvent('evaluate')">
  161. <SvgIcon name="ele-Flag" class="icon mr3" size="16px" />
  162. 评价
  163. </div>
  164. </template>
  165. <!-- 评价不可用 -->
  166. <template v-else>
  167. <div class="item disabled" title="评价"><SvgIcon name="ele-Flag" class="icon mr3" size="16px" />评价</div>
  168. </template>
  169. </div>
  170. </div>
  171. <!-- 签入弹窗 -->
  172. <el-dialog v-model="state.dutyDialogVisible" draggable title="签入" width="500px" :show-close="false">
  173. <el-form :model="state.dutyForm" label-width="80px" ref="dutyFormRef" @submit.native.prevent>
  174. <el-form-item label="分机号" prop="telNo" :rules="[{ required: true, message: '请选择分机号', trigger: 'change' }]">
  175. <!-- <el-select-v2
  176. v-model="state.dutyForm.telNo"
  177. :options="telsList"
  178. placeholder="请选择分机号"
  179. filterable
  180. class="w100"
  181. :props="{
  182. label: 'no',
  183. value: 'no',
  184. }"
  185. clearable
  186. />-->
  187. <el-input v-model="state.dutyForm.telNo"></el-input>
  188. </el-form-item>
  189. <el-form-item label="技能组" prop="skillId" :rules="[{ required: true, message: '请选择技能组', trigger: 'change' }]">
  190. <!-- <el-select-v2
  191. v-model="state.dutyForm.skillId"
  192. :options="telsListGroup"
  193. placeholder="请选择技能组"
  194. filterable
  195. class="w100"
  196. :props="{
  197. label: 'no',
  198. value: 'no',
  199. }"
  200. clearable
  201. >
  202. </el-select-v2>-->
  203. <el-input v-model="state.dutyForm.skillId"></el-input>
  204. </el-form-item>
  205. </el-form>
  206. <template #footer>
  207. <span class="dialog-footer">
  208. <el-button @click="state.dutyDialogVisible = false" class="default-button" :loading="state.loading">取 消</el-button>
  209. <el-button type="primary" @click="clickOnDuty(dutyFormRef)" :loading="state.loading">确 定</el-button>
  210. </span>
  211. </template>
  212. </el-dialog>
  213. <!-- 呼出弹窗 -->
  214. <el-dialog v-model="state.outboundDialogVisible" draggable title="呼出" width="450px">
  215. <el-form :model="state.outboundForm" label-width="80px" ref="outboundFormRef" @submit.native.prevent>
  216. <el-form-item label="呼出号码" prop="telNo" :rules="[{ required: true, message: '请填写呼出号码', trigger: 'blur' }]">
  217. <el-input v-model="state.outboundForm.telNo" placeholder="呼出号码" @keyup.enter="clickOnOutbound(outboundFormRef)" clearable />
  218. </el-form-item>
  219. </el-form>
  220. <template #footer>
  221. <span class="dialog-footer">
  222. <el-button @click="state.outboundDialogVisible = false" class="default-button" :loading="state.loading">取 消</el-button>
  223. <el-button type="primary" @click="clickOnOutbound(outboundFormRef)" :loading="state.loading">确 定</el-button>
  224. </span>
  225. </template>
  226. </el-dialog>
  227. <!-- 咨询弹窗 -->
  228. <el-dialog v-model="state.consultDialogVisible" draggable title="咨询" width="450px">
  229. <el-form :model="state.consultForm" label-width="80px" ref="consultFormRef" @submit.native.prevent>
  230. <el-form-item label="咨询类型" prop="strType" :rules="[{ required: false, message: '请选择咨询类型', trigger: 'blur' }]">
  231. <el-radio-group v-model="state.consultForm.strType">
  232. <el-radio value="0">内线</el-radio>
  233. <el-radio value="1">外线</el-radio>
  234. <el-radio value="2">群组</el-radio>
  235. </el-radio-group>
  236. </el-form-item>
  237. <el-form-item label="咨询" prop="telNo" :rules="[{ required: true, message: '请填写咨询号码', trigger: 'blur' }]">
  238. <el-input v-model="state.consultForm.telNo" placeholder="咨询号码" @keyup.enter="clickOnConsult(consultFormRef)" clearable />
  239. </el-form-item>
  240. </el-form>
  241. <template #footer>
  242. <span class="dialog-footer">
  243. <el-button @click="state.consultDialogVisible = false" class="default-button" :loading="state.loading">取 消</el-button>
  244. <el-button type="primary" @click="clickOnConsult(consultFormRef)" :loading="state.loading">确 定</el-button>
  245. </span>
  246. </template>
  247. </el-dialog>
  248. <!-- 盲转 -->
  249. <el-dialog v-model="state.blindDialogVisible" draggable title="盲转" width="450px">
  250. <el-form :model="state.blindForm" label-width="80px" ref="blindFormRef" @submit.native.prevent>
  251. <el-form-item label="盲转" prop="telNo" :rules="[{ required: true, message: '请填写盲转号码', trigger: 'blur' }]">
  252. <el-input v-model="state.blindForm.telNo" placeholder="盲转号码" @keyup.enter="clickOnBlind(blindFormRef)" clearable />
  253. </el-form-item>
  254. </el-form>
  255. <template #footer>
  256. <span class="dialog-footer">
  257. <el-button @click="state.blindDialogVisible = false" class="default-button" :loading="state.loading">取 消</el-button>
  258. <el-button type="primary" @click="clickOnBlind(blindFormRef)" :loading="state.loading">确 定</el-button>
  259. </span>
  260. </template>
  261. </el-dialog>
  262. <!-- 占位标签 -->
  263. <div class="seizeSeat-box"></div>
  264. </template>
  265. <script setup lang="ts" name="zgTelControl">
  266. import { computed, onMounted, reactive, ref } from 'vue';
  267. import { getNowDateTime } from '@/utils/constants';
  268. import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
  269. import { useRouter } from 'vue-router';
  270. import { useWebSocket } from '@/hooks/useWebsocket';
  271. import { useIntervalFn } from '@vueuse/shared';
  272. import { formatDuration } from '@/utils/formatTime';
  273. import { Local } from '@/utils/storage';
  274. import { useAppConfig } from '@/stores/appConfig';
  275. import { storeToRefs } from 'pinia';
  276. import { useUserInfo } from '@/stores/userInfo';
  277. import { callCenterIsOnThePhone, callCenterIsSignIn, callCenterWs, currentTel } from '@/utils/callCenter';
  278. import mittBus from '@/utils/mitt';
  279. import { callCenterSignIn, callCenterSignOut, getCallCenterGroupList, getCallCenterList } from '@/api/callCenter';
  280. import { getCurrentCityConfig } from '@/utils/appConfig';
  281. import { useTimeoutFn } from '@vueuse/shared/index';
  282. const state = reactive({
  283. dutyDialogVisible: false,
  284. loading: false,
  285. dutyForm: {
  286. telNo: null,
  287. skillId: null,
  288. },
  289. outboundDialogVisible: false,
  290. outboundForm: {
  291. telNo: '',
  292. },
  293. consultDialogVisible: false,
  294. consultForm: {
  295. telNo: '',
  296. strType: '0',
  297. },
  298. blindDialogVisible: false,
  299. blindForm: {
  300. telNo: '',
  301. },
  302. });
  303. /*
  304. * @description: 置当前可用的按钮
  305. * 电话控件在头部展示时切换;
  306. * 状态改变
  307. * 0-未登录;100-登录成功;
  308. * 200-空闲;201-繁忙;
  309. * 300-呼入振铃;301-呼入通话;302-呼出振铃;303-呼出通话;310-保持通话;320-会议;330-咨询;333-转接接通
  310. * 510-监听;520-插话;
  311. */
  312. const activeArr = computed(() => {
  313. const switchCases: any = {
  314. '0': ['dutyOn'], // 签出状态
  315. '100': [], // 登录成功
  316. '200': ['dutyOff', 'rest', 'outbound'], // 空闲
  317. '201': ['rest'], // 示忙
  318. '300': ['hangup'], // 呼入振铃
  319. '301': ['hangup', 'hold', 'consult', 'transferMz', 'evaluate'], // 呼入通话
  320. '302': ['hangup'], // 呼出振铃
  321. '303': ['hangup', 'hold', 'consult', 'transferMz', 'evaluate'], // 呼出通话
  322. '310': ['hangup', 'hold'], // 通话保持
  323. '320': ['hangup', 'evaluate'], // 三方会议中
  324. '330': ['hangup', 'hold', 'transfer', 'conference', 'evaluate'], // 转接 咨询
  325. '331': ['hangup', 'hold', 'evaluate'], // 咨询 转接
  326. '900': ['dutyOff', 'talkingDeal'], // 整理
  327. };
  328. let arr = <EmptyArrayType>[];
  329. if (m_strTelState.value in switchCases) {
  330. arr = switchCases[m_strTelState.value];
  331. }
  332. return arr;
  333. });
  334. // 当前对应code展示的中文
  335. const currentStatusText = computed(() => {
  336. const statusMap: any = {
  337. '0': '签出',
  338. '100': '登录成功',
  339. '200': '空闲',
  340. '201': '示忙',
  341. '300': '呼入振铃',
  342. '301': '呼入通话',
  343. '302': '呼出振铃',
  344. '303': '呼出通话',
  345. '310': '通话保持',
  346. '320': '三方会议',
  347. '330': '转接',
  348. '331': '转接',
  349. '900': '整理',
  350. };
  351. return statusMap[m_strTelState.value] || '';
  352. });
  353. // ws实例对象
  354. const wsRef = ref();
  355. const { callCenterSocketUrl } = getCurrentCityConfig();
  356. const initWs = () => {
  357. wsRef.value = useWebSocket(callCenterSocketUrl, {
  358. /* heartbeat: {
  359. message: 'ping',
  360. interval: 5000,
  361. pongTimeout: 5000,
  362. },*/
  363. autoReconnect: true, // 自动重连
  364. immediate: false, // 是否立即链接
  365. onMessage: e_TelMsgReceive, // 消息接收
  366. onError: e_websocketError, // 错误
  367. onDisconnected: e_websocketClose, // 断开
  368. onConnected: e_websocketOpen, // 链接成功
  369. });
  370. };
  371. // 发送消息
  372. const e_TelSendMsg = (strObj: Object) => {
  373. // 客户端当前时间
  374. const strMsg = JSON.stringify(strObj);
  375. console.log(`${getNowDateTime()} 发送消息:`, strMsg, wsRef.value.status);
  376. if (wsRef.value.ws?.readyState === 1) {
  377. // 已经链接并且可以通讯,则发放文本消息
  378. wsRef.value.send(strMsg);
  379. } else {
  380. ElMessage.error('请先签入');
  381. state.loading = false;
  382. }
  383. };
  384. const m_strUserNo = ref(''); // 分机号码
  385. const m_strJobNum = ref(''); // 坐席工号
  386. const m_strSkillId = ref(''); // 技能组
  387. const m_strLevel = ref('1'); // 优先级别
  388. const m_strGroup = ref('1'); // 分组ID
  389. const m_strCompanyId = ref(''); // 企业编码
  390. const m_bLogin = ref(false); // 登录状态
  391. const m_bTelBusy = ref(false); // 是否示忙中
  392. const m_strIsMonitor = ref('0'); // 是否监控分机 1-是监控分机
  393. const callId = ref(''); // 通话ID
  394. const m_IsCallOut = ref(false); // 是否呼出
  395. const m_IsCallIn = ref(false); // 是否是呼入
  396. const m_strOpenFlag = ref('2'); // 来电弹屏方式 1-接通弹屏;2-振铃弹屏
  397. const m_CallOutOpen = ref(false); // 呼出是否弹屏(用于未接统计“回拨”业务处理)
  398. const m_bIsOpen = ref(false); // 是否已经弹屏
  399. const m_bCallConnect = ref(false); // 是否在通话状态
  400. const m_IsConsult = ref(false); // 是否咨询
  401. const m_strConsultType = ref('-1'); // 咨询类型
  402. const m_IsHangup = ref(false); // 是否挂机
  403. const m_IsHold = ref(false); // 是否保持
  404. const m_IsTalkingDeal = ref(false); // 是否通话整理
  405. const m_IsMonListen = ref('0'); // 监控状态 0-未监听;1-监控成功;2-监控失败;
  406. const m_strTelState = ref('0'); // 当前状态
  407. const showPop = ref(false);
  408. // 点击事件
  409. const onEvent = (event: string) => {
  410. switch (event) {
  411. case 'signIn': // 签入
  412. onSignIn();
  413. break;
  414. case 'signOut': // 签出
  415. onSignOut();
  416. break;
  417. case 'busy': // 示忙
  418. onBusy();
  419. break;
  420. case 'idle': // 示闲
  421. onIdle();
  422. break;
  423. case 'answer': // 应答
  424. break;
  425. case 'hangup': // 挂机
  426. onHangup();
  427. break;
  428. case 'callOut': // 外呼
  429. onCallOut();
  430. break;
  431. case 'hold': // 保持
  432. onHold();
  433. break;
  434. case 'reHold': // 恢复
  435. onReHold();
  436. break;
  437. // 咨询
  438. case 'consult':
  439. e_TopStateChange('331');
  440. onConsultOpen();
  441. break;
  442. // 转接
  443. case 'transfer':
  444. onTransfer();
  445. break;
  446. // 盲转
  447. case 'transferMz':
  448. onTransferMzOpen();
  449. break;
  450. case 'conference': // 三方会议
  451. onConference();
  452. break;
  453. case 'DTMF': // 拨号盘
  454. break;
  455. // 评价
  456. case 'evaluate':
  457. i_evaluate();
  458. break;
  459. }
  460. };
  461. /*
  462. * 事件响应状态
  463. */
  464. const arrangeTime = ref(0);
  465. const arrangeTimer = useIntervalFn(
  466. () => {
  467. arrangeTime.value += 1;
  468. },
  469. 1000,
  470. { immediate: false }
  471. );
  472. // 整理时长开始
  473. const startArrangeTime = () => {
  474. arrangeTimer.resume();
  475. };
  476. // 整理时长开始结束
  477. const stopArrangeTime = () => {
  478. arrangeTime.value = 0;
  479. arrangeTimer.pause();
  480. };
  481. const evtSeatState = (data: any) => {
  482. if (m_strIsMonitor.value == '1') {
  483. // 推送分机信息
  484. const pushExt = data.Param.Extension || '';
  485. if (pushExt !== m_strUserNo.value) {
  486. // 如果分机实时推送的分机信息和当前登录分机不一致,则表明当前登录分机为监控分机,开启了状态监控功能
  487. // 1.不属于当前分机的状态,则不改变当前分机电话条状态
  488. // 2.调整其他分机大屏监控状态
  489. const pushState = GetTelState(data.Param.State);
  490. if (pushState) {
  491. // 修改后台数据
  492. console.log(pushExt, pushState);
  493. // SetMonitorState(pushExt, pushState);
  494. }
  495. return;
  496. }
  497. }
  498. if (m_IsHold.value) {
  499. // 正在保持通话状态下
  500. return;
  501. }
  502. // 状态 0:闲 1:忙 2:会议 3:登出 4:呼入 5:呼出 6:咨询 7:其他 8:通话
  503. const strState = data.Param.State;
  504. switch (strState) {
  505. // 空闲
  506. case '0':
  507. m_bIsOpen.value = false;
  508. m_IsHangup.value = false;
  509. m_strTelState.value = '200';
  510. m_bTelBusy.value = false;
  511. e_TopStateChange(m_strTelState.value);
  512. startIdleTime(); // 空闲计时器开始
  513. stopTalkTimer(); // 停止通话时长
  514. stopConferenceTime(); // 三方会议时长结束
  515. // m_IsTalkingDeal.value = false;
  516. break;
  517. // 示忙
  518. case '1':
  519. m_strTelState.value = '201';
  520. m_bTelBusy.value = true;
  521. e_TopStateChange(m_strTelState.value);
  522. startBusyTime(); // 示忙计时器开始
  523. stopIdleTime(); // 停止空闲时长
  524. break;
  525. case '2':
  526. stopIdleTime();
  527. break;
  528. // 登出
  529. case '3':
  530. // 附加签出方法(用户分机分离调用业务系统签出)
  531. m_strTelState.value = '0';
  532. e_TopStateChange(m_strTelState.value);
  533. break;
  534. // 通话振铃
  535. case '4':
  536. if (false === m_IsHangup.value) {
  537. if (m_IsCallOut.value == true) {
  538. // 呼出振铃
  539. m_strTelState.value = '302';
  540. } else {
  541. // 呼入振铃
  542. m_strTelState.value = '300';
  543. }
  544. e_TopStateChange(m_strTelState.value);
  545. }
  546. stopIdleTime();
  547. break;
  548. // 通话振铃
  549. case '5':
  550. if (!m_IsHangup.value) {
  551. m_strTelState.value = '302';
  552. e_TopStateChange(m_strTelState.value);
  553. }
  554. stopIdleTime();
  555. break;
  556. // 咨询
  557. case '6':
  558. break;
  559. // 其他
  560. case '7':
  561. break;
  562. // 接通
  563. case '8':
  564. if (!m_IsHangup.value) {
  565. if (m_IsCallOut.value) {
  566. // 呼出接通
  567. m_strTelState.value = '303';
  568. } else {
  569. // 呼入接通
  570. m_strTelState.value = '301';
  571. // 是否是保持通话
  572. }
  573. e_TopStateChange(m_strTelState.value);
  574. }
  575. stopIdleTime();
  576. break;
  577. case '9': // 工单整理
  578. m_strTelState.value = '900';
  579. startArrangeTime(); // 开始整理时长
  580. e_TopStateChange(m_strTelState.value);
  581. // m_IsTalkingDeal.value = true;
  582. break;
  583. }
  584. };
  585. // 获取分机状态
  586. const GetTelState = (strState: any) => {
  587. let strResult = '';
  588. switch (strState) {
  589. // 空闲
  590. case '0':
  591. strResult = '200';
  592. break;
  593. // 示忙
  594. case '1':
  595. strResult = '201';
  596. break;
  597. case '2':
  598. break;
  599. // 登出
  600. case '3':
  601. strResult = '0';
  602. break;
  603. // 通话振铃
  604. case '4':
  605. strResult = '302'; // 默认呼入振铃
  606. break;
  607. // 通话振铃
  608. case '5':
  609. strResult = '302'; // 默认呼出振铃
  610. break;
  611. // 咨询
  612. case '6':
  613. break;
  614. // 其他
  615. case '7':
  616. break;
  617. // 接通
  618. case '8':
  619. strResult = '301';
  620. break;
  621. case '9': // 工单整理
  622. strResult = '900';
  623. break;
  624. }
  625. return strResult;
  626. };
  627. /**
  628. * 状态初始化请求
  629. * */
  630. const ReqAgentMonitor = () => {
  631. // 开始坐席状态监控
  632. // SkillId 技能组为0则监控所有分机
  633. const msgObj = {
  634. Action: 'ReqAgentMonitor',
  635. Param: {
  636. Extension: m_strUserNo.value,
  637. CompanyId: m_strCompanyId.value,
  638. SkillId: '0',
  639. },
  640. };
  641. // 发送请求
  642. e_TelSendMsg(msgObj);
  643. };
  644. /**
  645. * {“Action”:”ResAgentMonitor”,”Param”:[{“Extension”:,”JobNumber”:,”SkillId”:,”Name”:,”Caller”:,”Called”:,”State”}]}
  646. * State:0:闲 1:忙 2:会议 3:登出 4:呼入 5:呼出 6:咨询 7:其他 8:通话 9:工单整理
  647. * 监控状态初始化返回状态
  648. * @param {any} data
  649. */
  650. const ResAgentMonitor = (data) => {
  651. if (null != data && null != data.Param && data.Param.length > 0) {
  652. // 监控分机集合
  653. const strMonitorInfo = JSON.stringify(data.Param);
  654. console.log(`${getNowDateTime()} 监控分机集合:`, strMonitorInfo);
  655. }
  656. };
  657. /*
  658. * 登录
  659. * ReqAgentLogin - 登录方法名
  660. * JobNum - 工号
  661. * Name - 姓名
  662. * Extension - 分机号
  663. * SkillId - 技能组
  664. * Level - 为要设置的级别,分为9个级别,从高到低分别为0-8;同一技能组中级别越高的坐席优先被分配
  665. * Role - 角色,保留,不做设置
  666. * GroupName - 技能组名称
  667. * OrgId - 组织ID
  668. * 返回内容:
  669. {“Action”:”ResAgentLogin”,”Param”:{“Result”:}}
  670. Result: 3:分机错误 7:已登录 0:登录成功
  671. */
  672. const sendSignIn = () => {
  673. callCenterWs.value = wsRef.value;
  674. const sendObj = {
  675. Action: 'ReqAgentLogin',
  676. Param: {
  677. ExtNum: m_strUserNo.value,
  678. JobNum: m_strJobNum.value,
  679. Name: m_strUserNo.value,
  680. Extension: m_strUserNo.value,
  681. SkillId: m_strSkillId.value,
  682. Level: m_strLevel.value,
  683. Role: '',
  684. GroupName: m_strGroup.value,
  685. OrgId: m_strCompanyId.value,
  686. },
  687. };
  688. // 发送请求
  689. e_TelSendMsg(sendObj);
  690. console.log(`${getNowDateTime()} 呼叫中心发起签入`);
  691. };
  692. const signTime = ref(0); //签入时长
  693. // 使用 useInterval 创建定时器
  694. const signInTimer = useIntervalFn(
  695. () => {
  696. signTime.value += 1;
  697. },
  698. 1000,
  699. { immediate: false }
  700. );
  701. // 签入时长及时开始
  702. const startSignTime = (second?: number) => {
  703. if (second) {
  704. // 从后台获取签入时长
  705. if (second < 0) second = 0; // 防止后台返回的签入时间大于当前时间
  706. signTime.value = second;
  707. signInTimer.resume();
  708. } else {
  709. signInTimer.resume();
  710. }
  711. };
  712. // 签入时长计时结束
  713. const stopSignTime = () => {
  714. signTime.value = 0;
  715. signInTimer.pause();
  716. };
  717. // 空闲时长
  718. const idleTime = ref(0);
  719. const idleTimer = useIntervalFn(
  720. () => {
  721. idleTime.value += 1;
  722. },
  723. 1000,
  724. { immediate: false }
  725. );
  726. // 空闲时长开始
  727. const startIdleTime = () => {
  728. idleTimer.resume();
  729. stopBusyTime(); // 结束示忙时长
  730. stopArrangeTime(); // 结束整理时长
  731. };
  732. // 空闲时长计时结束
  733. const stopIdleTime = () => {
  734. idleTime.value = 0;
  735. idleTimer.pause();
  736. };
  737. const appConfigStore = useAppConfig();
  738. const { AppConfigInfo } = storeToRefs(appConfigStore); // 系统配置信息
  739. const storesUserInfo = useUserInfo();
  740. const { userInfos } = storeToRefs(storesUserInfo); // 用户信息
  741. const onSignIn = () => {
  742. if (AppConfigInfo.value.isNeedTelNo) {
  743. // 需要填写分机号
  744. state.dutyDialogVisible = true;
  745. } else {
  746. if (!userInfos.value.defaultTelNo) {
  747. ElMessage.error('请先配置用户默认分机');
  748. return;
  749. }
  750. if (!userInfos.value.defaultTelGroup) {
  751. ElMessage.error('请先配置用户技能组');
  752. return;
  753. }
  754. state.loading = true;
  755. m_strUserNo.value = userInfos.value.defaultTelNo; // 默认分机
  756. m_strJobNum.value = <string>userInfos.value.staffNo; // 工号
  757. m_strSkillId.value = userInfos.value.defaultTelGroup; // 技能组
  758. currentTel.value = {
  759. telNo: userInfos.value.defaultTelNo,
  760. telGroup: userInfos.value.defaultTelGroup,
  761. jobNum: <string>userInfos.value.staffNo,
  762. };
  763. wsRef.value.open();
  764. }
  765. };
  766. const dutyFormRef = ref();
  767. const clickOnDuty = (formEl: FormInstance | undefined) => {
  768. if (!formEl) return;
  769. formEl.validate((valid: boolean) => {
  770. if (!valid) return;
  771. state.loading = true;
  772. m_strUserNo.value = <string>state.dutyForm.telNo;
  773. m_strJobNum.value = <string>state.dutyForm.telNo;
  774. m_strSkillId.value = <string>state.dutyForm.skillId;
  775. currentTel.value = {
  776. telNo: state.dutyForm.telNo,
  777. telGroup: state.dutyForm.skillId,
  778. jobNum: state.dutyForm.telNo,
  779. };
  780. wsRef.value.open();
  781. state.dutyDialogVisible = false;
  782. state.loading = false;
  783. });
  784. };
  785. // 签入消息回调
  786. const retSignIn = (data: any) => {
  787. state.loading = false;
  788. if (data.Param.Result === '0') {
  789. // 登录成功
  790. m_bLogin.value = true;
  791. m_strTelState.value = '100';
  792. e_TopStateChange(m_strTelState.value);
  793. // 刷新页面自动签出不需要调用业务系统签入
  794. // 附加签入方法(用户分机分离) 调用业务系统登录
  795. e_TelSignIn(m_strUserNo.value, m_strSkillId.value);
  796. // 登录成功
  797. startSignTime(); // 签入计时器
  798. callCenterIsSignIn.value = true; // 签入状态
  799. if (m_strIsMonitor.value === '1') {
  800. // 监控初始化状态
  801. ReqAgentMonitor();
  802. }
  803. console.log(`${getNowDateTime()} 呼叫中心签入成功回调`);
  804. } else if (data.Param.Result === '3') {
  805. // 登录不成功 调用业务系统
  806. e_TelSignOut(m_strUserNo.value, m_strSkillId.value);
  807. wsRef.value.close();
  808. // 分机错误
  809. ElMessage.error('分机错误');
  810. // 分机错误
  811. } else if (data.Param.Result === '7') {
  812. // 先签出再签入
  813. sendSignOut();
  814. }
  815. };
  816. // ws链接开启成功
  817. const e_websocketOpen = () => {
  818. sendSignIn();
  819. };
  820. // 链接关闭
  821. const e_websocketClose = () => {
  822. callCenterIsSignIn.value = false; // 签出状态
  823. console.log(`${getNowDateTime()} 呼叫中心链接关闭`);
  824. };
  825. // 链接错误
  826. const e_websocketError = () => {
  827. callCenterWs.value = null;
  828. callCenterIsSignIn.value = false; // 签出状态
  829. console.log(`${getNowDateTime()} 呼叫中心链接错误`);
  830. };
  831. // 消息接收
  832. const e_TelMsgReceive = (ws: any, restMsg: any) => {
  833. console.log(`${getNowDateTime()} 接收消息:${restMsg.data}`);
  834. if (restMsg.data) {
  835. const data = eval('(' + restMsg.data + ')');
  836. if (data) {
  837. // 方法
  838. const strAction = data.Action;
  839. switch (strAction) {
  840. // 登录返回值
  841. case 'ResAgentLogin':
  842. retSignIn(data);
  843. break;
  844. // 示闲
  845. case 'ResAgentIdle':
  846. retIdle(data);
  847. break;
  848. // 示忙
  849. case 'ResAgentBusy':
  850. retBusy(data);
  851. break;
  852. // 外呼状态
  853. case 'ResMakeCall':
  854. retCallOut(data);
  855. break;
  856. // 保持
  857. case 'ResHoldCall':
  858. retHold(data);
  859. break;
  860. // 取消保持
  861. case 'ResRetrieve':
  862. retRehold(data);
  863. break;
  864. // 咨询内线
  865. case 'ResConsultInline':
  866. retConsult(data, '0');
  867. break;
  868. // 咨询外线
  869. case 'ResConsultOutline':
  870. retConsult(data, '1');
  871. break;
  872. // 咨询群组
  873. case 'ResConsultSkillGroup':
  874. retConsult(data, '2');
  875. break;
  876. // 咨询转移
  877. case 'ResTransfer':
  878. retResTransfer(data);
  879. break;
  880. // 三方会议
  881. case 'ResConference':
  882. retResConference(data);
  883. break;
  884. // 三方会议
  885. case 'ResMonConf':
  886. retResConference(data);
  887. break;
  888. // 坐席实时状态
  889. case 'ResAgentMinitor':
  890. ResAgentMonitor(data);
  891. break;
  892. // 监听
  893. case 'ResMonListen':
  894. retResMonListen(data);
  895. break;
  896. // 取消监听返回
  897. case 'ResStopListen':
  898. retResStopListen(data);
  899. break;
  900. }
  901. // 事件
  902. const strEvent = data.Event;
  903. switch (strEvent) {
  904. // 签出事件
  905. case 'EvtLogout':
  906. retSignOut();
  907. break;
  908. // 呼入振铃事件
  909. case 'EvtCallAlerting':
  910. evtCallAlerting(data);
  911. break;
  912. // 应答事件
  913. case 'EvtCallAnswer':
  914. evtEvtCallAnswer(data);
  915. break;
  916. // 挂机事件
  917. case 'EvtHangup':
  918. retHangup(data);
  919. break;
  920. // 状态
  921. case 'EvtSeatState':
  922. evtSeatState(data);
  923. break;
  924. // 队列等待
  925. case 'EvtAcdInfo':
  926. i_QueueNum(data);
  927. break;
  928. // 语音识别结果通知事件
  929. case 'EvtRecognize':
  930. e_EvtRecognize(data.Param);
  931. break;
  932. // 转三方接通状态
  933. case 'EvtDispatchState':
  934. e_EvtDispatchState(data.Param);
  935. break;
  936. // 签出事件
  937. case 'ResStopMonitor':
  938. retSignOut();
  939. break;
  940. case 'ResError': // 异常
  941. retHangup(data); // 挂机
  942. // 异常处理
  943. retResError(data);
  944. break;
  945. // 呼出接通事件
  946. case 'EvtOutCalling':
  947. evtEvtCalling(data); // 呼出振铃事件
  948. break;
  949. // 签出事件
  950. case 'EvtQuit':
  951. retSignOut();
  952. break;
  953. }
  954. }
  955. }
  956. };
  957. /*
  958. * 签出
  959. * ReqAgentLogout - 签出方法名称
  960. * Extension - 分机号码
  961. */
  962. const sendSignOut = () => {
  963. const objMsg = {
  964. Action: 'ReqAgentLogout',
  965. Param: {
  966. Extension: m_strUserNo.value,
  967. },
  968. };
  969. // 发送请求
  970. e_TelSendMsg(objMsg);
  971. };
  972. const onSignOut = () => {
  973. ElMessageBox.confirm(`确定要签出,是否继续?`, '提示', {
  974. confirmButtonText: '确认',
  975. cancelButtonText: '取消',
  976. type: 'warning',
  977. draggable: true,
  978. cancelButtonClass: 'default-button',
  979. autofocus: false,
  980. })
  981. .then(() => {
  982. sendSignOut();
  983. })
  984. .catch(() => {
  985. state.loading = false;
  986. });
  987. };
  988. /*
  989. * 签出事件
  990. */
  991. const retSignOut = () => {
  992. // 刷新页面自动签出不需要调用业务系统签入
  993. // 附加签出方法(用户分机分离 调用业务系统等处)
  994. e_TelSignOut(m_strUserNo.value, m_strSkillId.value);
  995. state.loading = false;
  996. // 登出成功
  997. m_strTelState.value = '0';
  998. e_TopStateChange(m_strTelState.value);
  999. // 签出
  1000. e_ActionUpdate('1'); // 更新话机动作
  1001. m_bLogin.value = false;
  1002. callCenterIsSignIn.value = false; // 签出状态
  1003. stopSignTime(); // 停止签入计时器
  1004. stopBusyTime(); // 停止示忙计时器
  1005. stopIdleTime(); // 停止空闲计时器
  1006. stopTalkTimer(); // 停止通话计时器
  1007. stopArrangeTime(); // 停止整理时长
  1008. // 关闭ws
  1009. wsRef.value.close();
  1010. console.log(`${getNowDateTime()} 呼叫中心签出回调`);
  1011. };
  1012. /*
  1013. * 示忙
  1014. * ReqAgentBusy - 方法名
  1015. * Extension:分机号
  1016. */
  1017. // 示忙时长
  1018. const busyTime = ref(0);
  1019. const busyTimer = useIntervalFn(
  1020. () => {
  1021. busyTime.value += 1;
  1022. },
  1023. 1000,
  1024. { immediate: false }
  1025. );
  1026. // 示忙时长开始
  1027. const startBusyTime = () => {
  1028. busyTimer.resume();
  1029. };
  1030. // 示忙时长计时结束
  1031. const stopBusyTime = () => {
  1032. busyTime.value = 0;
  1033. busyTimer.pause();
  1034. };
  1035. const onBusy = () => {
  1036. ElMessageBox.confirm(`确定要示忙,是否继续?`, '提示', {
  1037. confirmButtonText: '确认',
  1038. cancelButtonText: '取消',
  1039. type: 'warning',
  1040. draggable: true,
  1041. cancelButtonClass: 'default-button',
  1042. autofocus: false,
  1043. })
  1044. .then(() => {
  1045. const objMsg = {
  1046. Action: 'ReqAgentBusy',
  1047. Param: {
  1048. Extension: m_strUserNo.value,
  1049. },
  1050. };
  1051. // 发送请求
  1052. e_TelSendMsg(objMsg);
  1053. })
  1054. .catch(() => {
  1055. state.loading = false;
  1056. });
  1057. };
  1058. /*
  1059. * 示忙返回值
  1060. {“Action”:”ResAgentBusy”,”Param”:{“Result”:}}
  1061. Result:0-1
  1062. 0:成功 1:失败
  1063. */
  1064. const retBusy = (data) => {
  1065. if (data.Param.Result === '0') {
  1066. m_bTelBusy.value = true;
  1067. m_strTelState.value = '201';
  1068. e_TopStateChange(m_strTelState.value);
  1069. } else {
  1070. ElMessage.error('示忙失败');
  1071. }
  1072. };
  1073. /*
  1074. * 示闲
  1075. * ReqAgentIdle - 方法名
  1076. * Extension:分机号
  1077. */
  1078. const onIdle = () => {
  1079. ElMessageBox.confirm(`确定要示闲,是否继续?`, '提示', {
  1080. confirmButtonText: '确认',
  1081. cancelButtonText: '取消',
  1082. type: 'warning',
  1083. draggable: true,
  1084. cancelButtonClass: 'default-button',
  1085. autofocus: false,
  1086. })
  1087. .then(() => {
  1088. const objMsg = {
  1089. Action: 'ReqAgentIdle',
  1090. Param: {
  1091. Extension: m_strUserNo.value,
  1092. },
  1093. };
  1094. // 发送请求
  1095. e_TelSendMsg(objMsg);
  1096. })
  1097. .catch(() => {
  1098. state.loading = false;
  1099. });
  1100. };
  1101. /*
  1102. * 示闲返回值
  1103. * {“Action”:”ResAgentIdle”,”Param”:{“Result”:}}
  1104. Result:0-1
  1105. 0:成功 1:失败
  1106. */
  1107. const retIdle = (data) => {
  1108. if (data.Param.Result == '0') {
  1109. m_bTelBusy.value = false;
  1110. m_strTelState.value = '200';
  1111. e_TopStateChange(m_strTelState.value);
  1112. // m_IsTalkingDeal.value = false;
  1113. } else {
  1114. ElMessage.error('示闲失败');
  1115. }
  1116. };
  1117. /*
  1118. * 保持
  1119. */
  1120. const onHold = () => {
  1121. ElMessageBox.confirm(`确定要保持,是否继续?`, '提示', {
  1122. confirmButtonText: '确认',
  1123. cancelButtonText: '取消',
  1124. type: 'warning',
  1125. draggable: true,
  1126. cancelButtonClass: 'default-button',
  1127. autofocus: false,
  1128. })
  1129. .then(() => {
  1130. const objMsg = {
  1131. Action: 'ReqHoldCall',
  1132. Param: {
  1133. Extension: m_strUserNo.value,
  1134. },
  1135. };
  1136. // 发送请求
  1137. e_TelSendMsg(objMsg);
  1138. })
  1139. .catch(() => {
  1140. state.loading = false;
  1141. });
  1142. };
  1143. /*
  1144. * 保持返回
  1145. */
  1146. const retHold = (data) => {
  1147. if (data.Param.Result == '0') {
  1148. m_IsHold.value = true;
  1149. m_strTelState.value = '310';
  1150. e_TopStateChange(m_strTelState.value);
  1151. } else {
  1152. ElMessage.error('保持操作失败');
  1153. }
  1154. };
  1155. /*
  1156. * 取消保持
  1157. */
  1158. const onReHold = () => {
  1159. ElMessageBox.confirm(`确定要恢复,是否继续?`, '提示', {
  1160. confirmButtonText: '确认',
  1161. cancelButtonText: '取消',
  1162. type: 'warning',
  1163. draggable: true,
  1164. cancelButtonClass: 'default-button',
  1165. autofocus: false,
  1166. })
  1167. .then(() => {
  1168. const objMsg = {
  1169. Action: 'ReqRetrieve',
  1170. Param: {
  1171. Extension: m_strUserNo.value,
  1172. },
  1173. };
  1174. // 发送请求
  1175. e_TelSendMsg(objMsg);
  1176. })
  1177. .catch(() => {
  1178. state.loading = false;
  1179. });
  1180. };
  1181. /*
  1182. * 取消保持返回状态
  1183. */
  1184. const retRehold = (data) => {
  1185. if (data.Param.Result == '0') {
  1186. // 是否是保持通话
  1187. m_IsHold.value = false;
  1188. if (m_IsCallIn.value) {
  1189. m_strTelState.value = '301';
  1190. } else {
  1191. m_strTelState.value = '303';
  1192. }
  1193. e_TopStateChange(m_strTelState.value);
  1194. } else {
  1195. ElMessage.error('取消保持操作失败');
  1196. }
  1197. };
  1198. // 外呼
  1199. const onCallOut = () => {
  1200. state.loading = false;
  1201. state.outboundDialogVisible = true;
  1202. };
  1203. const outboundFormRef = ref<RefType>();
  1204. const clickOnOutbound = (formEl: FormInstance | undefined) => {
  1205. if (!formEl) return;
  1206. formEl.validate((valid: boolean) => {
  1207. if (!valid) return;
  1208. state.loading = true;
  1209. callout(state.outboundForm.telNo);
  1210. state.outboundDialogVisible = false;
  1211. });
  1212. };
  1213. /*
  1214. * 外呼
  1215. * ReqMakeCall - 方法名
  1216. * Extension:分机号
  1217. * Called:被叫
  1218. * CustomerId:客户ID
  1219. */
  1220. const callout = (strCallNumber) => {
  1221. if (!strCallNumber) {
  1222. ElMessage.error('电话号码不能为空');
  1223. return;
  1224. }
  1225. const obkMsg = {
  1226. Action: 'ReqMakeCall',
  1227. Param: {
  1228. Extension: m_strUserNo.value,
  1229. Called: strCallNumber,
  1230. CustomerId: '',
  1231. },
  1232. };
  1233. // 是否外呼
  1234. m_IsCallOut.value = true;
  1235. // 发送请求
  1236. e_TelSendMsg(obkMsg);
  1237. };
  1238. /*
  1239. * 外呼返回事件
  1240. {“Action”:”ResMakeCall”,”Param”:{“Result”:}}
  1241. Result:0-1
  1242. 0:成功 1:失败
  1243. */
  1244. const retCallOut = (data) => {
  1245. state.loading = false;
  1246. if (data.Param.Result == '0') {
  1247. // 是否外呼
  1248. m_IsCallOut.value = true; // 呼出
  1249. m_strTelState.value = '302';
  1250. e_TopStateChange(m_strTelState.value);
  1251. } else {
  1252. ElMessage.error('呼叫失败');
  1253. }
  1254. };
  1255. /*
  1256. * 咨询
  1257. */
  1258. const onConsultOpen = () => {
  1259. m_strTelState.value = '331';
  1260. // 保持状态
  1261. m_IsHold.value = true;
  1262. state.loading = false;
  1263. state.consultDialogVisible = true;
  1264. };
  1265. const consultFormRef = ref<RefType>();
  1266. const clickOnConsult = (formEl: FormInstance | undefined) => {
  1267. if (!formEl) return;
  1268. formEl.validate((valid: boolean) => {
  1269. if (!valid) return;
  1270. state.loading = true;
  1271. onConsult(state.consultForm.telNo, state.consultForm.strType);
  1272. state.consultDialogVisible = false;
  1273. });
  1274. };
  1275. const onConsult = (strCallNumber: string, strType: string) => {
  1276. let strObj;
  1277. switch (strType) {
  1278. // 内线
  1279. case '0':
  1280. strObj = {
  1281. Action: 'ReqConsultInline',
  1282. Param: {
  1283. Extension: m_strUserNo.value,
  1284. TargetExtension: strCallNumber,
  1285. },
  1286. };
  1287. break;
  1288. // 外线
  1289. case '1':
  1290. strObj = {
  1291. Action: 'ReqConsultOutline',
  1292. Param: {
  1293. Extension: m_strUserNo.value,
  1294. TargetCalled: strCallNumber,
  1295. },
  1296. };
  1297. break;
  1298. // 群组
  1299. case '2':
  1300. strObj = {
  1301. Action: 'ReqConsultSkillGroup',
  1302. Param: {
  1303. Extension: m_strUserNo.value,
  1304. TargetSkillGroup: strCallNumber,
  1305. },
  1306. };
  1307. break;
  1308. }
  1309. // 转接类型
  1310. m_strConsultType.value = strType;
  1311. // 外呼
  1312. m_IsCallOut.value = true;
  1313. // 咨询状态
  1314. m_IsConsult.value = true;
  1315. // 发送请求
  1316. e_TelSendMsg(strObj);
  1317. };
  1318. /*
  1319. * 转接返回
  1320. * data
  1321. * {“Action”:”ResConsultSkillGroup”,”Param”:{“Result”:}}Result:0-1
  1322. * 0:成功 1:失败
  1323. * strType:0-转内线;1-转外线;2-转群组
  1324. */
  1325. const retConsult = (data: any, strType: string) => {
  1326. state.loading = false;
  1327. if (data.Param.Result == '0') {
  1328. // 咨询成功
  1329. // m_IsConsult = true;
  1330. //m_strTelState = "330";
  1331. // e_TopStateChange(m_strTelState);
  1332. } else {
  1333. //alert("转接失败");
  1334. if (!m_IsHangup.value) {
  1335. if (m_IsCallOut.value) {
  1336. // 呼出接通
  1337. m_strTelState.value = '303';
  1338. } else {
  1339. // 呼入接通
  1340. m_strTelState.value = '301';
  1341. }
  1342. e_TopStateChange(m_strTelState.value);
  1343. }
  1344. let strMsg = '咨询失败';
  1345. if (strType == '0') {
  1346. strMsg = '咨询内线失败';
  1347. } else if (strType == '1') {
  1348. strMsg = '咨询外线失败';
  1349. } else if (strType == '2') {
  1350. strMsg = '咨询技能组失败';
  1351. }
  1352. ElMessage.error(strMsg);
  1353. }
  1354. };
  1355. /*
  1356. * 转接
  1357. */
  1358. const onTransfer = () => {
  1359. const objMsg = {
  1360. Action: 'ReqTransfer',
  1361. Param: {
  1362. Extension: m_strUserNo.value,
  1363. },
  1364. };
  1365. // 发送请求
  1366. e_TelSendMsg(objMsg);
  1367. };
  1368. /*
  1369. * 转接返回
  1370. */
  1371. const retResTransfer = (data: any) => {
  1372. if (data.Param.Result == '0') {
  1373. // 咨询成功
  1374. m_IsConsult.value = true;
  1375. m_strTelState.value = '320';
  1376. e_TopStateChange(m_strTelState.value);
  1377. } else {
  1378. ElMessage.error('转接失败');
  1379. }
  1380. };
  1381. /*
  1382. * 盲转
  1383. */
  1384. const onTransferMzOpen = () => {
  1385. state.loading = false;
  1386. state.blindDialogVisible = true;
  1387. };
  1388. const blindFormRef = ref<RefType>();
  1389. const clickOnBlind = (formEl: FormInstance | undefined) => {
  1390. if (!formEl) return;
  1391. formEl.validate((valid: boolean) => {
  1392. if (!valid) return;
  1393. state.loading = true;
  1394. onTransferMz(state.blindForm.telNo);
  1395. state.blindDialogVisible = false;
  1396. });
  1397. };
  1398. const onTransferMz = (strCallNumber) => {
  1399. const objMsg = {
  1400. Action: 'ReqBlindTransfer',
  1401. Param: {
  1402. Extension: m_strUserNo.value,
  1403. TargetCalled: strCallNumber,
  1404. },
  1405. };
  1406. // 发送请求
  1407. e_TelSendMsg(objMsg);
  1408. state.loading = false;
  1409. console.log(`${getNowDateTime()}盲转消息:${JSON.stringify(objMsg)}`);
  1410. // 结束挂机
  1411. useTimeoutFn(() => {
  1412. sendHangup();
  1413. }, 300);
  1414. };
  1415. /*
  1416. * 三方会议
  1417. */
  1418. const onConference = () => {
  1419. ElMessageBox.confirm(`确定要发起三方会议,是否继续?`, '提示', {
  1420. confirmButtonText: '确认',
  1421. cancelButtonText: '取消',
  1422. type: 'warning',
  1423. draggable: true,
  1424. cancelButtonClass: 'default-button',
  1425. autofocus: false,
  1426. })
  1427. .then(() => {
  1428. state.loading = true;
  1429. const objMsg = {
  1430. Action: 'ReqConference',
  1431. Param: {
  1432. Extension: m_strUserNo.value,
  1433. },
  1434. };
  1435. // 发送请求
  1436. e_TelSendMsg(objMsg);
  1437. })
  1438. .catch(() => {
  1439. state.loading = false;
  1440. });
  1441. };
  1442. // 会议时长
  1443. const conferenceTime = ref(0);
  1444. const conferenceTimer = useIntervalFn(
  1445. () => {
  1446. conferenceTime.value += 1;
  1447. },
  1448. 1000,
  1449. { immediate: false }
  1450. );
  1451. // 三方会议开始
  1452. const startConferenceTime = () => {
  1453. conferenceTimer.resume();
  1454. };
  1455. // 三方会议时长计时结束
  1456. const stopConferenceTime = () => {
  1457. conferenceTime.value = 0;
  1458. conferenceTimer.pause();
  1459. };
  1460. /*
  1461. * 三方会议返回
  1462. * { "Action": "ReqConference", "Param": {"Extension": "1002"} }
  1463. */
  1464. const retResConference = (data) => {
  1465. state.loading = false;
  1466. if (data.Param.Result == '0') {
  1467. // 咨询成功
  1468. m_IsConsult.value = true;
  1469. m_strTelState.value = '320';
  1470. e_TopStateChange(m_strTelState.value);
  1471. } else {
  1472. ElMessage.error('三方会议失败');
  1473. }
  1474. };
  1475. /*
  1476. * 挂机
  1477. * ReqHangup - 方法名
  1478. * Extension:分机号
  1479. {“Event”: “EvtHangup”}
  1480. */
  1481. const sendHangup = () => {
  1482. const objMsg = {
  1483. Action: 'ReqHangup',
  1484. Param: {
  1485. Extension: m_strUserNo.value,
  1486. },
  1487. };
  1488. // 发送请求
  1489. e_TelSendMsg(objMsg);
  1490. };
  1491. const onHangup = () => {
  1492. ElMessageBox.confirm(`确定要挂机,是否继续?`, '提示', {
  1493. confirmButtonText: '确认',
  1494. cancelButtonText: '取消',
  1495. type: 'warning',
  1496. draggable: true,
  1497. cancelButtonClass: 'default-button',
  1498. autofocus: false,
  1499. })
  1500. .then(() => {
  1501. sendHangup();
  1502. })
  1503. .catch(() => {
  1504. state.loading = false;
  1505. });
  1506. };
  1507. /*
  1508. * 挂机事件
  1509. * {“Event”: “EvtHangup”}
  1510. */
  1511. const retHangup = (data?: any) => {
  1512. // 挂机后该状态为 false
  1513. console.log(`${getNowDateTime()}:接收消息:呼叫中心挂机回调`, data);
  1514. // 挂机后该状态为 false
  1515. // 挂机
  1516. m_IsHangup.value = true;
  1517. m_IsCallIn.value = false;
  1518. m_IsCallOut.value = false;
  1519. m_bCallConnect.value = false;
  1520. callCenterIsOnThePhone.value = false;
  1521. m_IsConsult.value = false;
  1522. m_IsHold.value = false;
  1523. // 是否弹屏
  1524. m_bIsOpen.value = false;
  1525. m_strConsultType.value = '-1';
  1526. /* if(!m_IsTalkingDeal.value){ // 如果不当前状态不是话后整理状态
  1527. m_strTelState.value = '200';
  1528. e_TopStateChange(m_strTelState.value);
  1529. }*/
  1530. m_strTelState.value = '200';
  1531. e_TopStateChange(m_strTelState.value);
  1532. // 未监听
  1533. m_IsMonListen.value = '0';
  1534. // 呼出是否弹屏(用于未接统计“回拨”业务处理)
  1535. m_CallOutOpen.value = false;
  1536. // 清除呼叫ID
  1537. callId.value = '';
  1538. };
  1539. /*
  1540. * 评价
  1541. */
  1542. const i_evaluate = () => {
  1543. ElMessageBox.confirm(`确定要开启评价,评价后将直接挂机,是否继续?`, '提示', {
  1544. confirmButtonText: '确认',
  1545. cancelButtonText: '取消',
  1546. type: 'warning',
  1547. draggable: true,
  1548. cancelButtonClass: 'default-button',
  1549. autofocus: false,
  1550. })
  1551. .then(() => {
  1552. const objMsg = {
  1553. Action: 'ReqSatisfaction',
  1554. Param: {
  1555. Extension: m_strUserNo.value,
  1556. },
  1557. };
  1558. // 发送请求
  1559. e_TelSendMsg(objMsg);
  1560. state.loading = false;
  1561. console.log(`${getNowDateTime()}评价消息:${JSON.stringify(objMsg)}`);
  1562. // 结束挂机
  1563. useTimeoutFn(() => {
  1564. sendHangup();
  1565. }, 300);
  1566. })
  1567. .catch(() => {
  1568. state.loading = false;
  1569. });
  1570. };
  1571. /*
  1572. * 监听
  1573. */
  1574. const reqMonListen = (strTargetNum) => {
  1575. const objMsg = {
  1576. Action: 'ReqMonListen',
  1577. Param: {
  1578. Extension: m_strUserNo.value,
  1579. TargetExtension: strTargetNum,
  1580. },
  1581. };
  1582. // 发送请求
  1583. e_TelSendMsg(objMsg);
  1584. };
  1585. /*
  1586. * 监听返回
  1587. */
  1588. const retResMonListen = (data) => {
  1589. if (data.Param.Result == '0') {
  1590. m_IsMonListen.value = '1';
  1591. // 成功
  1592. } else {
  1593. m_IsMonListen.value = '2';
  1594. }
  1595. };
  1596. /*
  1597. * 取消监听
  1598. */
  1599. const reqStopListen = (strTargetNum) => {
  1600. const objMsg = {
  1601. Action: 'ReqStopListen',
  1602. Param: {
  1603. Extension: m_strUserNo.value,
  1604. TargetExtension: strTargetNum,
  1605. },
  1606. };
  1607. // 发送请求
  1608. e_TelSendMsg(objMsg);
  1609. };
  1610. /*
  1611. * 取消监听返回
  1612. */
  1613. const retResStopListen = (data) => {
  1614. if (data.Param.Result == '0') {
  1615. m_IsMonListen.value = '1';
  1616. // 成功
  1617. } else {
  1618. m_IsMonListen.value = '2';
  1619. }
  1620. };
  1621. /*
  1622. * 呼入(振铃)事件
  1623. * EvtCallAlerting - 事件名称
  1624. * Caller:主叫
  1625. * Called:被叫
  1626. * Customerid:客户ID
  1627. * Callid:呼叫ID
  1628. {“Event”:”EvtCallAlerting”,”Param”:{“Caller”:”123”,”Called”:”456”,”Customerid”:”675”,”Callid”:”94593939”}}
  1629. */
  1630. const router = useRouter();
  1631. const evtCallAlerting = (data) => {
  1632. let strCalledNum;
  1633. let strTelNumber;
  1634. console.log(
  1635. `开始振铃,是否呼出:${m_IsCallOut.value},是否呼出弹屏:${m_CallOutOpen.value},是否弹屏:${m_bIsOpen.value},弹屏方式:${
  1636. m_strOpenFlag.value === '1' ? '接通弹屏' : '振铃弹屏'
  1637. }`
  1638. );
  1639. if (m_IsCallOut.value) {
  1640. // 呼出
  1641. // 呼出振铃
  1642. m_strTelState.value = '302';
  1643. e_TopStateChange(m_strTelState.value);
  1644. if (m_CallOutOpen.value) {
  1645. // 呼出是否弹屏(用于未接统计“回拨”业务处理)
  1646. // 主叫号码
  1647. strTelNumber = data.Param.Caller;
  1648. // 被叫号码
  1649. strCalledNum = data.Param.Called;
  1650. // 呼叫ID
  1651. callId.value = data.Param.Callid;
  1652. if (!m_bIsOpen.value && m_strOpenFlag.value == '2') {
  1653. // 振铃呼出弹屏
  1654. console.log(
  1655. '呼出是否弹屏[' +
  1656. m_bIsOpen.value +
  1657. '];弹屏方式[' +
  1658. m_strOpenFlag.value +
  1659. '];记录ID[' +
  1660. callId.value +
  1661. '];主叫号码[' +
  1662. strTelNumber +
  1663. '];被叫号码[' +
  1664. strCalledNum +
  1665. ']'
  1666. );
  1667. m_bIsOpen.value = true;
  1668. // 呼出不再弹单
  1669. m_CallOutOpen.value = false;
  1670. // 去电弹屏
  1671. router.push({
  1672. name: 'orderAccept',
  1673. query: {
  1674. createBy: 'tel',
  1675. fromTel: strTelNumber, // 来电号码
  1676. callId: callId.value, // 通话ID
  1677. transfer: strCalledNum, // 转接来源(如12345,12333)
  1678. telArea: '',
  1679. identityType: '', // 按键接收(1:市民 2:企业 3:智能应答)
  1680. },
  1681. });
  1682. }
  1683. }
  1684. } else {
  1685. // 呼入振铃该状态为呼入
  1686. m_IsCallIn.value = true;
  1687. // 呼入振铃
  1688. m_strTelState.value = '300';
  1689. e_TopStateChange(m_strTelState.value);
  1690. // 主叫号码
  1691. strTelNumber = data.Param.Caller;
  1692. // 被叫号码
  1693. strCalledNum = data.Param.Called;
  1694. // 呼叫ID
  1695. callId.value = data.Param.Callid;
  1696. if (strTelNumber.length == strCalledNum.length && strTelNumber.length == 4) {
  1697. // 如果主叫号码、被叫号码都是分机号码,则不弹屏
  1698. m_bIsOpen.value = true;
  1699. }
  1700. if (!m_bIsOpen.value && m_strOpenFlag.value === '2') {
  1701. m_bIsOpen.value = true;
  1702. // 用户按键
  1703. const strDigit = data.Param.Digit;
  1704. console.log(
  1705. '用户按键[' + strDigit + '];记录ID[' + callId.value + '];主叫号码[' + strTelNumber + '];被叫号码[' + strCalledNum + ']',
  1706. '来电弹屏'
  1707. );
  1708. router.push({
  1709. name: 'orderAccept',
  1710. query: {
  1711. createBy: 'tel',
  1712. fromTel: strTelNumber, // 来电号码
  1713. callId: callId.value, // 通话ID
  1714. transfer: strCalledNum, // 转接来源(如12345,12333)
  1715. telArea: '',
  1716. identityType: strDigit, // 按键接收(1:市民 2:企业 3:智能应答)
  1717. },
  1718. });
  1719. }
  1720. }
  1721. };
  1722. /*
  1723. * 应答事件
  1724. * Caller:主叫
  1725. * Called:被叫
  1726. * Callid:呼叫ID
  1727. {“Event”:”EvtCallAnswer”,”Param”:{“Caller”:”123”,”Called”:”456”,”Callid”:”94593939”}}
  1728. */
  1729. const talkTime = ref(0); // 通话时长
  1730. const talkTimer = useIntervalFn(
  1731. () => {
  1732. talkTime.value += 1;
  1733. Local.set('talkTime', String(talkTime.value));
  1734. },
  1735. 1000,
  1736. { immediate: false }
  1737. );
  1738. // 开始通话计时
  1739. const startTalkTimer = () => {
  1740. let localTalkTime = Local.get('talkTime');
  1741. if (talkTime.value) {
  1742. talkTime.value = Number(localTalkTime);
  1743. }
  1744. talkTimer.resume();
  1745. stopIdleTime();
  1746. };
  1747. // 结束通话计时
  1748. const stopTalkTimer = () => {
  1749. talkTimer.pause();
  1750. talkTime.value = 0;
  1751. Local.remove('talkTime');
  1752. };
  1753. const evtEvtCallAnswer = (data) => {
  1754. let strCalledNum;
  1755. let strTelNumber;
  1756. // 通话接通
  1757. m_bCallConnect.value = true;
  1758. callCenterIsOnThePhone.value = true;
  1759. console.log(
  1760. `接通电话,是否呼出:${m_IsCallOut.value},是否咨询:${m_IsConsult.value},是否呼出弹屏:${m_CallOutOpen.value},是否弹屏:${
  1761. m_bIsOpen.value
  1762. },弹屏方式:${m_strOpenFlag.value === '1' ? '接通弹屏' : '振铃弹屏'}`
  1763. );
  1764. if (m_IsCallOut.value) {
  1765. // 呼出
  1766. if (!m_IsConsult.value) {
  1767. // 不是转接、三方会议呼出
  1768. // 呼出应答
  1769. m_strTelState.value = '303';
  1770. e_TopStateChange(m_strTelState.value);
  1771. if (m_CallOutOpen.value) {
  1772. // 呼出是否弹屏(用于未接统计“回拨”业务处理)
  1773. // 主叫号码
  1774. strTelNumber = data.Param.Caller;
  1775. // 被叫号码
  1776. strCalledNum = data.Param.Called;
  1777. // 呼叫ID
  1778. callId.value = data.Param.Callid;
  1779. mittBus.emit('outboundConnect', {
  1780. callNumber: strCalledNum, // 被叫号码
  1781. callId: callId.value, //呼叫ID
  1782. }); // 外呼接通之后收到的消息
  1783. if (!m_bIsOpen.value && m_strOpenFlag.value === '1') {
  1784. console.log(
  1785. '呼出是否弹屏[' +
  1786. m_bIsOpen.value +
  1787. '];弹屏方式[' +
  1788. m_strOpenFlag.value +
  1789. '];记录ID[' +
  1790. callId.value +
  1791. '];主叫号码[' +
  1792. strTelNumber +
  1793. '];被叫号码[' +
  1794. strCalledNum +
  1795. ']'
  1796. );
  1797. m_bIsOpen.value = true;
  1798. // 呼出不再弹单
  1799. m_CallOutOpen.value = false;
  1800. router.push({
  1801. name: 'orderAccept',
  1802. query: {
  1803. createBy: 'tel',
  1804. fromTel: strTelNumber, // 来电号码
  1805. callId: callId.value, // 通话ID
  1806. transfer: strCalledNum, // 转接来源(如12345,12333)
  1807. telArea: '',
  1808. identityType: '', // 按键接收(1:市民 2:企业 3:智能应答)
  1809. },
  1810. });
  1811. }
  1812. }
  1813. }
  1814. } else {
  1815. // 呼入应答
  1816. m_strTelState.value = '301';
  1817. e_TopStateChange(m_strTelState.value);
  1818. // 呼入应答
  1819. // 主叫号码
  1820. strTelNumber = data.Param.Caller;
  1821. // 被叫号码
  1822. strCalledNum = data.Param.Called;
  1823. // 呼叫ID
  1824. callId.value = data.Param.Callid;
  1825. console.log(
  1826. '是否弹屏[' +
  1827. m_bIsOpen.value +
  1828. '];弹屏方式[' +
  1829. m_strOpenFlag.value +
  1830. '];记录ID[' +
  1831. callId.value +
  1832. '];主叫号码[' +
  1833. strTelNumber +
  1834. '];被叫号码[' +
  1835. strCalledNum +
  1836. ']'
  1837. );
  1838. if (strTelNumber.length == strCalledNum.length && strTelNumber.length == 4) {
  1839. // 如果主叫号码、被叫号码都是分机号码,则不弹屏
  1840. m_bIsOpen.value = true;
  1841. }
  1842. if (!m_bIsOpen.value && m_strOpenFlag.value === '1') {
  1843. m_bIsOpen.value = true;
  1844. // 用户按键
  1845. const strDigit = data.Param.Digit;
  1846. console.log(
  1847. '用户按键' +
  1848. strDigit +
  1849. '是否弹屏[' +
  1850. m_bIsOpen.value +
  1851. '];弹屏方式[' +
  1852. m_strOpenFlag.value +
  1853. '];记录ID[' +
  1854. callId.value +
  1855. '];主叫号码[' +
  1856. strTelNumber +
  1857. '];被叫号码[' +
  1858. strCalledNum +
  1859. ']'
  1860. );
  1861. router.push({
  1862. name: 'orderAccept',
  1863. query: {
  1864. createBy: 'tel',
  1865. fromTel: strTelNumber, // 来电号码
  1866. callId: callId.value, // 通话ID
  1867. transfer: strCalledNum, // 转接来源(如12345,12333)
  1868. telArea: '',
  1869. identityType: strDigit, // 按键接收(1:市民 2:企业 3:智能应答)
  1870. },
  1871. });
  1872. }
  1873. }
  1874. if (!m_IsConsult.value) {
  1875. // 如果不是咨询,则启动通话时间计时器
  1876. startTalkTimer(); // 通话计时器开始
  1877. }
  1878. };
  1879. /*
  1880. * 39.外呼振铃事件
  1881. * Caller:主叫
  1882. * Called:被叫
  1883. * Callid:呼叫ID
  1884. {“Event”:”EvtOutCalling”,”Param”:{“Caller”:”123”,”Called”:”456”,”Customerid”:”675”,”Callid”:”94593939”}}
  1885. */
  1886. const evtEvtCalling = (data) => {
  1887. let strCalledNum;
  1888. let strTelNumber;
  1889. m_IsCallOut.value = true; // 呼出
  1890. console.log(
  1891. `开始振铃,是否呼出:${m_IsCallOut.value},是否呼出弹屏:${m_CallOutOpen.value},是否弹屏:${m_bIsOpen.value},弹屏方式:${
  1892. m_strOpenFlag.value === '1' ? '接通弹屏' : '振铃弹屏'
  1893. }`
  1894. );
  1895. if (m_CallOutOpen.value) {
  1896. // 主叫号码
  1897. strTelNumber = data.Param.Caller;
  1898. // 被叫号码
  1899. strCalledNum = data.Param.Called;
  1900. // 呼叫ID
  1901. callId.value = data.Param.Callid;
  1902. if (!m_bIsOpen.value && m_strOpenFlag.value === '2') {
  1903. //振铃弹屏
  1904. m_bIsOpen.value = true;
  1905. console.log(
  1906. '呼出是否弹屏[' +
  1907. m_bIsOpen.value +
  1908. '];弹屏方式[' +
  1909. m_strOpenFlag.value +
  1910. '];记录ID[' +
  1911. callId.value +
  1912. '];主叫号码[' +
  1913. strTelNumber +
  1914. '];被叫号码[' +
  1915. strCalledNum +
  1916. ']'
  1917. );
  1918. router.push({
  1919. name: 'orderAccept',
  1920. query: {
  1921. createBy: 'tel',
  1922. fromTel: strTelNumber, // 来电号码
  1923. callId: callId.value, // 通话ID
  1924. transfer: strCalledNum, // 转接来源(如12345,12333)
  1925. telArea: '',
  1926. identityType: '', // 按键接收(1:市民 2:企业 3:智能应答)
  1927. },
  1928. });
  1929. }
  1930. }
  1931. };
  1932. /*
  1933. * 计算接通时长
  1934. */
  1935. // 是否在队列状态
  1936. const m_bIsQueue = ref(false);
  1937. /*
  1938. * 队列等待
  1939. */
  1940. const i_QueueNum = (data) => {
  1941. // 40,87098300,85961020,1,80|40,87098300,85961020,1,80|40,87098300,85961020,1,80
  1942. // 处于队列状态
  1943. m_bIsQueue.value = true;
  1944. // 队列信息
  1945. const strInfo = data.Param.Info;
  1946. if (undefined != strInfo && '' != strInfo) {
  1947. const arrFirst = strInfo.split('|');
  1948. if (null != arrFirst && 0 < arrFirst.length) {
  1949. // 设置队列等待数量
  1950. console.log(arrFirst.length - 1);
  1951. e_SetQueryWait(arrFirst.length - 1, strInfo);
  1952. }
  1953. }
  1954. console.log(`${getNowDateTime()}:队列消息:`, data.Param, strInfo);
  1955. };
  1956. /*
  1957. 语音识别结果保存
  1958. */
  1959. const e_EvtRecognize = (data) => {
  1960. console.log('EvtRecognize' + JSON.stringify(data));
  1961. const strDirection = data.Direciton || '';
  1962. const strResult = data.Result || '';
  1963. console.log(`${getNowDateTime()}:识别结果`, strResult, strDirection);
  1964. };
  1965. /*
  1966. 转三方接通状态
  1967. */
  1968. const e_EvtDispatchState = (data: any) => {
  1969. const strState = data.State || '';
  1970. console.log(`${getNowDateTime()}:转接三方状态-EvtDispatchState:`, data, `是否咨询状态:${m_IsConsult.value},当前str:${strState}`);
  1971. if (strState === '2') {
  1972. if (m_IsConsult.value) {
  1973. // 咨询成功
  1974. m_strTelState.value = '330';
  1975. e_TopStateChange(m_strTelState.value);
  1976. }
  1977. } else if (strState === '3') {
  1978. if (m_IsConsult.value) {
  1979. m_strTelState.value = '301';
  1980. m_IsHold.value = false; // 三方有一方挂断 取消保持
  1981. e_TopStateChange(m_strTelState.value);
  1982. }
  1983. } else {
  1984. //alert("转接失败");
  1985. }
  1986. };
  1987. /**
  1988. * 异常处理
  1989. * @param {any} data
  1990. */
  1991. const retResError = (data: any) => {
  1992. if (data.Param.Result == '99') {
  1993. // 掉线
  1994. m_strTelState.value = '0';
  1995. e_TopStateChange(m_strTelState.value);
  1996. ElMessage.error('连接已断开');
  1997. // 自动签入
  1998. onSignIn();
  1999. }
  2000. };
  2001. // 改变状态方法
  2002. const e_TopStateChange = (state: string) => {
  2003. console.log(`${getNowDateTime()}:状态改变:`, state);
  2004. switch (state) {
  2005. case '0': // 签出
  2006. break;
  2007. case '100': // 登录成功
  2008. break;
  2009. case '200': // 空闲
  2010. break;
  2011. case '201': // 示忙
  2012. break;
  2013. case '300': //呼入振铃
  2014. break;
  2015. case '301': // 呼入通话
  2016. stopConferenceTime();
  2017. break;
  2018. case '302': // 呼出振铃
  2019. break;
  2020. case '303': // 呼出通话
  2021. stopConferenceTime();
  2022. break;
  2023. case '310': // 通话保持
  2024. break;
  2025. case '320': // 三方会议
  2026. startConferenceTime(); // 三方会议时长开始
  2027. break;
  2028. case '330': // 转接
  2029. break;
  2030. case '331': // 转接
  2031. break;
  2032. case '900': // 整理
  2033. break;
  2034. }
  2035. // console.log(state);
  2036. };
  2037. /**
  2038. * 更新话机动作
  2039. * 1:登录登出;2:示忙示闲;3:摘机
  2040. */
  2041. const e_ActionUpdate = (strActionType: string) => {
  2042. const data = {
  2043. Action: 'ActionUpdate',
  2044. ActType: strActionType,
  2045. };
  2046. };
  2047. /*
  2048. * 用户分机签入签出(用户分机分离业务)
  2049. * 业务改造,先签入我方业务系统,再签入呼叫中心
  2050. *
  2051. */
  2052. const e_TelSignIn = async (telNo: string, groupId: string) => {
  2053. const data = {
  2054. telNo,
  2055. groupId,
  2056. telModelState: 1,
  2057. };
  2058. try {
  2059. const { result } = await callCenterSignIn(data);
  2060. console.log(`${getNowDateTime()}:业务系统:签入成功`, result);
  2061. } catch (e) {
  2062. console.log(e);
  2063. sendSignOut(); // 签出
  2064. }
  2065. };
  2066. /*
  2067. * 用户分机签出(用户分机分离业务)
  2068. */
  2069. const e_TelSignOut = async (telNo: string, groupId: string) => {
  2070. const data = {
  2071. telNo,
  2072. groupId,
  2073. telModelState: 1,
  2074. };
  2075. try {
  2076. await callCenterSignOut(data);
  2077. console.log(`${getNowDateTime()}:业务系统:签出成功`);
  2078. } catch (e) {
  2079. console.log(e);
  2080. }
  2081. };
  2082. /**
  2083. * 保存队列信息
  2084. * @param {any} strUserNum
  2085. * @param strQueueInfo
  2086. */
  2087. const e_SetQueryWait = (strUserNum, strQueueInfo) => {
  2088. const data = {
  2089. Action: 'SaveQueueNumNew',
  2090. QueueNum: strUserNum,
  2091. QueueInfo: strQueueInfo,
  2092. };
  2093. };
  2094. // 获取基础信息
  2095. const telsList = ref([]);
  2096. const telsListGroup = ref([]);
  2097. const getBaseInfo = async () => {
  2098. try {
  2099. const [tels, telGroup] = await Promise.all([getCallCenterList(), getCallCenterGroupList()]);
  2100. telsList.value = tels?.result ?? [];
  2101. telsListGroup.value = telGroup?.result ?? [];
  2102. } catch (e) {
  2103. console.log(e);
  2104. }
  2105. };
  2106. // 检查用户状态
  2107. onMounted(async () => {
  2108. await getBaseInfo();
  2109. initWs();
  2110. // 是否在通话中
  2111. window.onbeforeunload = function (e) {
  2112. if (m_bCallConnect.value) {
  2113. const dialogText = '正在通话中,您确定要刷新吗?';
  2114. e.returnValue = dialogText;
  2115. return dialogText;
  2116. }
  2117. };
  2118. });
  2119. </script>
  2120. <style scoped lang="scss">
  2121. .seizeSeat-box {
  2122. display: none;
  2123. }
  2124. .phoneControls {
  2125. display: flex;
  2126. flex: 1;
  2127. background-color: var(--el-color-white) !important;
  2128. padding: 0 10px;
  2129. color: var(--hotline-color-text-main);
  2130. height: 100%;
  2131. align-items: center;
  2132. .status-box {
  2133. width: 240px;
  2134. display: flex;
  2135. align-items: center;
  2136. justify-content: space-between;
  2137. background: var(--el-color-info-light-7);
  2138. position: relative;
  2139. box-sizing: border-box;
  2140. cursor: pointer;
  2141. text-align: left;
  2142. font-size: 14px;
  2143. padding: 4px 26px 4px 12px;
  2144. gap: 6px;
  2145. min-height: 32px;
  2146. line-height: 24px;
  2147. border-radius: var(--el-border-radius-round);
  2148. background-color: var(--el-color-info-light-8);
  2149. transition: var(--el-transition-duration);
  2150. .arrow {
  2151. position: absolute;
  2152. right: 10px;
  2153. transition: transform var(--el-transition-duration);
  2154. }
  2155. .is-reverse {
  2156. transform: rotate(180deg);
  2157. }
  2158. }
  2159. // 按钮列表
  2160. .btn-container {
  2161. display: flex;
  2162. justify-content: space-between;
  2163. width: calc(100% - 100px);
  2164. height: 100%;
  2165. border-right: 1px solid var(--el-border-color);
  2166. border-left: 1px solid var(--el-border-color);
  2167. overflow: hidden;
  2168. .item {
  2169. text-align: center;
  2170. cursor: pointer;
  2171. width: 100%;
  2172. user-select: none;
  2173. display: flex;
  2174. align-items: center;
  2175. justify-content: center;
  2176. //border-right: 1px solid var(--el-border-color);
  2177. .icon {
  2178. color: var(--el-color-primary);
  2179. }
  2180. &.disabled {
  2181. cursor: not-allowed;
  2182. overflow: hidden;
  2183. .icon {
  2184. color: #cccccc;
  2185. }
  2186. }
  2187. }
  2188. .active {
  2189. &:hover {
  2190. color: var(--hotline-color-white);
  2191. background-color: var(--el-color-primary);
  2192. .icon {
  2193. color: var(--hotline-color-white);
  2194. }
  2195. }
  2196. }
  2197. }
  2198. }
  2199. </style>