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

430 lines
9.4 KiB
Vue
Raw Permalink 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="function-page">
<!-- 统一顶部导航 -->
<view class="unified-header">
<view class="header-content">
<view class="header-left">
<!-- <i class="fas fa-search header-icon" @click="handleSearch"></i> -->
</view>
<view class="header-title">功能</view>
<view class="header-right">
<!-- <i class="fas fa-bell header-icon" @click="handleNotification">
<view class="badge" v-if="unreadCount > 0">{{ unreadCount }}</view>
</i> -->
</view>
</view>
</view>
<!-- 最近使用 -->
<view class="recent-section" v-if="recentFunctions.length > 0">
<view class="section-title">最近使用</view>
<view class="recent-functions">
<view
class="recent-item"
v-for="(func, index) in recentFunctions"
:key="index"
@click="handleFunction(func)"
>
<view class="recent-icon">
<i
class="icon-text"
:class="func.icon"
:style="{ color: func.color }"
></i>
</view>
<text class="recent-label">{{func.label}}</text>
</view>
</view>
</view>
<!-- 分类导航 -->
<view class="category-tabs">
<scroll-view scroll-x class="tabs-scroll">
<view class="tabs">
<view
class="tab-item"
:class="{ active: activeCategory === category.key }"
v-for="category in categories"
:key="category.key"
@click="switchCategory(category.key)"
>
<text>{{category.label}}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 功能列表 -->
<scroll-view scroll-y class="unified-content">
<view class="function-list">
<view
class="function-group"
v-for="(group, groupIndex) in currentFunctions"
:key="groupIndex"
>
<view class="group-title">{{group.title}}</view>
<view class="group-functions">
<view
class="function-item"
v-for="(func, funcIndex) in group.functions"
:key="funcIndex"
@click="handleFunction(func)"
>
<view class="function-icon">
<i
class="icon-text"
:class="func.icon"
:style="{ color: func.color }"
></i>
</view>
<view class="function-content">
<text class="function-name">{{func.name}}</text>
<text class="function-desc">{{func.description}}</text>
</view>
<view class="function-arrow">
<text class="arrow-icon"></text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { ref, reactive, computed } from 'vue'
// 已全部用FontAwesome图标无需emoji变量
// FontAwesome icon mapping:
const FA = {
clock: 'fas fa-clock',
calendar: 'fas fa-calendar-alt',
tasks: 'fas fa-tasks',
sync: 'fas fa-sync-alt',
money: 'fas fa-money-bill-wave',
file: 'fas fa-file-alt',
userFriends: 'fas fa-user-friends',
folder: 'fas fa-folder-open'
}
export default {
setup() {
// 响应式数据
const activeCategory = ref('hr')
const unreadCount = ref(2)
// 分类数据
const categories = reactive([
{ key: 'hr', label: '人事管理' },
{ key: 'finance', label: '财务报销' },
{ key: 'work', label: '工作协同' },
{ key: 'resource', label: '资源管理' }
])
// 最近使用功能 (全部FontAwesome图标)
const recentFunctions = reactive([
{ icon: FA.calendar, color: '#FF6B6B', label: '请假', action: 'leave' },
{ icon: FA.money, color: '#4ECDC4', label: '报销', action: 'reimbursement' },
{ icon: FA.clock, color: '#45B7D1', label: '打卡', action: 'checkin' }
])
// 功能数据 (全部FontAwesome图标)
const functionData = reactive({
hr: [
{
title: '考勤管理',
functions: [
{ name: '考勤打卡', description: '支持定位打卡、WiFi打卡', icon: FA.clock, color: '#45B7D1', action: 'checkin' },
{ name: '请假申请', description: '选择类型、日期、理由,附附件', icon: FA.calendar, color: '#FF6B6B', action: 'leave' },
{ name: '加班申请', description: '申请加班,记录加班时长', icon: FA.tasks, color: '#96CEB4', action: 'overtime' },
{ name: '调休记录', description: '查看调休申请和记录', icon: FA.sync, color: '#FECA57', action: 'compensatory' }
]
}
],
finance: [
{
title: '报销管理',
functions: [
{ name: '报销提交', description: '选择报销类型、上传发票、填写金额', icon: FA.money, color: '#4ECDC4', action: 'reimbursement' },
{ name: '报销进度', description: '查看报销申请进度', icon: FA.file, color: '#45B7D1', action: 'reimbursement-progress' },
{ name: '报销历史', description: '查看历史报销记录', icon: FA.tasks, color: '#96CEB4', action: 'reimbursement-history' }
]
}
],
work: [
{
title: '任务管理',
functions: [
{ name: '待办任务', description: '关联项目、设置截止时间', icon: FA.tasks, color: '#FF6B6B', action: 'todo' },
{ name: '已办任务', description: '查看已完成的任务', icon: FA.tasks, color: '#4ECDC4', action: 'completed' },
{ name: '会议预订', description: '选择会议室、邀请参会人', icon: FA.calendar, color: '#45B7D1', action: 'meeting' }
]
}
],
resource: [
{
title: '资源管理',
functions: [
{ name: '客户目录', description: '按行业、区域分类,显示联系方式', icon: FA.userFriends, color: '#FECA57', action: 'customer' },
{ name: '企业文件库', description: '按部门权限查看,支持在线预览', icon: FA.folder, color: '#96CEB4', action: 'files' }
]
}
]
})
// 计算当前分类的功能
const currentFunctions = computed(() => {
return functionData[activeCategory.value] || []
})
// 方法
const handleSearch = () => {
uni.showToast({
title: '搜索功能'
})
}
const handleNotification = () => {
uni.switchTab({
url: '/pages/message/message'
})
}
const switchCategory = (category) => {
activeCategory.value = category
}
const handleFunction = (func) => {
if (func.action === 'todo') {
uni.navigateTo({
url: '/pages/tasks/index'
});
return;
}
uni.showToast({
title: `功能开发中...`
})
}
return {
activeCategory,
unreadCount,
categories,
recentFunctions,
currentFunctions,
handleSearch,
handleNotification,
switchCategory,
handleFunction
}
}
}
</script>
<style lang="scss" scoped>
.function-page {
height: 100vh;
background-color: var(--background);
position: relative;
padding-top: calc(var(--status-bar-height) + 88rpx);
}
/* 支持安全区域的设备 */
@supports (padding: max(0px)) {
.function-page {
padding-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
}
}
.navbar-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.search-box {
flex: 1;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 25rpx;
padding: 15rpx 20rpx;
margin-right: 20rpx;
display: flex;
align-items: center;
}
.search-placeholder {
color: rgba(255, 255, 255, 0.8);
font-size: 28rpx;
margin-left: 10rpx;
}
.notification {
position: relative;
padding: 10rpx;
}
.recent-section {
background: var(--white);
padding: 30rpx;
border-bottom: 1rpx solid var(--border-light);
}
.section-title {
font-size: 28rpx;
font-weight: 600;
color: var(--text-color);
margin-bottom: 20rpx;
}
.recent-functions {
display: flex;
gap: 30rpx;
}
.recent-item {
display: flex;
flex-direction: column;
align-items: center;
}
.recent-icon {
margin-bottom: 10rpx;
}
.recent-label {
font-size: 24rpx;
color: var(--text-secondary);
}
.category-tabs {
background: var(--white);
border-bottom: 1rpx solid var(--border-light);
}
.tabs-scroll {
white-space: nowrap;
}
.tabs {
display: flex;
padding: 0 30rpx;
}
.tab-item {
padding: 30rpx 20rpx;
font-size: 28rpx;
color: var(--text-secondary);
white-space: nowrap;
position: relative;
}
.tab-item.active {
color: var(--primary-color);
font-weight: 600;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background-color: var(--primary-color);
border-radius: 2rpx;
}
.page-content {
height: calc(100vh - 300rpx);
}
.function-list {
padding: 30rpx;
}
.function-group {
margin-bottom: 40rpx;
}
.group-title {
font-size: 30rpx;
font-weight: 600;
color: var(--text-color);
margin-bottom: 20rpx;
}
.group-functions {
background: var(--white);
border-radius: 16rpx;
overflow: hidden;
box-shadow: var(--shadow);
}
.function-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid var(--border-light);
}
.function-item:last-child {
border-bottom: none;
}
.function-icon {
margin-right: 20rpx;
}
.function-content {
flex: 1;
}
.function-name {
font-size: 30rpx;
font-weight: 600;
color: var(--text-color);
margin-bottom: 8rpx;
display: block;
}
.function-desc {
font-size: 24rpx;
color: var(--text-secondary);
}
.function-arrow {
margin-left: 20rpx;
}
.search-icon, .notification-icon {
font-size: 32rpx;
margin-right: 10rpx;
color: var(--white);
}
.badge {
position: absolute;
top: 0rpx;
right: 15rpx;
background-color: var(--error);
color: var(--white);
font-size: 20rpx;
padding: 2rpx 8rpx;
border-radius: 50%;
// min-width: 30rpx;
text-align: center;
line-height: 1.2;
}
.icon-text {
font-size: 40rpx;
}
.arrow-icon {
font-size: 32rpx;
color: var(--text-muted);
}
</style>