吉安感知网项目-前端
罗广辉
2026-01-13 6d0342f18824eaabef3318ea289a10de79213c6a
Merge remote-tracking branch 'origin/master'
9 files modified
2 files added
769 ■■■■■ changed files
applications/drone-command/vite.config.mjs 4 ●●● patch | view | raw | blame | history
applications/mobile-web-view/env/.env.development 2 ●●● patch | view | raw | blame | history
applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue 621 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/router/page/index.js 6 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/hooks/useGlobalWS.js 4 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/pages/voiceCall/index.vue 11 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/voiceCallDetail/index.vue 53 ●●●●● patch | view | raw | blame | history
uniapps/work-wx/src/pages/user/index.vue 6 ●●●●● patch | view | raw | blame | history
uniapps/work-wx/src/subPackages/deviceRegistration/add.vue 23 ●●●●● patch | view | raw | blame | history
uniapps/work-wx/src/subPackages/userDetail/infos/index.vue 28 ●●●●● patch | view | raw | blame | history
uniapps/work-wx/src/subPackages/userDetail/password/index.vue 11 ●●●● patch | view | raw | blame | history
applications/drone-command/vite.config.mjs
@@ -3,6 +3,7 @@
import createVitePlugins from './vite/plugins'
import postCssPxToRem from 'postcss-pxtorem'
import { fileURLToPath, URL } from 'node:url';
import  basic from '@vitejs/plugin-basic-ssl'
// https://vitejs.dev/config/
export default ({ mode, command }) => {
  const env = loadEnv(mode, fileURLToPath(new URL("./env", import.meta.url)))
@@ -38,6 +39,7 @@
      __INTLIFY_PROD_DEVTOOLS__: false,
    },
    server: {
      https: true,
      port: 5174,
      // host: '192.168.1.178',
      proxy: {
@@ -79,7 +81,7 @@
        ]
      }
    },
    plugins: createVitePlugins(env, command === 'build'),
    plugins: [...createVitePlugins(env, command === 'build'), basic()],
    build: buildConfig,
    optimizeDeps: {
      esbuildOptions: {
applications/mobile-web-view/env/.env.development
@@ -18,7 +18,7 @@
VITE_APP_MAP_TILE_URL = https://wrj.shuixiongit.com/3Dtile
#开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
VITE_APP_API_URL = https://wrj.shuixiongit.com/api
VITE_APP_API_URL = http://218.202.104.82:8200
# VITE_APP_API_URL = http://192.168.1.168
# ws地址
applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue
New file
@@ -0,0 +1,621 @@
<template>
    <div class="call-container">
      <div class="content-wrapper">
        <div class="input-group">
            <div class="input-item">
                <span class="input-label">我的 userId:</span>
                <input v-model="uid" class="input-field" />
            </div>
            <div class="input-item">
                <span class="input-label">对方 userId:</span>
                <input v-model="peerUid" class="input-field" />
            </div>
        </div>
        <div class="button-group">
            <button @click="connectWS" :disabled="connected" class="btn btn-connect">连接WS</button>
        </div>
        <div class="button-group">
            <button @click="requestCall" :disabled="!connected || state!=='idle'" class="btn btn-call">呼叫</button>
            <button v-if="state==='ringing'" @click="acceptCall" class="btn btn-accept">接听</button>
            <button v-if="state==='ringing'" @click="rejectCall" class="btn btn-reject">拒绝</button>
            <button @click="hangup" :disabled="!connected || (state!=='calling' && state!=='in-call')" class="btn btn-hangup">挂断</button>
        </div>
        <div class="status-group">
            <strong class="status-text">状态:<span :style="{color: getStateColor(state)}">{{ state }}</span></strong>
            <strong v-if="state==='in-call'" class="status-text">通话时长:{{ durationText }}</strong>
        </div>
      </div>
      <div class="tip-message">
        <small>提示:若 getUserMedia 不可用,请用 https 或 localhost 访问页面。</small>
      </div>
      <div class="log-container">
        <div class="log-box">
            <div class="log-title">日志信息:</div>
            <pre class="log-content">{{ logText }}</pre>
        </div>
      </div>
      <audio ref="remoteAudio" autoplay class="hidden-audio"></audio>
    </div>
  </template>
  <script setup>
import { ref, computed, onBeforeUnmount, onMounted } from 'vue'
  /** ✅ 你的 WS 地址前缀(后面拼 userId) */
  const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?userId='
  // 解析URL参数
  const parseUrlParams = () => {
    const url = new URL(window.location.href)
    const paramsStr = url.searchParams.get('params')
    if (paramsStr) {
      try {
        const params = JSON.parse(decodeURIComponent(paramsStr))
        if (params.peerUid) {
          peerUid.value = String(params.peerUid)
          log('🔗 从URL参数获取peerUid:', params.peerUid)
        }
      } catch (e) {
        log('❌ 解析URL参数失败:', String(e))
      }
    }
  }
  const uid = ref('3')
  const peerUid = ref('2')
  const connected = ref(false)
  const state = ref('idle') // idle | calling | ringing | in-call
  const logText = ref('')
  const remoteAudio = ref(null)
    /**
     * 根据状态返回对应颜色
     */
    const getStateColor = (state) => {
        const colors = {
            'idle': '#666',
            'calling': '#2196F3',
            'ringing': '#FF9800',
            'in-call': '#4CAF50'
        }
        return colors[state] || '#666'
    }
  let ws = null
  let pc = null
  let localStream = null
  const pendingCandidates = []
  // 来电暂存
  let incomingFrom = null
  let acceptedByMe = false // 我是否已经点了接听
  let offeredByPeer = false // 是否已收到对方 offer(用于流程判断)
  // 计时
  let timer = null
  const seconds = ref(0)
  const durationText = computed(() => {
    const m = String(Math.floor(seconds.value / 60)).padStart(2, '0')
    const s = String(seconds.value % 60).padStart(2, '0')
    return `${m}:${s}`
  })
  function startTimer() {
    stopTimer()
    seconds.value = 0
    timer = setInterval(() => seconds.value++, 1000)
  }
  function stopTimer() {
    if (timer) clearInterval(timer)
    timer = null
  }
  // 心跳
  let pingTimer = null
  function startPing() {
    stopPing()
    // 每 25 秒发一次 ping(你后端有 PING/PONG)
    pingTimer = setInterval(() => {
      if (connected.value) send('ping', 'system', null)
    }, 25000)
  }
  function stopPing() {
    if (pingTimer) clearInterval(pingTimer)
    pingTimer = null
  }
  function log(...args) {
    const line = args.map(v => (typeof v === 'string' ? v : JSON.stringify(v))).join(' ')
    logText.value += `[${new Date().toLocaleTimeString()}] ${line}\n`
    console.log(...args)
  }
  /**
   * ✅ 严格按后端 ChatMessage 字段发:
   * { type, from, to, payload, timestamp }
   * 注意:后端会用 session 的 userId 覆盖 from,但我们带上也没坏处
   */
  function send(type, to, payload) {
    if (!type) return
    if (!ws || ws.readyState !== WebSocket.OPEN) return
    const msg = {
      type,                 // "call/accept/busy/offer/answer/candidate/hangup/ping"
      from: String(uid.value),
      to: String(to),
      payload: payload ?? null,
      timestamp: Date.now(),
    }
    ws.send(JSON.stringify(msg))
  }
  /* ---------------- WebRTC ---------------- */
  function createPC() {
    pc = new RTCPeerConnection({
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
    })
    pc.onicecandidate = (e) => {
      if (e.candidate) {
        send('candidate', peerUid.value, { candidate: e.candidate.toJSON ? e.candidate.toJSON() : e.candidate })
      }
    }
    pc.ontrack = (e) => {
      log('✅ 收到远端音频流')
      const stream = e.streams[0]
      if (remoteAudio.value) {
        remoteAudio.value.srcObject = stream
        remoteAudio.value.play().catch(() => {})
      }
    }
    pc.onconnectionstatechange = () => {
      log('pc.connectionState =', pc.connectionState)
    }
  }
  async function getLocalAudio() {
    if (localStream) return localStream
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
      log('❌ 当前环境不支持 getUserMedia:请使用 https 或 localhost 打开页面')
      throw new Error('getUserMedia not available')
    }
    try {
      localStream = await navigator.mediaDevices.getUserMedia({ audio: true })
      log('✅ 已获取本地麦克风')
      return localStream
    } catch (e) {
      log('❌ 获取麦克风失败:', String(e))
      throw e
    }
  }
  /** ✅ 关键:只 addTrack 一次,避免 “sender already exists” */
  async function ensureLocalTracksAdded() {
    if (!pc) createPC()
    const stream = await getLocalAudio()
    const existingTrackIds = new Set(
      pc.getSenders().map(s => (s.track ? s.track.id : null)).filter(Boolean)
    )
    stream.getTracks().forEach(track => {
      if (!existingTrackIds.has(track.id)) {
        pc.addTrack(track, stream)
        log('addTrack ok:', track.id)
      } else {
        // log('skip addTrack:', track.id)
      }
    })
    return stream
  }
  async function flushCandidates() {
    if (!pc || !pc.remoteDescription) return
    while (pendingCandidates.length) {
      const c = pendingCandidates.shift()
      try {
        await pc.addIceCandidate(new RTCIceCandidate(c))
      } catch (e) {
        log('addIceCandidate failed:', String(e))
      }
    }
  }
  /* ---------------- 信令处理 ---------------- */
  async function onSignal(msg) {
    if (!msg) return
    const t = (msg.type || '').toString()
    // ✅ 忽略后端回显给自己的消息(你的后端会回显 sender)
    if (msg.from && String(msg.from) === String(uid.value)) {
      return
    }
    // system 消息:login/pong/busy 仅记录
    if (msg.from === 'system') {
      log('system msg:', msg)
      return
    }
    if (!t) return
    if (t === 'call') {
      // 收到来电:弹接听按钮
      incomingFrom = String(msg.from)
      peerUid.value = incomingFrom
      acceptedByMe = false
      offeredByPeer = false
      state.value = 'ringing'
      log('📞 收到来电 call,来自', incomingFrom)
      return
    }
    if (t === 'accept') {
      // 主叫收到 accept:开始 offer
      log('✅ 对方接听 accept,开始建立连接(offer)')
      await startOffer()
      return
    }
    if (t === 'busy') {
      log('❌ 对方忙线/拒绝')
      cleanup(false)
      return
    }
    if (t === 'offer') {
      // 被叫收到 offer:只有点击接听后才真正应答(你要求“先点接听”)
      offeredByPeer = true
      incomingFrom = String(msg.from)
      peerUid.value = incomingFrom
      if (!acceptedByMe) {
        state.value = 'ringing'
        log('📩 收到 offer(但未接听),已等待用户点击接听')
        // 暂存 offer 到一个变量
        lastOfferSdp = msg.payload?.sdp
        return
      }
      // 已接听:直接 answer
      await answerOffer(msg.payload?.sdp, incomingFrom)
      return
    }
    if (t === 'answer') {
      log('✅ 收到 answer,通话建立')
      if (!pc) createPC()
      await pc.setRemoteDescription(new RTCSessionDescription(msg.payload?.sdp))
      await flushCandidates()
      state.value = 'in-call'
      startTimer()
      return
    }
    if (t === 'candidate') {
      const c = msg.payload?.candidate
      if (!c) return
      if (pc && pc.remoteDescription) {
        await pc.addIceCandidate(new RTCIceCandidate(c))
      } else {
        pendingCandidates.push(c)
      }
      return
    }
    if (t === 'hangup') {
      log('📴 对方挂断')
      cleanup(false)
      return
    }
    if (t === 'pong') {
      // 心跳响应
      // log('pong')
      return
    }
  }
  // 暂存 offer(用于“先点接听再 answer”)
  let lastOfferSdp = null
  /* ---------------- 业务操作 ---------------- */
  function connectWS() {
    if (ws) ws.close()
    ws = new WebSocket(WS_BASE + encodeURIComponent(uid.value))
    ws.onopen = () => {
      connected.value = true
      log('WS 已连接 userId=', uid.value)
      startPing()
    }
    ws.onclose = () => {
      connected.value = false
      log('WS 已关闭')
      stopPing()
    }
    ws.onerror = (e) => log('WS error', e)
    ws.onmessage = (e) => {
      const msg = JSON.parse(e.data)
      log('收到信令', msg)
      onSignal(msg)
    }
  }
  function requestCall() {
    state.value = 'calling'
    incomingFrom = null
    acceptedByMe = false
    offeredByPeer = false
    lastOfferSdp = null
    send('call', peerUid.value, null)
    log('➡️ 发起呼叫 call 给', peerUid.value)
  }
  function acceptCall() {
    if (!incomingFrom) return
    acceptedByMe = true
    // 先告诉对方我接听了(后端会把双方置忙)
    send('accept', incomingFrom, null)
    log('✅ 已点击接听,发送 accept 给', incomingFrom)
    state.value = 'calling'
    // 如果 offer 已经提前到达(我们暂存了),立刻 answer
    if (lastOfferSdp) {
      answerOffer(lastOfferSdp, incomingFrom)
      lastOfferSdp = null
    }
  }
  function rejectCall() {
    if (!incomingFrom) return
    // 你后端没有 reject,用 busy 表示拒绝/不可接听
    send('busy', incomingFrom, null)
    log('❌ 已拒绝来电,发送 busy 给', incomingFrom)
    incomingFrom = null
    acceptedByMe = false
    offeredByPeer = false
    lastOfferSdp = null
    state.value = 'idle'
  }
  async function startOffer() {
    if (!pc) createPC()
    // ✅ 不会重复 addTrack
    await ensureLocalTracksAdded()
    const offer = await pc.createOffer()
    await pc.setLocalDescription(offer)
    send('offer', peerUid.value, { sdp: pc.localDescription })
    log('➡️ 已发送 offer 给', peerUid.value)
  }
  async function answerOffer(offerSdp, from) {
    if (!offerSdp) {
      log('❌ offer sdp 为空,无法接听')
      return
    }
    if (!pc) createPC()
    await pc.setRemoteDescription(new RTCSessionDescription(offerSdp))
    await flushCandidates()
    await ensureLocalTracksAdded()
    const answer = await pc.createAnswer()
    await pc.setLocalDescription(answer)
    send('answer', from, { sdp: pc.localDescription })
    log('⬅️ 已发送 answer 给', from)
    state.value = 'in-call'
    startTimer()
  }
  function hangup() {
    cleanup(true)
  }
  /* ---------------- 清理 ---------------- */
  function cleanup(sendToPeer) {
    try {
      if (sendToPeer) send('hangup', peerUid.value, null)
    } catch {}
    stopTimer()
    if (pc) {
      pc.getSenders().forEach(s => s.track && s.track.stop())
      pc.close()
    }
    if (localStream) {
      localStream.getTracks().forEach(t => t.stop())
    }
    pc = null
    localStream = null
    pendingCandidates.length = 0
    incomingFrom = null
    acceptedByMe = false
    offeredByPeer = false
    lastOfferSdp = null
    state.value = 'idle'
  }
  onMounted(() => {
    // 解析URL参数
    parseUrlParams()
  })
  onBeforeUnmount(() => {
    cleanup(false)
    stopPing()
    if (ws) ws.close()
  })
  </script>
  <style scoped lang="scss">
    .call-container {
        padding: 20px;
        background-color: #f5f5f5;
        min-height: 100vh;
        font-family: Arial, sans-serif;
    }
    .content-wrapper {
        display: flex;
        flex-direction: column;
        gap: 15px;
        max-width: 600px;
        margin: 0 auto;
    }
    .input-group {
        display: flex;
        flex-direction: column;
        gap: 10px;
    }
    .input-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
    }
    .input-label {
        font-weight: bold;
        color: #333;
    }
    .input-field {
        width: 120px;
        padding: 8px;
        border: 1px solid #ddd;
        border-radius: 4px;
        font-size: 14px;
    }
    .button-group {
        display: flex;
        gap: 10px;
        flex-wrap: wrap;
    }
    .btn {
        padding: 10px 20px;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        font-size: 14px;
        transition: background-color 0.3s;
        &:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }
    }
    .btn-connect {
        background-color: #4CAF50;
    }
    .btn-call {
        background-color: #2196F3;
    }
    .btn-accept {
        background-color: #4CAF50;
    }
    .btn-reject {
        background-color: #f44336;
    }
    .btn-hangup {
        background-color: #9E9E9E;
    }
    .status-group {
        display: flex;
        flex-direction: column;
        gap: 5px;
    }
    .status-text {
        font-size: 16px;
        color: #333;
    }
    .tip-message {
        margin-top: 15px;
        color: #666;
        text-align: center;
    }
    .log-container {
        margin-top: 20px;
        max-width: 600px;
        margin-left: auto;
        margin-right: auto;
    }
    .log-box {
        background-color: white;
        border: 1px solid #ddd;
        border-radius: 8px;
        padding: 10px;
    }
    .log-title {
        font-weight: bold;
        color: #333;
        margin-bottom: 8px;
    }
    .log-content {
        max-height: 200px;
        overflow: auto;
        background-color: #fafafa;
        padding: 10px;
        border-radius: 4px;
        font-size: 12px;
        line-height: 1.4;
    }
    .hidden-audio {
        display: none;
    }
  </style>
applications/mobile-web-view/src/router/page/index.js
@@ -104,6 +104,12 @@
                component: () => import('@/appPages/inspectionTask/execution/index.vue'),
                meta: { title: '任务详情'},
            },
            {
                path: 'voiceCallDetail',
                name: '语音通话',
                component: () => import('@/appPages/voiceCallDetail/index.vue'),
                meta: { title: '语音通话'},
            },
        ],
    },
uniapps/work-app/src/hooks/useGlobalWS.js
@@ -1,6 +1,6 @@
import {useUserStore} from "@/store/index.js";
import {getEnvObj} from "@/utils/index.js";
import {enterRoom} from "@/utils/voiceCallByTX/index.js";
// import {enterRoom} from "@/utils/voiceCallByTX/index.js";
import useAppStore from "../store/modules/app/index.js";
let socketTask = null
@@ -31,7 +31,7 @@
                })
                break
            case 'VoiceCall':
                enterRoom(payload, userId.value)
                // enterRoom(payload, userId.value)
                break
            default:
                break;
uniapps/work-app/src/pages/voiceCall/index.vue
@@ -35,7 +35,7 @@
      <!-- 加载提示 -->
      <div class="loadingMore">
        <text v-if="loading">加载中...</text>
        <text v-else-if="!hasMore && contacts.length > 0">没有更多数据了</text>
      </div>
    </div>
  </div>
@@ -123,9 +123,12 @@
  const contactName = contact.friendNickName || '未知联系人'
  console.log('拨打电话给', contactName)
  // 这里可以添加实际的拨打电话逻辑
  uni.showToast({
    title: `正在拨打${contactName}的电话`,
    icon: 'none'
  // uni.showToast({
  //   title: `正在拨打${contactName}的电话`,
  //   icon: 'none'
  // })
  uni.navigateTo({
    url: `/subPackages/voiceCallDetail/index`,
  })
}
uniapps/work-app/src/subPackages/voiceCallDetail/index.vue
New file
@@ -0,0 +1,53 @@
<!-- 登录页 -->
<template>
   <!-- <web-view :src="`${viewUrl}`" @message="viewMessage" :allow="allow"/> -->
   <WebViewPlus
     ref="sWebViewRef"
     :src="`${viewUrl}`"
     @webMessage="onPostMessage"
   />
</template>
<script setup>
import { getWebViewUrl } from "@/utils/index.js";
import { onLoad } from "@dcloudio/uni-app";
const sWebViewRef = ref(null);
const viewUrl = ref("");
onLoad((options) => {
      // const currentItem = options.currentItem;
      // viewUrl.value = getWebViewUrl("/mapWork", { currentItem: currentItem });
      //       viewUrl.value = getWebViewUrl("/voiceCallDetail");
          viewUrl.value = 'https://192.168.57.124:5175/drone-command/demo';
    });
    function onPostMessage(data) {}
// #ifdef APP-PLUS
function requestAndroidMicPermission() {
  return new Promise((resolve) => {
    const main = plus.android.runtimeMainActivity()
    const Build = plus.android.importClass('android.os.Build')
    if (Build.VERSION.SDK_INT < 23) return resolve(true)
    const Manifest = plus.android.importClass('android.Manifest')
    const ActivityCompat = plus.android.importClass('androidx.core.app.ActivityCompat')
    const permission = Manifest.permission.RECORD_AUDIO
    ActivityCompat.requestPermissions(main, [permission], 1001)
    // 简化:延迟检查一次(更严谨可写原生回调插件)
    setTimeout(() => {
      const PackageManager = plus.android.importClass('android.content.pm.PackageManager')
      const granted = ActivityCompat.checkSelfPermission(main, permission) === PackageManager.PERMISSION_GRANTED
      resolve(granted)
    }, 800)
  })
}
requestAndroidMicPermission()
// #endif
</script>
<style lang="scss" scoped>
</style>
uniapps/work-wx/src/pages/user/index.vue
@@ -5,7 +5,7 @@
    <view class="userBox">
      <view class="flex items-center pb-30rpx pl-30rpx pr-20rpx">
        <view class="mr-20rpx">
          <u-avatar :src="user.avatar || defaultAvatar" size="70" />
          <u-avatar @click="uploadAvatar" :src="user.avatar || showDefaultHeader" size="70" />
        </view>
        <view class="flex-1">
          <view class="userName">{{user.nickName }}</view>
@@ -55,6 +55,7 @@
import { getDeviceRegionApi } from "@/api/map.js";
import defaultAvatar  from '/static/images/defaultAvatar.svg'
import rightImage from '@/static/images/user/rightBtn.svg';
import showDefaultHeader from "@/static/images/user/default-header.svg";
const { setClipboardData, getClipboardData } = useClipboard();
//
// const rightImage = getAssetsImage("/images/user/rightBtn.svg");
@@ -99,7 +100,8 @@
  height: 100%;
}
.pageUser {
  background: url("@/static/images/user/userbg.svg")  no-repeat ;
  background: url("@/static/images/user/userbg.svg")  no-repeat;
  background-size: 100%;
}
.userBox {
  padding-top: 212rpx;
uniapps/work-wx/src/subPackages/deviceRegistration/add.vue
@@ -260,12 +260,18 @@
      @confirm="onPurchaseDate"
      @cancel="showPurchaseDate = false"
    />
    <!-- 所属区域 -->
     <u-cascader
        show="show"
        v-model="value"
        :data="areaData"
    ></u-cascader>
    </u-form>
</view>
</template>
<script setup>
import { aircraftInfoSaveApi,uploadFileApi } from '@/api/index'
import { ref, computed } from 'vue'
import { aircraftInfoSaveApi,uploadFileApi,areaDataApi } from '@/api/index'
import { ref, computed, onMounted } from 'vue'
import dayjs from 'dayjs'
const formRef = ref(null);
@@ -445,6 +451,16 @@
        });
    });
}
// 获取所属单位
const isShowRegion = ref(false)
const areaData = ref([])
function getAreaData() {
    areaDataApi().then(res => {
        if (res.data.code === 200) {
            areaData.value = res.data.data
        }
    })
}
// 提交图片
function afterReadImage(event) {
    // 获取上传的文件对象
@@ -526,6 +542,9 @@
        // 这里可以添加额外的失败处理逻辑
    }
}
onMounted(() => {
    getAreaData()
})
</script>
<style scoped lang="scss">
.deviceRegistration {
uniapps/work-wx/src/subPackages/userDetail/infos/index.vue
@@ -1,22 +1,23 @@
<!-- 个人资料 -->
<template>
    <view class="container">
        <view class="pageBg"></view>
        <div class="avatarBox">
            <u-avatar @click="uploadAvatar" :src="userInfo.avatar" size="114" mode="aspectFill" />
            <u-avatar @click="uploadAvatar" :src="userInfo.avatar || showDefaultHeader" size="114" mode="aspectFill" />
        </div>
        <view class="detailBox">
            <div class="detailCon">
                <div class="orderRow">
                    <div class="rowTitle">姓名</div>
                    <div>{{userInfo.realName}}</div>
                    <div>{{userInfo.nickName}}</div>
                </div>
                <!-- <div class="orderRow">
                    <div class="rowTitle">所属单位</div>
                    <div>{{userInfo.deptName}}</div>
                </div> -->
                <div class="orderRow">
                    <div class="rowTitle">是否认证</div>
                    <div>{{userInfo.isAuth ? '已认证' : '未认证'}}</div>
                    <div class="rowTitle">认证状态</div>
                    <div>{{userInfo.status === 1 ? '已认证' : '未认证'}}</div>
                </div>
                <div class="orderRow">
                    <div class="rowTitle">手机号</div>
@@ -40,6 +41,7 @@
</template>
<script setup>
    import showDefaultHeader from "@/static/images/user/default-header.svg";
    import {
        getEnvObj,
        getWebViewUrl
@@ -55,7 +57,7 @@
    const userInfo = ref({
        id: '',
        avatar: '',
        realName: '',
        nickName: '',
        name: '',
        phone: '',
        email: '',
@@ -95,14 +97,15 @@
    const getUserInfoData = () => {
        getUserInfo().then(res => {
            const user = res.data.data;
            console.log('user',user)
            userInfo.value = {
                id: user.id,
                id: user.userId,
                avatar: user.avatar,
                name: user.name,
                realName: user.realName,
                phone: user.phone,
                email: user.email,
                deptName: user.deptName,
                nickName: user.nickName,
                // realName: user.realName,
                phone: user.phonenumber,
                email: user.sysDept.email,
                deptName: user.sysDept.deptName,
            };
        });
@@ -250,6 +253,7 @@
        width: 100%;
        height: 100%;
        background: url("@/static/images/user/userbg.svg")  no-repeat ;
        background-size: 100%;
    }
    .avatarBox {
        width: 228rpx;
@@ -323,6 +327,8 @@
        height: 100rpx !important;
    }
    .note {
        position: absolute;
        bottom: 400rpx;
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: 400;
        font-size: 10px;
uniapps/work-wx/src/subPackages/userDetail/password/index.vue
@@ -10,17 +10,17 @@
                </div>
                <div class="orderRow">
                    <div class="rowTitle">密码</div>
                    <input type="password" v-model="passwordForm.password" placeholder="请输入" class="input-item" />
                    <input type="password" v-model="passwordForm.password" placeholder="请输入新密码" class="input-item" />
                </div>
                <div class="orderRow">
                    <div class="rowTitle">确认密码</div>
                    <input type="password" v-model="passwordForm.password2" placeholder="请输入" class="input-item" />
                    <input type="password" v-model="passwordForm.password2" placeholder="请输入确认密码" class="input-item" />
                </div>
                <div class="orderRow">
                    <div class="rowTitle">验证码</div>
                    <input type="text" v-model="passwordForm.code" placeholder="请输入验证码" class="input-item" />
                    <u-button :disabled="countDown > 0" @click="sendVerificationCode" type="primary" color="#007AFF">
                    <u-button class="getCode" :disabled="countDown > 0" @click="sendVerificationCode" type="primary" color="#007AFF">
                        {{ countDown > 0 ? `${countDown}s后重发` : '获取验证码' }}
                    </u-button>
                </div>
@@ -262,6 +262,7 @@
        width: 100%;
        height: 100%;
        background: url("@/static/images/user/userbg.svg")  no-repeat ;
        background-size: 100%;
    }
    .detailBox {
@@ -363,4 +364,8 @@
        width: 276rpx !important;
        height: 100rpx !important;
    }
    .getCode {
        margin-left: 20rpx;
            width: 180rpx;
        }
</style>