# 编译错误修复任务
## 任务描述
下面的错误是uni-app项目调试运行到 [android] 平台时的编译阶段出现的，你需要分析以下报错信息并修复相应的错误。
### uni_modules
    如果需要修复 `找不到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
    
### 修复要求(必须严格遵守)
1. 你的任务是仅修复编译错误，禁止修改与错误无关的代码。
2. 简洁易懂,复杂的代码配上中文注释.
3. 不使用变量和函数的声明提升,严格的在清晰的范围内和正确的顺序中使用变量和函数.
4. 当生成某个平台专用代码时,应使用条件编译进行平台约束,避免干扰其他平台.
5. 修复完成后，需要使用中文向用户说明修复了哪些错误、发生错误的原因以及修复错误的思路.

## 第 1 个错误
- **文件路径**
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')
```

## 第 2 个错误
- **文件路径**
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')
```
