index.uvue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. <template>
  2. <view class="page-container">
  3. <!-- 背景图 -->
  4. <image class="bg-image" src="/static/images/profile/1.png" mode="widthFix"></image>
  5. <scroll-view class="page-content" style="flex: 1">
  6. <!-- 用户信息头部 -->
  7. <view class="header">
  8. <image class="avatar" src="/static/images/login/2.png" mode="aspectFill"></image>
  9. <text class="user-name">{{ nickName }}({{ userName }})</text>
  10. </view>
  11. <!-- 信息卡片区域 -->
  12. <view class="info-cards">
  13. <!-- 电话卡片 -->
  14. <view class="info-card">
  15. <image class="card-icon" src="/static/images/profile/2.png" mode="aspectFit"></image>
  16. <text class="card-label">电话</text>
  17. <text class="card-value">{{ userPhone }}</text>
  18. </view>
  19. <!-- 部门卡片 -->
  20. <view class="info-card">
  21. <image class="card-icon" src="/static/images/profile/3.png" mode="aspectFit"></image>
  22. <text class="card-label">部门</text>
  23. <text class="card-value">{{ userDept }}</text>
  24. </view>
  25. </view>
  26. <!-- 通讯录 -->
  27. <view class="contact-item">
  28. <text class="contact-text">{{userRole}}</text>
  29. </view>
  30. <view class="menu-section">
  31. <view class="menu-item" @click="handlePassword()">
  32. <image class="menu-icon" src="/static/images/profile/7.png" mode="aspectFit"></image>
  33. <text class="menu-title">修改密码</text>
  34. <text class="menu-arrow">›</text>
  35. </view>
  36. <view class="menu-item">
  37. <image class="menu-icon" src="/static/images/profile/5.png" mode="aspectFit"></image>
  38. <text class="menu-title">版本:{{version}}</text>
  39. <text class="menu-title-download" v-if="isNewVersion" @click="handleVersionClick()">升级</text>
  40. </view>
  41. </view>
  42. <!-- 功能菜单 -->
  43. <!-- <view class="menu-section">
  44. <view v-for="(item, index) in menuList" :key="index" class="menu-item" @click="handleMenuClick(item)">
  45. <image class="menu-icon" src="/static/images/profile/7.png" mode="aspectFit"></image>
  46. <text class="menu-title">修改密码</text>
  47. <text class="menu-arrow">›</text>
  48. </view>
  49. </view> -->
  50. <!-- 退出 -->
  51. <view class="logout-wrapper">
  52. <text class="logout-btn" @click="handleLogout">退出</text>
  53. </view>
  54. </scroll-view>
  55. <!-- 下载进度遮罩(仅更新数据不重建,无闪烁) -->
  56. <view v-if="isDownload" class="download-progress-mask">
  57. <view class="download-progress-box">
  58. <text class="download-progress-title">下载中...</text>
  59. <view class="download-progress-bar">
  60. <view class="download-progress-fill" :style="{ width: downloadProgress + '%' }"></view>
  61. </view>
  62. <text class="download-progress-text">{{ downloadProgress }}%</text>
  63. </view>
  64. </view>
  65. <custom-tabbar :current="3" />
  66. </view>
  67. </template>
  68. <script setup lang="uts">
  69. import { ref, onMounted } from 'vue'
  70. import { getUserInfo, getStoreIsKey } from '../../utils/storage'
  71. import { logout as logoutApi } from '../../api/auth/logout'
  72. import { useAuth } from '../../composables/useAuth'
  73. import { encryptAES, decryptAES } from '../../utils/crypto'
  74. import { getIsKey } from '../../api/auth/login'
  75. import { getConfigKey } from '../../api/system/config.uts'
  76. import { getBaseUrl } from '../../utils/request'
  77. // @ts-ignore
  78. import manifest from '@/manifest.json'
  79. // 用户信息
  80. const userName = ref<string>('')
  81. const userPhone = ref<string>('')
  82. const userDept = ref<string>('')
  83. const userRole = ref<string>('')
  84. const isNewVersion = ref<boolean>(false)
  85. const nickName = ref<string>('')
  86. const isDownload = ref<boolean>(false)
  87. const downloadProgress = ref<number>(0)
  88. // 版本信息
  89. const manifestVersion = manifest?.versionName as string | null
  90. const version = ref<string>(manifestVersion != null ? manifestVersion : '1.0.0')
  91. const versionServer = ref<string>('1.0.0')
  92. // 认证管理
  93. const auth = useAuth()
  94. const clearAuth = auth.logout
  95. const getCurrentAccessToken = auth.getCurrentAccessToken
  96. // 菜单列表
  97. type MenuItem = {
  98. id: number
  99. title: string
  100. icon: string
  101. path?: string
  102. action?: string
  103. }
  104. const menuList = ref<MenuItem[]>([
  105. {
  106. id: 1,
  107. title: '修改密码',
  108. icon: '/static/images/profile/7.png',
  109. path: '/pages/profile/password/index'
  110. },
  111. {
  112. id: 2,
  113. title: '关于版本',
  114. icon: '/static/images/profile/5.png',
  115. action: 'version'
  116. },
  117. {
  118. id: 3,
  119. title: '关于我们',
  120. icon: '/static/images/profile/6.png',
  121. path: '/pages/profile/about/index'
  122. },
  123. {
  124. id: 4,
  125. title: '通用设置',
  126. icon: '/static/images/profile/8.png',
  127. path: '/pages/profile/settings/index'
  128. }
  129. ])
  130. // 通讯录点击
  131. const handleContactClick = (): void => {
  132. uni.showToast({
  133. title: '功能开发中',
  134. icon: 'none'
  135. })
  136. }
  137. const compareVersion = (newVersion: string, currentVersion: string): boolean => {
  138. const newVer = newVersion.split('.').map(item => parseInt(item))
  139. const currentVer = currentVersion.split('.').map(item => parseInt(item))
  140. const maxLength = Math.max(newVer.length, currentVer.length)
  141. for (let i = 0; i < maxLength; i++) {
  142. const newNum = i < newVer.length ? newVer[i] : 0
  143. const currentNum = i < currentVer.length ? currentVer[i] : 0
  144. if (newNum > currentNum) {
  145. return true
  146. } else if (newNum < currentNum) {
  147. return false
  148. }
  149. }
  150. return false
  151. }
  152. const checkVersion = async (): Promise<void> => {
  153. const versionJSON = await getConfigKey("emcs.android.version") as UTSJSONObject
  154. versionServer.value = versionJSON['msg'] as string
  155. const hasNewVersion = compareVersion(versionServer.value, version.value) // true
  156. console.log("versionServer:"+versionServer.value)
  157. console.log("hasNewVersion:"+hasNewVersion)
  158. if(hasNewVersion){
  159. isNewVersion.value = true
  160. }
  161. }
  162. const handlePassword = (): void =>{
  163. uni.navigateTo({
  164. url:"/pages/profile/password/index",
  165. fail: (err: any) => {
  166. uni.showToast({
  167. title: '功能开发中',
  168. icon: 'none'
  169. })
  170. }
  171. })
  172. }
  173. // 菜单点击
  174. const handleMenuClick = (item: MenuItem): void => {
  175. if (item.action == 'version') {
  176. uni.showModal({
  177. title: '版本信息',
  178. content: `当前版本:v${version.value}`,
  179. showCancel: false
  180. })
  181. return
  182. }
  183. if (item.path != null && item.path.length > 0) {
  184. uni.navigateTo({
  185. url: item.path,
  186. fail: (err: any) => {
  187. uni.showToast({
  188. title: '功能开发中',
  189. icon: 'none'
  190. })
  191. }
  192. })
  193. }
  194. }
  195. // 执行退出登录(必须在 handleLogout 之前定义)
  196. const doLogout = async (): Promise<void> => {
  197. try {
  198. await logoutApi()
  199. } catch (e: any) {
  200. console.log('退出登录接口调用失败', e)
  201. } finally {
  202. uni.setStorageSync("login_key", "0")
  203. clearAuth()
  204. }
  205. }
  206. // 退出登录
  207. const handleLogout = (): void => {
  208. uni.showModal({
  209. title: '提示',
  210. content: '确定要退出登录吗?',
  211. success: (res) => {
  212. // 提取属性(any 类型不能直接访问属性)
  213. const confirm = res.confirm as boolean
  214. if (confirm) {
  215. // 使用异步函数包装
  216. doLogout()
  217. }
  218. }
  219. })
  220. }
  221. // 安装APK的单独函数
  222. const installApkFile = (filePath: string): void => {
  223. uni.installApk({
  224. filePath: filePath,
  225. success: () => {
  226. console.log('安装成功');
  227. uni.showToast({
  228. title: '安装成功',
  229. icon: 'success'
  230. });
  231. },
  232. fail: (error) => {
  233. console.error('安装失败:', error);
  234. uni.showToast({
  235. title: '安装失败',
  236. icon: 'none'
  237. });
  238. },
  239. complete: (res) => {
  240. console.log('安装完成:', res);
  241. }
  242. });
  243. }
  244. const installApkWithProgress = (): void => {
  245. // #ifdef APP-ANDROID
  246. isDownload.value = true;
  247. downloadProgress.value = 0;
  248. let donwloadUrl = '';
  249. donwloadUrl = getBaseUrl() + '/profile/app/emcs-release-'+versionServer.value+'.apk'
  250. // 下载APK
  251. const downloadTask = uni.downloadFile({
  252. url: donwloadUrl,
  253. filePath: `${uni.env.USER_DATA_PATH}/${Date.now()}_test.apk`, // 使用时间戳防止重名
  254. success: (downloadRes) => {
  255. isDownload.value = false;
  256. if (downloadRes.statusCode == 200) {
  257. // 确认安装
  258. uni.showModal({
  259. title: '安装提示',
  260. content: '下载完成,是否立即安装?',
  261. success: (modalRes) => {
  262. if (modalRes.confirm) {
  263. installApkFile(downloadRes.tempFilePath);
  264. }
  265. }
  266. });
  267. } else {
  268. uni.showToast({
  269. title: '下载失败',
  270. icon: 'error'
  271. });
  272. }
  273. },
  274. fail: (error) => {
  275. isDownload.value = false;
  276. uni.showToast({
  277. title: '下载失败',
  278. icon: 'none'
  279. });
  280. console.error('下载失败:', error);
  281. }
  282. });
  283. // 监听下载进度(更新页面内进度条,节流 5% 减轻渲染压力)
  284. let lastProgress = -5;
  285. downloadTask.onProgressUpdate((res) => {
  286. const p = Math.round(res.progress);
  287. if (p - lastProgress >= 5 || p >= 100) {
  288. lastProgress = p;
  289. downloadProgress.value = p;
  290. }
  291. });
  292. // #endif
  293. // #ifdef APP-HARMONY
  294. uni.showToast({
  295. title: '请登录PC端,扫描二维码下载并安装',
  296. icon: 'none'
  297. });
  298. // #endif
  299. // #ifdef APP-IOS
  300. uni.showToast({
  301. title: '请登录PC端,扫描二维码下载并安装',
  302. icon: 'none'
  303. });
  304. // #endif
  305. }
  306. // 菜单点击
  307. const handleVersionClick = (): void => {
  308. installApkWithProgress();
  309. }
  310. // 初始化
  311. onMounted(() => {
  312. checkVersion();
  313. const userInfo = getUserInfo()
  314. if (userInfo != null) {
  315. const nickNameRaw = userInfo['nickName'] as string | null
  316. const phone = userInfo['phone'] as string | null
  317. const userNameRaw = userInfo['userName'] as string | null
  318. userName.value = userNameRaw ?? ''
  319. nickName.value = nickNameRaw ?? ''
  320. if (phone != null && phone.length > 0) {
  321. userPhone.value = phone
  322. }
  323. const deptName = userInfo['deptName'] as string | null
  324. if (deptName != null && deptName.length > 0) {
  325. userDept.value = deptName
  326. }
  327. const roleNames = userInfo['roleNames'] as string | null
  328. if (roleNames != null && roleNames.length > 0) {
  329. userRole.value = roleNames
  330. }
  331. }
  332. })
  333. </script>
  334. <style lang="scss">
  335. .page-container {
  336. position: relative;
  337. flex: 1;
  338. background: #f5f8fb;
  339. padding-top: env(safe-area-inset-top);
  340. }
  341. .bg-image {
  342. position: absolute;
  343. top: 0;
  344. left: 0;
  345. width: 100%;
  346. height: 100%;
  347. z-index: 0;
  348. }
  349. .page-content {
  350. position: relative;
  351. flex: 1;
  352. padding-bottom: 100rpx;
  353. z-index: 1;
  354. }
  355. .header {
  356. padding: 80rpx 30rpx 40rpx;
  357. flex-direction: row;
  358. align-items: center;
  359. .avatar {
  360. width: 120rpx;
  361. height: 120rpx;
  362. border: 1rpx solid #ccc;
  363. border-radius: 60rpx;
  364. background-color: #fff;
  365. margin-right: 30rpx;
  366. }
  367. .user-name {
  368. font-size: 40rpx;
  369. color: #ffffff;
  370. font-weight: bold;
  371. }
  372. }
  373. .info-cards {
  374. padding: 0 30rpx;
  375. flex-direction: row;
  376. justify-content: space-between;
  377. margin-bottom: 30rpx;
  378. .info-card {
  379. width: 330rpx;
  380. background-color: #ffffff;
  381. border-radius: 24rpx;
  382. padding: 30rpx 24rpx;
  383. /* #ifndef APP-HARMONY */
  384. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  385. /* #endif */
  386. .card-icon {
  387. width: 48rpx;
  388. height: 48rpx;
  389. margin-bottom: 16rpx;
  390. }
  391. .card-label {
  392. font-size: 26rpx;
  393. color: #666666;
  394. margin-bottom: 12rpx;
  395. }
  396. .card-value {
  397. font-size: 32rpx;
  398. color: #333333;
  399. font-weight: bold;
  400. }
  401. }
  402. }
  403. .contact-item {
  404. margin: 0 30rpx 30rpx;
  405. padding: 30rpx;
  406. background-color: #ffffff;
  407. border-radius: 24rpx;
  408. flex-direction: row;
  409. align-items: center;
  410. /* #ifndef APP-HARMONY */
  411. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  412. /* #endif */
  413. .contact-icon {
  414. width: 48rpx;
  415. height: 48rpx;
  416. margin-right: 24rpx;
  417. }
  418. .contact-text {
  419. flex: 1;
  420. font-size: 32rpx;
  421. color: #333333;
  422. }
  423. .contact-arrow {
  424. font-size: 48rpx;
  425. color: #cccccc;
  426. line-height: 48rpx;
  427. }
  428. }
  429. .menu-section {
  430. margin: 0 30rpx 30rpx;
  431. background-color: #ffffff;
  432. border-radius: 24rpx;
  433. overflow: hidden;
  434. /* #ifndef APP-HARMONY */
  435. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  436. /* #endif */
  437. .menu-item {
  438. flex-direction: row;
  439. align-items: center;
  440. padding: 30rpx;
  441. border-bottom: 1rpx solid #f0f0f0;
  442. &:last-child {
  443. border-bottom: none;
  444. }
  445. .menu-icon {
  446. width: 48rpx;
  447. height: 48rpx;
  448. margin-right: 24rpx;
  449. }
  450. .menu-title {
  451. flex: 1;
  452. font-size: 32rpx;
  453. color: #333333;
  454. }
  455. .menu-title-download {
  456. font-size: 30rpx;
  457. color: #5500ff;
  458. }
  459. .menu-arrow {
  460. font-size: 48rpx;
  461. color: #cccccc;
  462. line-height: 48rpx;
  463. }
  464. }
  465. }
  466. .logout-wrapper {
  467. margin: 0 30rpx 30rpx;
  468. background-color: #ffffff;
  469. border-radius: 24rpx;
  470. overflow: hidden;
  471. align-items: center;
  472. justify-content: center;
  473. height: 100rpx;
  474. /* #ifndef APP-HARMONY */
  475. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
  476. /* #endif */
  477. .logout-btn {
  478. font-size: 32rpx;
  479. color: #666666;
  480. padding: 20rpx 0;
  481. }
  482. }
  483. .download-progress-mask {
  484. position: fixed;
  485. top: 0;
  486. left: 0;
  487. right: 0;
  488. bottom: 0;
  489. background-color: rgba(0, 0, 0, 0.5);
  490. z-index: 999;
  491. align-items: center;
  492. justify-content: center;
  493. }
  494. .download-progress-box {
  495. width: 500rpx;
  496. padding: 60rpx;
  497. background-color: #ffffff;
  498. border-radius: 24rpx;
  499. align-items: center;
  500. }
  501. .download-progress-title {
  502. font-size: 32rpx;
  503. color: #333333;
  504. margin-bottom: 30rpx;
  505. }
  506. .download-progress-bar {
  507. width: 100%;
  508. height: 16rpx;
  509. background-color: #f0f0f0;
  510. border-radius: 8rpx;
  511. overflow: hidden;
  512. margin-bottom: 20rpx;
  513. }
  514. .download-progress-fill {
  515. height: 100%;
  516. background-color: #0081ff;
  517. border-radius: 8rpx;
  518. transition: width 0.2s ease;
  519. }
  520. .download-progress-text {
  521. font-size: 28rpx;
  522. color: #666666;
  523. }
  524. </style>