index.uvue 15 KB

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