Dialer.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <template>
  2. <div class="dialer-container">
  3. <!-- 显示输入的电话号码 -->
  4. <el-input type="text" v-model="currentPhoneNumber" readonly />
  5. <div class="dialer-buttons">
  6. <!-- 循环生成数字按钮 -->
  7. <el-button
  8. v-for="number in numbers"
  9. type="primary"
  10. :key="number"
  11. :class="{ 'special-number': number === '1' }"
  12. @click="appendNumber(number)"
  13. >
  14. {{ number }}
  15. </el-button>
  16. <audio v-show="false" controls autoplay="autoplay" id="audioCtl"></audio>
  17. </div>
  18. <!-- 语音输入按钮 -->
  19. <el-button type="info" @click="startVoiceInput"> 语音输入 </el-button>
  20. <!-- 清除输入的电话号码 -->
  21. <el-button type="warning" @click="clearNumber">清除</el-button>
  22. <!-- 模拟拨号操作 -->
  23. <el-button type="success" @click="callNumber">拨号</el-button>
  24. <!-- 模拟拨号操作 -->
  25. <el-button type="danger" @click="downNumber">挂断</el-button>
  26. </div>
  27. </template>
  28. <script>
  29. export default {
  30. props: {
  31. phoneNumber: {
  32. type: String,
  33. default: "",
  34. },
  35. },
  36. data() {
  37. return {
  38. id: this.generateGuid(),
  39. baseUrl: "api/webrtc/",
  40. pc: null,
  41. // 拨号盘上的数字和符号
  42. numbers: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"],
  43. };
  44. },
  45. computed: {
  46. getOfferUrl() {
  47. return `${this.baseUrl}getoffer?id=${this.id}`;
  48. },
  49. setAnswerUrl() {
  50. return `${this.baseUrl}setanswer?id=${this.id}`;
  51. },
  52. setIceCandidateUrl() {
  53. return `${this.baseUrl}addicecandidate?id=${this.id}`;
  54. },
  55. currentPhoneNumber: {
  56. get() {
  57. return this.phoneNumber;
  58. },
  59. set(value) {
  60. // 当输入框的值改变时,通过自定义事件将新值传递给父组件
  61. this.$emit("update:phoneNumber", value);
  62. },
  63. },
  64. },
  65. methods: {
  66. appendNumber(number) {
  67. this.currentPhoneNumber += number;
  68. },
  69. clearNumber() {
  70. this.currentPhoneNumber = "";
  71. },
  72. callNumber() {
  73. if (this.currentPhoneNumber) {
  74. alert(`正在拨打号码: ${this.currentPhoneNumber}`);
  75. this.start();
  76. } else {
  77. alert("请输入电话号码");
  78. }
  79. },
  80. downNumber() {
  81. this.closePeer();
  82. },
  83. startVoiceInput() {
  84. const recognition = new (window.SpeechRecognition ||
  85. window.webkitSpeechRecognition)();
  86. recognition.lang = "zh-CN";
  87. recognition.onresult = (event) => {
  88. const transcript = event.results[0][0].transcript;
  89. this.currentPhoneNumber = transcript.replace(/[^0-9*#]/g, "");
  90. };
  91. recognition.start();
  92. },
  93. generateGuid() {
  94. return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
  95. /[xy]/g,
  96. function (c) {
  97. var r = (Math.random() * 16) | 0,
  98. v = c == "x" ? r : (r & 0x3) | 0x8;
  99. return v.toString(16);
  100. }
  101. );
  102. },
  103. async start() {
  104. this.closePeer();
  105. let videoControl = document.querySelector("#audioCtl");
  106. const localStream = await navigator.mediaDevices.getUserMedia({
  107. video: false,
  108. audio: true,
  109. });
  110. videoControl.srcObject = localStream;
  111. this.pc = new RTCPeerConnection(null);
  112. localStream.getTracks().forEach((track) => {
  113. console.log("add local track " + track.kind + " to peer connection.");
  114. console.log(track);
  115. this.pc.addTrack(track, localStream);
  116. });
  117. this.pc.onicegatheringstatechange = function () {
  118. console.log("onicegatheringstatechange: " + this.pc.iceGatheringState);
  119. }.bind(this);
  120. this.pc.oniceconnectionstatechange = function () {
  121. console.log(
  122. "oniceconnectionstatechange: " + this.pc.iceConnectionState
  123. );
  124. }.bind(this);
  125. this.pc.onsignalingstatechange = function () {
  126. console.log("onsignalingstatechange: " + this.pc.signalingState);
  127. }.bind(this);
  128. this.pc.onicecandidate = async function (event) {
  129. if (event.candidate) {
  130. console.log("new-ice-candidate:");
  131. console.log(event.candidate.candidate);
  132. console.log(event.candidate);
  133. await fetch(this.setIceCandidateUrl, {
  134. method: "POST",
  135. body: JSON.stringify(event.candidate),
  136. headers: { "Content-Type": "application/json" },
  137. });
  138. }
  139. }.bind(this);
  140. let offerResponse = await fetch(this.getOfferUrl);
  141. let offer = await offerResponse.json();
  142. console.log("got offer: " + offer.type + " " + offer.sdp + ".");
  143. await this.pc.setRemoteDescription(offer);
  144. this.pc
  145. .createAnswer()
  146. .then(
  147. function (answer) {
  148. return this.pc.setLocalDescription(answer);
  149. }.bind(this)
  150. )
  151. .then(
  152. async function () {
  153. console.log("Sending answer SDP.");
  154. console.log("SDP: " + this.pc.localDescription.sdp);
  155. await fetch(this.setAnswerUrl, {
  156. method: "POST",
  157. body: JSON.stringify(this.pc.localDescription),
  158. headers: { "Content-Type": "application/json" },
  159. });
  160. }.bind(this)
  161. );
  162. },
  163. closePeer() {
  164. if (this.pc != null) {
  165. console.log("close peer");
  166. this.pc.close();
  167. }
  168. },
  169. },
  170. };
  171. </script>
  172. <style scoped>
  173. .dialer-container {
  174. margin: 20px auto;
  175. width: 300px;
  176. text-align: center;
  177. font-weight: bold; /* 文字加粗 */
  178. }
  179. .dialer-buttons {
  180. margin: 10px auto;
  181. display: grid;
  182. grid-template-columns: repeat(3, 3fr);
  183. gap: 4px;
  184. }
  185. .dialer-buttons button {
  186. padding: 10px;
  187. font-size: 20px;
  188. }
  189. button {
  190. font-size: 20px;
  191. margin: 5px;
  192. }
  193. /* 定义数字 1 的特殊样式 */
  194. .special-number {
  195. margin-left: 10px;
  196. color: #ffffff; /* 文字颜色为白色 */
  197. }
  198. </style>