first commit
This commit is contained in:
@@ -0,0 +1,607 @@
|
||||
/**
|
||||
* API接口配置
|
||||
*/
|
||||
|
||||
import { apiBaseUrl, apiTimeout } from '../config/index.js'
|
||||
|
||||
// 基础配置 - 从配置文件获取
|
||||
const BASE_URL = apiBaseUrl
|
||||
const TIMEOUT = apiTimeout
|
||||
|
||||
|
||||
/**
|
||||
* 请求拦截器
|
||||
*/
|
||||
const requestInterceptor = (config) => {
|
||||
// 添加token
|
||||
const token = uni.getStorageSync('token')
|
||||
if (token) {
|
||||
config.header = {
|
||||
...config.header,
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
// 添加通用请求头
|
||||
config.header = {
|
||||
'Content-Type': 'application/json',
|
||||
...config.header
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应拦截器
|
||||
*/
|
||||
const responseInterceptor = (response) => {
|
||||
const { statusCode, data } = response
|
||||
|
||||
if (statusCode === 200) {
|
||||
if (data.code === 0) {
|
||||
return data.data
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: data.message || '请求失败',
|
||||
icon: 'none'
|
||||
})
|
||||
return Promise.reject(new Error(data.message || '请求失败'))
|
||||
}
|
||||
} else if (statusCode === 401) {
|
||||
// token过期,跳转登录
|
||||
uni.removeStorageSync('token')
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
return Promise.reject(new Error('登录已过期'))
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '网络错误',
|
||||
icon: 'none'
|
||||
})
|
||||
return Promise.reject(new Error('网络错误'))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用请求方法
|
||||
*/
|
||||
const request = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 请求拦截
|
||||
const config = requestInterceptor({
|
||||
url: options.url.startsWith('/') ? BASE_URL + options.url : BASE_URL + '/' + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data,
|
||||
header: options.header || {},
|
||||
timeout: options.timeout || TIMEOUT
|
||||
})
|
||||
|
||||
uni.request({
|
||||
...config,
|
||||
success: (response) => {
|
||||
try {
|
||||
const result = responseInterceptor(response)
|
||||
resolve(result)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
uni.showToast({
|
||||
title: '网络连接失败',
|
||||
icon: 'none'
|
||||
})
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户相关API
|
||||
*/
|
||||
export const userApi = {
|
||||
// 登录
|
||||
login(data) {
|
||||
return request({
|
||||
url: '/api/login',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 登出
|
||||
logout() {
|
||||
return request({
|
||||
url: '/api/logout',
|
||||
method: 'POST'
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo() {
|
||||
return request({
|
||||
url: '/api/user/info',
|
||||
method: 'GET'
|
||||
})
|
||||
},
|
||||
|
||||
// 更新用户信息
|
||||
updateUserInfo(data) {
|
||||
return request({
|
||||
url: '/api/user/info',
|
||||
method: 'PUT',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 修改密码
|
||||
changePassword(data) {
|
||||
return request({
|
||||
url: '/api/user/password',
|
||||
method: 'PUT',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 上传头像
|
||||
uploadAvatar(file) {
|
||||
return request({
|
||||
url: '/api/user/avatar',
|
||||
method: 'POST',
|
||||
data: file
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 考勤相关API
|
||||
*/
|
||||
export const attendanceApi = {
|
||||
// 打卡
|
||||
checkIn(data) {
|
||||
return request({
|
||||
url: '/api/attendance/checkin',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 下班打卡
|
||||
checkOut(data) {
|
||||
return request({
|
||||
url: '/api/attendance/checkout',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 获取考勤记录
|
||||
getAttendanceList(params) {
|
||||
return request({
|
||||
url: '/api/attendance/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 获取考勤统计
|
||||
getAttendanceStats(params) {
|
||||
return request({
|
||||
url: '/api/attendance/stats',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 获取考勤详情
|
||||
getAttendanceDetail(id) {
|
||||
return request({
|
||||
url: '/api/attendance/detail',
|
||||
method: 'GET',
|
||||
data: { id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请假相关API
|
||||
*/
|
||||
export const leaveApi = {
|
||||
// 申请请假
|
||||
applyLeave(data) {
|
||||
return request({
|
||||
url: '/api/leave/apply',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 获取请假列表
|
||||
getLeaveList(params) {
|
||||
return request({
|
||||
url: '/api/leave/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 获取请假详情
|
||||
getLeaveDetail(id) {
|
||||
return request({
|
||||
url: '/api/leave/detail',
|
||||
method: 'GET',
|
||||
data: { id }
|
||||
})
|
||||
},
|
||||
|
||||
// 取消请假
|
||||
cancelLeave(id) {
|
||||
return request({
|
||||
url: '/api/leave/cancel',
|
||||
method: 'PUT',
|
||||
data: { id }
|
||||
})
|
||||
},
|
||||
|
||||
// 审批请假
|
||||
approveLeave(id, data) {
|
||||
return request({
|
||||
url: '/api/leave/approve',
|
||||
method: 'PUT',
|
||||
data: { id, ...data }
|
||||
})
|
||||
},
|
||||
|
||||
// 拒绝请假
|
||||
rejectLeave(id, data) {
|
||||
return request({
|
||||
url: '/api/leave/reject',
|
||||
method: 'PUT',
|
||||
data: { id, ...data }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 报销相关API
|
||||
*/
|
||||
export const reimbursementApi = {
|
||||
// 提交报销
|
||||
submitReimbursement(data) {
|
||||
return request({
|
||||
url: '/api/reimbursement/submit',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 获取报销列表
|
||||
getReimbursementList(params) {
|
||||
return request({
|
||||
url: '/api/reimbursement/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 获取报销详情
|
||||
getReimbursementDetail(id) {
|
||||
return request({
|
||||
url: '/api/reimbursement/detail',
|
||||
method: 'GET',
|
||||
data: { id }
|
||||
})
|
||||
},
|
||||
|
||||
// 上传发票
|
||||
uploadInvoice(file) {
|
||||
return request({
|
||||
url: '/api/reimbursement/upload',
|
||||
method: 'POST',
|
||||
data: file
|
||||
})
|
||||
},
|
||||
|
||||
// 审批报销
|
||||
approveReimbursement(id, data) {
|
||||
return request({
|
||||
url: '/api/reimbursement/approve',
|
||||
method: 'PUT',
|
||||
data: { id, ...data }
|
||||
})
|
||||
},
|
||||
|
||||
// 拒绝报销
|
||||
rejectReimbursement(id, data) {
|
||||
return request({
|
||||
url: '/api/reimbursement/reject',
|
||||
method: 'PUT',
|
||||
data: { id, ...data }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务相关API
|
||||
*/
|
||||
export const taskApi = {
|
||||
// 获取任务列表
|
||||
getTaskList(params) {
|
||||
return request({
|
||||
url: '/api/task/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 创建任务
|
||||
createTask(data) {
|
||||
return request({
|
||||
url: '/api/task/create',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 获取任务详情
|
||||
getTaskDetail(id) {
|
||||
return request({
|
||||
url: '/api/task/detail',
|
||||
method: 'GET',
|
||||
data: { id }
|
||||
})
|
||||
},
|
||||
|
||||
// 更新任务
|
||||
updateTask(id, data) {
|
||||
return request({
|
||||
url: '/api/task/update',
|
||||
method: 'PUT',
|
||||
data: { id, ...data }
|
||||
})
|
||||
},
|
||||
|
||||
// 更新任务状态
|
||||
updateTaskStatus(id, status) {
|
||||
return request({
|
||||
url: '/api/task/status',
|
||||
method: 'PUT',
|
||||
data: { id, status }
|
||||
})
|
||||
},
|
||||
|
||||
// 分配任务
|
||||
assignTask(id, data) {
|
||||
return request({
|
||||
url: '/api/task/assign',
|
||||
method: 'PUT',
|
||||
data: { id, ...data }
|
||||
})
|
||||
},
|
||||
|
||||
// 完成任务
|
||||
completeTask(id, data) {
|
||||
return request({
|
||||
url: '/api/task/complete',
|
||||
method: 'PUT',
|
||||
data: { id, ...data }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息相关API
|
||||
*/
|
||||
export const messageApi = {
|
||||
// 获取消息列表
|
||||
getMessageList(params) {
|
||||
return request({
|
||||
url: '/api/message/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 获取消息详情
|
||||
getMessageDetail(id) {
|
||||
return request({
|
||||
url: '/api/message/detail',
|
||||
method: 'GET',
|
||||
data: { id }
|
||||
})
|
||||
},
|
||||
|
||||
// 标记消息为已读
|
||||
markAsRead(id) {
|
||||
return request({
|
||||
url: '/api/message/read',
|
||||
method: 'PUT',
|
||||
data: { id }
|
||||
})
|
||||
},
|
||||
|
||||
// 获取未读消息数量
|
||||
getUnreadCount() {
|
||||
return request({
|
||||
url: '/api/message/unread-count',
|
||||
method: 'GET'
|
||||
})
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
sendMessage(data) {
|
||||
return request({
|
||||
url: '/api/message/send',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件相关API
|
||||
*/
|
||||
export const fileApi = {
|
||||
// 上传文件
|
||||
uploadFile(file) {
|
||||
return request({
|
||||
url: '/api/file/upload',
|
||||
method: 'POST',
|
||||
data: file
|
||||
})
|
||||
},
|
||||
|
||||
// 获取文件列表
|
||||
getFileList(params) {
|
||||
return request({
|
||||
url: '/api/file/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 下载文件
|
||||
downloadFile(id) {
|
||||
return request({
|
||||
url: '/api/file/download',
|
||||
method: 'GET',
|
||||
data: { id }
|
||||
})
|
||||
},
|
||||
|
||||
// 删除文件
|
||||
deleteFile(id) {
|
||||
return request({
|
||||
url: '/api/file/delete',
|
||||
method: 'DELETE',
|
||||
data: { id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户相关API
|
||||
*/
|
||||
export const customerApi = {
|
||||
// 获取客户列表
|
||||
getCustomerList(params) {
|
||||
return request({
|
||||
url: '/api/customer/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 获取客户详情
|
||||
getCustomerDetail(id) {
|
||||
return request({
|
||||
url: '/api/customer/detail',
|
||||
method: 'GET',
|
||||
data: { id }
|
||||
})
|
||||
},
|
||||
|
||||
// 添加客户
|
||||
addCustomer(data) {
|
||||
return request({
|
||||
url: '/api/customer/add',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 更新客户信息
|
||||
updateCustomer(id, data) {
|
||||
return request({
|
||||
url: '/api/customer/update',
|
||||
method: 'PUT',
|
||||
data: { id, ...data }
|
||||
})
|
||||
},
|
||||
|
||||
// 删除客户
|
||||
deleteCustomer(id) {
|
||||
return request({
|
||||
url: '/api/customer/delete',
|
||||
method: 'DELETE',
|
||||
data: { id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门相关API
|
||||
*/
|
||||
export const departmentApi = {
|
||||
// 获取部门列表
|
||||
getDepartmentList(params) {
|
||||
return request({
|
||||
url: '/api/department/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 获取部门树
|
||||
getDepartmentTree() {
|
||||
return request({
|
||||
url: '/api/department/tree',
|
||||
method: 'GET'
|
||||
})
|
||||
},
|
||||
|
||||
// 获取部门详情
|
||||
getDepartmentDetail(id) {
|
||||
return request({
|
||||
url: '/api/department/detail',
|
||||
method: 'GET',
|
||||
data: { id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知相关API
|
||||
*/
|
||||
export const notificationApi = {
|
||||
// 获取通知列表
|
||||
getNotificationList(params) {
|
||||
return request({
|
||||
url: '/api/notification/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
},
|
||||
|
||||
// 标记通知为已读
|
||||
markAsRead(id) {
|
||||
return request({
|
||||
url: '/api/notification/read',
|
||||
method: 'PUT',
|
||||
data: { id }
|
||||
})
|
||||
},
|
||||
|
||||
// 获取未读通知数量
|
||||
getUnreadCount() {
|
||||
return request({
|
||||
url: '/api/notification/unread-count',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
userApi,
|
||||
attendanceApi,
|
||||
leaveApi,
|
||||
reimbursementApi,
|
||||
taskApi,
|
||||
messageApi,
|
||||
fileApi,
|
||||
customerApi,
|
||||
departmentApi,
|
||||
notificationApi
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<view class="custom-navbar" :style="navbarStyle">
|
||||
<view class="navbar-content">
|
||||
<view class="navbar-left" v-if="showBack" @click="handleBack">
|
||||
<u-icon name="arrow-left" size="20" color="#fff"></u-icon>
|
||||
</view>
|
||||
<view class="navbar-center">
|
||||
<text class="navbar-title">{{ title }}</text>
|
||||
</view>
|
||||
<view class="navbar-right">
|
||||
<slot name="right">
|
||||
<view class="search-box" @click="handleSearch" v-if="showSearch">
|
||||
<u-icon name="search" size="16" color="#999"></u-icon>
|
||||
<text class="search-placeholder">搜索</text>
|
||||
</view>
|
||||
<view class="notification" @click="handleNotification" v-if="showNotification">
|
||||
<u-icon name="bell" size="20" color="#fff"></u-icon>
|
||||
<u-badge :count="unreadCount" :offset="[-2, 2]" v-if="unreadCount > 0"></u-badge>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CustomNavbar',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '企业办公'
|
||||
},
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showNotification: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
unreadCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'linear-gradient(135deg, #2B7CE9 0%, #1E5F99 100%)'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
navbarStyle() {
|
||||
return {
|
||||
background: this.backgroundColor
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleBack() {
|
||||
this.$emit('back')
|
||||
uni.navigateBack()
|
||||
},
|
||||
handleSearch() {
|
||||
this.$emit('search')
|
||||
},
|
||||
handleNotification() {
|
||||
this.$emit('notification')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background: linear-gradient(135deg, #2B7CE9 0%, #1E5F99 100%);
|
||||
padding: 20rpx 30rpx;
|
||||
padding-top: calc(var(--status-bar-height) + 20rpx);
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.navbar-left {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.navbar-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 25rpx;
|
||||
padding: 15rpx 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 28rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.notification {
|
||||
position: relative;
|
||||
padding: 10rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<view class="data-card" :class="{ clickable: clickable }" @click="handleClick">
|
||||
<view class="card-icon" :style="{ backgroundColor: iconBgColor }">
|
||||
<u-icon :name="icon" size="24" :color="iconColor"></u-icon>
|
||||
</view>
|
||||
<view class="card-content">
|
||||
<text class="card-number" :style="{ color: numberColor }">{{ number }}</text>
|
||||
<text class="card-label">{{ label }}</text>
|
||||
<text class="card-desc" v-if="description">{{ description }}</text>
|
||||
</view>
|
||||
<view class="card-arrow" v-if="showArrow">
|
||||
<u-icon name="arrow-right" size="16" color="#999"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DataCard',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#2B7CE9'
|
||||
},
|
||||
iconBgColor: {
|
||||
type: String,
|
||||
default: 'rgba(43, 124, 233, 0.1)'
|
||||
},
|
||||
number: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
numberColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showArrow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
if (this.clickable) {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.data-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.data-card.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.data-card.clickable:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-number {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.card-arrow {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<view class="emoji-picker" v-if="visible">
|
||||
<!-- 表情内容区域 -->
|
||||
<scroll-view class="emoji-content" scroll-y>
|
||||
<view class="emoji-category">
|
||||
<view class="category-title">{{ emojiCategories[activeCategoryIndex].name }}</view>
|
||||
<view class="emoji-grid">
|
||||
<view
|
||||
class="emoji-item"
|
||||
v-for="(emoji, index) in emojiCategories[activeCategoryIndex].emojis"
|
||||
:key="index"
|
||||
@click="selectEmoji(emoji)"
|
||||
>
|
||||
<text class="emoji-text">{{ emoji.unicode }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 分类标签 -->
|
||||
<view class="category-tabs">
|
||||
<scroll-view class="tabs-container" scroll-x>
|
||||
<view class="tabs-content">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeCategoryIndex === index }"
|
||||
v-for="(category, index) in emojiCategories"
|
||||
:key="category.id"
|
||||
@click="scrollToCategory(index)"
|
||||
>
|
||||
<text class="tab-emoji">{{ category.emojis[0].unicode }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { emojiCategories } from '../utils/emojis.js';
|
||||
|
||||
export default {
|
||||
name: 'EmojiPicker',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
emojiCategories: emojiCategories,
|
||||
activeCategoryIndex: 0
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
selectEmoji(emoji) {
|
||||
this.$emit('select', emoji);
|
||||
},
|
||||
closePicker() {
|
||||
this.$emit('close');
|
||||
},
|
||||
scrollToCategory(index) {
|
||||
this.activeCategoryIndex = index;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.emoji-picker {
|
||||
background-color: #fff;
|
||||
border-top: 1rpx solid #eee;
|
||||
max-height: 400rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.emoji-content {
|
||||
flex: 1;
|
||||
max-height: 300rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.emoji-category {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.category-title {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 10rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.emoji-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.emoji-item {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12rpx;
|
||||
background-color: #f8f8f8;
|
||||
transition: all 0.2s ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.emoji-item:active {
|
||||
background-color: #e8f5e8;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.emoji-text {
|
||||
font-size: 40rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.category-tabs {
|
||||
border-top: 1rpx solid #eee;
|
||||
background-color: #f8f8f8;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tabs-content {
|
||||
display: inline-block;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: inline-block;
|
||||
padding: 16rpx 24rpx;
|
||||
margin-right: 10rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #fff;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tab-item:active {
|
||||
background-color: #e8f5e8;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-emoji {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tab-emoji {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<view class="function-list">
|
||||
<view class="list-header" v-if="title">
|
||||
<text class="list-title">{{ title }}</text>
|
||||
<text class="list-more" v-if="showMore" @click="handleMore">更多</text>
|
||||
</view>
|
||||
<view class="list-content">
|
||||
<view
|
||||
class="function-item"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
@click="handleItemClick(item, index)"
|
||||
>
|
||||
<view class="item-icon">
|
||||
<u-icon :name="item.icon" size="24" :color="item.color"></u-icon>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<text class="item-name">{{ item.name }}</text>
|
||||
<text class="item-desc" v-if="item.description">{{ item.description }}</text>
|
||||
</view>
|
||||
<view class="item-arrow" v-if="showArrow">
|
||||
<u-icon name="arrow-right" size="16" color="#999"></u-icon>
|
||||
</view>
|
||||
<view class="item-badge" v-if="item.badge">
|
||||
<u-badge :count="item.badge" :offset="[5, -5]"></u-badge>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FunctionList',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
showMore: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showArrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleItemClick(item, index) {
|
||||
this.$emit('item-click', item, index)
|
||||
},
|
||||
handleMore() {
|
||||
this.$emit('more-click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.function-list {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx 30rpx 20rpx;
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.list-more {
|
||||
font-size: 26rpx;
|
||||
color: #2B7CE9;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.function-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx 0;
|
||||
border-bottom: 1rpx solid #F0F0F0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.function-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.item-arrow {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.item-badge {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<view class="task-card" :class="{ completed: task.completed, overdue: isOverdue }">
|
||||
<view class="task-checkbox" @click="toggleTask">
|
||||
<view class="checkbox" :class="{ checked: task.completed }">
|
||||
<i class="fas fa-check" v-if="task.completed"></i>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="task-content" @click="toggleTask">
|
||||
<view class="task-header">
|
||||
<text class="task-title">{{ task.title }}</text>
|
||||
<view class="task-priority" :class="task.priority">
|
||||
<i class="fas fa-circle"></i>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="task-description" v-if="task.description">{{ task.description }}</text>
|
||||
|
||||
<view class="task-meta">
|
||||
<view class="task-tags" v-if="task.tags.length > 0">
|
||||
<text
|
||||
v-for="tag in task.tags.slice(0, 3)"
|
||||
:key="tag"
|
||||
class="task-tag">
|
||||
{{ tag }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="task-due-date" v-if="task.dueDate">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
<text class="due-date-text">{{ formatDate(task.dueDate) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="task-actions">
|
||||
<view class="action-btn" @click="editTask">
|
||||
<i class="fas fa-edit"></i>
|
||||
</view>
|
||||
<view class="action-btn delete" @click="deleteTask">
|
||||
<i class="fas fa-trash"></i>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TaskCard',
|
||||
props: {
|
||||
task: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['toggle', 'edit', 'delete'],
|
||||
computed: {
|
||||
isOverdue() {
|
||||
if (this.task.completed || !this.task.dueDate) return false
|
||||
return new Date(this.task.dueDate) < new Date()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleTask() {
|
||||
this.$emit('toggle', this.task.id)
|
||||
},
|
||||
|
||||
editTask() {
|
||||
this.$emit('edit', this.task)
|
||||
},
|
||||
|
||||
deleteTask() {
|
||||
this.$emit('delete', this.task.id)
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
const date = new Date(dateString)
|
||||
const now = new Date()
|
||||
const diffTime = date - now
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (diffDays === 0) return '今天'
|
||||
if (diffDays === 1) return '明天'
|
||||
if (diffDays === -1) return '昨天'
|
||||
if (diffDays < 0) return `${Math.abs(diffDays)}天前`
|
||||
if (diffDays <= 7) return `${diffDays}天后`
|
||||
|
||||
return date.toLocaleDateString()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.task-card.completed {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.task-card.overdue {
|
||||
border-left: 6rpx solid #e74c3c;
|
||||
}
|
||||
|
||||
.task-checkbox {
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border: 2rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.checkbox.checked {
|
||||
background: #27ae60;
|
||||
border-color: #27ae60;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.task-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.task-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.task-priority {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.task-priority.high {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.task-priority.medium {
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.task-priority.low {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.task-description {
|
||||
font-size: 26rpx;
|
||||
color: #7f8c8d;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.task-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-tags {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.task-tag {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.task-due-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
color: #7f8c8d;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 24rpx;
|
||||
background: #f8f9fa;
|
||||
color: #7f8c8d;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<view class="task-stats">
|
||||
<view class="stats-card">
|
||||
<view class="stat-item" @click="onStatClick('total')">
|
||||
<text class="stat-number">{{ stats.total }}</text>
|
||||
<text class="stat-label">总任务</text>
|
||||
</view>
|
||||
<view class="stat-item" @click="onStatClick('pending')">
|
||||
<text class="stat-number">{{ stats.pending }}</text>
|
||||
<text class="stat-label">待完成</text>
|
||||
</view>
|
||||
<view class="stat-item" @click="onStatClick('completed')">
|
||||
<text class="stat-number">{{ stats.completed }}</text>
|
||||
<text class="stat-label">已完成</text>
|
||||
</view>
|
||||
<view class="stat-item" @click="onStatClick('overdue')">
|
||||
<text class="stat-number">{{ stats.overdue }}</text>
|
||||
<text class="stat-label">已逾期</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="progress-section" v-if="stats.total > 0">
|
||||
<view class="progress-header">
|
||||
<text class="progress-title">完成进度</text>
|
||||
<text class="progress-percentage">{{ stats.completionRate }}%</text>
|
||||
</view>
|
||||
<view class="progress-bar">
|
||||
<view
|
||||
class="progress-fill"
|
||||
:style="{ width: stats.completionRate + '%' }">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TaskStats',
|
||||
props: {
|
||||
stats: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['statClick'],
|
||||
methods: {
|
||||
onStatClick(type) {
|
||||
this.$emit('statClick', type)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task-stats {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-item:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
color: #3498db;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.progress-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.progress-percentage {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 12rpx;
|
||||
background: #f0f0f0;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #3498db 0%, #27ae60 100%);
|
||||
border-radius: 6rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 配置模块统一导出
|
||||
* 直接定义配置,简单明了
|
||||
*/
|
||||
|
||||
// 常用配置 - 直接定义
|
||||
export const apiBaseUrl = 'https://apigo.yunzer.cn'
|
||||
export const apiTimeout = 10000
|
||||
export const appName = '企业办公移动应用'
|
||||
export const appVersion = '1.0.0'
|
||||
export const debug = true
|
||||
|
||||
// 环境判断
|
||||
export const isDev = true
|
||||
export const isTest = false
|
||||
export const isProd = false
|
||||
|
||||
// 默认导出
|
||||
export default {
|
||||
apiBaseUrl,
|
||||
apiTimeout,
|
||||
appName,
|
||||
appVersion,
|
||||
debug,
|
||||
isDev,
|
||||
isTest,
|
||||
isProd
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 启动画面配置文件
|
||||
*/
|
||||
|
||||
export const splashConfig = {
|
||||
// 应用信息
|
||||
app: {
|
||||
name: '企业办公',
|
||||
nameEn: 'Enterprise Office',
|
||||
version: '1.0.0',
|
||||
logo: '🏢'
|
||||
},
|
||||
|
||||
// 启动画面设置
|
||||
display: {
|
||||
// 最小显示时间(毫秒)
|
||||
minDuration: 2000,
|
||||
// 最大显示时间(毫秒)
|
||||
maxDuration: 5000,
|
||||
// 是否在热启动时显示
|
||||
showOnWarmStart: false,
|
||||
// 是否显示版本信息
|
||||
showVersion: true
|
||||
},
|
||||
|
||||
// 加载步骤配置
|
||||
loadingSteps: [
|
||||
{
|
||||
text: '正在初始化...',
|
||||
duration: 800,
|
||||
action: 'init',
|
||||
icon: '⚙️'
|
||||
},
|
||||
{
|
||||
text: '加载用户数据...',
|
||||
duration: 1000,
|
||||
action: 'loadUserData',
|
||||
icon: '👤'
|
||||
},
|
||||
{
|
||||
text: '同步工作数据...',
|
||||
duration: 800,
|
||||
action: 'syncWorkData',
|
||||
icon: '📊'
|
||||
},
|
||||
{
|
||||
text: '准备就绪...',
|
||||
duration: 600,
|
||||
action: 'ready',
|
||||
icon: '✅'
|
||||
}
|
||||
],
|
||||
|
||||
// 动画配置
|
||||
animation: {
|
||||
// 背景动画持续时间
|
||||
backgroundDuration: 6000,
|
||||
// Logo动画延迟
|
||||
logoDelay: 0,
|
||||
// 加载动画延迟
|
||||
loadingDelay: 500,
|
||||
// 版本信息延迟
|
||||
versionDelay: 1000
|
||||
},
|
||||
|
||||
// 主题配置
|
||||
theme: {
|
||||
// 背景渐变
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
// 主色调
|
||||
primaryColor: '#667eea',
|
||||
// 文字颜色
|
||||
textColor: '#ffffff',
|
||||
// 次要文字颜色
|
||||
secondaryTextColor: 'rgba(255, 255, 255, 0.8)'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启动画面配置
|
||||
*/
|
||||
export function getSplashConfig() {
|
||||
return splashConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新启动画面配置
|
||||
*/
|
||||
export function updateSplashConfig(newConfig) {
|
||||
Object.assign(splashConfig, newConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用信息
|
||||
*/
|
||||
export function getAppInfo() {
|
||||
return splashConfig.app
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取显示设置
|
||||
*/
|
||||
export function getDisplaySettings() {
|
||||
return splashConfig.display
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加载步骤
|
||||
*/
|
||||
export function getLoadingSteps() {
|
||||
return splashConfig.loadingSteps
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动画配置
|
||||
*/
|
||||
export function getAnimationConfig() {
|
||||
return splashConfig.animation
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主题配置
|
||||
*/
|
||||
export function getThemeConfig() {
|
||||
return splashConfig.theme
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// 状态
|
||||
const userInfo = ref(null)
|
||||
const token = ref(null)
|
||||
const isLoggedIn = ref(false)
|
||||
|
||||
// 计算属性
|
||||
const isAuthenticated = computed(() => {
|
||||
return isLoggedIn.value && token.value && userInfo.value
|
||||
})
|
||||
|
||||
// 登录
|
||||
const login = (userData, authToken) => {
|
||||
userInfo.value = userData
|
||||
token.value = authToken
|
||||
isLoggedIn.value = true
|
||||
|
||||
// 保存到本地存储
|
||||
uni.setStorageSync('userInfo', userData)
|
||||
uni.setStorageSync('token', authToken)
|
||||
uni.setStorageSync('isLoggedIn', true)
|
||||
}
|
||||
|
||||
// 登出
|
||||
const logout = () => {
|
||||
userInfo.value = null
|
||||
token.value = null
|
||||
isLoggedIn.value = false
|
||||
|
||||
// 清除本地存储
|
||||
uni.removeStorageSync('userInfo')
|
||||
uni.removeStorageSync('token')
|
||||
uni.removeStorageSync('isLoggedIn')
|
||||
|
||||
console.log('用户已登出')
|
||||
}
|
||||
|
||||
// 初始化认证状态(从本地存储恢复)
|
||||
const initAuth = () => {
|
||||
try {
|
||||
const savedUserInfo = uni.getStorageSync('userInfo')
|
||||
const savedToken = uni.getStorageSync('token')
|
||||
const savedIsLoggedIn = uni.getStorageSync('isLoggedIn')
|
||||
|
||||
if (savedUserInfo && savedToken && savedIsLoggedIn) {
|
||||
userInfo.value = savedUserInfo
|
||||
token.value = savedToken
|
||||
isLoggedIn.value = savedIsLoggedIn
|
||||
console.log('从本地存储恢复用户状态')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化认证状态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查登录状态
|
||||
const checkAuth = () => {
|
||||
return isAuthenticated.value
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
const updateUserInfo = (newUserInfo) => {
|
||||
userInfo.value = { ...userInfo.value, ...newUserInfo }
|
||||
uni.setStorageSync('userInfo', userInfo.value)
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
userInfo,
|
||||
token,
|
||||
isLoggedIn,
|
||||
|
||||
// 计算属性
|
||||
isAuthenticated,
|
||||
|
||||
// 方法
|
||||
login,
|
||||
logout,
|
||||
initAuth,
|
||||
checkAuth,
|
||||
updateUserInfo
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,276 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
export const useTaskStore = defineStore('task', () => {
|
||||
// 状态
|
||||
const tasks = ref([])
|
||||
const currentFilter = ref('all') // all, pending, completed, overdue
|
||||
const currentSort = ref('dueDate') // dueDate, priority, created
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 计算属性
|
||||
const filteredTasks = computed(() => {
|
||||
let filtered = tasks.value
|
||||
|
||||
// 按状态过滤
|
||||
if (currentFilter.value === 'pending') {
|
||||
filtered = filtered.filter(task => !task.completed)
|
||||
} else if (currentFilter.value === 'completed') {
|
||||
filtered = filtered.filter(task => task.completed)
|
||||
} else if (currentFilter.value === 'overdue') {
|
||||
const now = new Date()
|
||||
filtered = filtered.filter(task =>
|
||||
!task.completed &&
|
||||
task.dueDate &&
|
||||
new Date(task.dueDate) < now
|
||||
)
|
||||
}
|
||||
|
||||
// 按关键词搜索
|
||||
if (searchKeyword.value) {
|
||||
const keyword = searchKeyword.value.toLowerCase()
|
||||
filtered = filtered.filter(task =>
|
||||
task.title.toLowerCase().includes(keyword) ||
|
||||
task.description.toLowerCase().includes(keyword) ||
|
||||
task.tags.some(tag => tag.toLowerCase().includes(keyword))
|
||||
)
|
||||
}
|
||||
|
||||
// 排序
|
||||
filtered.sort((a, b) => {
|
||||
switch (currentSort.value) {
|
||||
case 'priority':
|
||||
const priorityOrder = { high: 3, medium: 2, low: 1 }
|
||||
return (priorityOrder[b.priority] || 0) - (priorityOrder[a.priority] || 0)
|
||||
case 'created':
|
||||
return new Date(b.createdAt) - new Date(a.createdAt)
|
||||
case 'dueDate':
|
||||
default:
|
||||
if (!a.dueDate && !b.dueDate) return 0
|
||||
if (!a.dueDate) return 1
|
||||
if (!b.dueDate) return -1
|
||||
return new Date(a.dueDate) - new Date(b.dueDate)
|
||||
}
|
||||
})
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
const taskStats = computed(() => {
|
||||
const total = tasks.value.length
|
||||
const completed = tasks.value.filter(task => task.completed).length
|
||||
const pending = total - completed
|
||||
const overdue = tasks.value.filter(task =>
|
||||
!task.completed &&
|
||||
task.dueDate &&
|
||||
new Date(task.dueDate) < new Date()
|
||||
).length
|
||||
|
||||
return {
|
||||
total,
|
||||
completed,
|
||||
pending,
|
||||
overdue,
|
||||
completionRate: total > 0 ? Math.round((completed / total) * 100) : 0
|
||||
}
|
||||
})
|
||||
|
||||
// 方法
|
||||
const addTask = (taskData) => {
|
||||
const newTask = {
|
||||
id: Date.now().toString(),
|
||||
title: taskData.title,
|
||||
description: taskData.description || '',
|
||||
priority: taskData.priority || 'medium',
|
||||
dueDate: taskData.dueDate || null,
|
||||
tags: taskData.tags || [],
|
||||
completed: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
|
||||
tasks.value.unshift(newTask)
|
||||
saveToStorage()
|
||||
return newTask
|
||||
}
|
||||
|
||||
const updateTask = (id, updates) => {
|
||||
const index = tasks.value.findIndex(task => task.id === id)
|
||||
if (index !== -1) {
|
||||
tasks.value[index] = {
|
||||
...tasks.value[index],
|
||||
...updates,
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
saveToStorage()
|
||||
return tasks.value[index]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const deleteTask = (id) => {
|
||||
const index = tasks.value.findIndex(task => task.id === id)
|
||||
if (index !== -1) {
|
||||
tasks.value.splice(index, 1)
|
||||
saveToStorage()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const toggleTask = (id) => {
|
||||
const task = tasks.value.find(task => task.id === id)
|
||||
if (task) {
|
||||
task.completed = !task.completed
|
||||
task.updatedAt = new Date().toISOString()
|
||||
saveToStorage()
|
||||
return task
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const setFilter = (filter) => {
|
||||
currentFilter.value = filter
|
||||
}
|
||||
|
||||
const setSort = (sort) => {
|
||||
currentSort.value = sort
|
||||
}
|
||||
|
||||
const setSearchKeyword = (keyword) => {
|
||||
searchKeyword.value = keyword
|
||||
}
|
||||
|
||||
const clearCompleted = () => {
|
||||
tasks.value = tasks.value.filter(task => !task.completed)
|
||||
saveToStorage()
|
||||
}
|
||||
|
||||
const getTaskById = (id) => {
|
||||
return tasks.value.find(task => task.id === id)
|
||||
}
|
||||
|
||||
const getTasksByTag = (tag) => {
|
||||
return tasks.value.filter(task => task.tags.includes(tag))
|
||||
}
|
||||
|
||||
const getOverdueTasks = () => {
|
||||
const now = new Date()
|
||||
return tasks.value.filter(task =>
|
||||
!task.completed &&
|
||||
task.dueDate &&
|
||||
new Date(task.dueDate) < now
|
||||
)
|
||||
}
|
||||
|
||||
const getTodayTasks = () => {
|
||||
const today = new Date().toDateString()
|
||||
return tasks.value.filter(task =>
|
||||
!task.completed &&
|
||||
task.dueDate &&
|
||||
new Date(task.dueDate).toDateString() === today
|
||||
)
|
||||
}
|
||||
|
||||
// 本地存储
|
||||
const saveToStorage = () => {
|
||||
try {
|
||||
uni.setStorageSync('tasks', JSON.stringify(tasks.value))
|
||||
} catch (error) {
|
||||
console.error('保存任务数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const loadFromStorage = () => {
|
||||
try {
|
||||
const stored = uni.getStorageSync('tasks')
|
||||
if (stored) {
|
||||
tasks.value = JSON.parse(stored)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载任务数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化示例数据
|
||||
const initSampleData = () => {
|
||||
if (tasks.value.length === 0) {
|
||||
const sampleTasks = [
|
||||
{
|
||||
id: '1',
|
||||
title: '完成项目需求分析',
|
||||
description: '分析用户需求,制定详细的功能规格说明',
|
||||
priority: 'high',
|
||||
dueDate: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
tags: ['工作', '项目'],
|
||||
completed: false,
|
||||
createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '准备会议材料',
|
||||
description: '准备下周团队会议的PPT和资料',
|
||||
priority: 'medium',
|
||||
dueDate: new Date(Date.now() + 1 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
tags: ['工作', '会议'],
|
||||
completed: false,
|
||||
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '购买生活用品',
|
||||
description: '去超市购买日用品和食材',
|
||||
priority: 'low',
|
||||
dueDate: null,
|
||||
tags: ['生活', '购物'],
|
||||
completed: true,
|
||||
createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: '学习新技术',
|
||||
description: '学习Vue 3和Pinia状态管理',
|
||||
priority: 'medium',
|
||||
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
tags: ['学习', '技术'],
|
||||
completed: false,
|
||||
createdAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
]
|
||||
tasks.value = sampleTasks
|
||||
saveToStorage()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
tasks,
|
||||
currentFilter,
|
||||
currentSort,
|
||||
searchKeyword,
|
||||
|
||||
// 计算属性
|
||||
filteredTasks,
|
||||
taskStats,
|
||||
|
||||
// 方法
|
||||
addTask,
|
||||
updateTask,
|
||||
deleteTask,
|
||||
toggleTask,
|
||||
setFilter,
|
||||
setSort,
|
||||
setSearchKeyword,
|
||||
clearCompleted,
|
||||
getTaskById,
|
||||
getTasksByTag,
|
||||
getOverdueTasks,
|
||||
getTodayTasks,
|
||||
loadFromStorage,
|
||||
initSampleData
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,205 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
// 用户基本信息
|
||||
userInfo: {
|
||||
name: '张三',
|
||||
avatar: '/static/avatar/user.png',
|
||||
department: '技术部',
|
||||
employeeId: 'EMP001',
|
||||
position: '高级工程师',
|
||||
phone: '138****8888',
|
||||
email: 'zhangsan@company.com'
|
||||
},
|
||||
|
||||
// 登录状态
|
||||
token: '',
|
||||
isLoggedIn: true,
|
||||
|
||||
// 用户权限
|
||||
permissions: [
|
||||
'attendance:view',
|
||||
'attendance:checkin',
|
||||
'leave:apply',
|
||||
'leave:view',
|
||||
'reimbursement:apply',
|
||||
'reimbursement:view',
|
||||
'task:view',
|
||||
'task:create',
|
||||
'meeting:book',
|
||||
'customer:view',
|
||||
'file:view'
|
||||
],
|
||||
|
||||
// 用户设置
|
||||
settings: {
|
||||
theme: 'light', // light, dark, auto
|
||||
messageEnabled: true,
|
||||
nightMode: false,
|
||||
language: 'zh-CN'
|
||||
},
|
||||
|
||||
// 工作数据
|
||||
workData: {
|
||||
attendanceRate: 98,
|
||||
completedTasks: 15,
|
||||
pendingTasks: 3,
|
||||
pendingApproval: 2,
|
||||
pendingAmount: 2580,
|
||||
thisMonthLeave: 2,
|
||||
thisMonthOvertime: 8
|
||||
},
|
||||
|
||||
// 最近使用功能
|
||||
recentFunctions: [
|
||||
{ icon: 'calendar', color: '#FF6B6B', label: '请假', action: 'leave' },
|
||||
{ icon: 'rmb-circle', color: '#4ECDC4', label: '报销', action: 'reimbursement' },
|
||||
{ icon: 'clock', color: '#45B7D1', label: '打卡', action: 'checkin' }
|
||||
],
|
||||
|
||||
// 快捷操作配置
|
||||
quickActions: [
|
||||
{ icon: 'calendar', color: '#FF6B6B', label: '请假', action: 'leave' },
|
||||
{ icon: 'rmb-circle', color: '#4ECDC4', label: '报销', action: 'reimbursement' },
|
||||
{ icon: 'clock', color: '#45B7D1', label: '打卡', action: 'checkin' },
|
||||
{ icon: 'calendar', color: '#96CEB4', label: '会议', action: 'meeting' },
|
||||
{ icon: 'account', color: '#FECA57', label: '客户', action: 'customer' }
|
||||
]
|
||||
}),
|
||||
|
||||
getters: {
|
||||
// 获取用户显示名称
|
||||
getUserName: (state) => state.userInfo?.name || '未登录用户',
|
||||
|
||||
// 获取用户头像
|
||||
getUserAvatar: (state) => state.userInfo?.avatar || '/static/avatar/default.png',
|
||||
|
||||
// 获取用户部门
|
||||
getUserDepartment: (state) => state.userInfo?.department || '',
|
||||
|
||||
// 检查是否有特定权限
|
||||
hasPermission: (state) => (permission) => {
|
||||
return state.permissions.includes(permission)
|
||||
},
|
||||
|
||||
// 获取工作数据概览
|
||||
getWorkOverview: (state) => ({
|
||||
attendanceRate: state.workData.attendanceRate,
|
||||
completedTasks: state.workData.completedTasks,
|
||||
pendingTasks: state.workData.pendingTasks,
|
||||
pendingApproval: state.workData.pendingApproval,
|
||||
pendingAmount: state.workData.pendingAmount
|
||||
}),
|
||||
|
||||
// 获取当前主题
|
||||
getCurrentTheme: (state) => state.settings.theme,
|
||||
|
||||
// 获取消息设置
|
||||
getMessageSettings: (state) => ({
|
||||
enabled: state.settings.messageEnabled,
|
||||
nightMode: state.settings.nightMode
|
||||
})
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 设置用户信息
|
||||
setUserInfo(info) {
|
||||
this.userInfo = { ...this.userInfo, ...info }
|
||||
this.isLoggedIn = true
|
||||
},
|
||||
|
||||
// 设置token
|
||||
setToken(token) {
|
||||
this.token = token
|
||||
},
|
||||
|
||||
// 更新用户设置
|
||||
updateSettings(settings) {
|
||||
this.settings = { ...this.settings, ...settings }
|
||||
},
|
||||
|
||||
// 更新工作数据
|
||||
updateWorkData(data) {
|
||||
this.workData = { ...this.workData, ...data }
|
||||
},
|
||||
|
||||
// 添加最近使用功能
|
||||
addRecentFunction(func) {
|
||||
const existingIndex = this.recentFunctions.findIndex(item => item.action === func.action)
|
||||
if (existingIndex > -1) {
|
||||
this.recentFunctions.splice(existingIndex, 1)
|
||||
}
|
||||
this.recentFunctions.unshift(func)
|
||||
if (this.recentFunctions.length > 5) {
|
||||
this.recentFunctions.pop()
|
||||
}
|
||||
},
|
||||
|
||||
// 更新快捷操作
|
||||
updateQuickActions(actions) {
|
||||
this.quickActions = actions
|
||||
},
|
||||
|
||||
// 添加权限
|
||||
addPermission(permission) {
|
||||
if (!this.permissions.includes(permission)) {
|
||||
this.permissions.push(permission)
|
||||
}
|
||||
},
|
||||
|
||||
// 移除权限
|
||||
removePermission(permission) {
|
||||
const index = this.permissions.indexOf(permission)
|
||||
if (index > -1) {
|
||||
this.permissions.splice(index, 1)
|
||||
}
|
||||
},
|
||||
|
||||
// 登出
|
||||
logout() {
|
||||
this.userInfo = {
|
||||
name: '',
|
||||
avatar: '',
|
||||
department: '',
|
||||
employeeId: '',
|
||||
position: '',
|
||||
phone: '',
|
||||
email: ''
|
||||
}
|
||||
this.token = ''
|
||||
this.isLoggedIn = false
|
||||
this.permissions = []
|
||||
this.workData = {
|
||||
attendanceRate: 0,
|
||||
completedTasks: 0,
|
||||
pendingTasks: 0,
|
||||
pendingApproval: 0,
|
||||
pendingAmount: 0,
|
||||
thisMonthLeave: 0,
|
||||
thisMonthOvertime: 0
|
||||
}
|
||||
this.recentFunctions = []
|
||||
this.quickActions = []
|
||||
},
|
||||
|
||||
// 重置设置
|
||||
resetSettings() {
|
||||
this.settings = {
|
||||
theme: 'light',
|
||||
messageEnabled: true,
|
||||
nightMode: false,
|
||||
language: 'zh-CN'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
persist: {
|
||||
key: 'user-store',
|
||||
storage: {
|
||||
getItem: (key) => uni.getStorageSync(key),
|
||||
setItem: (key, value) => uni.setStorageSync(key, value),
|
||||
removeItem: (key) => uni.removeStorageSync(key)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Emoji处理工具函数
|
||||
*/
|
||||
|
||||
// 将emoji代码转换为Unicode字符
|
||||
export function parseEmoji(text) {
|
||||
// 这里可以添加具体的emoji解析逻辑
|
||||
// 例如将 :smile: 转换为 😊
|
||||
return text;
|
||||
}
|
||||
|
||||
// 将Unicode字符转换为emoji代码
|
||||
export function encodeEmoji(text) {
|
||||
// 这里可以添加具体的emoji编码逻辑
|
||||
return text;
|
||||
}
|
||||
|
||||
// 检查文本中是否包含emoji
|
||||
export function containsEmoji(text) {
|
||||
// 基本的emoji Unicode范围检查
|
||||
const emojiRegex = /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu;
|
||||
return emojiRegex.test(text);
|
||||
}
|
||||
|
||||
export default {
|
||||
parseEmoji,
|
||||
encodeEmoji,
|
||||
containsEmoji
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
// 常用emoji表情数据
|
||||
export const emojis = [
|
||||
// 笑脸和情感
|
||||
{ id: 'smile', unicode: '😊', name: '微笑' },
|
||||
{ id: 'laugh', unicode: '😄', name: '大笑' },
|
||||
{ id: 'grin', unicode: '😁', name: ' grin' },
|
||||
{ id: 'tears_of_joy', unicode: '😂', name: '笑哭' },
|
||||
{ id: 'wink', unicode: '😉', name: '眨眼' },
|
||||
{ id: 'blush', unicode: '😊', name: '脸红' },
|
||||
{ id: 'innocent', unicode: '😇', name: '天使' },
|
||||
{ id: 'heart_eyes', unicode: '😍', name: '花痴' },
|
||||
{ id: 'kissing_heart', unicode: '😘', name: '飞吻' },
|
||||
{ id: 'kissing_closed_eyes', unicode: '😚', name: '闭眼亲亲' },
|
||||
{ id: 'yum', unicode: '😋', name: '好吃' },
|
||||
{ id: 'stuck_out_tongue_winking_eye', unicode: '😜', name: '吐舌头眨眼' },
|
||||
{ id: 'sunglasses', unicode: '😎', name: '酷' },
|
||||
{ id: 'smirk', unicode: '😏', name: '得意' },
|
||||
{ id: 'expressionless', unicode: '😑', name: '面无表情' },
|
||||
{ id: 'neutral_face', unicode: '😐', name: '中性' },
|
||||
|
||||
// 手势和身体部位
|
||||
{ id: 'thumbsup', unicode: '👍', name: '赞' },
|
||||
{ id: 'thumbsdown', unicode: '👎', name: '踩' },
|
||||
{ id: 'ok_hand', unicode: '👌', name: 'OK' },
|
||||
{ id: 'fist', unicode: '✊', name: '拳头' },
|
||||
{ id: 'v', unicode: '✌️', name: '胜利' },
|
||||
{ id: 'wave', unicode: '👋', name: '挥手' },
|
||||
{ id: 'clap', unicode: '👏', name: '鼓掌' },
|
||||
{ id: 'muscle', unicode: '💪', name: '肌肉' },
|
||||
{ id: 'pray', unicode: '🙏', name: '祈祷' },
|
||||
|
||||
// 动物和自然
|
||||
{ id: 'dog', unicode: '🐶', name: '狗' },
|
||||
{ id: 'cat', unicode: '🐱', name: '猫' },
|
||||
{ id: 'pig', unicode: '🐷', name: '猪' },
|
||||
{ id: 'rabbit', unicode: '🐰', name: '兔子' },
|
||||
{ id: 'koala', unicode: '🐨', name: '考拉' },
|
||||
{ id: 'tiger', unicode: '🐯', name: '老虎' },
|
||||
{ id: 'horse', unicode: '🐴', name: '马' },
|
||||
{ id: 'cow', unicode: '🐮', name: '牛' },
|
||||
{ id: 'panda_face', unicode: '🐼', name: '熊猫' },
|
||||
{ id: 'pig_nose', unicode: '🐽', name: '猪鼻子' },
|
||||
{ id: 'feet', unicode: '🐾', name: '爪子' },
|
||||
{ id: 'turtle', unicode: '🐢', name: '乌龟' },
|
||||
{ id: 'hatching_chick', unicode: '🐣', name: '孵化' },
|
||||
{ id: 'baby_chick', unicode: '🐤', name: '小鸡' },
|
||||
{ id: 'hatched_chick', unicode: '🐥', name: '雏鸡' },
|
||||
{ id: 'bird', unicode: '🐦', name: '鸟' },
|
||||
|
||||
// 食物和饮料
|
||||
{ id: 'grapes', unicode: '🍇', name: '葡萄' },
|
||||
{ id: 'melon', unicode: '🍈', name: '甜瓜' },
|
||||
{ id: 'watermelon', unicode: '🍉', name: '西瓜' },
|
||||
{ id: 'tangerine', unicode: '🍊', name: '橘子' },
|
||||
{ id: 'lemon', unicode: '🍋', name: '柠檬' },
|
||||
{ id: 'banana', unicode: '🍌', name: '香蕉' },
|
||||
{ id: 'pineapple', unicode: '🍍', name: '菠萝' },
|
||||
{ id: 'apple', unicode: '🍎', name: '苹果' },
|
||||
{ id: 'green_apple', unicode: '🍏', name: '青苹果' },
|
||||
{ id: 'cherries', unicode: '🍒', name: '樱桃' },
|
||||
{ id: 'strawberry', unicode: '🍓', name: '草莓' },
|
||||
{ id: 'hamburger', unicode: '🍔', name: '汉堡' },
|
||||
{ id: 'pizza', unicode: '🍕', name: '披萨' },
|
||||
{ id: 'meat_on_bone', unicode: '🍖', name: '排骨' },
|
||||
{ id: 'poultry_leg', unicode: '🍗', name: '鸡腿' },
|
||||
{ id: 'rice_cracker', unicode: '🍘', name: '米饼' },
|
||||
|
||||
// 活动和运动
|
||||
{ id: 'soccer', unicode: '⚽', name: '足球' },
|
||||
{ id: 'basketball', unicode: '🏀', name: '篮球' },
|
||||
{ id: 'football', unicode: '🏈', name: '橄榄球' },
|
||||
{ id: 'baseball', unicode: '⚾', name: '棒球' },
|
||||
{ id: 'tennis', unicode: '🎾', name: '网球' },
|
||||
{ id: 'golf', unicode: '⛳', name: '高尔夫' },
|
||||
{ id: 'ski', unicode: '🎿', name: '滑雪' },
|
||||
{ id: 'snowboarder', unicode: '🏂', name: '滑雪板' },
|
||||
{ id: 'swimmer', unicode: '🏊', name: '游泳' },
|
||||
{ id: 'surfer', unicode: '🏄', name: '冲浪' },
|
||||
{ id: 'cyclist', unicode: '🚴', name: '骑车' },
|
||||
{ id: 'runner', unicode: '🏃', name: '跑步' },
|
||||
{ id: 'dancer', unicode: '💃', name: '跳舞' },
|
||||
{ id: 'guitar', unicode: '🎸', name: '吉他' },
|
||||
{ id: 'musical_keyboard', unicode: '🎹', name: '键盘' },
|
||||
{ id: 'violin', unicode: '🎻', name: '小提琴' },
|
||||
|
||||
// 旅行和地点
|
||||
{ id: 'rocket', unicode: '🚀', name: '火箭' },
|
||||
{ id: 'helicopter', unicode: '🚁', name: '直升机' },
|
||||
{ id: 'steam_locomotive', unicode: '🚂', name: '火车头' },
|
||||
{ id: 'railway_car', unicode: '🚃', name: '车厢' },
|
||||
{ id: 'bullettrain_side', unicode: '🚄', name: '高铁' },
|
||||
{ id: 'bullettrain_front', unicode: '🚅', name: '子弹头' },
|
||||
{ id: 'train2', unicode: '🚆', name: '火车' },
|
||||
{ id: 'metro', unicode: '🚇', name: '地铁' },
|
||||
{ id: 'light_rail', unicode: '🚈', name: '轻轨' },
|
||||
{ id: 'station', unicode: '🚉', name: '车站' },
|
||||
{ id: 'tram', unicode: '🚊', name: '电车' },
|
||||
{ id: 'bus', unicode: '🚌', name: '公交车' },
|
||||
{ id: 'blue_car', unicode: '🚙', name: '汽车' },
|
||||
{ id: 'car', unicode: '🚗', name: '轿车' },
|
||||
{ id: 'taxi', unicode: '🚕', name: '出租车' },
|
||||
{ id: 'truck', unicode: '🚚', name: '卡车' },
|
||||
|
||||
// 符号和标志
|
||||
{ id: 'heart', unicode: '❤️', name: '爱心' },
|
||||
{ id: 'broken_heart', unicode: '💔', name: '心碎' },
|
||||
{ id: 'heartpulse', unicode: '💗', name: '心动' },
|
||||
{ id: 'sparkling_heart', unicode: '💖', name: '闪心' },
|
||||
{ id: 'cupid', unicode: '💘', name: '丘比特' },
|
||||
{ id: 'gift_heart', unicode: '💝', name: '礼盒心' },
|
||||
{ id: 'heart_decoration', unicode: '💟', name: '心装饰' },
|
||||
{ id: 'purple_heart', unicode: '💜', name: '紫心' },
|
||||
{ id: 'yellow_heart', unicode: '💛', name: '黄心' },
|
||||
{ id: 'green_heart', unicode: '💚', name: '绿心' },
|
||||
{ id: 'blue_heart', unicode: '💙', name: '蓝心' },
|
||||
{ id: 'star', unicode: '⭐', name: '星星' },
|
||||
{ id: 'sparkles', unicode: '✨', name: '闪亮' },
|
||||
{ id: 'zap', unicode: '⚡', name: '闪电' },
|
||||
{ id: 'fire', unicode: '🔥', name: '火' },
|
||||
{ id: 'boom', unicode: '💥', name: '爆炸' }
|
||||
];
|
||||
|
||||
// 将emoji按类别分组
|
||||
export const emojiCategories = [
|
||||
{
|
||||
id: 'people',
|
||||
name: '笑脸和情感',
|
||||
emojis: emojis.slice(0, 16)
|
||||
},
|
||||
{
|
||||
id: 'hands',
|
||||
name: '手势和身体',
|
||||
emojis: emojis.slice(16, 25)
|
||||
},
|
||||
{
|
||||
id: 'animals',
|
||||
name: '动物和自然',
|
||||
emojis: emojis.slice(25, 41)
|
||||
},
|
||||
{
|
||||
id: 'food',
|
||||
name: '食物和饮料',
|
||||
emojis: emojis.slice(41, 57)
|
||||
},
|
||||
{
|
||||
id: 'activity',
|
||||
name: '活动和运动',
|
||||
emojis: emojis.slice(57, 73)
|
||||
},
|
||||
{
|
||||
id: 'travel',
|
||||
name: '旅行和地点',
|
||||
emojis: emojis.slice(73, 89)
|
||||
},
|
||||
{
|
||||
id: 'symbols',
|
||||
name: '符号和标志',
|
||||
emojis: emojis.slice(89, 105)
|
||||
}
|
||||
];
|
||||
|
||||
export default {
|
||||
emojis,
|
||||
emojiCategories
|
||||
};
|
||||
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* 通用工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* @param {Date|string|number} date 时间
|
||||
* @param {string} format 格式 'YYYY-MM-DD HH:mm:ss'
|
||||
*/
|
||||
export function formatTime(date, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||
if (!date) return ''
|
||||
|
||||
const d = new Date(date)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
|
||||
const year = d.getFullYear()
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hours = String(d.getHours()).padStart(2, '0')
|
||||
const minutes = String(d.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(d.getSeconds()).padStart(2, '0')
|
||||
|
||||
return format
|
||||
.replace('YYYY', year)
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
.replace('HH', hours)
|
||||
.replace('mm', minutes)
|
||||
.replace('ss', seconds)
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化相对时间
|
||||
* @param {Date|string|number} date 时间
|
||||
*/
|
||||
export function formatRelativeTime(date) {
|
||||
if (!date) return ''
|
||||
|
||||
const now = new Date()
|
||||
const target = new Date(date)
|
||||
const diff = now.getTime() - target.getTime()
|
||||
|
||||
const minute = 60 * 1000
|
||||
const hour = 60 * minute
|
||||
const day = 24 * hour
|
||||
const week = 7 * day
|
||||
const month = 30 * day
|
||||
|
||||
if (diff < minute) {
|
||||
return '刚刚'
|
||||
} else if (diff < hour) {
|
||||
return `${Math.floor(diff / minute)}分钟前`
|
||||
} else if (diff < day) {
|
||||
return `${Math.floor(diff / hour)}小时前`
|
||||
} else if (diff < week) {
|
||||
return `${Math.floor(diff / day)}天前`
|
||||
} else if (diff < month) {
|
||||
return `${Math.floor(diff / week)}周前`
|
||||
} else {
|
||||
return formatTime(target, 'YYYY-MM-DD')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化金额
|
||||
* @param {number} amount 金额
|
||||
* @param {string} currency 货币符号
|
||||
*/
|
||||
export function formatMoney(amount, currency = '¥') {
|
||||
if (amount === null || amount === undefined || isNaN(amount)) return '0'
|
||||
return `${currency}${Number(amount).toLocaleString()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param {number} bytes 字节数
|
||||
*/
|
||||
export function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 B'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param {Function} func 要防抖的函数
|
||||
* @param {number} delay 延迟时间
|
||||
*/
|
||||
export function debounce(func, delay = 300) {
|
||||
let timeoutId
|
||||
return function (...args) {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => func.apply(this, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 节流函数
|
||||
* @param {Function} func 要节流的函数
|
||||
* @param {number} delay 延迟时间
|
||||
*/
|
||||
export function throttle(func, delay = 300) {
|
||||
let lastCall = 0
|
||||
return function (...args) {
|
||||
const now = Date.now()
|
||||
if (now - lastCall >= delay) {
|
||||
lastCall = now
|
||||
return func.apply(this, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝
|
||||
* @param {any} obj 要拷贝的对象
|
||||
*/
|
||||
export function deepClone(obj) {
|
||||
if (obj === null || typeof obj !== 'object') return obj
|
||||
if (obj instanceof Date) return new Date(obj.getTime())
|
||||
if (obj instanceof Array) return obj.map(item => deepClone(item))
|
||||
if (typeof obj === 'object') {
|
||||
const clonedObj = {}
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
clonedObj[key] = deepClone(obj[key])
|
||||
}
|
||||
}
|
||||
return clonedObj
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一ID
|
||||
*/
|
||||
export function generateId() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substr(2)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证手机号
|
||||
* @param {string} phone 手机号
|
||||
*/
|
||||
export function validatePhone(phone) {
|
||||
const reg = /^1[3-9]\d{9}$/
|
||||
return reg.test(phone)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证邮箱
|
||||
* @param {string} email 邮箱
|
||||
*/
|
||||
export function validateEmail(email) {
|
||||
const reg = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
return reg.test(email)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证身份证号
|
||||
* @param {string} idCard 身份证号
|
||||
*/
|
||||
export function validateIdCard(idCard) {
|
||||
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
|
||||
return reg.test(idCard)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取URL参数
|
||||
* @param {string} name 参数名
|
||||
* @param {string} url URL地址
|
||||
*/
|
||||
export function getUrlParam(name, url = window.location.href) {
|
||||
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)')
|
||||
const r = url.match(reg)
|
||||
if (r != null) return decodeURIComponent(r[2])
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储到本地
|
||||
* @param {string} key 键
|
||||
* @param {any} value 值
|
||||
*/
|
||||
export function setStorage(key, value) {
|
||||
try {
|
||||
uni.setStorageSync(key, JSON.stringify(value))
|
||||
} catch (error) {
|
||||
console.error('存储失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从本地获取
|
||||
* @param {string} key 键
|
||||
* @param {any} defaultValue 默认值
|
||||
*/
|
||||
export function getStorage(key, defaultValue = null) {
|
||||
try {
|
||||
const value = uni.getStorageSync(key)
|
||||
return value ? JSON.parse(value) : defaultValue
|
||||
} catch (error) {
|
||||
console.error('获取存储失败:', error)
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除本地存储
|
||||
* @param {string} key 键
|
||||
*/
|
||||
export function removeStorage(key) {
|
||||
try {
|
||||
uni.removeStorageSync(key)
|
||||
} catch (error) {
|
||||
console.error('删除存储失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示加载提示
|
||||
* @param {string} title 提示文字
|
||||
*/
|
||||
export function showLoading(title = '加载中...') {
|
||||
uni.showLoading({
|
||||
title,
|
||||
mask: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏加载提示
|
||||
*/
|
||||
export function hideLoading() {
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示成功提示
|
||||
* @param {string} title 提示文字
|
||||
*/
|
||||
export function showSuccess(title) {
|
||||
uni.showToast({
|
||||
title,
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误提示
|
||||
* @param {string} title 提示文字
|
||||
*/
|
||||
export function showError(title) {
|
||||
uni.showToast({
|
||||
title,
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示普通提示
|
||||
* @param {string} title 提示文字
|
||||
*/
|
||||
export function showToast(title) {
|
||||
uni.showToast({
|
||||
title,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示确认对话框
|
||||
* @param {string} content 内容
|
||||
* @param {string} title 标题
|
||||
*/
|
||||
export function showConfirm(content, title = '提示') {
|
||||
return new Promise((resolve) => {
|
||||
uni.showModal({
|
||||
title,
|
||||
content,
|
||||
success: (res) => {
|
||||
resolve(res.confirm)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面跳转
|
||||
* @param {string} url 页面路径
|
||||
* @param {object} params 参数
|
||||
*/
|
||||
export function navigateTo(url, params = {}) {
|
||||
const query = Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key])}`).join('&')
|
||||
const fullUrl = query ? `${url}?${query}` : url
|
||||
|
||||
uni.navigateTo({
|
||||
url: fullUrl,
|
||||
fail: (error) => {
|
||||
console.error('页面跳转失败:', error)
|
||||
showError('页面跳转失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面重定向
|
||||
* @param {string} url 页面路径
|
||||
* @param {object} params 参数
|
||||
*/
|
||||
export function redirectTo(url, params = {}) {
|
||||
const query = Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key])}`).join('&')
|
||||
const fullUrl = query ? `${url}?${query}` : url
|
||||
|
||||
uni.redirectTo({
|
||||
url: fullUrl,
|
||||
fail: (error) => {
|
||||
console.error('页面重定向失败:', error)
|
||||
showError('页面重定向失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换Tab页面
|
||||
* @param {string} url 页面路径
|
||||
*/
|
||||
export function switchTab(url) {
|
||||
uni.switchTab({
|
||||
url,
|
||||
fail: (error) => {
|
||||
console.error('Tab切换失败:', error)
|
||||
showError('页面切换失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回上一页
|
||||
* @param {number} delta 返回层数
|
||||
*/
|
||||
export function navigateBack(delta = 1) {
|
||||
uni.navigateBack({
|
||||
delta,
|
||||
fail: (error) => {
|
||||
console.error('返回失败:', error)
|
||||
showError('返回失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { useAuthStore } from '../store/authStore.js'
|
||||
|
||||
// 需要登录的页面路径
|
||||
const authRequiredPages = [
|
||||
'/pages/index/index',
|
||||
'/pages/profile/profile',
|
||||
'/pages/function/function',
|
||||
'/pages/message/message',
|
||||
'/pages/tasks/index'
|
||||
]
|
||||
|
||||
// 登录页面路径
|
||||
const loginPage = '/pages/login/index'
|
||||
|
||||
// 首页路径
|
||||
const homePage = '/pages/index/index'
|
||||
|
||||
/**
|
||||
* 检查当前页面是否需要登录
|
||||
* @param {string} currentPath 当前页面路径
|
||||
* @returns {boolean} 是否需要登录
|
||||
*/
|
||||
export function isAuthRequired(currentPath) {
|
||||
return authRequiredPages.includes(currentPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由守卫 - 检查登录状态
|
||||
* @param {string} toPath 目标页面路径
|
||||
* @returns {boolean} 是否允许访问
|
||||
*/
|
||||
export function checkAuthGuard(toPath) {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 如果是登录页面,直接允许访问
|
||||
if (toPath === loginPage) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否需要登录
|
||||
if (isAuthRequired(toPath)) {
|
||||
// 检查是否已登录
|
||||
if (!authStore.isAuthenticated) {
|
||||
// 跳转到登录页面
|
||||
uni.reLaunch({
|
||||
url: loginPage
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化路由守卫
|
||||
*/
|
||||
export function initRouteGuard() {
|
||||
// 监听页面跳转
|
||||
uni.addInterceptor('navigateTo', {
|
||||
invoke(args) {
|
||||
const toPath = args.url.split('?')[0] // 移除查询参数
|
||||
if (!checkAuthGuard(toPath)) {
|
||||
return false // 阻止跳转
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
uni.addInterceptor('redirectTo', {
|
||||
invoke(args) {
|
||||
const toPath = args.url.split('?')[0]
|
||||
if (!checkAuthGuard(toPath)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
uni.addInterceptor('switchTab', {
|
||||
invoke(args) {
|
||||
const toPath = args.url.split('?')[0]
|
||||
if (!checkAuthGuard(toPath)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
uni.addInterceptor('reLaunch', {
|
||||
invoke(args) {
|
||||
const toPath = args.url.split('?')[0]
|
||||
if (!checkAuthGuard(toPath)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录成功后跳转
|
||||
* @param {string} redirectPath 重定向路径,默认为首页
|
||||
*/
|
||||
export function redirectAfterLogin(redirectPath = homePage) {
|
||||
uni.reLaunch({
|
||||
url: redirectPath
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出后跳转
|
||||
*/
|
||||
export function redirectAfterLogout() {
|
||||
uni.reLaunch({
|
||||
url: loginPage
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 启动画面管理器
|
||||
*/
|
||||
|
||||
// 启动画面配置
|
||||
export const splashConfig = {
|
||||
// 最小显示时间(毫秒)
|
||||
minDisplayTime: 2000,
|
||||
// 最大显示时间(毫秒)
|
||||
maxDisplayTime: 5000,
|
||||
// 是否已显示过启动画面
|
||||
hasShown: false,
|
||||
// 启动时间戳
|
||||
startTime: null
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化启动画面
|
||||
*/
|
||||
export function initSplash() {
|
||||
splashConfig.startTime = Date.now()
|
||||
splashConfig.hasShown = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该显示启动画面
|
||||
*/
|
||||
export function shouldShowSplash() {
|
||||
// 如果已经显示过,不再显示
|
||||
if (splashConfig.hasShown) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否在冷启动状态
|
||||
const isColdStart = !getApp().globalData?.isWarmStart
|
||||
|
||||
return isColdStart
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记启动画面已显示
|
||||
*/
|
||||
export function markSplashShown() {
|
||||
splashConfig.hasShown = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启动画面显示时长
|
||||
*/
|
||||
export function getSplashDuration() {
|
||||
if (!splashConfig.startTime) {
|
||||
return splashConfig.minDisplayTime
|
||||
}
|
||||
|
||||
const elapsed = Date.now() - splashConfig.startTime
|
||||
return Math.max(splashConfig.minDisplayTime - elapsed, 500)
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动画面加载步骤配置
|
||||
*/
|
||||
export const loadingSteps = [
|
||||
{
|
||||
text: '正在初始化...',
|
||||
duration: 800,
|
||||
action: 'init'
|
||||
},
|
||||
{
|
||||
text: '加载用户数据...',
|
||||
duration: 1000,
|
||||
action: 'loadUserData'
|
||||
},
|
||||
{
|
||||
text: '同步工作数据...',
|
||||
duration: 800,
|
||||
action: 'syncWorkData'
|
||||
},
|
||||
{
|
||||
text: '准备就绪...',
|
||||
duration: 600,
|
||||
action: 'ready'
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 执行启动步骤
|
||||
*/
|
||||
export async function executeLoadingStep(step) {
|
||||
try {
|
||||
switch (step.action) {
|
||||
case 'init':
|
||||
await initializeApp()
|
||||
break
|
||||
case 'loadUserData':
|
||||
await loadUserData()
|
||||
break
|
||||
case 'syncWorkData':
|
||||
await syncWorkData()
|
||||
break
|
||||
case 'ready':
|
||||
await prepareReady()
|
||||
break
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(`启动步骤 ${step.action} 执行失败:`, error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用
|
||||
*/
|
||||
async function initializeApp() {
|
||||
// 模拟初始化过程
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
|
||||
// 这里可以添加实际的初始化逻辑
|
||||
// 例如:检查网络状态、初始化全局配置等
|
||||
console.log('应用初始化完成')
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载用户数据
|
||||
*/
|
||||
async function loadUserData() {
|
||||
// 模拟加载用户数据
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
|
||||
// 这里可以添加实际的用户数据加载逻辑
|
||||
// 例如:从本地存储加载用户信息、验证登录状态等
|
||||
console.log('用户数据加载完成')
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步工作数据
|
||||
*/
|
||||
async function syncWorkData() {
|
||||
// 模拟同步工作数据
|
||||
await new Promise(resolve => setTimeout(resolve, 400))
|
||||
|
||||
// 这里可以添加实际的工作数据同步逻辑
|
||||
// 例如:同步待办事项、考勤数据、消息等
|
||||
console.log('工作数据同步完成')
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备就绪
|
||||
*/
|
||||
async function prepareReady() {
|
||||
// 模拟准备就绪过程
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
|
||||
// 这里可以添加最后的准备逻辑
|
||||
// 例如:预加载关键数据、设置全局状态等
|
||||
console.log('应用准备就绪')
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动画面完成回调
|
||||
*/
|
||||
export function onSplashComplete() {
|
||||
// 标记应用为热启动
|
||||
if (getApp().globalData) {
|
||||
getApp().globalData.isWarmStart = true
|
||||
}
|
||||
|
||||
// 标记启动画面已显示
|
||||
markSplashShown()
|
||||
|
||||
console.log('启动画面完成')
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置启动状态(用于测试)
|
||||
*/
|
||||
export function resetSplashState() {
|
||||
splashConfig.hasShown = false
|
||||
splashConfig.startTime = null
|
||||
if (getApp().globalData) {
|
||||
getApp().globalData.isWarmStart = false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user