2025-10-27 23:13:08 +08:00

1048 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="workbench">
<!-- 移动设备顶部状态栏 -->
<view class="top_bar flex w-full" v-if="isMobile">
<view class="chat-header">
<view class="header-left">
<i class="fas fa-bell header-icon" @click="handleNotification"></i>
</view>
<view class="header-title">工作台</view>
<view class="header-right">
<i class="fas fa-search header-icon" @click="handleSearch"></i>
</view>
</view>
</view>
<!-- 浏览器环境顶部导航栏 -->
<view class="unified-header" v-else>
<view class="header-content">
<view class="header-left">
<i class="fas fa-bell header-icon" @click="handleNotification"></i>
</view>
<view class="header-title">工作台</view>
<view class="header-right">
<i class="fas fa-search header-icon" @click="handleSearch"></i>
</view>
</view>
</view>
<!-- 页面内容 -->
<scroll-view scroll-y class="unified-content">
<!-- 欢迎和天气模块 -->
<view class="welcome-section">
<view class="welcome-header">
<view class="welcome-info">
<text class="welcome-text"
>{{ getGreeting() }}{{ userName }}</text
>
<text class="welcome-date">{{ currentDate }}</text>
</view>
<view class="weather-card">
<view class="weather-icon">
<i class="fas fa-sun weather-icon-fa"></i>
</view>
<view class="weather-info">
<text class="weather-temp">{{ weather.temperature }}°</text>
<text class="weather-desc">{{ weather.description }}</text>
<text class="weather-location">{{ weather.location }}</text>
</view>
</view>
</view>
</view>
<!-- 工作状态卡片 -->
<view class="status-card">
<view class="status-header">
<text class="status-title">今日工作状态</text>
<view class="status-indicator" :class="workStatus">
<text class="status-text">{{ workStatusText }}</text>
</view>
</view>
<view class="status-content">
<view class="status-item">
<text class="status-label">上班时间</text>
<text class="status-value">{{ workTime }}</text>
</view>
<view class="status-item">
<text class="status-label">工作时长</text>
<text class="status-value">{{ workDuration }}</text>
</view>
</view>
</view>
<!-- 数据统计 -->
<view class="stats-section">
<view class="stats-grid">
<view class="stat-card pending" @click="goToApproval">
<view class="stat-icon">
<i class="fas fa-clipboard-list stat-icon-fa"></i>
</view>
<view class="stat-content">
<text class="stat-number">{{ pendingApproval }}</text>
<text class="stat-label">待审批</text>
</view>
</view>
<view class="stat-card tasks" @click="goToTasks">
<view class="stat-icon">
<i class="fas fa-check-circle stat-icon-fa"></i>
</view>
<view class="stat-content">
<text class="stat-number">{{ completedTasks }}</text>
<text class="stat-label">已完成</text>
</view>
</view>
<view class="stat-card money" @click="goToReimbursement">
<view class="stat-icon">
<i class="fas fa-money-bill-wave stat-icon-fa"></i>
</view>
<view class="stat-content">
<text class="stat-number">¥{{ reimbursementAmount }}</text>
<text class="stat-label">本月报销</text>
</view>
</view>
<view class="stat-card attendance" @click="goToAttendance">
<view class="stat-icon">
<i class="fas fa-clock stat-icon-fa"></i>
</view>
<view class="stat-content">
<text class="stat-number">{{ attendanceRate }}%</text>
<text class="stat-label">出勤率</text>
</view>
</view>
</view>
</view>
<!-- 快捷功能 -->
<view class="functions-section">
<view class="section-header">
<text class="section-title">快捷功能</text>
<text class="more-btn" @click="showAllFunctions">查看全部</text>
</view>
<view class="functions-grid">
<view
class="function-card"
v-for="(action, index) in quickActions"
:key="index"
@click="handleQuickAction(action)"
>
<view
class="function-icon"
:style="{ background: action.gradient }"
>
<i :class="action.iconClass" class="function-icon-fa"></i>
</view>
<text class="function-name">{{ action.label }}</text>
</view>
</view>
</view>
<!-- 最近动态 -->
<view class="recent-section">
<view class="section-header">
<text class="section-title">最近动态</text>
</view>
<view class="recent-list">
<view
class="recent-item"
v-for="(item, index) in recentActivities"
:key="index"
@click="handleRecentItem(item)"
>
<view
class="recent-icon"
:style="{ background: item.color + '20' }"
>
<i :class="item.iconClass" class="recent-icon-fa"></i>
</view>
<view class="recent-content">
<text class="recent-title">{{ item.title }}</text>
<text class="recent-desc">{{ item.description }}</text>
<text class="recent-time">{{ item.time }}</text>
</view>
<view class="recent-arrow">
<i class="fas fa-chevron-right arrow-icon"></i>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { ref, reactive, computed } from "vue";
import { useAuthStore } from "../../src/store/authStore.js";
export default {
setup() {
const authStore = useAuthStore();
// 用户信息 - 从 Pinia store 获取
const userInfo = computed(() => {
return (
authStore.userInfo || {
nickname: "未登录",
name: "未登录",
department: "未知部门",
dept: { name: "未知部门" },
employeeId: "N/A",
avatar: "static\\imgs\\default_avatar.png",
attendanceRate: 0,
completedTasks: 0,
pendingAmount: "0",
}
);
});
// 登录状态
const isLogin = computed(() => {
return authStore.isAuthenticated;
});
// 设备检测
const isMobile = computed(() => {
return getApp().globalData.isMobile;
});
// 用户信息
const userName = authStore.userInfo?.nickname || '未登录';
// 获取当前日期格式2024年10月15日 星期二
function getFormattedDate() {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const day = now.getDate();
const weekDays = [
"星期日",
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六",
];
const weekDay = weekDays[now.getDay()];
return `${year}${month}${day}${weekDay}`;
}
const currentDate = ref(getFormattedDate());
// 工作状态
const workStatus = ref("working");
const workStatusText = ref("工作中");
const workTime = ref("09:00");
const workDuration = ref("6小时30分");
// 统计数据
const unreadCount = ref(3);
const pendingApproval = ref(3);
const completedTasks = ref(12);
const reimbursementAmount = ref("2,580");
const attendanceRate = ref(98);
// 天气信息
const weather = reactive({
temperature: 22,
description: "晴朗",
location: "北京市",
icon: "fas fa-sun",
});
// 快捷功能数据
const quickActions = reactive([
{
iconClass: "fas fa-tasks",
label: "任务管理",
action: "tasks",
gradient: "linear-gradient(135deg, #3498db, #5dade2)",
},
{
iconClass: "fas fa-calendar-alt",
label: "请假申请",
action: "leave",
gradient: "linear-gradient(135deg, #ef4444, #f87171)",
},
{
iconClass: "fas fa-receipt",
label: "报销提交",
action: "reimbursement",
gradient: "linear-gradient(135deg, #10b981, #34d399)",
},
{
iconClass: "fas fa-clock",
label: "考勤打卡",
action: "checkin",
gradient: "linear-gradient(135deg, #3b82f6, #60a5fa)",
},
{
iconClass: "fas fa-building",
label: "会议预订",
action: "meeting",
gradient: "linear-gradient(135deg, #8b5cf6, #a78bfa)",
},
{
iconClass: "fas fa-users",
label: "客户管理",
action: "customer",
gradient: "linear-gradient(135deg, #f59e0b, #fbbf24)",
},
{
iconClass: "fas fa-chart-bar",
label: "工作报告",
action: "report",
gradient: "linear-gradient(135deg, #06b6d4, #22d3ee)",
},
]);
// 最近动态数据
const recentActivities = reactive([
{
iconClass: "fas fa-check-circle",
title: "请假申请已通过",
description: "您的年假申请已获得部门经理批准",
time: "2小时前",
color: "#10b981",
},
{
iconClass: "fas fa-money-bill-wave",
title: "报销到账通知",
description: "差旅费报销 ¥1,200 已到账",
time: "昨天",
color: "#3b82f6",
},
{
iconClass: "fas fa-calendar-check",
title: "会议提醒",
description: "项目评审会议将在30分钟后开始",
time: "3小时前",
color: "#f59e0b",
},
{
iconClass: "fas fa-exclamation-triangle",
title: "考勤异常",
description: "今日下班打卡时间异常,请确认",
time: "昨天",
color: "#ef4444",
},
]);
// 方法
const handleSearch = () => {
uni.showToast({
title: "搜索功能",
icon: "none",
});
};
const handleNotification = () => {
uni.switchTab({
url: "/pages/message/message",
});
};
const goToApproval = () => {
uni.showToast({
title: "跳转审批列表",
icon: "none",
});
};
const goToTasks = () => {
uni.showToast({
title: "跳转任务列表",
icon: "none",
});
};
const goToAttendance = () => {
uni.showToast({
title: "跳转考勤详情",
icon: "none",
});
};
const goToReimbursement = () => {
uni.showToast({
title: "跳转报销进度",
icon: "none",
});
};
const showAllFunctions = () => {
uni.switchTab({
url: "/pages/function/function",
});
};
const handleQuickAction = (action) => {
switch (action.action) {
case "tasks":
uni.navigateTo({
url: "/pages/tasks/index",
});
break;
case "leave":
uni.showToast({
title: "请假申请功能开发中",
icon: "none",
});
break;
case "reimbursement":
uni.showToast({
title: "报销提交功能开发中",
icon: "none",
});
break;
case "checkin":
uni.showToast({
title: "考勤打卡功能开发中",
icon: "none",
});
break;
case "meeting":
uni.showToast({
title: "会议预订功能开发中",
icon: "none",
});
break;
case "customer":
uni.showToast({
title: "客户管理功能开发中",
icon: "none",
});
break;
case "report":
uni.showToast({
title: "工作报告功能开发中",
icon: "none",
});
break;
default:
uni.showToast({
title: `打开${action.label}`,
icon: "none",
});
}
};
const handleRecentItem = (item) => {
uni.showToast({
title: `查看${item.title}`,
icon: "none",
});
};
// 获取天气信息
const getWeatherInfo = () => {
// 这里可以调用天气API获取实时天气
// 目前使用模拟数据
const weatherData = {
temperature: Math.floor(Math.random() * 15) + 15, // 15-30度随机温度
description: ["晴朗", "多云", "小雨", "阴天"][
Math.floor(Math.random() * 4)
],
location: "北京市",
icon: "fas fa-sun",
};
weather.temperature = weatherData.temperature;
weather.description = weatherData.description;
weather.location = weatherData.location;
weather.icon = weatherData.icon;
};
// 获取问候语
const getGreeting = () => {
const hour = new Date().getHours();
if (hour < 6) return "夜深了";
if (hour < 9) return "早上好";
if (hour < 12) return "上午好";
if (hour < 14) return "中午好";
if (hour < 18) return "下午好";
if (hour < 22) return "晚上好";
return "夜深了";
};
return {
userName,
currentDate,
workStatus,
workStatusText,
workTime,
workDuration,
unreadCount,
pendingApproval,
completedTasks,
reimbursementAmount,
attendanceRate,
weather,
quickActions,
recentActivities,
handleSearch,
handleNotification,
goToApproval,
goToTasks,
goToAttendance,
goToReimbursement,
showAllFunctions,
handleQuickAction,
handleRecentItem,
getWeatherInfo,
getGreeting,
isMobile,
};
},
};
</script>
<style lang="scss" scoped>
.workbench {
min-height: 100vh;
background: var(--gradient-surface);
position: relative;
width: 100vw;
box-sizing: border-box;
}
/* 浏览器环境下的顶部间距 */
.unified-header + .unified-content {
margin-top: calc(var(--status-bar-height) + 88rpx);
padding-top: 30rpx;
}
/* 支持安全区域的设备 - 移动设备不需要额外的 padding-top因为使用了 top_bar */
/* 移动设备顶部状态栏 */
.top_bar {
background: var(--gradient-primary);
box-shadow: var(--shadow-lg);
z-index: 9999;
position: fixed;
top: 0;
left: 0;
right: 0;
height: calc(var(--status-bar-height) + 88rpx);
display: flex;
align-items: flex-end;
padding-top: var(--status-bar-height);
box-sizing: border-box;
}
/* 支持安全区域的设备 */
@supports (padding: max(0px)) {
.top_bar {
height: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
}
.top_bar + .unified-content {
margin-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
padding-top: 30rpx;
}
}
/* 移动设备下的导航栏样式 */
.top_bar .chat-header {
background: transparent;
border-bottom: none;
box-shadow: none;
width: 100%;
height: 88rpx;
padding: 0 20rpx;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
}
.top_bar .header-title {
color: var(--white);
font-weight: 600;
font-size: 36rpx;
}
.top_bar .header-left i,
.top_bar .header-right i {
color: var(--white);
font-size: 40rpx;
}
.header-left,
.header-right {
width: 80rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease;
}
.top_bar .header-left:active,
.top_bar .header-right:active {
background-color: rgba(255, 255, 255, 0.2);
border-radius: 8rpx;
}
/* 移动设备下为内容添加顶部间距 */
.top_bar + .unified-content {
margin-top: calc(var(--status-bar-height) + 88rpx);
padding-top: 30rpx;
}
/* 浏览器环境顶部导航栏 */
.unified-header {
background: var(--gradient-primary);
box-shadow: var(--shadow-lg);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 9999;
height: calc(var(--status-bar-height) + 88rpx);
}
.unified-header .header-content {
padding: 20rpx 30rpx;
padding-top: calc(var(--status-bar-height) + 20rpx);
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
box-sizing: border-box;
}
.unified-header .header-title {
flex: 1;
text-align: center;
color: var(--white);
font-size: 32rpx;
font-weight: 600;
margin: 0 20rpx;
}
.unified-header .header-left,
.unified-header .header-right {
display: flex;
align-items: center;
min-width: 80rpx;
}
.unified-header .header-right {
justify-content: flex-end;
}
.unified-header .header-icon {
color: var(--white);
font-size: 36rpx;
padding: 10rpx;
transition: all 0.3s ease;
}
.unified-header .header-icon:active {
background: rgba(255, 255, 255, 0.2);
transform: scale(0.95);
}
/* 支持安全区域的设备 */
@supports (padding: max(0px)) {
.unified-header {
height: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
}
.unified-header .header-content {
padding-top: calc(var(--status-bar-height) + 20rpx + env(safe-area-inset-top));
}
.unified-header + .unified-content {
margin-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
}
}
/* 欢迎和天气模块 */
.welcome-section {
margin-bottom: 30rpx;
}
.welcome-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
background: var(--gradient-primary);
border-radius: 20rpx;
padding: 40rpx 32rpx;
color: var(--white);
box-shadow: var(--shadow-lg);
}
.welcome-info {
flex: 1;
}
.welcome-text {
font-size: 36rpx;
font-weight: 600;
margin-bottom: 12rpx;
display: block;
}
.welcome-date {
font-size: 26rpx;
opacity: 0.9;
display: block;
}
.weather-card {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.15);
border-radius: 16rpx;
padding: 20rpx 24rpx;
backdrop-filter: blur(10rpx);
border: 1rpx solid rgba(255, 255, 255, 0.2);
}
.weather-icon {
width: 60rpx;
height: 60rpx;
border-radius: 12rpx;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
}
.weather-icon-fa {
font-size: 28rpx;
color: var(--white);
}
.weather-info {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.weather-temp {
font-size: 32rpx;
font-weight: 700;
margin-bottom: 4rpx;
display: block;
}
.weather-desc {
font-size: 22rpx;
opacity: 0.9;
margin-bottom: 4rpx;
display: block;
}
.weather-location {
font-size: 20rpx;
opacity: 0.8;
display: block;
}
/* 工作状态卡片 */
.status-card {
background: var(--white);
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: var(--shadow);
border: 1rpx solid var(--border-light);
}
.status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.status-title {
font-size: 32rpx;
font-weight: 600;
color: var(--text-color);
}
.status-indicator {
padding: 12rpx 24rpx;
border-radius: 20rpx;
font-size: 24rpx;
}
.status-indicator.working {
background: var(--gradient-success);
color: var(--white);
}
.status-text {
font-weight: 500;
}
.status-content {
display: flex;
gap: 60rpx;
}
.status-item {
flex: 1;
}
.status-label {
font-size: 24rpx;
color: var(--text-secondary);
margin-bottom: 8rpx;
display: block;
}
.status-value {
font-size: 32rpx;
font-weight: 600;
color: var(--text-color);
}
/* 数据统计 */
.stats-section {
margin-bottom: 30rpx;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
width: 100%;
box-sizing: border-box;
}
.stat-card {
background: var(--white);
border-radius: 12rpx;
padding: 24rpx;
display: flex;
align-items: center;
box-shadow: var(--shadow);
border: 1rpx solid var(--border-light);
transition: all 0.3s ease;
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.stat-card:active {
transform: scale(0.98);
}
.stat-icon {
width: 80rpx;
height: 80rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.stat-card.pending .stat-icon {
background: var(--gradient-error);
}
.stat-card.tasks .stat-icon {
background: var(--gradient-success);
}
.stat-card.money .stat-icon {
background: var(--gradient-primary);
}
.stat-card.attendance .stat-icon {
background: var(--gradient-warning);
}
.stat-content {
flex: 1;
}
.stat-number {
font-size: 36rpx;
font-weight: 700;
color: var(--text-color);
margin-bottom: 8rpx;
display: block;
}
.stat-label {
font-size: 24rpx;
color: var(--text-secondary);
}
/* 快捷功能 */
.functions-section {
margin-bottom: 30rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: var(--text-color);
}
.more-btn {
font-size: 26rpx;
color: var(--primary-color);
}
.functions-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
width: 100%;
box-sizing: border-box;
}
.function-card {
background: var(--white);
border-radius: 12rpx;
padding: 24rpx 16rpx;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: var(--shadow);
border: 1rpx solid var(--border-light);
transition: all 0.3s ease;
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.function-card:active {
transform: scale(0.95);
}
.function-icon {
width: 80rpx;
height: 80rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
}
.function-name {
font-size: 24rpx;
color: var(--text-color);
text-align: center;
font-weight: 500;
}
/* 最近动态 */
.recent-section {
margin-bottom: 0;
}
.recent-list {
background: var(--white);
border-radius: 12rpx;
overflow: hidden;
box-shadow: var(--shadow);
border: 1rpx solid var(--border-light);
}
.recent-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
transition: background-color 0.3s ease;
}
.recent-item:last-child {
border-bottom: none;
}
.recent-item:active {
background-color: rgba(0, 0, 0, 0.02);
}
.recent-icon {
width: 60rpx;
height: 60rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.recent-content {
flex: 1;
}
.recent-title {
font-size: 28rpx;
font-weight: 600;
color: var(--text-color);
margin-bottom: 8rpx;
display: block;
}
.recent-desc {
font-size: 24rpx;
color: var(--text-secondary);
margin-bottom: 8rpx;
display: block;
}
.recent-time {
font-size: 22rpx;
color: var(--text-muted);
}
.recent-arrow {
margin-left: 20rpx;
}
/* 底部安全区域适配 */
.safe-area-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
/* 响应式设计 */
@media screen and (max-width: 750rpx) {
.page-content {
padding: 20rpx;
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
min-height: calc(100vh - 120rpx);
}
.welcome-header {
flex-direction: column;
align-items: flex-start;
gap: 20rpx;
}
.weather-card {
align-self: flex-end;
}
.stats-grid {
gap: 15rpx;
}
.functions-grid {
grid-template-columns: repeat(2, 1fr);
gap: 15rpx;
}
}
</style>