Release: 更新用户使用说明文档
This commit is contained in:
@@ -43,9 +43,9 @@
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| 快捷入口 | 常用功能快速访问 |
|
||||
| 待办事项 | 需要您处理的审批 |
|
||||
| 最新动态 | 最新通知和消息 |
|
||||
| 欢迎信息 | 显示用户名和当前日期 |
|
||||
| 语言切换 | 点击右上角国旗图标切换语言 |
|
||||
| 快捷入口 | 出差申请、审批等功能快速访问 |
|
||||
|
||||
---
|
||||
|
||||
@@ -82,6 +82,13 @@
|
||||
2. 点击「撤销申请」
|
||||
3. 确认撤销
|
||||
|
||||
### 查看出差记录
|
||||
|
||||
在「出差申请」页面可以查看:
|
||||
- 全部申请
|
||||
- 待审批
|
||||
- 已完成
|
||||
|
||||
---
|
||||
|
||||
## 📋 审批流程
|
||||
@@ -96,10 +103,14 @@
|
||||
4. 选择操作:
|
||||
- **通过**:批准申请
|
||||
- **拒绝**:拒绝申请(可填写原因)
|
||||
- **撤销**:(仅申请人可操作)撤回申请
|
||||
|
||||
### 我发起的
|
||||
|
||||
查看您提交的所有申请及其状态。
|
||||
查看您提交的所有申请及其状态:
|
||||
- 待审批:等待审批人处理
|
||||
- 已通过:申请已获批准
|
||||
- 已拒绝:申请被拒绝
|
||||
|
||||
### 我已审批
|
||||
|
||||
@@ -109,6 +120,14 @@
|
||||
|
||||
## 👤 个人中心
|
||||
|
||||
### 基本信息
|
||||
|
||||
在个人中心可以查看和修改:
|
||||
- 头像
|
||||
- 姓名
|
||||
- 邮箱
|
||||
- 手机号
|
||||
|
||||
### 修改密码
|
||||
|
||||
1. 进入「修改密码」页面
|
||||
@@ -126,6 +145,10 @@
|
||||
- 🇪🇸 Español
|
||||
- 🇫🇷 Français
|
||||
|
||||
### 帮助中心
|
||||
|
||||
点击「帮助中心」查看使用说明和常见问题。
|
||||
|
||||
---
|
||||
|
||||
## 💡 常见问题
|
||||
@@ -139,6 +162,17 @@ A: 草稿状态可以修改,已提交的申请无法修改,只能撤销后
|
||||
### Q: 审批拒绝后怎么办?
|
||||
A: 可以联系审批人了解原因,或重新提交申请。
|
||||
|
||||
### Q: 如何查看帮助文档?
|
||||
A: 在个人中心点击「帮助中心」,可以查看完整的使用说明。
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系支持
|
||||
|
||||
如有问题或建议,请联系:
|
||||
- 技术支持:support@example.com
|
||||
- 管理员:admin@example.com
|
||||
|
||||
---
|
||||
|
||||
**版本**: 1.0.0
|
||||
|
||||
@@ -89,6 +89,15 @@
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 2px 8px;
|
||||
background: #ff4d4f;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.more {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
@@ -181,6 +190,15 @@
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.todoMore {
|
||||
padding: 12px 16px;
|
||||
text-align: center;
|
||||
color: #1677ff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
border-top: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
@@ -252,6 +270,18 @@
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.activityIcon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.activityDot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useUserStore } from '@/stores/user'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Toast } from 'antd-mobile'
|
||||
import { showLanguageSwitcher } from '@/components/LanguageSwitcher'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styles from './dashboard.module.css'
|
||||
|
||||
interface QuickAction {
|
||||
@@ -14,35 +15,80 @@ interface QuickAction {
|
||||
description?: string
|
||||
}
|
||||
|
||||
interface TodoItem {
|
||||
id: number
|
||||
title: string
|
||||
type: string
|
||||
time: string
|
||||
}
|
||||
|
||||
interface ActivityItem {
|
||||
id: number
|
||||
content: string
|
||||
time: string
|
||||
type: 'approval' | 'submit' | 'other'
|
||||
}
|
||||
|
||||
// 从应用页面迁移的完整应用列表
|
||||
const quickActions: QuickAction[] = [
|
||||
{ id: '1', icon: '✈️', label: '出差申请', path: '/business-trip/apply', color: '#1677ff', description: '提交出差申请' },
|
||||
{ id: '2', icon: '📝', label: '采购申请', path: '/purchase/apply', color: '#52c41a', description: '物品采购审批' },
|
||||
{ id: '3', icon: '📦', label: '备货录入', path: '/stock/entry', color: '#faad14', description: '备货信息录入' },
|
||||
{ id: '4', icon: '📅', label: '日程管理', path: '/schedule', color: '#722ed1', description: '个人日程安排' },
|
||||
{ id: '5', icon: '💰', label: '报销申请', path: '/expense/apply', color: '#eb2f96', description: '费用报销审批' },
|
||||
{ id: '6', icon: '📋', label: '请假申请', path: '/leave/apply', color: '#13c2c2', description: '请假审批' },
|
||||
{ id: '7', icon: '🚗', label: '用车申请', path: '/car/apply', color: '#f759ab', description: '公车使用申请' },
|
||||
{ id: '8', icon: '🎁', label: '礼品申请', path: '/gift/apply', color: '#ff7a45', description: '礼品采购申请' },
|
||||
{ id: '9', icon: '📊', label: '数据报表', path: '/reports', color: '#1890ff', description: '业务数据统计' },
|
||||
{ id: '10', icon: '👥', label: '通讯录', path: '/contacts', color: '#096dd9', description: '内部通讯录' },
|
||||
{ id: '11', icon: '📁', label: '文件中心', path: '/files', color: '#595959', description: '文件共享管理' },
|
||||
{ id: '12', icon: '⚙️', label: '系统设置', path: '/settings', color: '#8c8c8c', description: '系统偏好设置' },
|
||||
{ id: '2', icon: '✍️', label: '审批', path: '/approval', color: '#52c41a', description: '待办事项审批' },
|
||||
]
|
||||
|
||||
export default function Dashboard() {
|
||||
const navigate = useNavigate()
|
||||
const { user } = useUserStore()
|
||||
const { t } = useTranslation()
|
||||
const [todos, setTodos] = useState<TodoItem[]>([])
|
||||
const [activities, setActivities] = useState<ActivityItem[]>([])
|
||||
const [pendingCount, setPendingCount] = useState(0)
|
||||
|
||||
// 获取待办事项
|
||||
useEffect(() => {
|
||||
const fetchTodos = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/v1/approval/list?type=pending&page=1&page_size=5')
|
||||
const result = await response.json()
|
||||
if (result.code === 0 && result.data?.list) {
|
||||
const list = result.data.list.map((item: any) => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
type: item.type || 'business_trip',
|
||||
time: item.created_at
|
||||
}))
|
||||
setTodos(list)
|
||||
setPendingCount(result.data.total || 0)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取待办事项失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchActivities = async () => {
|
||||
try {
|
||||
// 获取我发起的和已审批的
|
||||
const response = await fetch('/api/v1/approval/list?type=approved&page=1&page_size=5')
|
||||
const result = await response.json()
|
||||
if (result.code === 0 && result.data?.list) {
|
||||
const list = result.data.list.map((item: any) => ({
|
||||
id: item.id,
|
||||
content: `${item.applicant_name || '您'} ${item.status === 'approved' ? '通过了' : '拒绝了'} ${item.title}`,
|
||||
time: item.updated_at,
|
||||
type: 'approval' as const
|
||||
}))
|
||||
setActivities(list)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取动态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
fetchTodos()
|
||||
fetchActivities()
|
||||
}, [])
|
||||
|
||||
const handleQuickAction = (action: QuickAction) => {
|
||||
// 允许的应用:出差申请、审批、系统设置
|
||||
const allowedPaths = ['/business-trip/apply', '/business-trip', '/settings']
|
||||
if (allowedPaths.includes(action.path)) {
|
||||
navigate(action.path)
|
||||
} else {
|
||||
Toast.show({ content: '功能开发中...', duration: 1500 })
|
||||
}
|
||||
navigate(action.path)
|
||||
}
|
||||
|
||||
const getCurrentLanguage = () => {
|
||||
@@ -57,6 +103,21 @@ export default function Dashboard() {
|
||||
return langMap[lang] || '🇨🇳'
|
||||
}
|
||||
|
||||
const formatTime = (time: string) => {
|
||||
const date = new Date(time)
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
const minutes = Math.floor(diff / 60000)
|
||||
const hours = Math.floor(diff / 3600000)
|
||||
const days = Math.floor(diff / 86400000)
|
||||
|
||||
if (minutes < 1) return '刚刚'
|
||||
if (minutes < 60) return `${minutes}分钟前`
|
||||
if (hours < 24) return `${hours}小时前`
|
||||
if (days < 7) return `${days}天前`
|
||||
return date.toLocaleDateString('zh-CN')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.dashboard}>
|
||||
{/* 顶部欢迎区 */}
|
||||
@@ -73,11 +134,61 @@ export default function Dashboard() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 待办事项 */}
|
||||
{pendingCount > 0 && (
|
||||
<div className={styles.section}>
|
||||
<div className={styles.sectionHeader}>
|
||||
<h3 className={styles.sectionTitle}>待办事项</h3>
|
||||
<span className={styles.badge}>{pendingCount}</span>
|
||||
</div>
|
||||
<div className={styles.todoList}>
|
||||
{todos.slice(0, 3).map((todo) => (
|
||||
<div key={todo.id} className={styles.todoItem} onClick={() => navigate(`/approval/${todo.id}`)}>
|
||||
<div className={styles.todoIcon}>
|
||||
{todo.type === 'business_trip' ? '✈️' : '📋'}
|
||||
</div>
|
||||
<div className={styles.todoContent}>
|
||||
<div className={styles.todoTitle}>{todo.title}</div>
|
||||
<div className={styles.todoTime}>{formatTime(todo.time)}</div>
|
||||
</div>
|
||||
<div className={styles.todoArrow}>›</div>
|
||||
</div>
|
||||
))}
|
||||
{pendingCount > 3 && (
|
||||
<div className={styles.todoMore} onClick={() => navigate('/approval')}>
|
||||
查看全部 {pendingCount} 个待办 ›
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 最新动态 */}
|
||||
{activities.length > 0 && (
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>最新动态</h3>
|
||||
<div className={styles.activityList}>
|
||||
{activities.slice(0, 3).map((activity) => (
|
||||
<div key={activity.id} className={styles.activityItem}>
|
||||
<div className={styles.activityIcon}>{activity.type === 'approval' ? '✅' : '📝'}</div>
|
||||
<div className={styles.activityContent}>
|
||||
<div className={styles.activityText}>{activity.content}</div>
|
||||
<div className={styles.activityTime}>{formatTime(activity.time)}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{activities.length === 0 && (
|
||||
<div className={styles.empty}>暂无动态</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 快捷入口 */}
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>快捷入口</h3>
|
||||
<div className={styles.actionGrid}>
|
||||
{quickActions.slice(0, 3).map((action) => (
|
||||
{quickActions.map((action) => (
|
||||
<div
|
||||
key={action.id}
|
||||
className={styles.actionCard}
|
||||
@@ -92,31 +203,6 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 所有应用 */}
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>所有应用</h3>
|
||||
<div className={styles.actionGrid}>
|
||||
<div className={styles.actionCard} onClick={() => navigate('/approval')}>
|
||||
<div className={styles.actionIcon} style={{ backgroundColor: '#1677ff15' }}>
|
||||
<span style={{ fontSize: 32 }}>✍️</span>
|
||||
</div>
|
||||
<span className={styles.actionLabel}>审批</span>
|
||||
</div>
|
||||
<div className={styles.actionCard} onClick={() => handleQuickAction(quickActions[8])}>
|
||||
<div className={styles.actionIcon} style={{ backgroundColor: `${quickActions[8].color}15` }}>
|
||||
<span style={{ fontSize: 32 }}>{quickActions[8].icon}</span>
|
||||
</div>
|
||||
<span className={styles.actionLabel}>{quickActions[8].label}</span>
|
||||
</div>
|
||||
<div className={styles.actionCard} onClick={() => handleQuickAction(quickActions[11])}>
|
||||
<div className={styles.actionIcon} style={{ backgroundColor: `${quickActions[11].color}15` }}>
|
||||
<span style={{ fontSize: 32 }}>{quickActions[11].icon}</span>
|
||||
</div>
|
||||
<span className={styles.actionLabel}>{quickActions[11].label}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部安全区域 */}
|
||||
<div className={styles.safeArea} />
|
||||
</div>
|
||||
|
||||
@@ -33,12 +33,22 @@ const guideSections = [
|
||||
{ q: '查看我的申请?', a: '在「审批」页面点击「我发起的」标签查看。' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'profile',
|
||||
title: '个人中心',
|
||||
icon: '👤',
|
||||
content: [
|
||||
{ q: '如何修改密码?', a: '进入「修改密码」页面,输入原密码和新密码,点击「确认修改」即可。' },
|
||||
{ q: '支持哪些语言?', a: '系统支持简体中文、English、Русский、Español、Français 五种语言,可在首页或个人中心切换。' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'faq',
|
||||
title: '常见问题',
|
||||
icon: '💡',
|
||||
content: [
|
||||
{ q: '忘记密码?', a: '请联系管理员重置密码,或使用飞书扫码登录。' }
|
||||
{ q: '忘记密码?', a: '请联系管理员重置密码,或使用飞书扫码登录。' },
|
||||
{ q: '如何切换语言?', a: '点击首页右上角的国旗图标,或在个人中心进行切换。' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user