罗广辉
2026-05-15 e3c2cd23253f3501be7d31e03955fbb6f4aa3556
Merge branch 'refs/heads/feature/v9.0/9.0.4' into prod
19 files modified
2 files added
1240 ■■■■■ changed files
README.md 111 ●●●●● patch | view | raw | blame | history
src/components/GetPrivacy.vue 199 ●●●●● patch | view | raw | blame | history
src/components/WebViewPlus.vue 78 ●●●●● patch | view | raw | blame | history
src/config/env.js 15 ●●●● patch | view | raw | blame | history
src/manifest.json 14 ●●●● patch | view | raw | blame | history
src/pages.json 8 ●●●● patch | view | raw | blame | history
src/pages/login/index.vue 65 ●●●●● patch | view | raw | blame | history
src/pages/map/index.vue 18 ●●●●● patch | view | raw | blame | history
src/pages/user/index.vue 2 ●●●●● patch | view | raw | blame | history
src/pages/work/index.vue 15 ●●●●● patch | view | raw | blame | history
src/subPackages/downloadPage/index.vue 13 ●●●●● patch | view | raw | blame | history
src/subPackages/droneConsole/index.vue 17 ●●●● patch | view | raw | blame | history
src/subPackages/inspectionTask/taskDetails.vue 3 ●●●●● patch | view | raw | blame | history
src/subPackages/qrCode/index.vue 14 ●●●●● patch | view | raw | blame | history
src/subPackages/taskDetail/addTask/index.vue 11 ●●●●● patch | view | raw | blame | history
src/subPackages/taskDetail/execution/index.vue 13 ●●●●● patch | view | raw | blame | history
src/subPackages/taskDetail/inProgress/index.vue 2 ●●●●● patch | view | raw | blame | history
src/subPackages/userDetail/infos/index.vue 577 ●●●● patch | view | raw | blame | history
src/subPackages/userDetail/password/index.vue 35 ●●●●● patch | view | raw | blame | history
src/subPackages/workDetail/addWork/index.vue 14 ●●●●● patch | view | raw | blame | history
src/subPackages/workDetail/mapWork/index.vue 16 ●●●●● patch | view | raw | blame | history
README.md
@@ -19,10 +19,121 @@
/software/service/drone/web/drone-app
正式
/app/service/drone/web/drone-app
# app更新步骤
修改 /src/config 里面的development的参数,改为对应环境的对应变量
# 小程序app更新步骤
1. 修改 /src/config 里面的development的参数,改为对应环境的对应变量
2. hbuild里面点击运行 =》到小程序 =》自动打开了小程序 =》
3. 调试好=》上传 =》[微信公众号](https://mp.weixin.qq.com/) =》 提交审核
# 开发者文档
- 本项目大量采用webview方式开发,只有登录页是原生的uniapp,其他的都是webview
- 登录了之后在userStore里面存了用户信息(包括token)
- 每个页面都会走WebViewPlus组件
- WebViewPlus里面有个属性src都会走getWebViewUrl方法
- WebViewPlus会监听webview发送给app的消息,统一处理
- getWebViewUrl方法里面 会把 路径自动拼接上userInfo(包括token)和一些其他参数
- 然后到webview这里的 permission.js会去自动接收这些参数并存储到stroe里面
webview跟app通信
h5或app的webview
```
webview
const transmitData = { data: { type: 'submitSuccess', fun: 'add' } }
uni.postMessage(transmitData)
app 接收到了 然后再跳页面
uni.switchTab({
      url: `/pages/work/index?addLog=111`,
});
```
微信小程序的webview 只能在微信里面直接跳,postMessage无效
```
webview
// 跳转到小程序页面
wx.miniProgram.navigateTo({ url: '/pages/xxx/xxx' })
// 重定向到小程序页面
wx.miniProgram.redirectTo({ url: '/pages/xxx/xxx' })
// 切换Tab页
wx.miniProgram.switchTab({ url: '/pages/xxx/xxx' })
// 返回上一页
wx.miniProgram.navigateBack({ delta: 1 })
// 退出小程序
wx.miniProgram.exit()
// 获取小程序环境信息
wx.miniProgram.getEnv(function(res) {
  console.log(res.miniprogram) // true
})
```
src/components/GetPrivacy.vue
New file
@@ -0,0 +1,199 @@
<template>
  <view v-if="showPrivacy" :class="privacyClass">
    <view :class="contentClass">
      <view class="title">用户隐私保护指引</view>
      <view class="des">
        感谢您选择使用掌控智飞小程序,我们非常重视您的个人信息安全和隐私保护。使用我们的产品前,请您仔细阅读“
        <text class="link" @tap="openPrivacyContract">{{privacyContractName}} </text>”,
        如您同意此隐私保护指引,请点击同意按钮,开始使用此小程序,我们将尽全力保护您的个人信息及合法权益,感谢您的信任!<br />
      </view>
      <view class="btns">
        <button class="item reject" @click="exitMiniProgram">拒绝</button>
        <button id="agree-btn" class="item agree" open-type="agreePrivacyAuthorization"
                @agreeprivacyauthorization="handleAgreePrivacyAuthorization">同意</button>
      </view>
    </view>
  </view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
const props = defineProps({
  position: {
    type: String,
    default: 'center'
  }
});
const emit = defineEmits(['allowPrivacy']);
const isRead = ref(false);
const showPrivacy = ref(false);
const privacyContractName = ref('');
const resolvePrivacyAuthorization = ref(null);
const privacyClass = computed(() => {
  return props.position === 'bottom' ? 'privacy privacy-bottom' : 'privacy';
});
const contentClass = computed(() => {
  return props.position === 'bottom' ? 'content content-bottom' : 'content';
});
onMounted(() => {
  if (uni.onNeedPrivacyAuthorization) {
    uni.onNeedPrivacyAuthorization((resolve) => {
      resolvePrivacyAuthorization.value = resolve;
    });
  }
  if (uni.getPrivacySetting) {
    uni.getPrivacySetting({
      success: (res) => {
        if (res.needAuthorization) {
          privacyContractName.value = res.privacyContractName;
          showPrivacy.value = true;
        } else {
          console.log('已经同意隐私授权,不需要再次授权')
          showPrivacy.value = false;
        }
      },
    });
  }
});
const openPrivacyContract = () => {
  uni.openPrivacyContract({
    success: () => {
      isRead.value = true;
    },
    fail: () => {
      uni.showToast({
        title: '遇到错误',
        icon: 'error',
      });
    },
  });
};
const exitMiniProgram = () => {
  wx.exitMiniProgram();
};
const handleAgreePrivacyAuthorization = () => {
  showPrivacy.value = false;
  emit('allowPrivacy');
  if (typeof resolvePrivacyAuthorization.value === 'function') {
    resolvePrivacyAuthorization.value({
      buttonId: 'agree-btn',
      event: 'agree',
    });
  }
};
const closePrivacy = () => {
  showPrivacy.value = false;
};
// 暴露给父组件使用
defineExpose({
  closePrivacy
});
</script>
<style scoped>
.privacy {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: rgba(0, 0, 0, .5);
  z-index: 9999999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.privacy-bottom {
  align-items: flex-end;
}
.content {
  width: 632rpx;
  padding: 48rpx;
  box-sizing: border-box;
  background: #fff;
  border-radius: 16rpx;
}
.content-bottom {
  position: absolute;
  bottom: 0;
  width: 96%;
  padding: 36rpx;
  border-radius: 16rpx 16rpx 0 0;
}
.content .title {
  text-align: center;
  color: #333;
  font-weight: bold;
  font-size: 32rpx;
}
.content .des {
  font-size: 26rpx;
  color: #666;
  margin-top: 40rpx;
  text-align: justify;
  line-height: 1.6;
}
.content .des .link {
  color: #1989ff;
  text-decoration: underline;
}
.btns {
  margin-top: 48rpx;
  margin-bottom: 12rpx;
  display: flex;
}
.btns .item {
  width: 200rpx;
  height: 72rpx;
  overflow: visible;
  display: flex;
  align-items: center;
  justify-content: center;
  /* border-radius: 16rpx; */
  box-sizing: border-box;
  border: none !important;
}
.btns .reject {
  background: #f4f4f5;
  color: #1989ff;
  font-size: 14px;
  background: #edf5fe;
  font-weight: 300;
  margin-right: 16rpx;
}
.btns .agree {
  width: 200rpx;
  background: #1989ff;
  color: #fff;
  font-size: 16px;
}
.privacy-bottom .btns .agree {
  width: 440rpx;
}
</style>
src/components/WebViewPlus.vue
@@ -22,14 +22,82 @@
}
// WEB_INVOKE_APPSERVICE
// 下载图片一次
function oneImageDownload(url) {
  return new Promise((resolve, reject) => {
    wx.downloadFile({
      url,
      success: (res) => {
        wx.saveImageToPhotosAlbum({
          filePath: res.tempFilePath,
          success: function (res) {
            resolve()
          },
        });
      },
    });
  })
}
// 多次图片下载
function multipleImageDownloads(all){
  // const downloadUrlList = [
  //   // 'https://wrj.shuixiongit.com/aiskyminioTwo/cloud-bucket//089a4039-9c98-4441-abb7-1f9ac56caa8c/DJI_202604270944_002_089a4039-9c98-4441-abb7-1f9ac56caa8c/%E8%BD%A6AI20260427100047007.jpeg',
  //    // 'https://aisky.org.cn/command-center-dashboard/assets/zhddpt-BgsJ8s7S.png',
  //   'https://wrj.shuixiongit.com/aiskyminioTwo/cloud-bucket/8d9ba6b4-d255-4f49-b3f8-bdeaf99773b4/DJI_202602121039_002_8d9ba6b4-d255-4f49-b3f8-bdeaf99773b4-flyto/DJI_20260212104641_0003_V.mp4'
  // ]
  const downloadUrlList  = all.map(item => {
    return item.replace('aiskyminio', 'aiskyminioTwo')
  })
  const async = ['https://aisky.org.cn/command-center-dashboard/assets/zhddpt-BgsJ8s7S.png'].map(item => oneImageDownload(item))
  return Promise.all(async)
}
// 公共图片下载
function publicImageDownloads(data) {
  wx.getSetting({
    success(res) {
      if (res?.authSetting?.['scope.writePhotosAlbum']) {
        multipleImageDownloads(data.urlList)
      }else{
        wx.authorize({
          scope: 'scope.writePhotosAlbum',
          success() {
            multipleImageDownloads(data.urlList)
          }
        })
      }
    }
  })
}
// 公共定位
function publicPosit(data) {
  wx.openLocation({
    latitude: data.latitude,
    longitude: data.longitude,
    name: data.name,
    address: data.name,
    scale: 18
  })
}
function messageFun(e) {
  if (e.data.type === "WEB_INVOKE_APPSERVICE") {
    if (e.data.data.arg.type === "tokenExpired") {
      return uni.reLaunch({
        url: "/pages/login/index",
      });
    const {type,data} = e.data.data.arg
    if (type === "tokenExpired") {
      uni.reLaunch({url: "/pages/login/index"});
    }else if(type === "wxImageDownload"){
      publicImageDownloads(data)
    }else if(type === "wxOpenLocation"){
      publicPosit(data)
    }else{
      emit("webMessage", e.data.data.arg);
    }
    emit("webMessage", e.data.data.arg);
  }
}
src/config/env.js
@@ -11,13 +11,20 @@
// 如果是打包app,需要改这里,h5不需要改这里
const development = {
  VITE_APP_ENV:'development',
  // 开发环境这里改为自己的
  // 本地webview
  // VITE_APP_WEBVIEW_URL: 'http://localhost:5175/drone-app-web-view/#/webViewWrapper',
  // 测试环境
  VITE_APP_WEBVIEW_URL: 'https://wrj.shuixiongit.com/drone-app-web-view/#/webViewWrapper',
  // VITE_APP_WEBVIEW_URL: 'http://localhost:5173/drone-app-web-view/#/webViewWrapper',
  VITE_API_BASE_URL: 'https://wrj.shuixiongit.com/api',
  // VITE_API_BASE_URL: 'https://aisky.org.cn/api',
  VITE_APP_WS_API_URL:'wss://wrj.shuixiongit.com/drone-wss/api/v1/ws',
  // 生产环境
  // VITE_APP_WEBVIEW_URL: 'https://aisky.org.cn/drone-app-web-view/#/webViewWrapper',
  // VITE_API_BASE_URL: 'https://aisky.org.cn/api',
  // VITE_APP_WS_API_URL: 'wss://aisky.org.cn/drone-wss/api/v1/ws',
  // 静态资源存放地址'
  VITE_APP_ASSETS_URL: 'https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets'
}
src/manifest.json
@@ -50,7 +50,13 @@
                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
                    "<uses-feature android:name=\"android.hardware.camera\"/>",
                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />"
                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />",
                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />",
                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />",
                    "<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />",
                    "<uses-permission android:name=\"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\" />",
                    "<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />",
                    "<uses-permission android:name=\"android.permission.USE_FULL_SCREEN_INTENT\" />"
                ]
            },
            /* ios打包配置 */
@@ -68,8 +74,7 @@
                }
            }
        },
        "nativePlugins" : {
        }
        "nativePlugins" : {}
    },
    /* 快应用特有相关 */
    "quickapp" : {},
@@ -79,7 +84,8 @@
        "setting" : {
            "urlCheck" : false
        },
        "usingComponents" : true
        "usingComponents" : true,
        "__usePrivacyCheck__" : true
    },
    "mp-alipay" : {
        "usingComponents" : true
src/pages.json
@@ -113,6 +113,12 @@
          }
        },
        {
          "path": "downloadPage/index",
          "style": {
            "navigationBarTitleText": "下载"
          }
        },
        {
          "path": "userDetail/password/index",
          "style": {
            "navigationBarTitleText": "修改密码"
@@ -182,4 +188,4 @@
    "navigationBarBackgroundColor": "#F8F8F8",
    "backgroundColor": "#F8F8F8"
  }
}
}
src/pages/login/index.vue
@@ -11,8 +11,14 @@
      <image :src="passwordSvg" />
      <input
        v-model="loginForm.password"
        type="password"
        type="text"
        :password="showPassword"
        placeholder="请输入密码"
      />
      <image
        class="pass-word-icon"
        :src="passwordTriggerIcon"
        @click="showPassWord"
      />
    </div>
    <div class="remember-password">
@@ -30,6 +36,14 @@
    <button class="login-btn" :style="[inputStyle]" @tap="submit">登录</button>
    <image class="lowerRightCorner" :src="droneSvg" />
    <!-- #ifdef MP-WEIXIN -->
    <GetPrivacy
      ref="privacyComponent"
      position="center"
      @allowPrivacy="allowPrivacy"
    />
    <!-- #endif -->
  </view>
</template>
@@ -38,7 +52,9 @@
import md5 from "js-md5";
import { loginByUsername } from "@/api/user/index.js";
import { useUserStore } from "@/store/index.js";
// #ifdef MP-WEIXIN
import GetPrivacy from "@/components/GetPrivacy.vue";
// #endif
import { HOME_PATH, LOGIN_PATH, removeQueryString } from "@/router";
import { onMounted } from "vue";
@@ -46,6 +62,8 @@
const usernameSvg = getAssetsImage("/images/login/username.png");
const passwordSvg = getAssetsImage("/images/login/password.png");
const logoSvg = getAssetsImage("/images/login/logo.png");
const passwordShow = getAssetsImage("/images/password-show.png");
const passwordHide = getAssetsImage("/images/password-hide.png");
const userStore = useUserStore();
const loginForm = ref({
@@ -132,6 +150,16 @@
  }
}
const showPassword = ref(true);
const showPassWord = () => {
  showPassword.value = !showPassword.value;
};
const passwordTriggerIcon = computed(() => {
  return showPassword.value ? passwordHide : passwordShow;
});
// 从本地存储加载记住的密码
onMounted(() => {
  const savedUserInfo = uni.getStorageSync("rememberedUser");
@@ -147,6 +175,28 @@
    redirect = decodeURIComponent(options.redirect);
  }
});
// #ifdef MP-WEIXIN
const allowPrivacy = () => {
  // 同意隐私协议触发事件,有些接口需要同意授权后才能执行,比如获取手机号授权接口,可以在同意隐私协议后,再执行授权获取手机号接口,如果不需要可以不添加该方法
  console.log("同意隐私授权");
};
const privacyComponent = ref(null);
// onShow(() =>{
//   wx.getPrivacySetting({
//     success: res => {
//       console.log(res)
//       if (!res.needAuthorization) {
//         privacyComponent?.value?.closePrivacy()
//         // 查询授权,针对有tab切换的页面,可以在onshow中查询隐私授权状态,判断在tab切换后是否需要关闭授权弹框
//         console.log('已经同意隐私授权,不需要再次授权')
//       }
//     },
//     fail: () => {},
//     complete: () => {}
//   })
// })
// #endif
</script>
<style lang="scss" scoped>
@@ -203,10 +253,16 @@
      margin-right: 12rpx;
    }
    .pass-word-icon {
      margin-left: 12rpx;
      margin-right: 0;
    }
    :deep(uni-input) {
      height: 40rpx;
      line-height: 40rpx;
      width: 80%;
      width: 0;
      flex: 1;
      margin-top: 20rpx;
      padding: 0 !important;
    }
@@ -214,7 +270,8 @@
    :deep(input) {
      height: 40rpx;
      line-height: 40rpx;
      width: 80%;
      width: 0;
      flex: 1;
      margin-top: 20rpx;
      padding: 0 !important;
      margin: 0 !important;
src/pages/map/index.vue
@@ -1,13 +1,4 @@
<!--
 * @Author       : yuan
 * @Date         : 2025-12-03 14:20:57
 * @LastEditors  : yuan
 * @LastEditTime : 2025-12-20 16:49:02
 * @FilePath     : \src\pages\map\index.vue
 * @Description  :
 * Copyright 2025 OBKoro1, All Rights Reserved.
 * 2025-12-03 14:20:57
-->
<template>
  <view class="page-wrap">
    <WebViewPlus :src="`${viewUrl}`" @webMessage="onPostMessage" />
@@ -18,7 +9,7 @@
import { getWebViewUrl } from "@/utils/index.js";
import WebViewPlus from "@/components/WebViewPlus.vue";
import { onHide, onShow } from "@dcloudio/uni-app";
// const viewUrl = getWebViewUrl("/defaultMap");
import {getUserInfo} from "@/api/user/index.js";
let envParam = "";
// #ifdef WEB
@@ -56,7 +47,7 @@
    //新建任务
    const encodedData = encodeURIComponent(JSON.stringify(data.rowItem));
    uni.setStorageSync("webview_params", encodedData);
    uni.navigateTo({
    uni.switchTab({
      url: `/subPackages/taskDetail/addTask/index`,
    });
  } else if (data.type === "jumpMapNav") {
@@ -74,6 +65,9 @@
};
onShow(() => {
  // 验证一下有没有登录
  getUserInfo()
  // #ifndef MP-WEIXIN
  uni.setTabBarItem({
    index: 2, // Tab 的索引(从0开始)
src/pages/user/index.vue
@@ -53,12 +53,10 @@
<script setup>
import { getAssetsImage } from "@/utils/index.js";
import { getUserInfo } from "@/api/user/index.js";
import { useClipboard } from "@/hooks";
import { useUserStore } from "@/store/index.js";
import { onShow } from "@dcloudio/uni-app";
import { getDeviceRegionApi } from "@/api/map.js";
const { setClipboardData, getClipboardData } = useClipboard();
const rightImage = getAssetsImage("/images/user/rightBtn.png");
const logoImage = getAssetsImage("/images/user/logo1.png");
src/pages/work/index.vue
@@ -1,13 +1,3 @@
<!--
 * @Author       : yuan
 * @Date         : 2025-12-18 10:06:30
 * @LastEditors  : yuan
 * @LastEditTime : 2025-12-20 14:18:42
 * @FilePath     : \src\pages\work\index.vue
 * @Description  :
 * Copyright 2025 OBKoro1, All Rights Reserved.
 * 2025-12-18 10:06:30
-->
<template>
  <view class="eventTickets">
    <WebViewPlus
@@ -21,11 +11,9 @@
<script setup>
import { getWebViewUrl } from "@/utils/index.js";
import WebViewPlus from "@/components/WebViewPlus.vue";
import { useTabAddButton } from "@/hooks/index.js";
import { onShow, onHide } from "@dcloudio/uni-app";
const sWebViewRef = ref(null);
// const viewUrl = getWebViewUrl('/work', {})
const updateKey = ref(0);
const viewUrl = computed(() => {
  // #ifdef MP-WEIXIN
@@ -65,7 +53,6 @@
  }
  // #endif
}
const isApp = ref(false);
onShow(() => {
  const joinParams = uni.getStorageSync("joinParams");
  if (joinParams) {
@@ -86,11 +73,9 @@
  });
  // #endif
  // isApp.value = true
});
onHide(() => {
  // isApp.value = false
});
</script>
src/subPackages/downloadPage/index.vue
New file
@@ -0,0 +1,13 @@
<template>
  <view>下载</view>
</template>
<script setup>
</script>
<style scoped lang="scss">
</style>
src/subPackages/droneConsole/index.vue
@@ -6,21 +6,10 @@
import {onHide, onLoad, onShow} from "@dcloudio/uni-app";
import WebViewPlus from "@/components/WebViewPlus.vue";
import {getEnvObj, getWebViewUrl} from "@/utils/index.js";
import Recorder from "js-audio-recorder";
import dayjs from "dayjs";
import {useUserStore} from "@/store/index.js";
const queryParams = ref({})
const viewUrl = computed(() => {
  return getWebViewUrl('/DroneConsole', queryParams.value)
})
function onPostMessage(data) {
  if (data.type === 'back'){
    // uni.navigateTo({
    //   url: `/subPackages/inProgress/index?wayLineJobInfoId=${data.taskDetails.id}`
    // });
    // #ifdef H5
    window.history.back()
    // #endif
@@ -87,8 +76,12 @@
  })
}
const viewUrl = ref("");
onLoad((options) => {
  queryParams.value = options
  viewUrl.value = getWebViewUrl("/DroneConsole", {
    wayLineJobInfoId: options.wayLineJobInfoId,
    dockSn: options.dockSn,
  });
})
src/subPackages/inspectionTask/taskDetails.vue
@@ -26,7 +26,6 @@
<script setup>
import {onHide, onShow} from "@dcloudio/uni-app";
import {useUserStore} from "@/store/index.js";
const active = defineModel('active')
const sWebViewRef = ref(null)
@@ -36,8 +35,6 @@
import {onLoad} from "@dcloudio/uni-app";
// const active = defineModel('active')
// console.log(active.value, '555')
function onPostMessage(event) {
  if (event.detail.data[0].type === 'back'){
    active.value = null
src/subPackages/qrCode/index.vue
@@ -1,13 +1,3 @@
<!--
 * @Author       : yuan
 * @Date         : 2025-10-14 17:43:52
 * @LastEditors  : yuan
 * @LastEditTime : 2025-12-19 15:33:48
 * @FilePath     : \src\subPackages\qrCode\index.vue
 * @Description  :
 * Copyright 2025 OBKoro1, All Rights Reserved.
 * 2025-10-14 17:43:52
-->
<template>
  <view class="page-wrap">
    <WebViewPlus :src="`${viewUrl}`" @webMessage="onPostMessage" />
@@ -22,10 +12,6 @@
const onPostMessage = (data) => {
  if (data.type === "browser") {
    // #ifdef MP-WEIXIN
    // #endif
    // #ifndef MP-WEIXIN
    uni.navigateTo({
      url:
src/subPackages/taskDetail/addTask/index.vue
@@ -9,8 +9,6 @@
import { getWebViewUrl } from "@/utils/index.js";
import { onHide, onLoad, onShow } from "@dcloudio/uni-app";
// const viewUrl = ref(getWebViewUrl('/addTask'))
const sWebViewRef = ref(null);
const viewUrl = ref("");
function onPostMessage(data) {
@@ -27,23 +25,14 @@
    uni.setStorageSync("joinParams", {
      type: "add",
    });
    // console.log('打印新增成功')
    uni.switchTab({
      url: "/pages/inspectionTask/index",
    });
    // #endif
    // uni.redirectTo({
    //   url: `/pages/inspectionTask/index?addLog=1111`
    // });
  }
}
// onLoad( (options) => {
//   console.log(options, '8888')
//   const sns = options.device_sn
//
// });
const isApp = ref(false);
// #ifdef MP-WEIXIN
src/subPackages/taskDetail/execution/index.vue
@@ -10,17 +10,7 @@
const wayLineJobInfoId = ref(null)
const viewUrl = ref('')
// const viewUrl = ref(getWebViewUrl('/execution'))
// function onPostMessage(data) {
//   console.log(data, '9999')
//   if (data.type === 'taskDetails'){
//     console.log('4444')
//     uni.switchTab({
//       url: '/subPackages/taskDetail/execution/index'
//     });
//   }
// }
function onPostMessage(data) {
  if (data.type === 'back') {
    uni.switchTab({
@@ -47,8 +37,7 @@
});
onHide(() => {
  // 如果工单详情返回任务详情,那么tab栏对应的是关联事件一项
  // isApp.value = false
});
</script>
src/subPackages/taskDetail/inProgress/index.vue
@@ -16,8 +16,6 @@
const viewUrl = ref("");
const showComponent = ref(false);
function onPostMessage(data) {
  // #ifdef MP-WEIXIN
  // #endif
  // #ifndef MP-WEIXIN
  if (data.type === "back") {
src/subPackages/userDetail/infos/index.vue
@@ -1,307 +1,328 @@
<template>
    <view class="container">
        <div class="avatarBox">
            <u-avatar @click="uploadAvatar" :src="userInfo.avatar" size="114" mode="aspectFill" />
        </div>
        <view class="detailBox">
            <div class="detailCon">
                <div class="orderRow">
                    <div class="rowTitle">姓名</div>
                    <div>{{userInfo.realName}}</div>
                </div>
                <div class="orderRow">
                    <div class="rowTitle">所属单位</div>
                    <div>{{userInfo.deptName}}</div>
                </div>
                <div class="orderRow">
                    <div class="rowTitle">手机号</div>
                    <u-input input-align="right" v-model="userInfo.phone" type="number" placeholder="请输入手机号"
                        class="input-item" />
  <view class="container">
    <div class="avatarBox">
                </div>
                <div class="orderRow">
                    <div class="rowTitle">邮箱</div>
                    <u-input input-align="right" v-model="userInfo.email" type="email" placeholder="请输入邮箱"
                        class="input-item" />
                </div>
            </div>
        </view>
        <view class="btngroup">
            <u-button color="#AEAEAE" class="custom-style" shape="circle" @click="reset">重置</u-button>
            <u-button color="#1D6FE9" class="custom-style" shape="circle" @click="submit">提交</u-button>
        </view>
    </view>
      <u-avatar @click="uploadAvatar" :src="userInfo.avatar" size="114" mode="aspectFill"/>
    </div>
    <view class="detailBox">
      <div class="detailCon">
        <div class="orderRow">
          <div class="rowTitle">姓名</div>
          <div>{{ userInfo.realName }}</div>
        </div>
        <div class="orderRow">
          <div class="rowTitle">所属单位</div>
          <div>{{ userInfo.deptName }}</div>
        </div>
        <div class="orderRow">
          <div class="rowTitle">手机号</div>
          <u-input input-align="right" v-model="userInfo.phone" type="number" placeholder="请输入手机号"
                   class="input-item"/>
        </div>
        <div class="orderRow">
          <div class="rowTitle">邮箱</div>
          <u-input input-align="right" v-model="userInfo.email" type="email" placeholder="请输入邮箱"
                   class="input-item"/>
        </div>
      </div>
    </view>
    <view class="btngroup">
      <u-button color="#AEAEAE" class="custom-style" shape="circle" @click="reset">重置</u-button>
      <u-button
        :color="submitLoading ? '#AEAEAE' : '#1D6FE9'"
        class="custom-style"
        shape="circle"
        text="提交"
        :loading="submitLoading"
        loading-text="提交中"
        loading-mode="circle"
        :loading-size="13"
        @click="submit"
      />
    </view>
  </view>
</template>
<script setup>
    import {
        getEnvObj,
        getWebViewUrl
    } from "@/utils/index.js";
    import {
        getUserInfo,
        updateInfo,
        updatePassword
    } from '@/api/user/index.js';
    import {
        useUserStore
    } from "@/store/index.js";
    const userInfo = ref({
        id: '',
        avatar: '',
        realName: '',
        name: '',
        phone: '',
        email: '',
        deptName: '',
    });
    // 校验手机号
    const validatePhone = () => {
        const phone = userInfo.value.phone
        if (!phone) return true
        const phoneRegex = /^1[3-9]\d{9}$/
        if (!phoneRegex.test(phone)) {
import {getEnvObj} from "@/utils/index.js";
import {getUserInfo, updateInfo} from '@/api/user/index.js';
import {useUserStore} from "@/store/index.js";
import {ref} from "vue";
import {onShow} from "@dcloudio/uni-app";
            uni.showToast({
                title: '请输入正确的手机号码',
                icon: 'none',
                duration: 2000
            });
            return false
        }
        return true
    }
    // 校验邮箱
    const validateEmail = () => {
        if (!userInfo.value.email) return true
        const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
        if (!emailRegex.test(userInfo.value.email)) {
const userInfo = ref({
  id: '',
  avatar: '',
  realName: '',
  name: '',
  phone: '',
  email: '',
  deptName: '',
});
const submitLoading = ref(false);
            uni.showToast({
                title: '请输入正确的邮箱地址',
                icon: 'none',
                duration: 2000
            });
            return false
        }
        return true
    }
    const getUserInfoData = () => {
        getUserInfo().then(res => {
            const user = res.data.data;
            userInfo.value = {
                id: user.id,
                avatar: user.avatar,
                name: user.name,
                realName: user.realName,
                phone: user.phone,
                email: user.email,
                deptName: user.deptName,
            };
// 校验手机号
const validatePhone = () => {
  const phone = userInfo.value.phone
  if (!phone) return true
  const phoneRegex = /^1[3-9]\d{9}$/
  if (!phoneRegex.test(phone)) {
    uni.showToast({
      title: '请输入正确的手机号码',
      icon: 'none',
      duration: 2000
    });
    return false
  }
  return true
}
        });
    };
    const reset = () => {
        getUserInfoData();
    };
// 校验邮箱
const validateEmail = () => {
  if (!userInfo.value.email) return true
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
  if (!emailRegex.test(userInfo.value.email)) {
    const {
        VITE_API_BASE_URL
    } = getEnvObj()
    uni.showToast({
      title: '请输入正确的邮箱地址',
      icon: 'none',
      duration: 2000
    });
    return false
  }
  return true
}
    function uploadUtil(options) {
        const {
            formData,
            filePath,
            url
        } = options;
const getUserInfoData = () => {
  getUserInfo().then(res => {
    const user = res.data.data;
    userInfo.value = {
      id: user.id,
      avatar: user.avatar,
      name: user.name,
      realName: user.realName,
      phone: user.phone,
      email: user.email,
      deptName: user.deptName,
    };
        return new Promise((resolve, reject) => {
            let accessToken = useUserStore()?.$state?.userInfo?.access_token;
  });
};
const reset = () => {
  getUserInfoData();
};
            uni.uploadFile({
                url: `${VITE_API_BASE_URL}${url}`,
                name: 'file',
                header: {
                    'Blade-Auth': 'bearer ' + accessToken
                },
                filePath: filePath,
                formData,
                success: (res) => {
                    const resData = JSON.parse(res.data)
                    if (resData.code === 200 || resData.code === 0) {
                        resolve(res)
                    } else {
                        showToast(resData.message)
const {
  VITE_API_BASE_URL
} = getEnvObj()
                        reject(res)
                    }
                },
                fail: (err) => {
                    reject(err)
                }
            });
        })
    }
    const uploadAvatar = () => {
        uni.chooseImage({
            count: 1,
            success: (res) => {
                const tempFile = res.tempFiles[0]; // 获取文件对象
                const filePath = tempFile.path || tempFile.tempFilePath;
                if (!filePath) {
                    uni.showToast({
                        title: '获取文件路径失败',
                        icon: 'none'
                    });
                    return;
                }
                let fileName = tempFile.name;
                if (!fileName) {
                    const pathWithoutProtocol = filePath.replace(/^file:\/\//, '');
                    fileName = pathWithoutProtocol.split('/').pop() || 'unknown.png';
                }
                // 显示加载中
                uni.showLoading({
                    title: '上传中...'
                });
// 头像上传到服务器
function uploadUtil(options) {
  const {formData, filePath, url} = options;
  uni.showLoading({title: '上传中...'})
  return new Promise((resolve, reject) => {
    let accessToken = useUserStore()?.$state?.userInfo?.access_token;
    uni.uploadFile({
      url: `${VITE_API_BASE_URL}${url}`,
      name: 'file',
      header: {
        'Blade-Auth': 'bearer ' + accessToken
      },
      filePath: filePath,
      formData,
      success: (res) => {
        const resData = JSON.parse(res.data)
        if (resData.code === 200 || resData.code === 0) {
          resolve(res)
        } else {
          uni.showToast(resData.message)
          reject(res)
        }
      },
      fail: (err) => {
        reject(err)
      },
      complete: () => {
        uni.hideLoading()
      }
    });
  })
}
                // 上传文件
                uploadUtil({
                    filePath: filePath,
                    formData: {
                        fileName: fileName,
                        sn: 'avatar_upload'
                    },
                    url: '/blade-resource/oss/endpoint/put-file'
                }).then(res => {
                    const resData = JSON.parse(res.data);
                    if (resData.code === 200 || resData.code === 0) {
                        // 更新头像显示
                        userInfo.value.avatar = resData.data.link || resData.data.url;
                        uni.hideLoading();
                        uni.showToast({
                            title: '头像上传成功',
                            icon: 'success'
                        });
                    } else {
                        throw new Error(resData.message || '上传失败');
const handleUploadResult = (filePath, fileName) => {
  uploadUtil({
    filePath: filePath,
    formData: {
      fileName: fileName,
      sn: 'avatar_upload'
    },
    url: '/blade-resource/oss/endpoint/put-file'
  }).then(res => {
    const resData = JSON.parse(res.data);
    if (resData.code === 200 || resData.code === 0) {
      // 更新头像显示
      userInfo.value.avatar = resData.data.link || resData.data.url;
      uni.showToast({title: '头像上传成功', icon: 'success'});
    } else {
      throw new Error(resData.message || '上传失败');
    }
  }).catch(err => {
    uni.showToast({title: err.message || '上传失败', icon: 'none'});
  });
};
                    }
                }).catch(err => {
                    uni.hideLoading();
                    uni.showToast({
                        title: err.message || '上传失败',
                        icon: 'none'
                    });
                });
            }
        });
    }
    const submit = () => {
        if (!validatePhone() || !validateEmail()) return
        userInfo.value.name = userInfo.value.realName;
        updateInfo(userInfo.value).then(res => {
            if (res.data.code === 200) {
                uni.showToast({
                    title: '修改信息成功',
                    icon: 'none',
                    duration: 2000
                });
                    getUserInfoData()
// 上传头像功能
const uploadAvatar = () => {
  // #ifdef MP-WEIXIN
  uni.chooseMedia({
    count: 1,
    mediaType: ['image'],
    sourceType: ['album', 'camera'],
    maxDuration: 30,
    camera: 'back',
    success(res) {
      const filePath = res.tempFiles[0].tempFilePath;
      handleUploadResult(filePath, filePath);
    }
  })
  // #endif
            } else {
                uni.showToast({
                    title: res.msg,
                    icon: 'none',
                    duration: 2000
                });
                    getUserInfoData()
            }
  // #ifndef MP-WEIXIN
  uni.chooseImage({
    count: 1,
    success: (res) => {
      const tempFile = res.tempFiles[0]; // 获取文件对象
      const filePath = tempFile.path || tempFile.tempFilePath;
      if (!filePath) {
        uni.showToast({title: '获取文件路径失败', icon: 'none'});
        return;
      }
      let fileName = tempFile.name;
      if (!fileName) {
        const pathWithoutProtocol = filePath.replace(/^file:\/\//, '');
        fileName = pathWithoutProtocol.split('/').pop() || 'unknown.png';
      }
      // 上传文件
      handleUploadResult(filePath, fileName);
    }
  });
  // #endif
}
const submit = () => {
  if (submitLoading.value) return
  if (!validatePhone() || !validateEmail()) return
  submitLoading.value = true
  userInfo.value.name = userInfo.value.realName;
  updateInfo(userInfo.value).then(res => {
    if (res.data.code === 200) {
      uni.showToast({
        title: '修改信息成功',
        icon: 'none',
        duration: 1500
      });
      setTimeout(() => {
        uni.switchTab({
          url: '/pages/user/index'
        });
      }, 1500);
      return
        });
    };
    onShow(async () => {
        getUserInfoData()
    });
    } else {
      uni.showToast({
        title: res.msg,
        icon: 'none',
        duration: 2000
      });
      getUserInfoData()
    }
    submitLoading.value = false
  }).catch(() => {
    getUserInfoData()
    submitLoading.value = false
  });
};
onShow(async () => {
  getUserInfoData()
});
</script>
<style lang="scss" scoped>
    .container {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
    }
    .avatarBox {
        width: 228rpx;
        height: 228rpx;
        margin: 76rpx 0;
    }
.container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}
    .detailBox {
        width: 702rpx;
        min-height: 430rpx;
        background: #FFFFFF;
        border-radius: 12rpx;
        box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.avatarBox {
  width: 228rpx;
  height: 228rpx;
  margin: 76rpx 0;
}
        .detailCon {
            padding: 0 24rpx;
        }
.detailBox {
  width: 702rpx;
  min-height: 430rpx;
  background: #FFFFFF;
  border-radius: 12rpx;
  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
        .orderRow {
            display: flex;
            justify-content: space-between;
            align-items: center;
            height: 96rpx;
            border-bottom: 2rpx solid #f5f5f5;
            color: #7b7b7b;
  .detailCon {
    padding: 0 24rpx;
  }
            .rowTitle {
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 30rpx;
                color: #222324;
                white-space: nowrap;
            }
  .orderRow {
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 96rpx;
    border-bottom: 2rpx solid #f5f5f5;
    color: #7b7b7b;
            ::v-deep .u-input {
                border: none !important;
                background: transparent !important;
                padding: 0 !important;
            }
    .rowTitle {
      font-family: Source Han Sans CN, Source Han Sans CN;
      font-weight: 400;
      font-size: 30rpx;
      color: #222324;
      white-space: nowrap;
    }
            ::v-deep .u-input__input {
                border: none !important;
                box-shadow: none !important;
                background: transparent !important;
                padding: 0 !important;
                margin: 0 !important;
                height: auto !important;
            }
        }
    }
    ::v-deep .u-input {
      border: none !important;
      background: transparent !important;
      padding: 0 !important;
    }
    .btngroup {
        display: flex;
        position: absolute;
        bottom: 150rpx;
    }
    ::v-deep .u-input__input {
      border: none !important;
      box-shadow: none !important;
      background: transparent !important;
      padding: 0 !important;
      margin: 0 !important;
      height: auto !important;
    }
  }
}
    :deep(.u-button:first-child) {
        margin-right: 30rpx !important;
    }
.btngroup {
  display: flex;
  position: absolute;
  bottom: 150rpx;
}
    .custom-style {
        width: 276rpx;
        height: 76rpx;
    }
    :deep(.u-button){
        width: 276rpx !important;
        height: 76rpx !important;
    }
</style>
:deep(.u-button:first-child) {
  margin-right: 30rpx !important;
}
.custom-style {
  width: 276rpx;
  height: 76rpx;
}
:deep(.u-button) {
  width: 276rpx !important;
  height: 76rpx !important;
}
</style>
src/subPackages/userDetail/password/index.vue
@@ -21,7 +21,17 @@
        </view>
        <view class="btngroup">
            <u-button color="#AEAEAE" class="custom-style" shape="circle" @click="reset">重置</u-button>
            <u-button color="#1D6FE9" class="custom-style" shape="circle" @click="submit">提交</u-button>
            <u-button
                :color="submitLoading ? '#AEAEAE' : '#1D6FE9'"
                class="custom-style"
                shape="circle"
                text="提交"
                :loading="submitLoading"
                loading-text="提交中"
                loading-mode="circle"
                :loading-size="13"
                @click="submit"
            />
        </view>
    </view>
</template>
@@ -46,6 +56,7 @@
        newPassword: '',
        newPassword1: '',
    });
    const submitLoading = ref(false);
    // 校验密码不能包含中文
    const validatePasswordNoChinese = (password) => {
        const chineseRegex = /[\u4e00-\u9fa5]/;
@@ -71,12 +82,6 @@
            });
            return false;
        }
        // const hasLetter = /[a-zA-Z]/.test(password);
        // const hasNumber = /\d/.test(password);
        // if (!hasLetter || !hasNumber) {
        //   ElMessage.error('密码需同时包含字母和数字');
        //   return false;
        // }
        return true;
    };
    const clearForm = () => {
@@ -90,6 +95,7 @@
        clearForm();
    };
    const submit = () => {
        if (submitLoading.value) return;
        // 原始密码校验
        if (!passwordForm.value.oldPassword) {
@@ -125,6 +131,7 @@
        // 复杂度校验
        if (!validatePasswordStrength(passwordForm.value.newPassword)) return;
        submitLoading.value = true;
        updatePassword(md5(passwordForm.value.oldPassword),
            md5(passwordForm.value.newPassword),
            md5(passwordForm.value.newPassword1)).then(res => {
@@ -132,14 +139,17 @@
                uni.showToast({
                    title: '修改信息成功',
                    icon: 'none',
                    duration: 2000
                    duration: 1500
                });
                clearForm();
                userStore.setUserInfo(null)
                uni.removeStorageSync('rememberedUser');
                uni.reLaunch({
                    url: '/pages/login/index'
                })
                setTimeout(() => {
                    uni.reLaunch({
                        url: '/pages/login/index'
                    })
                }, 1500);
                return
            } else {
                uni.showToast({
@@ -148,7 +158,10 @@
                    duration: 2000
                });
            }
            submitLoading.value = false;
        }).catch(() => {
            submitLoading.value = false;
        });
    };
src/subPackages/workDetail/addWork/index.vue
@@ -1,13 +1,4 @@
<!--
 * @Author       : yuan
 * @Date         : 2025-11-13 18:14:52
 * @LastEditors  : yuan
 * @LastEditTime : 2025-12-20 16:56:27
 * @FilePath     : \src\subPackages\workDetail\addWork\index.vue
 * @Description  :
 * Copyright 2025 OBKoro1, All Rights Reserved.
 * 2025-11-13 18:14:52
-->
<template>
  <view>
    <WebViewPlus
@@ -38,9 +29,6 @@
    uni.setStorageSync("joinParams", {
      type: "add",
    });
    // uni.switchTab({
    //   url: '/pages/work/index'
    // });
    uni.switchTab({
      url: `/pages/work/index?addLog=111`,
    });
src/subPackages/workDetail/mapWork/index.vue
@@ -1,13 +1,4 @@
<!--
 * @Author       : yuan
 * @Date         : 2025-10-22 14:59:10
 * @LastEditors  : yuan
 * @LastEditTime : 2025-12-19 14:52:54
 * @FilePath     : \src\subPackages\workDetail\mapWork\index.vue
 * @Description  :
 * Copyright 2025 OBKoro1, All Rights Reserved.
 * 2025-10-22 14:59:10
-->
<!-- 地图展示 -->
<template>
  <WebViewPlus
@@ -29,14 +20,9 @@
  viewUrl.value = getWebViewUrl("/mapWork", { currentItem: currentItem });
});
function onPostMessage(data) {
  // #ifdef MP-WEIXIN
  // #endif
  // #ifndef MP-WEIXIN
  if (data.type === "workDetailback") {
    // uni.navigateTo({
    //   url: '/subPackages/workDetail/index'
    // });
    uni.navigateBack({
      delta: 1,
    });