合同管理系统移动端方案:从小程序到React Native的实践
时间:2025-04-23 人气:

一、移动端技术选型

基于业务场景的跨平台方案决策:

1.1 技术方案对比

技术栈开发效率性能表现合同场景适用性典型案例
React Native高(代码复用率80%)接近原生复杂合同审批阿里钉钉
微信小程序极高(生态完善)优秀轻量签署场景腾讯电子签
Flutter中(学习曲线陡)最佳高交互合同编辑字节飞书
原生开发低(双端独立)极致定制签署SDKAdobe Sign

1.2 混合架构方案

移动端架构图

  1. 核心层:React Native容器(iOS/Android)

  2. 业务层:小程序轻量模块(快速迭代)

  3. 能力层:原生插件(电子签名/生物认证)

  4. 桥接层:统一JS Bridge协议

二、React Native实践

合同审批功能的跨平台实现:

2.1 技术栈组合

技术领域核心库合同场景应用版本要求
UI框架React Native 0.72+合同列表/审批流支持新架构
状态管理Zustand全局签署状态v4.0+
导航路由React Navigation 6.x多步骤签署流程支持原生栈
原生能力TurboModules手写签名插件新架构专用

2.2 核心代码实现

合同审批组件:

// ContractApprovalScreen.js
import {useContractStore} from '../stores/contract';

const ContractApprovalScreen = ({route}) => {
  const {contractId} = route.params;
  const {approveContract, rejectContract} = useContractStore();
  const [comment, setComment] = useState('');

  const handleApprove = async () => {
    try {
      await approveContract(contractId, comment);
      navigation.replace('ApprovalResult', {success: true});
    } catch (error) {
      Alert.alert('审批失败', error.message);
    }
  };

  return (
    <ScrollView contentContainerStyle={styles.container}>
      <ContractViewer contractId={contractId} />
      
      <TextInput
        placeholder="请输入审批意见"
        value={comment}
        onChangeText={setComment}
        style={styles.commentInput}
      />

      <View style={styles.buttonGroup}>
        <Button 
          title="拒绝" 
          onPress={rejectContract} 
          color="red"
        />
        <Button 
          title="同意" 
          onPress={handleApprove}
          color="green" 
        />
      </View>
    </ScrollView>
  );
};

// 手写签名原生模块
import {NativeModules} from 'react-native';
const {SignatureCapture} = NativeModules;

const SignaturePad = ({onSave}) => {
  const showSignPad = async () => {
    try {
      const {path, width, height} = await SignatureCapture.open();
      onSave({uri: `file://${path}`, width, height});
    } catch (err) {
      console.error('签名失败:', err);
    }
  };

  return (
    <TouchableOpacity onPress={showSignPad}>
      <View style={styles.signPlaceholder}>
        <Text>点击签名</Text>
      </View>
    </TouchableOpacity>
  );
};

TurboModule原生模块:

// SignatureCaptureModule.h (iOS)
#import <React/RCTBridgeModule.h>
@interface SignatureCaptureModule : NSObject <RCTTurboModule>
@end

// SignatureCaptureModule.mm
@implementation SignatureCaptureModule {
  RCTPromiseResolveBlock _resolve;
}

RCT_EXPORT_MODULE()

RCT_EXPORT_METHOD(open:(RCTPromiseResolveBlock)resolve 
                 reject:(RCTPromiseRejectBlock)reject) {
  dispatch_async(dispatch_get_main_queue(), ^{
    self->_resolve = resolve;
    SignatureVC *vc = [[SignatureVC alloc] init];
    vc.delegate = self;
    [RCTPresentedViewController() presentViewController:vc animated:YES completion:nil];
  });
}

- (void)signatureSaved:(NSString *)path width:(NSInteger)width height:(NSInteger)height {
  if (_resolve) {
    _resolve(@{@"path": path, @"width": @(width), @"height": @(height)});
    _resolve = nil;
  }
}

@end

三、小程序集成

微信生态下的轻量签署方案:

3.1 小程序能力矩阵

小程序API合同功能实现方案注意事项
实时音视频面签见证TRTC插件企业主体认证
生物认证签署人核身wx.startSoterAuthAndroid兼容性
文件系统合同缓存FileSystemManager100MB限制
云开发签署状态同步云数据库配额管理

3.2 签署功能实现

电子签名页面:

// pages/sign/index.wxml
<view class="container">
  <canvas canvas-id="signPad" 
          disable-scroll
          bindtouchstart="onTouchStart"
          bindtouchmove="onTouchMove"
          bindtouchend="onTouchEnd"></canvas>
  
  <button type="primary" bindtap="confirmSign">确认签署</button>
</view>

// pages/sign/index.js
Page({
  data: { strokes: [] },
  
  onTouchStart(e) {
    this.setData({ 
      strokes: [...this.data.strokes, {
        points: [{x: e.touches[0].x, y: e.touches[0].y}],
        color: '#000',
        width: 2
      }]
    });
    this.drawSign();
  },
  
  onTouchMove(e) {
    const lastStroke = this.data.strokes[this.data.strokes.length-1];
    lastStroke.points.push({x: e.touches[0].x, y: e.touches[0].y});
    this.drawSign();
  },
  
  drawSign() {
    const ctx = wx.createCanvasContext('signPad');
    ctx.setFillStyle('#fff');
    ctx.fillRect(0, 0, 300, 150);
    
    this.data.strokes.forEach(stroke => {
      ctx.beginPath();
      ctx.moveTo(stroke.points[0].x, stroke.points[0].y);
      stroke.points.forEach(p => ctx.lineTo(p.x, p.y));
      ctx.setStrokeStyle(stroke.color);
      ctx.setLineWidth(stroke.width);
      ctx.stroke();
    });
    
    ctx.draw();
  },
  
  async confirmSign() {
    try {
      // 1. 保存签名图片
      const {tempFilePath} = await new Promise(resolve => {
        wx.canvasToTempFilePath({
          canvasId: 'signPad',
          success: resolve
        });
      });
      
      // 2. 调用生物认证
      const res = await wx.startSoterAuth({
        requestAuthModes: ['fingerPrint'],
        challenge: 'contract-' + this.data.contractId,
        authContent: '请验证指纹确认签署'
      });
      
      // 3. 提交签署
      wx.cloud.callFunction({
        name: 'confirmSign',
        data: {
          contractId: this.data.contractId,
          signImage: tempFilePath,
          authResult: res.resultJSON
        }
      });
      
      wx.navigateTo({url: '/pages/sign/success'});
    } catch (err) {
      wx.showToast({title: '签署失败', icon: 'error'});
    }
  }
})

云函数签署验证:

// 云函数confirmSign
const cloud = require('wx-server-sdk')
cloud.init({env: cloud.DYNAMIC_CURRENT_ENV})

exports.main = async (event, context) => {
  const {contractId, signImage, authResult} = event;
  
  // 1. 验证生物认证结果
  const authData = JSON.parse(authResult);
  if (authData.rawAuthMsg !== md5('contract-' + contractId)) {
    throw new Error('认证信息不匹配');
  }
  
  // 2. 上传签名图片到OSS
  const fileRes = await cloud.uploadFile({
    cloudPath: `signatures/${Date.now()}.png`,
    fileContent: Buffer.from(signImage, 'base64')
  });
  
  // 3. 更新合同状态
  const db = cloud.database();
  await db.collection('contracts').doc(contractId).update({
    data: {
      status: 'signed',
      signTime: db.serverDate(),
      signImage: fileRes.fileID
    }
  });
  
  // 4. 发送签署通知
  await cloud.openapi.subscribeMessage.send({
    touser: context.OPENID,
    templateId: 'SIGN_RESULT_TEMPLATE',
    data: {
      thing1: {value: '合同签署完成'},
      time2: {value: new Date().toLocaleString()}
    }
  });
  
  return {success: true};
}

四、离线同步方案

弱网环境下的合同数据一致性保障:

4.1 同步策略对比

同步模式实现技术合同场景用例数据一致性
全量同步REST API首次加载强一致
增量同步WebSocket实时状态更新最终一致
冲突解决版本向量多端编辑人工介入
本地优先SQLite+CRDT离线签署自动合并

4.2 离线签署实现

React Native离线存储:

// 使用WatermelonDB实现本地存储
import {Database} from '@nozbe/watermelondb';
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite';
import {Contract, ContractComment} from './models';

const adapter = new SQLiteAdapter({
  schema: [
    Contract.schema,
    ContractComment.schema
  ],
  dbName: 'ContractDB',
});

export const database = new Database({
  adapter,
  modelClasses: [Contract, ContractComment],
});

// Contract模型定义
export default class Contract extends Model {
  static table = 'contracts';
  static associations = {
    comments: {type: 'has_many', foreignKey: 'contract_id'},
  };
  
  @field('title') title;
  @field('content') content;
  @field('status') status;
  @field('last_sync_at') lastSyncAt;
  
  @children('comments') comments;
  
  async syncWithServer() {
    const lastSync = this.lastSyncAt || 0;
    const response = await api.get(`/contracts/${this.id}/changes?since=${lastSync}`);
    
    await this.batch(() => {
      this.update(contract => {
        for (const key in response.data) {
          contract[key] = response.data[key];
        }
        contract.lastSyncAt = Date.now();
      });
      
      for (const comment of response.comments) {
        this.collections.get('comments').prepareCreate(c => {
          c.text = comment.text;
          c.author = comment.author;
        });
      }
    });
    
    return this;
  }
}

离线操作队列:

// 操作队列服务
class OperationQueue {
  constructor() {
    this.queue = [];
    this.isProcessing = false;
    this.db = new Database();
  }
  
  async addOperation(type, payload) {
    const op = {id: uuid(), type, payload, status: 'pending'};
    await this.db.save('operations', op);
    this.processQueue();
    return op;
  }
  
  async processQueue() {
    if (this.isProcessing) return;
    
    this.isProcessing = true;
    const operations = await this.db.getAll('operations', {status: 'pending'});
    
    for (const op of operations) {
      try {
        await api.post('/operations', {
          type: op.type,
          payload: op.payload
        });
        
        await this.db.update('operations', op.id, {status: 'completed'});
      } catch (err) {
        if (err.isNetworkError) {
          break; // 网络恢复后重试
        }
        await this.db.update('operations', op.id, {status: 'failed'});
      }
    }
    
    this.isProcessing = false;
    if (await this.db.count('operations', {status: 'pending'}) > 0) {
      setTimeout(() => this.processQueue(), 5000);
    }
  }
}

// 离线签署示例
const queue = new OperationQueue();
await queue.addOperation('sign_contract', {
  contractId: '123',
  signature: 'base64-image',
  timestamp: Date.now()
});

五、移动端工具包

开箱即用的移动端开发资源集合:

5.1 推荐工具集

开发领域开源方案商业产品合同场景适用
跨平台框架React NativeFlutter核心审批功能
小程序框架Tarouni-app轻量签署场景
离线存储WatermelonDBRealm合同本地缓存

5.2 开发资源包

▶ 免费获取资源:

关注「移动开发生态」公众号领取:
               • 《React Native签名组件源码》
               • 小程序电子签模板
               • 离线同步方案白皮书

上一篇:没有了
山西肇新科技logo

山西肇新科技

专注于提供合同管理领域,做最专业的合同管理解决方案。

备案号:晋ICP备2021020298号-1 晋公网安备 14010502051117号

请备注咨询合同系统