758 lines
13 KiB
Vue
758 lines
13 KiB
Vue
<template>
|
||
<view id="app">
|
||
<!-- 应用根组件 -->
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { onLaunch, onShow, onHide, onError } from '@dcloudio/uni-app'
|
||
import { useUserStore } from '@/common/store/modules/user.js'
|
||
import { createLogger } from '@/common/utils/logger.js'
|
||
import { CONFIG } from '@/common/config/app.config.js'
|
||
|
||
const logger = createLogger('App')
|
||
|
||
export default {
|
||
name: 'App',
|
||
setup() {
|
||
const userStore = useUserStore()
|
||
|
||
onLaunch((options) => {
|
||
logger.info('Application launched', {
|
||
scene: options.scene,
|
||
query: options.query,
|
||
referrerInfo: options.referrerInfo
|
||
})
|
||
|
||
// 初始化应用
|
||
initializeApp(options)
|
||
})
|
||
|
||
onShow((options) => {
|
||
logger.info('Application shown', {
|
||
scene: options.scene,
|
||
query: options.query
|
||
})
|
||
|
||
// 应用显示时的处理
|
||
handleAppShow(options)
|
||
})
|
||
|
||
onHide(() => {
|
||
logger.info('Application hidden')
|
||
|
||
// 应用隐藏时的处理
|
||
handleAppHide()
|
||
})
|
||
|
||
onError((error) => {
|
||
logger.error('Application error', null, new Error(error))
|
||
})
|
||
|
||
/**
|
||
* 初始化应用
|
||
*/
|
||
const initializeApp = async (options) => {
|
||
try {
|
||
// 检查登录状态
|
||
await checkLoginStatus()
|
||
|
||
// 处理启动参数
|
||
handleLaunchOptions(options)
|
||
|
||
// 初始化全局数据
|
||
await initializeGlobalData()
|
||
|
||
logger.info('App initialization completed')
|
||
} catch (error) {
|
||
logger.error('App initialization failed', null, error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查登录状态
|
||
*/
|
||
const checkLoginStatus = async () => {
|
||
try {
|
||
const token = uni.getStorageSync('token')
|
||
if (token) {
|
||
// 验证 token 并获取用户信息
|
||
await userStore.getCurrentUser()
|
||
logger.info('User login status verified')
|
||
}
|
||
} catch (error) {
|
||
logger.warn('Login status check failed', null, error)
|
||
// 清除无效的登录信息
|
||
uni.removeStorageSync('token')
|
||
uni.removeStorageSync('userInfo')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理启动参数
|
||
*/
|
||
const handleLaunchOptions = (options) => {
|
||
// 处理分享参数
|
||
if (options.query && Object.keys(options.query).length > 0) {
|
||
logger.info('Launch with query parameters', options.query)
|
||
|
||
// 保存启动参数供后续使用
|
||
uni.setStorageSync('launchQuery', options.query)
|
||
}
|
||
|
||
// 处理场景值
|
||
switch (options.scene) {
|
||
case 1001: // 发现栏小程序主入口
|
||
case 1019: // 微信钱包
|
||
case 1020: // 公众号 profile 页相关小程序列表
|
||
logger.info('Launched from WeChat discovery')
|
||
break
|
||
case 1007: // 单人聊天会话中的小程序消息卡片
|
||
case 1008: // 群聊会话中的小程序消息卡片
|
||
logger.info('Launched from chat message')
|
||
break
|
||
case 1011: // 扫描二维码
|
||
logger.info('Launched from QR code scan')
|
||
break
|
||
default:
|
||
logger.info('Launched from scene', { scene: options.scene })
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化全局数据
|
||
*/
|
||
const initializeGlobalData = async () => {
|
||
try {
|
||
// 获取系统信息
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
|
||
// 设置全局变量
|
||
getApp().globalData = {
|
||
systemInfo,
|
||
userInfo: userStore.userInfo,
|
||
config: CONFIG,
|
||
version: CONFIG.APP_VERSION || '1.0.0'
|
||
}
|
||
|
||
// 设置导航栏标题
|
||
uni.setNavigationBarTitle({
|
||
title: CONFIG.APP_NAME || '畜牧管理系统'
|
||
})
|
||
|
||
logger.info('Global data initialized', {
|
||
platform: systemInfo.platform,
|
||
version: systemInfo.version
|
||
})
|
||
} catch (error) {
|
||
logger.error('Failed to initialize global data', null, error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 应用显示处理
|
||
*/
|
||
const handleAppShow = (options) => {
|
||
// 检查网络状态
|
||
uni.getNetworkType({
|
||
success: (res) => {
|
||
if (res.networkType === 'none') {
|
||
uni.showToast({
|
||
title: '网络连接不可用',
|
||
icon: 'none',
|
||
duration: 3000
|
||
})
|
||
}
|
||
}
|
||
})
|
||
|
||
// 刷新用户信息(如果已登录)
|
||
if (userStore.isLoggedIn) {
|
||
userStore.refreshUserInfo().catch(error => {
|
||
logger.warn('Failed to refresh user info', null, error)
|
||
})
|
||
}
|
||
|
||
// 处理从后台切换回前台的逻辑
|
||
const lastHideTime = uni.getStorageSync('lastHideTime')
|
||
if (lastHideTime) {
|
||
const hideTime = Date.now() - lastHideTime
|
||
|
||
// 如果后台时间超过30分钟,重新验证登录状态
|
||
if (hideTime > 30 * 60 * 1000 && userStore.isLoggedIn) {
|
||
checkLoginStatus()
|
||
}
|
||
|
||
uni.removeStorageSync('lastHideTime')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 应用隐藏处理
|
||
*/
|
||
const handleAppHide = () => {
|
||
// 记录隐藏时间
|
||
uni.setStorageSync('lastHideTime', Date.now())
|
||
|
||
// 清理临时数据
|
||
cleanupTempData()
|
||
|
||
// 保存重要数据
|
||
saveImportantData()
|
||
}
|
||
|
||
/**
|
||
* 清理临时数据
|
||
*/
|
||
const cleanupTempData = () => {
|
||
try {
|
||
// 清理临时文件
|
||
const tempFiles = uni.getStorageSync('tempFiles') || []
|
||
tempFiles.forEach(filePath => {
|
||
uni.removeSavedFile({
|
||
filePath,
|
||
complete: () => {
|
||
logger.debug('Temp file removed', { filePath })
|
||
}
|
||
})
|
||
})
|
||
|
||
// 清理临时存储
|
||
uni.removeStorageSync('tempFiles')
|
||
uni.removeStorageSync('tempData')
|
||
|
||
logger.debug('Temp data cleaned up')
|
||
} catch (error) {
|
||
logger.error('Failed to cleanup temp data', null, error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存重要数据
|
||
*/
|
||
const saveImportantData = () => {
|
||
try {
|
||
// 保存用户偏好设置
|
||
if (userStore.userInfo) {
|
||
uni.setStorageSync('userPreferences', {
|
||
theme: userStore.userInfo.theme || 'light',
|
||
language: userStore.userInfo.language || 'zh-CN',
|
||
notifications: userStore.userInfo.notifications || true
|
||
})
|
||
}
|
||
|
||
// 保存应用状态
|
||
const currentPages = getCurrentPages()
|
||
if (currentPages.length > 0) {
|
||
const currentPage = currentPages[currentPages.length - 1]
|
||
uni.setStorageSync('lastPage', {
|
||
route: currentPage.route,
|
||
options: currentPage.options
|
||
})
|
||
}
|
||
|
||
logger.debug('Important data saved')
|
||
} catch (error) {
|
||
logger.error('Failed to save important data', null, error)
|
||
}
|
||
}
|
||
|
||
return {}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
/**
|
||
* 全局样式
|
||
*/
|
||
|
||
// 导入基础样式
|
||
@import '@/common/styles/base.scss';
|
||
@import '@/common/styles/variables.scss';
|
||
@import '@/common/styles/mixins.scss';
|
||
|
||
// 应用根样式
|
||
#app {
|
||
font-family: $font-family-base;
|
||
font-size: $font-size-base;
|
||
line-height: $line-height-base;
|
||
color: $text-color-primary;
|
||
background-color: $background-color-base;
|
||
}
|
||
|
||
// 全局重置样式
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
// 页面基础样式
|
||
page {
|
||
background-color: $background-color-base;
|
||
font-size: $font-size-base;
|
||
line-height: $line-height-base;
|
||
}
|
||
|
||
// 通用工具类
|
||
.text-center {
|
||
text-align: center;
|
||
}
|
||
|
||
.text-left {
|
||
text-align: left;
|
||
}
|
||
|
||
.text-right {
|
||
text-align: right;
|
||
}
|
||
|
||
.text-primary {
|
||
color: $color-primary;
|
||
}
|
||
|
||
.text-success {
|
||
color: $color-success;
|
||
}
|
||
|
||
.text-warning {
|
||
color: $color-warning;
|
||
}
|
||
|
||
.text-error {
|
||
color: $color-error;
|
||
}
|
||
|
||
.text-info {
|
||
color: $color-info;
|
||
}
|
||
|
||
.text-muted {
|
||
color: $text-color-secondary;
|
||
}
|
||
|
||
.bg-primary {
|
||
background-color: $color-primary;
|
||
}
|
||
|
||
.bg-success {
|
||
background-color: $color-success;
|
||
}
|
||
|
||
.bg-warning {
|
||
background-color: $color-warning;
|
||
}
|
||
|
||
.bg-error {
|
||
background-color: $color-error;
|
||
}
|
||
|
||
.bg-info {
|
||
background-color: $color-info;
|
||
}
|
||
|
||
// 间距工具类
|
||
@for $i from 0 through 10 {
|
||
.m-#{$i} {
|
||
margin: #{$i * 8}rpx;
|
||
}
|
||
|
||
.mt-#{$i} {
|
||
margin-top: #{$i * 8}rpx;
|
||
}
|
||
|
||
.mr-#{$i} {
|
||
margin-right: #{$i * 8}rpx;
|
||
}
|
||
|
||
.mb-#{$i} {
|
||
margin-bottom: #{$i * 8}rpx;
|
||
}
|
||
|
||
.ml-#{$i} {
|
||
margin-left: #{$i * 8}rpx;
|
||
}
|
||
|
||
.mx-#{$i} {
|
||
margin-left: #{$i * 8}rpx;
|
||
margin-right: #{$i * 8}rpx;
|
||
}
|
||
|
||
.my-#{$i} {
|
||
margin-top: #{$i * 8}rpx;
|
||
margin-bottom: #{$i * 8}rpx;
|
||
}
|
||
|
||
.p-#{$i} {
|
||
padding: #{$i * 8}rpx;
|
||
}
|
||
|
||
.pt-#{$i} {
|
||
padding-top: #{$i * 8}rpx;
|
||
}
|
||
|
||
.pr-#{$i} {
|
||
padding-right: #{$i * 8}rpx;
|
||
}
|
||
|
||
.pb-#{$i} {
|
||
padding-bottom: #{$i * 8}rpx;
|
||
}
|
||
|
||
.pl-#{$i} {
|
||
padding-left: #{$i * 8}rpx;
|
||
}
|
||
|
||
.px-#{$i} {
|
||
padding-left: #{$i * 8}rpx;
|
||
padding-right: #{$i * 8}rpx;
|
||
}
|
||
|
||
.py-#{$i} {
|
||
padding-top: #{$i * 8}rpx;
|
||
padding-bottom: #{$i * 8}rpx;
|
||
}
|
||
}
|
||
|
||
// 弹性布局工具类
|
||
.d-flex {
|
||
display: flex;
|
||
}
|
||
|
||
.flex-row {
|
||
flex-direction: row;
|
||
}
|
||
|
||
.flex-column {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.flex-wrap {
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.flex-nowrap {
|
||
flex-wrap: nowrap;
|
||
}
|
||
|
||
.justify-start {
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.justify-end {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.justify-center {
|
||
justify-content: center;
|
||
}
|
||
|
||
.justify-between {
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.justify-around {
|
||
justify-content: space-around;
|
||
}
|
||
|
||
.align-start {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.align-end {
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.align-center {
|
||
align-items: center;
|
||
}
|
||
|
||
.align-stretch {
|
||
align-items: stretch;
|
||
}
|
||
|
||
.flex-1 {
|
||
flex: 1;
|
||
}
|
||
|
||
.flex-auto {
|
||
flex: auto;
|
||
}
|
||
|
||
.flex-none {
|
||
flex: none;
|
||
}
|
||
|
||
// 显示隐藏工具类
|
||
.d-none {
|
||
display: none;
|
||
}
|
||
|
||
.d-block {
|
||
display: block;
|
||
}
|
||
|
||
.d-inline {
|
||
display: inline;
|
||
}
|
||
|
||
.d-inline-block {
|
||
display: inline-block;
|
||
}
|
||
|
||
// 位置工具类
|
||
.position-relative {
|
||
position: relative;
|
||
}
|
||
|
||
.position-absolute {
|
||
position: absolute;
|
||
}
|
||
|
||
.position-fixed {
|
||
position: fixed;
|
||
}
|
||
|
||
.position-sticky {
|
||
position: sticky;
|
||
}
|
||
|
||
// 圆角工具类
|
||
.rounded {
|
||
border-radius: $border-radius-base;
|
||
}
|
||
|
||
.rounded-sm {
|
||
border-radius: $border-radius-sm;
|
||
}
|
||
|
||
.rounded-lg {
|
||
border-radius: $border-radius-lg;
|
||
}
|
||
|
||
.rounded-circle {
|
||
border-radius: 50%;
|
||
}
|
||
|
||
// 阴影工具类
|
||
.shadow-sm {
|
||
box-shadow: $box-shadow-sm;
|
||
}
|
||
|
||
.shadow {
|
||
box-shadow: $box-shadow-base;
|
||
}
|
||
|
||
.shadow-lg {
|
||
box-shadow: $box-shadow-lg;
|
||
}
|
||
|
||
// 边框工具类
|
||
.border {
|
||
border: 1rpx solid $border-color-base;
|
||
}
|
||
|
||
.border-top {
|
||
border-top: 1rpx solid $border-color-base;
|
||
}
|
||
|
||
.border-right {
|
||
border-right: 1rpx solid $border-color-base;
|
||
}
|
||
|
||
.border-bottom {
|
||
border-bottom: 1rpx solid $border-color-base;
|
||
}
|
||
|
||
.border-left {
|
||
border-left: 1rpx solid $border-color-base;
|
||
}
|
||
|
||
.border-0 {
|
||
border: 0;
|
||
}
|
||
|
||
// 溢出处理
|
||
.overflow-hidden {
|
||
overflow: hidden;
|
||
}
|
||
|
||
.overflow-auto {
|
||
overflow: auto;
|
||
}
|
||
|
||
.overflow-scroll {
|
||
overflow: scroll;
|
||
}
|
||
|
||
// 文本溢出
|
||
.text-ellipsis {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.text-ellipsis-2 {
|
||
display: -webkit-box;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 2;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.text-ellipsis-3 {
|
||
display: -webkit-box;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 3;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
// 字体大小工具类
|
||
.font-xs {
|
||
font-size: $font-size-xs;
|
||
}
|
||
|
||
.font-sm {
|
||
font-size: $font-size-sm;
|
||
}
|
||
|
||
.font-base {
|
||
font-size: $font-size-base;
|
||
}
|
||
|
||
.font-lg {
|
||
font-size: $font-size-lg;
|
||
}
|
||
|
||
.font-xl {
|
||
font-size: $font-size-xl;
|
||
}
|
||
|
||
// 字体粗细
|
||
.font-light {
|
||
font-weight: 300;
|
||
}
|
||
|
||
.font-normal {
|
||
font-weight: 400;
|
||
}
|
||
|
||
.font-medium {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.font-semibold {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.font-bold {
|
||
font-weight: 700;
|
||
}
|
||
|
||
// 宽高工具类
|
||
.w-full {
|
||
width: 100%;
|
||
}
|
||
|
||
.h-full {
|
||
height: 100%;
|
||
}
|
||
|
||
.w-auto {
|
||
width: auto;
|
||
}
|
||
|
||
.h-auto {
|
||
height: auto;
|
||
}
|
||
|
||
// 最小高度
|
||
.min-h-screen {
|
||
min-height: 100vh;
|
||
}
|
||
|
||
// 清除浮动
|
||
.clearfix::after {
|
||
content: '';
|
||
display: table;
|
||
clear: both;
|
||
}
|
||
|
||
// 禁用选择
|
||
.user-select-none {
|
||
user-select: none;
|
||
}
|
||
|
||
// 指针样式
|
||
.cursor-pointer {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.cursor-not-allowed {
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
// 透明度
|
||
.opacity-0 {
|
||
opacity: 0;
|
||
}
|
||
|
||
.opacity-25 {
|
||
opacity: 0.25;
|
||
}
|
||
|
||
.opacity-50 {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.opacity-75 {
|
||
opacity: 0.75;
|
||
}
|
||
|
||
.opacity-100 {
|
||
opacity: 1;
|
||
}
|
||
|
||
// 过渡动画
|
||
.transition {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.transition-fast {
|
||
transition: all 0.15s ease;
|
||
}
|
||
|
||
.transition-slow {
|
||
transition: all 0.5s ease;
|
||
}
|
||
|
||
// 变换
|
||
.transform {
|
||
transform: translateZ(0);
|
||
}
|
||
|
||
.scale-95 {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.scale-100 {
|
||
transform: scale(1);
|
||
}
|
||
|
||
.scale-105 {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
// 响应式断点(小程序中主要用于不同屏幕尺寸适配)
|
||
@media screen and (max-width: 750rpx) {
|
||
.hidden-xs {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 751rpx) {
|
||
.hidden-sm {
|
||
display: none;
|
||
}
|
||
}
|
||
</style> |