web 语音通话 jssip

news2024/9/29 19:19:11

先把封装好的地址安上(非本人封装):webrtc-webphone: 基于JsSIP开发的webrtc软电话

jssip中文文档:jssip中文开发文档(完整版) - 简书

jssip使用文档:(我没有运行过,但是他写的很清楚,反正比我好)jssip+webrtc+freeswitch实现电话网页及遇到的488状态码问题_freeswitch 488_weixin_39715323的博客-CSDN博客

正常使用由于web限制应该在https上使用,但是http也不是不可以,我放到下一篇文章了,这就说怎么使用

我这用的是webrtc+jssip

webrtc-webphone已经实现了我的需求,所以我没有使用原生jssip

特主要实现的功能有:注册,拨叫,接听,保持,恢复

我的项目中需要静音(指我不能说话,通话人可以讲话)

所以我将hold(保持)和unhold(恢复)改成了mute和unmute

 下面是完整代码和方法说明:

1.init(注册citbar)config中需要使用ip地址、端口号、extNo拨号人、extPwd密码

2.handleAgentBarBtnClick(通话状态更改)

        makecall(拨号)

        hangup(挂断)

        hold(静音)

        unhold(取消静音)

3.onbeforeunload (通话中刷新对讲群组中没有退出,导致群组中有多个同一个人)

4.beforeDestroy(切换页面后没有退出群组,刷新不走这个方法)

注意:

1.不要重复拨号,状态卡住后就能同一个设备对话了bug

2.如果想切换页面还能通话就不要beforeDestroy,但是在其他页面在回来时会导致状态不一致,而且容易出现卡状态,所以我将通话操作放在index最顶层里面

<template>
  <div class="top">
   
    <div style="padding: 0 30px;">
      <div style="display: flex;padding-top: 3px;">

        <!-- 设备树插件 -->
        <organizationTree ref="organizationTree"></organizationTree>

        <div style="width:77vw;height:80vh; margin-top: 5px;">
          <div class="photo_date" style="position:relative">
            <div style="display:flex;min-width:30%">
              <div class="text_type photo_deviceName " v-show="groupName" style="text-align: center;">
                {{groupName}}</div>
              <div class="text_type" :class="meetingStatus==2?' meetingName1':' meetingName'"
                style="width: 125px;text-align: center;">
                {{meetingStatus==2?meetingName:meetingName1}}</div>
            </div>
            <div style="position: absolute;right: 0;" v-show="createBy==userName">

              <div v-show="meetingStatus==2" class="btn_type photo_meeting_btn" @click="meetingAll"
                style="position:absolute; right:135px;width: 140px;height: 35px;line-height: 35px;">
                邀请全部成员</div>
              <div class="bg_btn_type photo_meeting_btn" @click="meeting"
                style="position:absolute; right:0;width: 125px;height: 35px;line-height: 35px;">
                {{meetingStatus==1?'开始会议':'结束会议'}}</div>

            </div>
          </div>

          <div class="">
            <div class="deviceList_title">
              <div class="deviceList_title_text" style="width:15%">名称</div>
              <div class="deviceList_title_text" style="width:15%">imei</div>
              <div class="deviceList_title_text" style="width:15%">类型</div>
              <div class="deviceList_title_text" style="width:15%">状态</div>
              <div class="deviceList_title_text" style="width:15%">会议状态</div>
              <div class="deviceList_title_text" style="width:24%">操作</div>
              <!-- <div class="deviceList_title_text">操作</div> -->
            </div>
            <div v-show="deviceList" class="deviceList">

              <div class="deviceList_list" :class="item.id==itemId?'deviceList_list1':''"
                v-for="(item,index) in deviceList" :key="item.id" @mouseover="mouseover(item.id)"
                @mouseleave="mouseout()">
                <!-- <el-tooltip :content="item.name" placement="bottom" effect="light"> -->
                <div class="deviceList_title_text" style="width:15%">{{item.devName}}</div>
                <!-- </el-tooltip> -->
                <div class="deviceList_title_text" style="width:15%">{{item.imei}}</div>
                <div class="deviceList_title_text" style="width:15%">{{item.devTypeName}}
                </div>
                <div class="deviceList_title_text" style="width:15%">
                  {{dictionary(item.devStatus,'dev_status',item.imei)}}
                </div>
                <div class="deviceList_title_text" style="width:15%">
                  {{dictionary(item.memberStatus,'meeting_member_status')}}
                </div>
                <div style="width:24%;">
                  <div v-if="meetingStatus==2">
                    <div v-if="item.imei == createBy&&item.imei == userName">
                      <div v-if="item.devStatus==3||item.devStatus==4||item.devStatus==5||item.devStatus==1">
                        <div style="width:100%;justify-content: center;" class="deviceList_operate">
                          <div class="deviceList_title_text1" @click="speak('request')"
                            v-if="item.memberStatus!=3&&item.memberStatus!=1&&item.memberStatus!=4&&item.memberStatus!=5">
                            <div>开始发言</div>
                          </div>
                          <div class="deviceList_title_text1" @click="speak('request')" v-if="item.memberStatus==3">
                            <div>结束发言</div>
                          </div>
                          <div class="deviceList_title_text1" @click="outMeeting" v-if="item.memberStatus==2">离开会议
                          </div>
                          <div class="deviceList_title_text1" @click="inMeeting" v-if="item.memberStatus==1">进入会议
                          </div>
                        </div>
                      </div>
                    </div>
                    <div v-if="item.imei != createBy">
                      <div v-if="item.devStatus==3||item.devStatus==4||item.devStatus==5||item.devStatus==1">
                        <div style="width:100%;justify-content: center;" class="deviceList_operate"
                          v-show="createBy==userName&&isInOrOutMeeting&&deviceList[0].memberStatus==2">
                          <div class="deviceList_title_text1" @click="speak('call',item.imei,'start')"
                            v-if="item.memberStatus!=3&&item.memberStatus!=1&&item.memberStatus!=4&&item.memberStatus!=5">
                            <div>点名发言</div>
                          </div>
                          <div class="deviceList_title_text1" @click="speak('call',item.imei,'end')"
                            v-if="item.memberStatus==3">
                            <div>结束发言</div>
                          </div>
                          <div class="deviceList_title_text1" @click="inOrOut('out',item.imei)"
                            v-if="item.memberStatus==2">请离会议</div>
                          <div class="deviceList_title_text1" @click="inOrOut('in',item.imei)"
                            v-if="item.memberStatus==1">拉入会议</div>
                        </div>
                      </div>
                    </div>
                  </div>

                </div>
              </div>
            </div>
          </div>
        </div>

      </div>

    </div>
  </div>

</template>
<script>
  import Header from "../home/header/index";
  import Footer from "../home/footer/index";
  import webSocketClass from "@/utils/webSocket";
  import { postWarnStatus } from "@/api/AlarmRecord";
  import organizationTree from "./deviceTree/organizationTree"
  import { devicetree } from "@/api/system/deviceTree";
  import { timestampToTime } from "../../../utils/time.js"
  import { addGroup, delGroup, getGroup, listGroup, updateGroup, updateMeetingStatus, selectDeviceGroupDetailList, deviceRequestTalking, inOrOutMeeting } from "@/api/system/group";
  import { listData } from "@/api/system/dict/data";
  import Ctibar from './AgentBar/ctibar.js';
  var audio = document.getElementById('audio');

  var constraints = {
    audio: true,
    video: true,
    mandatory: {
      maxWidth: 640,
      maxHeight: 360
    }
  };
  URL = window.URL || window.webkitURL;

  var eventHandlers = {
    'progress': function (e) {
      console.log('call is in progress');
    },
    'failed': function (e) {
      console.log('call failed: ', e);
    },
    'ended': function (e) {
      console.log('call ended : ', e);
    },
    'confirmed': function (e) {
      console.log('call confirmed');
    }
  };
  export default {
    dicts: ["warn_type"],
    components: {
      Header,
      Footer,
      organizationTree
    },
    data() {
    
      return {
     
        // 以下群组
        isGroup: false,
        createBy: '',
        isInOrOutMeeting: false,
        isSpeak: false,
        itemId: '',
        meetingStatus: '',
        groupDetail: {},
        isMike: false,
        deviceList: null,
        meetingName1: "会议未开始",
        meetingName: "会议中",
        groupName: "",
        dataList: null,
        userId: JSON.parse(sessionStorage.getItem("userInfo")).userId,
        userName: JSON.parse(sessionStorage.getItem("userInfo")).userName,
        // 查询参数
        queryParams: {
          pageNum: 1,
          pageSize: 10,
          deptId: null,
          planName: null,
          status: null,
        },
        total: 0,
        deviceTreeList: [],
        title: "",
        num: 1,
        groupId: '',
        dictionaryList: [],
        websocket: null,

        //初始化SDK所需要的配置
        config: {
          host: '39.152.2.103',
          port: '5066',
          proto: false,
          extNo: '',
          extPwd: '20181231',
          autoRegister: true,
          debug: true,
          //stunServer: 'stun.1.google.com',   可自行修改使用stun服务器地址
          stateEventListener: this.stateEventListener
        },
        //坐席分机号
        agentNo: '',
        //客户号码
        customerNo: '',
        //拨号弹窗
        showDial: false,
        //转接弹窗
        showTransferDial: false,
        //转接号码
        transNum: '',
        numList: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#',],
        agentStatus: 'DISCONNECTED',
        statusMap: {
          CONNECTED: '已连接',
          DISCONNECTED: '网络断开',
          REGISTERED: '已注册',
          UNREGISTERED: '未注册',
          REGISTER_FAILED: '注册失败',
          IN_CALL: '通话中',
          INCOMING_CALL: '来电振铃',
          OUTGOING_CALL: '外呼振铃',
          HOLD: '保持中',
          CALL_END: '通话结束'
        },
        timer: null,
        timerString: '00:00:00',
        outNum: '',
        isHold: true
      };
    },
    computed: {
      classObject() {
        const bool1 = this.alarmArr.length > 1;
        const bool2 = this.alarmArr.length === 1;
        return {
          tanchuangbox: true,
          "tanchuangbox-height-multi": bool1,
          "tanchuangbox-height-single": bool2,
        };
      },
    },
    mounted() {

      this.init()
  
    },
    methods: {

      // 以下群组
      inMeeting() {
        this.loadingFun()
        var body = {
          devImei: this.userName,
          flag: 'in',
          groupId: this.groupId,
        }

        inOrOutMeeting(body).then(response => {
          if (response.data.success) {
          } else {
            this.$modal.msgError(response.data.message);
          }
          this.loading.close();
        },
          error => {
            this.loading.close();
          });




      },
      outMeeting() {
        this.loadingFun()
        var body = {
          devImei: this.userName,
          flag: 'out',
          groupId: this.groupId,
        }

        inOrOutMeeting(body).then(response => {

          if (response.data.message == '已离开!') {

          } else {
            this.$modal.msgError(response.data.message);
          }
          this.loading.close();
        },
          error => {
            this.loading.close();
          });




      },

      inOrOut(flag, devImei) {
        this.loadingFun()
        var body = {
          devImei: devImei,
          flag: flag,
          groupId: this.groupId,
        }
        inOrOutMeeting(body).then(response => {
          console.log("123123123", response)
          if (response.data.success) {
            this.$modal.msgSuccess(response.data.message);
          } else {
            this.$modal.msgError(response.data.message);
          }
          this.loading.close();
        },
          error => {
            this.loading.close();
          });
      },
      loadingFun() {
        this.loading = this.$loading({
          lock: true,
          text: 'Loading',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
        });
      },
      speak(type, devImei, flag) {
        this.loadingFun()
        var flagg = ''
        if (type == 'request') {
          flagg = !this.isSpeak ? 'start' : 'end'
          devImei = this.userName
        } else {
          flagg = flag
        }
        var body = {
          devImei: devImei,
          flag: flagg,
          groupId: this.groupId,
          type: type,
        }
        deviceRequestTalking(body).then(response => {
          if (response.data.success) {
            if (type == 'request') {

            }
            this.$modal.msgSuccess(response.data.message);
            //恢复发言
          } else {
            this.$modal.msgError(response.data.message);
            this.hold()
          }
          this.loading.close();
        },
          error => {
            this.loading.close();
          });
      },
      // 1、进入元素
      mouseover(index) {
        this.itemId = index
      },
      // 4、离开元素
      mouseout() {
        this.itemId = ''
      },
      ws() {
        console.log('{"groupId":' + this.groupId + '}')
        this.websocket.webSocketSendMsg('{"groupId":' + this.groupId + '}')

      },
      devStatus(devStatus) {
        for (const i in this.deviceList) {
          if (this.deviceList[i].devStatus == devStatus) {
            this.deviceList[i].devStatus = devStatus
          }
        }
      },
      dictionary(e, type, imei) {
        for (const i in this.dictionaryList) {
          if (this.createBy == imei && e == 4) {
            return '在线'
          }
          if (this.dictionaryList[i].dictValue == e && this.dictionaryList[i].dictType == type) {
            return this.dictionaryList[i].dictLabel
          }
        }
      },
      handleAdd() {
        // this.reset();
        // this.open = true;
        this.title = "添加群组";
      },

      tableRowClassName({ row, rowIndex }) {

        return 'photo';
      },
      rowClass({ row, rowIndex }) {


        return 'text-align: center;background-color: #1A1D30;color: #fff'

      },
      meetingAll() {
        var meetingStatusAll = 2
        var params = {
          groupId: this.groupId,
          meetingStatus: meetingStatusAll
        }
        updateMeetingStatus(params).then(response => {
          if (response.code == 200) {

            this.$modal.msgSuccess(response.msg);
          } else {
            this.$modal.msgError(response.msg);

          }
        });
      },

      meeting() {
        this.loadingFun()
        if (this.deviceList != null) {
          console.log('meetingStatusmeetingStatus', this.meetingStatus)
          var params = {
            groupId: this.groupId,
            meetingStatus: this.meetingStatus == 1 ? 2 : 1
          }
          updateMeetingStatus(params).then(response => {
            setTimeout(() => {
              if (response.code == 200) {
                if (this.meetingStatus == 1) {
                  if (sessionStorage.getItem('groupId') == this.groupId) {
                    this.handleAgentBarBtnClick('hangup')
                    sessionStorage.setItem('groupId', '')
                  }
                  this.isInOrOutMeeting = false
                  this.isHold = true

                  this.loading.close()
                }
              }
            }, 5000);

          });
        }
      },
      //孙组件向父组件传递数据
      wsMeetingStatus(isInterface) {
        console.log('isMeeting状态:', isInterface)
        this.meetingStatus = isInterface
      },
      groupDeviceItemClick(item) {
        // this.handleAgentBarBtnClick('hangup')
        this.isSpeak = false
        this.deviceList = []
        this.groupId = item.id
        this.meetingStatus = item.meetingStatus
        this.groupName = item.groupName
        this.createBy = item.createBy
        this.item = item
        var params = { groupInfoId: item.id, pageNum: 0, pageSize: 0, }
        selectDeviceGroupDetailList(params).then(response => {
          this.deviceList = response.rows
          this.total = this.deviceList.length
          this.ws()
          if (this.deviceList[0].memberStatus == 3 &&
            this.deviceList[0].imei == this.userName) {
            this.isSpeak = true;
          }
          if (
            this.deviceList[0].memberStatus == 2 &&
            this.deviceList[0].imei == this.userName
          ) {
            if (sessionStorage.getItem("groupId") == this.groupId) {
              this.isInOrOutMeeting = true
              return
            }
            if (sessionStorage.getItem("groupId")) {
            } else {
              this.call()
            }
          }
        });

      },




      onClickDialOutside(event) {
        console.log(event)
        this.showDial = false
      },
      onClickTransDialOutside(event) {
        this.showTransferDial = false;
      },
      handleAgentBarBtnClick(name) {
        console.log(name + '当前')
        if (name === 'login') {
          this.login();
        } else if (name === 'logout') {
          this.logout();
        } else if (name === 'answer') {
          this.answer();
        } else if (name === 'hangup') {
          this.hangup();
        } else if (name === 'makecall') {
          this.makeCall('9*' + this.groupId)
        } else if (name === 'hold') {
          this.hold();
        } else if (name === 'unhold') {
          this.unhold();
        } else if (name === 'transfer') {
          this.transfer(this.transNum)
        }
      },

      login() {
        this.init()
        Ctibar.register()
      },
      logout() {
        Ctibar.unregister()
      },
      makeCall(phone) {
        if (phone === "" || phone === undefined) {
          console.error('无效的号码,请重新输入!');
          return
        }
        Ctibar.makecall(phone);

      },
      hold() {
        Ctibar.hold();

      },
      unhold() {
        Ctibar.unhold();
      },
      answer() {
        Ctibar.answer();
      },
      hangup() {
        Ctibar.hangup();
      },
      transfer(phone) {
        console.info("触发转接", phone)
        Ctibar.transfer(phone);
      },
      //外呼拨号盘
      handleDialBtnClick(val) {
        this.outNum += val;
        this.$refs.outNumInput.focus();
      },
      //转接拨号盘
      handleTransDialBtnClick(val) {
        this.transNum += val;
        this.$refs.transNumInput.focus();
      },

      //参数为时间差秒数,返回这两个时间差并格式化
      computeTimeDiff(diff) {
        diff = Math.round(diff / 1000);
        let hour = Math.floor(diff / 3600).toString().padStart(2, '0');
        let min = Math.floor((diff - hour * 3600) / 60).toString().padStart(2, '0');
        let sec = (diff % 60).toString().padStart(2, '0');
        return hour + ':' + min + ':' + sec;
      },
      //重置时间
      restoreTime(origin) {
        clearInterval(this.timer);
        this.timerString = '00:00:00';
        this.timer = setInterval(() => {
          this.timerString = this.computeTimeDiff(new Date().getTime() - origin);
        }, 1000);
      },
      //状态变更回调
      stateEventListener(event, data) {
        //debug使用
        console.log('当前event为: ' + event + ', 当前data为: ' + JSON.stringify(data))

        this.agentStatus = event
        let origin = new Date().getTime();
        switch (event) {
          case "CONNECTED":
            this.agentNo = data.localAgent
            this.restoreTime(origin);
            break;

          case "DISCONNECTED":
            this.restoreTime(origin);
            break;

          case "UNREGISTERED":
            this.restoreTime(origin);
            break;

          case "OUTGOING_CALL":
            this.customerNo = data.otherLegNumber;
            this.restoreTime(origin);
            break;

          case "INCOMING_CALL":
            this.customerNo = data.otherLegNumber;
            //播放来电振铃音
            this.playRingMedia();
            this.restoreTime(origin);
            break;

          case "IN_CALL":
            this.stopPlayRingMedia();
            this.restoreTime(origin);

            // this.timer = setInterval(() => {
            // }, 1000);
            console.log('当前是否hold', this.isHold)
            if (this.isHold) {
              setTimeout(() => {
                this.handleAgentBarBtnClick('hold')
                this.isHold = false
                // 方法区
              }, 500);
            }

            break;

          case "CALL_END":
            this.stopPlayRingMedia();
            //挂机铃声
            this.playHangupMedia()
            this.restoreTime(origin);
            break;

          default:

        }
      },

      //播放挂机铃声
      playHangupMedia() {
        const _this = this;
        var hangupAudio = document.getElementById("hangupMediaAudioId")
        if (!hangupAudio) {
          hangupAudio = document.createElement('audio');
          hangupAudio.id = 'hangupMediaAudioId';
          hangupAudio.hidden = true;
          hangupAudio.src = 'wav/hangup.wav'
          document.body.appendChild(hangupAudio);
        }
        hangupAudio.play();
      },
      //播放来电振铃
      playRingMedia() {
        const _this = this;
        _this.stopPlayRingMedia();

        var ringAudio = document.getElementById("ringMediaAudioId")
        if (!ringAudio) {
          ringAudio = document.createElement('audio');
          ringAudio.id = 'ringMediaAudioId';
          ringAudio.hidden = true;
          ringAudio.src = 'wav/ring.wav';
          ringAudio.loop = 'loop';
          document.body.appendChild(ringAudio);
        }
        ringAudio.play();
      },
      //停止播放来电振铃
      stopPlayRingMedia() {
        const _this = this;
        var ringAudio = document.getElementById("ringMediaAudioId");
        if (ringAudio) {
          document.body.removeChild(ringAudio);
        }
      },
      //初始化方法
      init() {
        this.config.extNo = this.userName
        if (sessionStorage.getItem("freeSwitchWs") != null) {
          this.config.host = sessionStorage.getItem("freeSwitchWs").split(":")[0]
          this.config.port = sessionStorage.getItem("freeSwitchWs").split(":")[1]
        }
        Ctibar.initSDK(this.config)


        let url = `/ws/` + this.userId + '/group/device/push';
        this.websocket = new webSocketClass(url)
        this.websocket.getWebSocketMsg(evt => {
          // 客户端接收服务端返回的数据
          var data = JSON.parse(evt.data);
          console.log("websocket返回的数据123:", data);
          switch (data.flag) {
            case "group"://会议状态
              this.$refs.organizationTree.setGroupList(data, this.groupId)
              console.log("websocket返回的数据123:", data);
              break
            case "memberStatus"://成员状态
              for (const i in this.deviceList) {
                console.log('123123132', data.member);
                if (this.deviceList[i].imei == data.member) {
                  this.deviceList[i].devStatus = data.memberStatus;
                }
              }
              break
            case "memberMeetingStatus"://成员会议状态
              for (const i in this.deviceList) {
                if (this.deviceList[i].imei == data.member) {
                  this.deviceList[i].memberStatus = data.memberMeetingStatus;
                }
              }
              if (this.groupId == data.groupId && this.userName == data.member && this.createBy == data.member) {
                if (data.memberMeetingStatus == 3) {
                  this.isSpeak = true;
                  this.unhold()
                  console.log('数据this.isInOrOutMeeting1111', this.isSpeak)
                } else {
                  this.isSpeak = false;
                  this.hold()
                }
              }

              if (data.memberMeetingStatus == 4) {
                this.loading.close()
              }
              if (!this.isInOrOutMeeting && this.groupId == data.groupId && this.userName == data.member && this.createBy == data.member && data.memberMeetingStatus == 2) {
                this.hold()
                if (sessionStorage.getItem("groupId") &&
                  sessionStorage.getItem("groupId") !== this.groupId) {
                } else {
                  console.log('this.isInOrOutMeeting1111', data.memberMeetingStatus)
                  this.call()
                }
              }
              if (this.createBy == data.member && data.memberMeetingStatus == 1 && this.userName == data.member) {
                this.handleAgentBarBtnClick('hangup')
                sessionStorage.setItem("groupId", '')
                this.isInOrOutMeeting = false
                this.isSpeak = false
                this.isHold = true
              }
              break

          }
        })
      },
      setGroup(group) {
        this.isGroup = group
     
   
      },
      call() {
        this.handleAgentBarBtnClick('makecall')
        sessionStorage.setItem("groupId", this.groupId)
        this.isInOrOutMeeting = true
        if (this.deviceList[0].imei != this.createBy) {
          // this.groupDeviceItemClick(this.item)
        }
        var that = this
        window.onbeforeunload = (e) => {
          console.log('this.isInOrOutMeeting', that.isInOrOutMeeting)
          if (that.isInOrOutMeeting) {
            that.handleAgentBarBtnClick('hangup')
            sessionStorage.setItem("groupId", '')
            that.websocket.closeSocket()
            window.onbeforeunload = null
          }
        }
        this.loading.close()
        this.isInOrOutMeeting = true
        this.isHold = true
      }
    },
    beforeDestroy() {  //进行监听销毁
      console.log('1231232131232132131,', this.isInOrOutMeeting)
      if (this.isInOrOutMeeting) {
        this.handleAgentBarBtnClick('hangup')
        sessionStorage.setItem("groupId", '')
        this.websocket.closeSocket()
        window.onbeforeunload = null
      }
    },



  };
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/670737.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx服务器,在window系统中的使用(前端,nginx的应用)

简介&#xff1a;Nginx是一个轻量级、高性能的HTTP和反向代理web服务器&#xff0c;且支持电子邮件&#xff08;IMAP/POP3&#xff09;代理服务&#xff0c;特点是占用内存少&#xff0c;并发能力强&#xff0c;给我们来了很多的便利&#xff0c;国内大部分网站都有使用nginx&a…

18款奔驰S350升级后排座椅记忆功能,提升您乘坐舒适性

带有记忆功能的座椅可以存储三个的座椅设置以及行车电脑中的舒适性设置。只要按一下按钮就可以跳到记忆模式&#xff0c;让座椅回到上一次设置。

使用 BigQuery Omni,发现跨云地理空间分析的优势

【本文由 Cloud Ace 整理发布。Cloud Ace 是谷歌云全球战略合作伙伴&#xff0c;拥有 300 多名工程师&#xff0c;也是谷歌最高级别合作伙伴&#xff0c;多次获得 Google Cloud 合作伙伴奖。作为谷歌托管服务商&#xff0c;我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌云认证…

第十章详解synchronized锁升级

文章目录 升级的流程为什么要引入锁升级这套流程多线程访问情况具体流程 轻量级锁如何使用CAS实现轻量级锁CAS加锁成功CAS加锁失败CAS进行解锁 总结何时变为重量级锁 锁膨胀自旋优化 偏向锁主要作用偏向状态测试撤销偏向锁 撤销 - 调用对象 hashCode撤销 - 其它线程使用对象撤销…

js:codemirror实现在线代码编辑器代码高亮显示

CodeMirror is a versatile text editor implemented in JavaScript for the browser. It is specialized for editing code, and comes with a number of language modes and addons that implement more advanced editing functionality. 译文&#xff1a;CodeMirror是一个多…

第二章:软件工程师必备的网络基础

目录 一、网线的制作 二、集线器、交换机介绍 三、路由器的配置 一、网线的制作 1.1、水晶头 ​​​ 1.2、网线钳 1.3、网线的标准 T568A标准&#xff08;交叉线&#xff09;&#xff1a; 适用链接场合&#xff1a;电脑-电脑、交换机-交换机、集线器-集线器 接线顺序&…

【正点原子STM32连载】第三十九章 触摸屏实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html# 第三…

有源电力滤波器及配电能效平台在污水处理厂中的应用

【摘要】为减少污水处理设备产生的各次谐波&#xff0c;通过确定主要谐波源&#xff0c;检测和计算谐波分量&#xff0c;采用有源电力滤波器进行谐波治理&#xff0c;大幅降低了电力系统中的三相电流畸变率&#xff0c;提高了电能质量&#xff1b;抑制了谐波分量&#xff0c;减…

doris docker部署和本地化部署 1.2.4.1版本

写在前面 以下操作语句按顺序执行即可&#xff0c;注意切换目录的命令一定记得执行&#xff0c;如果需要改动的地方会有${}注释&#xff0c;其余不需要任何改动&#xff0c;默认安装版本为1.12.4&#xff08;稳定版&#xff09; 本地化部署 下载 # 创建目录 mkdir /data/sof…

软件测试日常工作和前景是怎么样的?

笔者从测试的工作情况&#xff0c;职业发展&#xff0c;还有测试的工作日常等等来给大家讲解一下软件测试到底是什么样的工作&#xff1f; 通俗来说软件测试工程师就相当于一个质检员&#xff0c;专门处理软件测试质量的工作&#xff0c;不管是功能测试也好&#xff0c;性能测…

BK7231N开发平台原厂烧录工具使用说明

BK7231N开发平台原厂烧录工具使用说明 烧录流程介绍 1.打开原厂烧录工具 以管理员身份打开名为 bk_writer_gui_V1.6.3.exe 的可执行文件。 2. 烧录对象 烧录对象选择 BK7231n 3.烧录地址 当我们烧录UA文件的时候&#xff0c;需要把起始地址设置为&#xff1a; 0X00011000。…

Windows提示“找不到rgss202j.dll”怎么办?

Rgss202j.dll文件是Windows操作系统最重要的系统文件之一&#xff0c;它包含了一组程序和驱动函数。如果此文件丢失或损坏&#xff0c;驱动程序将无法正常工作&#xff0c;并且相应的应用程序也将无法正常启动且运行。通常情况下&#xff0c;造成Rgss202j.dll文件无法找到的原因…

爬虫 - ProtoBuf 协议

一、抓取请求 以下是请求的大致内容&#xff1a; 是乱码&#xff0c;需要解析。 二、解析 通过分析 request 和 response 的 Content-Type: application/x-protobuf 得知&#xff1a;使用了谷歌的 protobuf 协议来传输数据&#xff0c;需要破解。 大致破解过程&#xff…

随时随地保持连接:数字游民适用的远程桌面

随着世界迅速适应数字革命&#xff0c;一种全新的职业——数字游民应运而生。数字游民指利用技术远程办公的专业人群&#xff0c;这是一种允许人们在旅行中办公、不受地点限制的工作生活方式。游牧式工作生活趋势并非一时的风尚&#xff0c;而是我们工作观念的彻底转变&#xf…

MUR8060PT-ASEMI快恢复二极管MUR8060PT

编辑-Z MUR8060PT在TO-247封装里采用的2个芯片&#xff0c;其尺寸都是140MIL&#xff0c;是一款高耐压大电流快恢复二极管。MUR8060PT的浪涌电流Ifsm为600A&#xff0c;漏电流(Ir)为10uA&#xff0c;其工作时耐温度范围为-55~150摄氏度。MUR8060PT采用抗冲击硅芯片材质&#x…

实在智能率先拥抱大模型技术,旗下“AI+RPA”系列产品迎来全面智能升级

实在智能RPA ​ AI时代&#xff0c;所有产品都将迎来用大模型进行全面智能升级。 随着以ChatGPT等为代表的生成式AI持续火热&#xff0c;大型语言模型&#xff08;Large Language Model, LLM&#xff09;领域的研发和布局在国内外有目共睹&#xff0c;微软、谷歌、百度系等生…

回收小程序是什么?有什么特点?

回收小程序旨在为用户提供便捷、环保的废品回收服务。以下是关于上门回收小程序的介绍&#xff1a;回收小程序旨在解决废品回收的难题&#xff0c;为用户提供一种方便、可持续的回收方式。通过小程序&#xff0c;可以轻松预约回收服务&#xff0c;将废品交由专业回收人员处理&a…

基于QFT的量子加法器的原理与实现-mindspore quantum

1 量子Fourier变换 离散Fourier变换以一一个复向量 x 0 , . . . , x N − 1 {x_0},...,{x_{N - 1}} x0​,...,xN−1​为输入&#xff0c;输出的数据是如下复向量 y 0 , . . . , y N − 1 {y_0},...,{y_{N - 1}} y0​,...,yN−1​&#xff1a; y k ≡ 1 N ∑ j 0 N − 1 x j …

【初识C语言】变量和常量

文章目录 1. 局部变量和全局变量2. 变量的作用域和生命周期3. 常量 生活中的有些值是不变的&#xff08;比如&#xff1a;圆周率&#xff0c;性别&#xff0c;身份证号码&#xff0c;血型等等&#xff09;有些值是可变的&#xff08;比如&#xff1a;年龄&#xff0c;体重&…

18款奔驰S450 4MATIC升级发光出风口,提升车内氛围感

完美匹配&#xff0c;全部都是原装位&#xff0c;安装很快&#xff0c;瞬间发光。随着氛围灯颜色的变化而变化。美丽靓丽与质感同存&#xff0c;大大提升了车的颜值档次。让您车生活更加的富有乐趣与满足&#xff01;