下面的错误是uni-app项目调试运行到 [android] 平台时的编译阶段出现的,你需要分析以下报错信息并修复相应的错误。
如果需要修复 `找不到uni_modules的模块` 错误时,请参考以下模块列表:
android-keeplive
lime-color lime-date-time-picker lime-dayuts lime-echart lime-icon lime-input lime-loading lime-overlay lime-picker lime-popup lime-shared lime-style lime-textarea lime-transition tq-encrypt tui-xechars uni-captcha uni-config-center uni-icons uni-id-common uni-id-pages uni-load-more uni-scss uni-sign-in uni-upgrade-center-app uts-openSchema uts-progressNotification
文件路径 pages/login/index.uvue
文件内容
<template>
<view class="login-page">
<!-- 背景图 -->
<image class="bg-image" src="/static/images/login/1.png" mode="aspectFill"></image>
<scroll-view class="login-content">
<!-- Logo 和标题 -->
<view class="header">
<image class="logo" src="/static/images/index/logo.png" mode="aspectFit"></image>
<text class="title">EMCS</text>
</view>
<!-- 登录表单卡片 -->
<view class="form-card">
<!-- 账号输入 -->
<view class="form-item">
<text class="label">账号</text>
<input class="input" type="text" placeholder="请输入您的账号" v-model="username" />
</view>
<!-- 密码输入 -->
<view class="form-item">
<text class="label">密码</text>
<view class="input-wrapper">
<input class="input" :type="showPassword ? 'text' : 'password'" placeholder="请输入您的密码" v-model="password" />
<image class="eye-icon" :src="showPassword ? '/static/images/login/4.png' : '/static/images/login/3.png'" mode="aspectFit" @click="handleTogglePassword"></image>
</view>
</view>
<!-- 记住密码 (可点击区域切换) -->
<view class="remember-box" @click="handleRememberChange" style="cursor: pointer;">
<checkbox :checked="rememberPassword" class="checkbox" />
<text class="remember-text" style="margin-left: 8px;">记住密码</text>
</view>
<!-- 登录按钮 -->
<button class="login-btn" @click="handleLogin">
<text class="login-btn-text">{{ loading ? "登录中..." : "登录" }}</text>
</button>
</view>
<!-- 底部信息 -->
<view class="footer">
<text class="version">v{{ version }}</text>
<text class="company">韫珠科技</text>
</view>
</scroll-view>
<!-- 自定义选择器弹窗 -->
<view v-if="showPasswordPicker" class="picker-modal">
<view class="modal-mask" @click="showPasswordPicker = false"></view>
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">修改密码</text>
<text class="modal-close" @click="showPasswordPicker = false">取消</text>
</view>
<view class="form-item-input">
<text class="label-picker">新密码</text>
<view class="view-input-picker">
<input class="input-picker" :type="showNewPassword ? 'text' : 'password'" placeholder="请输入新密码" v-model="newpassword" />
<image class="eye-icon-picker" :src="showNewPassword ? '/static/images/login/4.png' : '/static/images/login/3.png'" mode="aspectFit" @click="handleToggleNewPassword"></image>
</view>
</view>
<view class="form-item-input">
<text class="label-picker">确认密码</text>
<view class="view-input-picker">
<input class="input-picker" :type="showConfirmPassword ? 'text' : 'password'" placeholder="请输入确认密码" v-model="confirmpassword" />
<image class="eye-icon-picker" :src="showConfirmPassword ? '/static/images/login/4.png' : '/static/images/login/3.png'" mode="aspectFit" @click="handleToggleConfirmPassword"></image>
</view>
</view>
<view class="form-item-btn">
<button class="btn-primary" @click="handleRestPassword">
确认
</button>
</view>
</view>
</view>
<!-- 下载进度遮罩(仅更新数据不重建,无闪烁) -->
<view v-if="isDownload" class="download-progress-mask">
<view class="download-progress-box">
<text class="download-progress-title">下载中...</text>
<view class="download-progress-bar">
<view class="download-progress-fill" :style="{ width: downloadProgress + '%' }"></view>
</view>
<text class="download-progress-text">{{ downloadProgress }}%</text>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed, onMounted } from 'vue'
import { loginByAccount, loginSSO, getUserInfo, resetPassword , getIsKey} from '../../api/auth/login'
import { getVersion } from '../../api/system/config.uts'
import { getBaseUrl } from '../../utils/request'
import {
saveAccessToken,
saveUserInfo,
getRememberedAccount,
saveRememberedAccount,
clearRememberedAccount,
saveStoreIsKey,
getStoreIsKey
} from '../../utils/storage'
import { validatePassword } from '../../utils/validate'
// @ts-ignore
import manifest from '@/manifest.json'
// 表单数据
const username = ref<string>("")
const password = ref<string>("")
const newpassword = ref<string>("")
const confirmpassword = ref<string>("")
const rememberPassword = ref<boolean>(false)
const showPassword = ref<boolean>(false)
const showNewPassword = ref<boolean>(false)
const showConfirmPassword = ref<boolean>(false)
const loading = ref<boolean>(false)
const isDownload = ref<boolean>(false)
const downloadProgress = ref<number>(0)
const showPasswordPicker = ref<boolean>(false)
// 版本号
const manifestVersion = manifest.versionName as string | null
const version = ref<string>(manifestVersion != null ? manifestVersion : '1.0.0')
const versionServer = ref<string>('1.0.0')
// 是否可以登录
const canLogin = computed((): boolean => {
return username.value.length > 0 && password.value.length > 0 && !loading.value
})
// 记住密码切换
const handleRememberChange = (): void => {
rememberPassword.value = !rememberPassword.value
}
// 切换密码显示/隐藏
const handleTogglePassword = (): void => {
showPassword.value = !showPassword.value
}
// 切换密码显示/隐藏
const handleToggleNewPassword = (): void => {
showNewPassword.value = !showNewPassword.value
}
// 切换密码显示/隐藏
const handleToggleConfirmPassword = (): void => {
showConfirmPassword.value = !showConfirmPassword.value
}
const handleRestPassword = async (): Promise<void> => {
try {
if(newpassword.value != confirmpassword.value){
uni.showToast({
title: '两次输入的密码不一致',
icon: 'error',
duration: 3000
})
return;
}
await resetPassword(username.value, password.value, newpassword.value);
showPasswordPicker.value = false;
uni.showToast({
title: '修改成功,请重新登录',
icon: 'success',
duration: 3000
})
newpassword.value = ""
confirmpassword.value = "";
} catch (e: any) {
uni.showToast({
title: e.message ?? '密码修改失败',
icon: 'none',
duration: 2000
})
}
};
const loginSuccess = async(result: any) : Promise<void> => {
// 提取 data 部分
const resultObj = result as UTSJSONObject
// const data = resultObj['data'] as UTSJSONObject
const isInitPassword = resultObj["isInitPassword"] as boolean | null;
// console.log("================"+ code)
if(isInitPassword==true){
showPasswordPicker.value = true;
return
}
uni.setStorageSync("login_key", "1")
// 保存登录信息
saveAccessToken(resultObj['token'] as string)
const userInfoJson = await getUserInfo();
console.log(userInfoJson);
const userInfoObj = userInfoJson as UTSJSONObject
const userInfo = userInfoObj['user'] as UTSJSONObject
const deptInfo = userInfo['dept'] as UTSJSONObject
const permissions = userInfoObj['permissions'] as any[]
saveUserInfo({
userId: userInfo['userId'],
userName: userInfo['userName'],
nickName: userInfo['nickName'],
phone: userInfo['phonenumber'],
deptName: deptInfo['deptName'],
roleNames: userInfoObj['roleNames'],
permissions: permissions
})
// 保存或清除记住的账号密码
if (rememberPassword.value) {
saveRememberedAccount(username.value, password.value)
} else {
clearRememberedAccount()
}
// 跳转到首页
/*setTimeout(() => {
uni.redirectTo({
url: '/pages/index/index'
})
}, 1000)*/
setTimeout(() => {
uni.redirectTo({
url: '/pages/index/index'
})
}, 1000)
}
// 登录处理
const handleLogin = async (): Promise<void> => {
// 验证输入
if (username.value.trim().length == 0) {
uni.showToast({
title: '请输入账号',
icon: 'none',
duration: 2000
})
return
}
if (password.value.trim().length == 0) {
uni.showToast({
title: '请输入密码',
icon: 'none',
duration: 2000
})
return
}
try {
loading.value = true
/* const resultKey = await getIsKey();
const resultKeyObj = resultKey as UTSJSONObject
const isKey = resultKeyObj["data"] as string |'0'
saveStoreIsKey(isKey); */
const result = await loginByAccount(username.value, password.value)
loginSuccess(result);
uni.showToast({
title: '登录成功',
icon: 'success',
duration: 3000
})
} catch (e: any) {
uni.showToast({
title: e.message ?? '登录失败',
icon: 'none',
duration: 2000
})
} finally {
loading.value = false
}
}
const handleLoginSSO = async (apptoken:string): Promise<void> => {
try {
/* const resultKey = await getIsKey();
const resultKeyObj = resultKey as UTSJSONObject
const isKey = resultKeyObj["data"] as string |'0'
saveStoreIsKey(isKey); */
let result = await loginSSO(apptoken)
console.log('自动登录:', result)
loginSuccess(result);
} catch (e: any) {
uni.showToast({
title: e.message ?? '登录失败',
icon: 'none',
duration: 2000
})
} finally {
loading.value = false
}
}
const compareVersion = (newVersion: string, currentVersion: string): boolean => {
const newVer = newVersion.split('.').map(item => parseInt(item))
const currentVer = currentVersion.split('.').map(item => parseInt(item))
const maxLength = Math.max(newVer.length, currentVer.length)
for (let i = 0; i < maxLength; i++) {
const newNum = i < newVer.length ? newVer[i] : 0
const currentNum = i < currentVer.length ? currentVer[i] : 0
if (newNum > currentNum) {
return true
} else if (newNum < currentNum) {
return false
}
}
return false
}
// 安装APK的单独函数
const installApkFile = (filePath: string): void => {
uni.installApk({
filePath: filePath,
success: () => {
console.log('安装成功');
uni.showToast({
title: '安装成功',
icon: 'success',
duration: 3000
});
},
fail: (error) => {
console.error('安装失败:', error);
uni.showToast({
title: '安装失败',
icon: 'none',
duration: 3000
});
},
complete: (res) => {
console.log('安装完成:', res);
}
});
}
const installApkWithProgress = (): void => {
// #ifdef APP-ANDROID
isDownload.value = true;
downloadProgress.value = 0;
let donwloadUrl = '';
donwloadUrl = getBaseUrl() + '/profile/app/emcs-release-'+versionServer.value+'.apk'
// 下载APK
const downloadTask = uni.downloadFile({
url: donwloadUrl,
filePath: `${uni.env.USER_DATA_PATH}/${Date.now()}_test.apk`, // 使用时间戳防止重名
success: (downloadRes) => {
isDownload.value = false;
if (downloadRes.statusCode == 200) {
// 确认安装
uni.showModal({
title: '安装提示',
content: '下载完成,是否立即安装?',
success: (modalRes) => {
if (modalRes.confirm) {
installApkFile(downloadRes.tempFilePath);
}
}
});
} else {
uni.showToast({
title: '下载失败',
icon: 'error',
duration: 3000
});
}
},
fail: (error) => {
isDownload.value = false;
uni.showToast({
title: '下载失败',
icon: 'none',
duration: 3000
});
console.error('下载失败:', error);
}
});
// 监听下载进度(更新页面内进度条,节流 5% 减轻渲染压力)
let lastProgress = -5;
downloadTask.onProgressUpdate((res) => {
const p = Math.round(res.progress);
if (p - lastProgress >= 5 || p >= 100) {
lastProgress = p;
downloadProgress.value = p;
}
});
// #endif
// #ifdef APP-HARMONY
uni.showToast({
title: '请登录PC端,扫描二维码下载并安装',
icon: 'none',
duration: 3000
});
// #endif
// #ifdef APP-IOS
uni.showToast({
title: '请登录PC端,扫描二维码下载并安装',
icon: 'none',
duration: 3000
});
// #endif
}
const checkVersion = async (): Promise<void> => {
const versionJSON = await getVersion() as UTSJSONObject
versionServer.value = versionJSON['msg'] as string
const hasNewVersion = compareVersion(versionServer.value, version.value) // true
console.log("versionServer:"+versionServer.value)
console.log("hasNewVersion:"+hasNewVersion)
if(hasNewVersion){
installApkWithProgress();
}
}
onLoad((options: UTSJSONObject | null) => {
console.log('URL参数:', options)
// 延迟一点执行,确保App.vue已保存到缓存
setTimeout(() => {
// 2. 从页面参数获取
if (options != null) {
const apptokenValue = options['apptoken']
if (apptokenValue != null) {
if (typeof apptokenValue == 'string') {
if(apptokenValue.length > 0){
const apptoken = apptokenValue as string
console.log('获取到自动登录apptoken:', apptoken)
handleLoginSSO(apptoken);
}
}
}
}
}, 500)
})
// 初始化:加载记住的账号密码
onMounted(() => {
checkVersion()
const remembered = getRememberedAccount()
if (remembered != null) {
username.value = remembered['username'] as string
password.value = remembered['password'] as string
rememberPassword.value = true
// 检查是否是从退出操作进入登录页
const isLogout = uni.getStorageSync('isLogout')
if (isLogout == null || isLogout == false || isLogout == '') {
// 自动登录
handleLogin()
} else {
// 清除退出标志
uni.removeStorageSync('isLogout')
}
}
})
</script>
<style lang="scss">
.login-page {
position: relative;
flex: 1;
padding-top: env(safe-area-inset-top);
}
.bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.login-content {
position: relative;
flex: 1;
padding: 60rpx 40rpx;
z-index: 1;
}
.header {
align-items: center;
margin-bottom: 80rpx;
}
.logo {
height: 200rpx;
margin-bottom: 40rpx;
border-radius:10px;
}
.title {
font-size: 48rpx;
color: #ffffff;
font-weight: bold;
// #ifndef APP-HARMONY
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
// #endif
}
.form-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 24rpx;
padding: 50rpx 40rpx;
// #ifndef APP-HARMONY
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
// #endif
margin-bottom: 100rpx;
}
.form-item {
margin-bottom: 40rpx;
}
.label {
font-size: 32rpx;
color: #333333;
font-weight: bold;
margin-bottom: 20rpx;
}
.input-wrapper {
position: relative;
width: 100%;
}
.input {
width: 100%;
height: 90rpx;
padding: 0 100rpx 0 30rpx;
background-color: #f5f5f5;
border-radius: 12rpx;
font-size: 30rpx;
color: #333333;
border: 1rpx solid transparent;
}
.input:focus {
border-color: #007aff;
background-color: #ffffff;
}
.eye-icon {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
width: 40rpx;
height: 40rpx;
}
.remember-box {
flex-direction: row;
align-items: center;
margin-bottom: 50rpx;
}
.checkbox {
transform: scale(0.8);
}
.remember-text {
font-size: 28rpx;
color: #1677ff;
margin-left: 12rpx;
}
.login-btn {
width: 100%;
height: 100rpx;
line-height: 100rpx;
background-color: #0081ff;
border-radius: 50rpx;
color: #ffffff;
// #ifndef APP-HARMONY
box-shadow: 0 8rpx 16rpx rgba(0, 122, 255, 0.3);
// #endif
}
.login-btn-text {
font-size: 36rpx;
color: #ffffff !important;
font-weight: bold;
}
.footer {
position: fixed;
bottom: 60rpx;
left: 0;
right: 0;
align-items: center;
z-index: 2;
}
.version {
font-size: 28rpx;
color: #333333;
margin-bottom: 15rpx;
}
.company {
font-size: 24rpx;
color: #333333;
}
.picker-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
}
.modal-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
border-top-left-radius: 16rpx;
border-top-right-radius: 16rpx;
max-height: 1000rpx;
}
.modal-header {
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.modal-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.modal-close {
font-size: 28rpx;
color: #007aff;
}
.form-item-btn{
flex: 1;
align-items: center;
margin-bottom: 10px;
}
.form-item-input{
flex: 1;
flex-direction: row;
background-color: #ffffff;
padding: 10px;
}
.btn-primary {
z-index: 999;
font-size: 15px;
border-radius: 10rpx;
width: 100px;
padding: 5px;
background-color: #165DFF;
line-height: 45rpx;
color: #ffffff;
.btn-text{
color: #ffffff;
padding: 5px 15px;
}
}
.label-picker{
font-size: 32rpx;
color: #333333;
font-weight: bold;
width: 180rpx;
padding: 20rpx;
}
.input-picker {
width: 70%;
height: 90rpx;
padding: 0 100rpx 0 30rpx;
background-color: #f5f5f5;
border-radius: 12rpx;
font-size: 30rpx;
color: #333333;
border: 1rpx solid transparent;
}
.eye-icon-picker {
position: absolute;
right: 230rpx;
top: 50%;
transform: translateY(-50%);
width: 40rpx;
height: 40rpx;
}
.download-progress-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
align-items: center;
justify-content: center;
}
.download-progress-box {
width: 500rpx;
padding: 60rpx;
background-color: #ffffff;
border-radius: 24rpx;
align-items: center;
}
.download-progress-title {
font-size: 32rpx;
color: #333333;
margin-bottom: 30rpx;
}
.download-progress-bar {
width: 100%;
height: 16rpx;
background-color: #f0f0f0;
border-radius: 8rpx;
overflow: hidden;
margin-bottom: 20rpx;
}
.download-progress-fill {
height: 100%;
background-color: #0081ff;
border-radius: 8rpx;
transition: width 0.2s ease;
}
.download-progress-text {
font-size: 28rpx;
color: #666666;
}
</style>
错误信息
error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'UTSJSONObject?'.
at pages/login/index.uvue:127:28
125|
126| // 版本号
127| const manifestVersion = manifest.versionName as string | null
| ^
128| const version = ref<string>(manifestVersion != null ? manifestVersion : '1.0.0')
129| const versionServer = ref<string>('1.0.0')
文件路径 pages/profile/index.uvue
文件内容
<template>
<view class="page-container">
<!-- 背景图 -->
<image class="bg-image" src="/static/images/profile/1.png" mode="widthFix"></image>
<scroll-view class="page-content" style="flex: 1">
<!-- 用户信息头部 -->
<view class="header">
<image class="avatar" src="/static/images/login/2.png" mode="aspectFill"></image>
<text class="user-name">{{ nickName }}({{ userName }})</text>
</view>
<!-- 信息卡片区域 -->
<view class="info-cards">
<!-- 电话卡片 -->
<view class="info-card">
<image class="card-icon" src="/static/images/profile/2.png" mode="aspectFit"></image>
<text class="card-label">电话</text>
<text class="card-value">{{ userPhone }}</text>
</view>
<!-- 部门卡片 -->
<view class="info-card">
<image class="card-icon" src="/static/images/profile/3.png" mode="aspectFit"></image>
<text class="card-label">部门</text>
<text class="card-value">{{ userDept }}</text>
</view>
</view>
<!-- 通讯录 -->
<view class="contact-item">
<text class="contact-text">{{userRole}}</text>
</view>
<view class="menu-section">
<view class="menu-item" @click="handlePassword()">
<image class="menu-icon" src="/static/images/profile/7.png" mode="aspectFit"></image>
<text class="menu-title">修改密码</text>
<text class="menu-arrow">›</text>
</view>
<view class="menu-item">
<image class="menu-icon" src="/static/images/profile/5.png" mode="aspectFit"></image>
<text class="menu-title">版本:{{version}}</text>
<text class="menu-title-download" v-if="isNewVersion" @click="handleVersionClick()">升级</text>
</view>
</view>
<!-- 功能菜单 -->
<!-- <view class="menu-section">
<view v-for="(item, index) in menuList" :key="index" class="menu-item" @click="handleMenuClick(item)">
<image class="menu-icon" src="/static/images/profile/7.png" mode="aspectFit"></image>
<text class="menu-title">修改密码</text>
<text class="menu-arrow">›</text>
</view>
</view> -->
<!-- 退出 -->
<view class="logout-wrapper">
<text class="logout-btn" @click="handleLogout">退出</text>
</view>
</scroll-view>
<!-- 下载进度遮罩(仅更新数据不重建,无闪烁) -->
<view v-if="isDownload" class="download-progress-mask">
<view class="download-progress-box">
<text class="download-progress-title">下载中...</text>
<view class="download-progress-bar">
<view class="download-progress-fill" :style="{ width: downloadProgress + '%' }"></view>
</view>
<text class="download-progress-text">{{ downloadProgress }}%</text>
</view>
</view>
<custom-tabbar :current="3" />
</view>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import { getUserInfo, getStoreIsKey } from '../../utils/storage'
import { logout as logoutApi } from '../../api/auth/logout'
import { useAuth } from '../../composables/useAuth'
import { encryptAES, decryptAES } from '../../utils/crypto'
import { getIsKey } from '../../api/auth/login'
import { getConfigKey } from '../../api/system/config.uts'
import { getBaseUrl } from '../../utils/request'
// @ts-ignore
import manifest from '@/manifest.json'
// 用户信息
const userName = ref<string>('')
const userPhone = ref<string>('')
const userDept = ref<string>('')
const userRole = ref<string>('')
const isNewVersion = ref<boolean>(false)
const nickName = ref<string>('')
const isDownload = ref<boolean>(false)
const downloadProgress = ref<number>(0)
// 版本信息
const manifestVersion = manifest.versionName as string | null
const version = ref<string>(manifestVersion != null ? manifestVersion : '1.0.0')
const versionServer = ref<string>('1.0.0')
// 认证管理
const auth = useAuth()
const clearAuth = auth.logout
const getCurrentAccessToken = auth.getCurrentAccessToken
// 菜单列表
type MenuItem = {
id: number
title: string
icon: string
path?: string
action?: string
}
const menuList = ref<MenuItem[]>([
{
id: 1,
title: '修改密码',
icon: '/static/images/profile/7.png',
path: '/pages/profile/password/index'
},
{
id: 2,
title: '关于版本',
icon: '/static/images/profile/5.png',
action: 'version'
},
{
id: 3,
title: '关于我们',
icon: '/static/images/profile/6.png',
path: '/pages/profile/about/index'
},
{
id: 4,
title: '通用设置',
icon: '/static/images/profile/8.png',
path: '/pages/profile/settings/index'
}
])
// 通讯录点击
const handleContactClick = (): void => {
uni.showToast({
title: '功能开发中',
icon: 'none'
})
}
const compareVersion = (newVersion: string, currentVersion: string): boolean => {
const newVer = newVersion.split('.').map(item => parseInt(item))
const currentVer = currentVersion.split('.').map(item => parseInt(item))
const maxLength = Math.max(newVer.length, currentVer.length)
for (let i = 0; i < maxLength; i++) {
const newNum = i < newVer.length ? newVer[i] : 0
const currentNum = i < currentVer.length ? currentVer[i] : 0
if (newNum > currentNum) {
return true
} else if (newNum < currentNum) {
return false
}
}
return false
}
const checkVersion = async (): Promise<void> => {
const versionJSON = await getConfigKey("emcs.android.version") as UTSJSONObject
versionServer.value = versionJSON['msg'] as string
const hasNewVersion = compareVersion(versionServer.value, version.value) // true
console.log("versionServer:"+versionServer.value)
console.log("hasNewVersion:"+hasNewVersion)
if(hasNewVersion){
isNewVersion.value = true
}
}
const handlePassword = (): void =>{
uni.navigateTo({
url:"/pages/profile/password/index",
fail: (err: any) => {
uni.showToast({
title: '功能开发中',
icon: 'none'
})
}
})
}
// 菜单点击
const handleMenuClick = (item: MenuItem): void => {
if (item.action == 'version') {
uni.showModal({
title: '版本信息',
content: `当前版本:v${version.value}`,
showCancel: false
})
return
}
if (item.path != null && item.path.length > 0) {
uni.navigateTo({
url: item.path,
fail: (err: any) => {
uni.showToast({
title: '功能开发中',
icon: 'none'
})
}
})
}
}
// 执行退出登录(必须在 handleLogout 之前定义)
const doLogout = async (): Promise<void> => {
try {
await logoutApi()
} catch (e: any) {
console.log('退出登录接口调用失败', e)
} finally {
uni.setStorageSync("login_key", "0")
clearAuth()
}
}
// 退出登录
const handleLogout = (): void => {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
// 提取属性(any 类型不能直接访问属性)
const confirm = res.confirm as boolean
if (confirm) {
// 使用异步函数包装
doLogout()
}
}
})
}
// 安装APK的单独函数
const installApkFile = (filePath: string): void => {
uni.installApk({
filePath: filePath,
success: () => {
console.log('安装成功');
uni.showToast({
title: '安装成功',
icon: 'success'
});
},
fail: (error) => {
console.error('安装失败:', error);
uni.showToast({
title: '安装失败',
icon: 'none'
});
},
complete: (res) => {
console.log('安装完成:', res);
}
});
}
const installApkWithProgress = (): void => {
// #ifdef APP-ANDROID
isDownload.value = true;
downloadProgress.value = 0;
let donwloadUrl = '';
donwloadUrl = getBaseUrl() + '/profile/app/emcs-release-'+versionServer.value+'.apk'
// 下载APK
const downloadTask = uni.downloadFile({
url: donwloadUrl,
filePath: `${uni.env.USER_DATA_PATH}/${Date.now()}_test.apk`, // 使用时间戳防止重名
success: (downloadRes) => {
isDownload.value = false;
if (downloadRes.statusCode == 200) {
// 确认安装
uni.showModal({
title: '安装提示',
content: '下载完成,是否立即安装?',
success: (modalRes) => {
if (modalRes.confirm) {
installApkFile(downloadRes.tempFilePath);
}
}
});
} else {
uni.showToast({
title: '下载失败',
icon: 'error'
});
}
},
fail: (error) => {
isDownload.value = false;
uni.showToast({
title: '下载失败',
icon: 'none'
});
console.error('下载失败:', error);
}
});
// 监听下载进度(更新页面内进度条,节流 5% 减轻渲染压力)
let lastProgress = -5;
downloadTask.onProgressUpdate((res) => {
const p = Math.round(res.progress);
if (p - lastProgress >= 5 || p >= 100) {
lastProgress = p;
downloadProgress.value = p;
}
});
// #endif
// #ifdef APP-HARMONY
uni.showToast({
title: '请登录PC端,扫描二维码下载并安装',
icon: 'none'
});
// #endif
// #ifdef APP-IOS
uni.showToast({
title: '请登录PC端,扫描二维码下载并安装',
icon: 'none'
});
// #endif
}
// 菜单点击
const handleVersionClick = (): void => {
installApkWithProgress();
}
// 初始化
onMounted(() => {
checkVersion();
const userInfo = getUserInfo()
if (userInfo != null) {
const nickNameRaw = userInfo['nickName'] as string | null
const phone = userInfo['phone'] as string | null
const userNameRaw = userInfo['userName'] as string | null
userName.value = userNameRaw ?? ''
nickName.value = nickNameRaw ?? ''
if (phone != null && phone.length > 0) {
userPhone.value = phone
}
const deptName = userInfo['deptName'] as string | null
if (deptName != null && deptName.length > 0) {
userDept.value = deptName
}
const roleNames = userInfo['roleNames'] as string | null
if (roleNames != null && roleNames.length > 0) {
userRole.value = roleNames
}
}
})
</script>
<style lang="scss">
.page-container {
position: relative;
flex: 1;
background: #f5f8fb;
padding-top: env(safe-area-inset-top);
}
.bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.page-content {
position: relative;
flex: 1;
padding-bottom: 100rpx;
z-index: 1;
}
.header {
padding: 80rpx 30rpx 40rpx;
flex-direction: row;
align-items: center;
.avatar {
width: 120rpx;
height: 120rpx;
border: 1rpx solid #ccc;
border-radius: 60rpx;
background-color: #fff;
margin-right: 30rpx;
}
.user-name {
font-size: 40rpx;
color: #ffffff;
font-weight: bold;
}
}
.info-cards {
padding: 0 30rpx;
flex-direction: row;
justify-content: space-between;
margin-bottom: 30rpx;
.info-card {
width: 330rpx;
background-color: #ffffff;
border-radius: 24rpx;
padding: 30rpx 24rpx;
/* #ifndef APP-HARMONY */
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
/* #endif */
.card-icon {
width: 48rpx;
height: 48rpx;
margin-bottom: 16rpx;
}
.card-label {
font-size: 26rpx;
color: #666666;
margin-bottom: 12rpx;
}
.card-value {
font-size: 32rpx;
color: #333333;
font-weight: bold;
}
}
}
.contact-item {
margin: 0 30rpx 30rpx;
padding: 30rpx;
background-color: #ffffff;
border-radius: 24rpx;
flex-direction: row;
align-items: center;
/* #ifndef APP-HARMONY */
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
/* #endif */
.contact-icon {
width: 48rpx;
height: 48rpx;
margin-right: 24rpx;
}
.contact-text {
flex: 1;
font-size: 32rpx;
color: #333333;
}
.contact-arrow {
font-size: 48rpx;
color: #cccccc;
line-height: 48rpx;
}
}
.menu-section {
margin: 0 30rpx 30rpx;
background-color: #ffffff;
border-radius: 24rpx;
overflow: hidden;
/* #ifndef APP-HARMONY */
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
/* #endif */
.menu-item {
flex-direction: row;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.menu-icon {
width: 48rpx;
height: 48rpx;
margin-right: 24rpx;
}
.menu-title {
flex: 1;
font-size: 32rpx;
color: #333333;
}
.menu-title-download {
font-size: 30rpx;
color: #5500ff;
}
.menu-arrow {
font-size: 48rpx;
color: #cccccc;
line-height: 48rpx;
}
}
}
.logout-wrapper {
margin: 0 30rpx 30rpx;
background-color: #ffffff;
border-radius: 24rpx;
overflow: hidden;
align-items: center;
justify-content: center;
height: 100rpx;
/* #ifndef APP-HARMONY */
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
/* #endif */
.logout-btn {
font-size: 32rpx;
color: #666666;
padding: 20rpx 0;
}
}
.download-progress-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
align-items: center;
justify-content: center;
}
.download-progress-box {
width: 500rpx;
padding: 60rpx;
background-color: #ffffff;
border-radius: 24rpx;
align-items: center;
}
.download-progress-title {
font-size: 32rpx;
color: #333333;
margin-bottom: 30rpx;
}
.download-progress-bar {
width: 100%;
height: 16rpx;
background-color: #f0f0f0;
border-radius: 8rpx;
overflow: hidden;
margin-bottom: 20rpx;
}
.download-progress-fill {
height: 100%;
background-color: #0081ff;
border-radius: 8rpx;
transition: width 0.2s ease;
}
.download-progress-text {
font-size: 28rpx;
color: #666666;
}
</style>
错误信息
error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'UTSJSONObject?'.
at pages/profile/index.uvue:100:28
98 |
99 | // 版本信息
100| const manifestVersion = manifest.versionName as string | null
| ^
101| const version = ref<string>(manifestVersion != null ? manifestVersion : '1.0.0')
102| const versionServer = ref<string>('1.0.0')