index.uvue 16 KB

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