企业应用实战 #

一、项目概述 #

1.1 功能需求 #

模块 功能
认证 企业登录、SSO、生物识别
首页 工作台、待办事项、通知公告
审批 发起审批、审批列表、审批详情
通讯录 组织架构、员工搜索、部门管理
消息 企业通讯、群聊、文件传输

1.2 技术栈 #

  • 前端框架: React + TypeScript
  • 状态管理: Zustand
  • UI组件: Ant Design Mobile
  • 原生功能: Capacitor
  • 离线支持: Service Worker + IndexedDB

二、项目初始化 #

2.1 创建项目 #

bash
# 创建项目
npm create vite@latest enterprise-app -- --template react-ts

cd enterprise-app

# 安装依赖
npm install

# 安装Capacitor
npm install @capacitor/core @capacitor/cli
npx cap init "企业应用" "com.company.enterprise"

# 安装插件
npm install @capacitor/preferences
npm install @capacitor/network
npm install @capacitor/status-bar
npm install @capacitor/splash-screen
npm install @capacitor/keyboard
npm install @capacitor/app
npm install @capacitor/browser

# 安装其他依赖
npm install zustand
npm install react-router-dom
npm install axios
npm install antd-mobile
npm install dayjs
npm install idb

2.2 项目结构 #

text
src/
├── api/
│   ├── index.ts
│   ├── auth.ts
│   ├── approval.ts
│   └── contacts.ts
├── components/
│   ├── Layout/
│   ├── ApprovalCard/
│   └── EmployeeCard/
├── hooks/
│   ├── useAuth.ts
│   ├── useOffline.ts
│   └── useSync.ts
├── pages/
│   ├── Login/
│   ├── Home/
│   ├── Approval/
│   ├── Contacts/
│   └── Messages/
├── services/
│   ├── auth.service.ts
│   ├── offline.service.ts
│   └── sync.service.ts
├── store/
│   ├── authStore.ts
│   ├── approvalStore.ts
│   └── offlineStore.ts
├── types/
│   └── index.ts
├── utils/
│   ├── crypto.ts
│   ├── storage.ts
│   └── db.ts
├── App.tsx
└── main.tsx

三、企业认证 #

3.1 认证服务 #

typescript
// src/services/auth.service.ts
import { Preferences } from '@capacitor/preferences';
import { BiometricAuth } from '@capacitor-community/biometric';
import { Capacitor } from '@capacitor/core';
import { api } from '../api';
import { Encryption } from '../utils/crypto';

interface LoginCredentials {
    username: string;
    password: string;
    companyCode?: string;
}

interface AuthResult {
    success: boolean;
    user?: any;
    token?: string;
    error?: string;
}

class AuthService {
    private tokenKey = 'auth_token';
    private userKey = 'user_info';
    private biometricKey = 'biometric_enabled';
    
    async login(credentials: LoginCredentials): Promise<AuthResult> {
        try {
            const response = await api.post('/auth/login', credentials);
            
            const { token, user } = response.data;
            
            // 安全存储token
            await this.secureStore(this.tokenKey, token);
            await Preferences.set({
                key: this.userKey,
                value: JSON.stringify(user)
            });
            
            return { success: true, user, token };
        } catch (error: any) {
            return {
                success: false,
                error: error.response?.data?.message || '登录失败'
            };
        }
    }
    
    async logout(): Promise<void> {
        try {
            await api.post('/auth/logout');
        } finally {
            await Preferences.remove({ key: this.tokenKey });
            await Preferences.remove({ key: this.userKey });
        }
    }
    
    async getToken(): Promise<string | null> {
        return this.secureRetrieve(this.tokenKey);
    }
    
    async getUser(): Promise<any | null> {
        const { value } = await Preferences.get({ key: this.userKey });
        return value ? JSON.parse(value) : null;
    }
    
    // 生物识别登录
    async enableBiometric(): Promise<boolean> {
        if (!Capacitor.isNativePlatform()) return false;
        
        try {
            const result = await BiometricAuth.checkBiometry();
            
            if (!result.isAvailable) {
                return false;
            }
            
            // 验证生物识别
            const authResult = await BiometricAuth.authenticate({
                reason: '启用生物识别登录',
                title: '身份验证'
            });
            
            if (authResult.isAuthenticated) {
                await Preferences.set({
                    key: this.biometricKey,
                    value: 'true'
                });
                return true;
            }
            
            return false;
        } catch {
            return false;
        }
    }
    
    async loginWithBiometric(): Promise<AuthResult> {
        try {
            const { value: enabled } = await Preferences.get({
                key: this.biometricKey
            });
            
            if (enabled !== 'true') {
                return { success: false, error: '生物识别未启用' };
            }
            
            const authResult = await BiometricAuth.authenticate({
                reason: '请验证身份以登录',
                title: '生物识别登录'
            });
            
            if (authResult.isAuthenticated) {
                const token = await this.getToken();
                const user = await this.getUser();
                
                if (token && user) {
                    return { success: true, user, token };
                }
            }
            
            return { success: false, error: '验证失败' };
        } catch (error: any) {
            return { success: false, error: error.message };
        }
    }
    
    private async secureStore(key: string, value: string): Promise<void> {
        // 简化版:实际应使用加密存储
        await Preferences.set({ key, value });
    }
    
    private async secureRetrieve(key: string): Promise<string | null> {
        const { value } = await Preferences.get({ key });
        return value;
    }
}

export const authService = new AuthService();

3.2 登录页面 #

tsx
// src/pages/Login/index.tsx
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Input, Form, Toast } from 'antd-mobile';
import { authService } from '../../services/auth.service';
import { useAuthStore } from '../../store/authStore';
import { Capacitor } from '@capacitor/core';

export default function Login() {
    const [loading, setLoading] = useState(false);
    const [biometricLoading, setBiometricLoading] = useState(false);
    const navigate = useNavigate();
    const { login } = useAuthStore();
    
    async function handleSubmit(values: { username: string; password: string }) {
        setLoading(true);
        
        const result = await authService.login(values);
        
        if (result.success) {
            login(result.user, result.token!);
            navigate('/');
        } else {
            Toast.show({ content: result.error });
        }
        
        setLoading(false);
    }
    
    async function handleBiometricLogin() {
        setBiometricLoading(true);
        
        const result = await authService.loginWithBiometric();
        
        if (result.success) {
            login(result.user!, result.token!);
            navigate('/');
        } else {
            Toast.show({ content: result.error });
        }
        
        setBiometricLoading(false);
    }
    
    return (
        <div className="login-page">
            <div className="login-header">
                <h1>企业办公</h1>
                <p>欢迎登录</p>
            </div>
            
            <Form onFinish={handleSubmit}>
                <Form.Item name="username" rules={[{ required: true }]}>
                    <Input placeholder="用户名" />
                </Form.Item>
                
                <Form.Item name="password" rules={[{ required: true }]}>
                    <Input type="password" placeholder="密码" />
                </Form.Item>
                
                <Button
                    type="submit"
                    color="primary"
                    block
                    loading={loading}
                >
                    登录
                </Button>
            </Form>
            
            {Capacitor.isNativePlatform() && (
                <Button
                    onClick={handleBiometricLogin}
                    block
                    loading={biometricLoading}
                    style={{ marginTop: 16 }}
                >
                    生物识别登录
                </Button>
            )}
        </div>
    );
}

四、离线支持 #

4.1 IndexedDB封装 #

typescript
// src/utils/db.ts
import { openDB, DBSchema, IDBPDatabase } from 'idb';

interface EnterpriseDB extends DBSchema {
    approvals: {
        key: string;
        value: {
            id: string;
            data: any;
            syncStatus: 'pending' | 'synced' | 'failed';
            updatedAt: number;
        };
    };
    employees: {
        key: string;
        value: {
            id: string;
            data: any;
            updatedAt: number;
        };
    };
    offlineQueue: {
        key: string;
        value: {
            id: string;
            action: string;
            data: any;
            createdAt: number;
        };
    };
}

class DatabaseService {
    private db: IDBPDatabase<EnterpriseDB> | null = null;
    
    async init(): Promise<void> {
        this.db = await openDB<EnterpriseDB>('enterprise-db', 1, {
            upgrade(db) {
                db.createObjectStore('approvals', { keyPath: 'id' });
                db.createObjectStore('employees', { keyPath: 'id' });
                db.createObjectStore('offlineQueue', { keyPath: 'id' });
            }
        });
    }
    
    // 审批数据
    async saveApproval(id: string, data: any, syncStatus: 'pending' | 'synced' | 'failed' = 'pending') {
        await this.db?.put('approvals', {
            id,
            data,
            syncStatus,
            updatedAt: Date.now()
        });
    }
    
    async getApproval(id: string) {
        return this.db?.get('approvals', id);
    }
    
    async getAllApprovals() {
        return this.db?.getAll('approvals') || [];
    }
    
    // 员工数据
    async saveEmployee(id: string, data: any) {
        await this.db?.put('employees', {
            id,
            data,
            updatedAt: Date.now()
        });
    }
    
    async getEmployee(id: string) {
        return this.db?.get('employees', id);
    }
    
    async getAllEmployees() {
        return this.db?.getAll('employees') || [];
    }
    
    // 离线队列
    async addToQueue(action: string, data: any) {
        await this.db?.put('offlineQueue', {
            id: `${Date.now()}-${Math.random()}`,
            action,
            data,
            createdAt: Date.now()
        });
    }
    
    async getQueue() {
        return this.db?.getAll('offlineQueue') || [];
    }
    
    async removeFromQueue(id: string) {
        await this.db?.delete('offlineQueue', id);
    }
    
    async clearQueue() {
        const tx = this.db?.transaction('offlineQueue', 'readwrite');
        await tx?.store.clear();
    }
}

export const db = new DatabaseService();

4.2 离线同步服务 #

typescript
// src/services/sync.service.ts
import { Network } from '@capacitor/network';
import { db } from '../utils/db';
import { api } from '../api';

class SyncService {
    private syncing = false;
    
    async init(): Promise<void> {
        await db.init();
        
        // 监听网络状态
        Network.addListener('networkStatusChange', (status) => {
            if (status.connected) {
                this.sync();
            }
        });
        
        // 如果在线,立即同步
        const status = await Network.getStatus();
        if (status.connected) {
            await this.sync();
        }
    }
    
    async sync(): Promise<void> {
        if (this.syncing) return;
        
        this.syncing = true;
        
        try {
            // 同步离线队列
            await this.syncQueue();
            
            // 同步审批数据
            await this.syncApprovals();
            
            // 同步员工数据
            await this.syncEmployees();
        } catch (error) {
            console.error('Sync error:', error);
        } finally {
            this.syncing = false;
        }
    }
    
    private async syncQueue(): Promise<void> {
        const queue = await db.getQueue();
        
        for (const item of queue) {
            try {
                await this.processQueueItem(item);
                await db.removeFromQueue(item.id);
            } catch (error) {
                console.error('Queue item sync failed:', error);
            }
        }
    }
    
    private async processQueueItem(item: any): Promise<void> {
        switch (item.action) {
            case 'submit_approval':
                await api.post('/approvals', item.data);
                break;
            case 'approve':
                await api.post(`/approvals/${item.data.id}/approve`, item.data);
                break;
            case 'reject':
                await api.post(`/approvals/${item.data.id}/reject`, item.data);
                break;
        }
    }
    
    private async syncApprovals(): Promise<void> {
        const response = await api.get('/approvals');
        
        for (const approval of response.data) {
            await db.saveApproval(approval.id, approval, 'synced');
        }
    }
    
    private async syncEmployees(): Promise<void> {
        const response = await api.get('/employees');
        
        for (const employee of response.data) {
            await db.saveEmployee(employee.id, employee);
        }
    }
    
    async submitOffline(action: string, data: any): Promise<void> {
        // 先保存到本地
        await db.addToQueue(action, data);
        
        // 如果在线,尝试同步
        const status = await Network.getStatus();
        if (status.connected) {
            await this.sync();
        }
    }
}

export const syncService = new SyncService();

4.3 离线Hook #

typescript
// src/hooks/useOffline.ts
import { useState, useEffect } from 'react';
import { Network, ConnectionStatus } from '@capacitor/network';

export function useOffline() {
    const [isOnline, setIsOnline] = useState(true);
    const [connectionType, setConnectionType] = useState<string>('unknown');
    
    useEffect(() => {
        // 获取初始状态
        Network.getStatus().then((status: ConnectionStatus) => {
            setIsOnline(status.connected);
            setConnectionType(status.connectionType);
        });
        
        // 监听变化
        Network.addListener('networkStatusChange', (status: ConnectionStatus) => {
            setIsOnline(status.connected);
            setConnectionType(status.connectionType);
        });
        
        return () => {
            Network.removeAllListeners();
        };
    }, []);
    
    return {
        isOnline,
        isOffline: !isOnline,
        connectionType
    };
}

五、审批流程 #

5.1 审批Store #

typescript
// src/store/approvalStore.ts
import { create } from 'zustand';
import { db } from '../utils/db';
import { syncService } from '../services/sync.service';
import { useOffline } from '../hooks/useOffline';

interface Approval {
    id: string;
    type: string;
    title: string;
    applicant: { id: string; name: string };
    status: 'pending' | 'approved' | 'rejected';
    currentApprover?: { id: string; name: string };
    createdAt: string;
    content: any;
}

interface ApprovalStore {
    approvals: Approval[];
    loading: boolean;
    
    loadApprovals: () => Promise<void>;
    submitApproval: (data: any) => Promise<void>;
    approve: (id: string, comment: string) => Promise<void>;
    reject: (id: string, reason: string) => Promise<void>;
}

export const useApprovalStore = create<ApprovalStore>((set, get) => ({
    approvals: [],
    loading: false,
    
    loadApprovals: async () => {
        set({ loading: true });
        
        try {
            // 先从本地加载
            const localApprovals = await db.getAllApprovals();
            set({ approvals: localApprovals.map(a => a.data) });
            
            // 如果在线,从服务器同步
            const status = await Network.getStatus();
            if (status.connected) {
                // 同步后更新
            }
        } finally {
            set({ loading: false });
        }
    },
    
    submitApproval: async (data) => {
        const id = `local-${Date.now()}`;
        
        // 保存到本地
        await db.saveApproval(id, {
            ...data,
            status: 'pending',
            createdAt: new Date().toISOString()
        }, 'pending');
        
        // 添加到离线队列
        await syncService.submitOffline('submit_approval', data);
        
        // 更新状态
        set(state => ({
            approvals: [...state.approvals, { id, ...data }]
        }));
    },
    
    approve: async (id, comment) => {
        await syncService.submitOffline('approve', { id, comment });
        
        set(state => ({
            approvals: state.approvals.map(a =>
                a.id === id ? { ...a, status: 'approved' } : a
            )
        }));
    },
    
    reject: async (id, reason) => {
        await syncService.submitOffline('reject', { id, reason });
        
        set(state => ({
            approvals: state.approvals.map(a =>
                a.id === id ? { ...a, status: 'rejected' } : a
            )
        }));
    }
}));

六、总结 #

6.1 企业级特性 #

特性 实现方式
安全认证 生物识别 + Token
离线支持 IndexedDB + 同步队列
数据安全 加密存储
网络监控 Network插件

6.2 最佳实践 #

  • 敏感数据加密存储
  • 完善的离线支持
  • 自动同步机制
  • 企业级安全策略

恭喜你完成了Capacitor完全指南的学习!

最后更新:2026-03-28