zgTel.vue 76 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744
  1. <template>
  2. <div class="phoneControls">
  3. <el-popover :width="240" trigger="hover" v-model:visible="showPop">
  4. <template #reference>
  5. <div class="mr15 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') && !isRestAudit">
  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-if="m_bLogin && isRestAudit">
  90. <div class="item disabled" title="小休审核中">
  91. <SvgIcon name="iconfont icon-rest" class="icon mr3" size="16px" />
  92. 审核中
  93. </div>
  94. </template>
  95. <!-- 灰色小休不可用 -->
  96. <template v-else>
  97. <div class="item disabled" title="小休"><SvgIcon name="iconfont icon-rest" class="icon mr3" size="18px" />小休</div>
  98. </template>
  99. <!-- 保持和取消保持 可用 -->
  100. <template v-if="m_bLogin && activeArr.includes('hold')">
  101. <div class="item active" :title="m_IsHold ? '恢复' : '保持'" @click="onEvent(m_IsHold ? 'reHold' : 'hold')">
  102. <SvgIcon name="iconfont icon-hold" class="icon mr3" size="16px" />
  103. {{ m_IsHold ? '恢复' : '保持' }}
  104. </div>
  105. </template>
  106. <!-- 灰色保持不可用 -->
  107. <template v-else>
  108. <div class="item disabled" title="保持"><SvgIcon name="iconfont icon-hold" class="icon mr3" size="16px" />保持</div>
  109. </template>
  110. <!-- 咨询 可用 -->
  111. <template v-if="m_bLogin && activeArr.includes('consult')">
  112. <div class="item active" title="咨询" @click="onEvent('consult')">
  113. <SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />
  114. 咨询
  115. </div>
  116. </template>
  117. <!-- 灰色咨询不可用 -->
  118. <template v-else>
  119. <div class="item disabled" title="咨询"><SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />咨询</div>
  120. </template>
  121. <!-- 盲转可用 -->
  122. <template v-if="m_bLogin && activeArr.includes('transferMz')">
  123. <div class="item active" title="盲转" @click="onEvent('transferMz')">
  124. <SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />
  125. 盲转
  126. </div>
  127. </template>
  128. <!-- 灰色盲转不可用 -->
  129. <template v-else>
  130. <div class="item disabled" title="盲转"><SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />盲转</div>
  131. </template>
  132. <!-- 转接可用 -->
  133. <template v-if="m_bLogin && activeArr.includes('transfer')">
  134. <div class="item active" title="转接" @click="onEvent('transfer')">
  135. <SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />
  136. 转接
  137. </div>
  138. </template>
  139. <!-- 灰色转接不可用 -->
  140. <template v-else>
  141. <div class="item disabled" title="转接"><SvgIcon name="iconfont icon-transfer" class="icon mr3" size="16px" />转接</div>
  142. </template>
  143. <!-- 整理和取消整理 可用 -->
  144. <template v-if="m_bLogin && activeArr.includes('talkingDeal')">
  145. <div class="item active" :title="m_IsTalkingDeal ? '取消整理' : '整理'" @click="onEvent(m_IsTalkingDeal ? 'cancelTalkDeal' : '')">
  146. <SvgIcon name="iconfont icon-talkingDeal" class="icon mr3" size="16px" />
  147. {{ m_IsTalkingDeal ? '取消整理' : '整理' }}
  148. </div>
  149. </template>
  150. <!-- 灰色整理不可用 -->
  151. <template v-else>
  152. <div class="item disabled" title="整理"><SvgIcon name="iconfont icon-talkingDeal" class="icon mr3" size="16px" />整理</div>
  153. </template>
  154. <!-- 三方会议可用 -->
  155. <template v-if="m_bLogin && activeArr.includes('conference')">
  156. <div class="item active" title="三方会议" @click="onEvent('conference')">
  157. <SvgIcon name="iconfont icon-conference" class="icon mr3" size="16px" />
  158. 三方会议
  159. </div>
  160. </template>
  161. <!-- 三方会议不可用 -->
  162. <template v-else>
  163. <div class="item disabled" title="三方会议"><SvgIcon name="iconfont icon-conference" class="icon mr3" size="16px" />三方会议</div>
  164. </template>
  165. <!-- &lt;!&ndash; 评价可用 登录并且是呼入 &ndash;&gt;
  166. <template v-if="m_bLogin && m_IsCallIn && activeArr.includes('evaluate')">
  167. <div class="item active" title="评价" @click="onEvent('evaluate')">
  168. <SvgIcon name="ele-Flag" class="icon mr3" size="16px" />
  169. 评价
  170. </div>
  171. </template>
  172. &lt;!&ndash; 评价不可用 &ndash;&gt;
  173. <template v-else>
  174. <div class="item disabled" title="评价"><SvgIcon name="ele-Flag" class="icon mr3" size="16px" />评价</div>
  175. </template>-->
  176. </div>
  177. <!-- 等待人数 -->
  178. <div class="wait-box">
  179. <!-- <div class="today-wait">
  180. <span class="today-wait-label">今日等待:</span
  181. ><el-text class="today-wait-num ml5" tag="b" type="danger">{{ todayWaitValue.toFixed(0) }}</el-text>
  182. </div>-->
  183. <div class="current-wait">
  184. <span class="current-wait-label">当前等待:</span
  185. ><el-text class="current-wait-time ml5" tag="b" type="danger">{{ currentWaitValue.toFixed(0) }}</el-text>
  186. </div>
  187. </div>
  188. </div>
  189. <!-- 签入弹窗 -->
  190. <el-dialog v-model="state.dutyDialogVisible" draggable title="签入" width="500px" :show-close="false" @close="dutyFormClose">
  191. <el-form :model="state.dutyForm" label-width="80px" ref="dutyFormRef" @submit.native.prevent>
  192. <el-form-item label="分机号" prop="telNo" :rules="[{ required: true, message: '请选择分机号', trigger: 'change' }]">
  193. <el-select-v2
  194. v-model="state.dutyForm.telNo"
  195. :options="telsList"
  196. placeholder="请选择分机号"
  197. filterable
  198. class="w100"
  199. :props="{
  200. label: 'no',
  201. value: 'no',
  202. }"
  203. clearable
  204. />
  205. </el-form-item>
  206. </el-form>
  207. <template #footer>
  208. <span class="dialog-footer">
  209. <el-button @click="state.dutyDialogVisible = false" class="default-button" :loading="state.loading">取 消</el-button>
  210. <el-button type="primary" @click="clickOnDuty(dutyFormRef)" :loading="state.loading">确 定</el-button>
  211. </span>
  212. </template>
  213. </el-dialog>
  214. <!-- 呼出弹窗 -->
  215. <el-dialog v-model="state.outboundDialogVisible" draggable title="呼叫" width="450px" @close="outboundFormClose">
  216. <el-form :model="state.outboundForm" label-width="80px" ref="outboundFormRef" @submit.native.prevent>
  217. <el-form-item label="呼叫号码" prop="telNo" :rules="[{ required: true, message: '请选择呼叫号码或填写外部电话', trigger: 'blur' }]">
  218. <el-select-v2
  219. v-model="state.outboundForm.telNo"
  220. :options="threeWayAndTransfer"
  221. placeholder="请选择呼叫号码或填写外部电话"
  222. filterable
  223. class="w100"
  224. :props="{
  225. label: 'dicDataName',
  226. value: 'dicDataValue',
  227. }"
  228. clearable
  229. allow-create
  230. default-first-option
  231. :height="500"
  232. />
  233. </el-form-item>
  234. </el-form>
  235. <template #footer>
  236. <span class="dialog-footer">
  237. <el-button @click="state.outboundDialogVisible = false" class="default-button" :loading="state.loading">取 消</el-button>
  238. <el-button type="primary" @click="clickOnOutbound(outboundFormRef)" :loading="state.loading">确 定</el-button>
  239. </span>
  240. </template>
  241. </el-dialog>
  242. <!-- 咨询弹窗 -->
  243. <el-dialog v-model="state.consultDialogVisible" draggable title="咨询" width="450px" @close="consultFormClose">
  244. <el-form :model="state.consultForm" label-width="80px" ref="consultFormRef" @submit.native.prevent>
  245. <el-form-item label="咨询类型" prop="strType" :rules="[{ required: false, message: '请选择咨询类型', trigger: 'blur' }]">
  246. <el-radio-group v-model="state.consultForm.strType">
  247. <el-radio value="0">内线</el-radio>
  248. <el-radio value="1">外线</el-radio>
  249. <el-radio value="2">群组</el-radio>
  250. </el-radio-group>
  251. </el-form-item>
  252. <el-form-item label="咨询" prop="telNo" :rules="[{ required: true, message: '请填写咨询号码', trigger: 'blur' }]">
  253. <!-- <el-input v-model="state.consultForm.telNo" placeholder="咨询号码" @keyup.enter="clickOnConsult(consultFormRef)" clearable />-->
  254. <el-select-v2
  255. v-model="state.consultForm.telNo"
  256. :options="threeWayAndTransfer"
  257. placeholder="请选择咨询号码或填写外部电话"
  258. filterable
  259. class="w100"
  260. :props="{
  261. label: 'dicDataName',
  262. value: 'dicDataValue',
  263. }"
  264. clearable
  265. allow-create
  266. default-first-option
  267. :height="500"
  268. />
  269. </el-form-item>
  270. </el-form>
  271. <template #footer>
  272. <span class="dialog-footer">
  273. <el-button @click="state.consultDialogVisible = false" class="default-button" :loading="state.loading">取 消</el-button>
  274. <el-button type="primary" @click="clickOnConsult(consultFormRef)" :loading="state.loading">确 定</el-button>
  275. </span>
  276. </template>
  277. </el-dialog>
  278. <!-- 盲转 -->
  279. <el-dialog v-model="state.blindDialogVisible" draggable title="盲转" width="450px" @close="blindFormClose">
  280. <el-form :model="state.blindForm" label-width="80px" ref="blindFormRef" @submit.native.prevent>
  281. <el-form-item label="盲转" prop="telNo" :rules="[{ required: true, message: '请填写盲转号码', trigger: 'blur' }]">
  282. <!-- <el-input v-model="state.blindForm.telNo" placeholder="盲转号码" @keyup.enter="clickOnBlind(blindFormRef)" clearable />-->
  283. <el-select-v2
  284. v-model="state.blindForm.telNo"
  285. :options="threeWayAndTransfer"
  286. placeholder="请选择盲转号码或填写外部电话"
  287. filterable
  288. class="w100"
  289. :props="{
  290. label: 'dicDataName',
  291. value: 'dicDataValue',
  292. }"
  293. clearable
  294. allow-create
  295. default-first-option
  296. :height="500"
  297. />
  298. </el-form-item>
  299. </el-form>
  300. <template #footer>
  301. <span class="dialog-footer">
  302. <el-button @click="state.blindDialogVisible = false" class="default-button" :loading="state.loading">取 消</el-button>
  303. <el-button type="primary" @click="clickOnBlind(blindFormRef)" :loading="state.loading">确 定</el-button>
  304. </span>
  305. </template>
  306. </el-dialog>
  307. <!-- 小休弹窗 -->
  308. <el-dialog v-model="state.restDialogVisible" draggable title="小休" width="450px" @close="restFormOpened">
  309. <el-form :model="state.restForm" label-width="80px" ref="restFormRef" @submit.native.prevent>
  310. <el-form-item label="小休" prop="reason" :rules="[{ required: true, message: '请选择小休原因', trigger: 'change' }]">
  311. <el-select v-model="state.restForm.reason" placeholder="请选择小休原因" class="w100" clearable>
  312. <el-option v-for="item in state.restReasonOptions" :key="item.dicDataValue" :label="item.dicDataName" :value="item.dicDataValue" />
  313. </el-select>
  314. <!-- <el-text type="danger">注意:小休审批通过,状态才会变成小休</el-text>-->
  315. </el-form-item>
  316. </el-form>
  317. <template #footer>
  318. <span class="dialog-footer">
  319. <el-button @click="state.restDialogVisible = false" class="default-button" :loading="state.loading">取 消</el-button>
  320. <el-button type="primary" @click="clickOnRest(restFormRef)" :loading="state.loading">申 请</el-button>
  321. </span>
  322. </template>
  323. </el-dialog>
  324. <!-- 占位标签 -->
  325. <div class="seizeSeat-box"></div>
  326. </template>
  327. <script setup lang="ts" name="zgTelControl">
  328. import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
  329. import { getNowDateTime } from '@/utils/constants';
  330. import { ElMessage, ElMessageBox, ElNotification, FormInstance } from 'element-plus';
  331. import { useRouter } from 'vue-router';
  332. import { useWebSocket } from '@/hooks/useWebsocket';
  333. import { useIntervalFn } from '@vueuse/shared';
  334. import { formatDuration } from '@/utils/formatTime';
  335. import { Local } from '@/utils/storage';
  336. import { useAppConfig } from '@/stores/appConfig';
  337. import { storeToRefs } from 'pinia';
  338. import { useUserInfo } from '@/stores/userInfo';
  339. import mittBus from '@/utils/mitt';
  340. import { callCenterSignIn, callCenterSignOut, getCallCenterList, getCallCenterStatus } from '@/api/callCenter';
  341. import { useTimeoutFn } from '@vueuse/shared/index';
  342. import { useGlobalState } from '@/utils/callCenter';
  343. import { useThemeConfig } from '@/stores/themeConfig';
  344. import { getDataByCode } from '@/api/system/dict';
  345. import { trimCompat } from '@/utils/tools';
  346. import XEUtils from 'xe-utils';
  347. import { submitLog } from '@/api/public/log';
  348. import { useTransition } from '@vueuse/core';
  349. import { busyOff, busyOn, telRestBaseData } from '@/api/public/wex';
  350. import { getSpecialNumberDetailByPhone } from '@/api/auxiliary/specialNumber';
  351. import signalR from '@/utils/signalR';
  352. const globalState = useGlobalState(); // 全局变量
  353. const state = reactive<any>({
  354. dutyDialogVisible: false,
  355. loading: false,
  356. dutyForm: {
  357. telNo: null,
  358. skillId: null,
  359. },
  360. outboundDialogVisible: false,
  361. outboundForm: {
  362. telNo: null,
  363. },
  364. consultDialogVisible: false,
  365. consultForm: {
  366. telNo: null,
  367. strType: '0',
  368. },
  369. blindDialogVisible: false,
  370. blindForm: {
  371. telNo: null,
  372. },
  373. restDialogVisible: false,
  374. restForm: {
  375. reason: null,
  376. },
  377. restReasonOptions: [],
  378. });
  379. /*
  380. * @description: 置当前可用的按钮
  381. * 电话控件在头部展示时切换;
  382. * 状态改变
  383. * 0-未登录;100-登录成功;
  384. * 200-空闲;201-繁忙;
  385. * 300-呼入振铃;301-呼入通话;302-呼出振铃;303-呼出通话;310-保持通话;320-会议;330-咨询;333-转接接通
  386. * 510-监听;520-插话;
  387. */
  388. const activeArr = computed(() => {
  389. const switchCases: any = {
  390. '0': ['dutyOn'], // 签出状态
  391. '100': [], // 登录成功
  392. '200': ['dutyOff', 'rest', 'outbound'], // 空闲
  393. '201': ['dutyOff', 'rest', 'outbound'], // 小休
  394. '202': ['dutyOff'], // 小休审批中
  395. '300': ['hangup'], // 呼入振铃
  396. '301': ['hangup', 'hold', 'consult', 'transferMz', 'evaluate'], // 呼入通话
  397. '302': ['hangup'], // 呼出振铃
  398. '303': ['hangup', 'hold', 'consult', 'transferMz', 'evaluate'], // 呼出通话
  399. '310': ['hangup', 'hold'], // 通话保持
  400. '320': ['hangup', 'evaluate'], // 三方会议中
  401. '330': ['hangup', 'hold', 'transfer', 'conference', 'evaluate'], // 转接 咨询
  402. '331': ['hangup', 'hold', 'evaluate'], // 咨询 转接
  403. '900': ['dutyOff', 'talkingDeal'], // 整理
  404. };
  405. let arr = <EmptyArrayType>[];
  406. if (m_strTelState.value in switchCases) {
  407. arr = switchCases[m_strTelState.value];
  408. }
  409. return arr;
  410. });
  411. // 当前对应code展示的中文
  412. const currentStatusText = computed(() => {
  413. const statusMap: any = {
  414. '0': '签出',
  415. '100': '登录成功',
  416. '200': '空闲',
  417. '201': '小休',
  418. '202': '小休审批中',
  419. '300': '呼入振铃',
  420. '301': '呼入通话',
  421. '302': '呼出振铃',
  422. '303': '呼出通话',
  423. '310': '通话保持',
  424. '320': '三方会议',
  425. '330': '咨询',
  426. '331': '咨询',
  427. '900': '整理',
  428. };
  429. return statusMap[m_strTelState.value] || '';
  430. });
  431. // ws实例对象
  432. const wsRef = ref();
  433. const storesThemeConfig = useThemeConfig();
  434. const { themeConfig } = storeToRefs(storesThemeConfig);
  435. const initWs = () => {
  436. wsRef.value = useWebSocket(themeConfig.value.callCenterSocketUrl, {
  437. heartbeat: {
  438. message: JSON.stringify({ Action: 'ReqHealthCheck', Param: { Extension: m_strUserNo.value } }),
  439. interval: 5000,
  440. pongTimeout: 5000,
  441. },
  442. autoReconnect: {
  443. delay: 2000,
  444. }, // 自动重连
  445. immediate: false, // 是否立即链接
  446. onMessage: e_TelMsgReceive, // 消息接收
  447. onError: e_websocketError, // 错误
  448. onDisconnected: e_websocketClose, // 断开
  449. onConnected: e_websocketOpen, // 链接成功
  450. });
  451. };
  452. // 发送消息
  453. const e_TelSendMsg = (strObj: Object) => {
  454. // 客户端当前时间
  455. const strMsg = JSON.stringify(strObj);
  456. console.log(`${getNowDateTime()} 发送消息:`, strMsg, wsRef.value.status);
  457. if (wsRef.value.ws?.readyState === 1) {
  458. // 已经链接并且可以通讯,则发放文本消息
  459. wsRef.value.send(strMsg);
  460. } else {
  461. ElMessage.error('请先签入');
  462. state.loading = false;
  463. }
  464. };
  465. const appConfigStore = useAppConfig();
  466. const { AppConfigInfo } = storeToRefs(appConfigStore); // 系统配置信息
  467. const storesUserInfo = useUserInfo();
  468. const { userInfos } = storeToRefs(storesUserInfo); // 用户信息
  469. const m_strUserNo = ref<string | null>(''); // 分机号码
  470. const m_strUserName = computed(() => {
  471. return userInfos.value.name;
  472. }); // 坐席名称
  473. const m_strJobNum = ref<string | null>(''); // 坐席工号
  474. const m_strSkillId = ref(''); // 技能组
  475. const m_strLevel = ref('1'); // 优先级别
  476. const m_strGroup = ref('1'); // 分组ID
  477. const m_strCompanyId = ref(''); // 企业编码
  478. const m_bLogin = ref(false); // 登录状态
  479. const m_bTelBusy = ref(false); // 是否小休中
  480. const m_strIsMonitor = ref('0'); // 是否监控分机 1-是监控分机
  481. const callId = ref(''); // 通话ID
  482. const m_IsCallOut = ref(false); // 是否呼出
  483. const m_IsCallIn = ref(false); // 是否是呼入
  484. const m_strOpenFlag = ref('2'); // 来电弹屏方式 1-接通弹屏;2-振铃弹屏
  485. const m_CallOutOpen = ref(false); // 呼出是否弹屏(用于未接统计“回拨”业务处理)
  486. const m_bIsOpen = ref(false); // 是否已经弹屏
  487. const m_bCallConnect = ref(false); // 是否在通话状态
  488. const m_IsConsult = ref(false); // 是否咨询
  489. const m_strConsultType = ref('-1'); // 咨询类型
  490. const m_IsHangup = ref(false); // 是否挂机
  491. const m_IsHold = ref(false); // 是否保持
  492. const m_IsTalkingDeal = ref(false); // 是否通话整理
  493. const m_IsMonListen = ref('0'); // 监控状态 0-未监听;1-监控成功;2-监控失败;
  494. const m_strTelState = ref('0'); // 当前状态
  495. m_strOpenFlag.value = XEUtils.toValueString(AppConfigInfo.value.callInOpenType)?.trim(); // 转换为字符串
  496. const showPop = ref(false);
  497. // 点击事件
  498. const onEvent = (event: string) => {
  499. switch (event) {
  500. case 'signIn': // 签入
  501. onSignIn();
  502. break;
  503. case 'signOut': // 签出
  504. onSignOut();
  505. break;
  506. case 'busy': // 小休
  507. onBusy();
  508. break;
  509. case 'idle': // 示闲
  510. onIdle();
  511. break;
  512. case 'cancelTalkDeal': // 取消整理
  513. onCancelTalkDeal();
  514. break;
  515. case 'answer': // 应答
  516. break;
  517. case 'hangup': // 挂机
  518. onHangup();
  519. break;
  520. case 'callOut': // 外呼
  521. onCallOut();
  522. break;
  523. case 'hold': // 保持
  524. onHold();
  525. break;
  526. case 'reHold': // 恢复
  527. onReHold();
  528. break;
  529. // 咨询
  530. case 'consult':
  531. e_TopStateChange('331');
  532. onConsultOpen();
  533. break;
  534. // 转接
  535. case 'transfer':
  536. onTransfer();
  537. break;
  538. // 盲转
  539. case 'transferMz':
  540. onTransferMzOpen();
  541. break;
  542. case 'conference': // 三方会议
  543. onConference();
  544. break;
  545. case 'DTMF': // 拨号盘
  546. break;
  547. // 评价
  548. case 'evaluate':
  549. i_evaluate();
  550. break;
  551. }
  552. };
  553. /*
  554. * 事件响应状态
  555. */
  556. const arrangeTime = ref(0);
  557. const arrangeTimer = useIntervalFn(
  558. () => {
  559. arrangeTime.value += 1;
  560. },
  561. 1000,
  562. { immediate: false }
  563. );
  564. // 整理时长开始
  565. const startArrangeTime = () => {
  566. arrangeTimer.resume();
  567. };
  568. // 整理时长开始结束
  569. const stopArrangeTime = () => {
  570. arrangeTime.value = 0;
  571. arrangeTimer.pause();
  572. };
  573. const evtSeatState = (data: any) => {
  574. if (m_strIsMonitor.value == '1') {
  575. // 推送分机信息
  576. const pushExt = data.Param.Extension || '';
  577. if (pushExt !== m_strUserNo.value) {
  578. // 如果分机实时推送的分机信息和当前登录分机不一致,则表明当前登录分机为监控分机,开启了状态监控功能
  579. // 1.不属于当前分机的状态,则不改变当前分机电话条状态
  580. // 2.调整其他分机大屏监控状态
  581. const pushState = GetTelState(data.Param.State);
  582. if (pushState) {
  583. // 修改后台数据
  584. console.log(pushExt, pushState);
  585. // SetMonitorState(pushExt, pushState);
  586. }
  587. return;
  588. }
  589. }
  590. if (m_IsHold.value) {
  591. // 正在保持通话状态下
  592. return;
  593. }
  594. // 状态 0:闲 1:忙 2:会议 3:登出 4:呼入 5:呼出 6:咨询 7:其他 8:通话
  595. const strState = data.Param.State;
  596. switch (strState) {
  597. // 空闲
  598. case '0':
  599. m_bIsOpen.value = false;
  600. m_IsHangup.value = false;
  601. m_strTelState.value = '200';
  602. m_bTelBusy.value = false;
  603. e_TopStateChange(m_strTelState.value);
  604. startIdleTime(); // 空闲计时器开始
  605. stopTalkTimer(); // 停止通话时长
  606. stopConferenceTime(); // 三方会议时长结束
  607. m_IsTalkingDeal.value = false;
  608. break;
  609. // 小休
  610. case '1':
  611. m_strTelState.value = '201';
  612. m_bTelBusy.value = true;
  613. e_TopStateChange(m_strTelState.value);
  614. startBusyTime(); // 小休计时器开始
  615. stopIdleTime(); // 停止空闲时长
  616. break;
  617. case '2':
  618. stopIdleTime();
  619. break;
  620. // 登出
  621. case '3':
  622. // 附加签出方法(用户分机分离调用业务系统签出)
  623. // e_TelSignOut(m_strUserNo.value, m_strSkillId.value);
  624. m_strTelState.value = '0';
  625. e_TopStateChange(m_strTelState.value);
  626. break;
  627. // 通话振铃
  628. case '4':
  629. if (false === m_IsHangup.value) {
  630. if (m_IsCallOut.value == true) {
  631. // 呼出振铃
  632. m_strTelState.value = '302';
  633. } else {
  634. // 呼入振铃
  635. m_strTelState.value = '300';
  636. }
  637. e_TopStateChange(m_strTelState.value);
  638. }
  639. stopIdleTime();
  640. break;
  641. // 通话振铃
  642. case '5':
  643. if (!m_IsHangup.value) {
  644. m_strTelState.value = '302';
  645. e_TopStateChange(m_strTelState.value);
  646. }
  647. stopIdleTime();
  648. break;
  649. // 咨询
  650. case '6':
  651. break;
  652. // 其他
  653. case '7':
  654. break;
  655. // 接通
  656. case '8':
  657. if (!m_IsHangup.value) {
  658. if (m_IsCallOut.value) {
  659. // 呼出接通
  660. m_strTelState.value = '303';
  661. } else {
  662. // 呼入接通
  663. m_strTelState.value = '301';
  664. // 是否是保持通话
  665. }
  666. e_TopStateChange(m_strTelState.value);
  667. }
  668. stopIdleTime();
  669. break;
  670. case '9': // 工单整理
  671. m_strTelState.value = '900';
  672. startArrangeTime(); // 开始整理时长
  673. e_TopStateChange(m_strTelState.value);
  674. m_IsTalkingDeal.value = true;
  675. break;
  676. }
  677. };
  678. // 获取分机状态
  679. const GetTelState = (strState: any) => {
  680. let strResult = '';
  681. switch (strState) {
  682. // 空闲
  683. case '0':
  684. strResult = '200';
  685. break;
  686. // 小休
  687. case '1':
  688. strResult = '201';
  689. break;
  690. case '2':
  691. break;
  692. // 登出
  693. case '3':
  694. strResult = '0';
  695. break;
  696. // 通话振铃
  697. case '4':
  698. strResult = '302'; // 默认呼入振铃
  699. break;
  700. // 通话振铃
  701. case '5':
  702. strResult = '302'; // 默认呼出振铃
  703. break;
  704. // 咨询
  705. case '6':
  706. break;
  707. // 其他
  708. case '7':
  709. break;
  710. // 接通
  711. case '8':
  712. strResult = '301';
  713. break;
  714. case '9': // 工单整理
  715. strResult = '900';
  716. break;
  717. }
  718. return strResult;
  719. };
  720. /**
  721. * 状态初始化请求
  722. * */
  723. const ReqAgentMonitor = () => {
  724. // 开始坐席状态监控
  725. // SkillId 技能组为0则监控所有分机
  726. const msgObj = {
  727. Action: 'ReqAgentMonitor',
  728. Param: {
  729. Extension: m_strUserNo.value,
  730. CompanyId: m_strCompanyId.value,
  731. SkillId: '0',
  732. },
  733. };
  734. // 发送请求
  735. e_TelSendMsg(msgObj);
  736. };
  737. /**
  738. * {“Action”:”ResAgentMonitor”,”Param”:[{“Extension”:,”JobNumber”:,”SkillId”:,”Name”:,”Caller”:,”Called”:,”State”}]}
  739. * State:0:闲 1:忙 2:会议 3:登出 4:呼入 5:呼出 6:咨询 7:其他 8:通话 9:工单整理
  740. * 监控状态初始化返回状态
  741. * @param {any} data
  742. */
  743. const ResAgentMonitor = (data: any) => {
  744. if (null != data && null != data.Param && data.Param.length > 0) {
  745. // 监控分机集合
  746. const strMonitorInfo = JSON.stringify(data.Param);
  747. console.log(`${getNowDateTime()} 监控分机集合:`, strMonitorInfo);
  748. }
  749. };
  750. /*
  751. * 登录
  752. * ReqAgentLogin - 登录方法名
  753. * JobNum - 工号
  754. * Name - 姓名
  755. * Extension - 分机号
  756. * SkillId - 技能组
  757. * Level - 为要设置的级别,分为9个级别,从高到低分别为0-8;同一技能组中级别越高的坐席优先被分配
  758. * Role - 角色,保留,不做设置
  759. * GroupName - 技能组名称
  760. * OrgId - 组织ID
  761. * 返回内容:
  762. {“Action”:”ResAgentLogin”,”Param”:{“Result”:}}
  763. Result: 3:分机错误 7:已登录 0:登录成功
  764. */
  765. const sendSignIn = () => {
  766. globalState.callCenterWs = wsRef.value;
  767. const sendObj = {
  768. Action: 'ReqAgentLogin',
  769. Param: {
  770. JobNum: m_strJobNum.value,
  771. Name: m_strUserName.value,
  772. Extension: m_strUserNo.value,
  773. SkillId: m_strSkillId.value,
  774. Level: m_strLevel.value,
  775. Role: '',
  776. GroupName: m_strGroup.value,
  777. OrgId: m_strCompanyId.value,
  778. Userdata: userInfos.value.id + ':' + userInfos.value.name, // 签入时携带的参数,用于后台记录 用户ID
  779. },
  780. };
  781. // 发送请求
  782. e_TelSendMsg(sendObj);
  783. console.log(`${getNowDateTime()} 呼叫中心发起签入`);
  784. const request = {
  785. creationTime: new Date(),
  786. name: `向兴唐呼叫中心发起签入,分机号:${m_strUserNo.value},工号:${m_strJobNum.value},技能组:${m_strSkillId.value}`,
  787. remark: JSON.stringify(sendObj),
  788. executeUrl: themeConfig.value.callCenterSocketUrl,
  789. };
  790. submitLogFn(request);
  791. };
  792. const signTime = ref(0); //签入时长
  793. // 使用 useInterval 创建定时器
  794. const signInTimer = useIntervalFn(
  795. () => {
  796. signTime.value += 1;
  797. },
  798. 1000,
  799. { immediate: false }
  800. );
  801. // 签入时长及时开始
  802. const startSignTime = (second?: number) => {
  803. if (second) {
  804. // 从后台获取签入时长
  805. if (second < 0) second = 0; // 防止后台返回的签入时间大于当前时间
  806. signTime.value = second;
  807. signInTimer.resume();
  808. } else {
  809. signInTimer.resume();
  810. }
  811. };
  812. // 签入时长计时结束
  813. const stopSignTime = () => {
  814. signTime.value = 0;
  815. signInTimer.pause();
  816. };
  817. // 空闲时长
  818. const idleTime = ref(0);
  819. const idleTimer = useIntervalFn(
  820. () => {
  821. idleTime.value += 1;
  822. },
  823. 1000,
  824. { immediate: false }
  825. );
  826. // 空闲时长开始
  827. const startIdleTime = () => {
  828. idleTimer.resume();
  829. stopBusyTime(); // 结束小休时长
  830. stopArrangeTime(); // 结束整理时长
  831. };
  832. // 空闲时长计时结束
  833. const stopIdleTime = () => {
  834. idleTime.value = 0;
  835. idleTimer.pause();
  836. };
  837. const onSignIn = () => {
  838. if (AppConfigInfo.value.isNeedTelNo) {
  839. // 需要填写分机号
  840. state.dutyDialogVisible = true;
  841. } else {
  842. if (!userInfos.value.defaultTelNo) {
  843. ElMessage.error('请先配置用户默认分机');
  844. return;
  845. }
  846. if (!userInfos.value.defaultTelGroup) {
  847. ElMessage.error('请先配置用户技能组');
  848. return;
  849. }
  850. state.loading = true;
  851. m_strUserNo.value = userInfos.value.defaultTelNo; // 默认分机
  852. m_strJobNum.value = <string>userInfos.value.staffNo; // 工号
  853. m_strSkillId.value = userInfos.value.defaultTelGroup; // 技能组
  854. globalState.currentTel = {
  855. telNo: userInfos.value.defaultTelNo,
  856. telGroup: userInfos.value.defaultTelGroup,
  857. jobNum: <string>userInfos.value.staffNo,
  858. };
  859. wsRef.value.open();
  860. }
  861. };
  862. const dutyFormRef = ref();
  863. const clickOnDuty = (formEl: FormInstance | undefined) => {
  864. if (!formEl) return;
  865. if (!userInfos.value.defaultTelGroup) {
  866. ElMessage.error('请先配置用户默认分机组');
  867. return;
  868. }
  869. formEl.validate((valid: boolean) => {
  870. if (!valid) return;
  871. state.loading = true;
  872. const currentTel: any = telsList.value.find((item: any) => item.no === state.dutyForm.telNo);
  873. m_strUserNo.value = state.dutyForm.telNo;
  874. m_strJobNum.value = <string>userInfos.value.staffNo; // 默认取用户工号
  875. m_strSkillId.value = userInfos.value.defaultTelGroup; // 技能组
  876. globalState.currentTel = {
  877. telNo: state.dutyForm.telNo,
  878. telGroup: m_strSkillId.value,
  879. jobNum: state.dutyForm.telNo,
  880. };
  881. wsRef.value.open();
  882. state.dutyDialogVisible = false;
  883. state.loading = false;
  884. });
  885. };
  886. // 签入弹窗关闭清空
  887. const dutyFormClose = () => {
  888. dutyFormRef.value?.resetFields();
  889. dutyFormRef.value?.clearValidate();
  890. };
  891. // 签入消息回调
  892. const retSignIn = (data: any) => {
  893. state.loading = false;
  894. if (data.Param.Result === '0') {
  895. // 登录成功
  896. m_bLogin.value = true;
  897. m_strTelState.value = '100';
  898. e_TopStateChange(m_strTelState.value);
  899. if (!userAlreadyLogin.value) {
  900. // 检查用户没有登录才需要调用签入
  901. // 刷新页面自动签出不需要调用业务系统签入
  902. // 附加签入方法(用户分机分离) 调用业务系统登录
  903. e_TelSignIn(m_strUserNo.value, m_strSkillId.value);
  904. }
  905. // 登录成功
  906. startSignTime(signTime.value); // 签入计时器
  907. globalState.callCenterIsSignIn = true; // 签入状态
  908. if (m_strIsMonitor.value === '1') {
  909. // 监控初始化状态
  910. ReqAgentMonitor();
  911. }
  912. console.log(`${getNowDateTime()} 呼叫中心签入成功回调`);
  913. } else if (data.Param.Result === '3') {
  914. // 分机错误
  915. ElMessage.error('分机错误');
  916. userAlreadyLogin.value = false; // 将登录状态重置
  917. wsRef.value.close(); // 关闭链接
  918. // 登录不成功 调用业务系统
  919. // e_TelSignOut(m_strUserNo.value, m_strSkillId.value);
  920. // 分机错误
  921. } else if (data.Param.Result === '7') {
  922. // 已经处于登录状态
  923. // 先签出再签入
  924. /* sendSignOut();
  925. userAlreadyLogin.value = false; // 将登录状态重置*/
  926. ElMessage.error('当前分机已签入');
  927. userAlreadyLogin.value = false; // 将登录状态重置
  928. wsRef.value.close(); // 关闭链接
  929. }
  930. const request = {
  931. creationTime: new Date(),
  932. name: `兴唐呼叫中心签入返回消息,分机号:${m_strUserNo.value},工号:${m_strJobNum.value},技能组:${m_strSkillId.value}`,
  933. remark: JSON.stringify(data),
  934. executeUrl: themeConfig.value.callCenterSocketUrl,
  935. };
  936. submitLogFn(request);
  937. };
  938. // ws链接开启成功
  939. const e_websocketOpen = () => {
  940. if (userAlreadyLogin.value) {
  941. // 检查到用户已经登录需要先签出 再签入
  942. sendSignOut();
  943. useTimeoutFn(() => {
  944. sendSignIn();
  945. }, 500);
  946. } else {
  947. if (m_strUserNo.value && m_strSkillId.value) sendSignIn();
  948. else {
  949. ElMessage.error('分机号或技能组配置有误,请联系管理员');
  950. wsRef.value.close();
  951. }
  952. }
  953. const sendObj = {
  954. Action: 'wsOpen',
  955. Param: {
  956. JobNum: m_strJobNum.value,
  957. Name: m_strUserName.value,
  958. Extension: m_strUserNo.value,
  959. SkillId: m_strSkillId.value,
  960. Level: m_strLevel.value,
  961. Role: '',
  962. GroupName: m_strGroup.value,
  963. OrgId: m_strCompanyId.value,
  964. UserData: userInfos.value.id + ':' + userInfos.value.name,
  965. },
  966. };
  967. const request = {
  968. creationTime: new Date(),
  969. name: `兴唐呼叫中心链接开启成功,分机号:${m_strUserNo.value},工号:${m_strJobNum.value},技能组:${m_strSkillId.value}`,
  970. remark: JSON.stringify(sendObj),
  971. executeUrl: themeConfig.value.callCenterSocketUrl,
  972. };
  973. submitLogFn(request);
  974. };
  975. // 链接关闭
  976. const e_websocketClose = () => {
  977. // 需要将话机状态重置
  978. resetTelState();
  979. console.log(`${getNowDateTime()} 呼叫中心链接关闭`);
  980. const sendObj = {
  981. Action: 'wsClose',
  982. Param: {
  983. JobNum: m_strJobNum.value,
  984. Name: m_strUserName.value,
  985. Extension: m_strUserNo.value,
  986. SkillId: m_strSkillId.value,
  987. Level: m_strLevel.value,
  988. Role: '',
  989. GroupName: m_strGroup.value,
  990. OrgId: m_strCompanyId.value,
  991. },
  992. };
  993. const request = {
  994. creationTime: new Date(),
  995. name: `兴唐呼叫中心链接关闭,分机号:${m_strUserNo.value},工号:${m_strJobNum.value},技能组:${m_strSkillId.value}`,
  996. remark: JSON.stringify(sendObj),
  997. executeUrl: themeConfig.value.callCenterSocketUrl,
  998. };
  999. submitLogFn(request);
  1000. };
  1001. // 链接错误
  1002. const e_websocketError = () => {
  1003. // 需要将话机状态重置
  1004. resetTelState();
  1005. console.log(`${getNowDateTime()} 呼叫中心链接错误`);
  1006. };
  1007. // 重置话机状态
  1008. const resetTelState = () => {
  1009. state.loading = false;
  1010. // 登出成功
  1011. m_strTelState.value = '0';
  1012. e_TopStateChange(m_strTelState.value);
  1013. // 签出
  1014. e_ActionUpdate('1'); // 更新话机动作
  1015. m_bLogin.value = false;
  1016. userAlreadyLogin.value = false; // 将登录状态重置
  1017. globalState.callCenterWs = null;
  1018. globalState.callCenterIsSignIn = false; // 签出状态
  1019. globalState.callCenterIsOnThePhone = false; // 清除通话状态
  1020. stopBusyTime(); // 停止小休计时器
  1021. stopIdleTime(); // 停止空闲计时器
  1022. stopTalkTimer(); // 停止通话计时器
  1023. stopArrangeTime(); // 停止整理时长
  1024. };
  1025. // 记录日志
  1026. const submitLogFn = async (request: any) => {
  1027. try {
  1028. await submitLog(request);
  1029. Local.remove('telNo');
  1030. } catch (error) {
  1031. console.log(`${getNowDateTime()}:日志记录失败,${error}`);
  1032. }
  1033. };
  1034. // 消息接收
  1035. const e_TelMsgReceive = (ws: any, restMsg: any) => {
  1036. console.log(`${getNowDateTime()} 接收消息:${restMsg.data}`);
  1037. if (restMsg.data) {
  1038. const data = JSON.parse(restMsg.data);
  1039. if (data) {
  1040. // 方法
  1041. const strAction = data.Action;
  1042. switch (strAction) {
  1043. // 登录返回值
  1044. case 'ResAgentLogin':
  1045. retSignIn(data);
  1046. break;
  1047. // 示闲
  1048. case 'ResAgentIdle':
  1049. retIdle(data);
  1050. break;
  1051. // 小休
  1052. case 'ResAgentBusy':
  1053. retBusy(data);
  1054. break;
  1055. // 外呼状态
  1056. case 'ResMakeCall':
  1057. retCallOut(data);
  1058. break;
  1059. // 保持
  1060. case 'ResHoldCall':
  1061. retHold(data);
  1062. break;
  1063. // 取消保持
  1064. case 'ResRetrieve':
  1065. retRehold(data);
  1066. break;
  1067. // 咨询内线
  1068. case 'ResConsultInline':
  1069. retConsult(data, '0');
  1070. break;
  1071. // 咨询外线
  1072. case 'ResConsultOutline':
  1073. retConsult(data, '1');
  1074. break;
  1075. // 咨询群组
  1076. case 'ResConsultSkillGroup':
  1077. retConsult(data, '2');
  1078. break;
  1079. // 咨询转移
  1080. case 'ResTransfer':
  1081. retResTransfer(data);
  1082. break;
  1083. // 三方会议
  1084. case 'ResConference':
  1085. retResConference(data);
  1086. break;
  1087. // 三方会议
  1088. case 'ResMonConf':
  1089. retResConference(data);
  1090. break;
  1091. // 坐席实时状态
  1092. case 'ResAgentMinitor':
  1093. ResAgentMonitor(data);
  1094. break;
  1095. // 监听
  1096. case 'ResMonListen':
  1097. retResMonListen(data);
  1098. break;
  1099. // 取消监听返回
  1100. case 'ResStopListen':
  1101. retResStopListen(data);
  1102. break;
  1103. }
  1104. // 事件
  1105. const strEvent = data.Event;
  1106. switch (strEvent) {
  1107. // 签出事件
  1108. case 'EvtLogout':
  1109. retSignOut();
  1110. break;
  1111. // 呼入振铃事件
  1112. case 'EvtCallAlerting':
  1113. evtCallAlerting(data);
  1114. break;
  1115. // 应答事件
  1116. case 'EvtCallAnswer':
  1117. evtEvtCallAnswer(data);
  1118. break;
  1119. // 挂机事件
  1120. case 'EvtHangup':
  1121. retHangup(data);
  1122. break;
  1123. // 状态
  1124. case 'EvtSeatState':
  1125. evtSeatState(data);
  1126. break;
  1127. // 队列等待
  1128. case 'EvtAcdInfo':
  1129. i_QueueNum(data);
  1130. break;
  1131. // 语音识别结果通知事件
  1132. case 'EvtRecognize':
  1133. e_EvtRecognize(data.Param);
  1134. break;
  1135. // 转三方接通状态
  1136. case 'EvtDispatchState':
  1137. e_EvtDispatchState(data.Param);
  1138. break;
  1139. // 签出事件
  1140. case 'ResStopMonitor':
  1141. retSignOut();
  1142. break;
  1143. case 'ResError': // 异常
  1144. retHangup(data); // 挂机
  1145. // 异常处理
  1146. retResError(data);
  1147. break;
  1148. // 呼出接通事件
  1149. case 'EvtOutCalling':
  1150. evtEvtCalling(data); // 呼出振铃事件
  1151. break;
  1152. // 签出事件
  1153. case 'EvtQuit':
  1154. retSignOut();
  1155. break;
  1156. }
  1157. }
  1158. }
  1159. };
  1160. /*
  1161. * 签出
  1162. * ReqAgentLogout - 签出方法名称
  1163. * Extension - 分机号码
  1164. */
  1165. const sendSignOut = () => {
  1166. const objMsg = {
  1167. Action: 'ReqAgentLogout',
  1168. Param: {
  1169. Extension: m_strUserNo.value,
  1170. },
  1171. };
  1172. // 发送请求
  1173. e_TelSendMsg(objMsg);
  1174. const request = {
  1175. creationTime: new Date(),
  1176. name: `向兴唐呼叫中心发起签出,分机号:${m_strUserNo.value},工号:${m_strJobNum.value},技能组:${m_strSkillId.value}`,
  1177. remark: JSON.stringify(objMsg),
  1178. executeUrl: themeConfig.value.callCenterSocketUrl,
  1179. };
  1180. submitLogFn(request);
  1181. };
  1182. const onSignOut = () => {
  1183. ElMessageBox.confirm(`确定要签出,是否继续?`, '提示', {
  1184. confirmButtonText: '确认',
  1185. cancelButtonText: '取消',
  1186. type: 'warning',
  1187. draggable: true,
  1188. cancelButtonClass: 'default-button',
  1189. autofocus: false,
  1190. })
  1191. .then(() => {
  1192. sendSignOut();
  1193. userAlreadyLogin.value = false; // 将登录状态重置
  1194. })
  1195. .catch(() => {
  1196. state.loading = false;
  1197. });
  1198. };
  1199. /*
  1200. * 签出事件
  1201. */
  1202. const retSignOut = () => {
  1203. if (!userAlreadyLogin.value) {
  1204. // 检查用户没有登录才需要调用签出
  1205. // 刷新页面自动签出不需要调用业务系统签入
  1206. // 附加签出方法(用户分机分离 调用业务系统等处)
  1207. e_TelSignOut(m_strUserNo.value, m_strSkillId.value);
  1208. }
  1209. state.loading = false;
  1210. // 登出成功
  1211. m_strTelState.value = '0';
  1212. e_TopStateChange(m_strTelState.value);
  1213. // 签出
  1214. e_ActionUpdate('1'); // 更新话机动作
  1215. m_bLogin.value = false;
  1216. globalState.callCenterIsSignIn = false; // 签出状态
  1217. stopBusyTime(); // 停止小休计时器
  1218. stopIdleTime(); // 停止空闲计时器
  1219. stopTalkTimer(); // 停止通话计时器
  1220. stopArrangeTime(); // 停止整理时长
  1221. // 如果用户没有登录 关闭ws
  1222. if (!userAlreadyLogin.value) {
  1223. wsRef.value.close();
  1224. stopSignTime(); // 停止签入计时器
  1225. }
  1226. currentWait.value = 0; // 当前等待重置
  1227. globalState.callCenterWs = null;
  1228. isRestAudit.value = false; // 重置小休审核状态
  1229. console.log(`${getNowDateTime()} 呼叫中心签出回调`);
  1230. const objMsg = {
  1231. Action: 'ResAgentLogout',
  1232. Param: {
  1233. Extension: m_strUserNo.value,
  1234. },
  1235. };
  1236. const request = {
  1237. creationTime: new Date(),
  1238. name: `兴唐呼叫中心签出事件回调,分机号:${m_strUserNo.value},工号:${m_strJobNum.value},技能组:${m_strSkillId.value}`,
  1239. remark: JSON.stringify(objMsg),
  1240. executeUrl: themeConfig.value.callCenterSocketUrl,
  1241. };
  1242. submitLogFn(request);
  1243. };
  1244. /*
  1245. * 小休
  1246. * ReqAgentBusy - 方法名
  1247. * Extension:分机号
  1248. */
  1249. // 定义标志变量,用于标记是否已经弹过窗
  1250. const hasShownBusyAlert = ref(false);
  1251. // 小休时长
  1252. const busyTime = ref(0);
  1253. const busyTimer = useIntervalFn(
  1254. () => {
  1255. busyTime.value += 1;
  1256. if (AppConfigInfo.value.isTelRest) {
  1257. // 配置开关
  1258. // 将秒转换为分钟
  1259. const minutes = Math.floor(AppConfigInfo.value.telRestNum / 60);
  1260. // 检查示忙时长是否超过 分钟
  1261. if (busyTime.value >= AppConfigInfo.value.telRestNum && !hasShownBusyAlert.value) {
  1262. // 如果示忙时长超过 分钟需要弹窗提示 并且只执行一次
  1263. // 弹窗提示
  1264. ElMessageBox.alert(`小休时长已超过${minutes}分钟,请及时处理!`, '提示', {
  1265. confirmButtonText: '确定',
  1266. type: 'warning',
  1267. draggable: true,
  1268. showClose: false,
  1269. });
  1270. // 更新标志变量,确保只弹一次
  1271. hasShownBusyAlert.value = true;
  1272. }
  1273. }
  1274. },
  1275. 1000,
  1276. { immediate: false }
  1277. );
  1278. // 小休时长开始
  1279. const startBusyTime = () => {
  1280. hasShownBusyAlert.value = false; // 重置标志变量
  1281. busyTimer.resume();
  1282. };
  1283. // 小休时长计时结束
  1284. const stopBusyTime = () => {
  1285. busyTime.value = 0;
  1286. busyTimer.pause();
  1287. hasShownBusyAlert.value = false; // 重置标志变量
  1288. };
  1289. const restFormRef = ref<RefType>(); // 小休表单
  1290. const onBusy = () => {
  1291. if (AppConfigInfo.value.isRestApproval) {
  1292. // 需要小休审批
  1293. state.restDialogVisible = true;
  1294. } else {
  1295. ElMessageBox.confirm(`确定要小休,是否继续?`, '提示', {
  1296. confirmButtonText: '确认',
  1297. cancelButtonText: '取消',
  1298. type: 'warning',
  1299. draggable: true,
  1300. cancelButtonClass: 'default-button',
  1301. autofocus: false,
  1302. })
  1303. .then(() => {
  1304. const objMsg = {
  1305. Action: 'ReqAgentBusy',
  1306. Param: {
  1307. Extension: m_strUserNo.value,
  1308. },
  1309. };
  1310. // 发送请求
  1311. e_TelSendMsg(objMsg);
  1312. })
  1313. .catch(() => {
  1314. state.loading = false;
  1315. });
  1316. }
  1317. };
  1318. // 确认小休
  1319. const restReason = ref(''); // 小休原因
  1320. const isRestAudit = ref(false); // 是否小休审核
  1321. const clickOnRest = (formEl: FormInstance | undefined) => {
  1322. if (!formEl) return;
  1323. formEl.validate((valid: boolean) => {
  1324. if (!valid) return;
  1325. state.loading = true;
  1326. restReason.value = state.restForm.reason; // 小休原因
  1327. const objMsg = {
  1328. Action: 'ReqAgentBusy',
  1329. Param: {
  1330. Extension: m_strUserNo.value,
  1331. },
  1332. };
  1333. // 发送请求
  1334. e_TelSendMsg(objMsg);
  1335. isRestAudit.value = true; // 正在小休审核中
  1336. });
  1337. };
  1338. const restFormOpened = () => {
  1339. restFormRef.value?.resetFields();
  1340. restFormRef.value?.clearValidate();
  1341. };
  1342. /*
  1343. * 小休返回值
  1344. {“Action”:”ResAgentBusy”,”Param”:{“Result”:}}
  1345. Result:0-1
  1346. 0:成功 1:失败
  1347. */
  1348. const retBusy = (data: any) => {
  1349. if (data.Param.Result === '0') {
  1350. m_bTelBusy.value = true;
  1351. m_strTelState.value = '201';
  1352. e_TopStateChange(m_strTelState.value);
  1353. console.log('小休开始了');
  1354. // 小休开始了
  1355. // const restReasons = state.restReasonOptions.find((item: any) => item.dicDataValue === reason);
  1356. busyOn({ reason: '' }) // 开始小休 设置示忙 业务系统统计需要
  1357. .then(() => {
  1358. console.log(`${getNowDateTime()}:业务系统调用示忙开始成功`);
  1359. state.loading = false;
  1360. })
  1361. .catch((err: any) => {
  1362. console.log(`${getNowDateTime()}:业务系统调用示忙开始失败,${err}`);
  1363. state.loading = false;
  1364. });
  1365. } else {
  1366. ElMessage.error('小休失败');
  1367. }
  1368. };
  1369. /*
  1370. * 示闲
  1371. * ReqAgentIdle - 方法名
  1372. * Extension:分机号
  1373. */
  1374. const onIdle = () => {
  1375. ElMessageBox.confirm(`确定要示闲,是否继续?`, '提示', {
  1376. confirmButtonText: '确认',
  1377. cancelButtonText: '取消',
  1378. type: 'warning',
  1379. draggable: true,
  1380. cancelButtonClass: 'default-button',
  1381. autofocus: false,
  1382. })
  1383. .then(() => {
  1384. const objMsg = {
  1385. Action: 'ReqAgentIdle',
  1386. Param: {
  1387. Extension: m_strUserNo.value,
  1388. },
  1389. };
  1390. // 发送请求
  1391. e_TelSendMsg(objMsg);
  1392. console.log('小休结束');
  1393. // 结束小休 设置示忙 业务系统统计需要
  1394. busyOff()
  1395. .then(() => {
  1396. console.log(`${getNowDateTime()}:业务系统调用示忙结束成功`);
  1397. })
  1398. .catch((err) => {
  1399. console.log(`${getNowDateTime()}:业务系统调用示忙结束失败,${err}`);
  1400. }); // 结束小休(调用业务系统接口,统计需要)
  1401. })
  1402. .catch(() => {
  1403. state.loading = false;
  1404. });
  1405. };
  1406. /*
  1407. * 示闲返回值
  1408. * {“Action”:”ResAgentIdle”,”Param”:{“Result”:}}
  1409. Result:0-1
  1410. 0:成功 1:失败
  1411. */
  1412. const retIdle = (data: any) => {
  1413. if (data.Param.Result == '0') {
  1414. m_bTelBusy.value = false;
  1415. m_strTelState.value = '200';
  1416. e_TopStateChange(m_strTelState.value);
  1417. m_IsTalkingDeal.value = false;
  1418. } else {
  1419. ElMessage.error('示闲失败');
  1420. }
  1421. };
  1422. // 手动取消整理
  1423. const onCancelTalkDeal = () => {
  1424. ElMessageBox.confirm(`确定要取消整理,是否继续?`, '提示', {
  1425. confirmButtonText: '确认',
  1426. cancelButtonText: '取消',
  1427. type: 'warning',
  1428. draggable: true,
  1429. cancelButtonClass: 'default-button',
  1430. autofocus: false,
  1431. })
  1432. .then(() => {
  1433. console.log(m_strTelStateBeforeCallOut.value, '这是手动取消整理之前的状态');
  1434. let objMsg = {};
  1435. if (m_strTelStateBeforeCallOut.value === '201') {
  1436. // 如果之前的状态是示忙 调用示忙
  1437. objMsg = {
  1438. Action: 'ReqAgentBusy',
  1439. Param: {
  1440. Extension: m_strUserNo.value,
  1441. },
  1442. };
  1443. } else {
  1444. // 默认调用示闲
  1445. objMsg = {
  1446. Action: 'ReqAgentIdle',
  1447. Param: {
  1448. Extension: m_strUserNo.value,
  1449. },
  1450. };
  1451. }
  1452. // 发送请求
  1453. e_TelSendMsg(objMsg);
  1454. })
  1455. .catch(() => {
  1456. state.loading = false;
  1457. });
  1458. };
  1459. /*
  1460. * 保持
  1461. */
  1462. const onHold = () => {
  1463. ElMessageBox.confirm(`确定要保持,是否继续?`, '提示', {
  1464. confirmButtonText: '确认',
  1465. cancelButtonText: '取消',
  1466. type: 'warning',
  1467. draggable: true,
  1468. cancelButtonClass: 'default-button',
  1469. autofocus: false,
  1470. })
  1471. .then(() => {
  1472. const objMsg = {
  1473. Action: 'ReqHoldCall',
  1474. Param: {
  1475. Extension: m_strUserNo.value,
  1476. },
  1477. };
  1478. // 发送请求
  1479. e_TelSendMsg(objMsg);
  1480. })
  1481. .catch(() => {
  1482. state.loading = false;
  1483. });
  1484. };
  1485. /*
  1486. * 保持返回
  1487. */
  1488. const retHold = (data: any) => {
  1489. if (data.Param.Result == '0') {
  1490. m_IsHold.value = true;
  1491. m_strTelState.value = '310';
  1492. e_TopStateChange(m_strTelState.value);
  1493. } else {
  1494. ElMessage.error('保持操作失败');
  1495. }
  1496. };
  1497. /*
  1498. * 取消保持·······
  1499. */
  1500. const onReHold = () => {
  1501. ElMessageBox.confirm(`确定要恢复,是否继续?`, '提示', {
  1502. confirmButtonText: '确认',
  1503. cancelButtonText: '取消',
  1504. type: 'warning',
  1505. draggable: true,
  1506. cancelButtonClass: 'default-button',
  1507. autofocus: false,
  1508. })
  1509. .then(() => {
  1510. const objMsg = {
  1511. Action: 'ReqRetrieve',
  1512. Param: {
  1513. Extension: m_strUserNo.value,
  1514. },
  1515. };
  1516. // 发送请求
  1517. e_TelSendMsg(objMsg);
  1518. })
  1519. .catch(() => {
  1520. state.loading = false;
  1521. });
  1522. };
  1523. /*
  1524. * 取消保持返回状态
  1525. */
  1526. const retRehold = (data: any) => {
  1527. if (data.Param.Result == '0') {
  1528. // 是否是保持通话
  1529. m_IsHold.value = false;
  1530. if (m_IsCallIn.value) {
  1531. m_strTelState.value = '301';
  1532. } else {
  1533. m_strTelState.value = '303';
  1534. }
  1535. e_TopStateChange(m_strTelState.value);
  1536. } else {
  1537. ElMessage.error('取消保持操作失败');
  1538. }
  1539. };
  1540. // 外呼
  1541. const onCallOut = () => {
  1542. state.loading = false;
  1543. state.outboundDialogVisible = true;
  1544. };
  1545. // 记录一下呼叫之前的状态
  1546. const m_strTelStateBeforeCallOut = ref('');
  1547. const outboundFormRef = ref<RefType>();
  1548. const clickOnOutbound = (formEl: FormInstance | undefined) => {
  1549. if (!formEl) return;
  1550. formEl.validate((valid: boolean) => {
  1551. if (!valid) return;
  1552. state.loading = true;
  1553. m_strTelStateBeforeCallOut.value = m_strTelState.value;
  1554. callout(state.outboundForm.telNo);
  1555. state.outboundDialogVisible = false;
  1556. });
  1557. };
  1558. // 外呼弹窗关闭清空
  1559. const outboundFormClose = () => {
  1560. outboundFormRef.value?.resetFields();
  1561. outboundFormRef.value?.clearValidate();
  1562. };
  1563. /*
  1564. * 外呼
  1565. * ReqMakeCall - 方法名
  1566. * Extension:分机号
  1567. * Called:被叫
  1568. * CustomerId:客户ID
  1569. */
  1570. const callout = (strCallNumber: string | number | null) => {
  1571. if (!strCallNumber) {
  1572. ElMessage.error('电话号码不能为空');
  1573. return;
  1574. }
  1575. const strCallNumberTrim = trimCompat(strCallNumber);
  1576. const obkMsg = {
  1577. Action: 'ReqMakeCall',
  1578. Param: {
  1579. Extension: m_strUserNo.value,
  1580. Called: strCallNumberTrim,
  1581. CustomerId: '',
  1582. },
  1583. };
  1584. // 是否外呼
  1585. m_IsCallOut.value = true;
  1586. // 发送请求
  1587. e_TelSendMsg(obkMsg);
  1588. };
  1589. /*
  1590. * 外呼返回事件
  1591. {“Action”:”ResMakeCall”,”Param”:{“Result”:}}
  1592. Result:0-1
  1593. 0:成功 1:失败
  1594. */
  1595. const retCallOut = (data: any) => {
  1596. state.loading = false;
  1597. if (data.Param.Result == '0') {
  1598. // 是否外呼
  1599. m_IsCallOut.value = true; // 呼出
  1600. m_strTelState.value = '302';
  1601. e_TopStateChange(m_strTelState.value);
  1602. } else {
  1603. ElMessage.error('呼叫失败');
  1604. }
  1605. };
  1606. /*
  1607. * 咨询
  1608. */
  1609. const onConsultOpen = () => {
  1610. m_strTelState.value = '331';
  1611. // 保持状态
  1612. m_IsHold.value = true;
  1613. state.loading = false;
  1614. state.consultDialogVisible = true;
  1615. };
  1616. const consultFormRef = ref<RefType>();
  1617. const clickOnConsult = (formEl: FormInstance | undefined) => {
  1618. if (!formEl) return;
  1619. formEl.validate((valid: boolean) => {
  1620. if (!valid) return;
  1621. state.loading = true;
  1622. onConsult(state.consultForm.telNo, state.consultForm.strType);
  1623. state.consultDialogVisible = false;
  1624. });
  1625. };
  1626. // 咨询弹窗关闭清空
  1627. const consultFormClose = () => {
  1628. consultFormRef.value?.resetFields();
  1629. consultFormRef.value?.clearValidate();
  1630. };
  1631. const onConsult = (strCallNumber: string | number | null, strType: string) => {
  1632. let strObj: EmptyObjectType;
  1633. const strCallNumberTrim = trimCompat(strCallNumber);
  1634. switch (strType) {
  1635. // 内线
  1636. case '0':
  1637. strObj = {
  1638. Action: 'ReqConsultInline',
  1639. Param: {
  1640. Extension: m_strUserNo.value,
  1641. TargetExtension: strCallNumberTrim,
  1642. },
  1643. };
  1644. break;
  1645. // 外线
  1646. case '1':
  1647. strObj = {
  1648. Action: 'ReqConsultOutline',
  1649. Param: {
  1650. Extension: m_strUserNo.value,
  1651. TargetCalled: strCallNumberTrim,
  1652. },
  1653. };
  1654. break;
  1655. // 群组
  1656. case '2':
  1657. strObj = {
  1658. Action: 'ReqConsultSkillGroup',
  1659. Param: {
  1660. Extension: m_strUserNo.value,
  1661. TargetSkillGroup: strCallNumberTrim,
  1662. },
  1663. };
  1664. break;
  1665. }
  1666. // 转接类型
  1667. m_strConsultType.value = strType;
  1668. // 外呼
  1669. m_IsCallOut.value = true;
  1670. // 咨询状态
  1671. m_IsConsult.value = true;
  1672. // 发送请求
  1673. e_TelSendMsg(strObj);
  1674. };
  1675. /*
  1676. * 转接返回
  1677. * data
  1678. * {“Action”:”ResConsultSkillGroup”,”Param”:{“Result”:}}Result:0-1
  1679. * 0:成功 1:失败
  1680. * strType:0-转内线;1-转外线;2-转群组
  1681. */
  1682. const retConsult = (data: any, strType: string) => {
  1683. state.loading = false;
  1684. if (data.Param.Result == '0') {
  1685. // 咨询成功
  1686. // m_IsConsult = true;
  1687. //m_strTelState = "330";
  1688. // e_TopStateChange(m_strTelState);
  1689. } else {
  1690. //alert("转接失败");
  1691. if (!m_IsHangup.value) {
  1692. if (m_IsCallOut.value) {
  1693. // 呼出接通
  1694. m_strTelState.value = '303';
  1695. } else {
  1696. // 呼入接通
  1697. m_strTelState.value = '301';
  1698. }
  1699. e_TopStateChange(m_strTelState.value);
  1700. }
  1701. let strMsg = '咨询失败';
  1702. if (strType == '0') {
  1703. strMsg = '咨询内线失败';
  1704. } else if (strType == '1') {
  1705. strMsg = '咨询外线失败';
  1706. } else if (strType == '2') {
  1707. strMsg = '咨询技能组失败';
  1708. }
  1709. ElMessage.error(strMsg);
  1710. }
  1711. };
  1712. /*
  1713. * 转接
  1714. */
  1715. const onTransfer = () => {
  1716. const objMsg = {
  1717. Action: 'ReqTransfer',
  1718. Param: {
  1719. Extension: m_strUserNo.value,
  1720. },
  1721. };
  1722. // 发送请求
  1723. e_TelSendMsg(objMsg);
  1724. };
  1725. /*
  1726. * 转接返回
  1727. */
  1728. const retResTransfer = (data: any) => {
  1729. if (data.Param.Result == '0') {
  1730. // 咨询成功
  1731. m_IsConsult.value = true;
  1732. m_strTelState.value = '320';
  1733. e_TopStateChange(m_strTelState.value);
  1734. } else {
  1735. ElMessage.error('转接失败');
  1736. }
  1737. };
  1738. /*
  1739. * 盲转
  1740. */
  1741. const onTransferMzOpen = () => {
  1742. state.loading = false;
  1743. state.blindDialogVisible = true;
  1744. };
  1745. const blindFormRef = ref<RefType>();
  1746. const clickOnBlind = (formEl: FormInstance | undefined) => {
  1747. if (!formEl) return;
  1748. formEl.validate((valid: boolean) => {
  1749. if (!valid) return;
  1750. state.loading = true;
  1751. onTransferMz(state.blindForm.telNo);
  1752. state.blindDialogVisible = false;
  1753. });
  1754. };
  1755. // 盲转弹窗关闭清空
  1756. const blindFormClose = () => {
  1757. blindFormRef.value?.resetFields();
  1758. blindFormRef.value?.clearValidate();
  1759. };
  1760. const onTransferMz = (strCallNumber: string | number | null) => {
  1761. const strCallNumberTrim = trimCompat(strCallNumber);
  1762. const objMsg = {
  1763. Action: 'ReqBlindTransfer',
  1764. Param: {
  1765. Extension: m_strUserNo.value,
  1766. TargetCalled: strCallNumberTrim,
  1767. },
  1768. };
  1769. // 发送请求
  1770. e_TelSendMsg(objMsg);
  1771. state.loading = false;
  1772. console.log(`${getNowDateTime()}盲转消息:${JSON.stringify(objMsg)}`);
  1773. // 结束挂机
  1774. useTimeoutFn(() => {
  1775. sendHangup();
  1776. }, 300);
  1777. };
  1778. /*
  1779. * 三方会议
  1780. */
  1781. const onConference = () => {
  1782. ElMessageBox.confirm(`确定要发起三方会议,是否继续?`, '提示', {
  1783. confirmButtonText: '确认',
  1784. cancelButtonText: '取消',
  1785. type: 'warning',
  1786. draggable: true,
  1787. cancelButtonClass: 'default-button',
  1788. autofocus: false,
  1789. })
  1790. .then(() => {
  1791. state.loading = true;
  1792. const objMsg = {
  1793. Action: 'ReqConference',
  1794. Param: {
  1795. Extension: m_strUserNo.value,
  1796. },
  1797. };
  1798. // 发送请求
  1799. e_TelSendMsg(objMsg);
  1800. })
  1801. .catch(() => {
  1802. state.loading = false;
  1803. });
  1804. };
  1805. // 会议时长
  1806. const conferenceTime = ref(0);
  1807. const conferenceTimer = useIntervalFn(
  1808. () => {
  1809. conferenceTime.value += 1;
  1810. },
  1811. 1000,
  1812. { immediate: false }
  1813. );
  1814. // 三方会议开始
  1815. const startConferenceTime = () => {
  1816. conferenceTimer.resume();
  1817. };
  1818. // 三方会议时长计时结束
  1819. const stopConferenceTime = () => {
  1820. conferenceTime.value = 0;
  1821. conferenceTimer.pause();
  1822. };
  1823. /*
  1824. * 三方会议返回
  1825. * { "Action": "ReqConference", "Param": {"Extension": "1002"} }
  1826. */
  1827. const retResConference = (data: any) => {
  1828. state.loading = false;
  1829. if (data.Param.Result == '0') {
  1830. // 咨询成功
  1831. m_IsConsult.value = true;
  1832. m_strTelState.value = '320';
  1833. e_TopStateChange(m_strTelState.value);
  1834. } else {
  1835. ElMessage.error('三方会议失败');
  1836. }
  1837. };
  1838. /*
  1839. * 挂机
  1840. * ReqHangup - 方法名
  1841. * Extension:分机号
  1842. {“Event”: “EvtHangup”}
  1843. */
  1844. const sendHangup = () => {
  1845. const objMsg = {
  1846. Action: 'ReqHangup',
  1847. Param: {
  1848. Extension: m_strUserNo.value,
  1849. },
  1850. };
  1851. // 发送请求
  1852. e_TelSendMsg(objMsg);
  1853. };
  1854. const onHangup = () => {
  1855. ElMessageBox.confirm(`确定要挂机,是否继续?`, '提示', {
  1856. confirmButtonText: '确认',
  1857. cancelButtonText: '取消',
  1858. type: 'warning',
  1859. draggable: true,
  1860. cancelButtonClass: 'default-button',
  1861. autofocus: false,
  1862. })
  1863. .then(() => {
  1864. sendHangup();
  1865. })
  1866. .catch(() => {
  1867. state.loading = false;
  1868. });
  1869. };
  1870. /*
  1871. * 挂机事件
  1872. * {“Event”: “EvtHangup”}
  1873. */
  1874. const retHangup = (data?: any) => {
  1875. // 挂机后该状态为 false
  1876. console.log(`${getNowDateTime()}:接收消息:呼叫中心挂机回调`, data);
  1877. // 挂机后该状态为 false
  1878. // 挂机
  1879. m_IsHangup.value = true;
  1880. m_IsCallIn.value = false;
  1881. m_IsCallOut.value = false;
  1882. m_bCallConnect.value = false;
  1883. globalState.callCenterIsOnThePhone = false;
  1884. m_IsConsult.value = false;
  1885. m_IsHold.value = false;
  1886. // 是否弹屏
  1887. m_bIsOpen.value = false;
  1888. m_strConsultType.value = '-1';
  1889. /*
  1890. m_strTelState.value = '200';
  1891. e_TopStateChange(m_strTelState.value);*/
  1892. // 未监听
  1893. m_IsMonListen.value = '0';
  1894. // 呼出是否弹屏(用于未接统计“回拨”业务处理)
  1895. m_CallOutOpen.value = false;
  1896. // 清除呼叫ID
  1897. callId.value = '';
  1898. };
  1899. /*
  1900. * 评价
  1901. */
  1902. const i_evaluate = () => {
  1903. ElMessageBox.confirm(`确定要开启评价,评价后将直接挂机,是否继续?`, '提示', {
  1904. confirmButtonText: '确认',
  1905. cancelButtonText: '取消',
  1906. type: 'warning',
  1907. draggable: true,
  1908. cancelButtonClass: 'default-button',
  1909. autofocus: false,
  1910. })
  1911. .then(() => {
  1912. const objMsg = {
  1913. Action: 'ReqSatisfaction',
  1914. Param: {
  1915. Extension: m_strUserNo.value,
  1916. },
  1917. };
  1918. // 发送请求
  1919. e_TelSendMsg(objMsg);
  1920. state.loading = false;
  1921. console.log(`${getNowDateTime()}评价消息:${JSON.stringify(objMsg)}`);
  1922. // 结束挂机
  1923. useTimeoutFn(() => {
  1924. sendHangup();
  1925. }, 300);
  1926. })
  1927. .catch(() => {
  1928. state.loading = false;
  1929. });
  1930. };
  1931. /*
  1932. * 监听
  1933. */
  1934. const reqMonListen = (strTargetNum: string) => {
  1935. const objMsg = {
  1936. Action: 'ReqMonListen',
  1937. Param: {
  1938. Extension: m_strUserNo.value,
  1939. TargetExtension: strTargetNum,
  1940. },
  1941. };
  1942. // 发送请求
  1943. e_TelSendMsg(objMsg);
  1944. };
  1945. /*
  1946. * 监听返回
  1947. */
  1948. const retResMonListen = (data: any) => {
  1949. if (data.Param.Result == '0') {
  1950. m_IsMonListen.value = '1';
  1951. // 成功
  1952. } else {
  1953. m_IsMonListen.value = '2';
  1954. }
  1955. };
  1956. /*
  1957. * 取消监听
  1958. */
  1959. const reqStopListen = (strTargetNum: string) => {
  1960. const objMsg = {
  1961. Action: 'ReqStopListen',
  1962. Param: {
  1963. Extension: m_strUserNo.value,
  1964. TargetExtension: strTargetNum,
  1965. },
  1966. };
  1967. // 发送请求
  1968. e_TelSendMsg(objMsg);
  1969. };
  1970. /*
  1971. * 取消监听返回
  1972. */
  1973. const retResStopListen = (data: any) => {
  1974. if (data.Param.Result == '0') {
  1975. m_IsMonListen.value = '1';
  1976. // 成功
  1977. } else {
  1978. m_IsMonListen.value = '2';
  1979. }
  1980. };
  1981. /*
  1982. * 呼入(振铃)事件
  1983. * EvtCallAlerting - 事件名称
  1984. * Caller:主叫
  1985. * Called:被叫
  1986. * Customerid:客户ID
  1987. * Callid:呼叫ID
  1988. {“Event”:”EvtCallAlerting”,”Param”:{“Caller”:”123”,”Called”:”456”,”Customerid”:”675”,”Callid”:”94593939”}}
  1989. */
  1990. const router = useRouter();
  1991. const evtCallAlerting = (data: any) => {
  1992. let strCalledNum: string;
  1993. let strTelNumber: string;
  1994. console.log(
  1995. `开始振铃,是否呼出:${m_IsCallOut.value},是否呼出弹屏:${m_CallOutOpen.value},是否弹屏:${m_bIsOpen.value},弹屏方式:${
  1996. m_strOpenFlag.value === '1' ? '接通弹屏' : '振铃弹屏'
  1997. }`
  1998. );
  1999. if (m_IsCallOut.value) {
  2000. // 呼出
  2001. // 呼出振铃
  2002. m_strTelState.value = '302';
  2003. e_TopStateChange(m_strTelState.value);
  2004. if (m_CallOutOpen.value) {
  2005. // 呼出是否弹屏(用于未接统计“回拨”业务处理)
  2006. // 主叫号码
  2007. strTelNumber = data.Param.Caller;
  2008. // 被叫号码
  2009. strCalledNum = data.Param.Called;
  2010. // 呼叫ID
  2011. callId.value = data.Param.Callid;
  2012. if (!m_bIsOpen.value && m_strOpenFlag.value == '2') {
  2013. // 振铃呼出弹屏
  2014. console.log(
  2015. '呼出是否弹屏[' +
  2016. m_bIsOpen.value +
  2017. '];弹屏方式[' +
  2018. m_strOpenFlag.value +
  2019. '];记录ID[' +
  2020. callId.value +
  2021. '];主叫号码[' +
  2022. strTelNumber +
  2023. '];被叫号码[' +
  2024. strCalledNum +
  2025. ']'
  2026. );
  2027. m_bIsOpen.value = true;
  2028. // 呼出不再弹单
  2029. m_CallOutOpen.value = false;
  2030. // 去电振铃弹屏
  2031. router.push({
  2032. name: 'orderAccept',
  2033. query: {
  2034. createBy: 'tel',
  2035. fromTel: strTelNumber, // 来电号码
  2036. callId: callId.value, // 通话ID
  2037. transfer: strCalledNum, // 转接来源(如12345,12333)
  2038. telArea: '',
  2039. identityType: '', // 按键接收(2:市民 1:企业) 默认市民
  2040. },
  2041. });
  2042. }
  2043. }
  2044. } else {
  2045. // 呼入振铃该状态为呼入
  2046. m_IsCallIn.value = true;
  2047. // 呼入振铃
  2048. m_strTelState.value = '300';
  2049. e_TopStateChange(m_strTelState.value);
  2050. // 主叫号码
  2051. strTelNumber = data.Param.Caller;
  2052. // 被叫号码
  2053. strCalledNum = data.Param.Called;
  2054. // 呼叫ID
  2055. callId.value = data.Param.Callid;
  2056. if (strTelNumber.length == strCalledNum.length && strTelNumber.length == 4) {
  2057. // 如果主叫号码、被叫号码都是分机号码,则不弹屏
  2058. m_bIsOpen.value = true;
  2059. }
  2060. if (!m_bIsOpen.value && m_strOpenFlag.value === '2') {
  2061. m_bIsOpen.value = true;
  2062. // 用户按键
  2063. const strDigit = data.Param.Digit;
  2064. console.log(
  2065. '用户按键[' + strDigit + '];记录ID[' + callId.value + '];主叫号码[' + strTelNumber + '];被叫号码[' + strCalledNum + ']',
  2066. '来电弹屏'
  2067. );
  2068. // 来电 振铃弹单
  2069. router.push({
  2070. name: 'orderAccept',
  2071. query: {
  2072. createBy: 'tel',
  2073. fromTel: strTelNumber, // 来电号码
  2074. callId: callId.value, // 通话ID
  2075. transfer: strCalledNum, // 转接来源(如12345,12333)
  2076. telArea: '',
  2077. identityType: strDigit, // 按键接收(2:市民 1:企业) 默认市民
  2078. },
  2079. });
  2080. if (AppConfigInfo.value.isOpenSpecialPhone) {
  2081. // 配置开关
  2082. getSpecialNumberDetailByPhone({ PhoneNumber: strTelNumber }).then((res: any) => {
  2083. // 如果来电电话在在特殊号码配置中 需要提示信息
  2084. if (res.result.notes) {
  2085. ElNotification({
  2086. title: '特殊号码提醒',
  2087. message: res.result.notes,
  2088. type: 'info',
  2089. });
  2090. }
  2091. });
  2092. }
  2093. }
  2094. }
  2095. const request = {
  2096. creationTime: new Date(),
  2097. name: `兴唐呼叫中心呼入振铃事件消息,分机号:${m_strUserNo.value},工号:${m_strJobNum.value},技能组:${m_strSkillId.value}`,
  2098. remark: JSON.stringify(data),
  2099. executeUrl: themeConfig.value.callCenterSocketUrl,
  2100. };
  2101. submitLogFn(request);
  2102. };
  2103. /*
  2104. * 应答事件
  2105. * Caller:主叫
  2106. * Called:被叫
  2107. * Callid:呼叫ID
  2108. {“Event”:”EvtCallAnswer”,”Param”:{“Caller”:”123”,”Called”:”456”,”Callid”:”94593939”}}
  2109. */
  2110. const talkTime = ref(0); // 通话时长
  2111. const talkTimer = useIntervalFn(
  2112. () => {
  2113. talkTime.value += 1;
  2114. Local.set('talkTime', String(talkTime.value));
  2115. },
  2116. 1000,
  2117. { immediate: false }
  2118. );
  2119. // 开始通话计时
  2120. const startTalkTimer = () => {
  2121. let localTalkTime = Local.get('talkTime');
  2122. if (talkTime.value) {
  2123. talkTime.value = Number(localTalkTime);
  2124. }
  2125. talkTimer.resume();
  2126. stopIdleTime();
  2127. };
  2128. // 结束通话计时
  2129. const stopTalkTimer = () => {
  2130. talkTimer.pause();
  2131. talkTime.value = 0;
  2132. Local.remove('talkTime');
  2133. };
  2134. const evtEvtCallAnswer = (data: any) => {
  2135. let strCalledNum: string;
  2136. let strTelNumber: string;
  2137. // 通话接通
  2138. m_bCallConnect.value = true;
  2139. globalState.callCenterIsOnThePhone = true;
  2140. console.log(
  2141. `接通电话,是否呼出:${m_IsCallOut.value},是否咨询:${m_IsConsult.value},是否呼出弹屏:${m_CallOutOpen.value},是否弹屏:${
  2142. m_bIsOpen.value
  2143. },弹屏方式:${m_strOpenFlag.value === '1' ? '接通弹屏' : '振铃弹屏'}`
  2144. );
  2145. if (m_IsCallOut.value) {
  2146. // 呼出
  2147. if (!m_IsConsult.value) {
  2148. // 不是转接、三方会议呼出
  2149. // 呼出应答
  2150. m_strTelState.value = '303';
  2151. e_TopStateChange(m_strTelState.value);
  2152. // 主叫号码
  2153. strTelNumber = data.Param.Caller;
  2154. // 被叫号码
  2155. strCalledNum = data.Param.Called;
  2156. // 呼叫ID
  2157. callId.value = data.Param.Callid;
  2158. mittBus.emit('outboundConnect', {
  2159. callNumber: strCalledNum, // 被叫号码
  2160. callId: callId.value, //呼叫ID
  2161. }); // 外呼接通之后收到的消息
  2162. if (m_CallOutOpen.value) {
  2163. // 呼出是否弹屏(用于未接统计“回拨”业务处理)
  2164. if (!m_bIsOpen.value && m_strOpenFlag.value === '1') {
  2165. console.log(
  2166. '呼出是否弹屏[' +
  2167. m_bIsOpen.value +
  2168. '];弹屏方式[' +
  2169. m_strOpenFlag.value +
  2170. '];记录ID[' +
  2171. callId.value +
  2172. '];主叫号码[' +
  2173. strTelNumber +
  2174. '];被叫号码[' +
  2175. strCalledNum +
  2176. ']'
  2177. );
  2178. m_bIsOpen.value = true;
  2179. // 呼出不再弹单 接通弹单
  2180. m_CallOutOpen.value = false;
  2181. router.push({
  2182. name: 'orderAccept',
  2183. query: {
  2184. createBy: 'tel',
  2185. fromTel: strTelNumber, // 来电号码
  2186. callId: callId.value, // 通话ID
  2187. transfer: strCalledNum, // 转接来源(如12345,12333)
  2188. telArea: '',
  2189. identityType: '', // 按键接收(2:市民 1:企业) 默认市民
  2190. },
  2191. });
  2192. if (AppConfigInfo.value.isOpenSpecialPhone) {
  2193. // 配置开关
  2194. getSpecialNumberDetailByPhone({ PhoneNumber: strTelNumber }).then((res: any) => {
  2195. // 如果来电电话在在特殊号码配置中 需要提示信息
  2196. if (res.result.notes) {
  2197. ElNotification({
  2198. title: '特殊号码提醒',
  2199. message: res.result.notes,
  2200. type: 'info',
  2201. });
  2202. }
  2203. });
  2204. }
  2205. }
  2206. }
  2207. }
  2208. } else {
  2209. // 呼入应答
  2210. m_strTelState.value = '301';
  2211. e_TopStateChange(m_strTelState.value);
  2212. // 呼入应答
  2213. // 主叫号码
  2214. strTelNumber = data.Param.Caller;
  2215. // 被叫号码
  2216. strCalledNum = data.Param.Called;
  2217. // 呼叫ID
  2218. callId.value = data.Param.Callid;
  2219. console.log(
  2220. '是否弹屏[' +
  2221. m_bIsOpen.value +
  2222. '];弹屏方式[' +
  2223. m_strOpenFlag.value +
  2224. '];记录ID[' +
  2225. callId.value +
  2226. '];主叫号码[' +
  2227. strTelNumber +
  2228. '];被叫号码[' +
  2229. strCalledNum +
  2230. ']'
  2231. );
  2232. if (strTelNumber.length == strCalledNum.length && strTelNumber.length == 4) {
  2233. // 如果主叫号码、被叫号码都是分机号码,则不弹屏
  2234. m_bIsOpen.value = true;
  2235. }
  2236. if (!m_bIsOpen.value && m_strOpenFlag.value === '1') {
  2237. m_bIsOpen.value = true;
  2238. // 用户按键
  2239. const strDigit = data.Param.Digit;
  2240. console.log(
  2241. '用户按键' +
  2242. strDigit +
  2243. '是否弹屏[' +
  2244. m_bIsOpen.value +
  2245. '];弹屏方式[' +
  2246. m_strOpenFlag.value +
  2247. '];记录ID[' +
  2248. callId.value +
  2249. '];主叫号码[' +
  2250. strTelNumber +
  2251. '];被叫号码[' +
  2252. strCalledNum +
  2253. ']'
  2254. );
  2255. // 来电 接通弹单
  2256. router.push({
  2257. name: 'orderAccept',
  2258. query: {
  2259. createBy: 'tel',
  2260. fromTel: strTelNumber, // 来电号码
  2261. callId: callId.value, // 通话ID
  2262. transfer: strCalledNum, // 转接来源(如12345,12333)
  2263. telArea: '',
  2264. identityType: strDigit, // 按键接收(2:市民 1:企业) 默认市民
  2265. },
  2266. });
  2267. }
  2268. }
  2269. if (!m_IsConsult.value) {
  2270. // 如果不是咨询,则启动通话时间计时器
  2271. startTalkTimer(); // 通话计时器开始
  2272. }
  2273. const request = {
  2274. creationTime: new Date(),
  2275. name: `兴唐呼叫中心应答事件消息,分机号:${m_strUserNo.value},工号:${m_strJobNum.value},技能组:${m_strSkillId.value}`,
  2276. remark: JSON.stringify(data),
  2277. executeUrl: themeConfig.value.callCenterSocketUrl,
  2278. };
  2279. submitLogFn(request);
  2280. };
  2281. /*
  2282. * 39.外呼振铃事件
  2283. * Caller:主叫
  2284. * Called:被叫
  2285. * Callid:呼叫ID
  2286. {“Event”:”EvtOutCalling”,”Param”:{“Caller”:”123”,”Called”:”456”,”Customerid”:”675”,”Callid”:”94593939”}}
  2287. */
  2288. const evtEvtCalling = (data: any) => {
  2289. let strCalledNum: string;
  2290. let strTelNumber: string;
  2291. m_IsCallOut.value = true; // 呼出
  2292. console.log(
  2293. `开始振铃,是否呼出:${m_IsCallOut.value},是否呼出弹屏:${m_CallOutOpen.value},是否弹屏:${m_bIsOpen.value},弹屏方式:${
  2294. m_strOpenFlag.value === '1' ? '接通弹屏' : '振铃弹屏'
  2295. }`
  2296. );
  2297. if (m_CallOutOpen.value) {
  2298. // 主叫号码
  2299. strTelNumber = data.Param.Caller;
  2300. // 被叫号码
  2301. strCalledNum = data.Param.Called;
  2302. // 呼叫ID
  2303. callId.value = data.Param.Callid;
  2304. if (!m_bIsOpen.value && m_strOpenFlag.value === '2') {
  2305. //振铃弹屏
  2306. m_bIsOpen.value = true;
  2307. console.log(
  2308. '呼出是否弹屏[' +
  2309. m_bIsOpen.value +
  2310. '];弹屏方式[' +
  2311. m_strOpenFlag.value +
  2312. '];记录ID[' +
  2313. callId.value +
  2314. '];主叫号码[' +
  2315. strTelNumber +
  2316. '];被叫号码[' +
  2317. strCalledNum +
  2318. ']'
  2319. );
  2320. // 外呼振铃弹单
  2321. router.push({
  2322. name: 'orderAccept',
  2323. query: {
  2324. createBy: 'tel',
  2325. fromTel: strTelNumber, // 来电号码
  2326. callId: callId.value, // 通话ID
  2327. transfer: strCalledNum, // 转接来源(如12345,12333)
  2328. telArea: '',
  2329. identityType: '', // 按键接收(2:市民 1:企业) 默认市民
  2330. },
  2331. });
  2332. }
  2333. }
  2334. const request = {
  2335. creationTime: new Date(),
  2336. name: `兴唐呼叫中心外呼振铃消息,分机号:${m_strUserNo.value},工号:${m_strJobNum.value},技能组:${m_strSkillId.value}`,
  2337. remark: JSON.stringify(data),
  2338. executeUrl: themeConfig.value.callCenterSocketUrl,
  2339. };
  2340. submitLogFn(request);
  2341. };
  2342. /*
  2343. * 计算接通时长
  2344. */
  2345. // 是否在队列状态
  2346. const m_bIsQueue = ref(false);
  2347. /*
  2348. * 队列等待
  2349. */
  2350. const i_QueueNum = (data: any) => {
  2351. // 40,87098300,85961020,1,80|40,87098300,85961020,1,80|40,87098300,85961020,1,80
  2352. // 处于队列状态
  2353. m_bIsQueue.value = true;
  2354. // 队列信息
  2355. const strInfo = data.Param.Info;
  2356. if (undefined != strInfo && '' != strInfo) {
  2357. const arrFirst = strInfo.split('|');
  2358. if (null != arrFirst && 0 < arrFirst.length) {
  2359. // 设置队列等待数量
  2360. console.log(arrFirst.length - 1);
  2361. currentWait.value = arrFirst.length - 1;
  2362. e_SetQueryWait(arrFirst.length - 1, strInfo);
  2363. } else {
  2364. currentWait.value = 0;
  2365. }
  2366. console.log(`${getNowDateTime()}:队列消息1:`, strInfo, arrFirst.length - 1);
  2367. } else {
  2368. currentWait.value = 0;
  2369. }
  2370. console.log(`${getNowDateTime()}:队列消息2:`, data.Param);
  2371. };
  2372. /*
  2373. 语音识别结果保存
  2374. */
  2375. const e_EvtRecognize = (data: any) => {
  2376. console.log('EvtRecognize' + JSON.stringify(data));
  2377. const strDirection = data.Direciton || '';
  2378. const strResult = data.Result || '';
  2379. console.log(`${getNowDateTime()}:识别结果`, strResult, strDirection);
  2380. };
  2381. /*
  2382. 转三方接通状态
  2383. */
  2384. const e_EvtDispatchState = (data: any) => {
  2385. const strState = data.State || '';
  2386. console.log(`${getNowDateTime()}:转接三方状态-EvtDispatchState:`, data, `是否咨询状态:${m_IsConsult.value},当前str:${strState}`);
  2387. if (strState === '2') {
  2388. if (m_IsConsult.value) {
  2389. // 咨询成功
  2390. m_strTelState.value = '330';
  2391. e_TopStateChange(m_strTelState.value);
  2392. }
  2393. } else if (strState === '3') {
  2394. if (m_IsConsult.value) {
  2395. m_strTelState.value = '301';
  2396. m_IsHold.value = false; // 三方有一方挂断 取消保持
  2397. e_TopStateChange(m_strTelState.value);
  2398. }
  2399. } else {
  2400. //alert("转接失败");
  2401. }
  2402. };
  2403. /**
  2404. * 异常处理
  2405. * @param {any} data
  2406. */
  2407. const retResError = (data: any) => {
  2408. if (data.Param.Result == '99') {
  2409. // 掉线
  2410. m_strTelState.value = '0';
  2411. e_TopStateChange(m_strTelState.value);
  2412. globalState.callCenterWs = null;
  2413. ElMessage.error('连接已断开');
  2414. // 自动签入
  2415. onSignIn();
  2416. }
  2417. };
  2418. // 改变状态方法
  2419. const e_TopStateChange = (state: string) => {
  2420. console.log(`${getNowDateTime()}:状态改变:`, state);
  2421. switch (state) {
  2422. case '0': // 签出
  2423. break;
  2424. case '100': // 登录成功
  2425. break;
  2426. case '200': // 空闲
  2427. break;
  2428. case '201': // 小休
  2429. break;
  2430. case '300': //呼入振铃
  2431. break;
  2432. case '301': // 呼入通话
  2433. stopConferenceTime();
  2434. break;
  2435. case '302': // 呼出振铃
  2436. break;
  2437. case '303': // 呼出通话
  2438. stopConferenceTime();
  2439. break;
  2440. case '310': // 通话保持
  2441. break;
  2442. case '320': // 三方会议
  2443. startConferenceTime(); // 三方会议时长开始
  2444. break;
  2445. case '330': // 转接
  2446. break;
  2447. case '331': // 转接
  2448. break;
  2449. case '900': // 整理
  2450. break;
  2451. }
  2452. // console.log(state);
  2453. };
  2454. /**
  2455. * 更新话机动作
  2456. * 1:登录登出;2:小休示闲;3:摘机
  2457. */
  2458. const e_ActionUpdate = (strActionType: string) => {
  2459. const data = {
  2460. Action: 'ActionUpdate',
  2461. ActType: strActionType,
  2462. };
  2463. };
  2464. /*
  2465. * 用户分机签入签出(用户分机分离业务)
  2466. * 业务改造,先签入我方业务系统,再签入呼叫中心
  2467. *
  2468. */
  2469. const e_TelSignIn = async (telNo: string | null, groupId: string) => {
  2470. const data = {
  2471. telNo,
  2472. groupId,
  2473. telModelState: 1,
  2474. };
  2475. try {
  2476. const { result } = await callCenterSignIn(data);
  2477. console.log(`${getNowDateTime()}:业务系统:签入成功`, result);
  2478. } catch (e) {
  2479. console.log(e);
  2480. }
  2481. };
  2482. /*
  2483. * 用户分机签出(用户分机分离业务)
  2484. */
  2485. const e_TelSignOut = async (telNo: string | null, groupId: string) => {
  2486. const data = {
  2487. telNo,
  2488. groupId,
  2489. telModelState: 1,
  2490. };
  2491. try {
  2492. await callCenterSignOut(data);
  2493. console.log(`${getNowDateTime()}:业务系统:签出成功`);
  2494. } catch (e) {
  2495. console.log(e);
  2496. }
  2497. };
  2498. /**
  2499. * 保存队列信息
  2500. * @param {any} strUserNum
  2501. * @param strQueueInfo
  2502. */
  2503. const e_SetQueryWait = (strUserNum: string | number, strQueueInfo: any) => {
  2504. const data = {
  2505. Action: 'SaveQueueNumNew',
  2506. QueueNum: strUserNum,
  2507. QueueInfo: strQueueInfo,
  2508. };
  2509. };
  2510. // 获取基础信息
  2511. const telsList = ref([]);
  2512. const getBaseInfo = async () => {
  2513. try {
  2514. const { result } = await getCallCenterList();
  2515. telsList.value = result ?? [];
  2516. } catch (e) {
  2517. console.log(e);
  2518. }
  2519. };
  2520. // 查询三方会议和转接的号码
  2521. const threeWayAndTransfer = ref<EmptyArrayType>([]);
  2522. const getThreeWayAndTransfer = async () => {
  2523. try {
  2524. const { result } = await getDataByCode('TransferNumber');
  2525. threeWayAndTransfer.value = result ?? [];
  2526. } catch (err) {
  2527. console.log(`${getNowDateTime()}:获取转接和三方会议的号码错误`, err);
  2528. }
  2529. };
  2530. // 检查登录状态
  2531. const userAlreadyLogin = ref(false); // 用户已经登录
  2532. const checkLogin = async () => {
  2533. try {
  2534. const { result } = await getCallCenterStatus();
  2535. console.log(`${getNowDateTime()}:检测呼叫中心签入状态:`, result);
  2536. if (result) {
  2537. // 如果查询到登录状态
  2538. wsRef.value.open();
  2539. userAlreadyLogin.value = true;
  2540. m_strUserNo.value = result.telNo; // 分机号
  2541. m_strJobNum.value = <string>result.staffNo; // 工号
  2542. m_strSkillId.value = result.queueId; // 技能组
  2543. signTime.value = result.second ?? 0; // 签入时长
  2544. globalState.currentTel = {
  2545. telNo: m_strUserNo.value,
  2546. telGroup: m_strSkillId.value,
  2547. jobNum: m_strJobNum.value,
  2548. };
  2549. } else {
  2550. userAlreadyLogin.value = false;
  2551. }
  2552. } catch (e) {
  2553. console.log(e);
  2554. userAlreadyLogin.value = false;
  2555. }
  2556. };
  2557. // 今日等待
  2558. const todayWait = ref(0);
  2559. const todayWaitValue = useTransition(todayWait, {
  2560. duration: 500,
  2561. });
  2562. // 当前等待
  2563. const currentWait = ref<number>(0);
  2564. const currentWaitValue = useTransition(currentWait, {
  2565. duration: 500,
  2566. });
  2567. // 获取小休原因
  2568. const getReason = async () => {
  2569. try {
  2570. // 查询小休原因
  2571. const { result } = await telRestBaseData();
  2572. state.restReasonOptions = result?.restReason ?? [];
  2573. } catch (err) {
  2574. console.log(err);
  2575. }
  2576. };
  2577. // 监听消息
  2578. const signalRStart = () => {
  2579. signalR?.SR?.on('RestApplyPass', (data: any) => {
  2580. // 小休审批通过消息
  2581. console.log(`${getNowDateTime()}:小休审批通过消息`, data);
  2582. RestApplyPassFn(data);
  2583. });
  2584. };
  2585. // 小休审批通过消息
  2586. const RestApplyPassFn = (data: any) => {
  2587. ElNotification({
  2588. title: '成功',
  2589. message: '小休审批通过,开始小休',
  2590. type: 'success',
  2591. });
  2592. isRestAudit.value = false;
  2593. const objMsg = {
  2594. Action: 'ReqAgentBusy',
  2595. Param: {
  2596. Extension: m_strUserNo.value,
  2597. },
  2598. };
  2599. // 发送请求
  2600. e_TelSendMsg(objMsg);
  2601. console.log(`${getNowDateTime()}:小休审核通过,${data}`);
  2602. };
  2603. onMounted(async () => {
  2604. await getBaseInfo(); // 查询可以签入的分机
  2605. await getThreeWayAndTransfer(); // 查询呼叫转接的号码列表
  2606. // await getReason(); // 获取小休原因
  2607. initWs();
  2608. await checkLogin();
  2609. signalRStart();
  2610. // 是否在通话中
  2611. window.onbeforeunload = function (e: any) {
  2612. if (m_bCallConnect.value) {
  2613. const dialogText = '正在通话中,您确定要刷新吗?';
  2614. e.returnValue = dialogText;
  2615. return dialogText;
  2616. }
  2617. };
  2618. });
  2619. onBeforeUnmount(() => {
  2620. signalR.SR.off('RestApplyPass');
  2621. });
  2622. </script>
  2623. <style scoped lang="scss">
  2624. .seizeSeat-box {
  2625. display: none;
  2626. }
  2627. .phoneControls {
  2628. display: flex;
  2629. flex: 1;
  2630. padding: 0 10px;
  2631. color: var(--hotline-color-text-main);
  2632. height: 100%;
  2633. align-items: center;
  2634. .status-box {
  2635. width: 240px;
  2636. display: flex;
  2637. align-items: center;
  2638. justify-content: space-between;
  2639. position: relative;
  2640. box-sizing: border-box;
  2641. cursor: pointer;
  2642. text-align: left;
  2643. padding: 4px 26px 4px 12px;
  2644. gap: 6px;
  2645. min-height: 32px;
  2646. line-height: 24px;
  2647. border-radius: var(--el-border-radius-round);
  2648. background-color: var(--el-color-info-light-8);
  2649. transition: var(--el-transition-duration);
  2650. .arrow {
  2651. position: absolute;
  2652. right: 10px;
  2653. transition: transform var(--el-transition-duration);
  2654. }
  2655. .is-reverse {
  2656. transform: rotate(180deg);
  2657. }
  2658. }
  2659. // 按钮列表
  2660. .btn-container {
  2661. display: flex;
  2662. justify-content: space-between;
  2663. width: calc(100% - 100px);
  2664. height: 100%;
  2665. border-right: 1px solid var(--el-border-color);
  2666. border-left: 1px solid var(--el-border-color);
  2667. overflow: hidden;
  2668. .item {
  2669. text-align: center;
  2670. cursor: pointer;
  2671. width: 100%;
  2672. user-select: none;
  2673. display: flex;
  2674. align-items: center;
  2675. justify-content: center;
  2676. //border-right: 1px solid var(--el-border-color);
  2677. .icon {
  2678. color: var(--el-color-primary);
  2679. }
  2680. &.disabled {
  2681. cursor: not-allowed;
  2682. overflow: hidden;
  2683. .icon {
  2684. color: #cccccc;
  2685. }
  2686. }
  2687. }
  2688. .active {
  2689. &:hover {
  2690. color: var(--hotline-color-white);
  2691. background-color: var(--el-color-primary);
  2692. .icon {
  2693. color: var(--hotline-color-white);
  2694. }
  2695. }
  2696. }
  2697. }
  2698. .wait-box {
  2699. width: 150px;
  2700. display: flex;
  2701. flex-direction: column;
  2702. justify-content: center;
  2703. text-align: center;
  2704. border-right: 1px solid var(--el-border-color);
  2705. height: 100%;
  2706. }
  2707. }
  2708. </style>