m-home.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. <script setup lang="ts">
  2. import appStore from "@s"
  3. import { useDebounceFn, useResizeObserver } from "@vueuse/core"
  4. import { ref, onBeforeUnmount, onMounted, computed } from "vue"
  5. import VbQrScan from "@/components/qrcode/VbQrScan.vue"
  6. import router from "@r"
  7. const pageRef = ref<HTMLDivElement | null>(null)
  8. const name = ref(appStore.appConfigStore.getPlatformName())
  9. const showScanner = ref(false)
  10. const scanResult = ref("")
  11. const currentTime = ref(new Date())
  12. const copyright = computed(() => {
  13. return appStore.appConfigStore.getConfig().copyrightYear || "2020"
  14. })
  15. // 更新当前时间
  16. onMounted(() => {
  17. const timer = setInterval(() => {
  18. currentTime.value = new Date()
  19. }, 1000)
  20. onBeforeUnmount(() => {
  21. clearInterval(timer)
  22. document.querySelector("body")?.classList.remove("is-mobile-home")
  23. })
  24. })
  25. function handleScan() {
  26. showScanner.value = true
  27. }
  28. function handleScanResult(result: string) {
  29. scanResult.value = result
  30. if (result.startsWith("vb@")) {
  31. const arr = result.split("@")
  32. if (arr.length > 2) {
  33. router.push(arr[2])
  34. return
  35. }
  36. }
  37. message.msgError("无效二维码")
  38. }
  39. function handleCloseScanner() {
  40. showScanner.value = false
  41. }
  42. // 格式化时间显示
  43. const formattedTime = computed(() => {
  44. return currentTime.value.toLocaleTimeString("zh-CN", {
  45. hour: "2-digit",
  46. minute: "2-digit"
  47. })
  48. })
  49. const formattedDate = computed(() => {
  50. return currentTime.value.toLocaleDateString("zh-CN", {
  51. year: "numeric",
  52. month: "long",
  53. day: "numeric",
  54. weekday: "long"
  55. })
  56. })
  57. // 问候语
  58. const greeting = computed(() => {
  59. const hour = currentTime.value.getHours()
  60. if (hour < 6) return "夜深了"
  61. if (hour < 9) return "早上好"
  62. if (hour < 12) return "上午好"
  63. if (hour < 14) return "中午好"
  64. if (hour < 18) return "下午好"
  65. if (hour < 22) return "晚上好"
  66. return "夜深了"
  67. })
  68. function handleResize() {
  69. if (pageRef.value) {
  70. pageRef.value.style.width = window.innerWidth + "px"
  71. pageRef.value.style.height = window.innerHeight + "px"
  72. }
  73. }
  74. onMounted(() => {
  75. handleResize()
  76. useResizeObserver(pageRef.value, useDebounceFn(handleResize, 200, { maxWait: 500 }))
  77. })
  78. </script>
  79. <template>
  80. <div ref="pageRef" class="mobile-home-page">
  81. <!-- 动态背景 -->
  82. <div class="background-decoration">
  83. <div class="circle circle-1"></div>
  84. <div class="circle circle-2"></div>
  85. <div class="circle circle-3"></div>
  86. </div>
  87. <div class="home-content">
  88. <!-- 头部区域 -->
  89. <header class="header-section">
  90. <div class="time-display">
  91. <div class="time">{{ formattedTime }}</div>
  92. <div class="date">{{ formattedDate }}</div>
  93. </div>
  94. <div class="company-name">
  95. <h1>{{ name }}</h1>
  96. <p class="tagline">移动工作平台</p>
  97. </div>
  98. </header>
  99. <!-- 主要内容区 -->
  100. <main class="main-content">
  101. <!-- 用户信息卡片 -->
  102. <div class="user-card">
  103. <div class="user-avatar">
  104. <vb-symbol
  105. :text="appStore.authStore.user.nickName"
  106. :src="appStore.authStore.user.avatar"
  107. :size="80"
  108. shape="circle" />
  109. </div>
  110. <div class="user-info">
  111. <div class="greeting">{{ greeting }}</div>
  112. <div class="nickname">{{ appStore.authStore.user.nickName }}</div>
  113. <div class="status">在线工作中</div>
  114. </div>
  115. </div>
  116. <!-- 快捷操作区 -->
  117. <div class="quick-actions">
  118. <div class="action-card primary" @click="handleScan">
  119. <div class="action-icon">
  120. <i class="bi bi-qr-code-scan"></i>
  121. </div>
  122. <div class="action-info">
  123. <div class="action-title">扫一扫</div>
  124. <div class="action-desc">扫描二维码快速访问</div>
  125. </div>
  126. <div class="action-arrow">
  127. <i class="bi bi-chevron-right"></i>
  128. </div>
  129. </div>
  130. <!-- <div class="action-card">
  131. <div class="action-icon">
  132. <i class="bi bi-calendar-check"></i>
  133. </div>
  134. <div class="action-info">
  135. <div class="action-title">工作台</div>
  136. <div class="action-desc">查看待办事项</div>
  137. </div>
  138. <div class="action-arrow">
  139. <i class="bi bi-chevron-right"></i>
  140. </div>
  141. </div>
  142. <div class="action-card">
  143. <div class="action-icon">
  144. <i class="bi bi-bell"></i>
  145. </div>
  146. <div class="action-info">
  147. <div class="action-title">消息通知</div>
  148. <div class="action-desc">查看系统消息</div>
  149. </div>
  150. <div class="action-badge">3</div>
  151. <div class="action-arrow">
  152. <i class="bi bi-chevron-right"></i>
  153. </div>
  154. </div> -->
  155. </div>
  156. <!-- 数据统计区 -->
  157. <!-- <div class="stats-section">
  158. <h3 class="section-title">今日概览</h3>
  159. <div class="stats-grid">
  160. <div class="stat-item">
  161. <div class="stat-value">12</div>
  162. <div class="stat-label">待处理</div>
  163. </div>
  164. <div class="stat-item">
  165. <div class="stat-value">8</div>
  166. <div class="stat-label">已完成</div>
  167. </div>
  168. <div class="stat-item">
  169. <div class="stat-value">3</div>
  170. <div class="stat-label">进行中</div>
  171. </div>
  172. <div class="stat-item">
  173. <div class="stat-value">1</div>
  174. <div class="stat-label">预警</div>
  175. </div>
  176. </div>
  177. </div> -->
  178. </main>
  179. <!-- 底部区域 -->
  180. <footer class="footer-section">
  181. <p>Copyright ©{{ copyright }}-{{ new Date().getFullYear() }} VAP All Rights Reserved.</p>
  182. </footer>
  183. </div>
  184. <!-- 扫码组件 -->
  185. <VbQrScan
  186. v-if="showScanner"
  187. :enable-torch="true"
  188. :continuous="false"
  189. @close="handleCloseScanner"
  190. @scanSuccess="handleScanResult" />
  191. </div>
  192. </template>
  193. <style lang="scss" scoped>
  194. .mobile-home-page {
  195. width: 100%;
  196. height: 100vh;
  197. overflow: hidden;
  198. position: relative;
  199. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  200. .background-decoration {
  201. position: absolute;
  202. top: 0;
  203. left: 0;
  204. width: 100%;
  205. height: 100%;
  206. overflow: hidden;
  207. pointer-events: none;
  208. .circle {
  209. position: absolute;
  210. border-radius: 50%;
  211. background: rgba(255, 255, 255, 0.05);
  212. animation: float 20s infinite ease-in-out;
  213. &.circle-1 {
  214. width: 300px;
  215. height: 300px;
  216. top: -100px;
  217. right: -100px;
  218. animation-delay: 0s;
  219. }
  220. &.circle-2 {
  221. width: 200px;
  222. height: 200px;
  223. bottom: -50px;
  224. left: -50px;
  225. animation-delay: 5s;
  226. }
  227. &.circle-3 {
  228. width: 150px;
  229. height: 150px;
  230. top: 50%;
  231. left: 50%;
  232. animation-delay: 10s;
  233. }
  234. }
  235. }
  236. }
  237. .home-content {
  238. width: 100%;
  239. height: 100%;
  240. display: flex;
  241. flex-direction: column;
  242. position: relative;
  243. z-index: 1;
  244. }
  245. .header-section {
  246. padding: 40px 20px 20px;
  247. text-align: center;
  248. .time-display {
  249. margin-bottom: 20px;
  250. .time {
  251. font-size: 3rem;
  252. font-weight: 200;
  253. color: rgba(255, 255, 255, 0.95);
  254. letter-spacing: 2px;
  255. text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  256. }
  257. .date {
  258. font-size: 0.9rem;
  259. color: rgba(255, 255, 255, 0.7);
  260. margin-top: 5px;
  261. letter-spacing: 1px;
  262. }
  263. }
  264. .company-name {
  265. h1 {
  266. font-size: 1.8rem;
  267. font-weight: 700;
  268. color: white;
  269. margin-bottom: 5px;
  270. text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  271. }
  272. .tagline {
  273. font-size: 0.9rem;
  274. color: rgba(255, 255, 255, 0.8);
  275. letter-spacing: 3px;
  276. }
  277. }
  278. }
  279. .main-content {
  280. flex: 1;
  281. overflow-y: auto;
  282. padding: 0 20px 20px;
  283. &::-webkit-scrollbar {
  284. display: none;
  285. }
  286. }
  287. .user-card {
  288. background: rgba(255, 255, 255, 0.15);
  289. backdrop-filter: blur(10px);
  290. border-radius: 20px;
  291. padding: 25px;
  292. display: flex;
  293. align-items: center;
  294. gap: 20px;
  295. margin-bottom: 25px;
  296. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
  297. border: 1px solid rgba(255, 255, 255, 0.2);
  298. .user-avatar {
  299. flex-shrink: 0;
  300. border: 3px solid rgba(255, 255, 255, 0.3);
  301. border-radius: 50%;
  302. background: rgba(255, 255, 255, 0.1);
  303. :deep(.symbol) {
  304. border-radius: 50%;
  305. }
  306. }
  307. .user-info {
  308. flex: 1;
  309. .greeting {
  310. font-size: 0.85rem;
  311. color: rgba(255, 255, 255, 0.7);
  312. margin-bottom: 5px;
  313. }
  314. .nickname {
  315. font-size: 1.4rem;
  316. font-weight: 600;
  317. color: white;
  318. margin-bottom: 5px;
  319. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  320. }
  321. .status {
  322. font-size: 0.75rem;
  323. color: #4ade80;
  324. display: flex;
  325. align-items: center;
  326. gap: 5px;
  327. &::before {
  328. content: "";
  329. width: 6px;
  330. height: 6px;
  331. background: #4ade80;
  332. border-radius: 50%;
  333. display: inline-block;
  334. animation: pulse 2s infinite;
  335. }
  336. }
  337. }
  338. }
  339. .quick-actions {
  340. margin-bottom: 25px;
  341. }
  342. .action-card {
  343. background: rgba(255, 255, 255, 0.15);
  344. backdrop-filter: blur(10px);
  345. border-radius: 16px;
  346. padding: 18px;
  347. display: flex;
  348. align-items: center;
  349. gap: 15px;
  350. margin-bottom: 12px;
  351. cursor: pointer;
  352. transition: all 0.3s ease;
  353. border: 1px solid rgba(255, 255, 255, 0.1);
  354. position: relative;
  355. &:active {
  356. transform: scale(0.98);
  357. background: rgba(255, 255, 255, 0.2);
  358. }
  359. &.primary {
  360. background: linear-gradient(135deg, rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0.1));
  361. border-color: rgba(255, 255, 255, 0.3);
  362. }
  363. .action-icon {
  364. width: 45px;
  365. height: 45px;
  366. border-radius: 12px;
  367. background: rgba(255, 255, 255, 0.2);
  368. display: flex;
  369. align-items: center;
  370. justify-content: center;
  371. font-size: 1.5rem;
  372. color: white;
  373. flex-shrink: 0;
  374. i {
  375. font-size: 1.5rem;
  376. color: #eee;
  377. }
  378. }
  379. .action-info {
  380. flex: 1;
  381. .action-title {
  382. font-size: 1rem;
  383. font-weight: 600;
  384. color: white;
  385. margin-bottom: 3px;
  386. }
  387. .action-desc {
  388. font-size: 0.8rem;
  389. color: rgba(255, 255, 255, 0.6);
  390. }
  391. }
  392. .action-arrow {
  393. color: rgba(255, 255, 255, 0.5);
  394. font-size: 1.2rem;
  395. }
  396. .action-badge {
  397. position: absolute;
  398. top: 10px;
  399. right: 45px;
  400. background: #ef4444;
  401. color: white;
  402. font-size: 0.7rem;
  403. font-weight: 600;
  404. padding: 2px 8px;
  405. border-radius: 10px;
  406. animation: bounce 2s infinite;
  407. }
  408. }
  409. .stats-section {
  410. background: rgba(255, 255, 255, 0.1);
  411. backdrop-filter: blur(10px);
  412. border-radius: 16px;
  413. padding: 20px;
  414. border: 1px solid rgba(255, 255, 255, 0.1);
  415. .section-title {
  416. font-size: 1rem;
  417. font-weight: 600;
  418. color: white;
  419. margin-bottom: 15px;
  420. text-align: left;
  421. }
  422. .stats-grid {
  423. display: grid;
  424. grid-template-columns: repeat(4, 1fr);
  425. gap: 15px;
  426. .stat-item {
  427. text-align: center;
  428. .stat-value {
  429. font-size: 1.6rem;
  430. font-weight: 700;
  431. color: #fbbf24;
  432. margin-bottom: 5px;
  433. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  434. }
  435. .stat-label {
  436. font-size: 0.75rem;
  437. color: rgba(255, 255, 255, 0.6);
  438. }
  439. }
  440. }
  441. }
  442. .footer-section {
  443. padding: 20px;
  444. text-align: center;
  445. p {
  446. font-size: 0.75rem;
  447. color: rgba(255, 255, 255, 0.5);
  448. }
  449. }
  450. @keyframes shine {
  451. 0% {
  452. background-position: 0% 50%;
  453. }
  454. 100% {
  455. background-position: 200% 50%;
  456. }
  457. }
  458. @keyframes float {
  459. 0%,
  460. 100% {
  461. transform: translate(0, 0) rotate(0deg);
  462. }
  463. 33% {
  464. transform: translate(30px, -30px) rotate(120deg);
  465. }
  466. 66% {
  467. transform: translate(-20px, 20px) rotate(240deg);
  468. }
  469. }
  470. @keyframes pulse {
  471. 0%,
  472. 100% {
  473. opacity: 1;
  474. }
  475. 50% {
  476. opacity: 0.5;
  477. }
  478. }
  479. @keyframes bounce {
  480. 0%,
  481. 100% {
  482. transform: translateY(0);
  483. }
  484. 50% {
  485. transform: translateY(-5px);
  486. }
  487. }
  488. </style>