个人编制软件展示

PSI - Purchase Sale Inventory 进销存软件

PSI进销存系统移动端小程序开发实战

移动端方案概述

PSI进销存系统支持多种移动端接入方式,包括微信小程序、H5移动端和原生APP。本文介绍移动端小程序的设计与实现,帮助企业实现随时随地的业务管理。

技术架构

移动端采用小程序 + H5混合开发模式:

终端类型 技术方案 适用场景
微信小程序 Taro/uni-app 快速上手、微信生态
H5移动端 Vue3 + Vant 跨平台、无需审核
原生APP Flutter/React Native 性能要求高、离线使用

微信小程序开发

项目结构

psi-miniapp/
├── src/
│   ├── pages/
│   │   ├── dashboard/         # 首页/控制台
│   │   ├── inventory/         # 库存管理
│   │   ├── purchase/          # 采购管理
│   │   ├── sales/             # 销售管理
│   │   ├── orders/            # 订单列表
│   │   ├── scan/              # 扫码功能
│   │   └── settings/          # 设置
│   ├── components/
│   │   ├── product-card/      # 商品卡片
│   │   ├── order-item/        # 订单项
│   │   ├── number-input/      # 数字输入
│   │   └── scan-button/       # 扫码按钮
│   ├── services/
│   │   ├── api.js             # API封装
│   │   ├── auth.js            # 认证服务
│   │   └── storage.js         # 本地存储
│   ├── utils/
│   │   ├── format.js          # 格式化工具
│   │   └── validation.js      # 校验工具
│   └── app.js
├── project.config.json
└── package.json

API服务封装

// services/api.js
const API_BASE_URL = 'https://api.psi.example.com';
const TOKEN_KEY = 'psi_token';

class ApiService {
  request(options) {
    const token = wx.getStorageSync(TOKEN_KEY);

    return new Promise((resolve, reject) => {
      wx.request({
        url: API_BASE_URL + options.url,
        method: options.method || 'GET',
        data: options.data || {},
        header: {
          'Content-Type': 'application/json',
          'Authorization': token ? `Bearer ${token}` : '',
          ...options.header
        },
        success: (res) => {
          if (res.statusCode === 200) {
            resolve(res.data);
          } else if (res.statusCode === 401) {
            // token过期,重新登录
            this.handleUnauthorized();
            reject(new Error('未授权'));
          } else {
            reject(res.data);
          }
        },
        fail: (err) => {
          wx.showToast({
            title: '网络请求失败',
            icon: 'none'
          });
          reject(err);
        }
      });
    });
  }

  // 登录
  login(code) {
    return this.request({
      url: '/auth/login',
      method: 'POST',
      data: { code }
    });
  }

  // 获取商品列表
  getProducts(params) {
    return this.request({
      url: '/products',
      data: params
    });
  }

  // 创建销售订单
  createSalesOrder(data) {
    return this.request({
      url: '/sales-orders',
      method: 'POST',
      data
    });
  }

  // 扫码查询商品
  scanProduct(barcode) {
    return this.request({
      url: '/products/barcode/' + barcode
    });
  }
}

export default new ApiService();

扫码出入库功能

// pages/scan/index.js
import ApiService from '../../services/api';

Page({
  data: {
    scanHistory: [],
    currentProduct: null,
    mode: 'in' // in:入库, out:出库
  },

  // 扫码
  async handleScan() {
    try {
      const result = await wx.scanCode({
        onlyFromCamera: true,
        scanType: ['barCode', 'qrCode']
      });

      wx.showLoading({ title: '查询中...' });

      const product = await ApiService.scanProduct(result.result);

      this.setData({
        currentProduct: product,
        scanHistory: [product, ...this.data.scanHistory.slice(0, 9)]
      });

      wx.hideLoading();
    } catch (err) {
      wx.showModal({
        title: '提示',
        content: '未找到对应商品,是否手动输入?',
        success: (res) => {
          if (res.confirm) {
            this.showManualInput();
          }
        }
      });
    }
  },

  // 确认出入库
  async confirmStockChange() {
    const { currentProduct, mode, quantity } = this.data;

    if (!quantity || quantity <= 0) {
      wx.showToast({
        title: '请输入有效数量',
        icon: 'none'
      });
      return;
    }

    try {
      wx.showLoading({ title: '处理中...' });

      const apiName = mode === 'in' ? 'stockIn' : 'stockOut';
      await ApiService[apiName]({
        productId: currentProduct.id,
        quantity: parseInt(quantity),
        warehouseId: this.data.warehouseId,
        remark: this.data.remark
      });

      wx.showToast({
        title: mode === 'in' ? '入库成功' : '出库成功',
        icon: 'success'
      });

      // 清空当前商品
      this.setData({
        currentProduct: null,
        quantity: ''
      });

      // 触发库存页面刷新
      const pages = getCurrentPages();
      const inventoryPage = pages.find(p => p.route === 'pages/inventory/index');
      if (inventoryPage) {
        inventoryPage.onShow();
      }
    } catch (err) {
      wx.showToast({
        title: err.message || '操作失败',
        icon: 'none'
      });
    } finally {
      wx.hideLoading();
    }
  },

  // 切换模式
  switchMode(e) {
    this.setData({
      mode: e.currentTarget.dataset.mode
    });
  }
});

离线数据同步

// services/sync.js
class OfflineSync {
  constructor() {
    this.pendingRequests = []; // 待同步请求
    this.isSyncing = false;
  }

  // 保存离线操作
  saveOfflineOperation(type, data) {
    const operation = {
      id: this.generateId(),
      type,
      data,
      timestamp: Date.now(),
      status: 'pending'
    };

    // 保存到本地存储
    const pending = wx.getStorageSync('pending_operations') || [];
    pending.push(operation);
    wx.setStorageSync('pending_operations', pending);

    // 尝试同步
    this.trySync();
  }

  // 同步离线数据
  async trySync() {
    if (this.isSyncing) return;

    const pending = wx.getStorageSync('pending_operations') || [];
    if (pending.length === 0) return;

    // 检查网络状态
    const networkType = await this.getNetworkType();
    if (networkType === 'none') {
      console.log('无网络,延迟同步');
      return;
    }

    this.isSyncing = true;

    for (const operation of pending) {
      try {
        await this.syncOperation(operation);
        // 同步成功,移除
        this.removePendingOperation(operation.id);
      } catch (err) {
        console.error('同步失败:', err);
        // 标记失败
        this.markOperationFailed(operation.id, err.message);
      }
    }

    this.isSyncing = false;

    // 通知UI更新
    wx.eventCenter.trigger('sync_complete');
  }

  // 同步单个操作
  async syncOperation(operation) {
    const apiMap = {
      'stock_in': ApiService.stockIn,
      'stock_out': ApiService.stockOut,
      'order_create': ApiService.createOrder,
      'order_update': ApiService.updateOrder
    };

    const apiFunc = apiMap[operation.type];
    if (!apiFunc) {
      throw new Error(`未知操作类型: ${operation.type}`);
    }

    return await apiFunc(operation.data);
  }

  // 获取网络状态
  getNetworkType() {
    return new Promise((resolve) => {
      wx.getNetworkType({
        success: (res) => resolve(res.networkType)
      });
    });
  }

  // 监听网络状态变化
  watchNetworkChange() {
    wx.onNetworkStatusChange((res) => {
      if (res.isConnected) {
        this.trySync();
      }
    });
  }
}

移动端库存查询

// pages/inventory/index.js
Page({
  data: {
    inventoryList: [],
    warehouseList: [],
    currentWarehouse: null,
    searchKeyword: '',
    loading: false,
    refreshing: false,
    page: 1,
    pageSize: 20,
    hasMore: true
  },

  onLoad() {
    this.loadWarehouses();
  },

  onShow() {
    this.loadInventory();
  },

  // 加载仓库列表
  async loadWarehouses() {
    try {
      const warehouses = await ApiService.getWarehouses();
      this.setData({
        warehouseList: warehouses,
        currentWarehouse: warehouses[0]
      });
    } catch (err) {
      console.error('加载仓库失败:', err);
    }
  },

  // 加载库存数据
  async loadInventory(loadMore = false) {
    if (!loadMore) {
      this.setData({ page: 1, loading: true });
    }

    try {
      const params = {
        warehouseId: this.data.currentWarehouse?.id,
        keyword: this.data.searchKeyword,
        page: this.data.page,
        pageSize: this.data.pageSize
      };

      const result = await ApiService.getInventory(params);

      this.setData({
        inventoryList: loadMore
          ? [...this.data.inventoryList, ...result.items]
          : result.items,
        hasMore: result.hasMore,
        page: this.data.page + 1,
        loading: false,
        refreshing: false
      });
    } catch (err) {
      this.setData({ loading: false, refreshing: false });
      wx.showToast({
        title: '加载失败',
        icon: 'none'
      });
    }
  },

  // 下拉刷新
  onPullDownRefresh() {
    this.setData({ refreshing: true });
    this.loadInventory();
  },

  // 上拉加载更多
  onReachBottom() {
    if (this.data.hasMore && !this.data.loading) {
      this.loadInventory(true);
    }
  },

  // 切换仓库
  switchWarehouse(e) {
    const warehouse = e.currentTarget.dataset.warehouse;
    this.setData({ currentWarehouse: warehouse });
    this.loadInventory();
  },

  // 搜索
  onSearch(e) {
    this.setData({ searchKeyword: e.detail.value });
    this.loadInventory();
  },

  // 查看商品详情
  goToDetail(e) {
    const productId = e.currentTarget.dataset.id;
    wx.navigateTo({
      url: `/pages/product/detail?id=${productId}`
    });
  }
});

消息推送集成

集成微信消息推送,实现业务提醒:

// services/notify.js
class NotifyService {
  // 订阅消息
  async subscribeMessage(templateId) {
    return new Promise((resolve, reject) => {
      wx.requestSubscribeMessage({
        tmplIds: [templateId],
        success: (res) => {
          resolve(res[templateId]);
        },
        fail: reject
      });
    });
  }

  // 订单状态变化通知
  async notifyOrderStatus(order, status) {
    const templateId = 'ORDER_STATUS_TEMPLATE_ID';

    const result = await this.subscribeMessage(templateId);
    if (result !== 'accept') return;

    await ApiService.sendNotify({
      templateId,
      data: {
        thing1: { value: order.orderNo },
        phrase2: { value: this.getStatusText(status) },
        time3: { value: new Date().toLocaleString() }
      },
      page: `/pages/order/detail?id=${order.id}`
    });
  }

  getStatusText(status) {
    const map = {
      pending: '待审核',
      approved: '已审核',
      processing: '处理中',
      completed: '已完成',
      cancelled: '已取消'
    };
    return map[status] || status;
  }

  // 库存预警通知
  async notifyLowStock(product, currentStock) {
    await ApiService.sendNotify({
      templateId: 'LOW_STOCK_TEMPLATE_ID',
      data: {
        thing1: { value: product.name },
        number2: { value: currentStock },
        thing3: { value: `仓库: ${product.warehouseName}` }
      },
      page: `/pages/product/detail?id=${product.id}`
    });
  }
}

性能优化

总结

PSI移动端小程序提供了完整的进销存管理功能,包括扫码出入库、库存查询、订单管理等。通过离线数据同步和消息推送机制,确保用户在各种网络环境下都能高效工作。

← 下一篇:PSI云端部署与数据安全方案 上篇:PSI进销存系统用户行为分析与数据挖掘 →