个人编制软件展示

PSI - Purchase Sale Inventory 进销存软件

PSI移动端小程序对接指南

移动端应用场景

随着移动办公需求的增长,PSI 进销存软件需要支持手机端操作。常见的移动端应用场景包括:

整体架构设计

┌─────────────────────────────────────────────────────────┐
│                    微信小程序 / App                        │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐     │
│  │扫码出入库│  │库存查询 │  │销售开单 │  │数据看板 │     │
│  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘     │
└───────┼────────────┼────────────┼────────────┼──────────┘
        │            │            │            │
        ▼            ▼            ▼            ▼
┌─────────────────────────────────────────────────────────┐
│                    API 网关 (Nginx)                      │
│            鉴权、限流、路由、SSL 加速                     │
└───────────────────────────┬─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│              PSI 后端服务 (Node.js/Koa)                  │
│  ┌─────────┐  ┌──────────┐  ┌─────────┐  ┌──────────┐  │
│  │ 用户服务 │  │ 商品服务  │  │ 库存服务 │  │ 订单服务  │  │
│  └────┬────┘  └────┬─────┘  └────┬────┘  └────┬─────┘  │
└───────┼────────────┼────────────┼────────────┼─────────┘
        │            │            │            │
        ▼            ▼            ▼            ▼
┌─────────────────────────────────────────────────────────┐
│                   MySQL 数据库集群                       │
└─────────────────────────────────────────────────────────┘

小程序对接实现

1. 小程序主要页面结构

页面 路径 功能说明
首页 pages/index/index 快捷功能、待办事项、数据概览
扫码 pages/scan/scan 扫码识别商品、快速出入库
库存 pages/inventory/inventory 库存查询、库存预警
开单 pages/order/create 创建采购/销售订单
我的 pages/profile/profile 个人信息、设置、退出

2. 小程序请求封装

// utils/request.js - 请求封装
import { getToken } from '../utils/auth';

const BASE_URL = 'https://api.psi.js.cn';

class Request {
  request(url, options = {}) {
    const token = getToken();

    return new Promise((resolve, reject) => {
      wx.request({
        url: BASE_URL + url,
        ...options,
        header: {
          'Content-Type': 'application/json',
          'Authorization': token ? `Bearer ${token}` : '',
          ...options.header
        },
        success: (res) => {
          if (res.statusCode === 200) {
            if (res.data.code === 0) {
              resolve(res.data.data);
            } else if (res.data.code === 401) {
              // token 过期,重新登录
              this.handleUnauthorized();
              reject(res.data);
            } else {
              wx.showToast({
                title: res.data.message || '请求失败',
                icon: 'none'
              });
              reject(res.data);
            }
          } else {
            reject({ message: '服务器错误' });
          }
        },
        fail: (err) => {
          wx.showToast({
            title: '网络请求失败',
            icon: 'none'
          });
          reject(err);
        }
      });
    });
  }

  get(url, params, options) {
    const queryString = new URLSearchParams(params).toString();
    return this.request(url + (queryString ? `?${queryString}` : ''), {
      ...options,
      method: 'GET'
    });
  }

  post(url, data, options) {
    return this.request(url, {
      ...options,
      method: 'POST',
      data
    });
  }

  handleUnauthorized() {
    wx.removeStorageSync('token');
    wx.reLaunch({ url: '/pages/login/login' });
  }
}

export default new Request();

3. 扫码出入库实现

// pages/scan/scan.js
import Request from '../../utils/request';
import { scanProduct } from '../../api/product';

Page({
  data: {
    scanResult: null,
    warehouseList: [],
    warehouseId: '',
    quantity: 1,
    type: 'in'  // 'in' 入库, 'out' 出库
  },

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

  // 扫描条码
  handleScan() {
    wx.scanCode({
      success: (res) => {
        this.queryProduct(res.result);
      },
      fail: (err) => {
        wx.showToast({ title: '扫描失败', icon: 'none' });
      }
    });
  },

  // 根据条码查询商品
  async queryProduct(barcode) {
    wx.showLoading({ title: '查询中...' });
    try {
      const product = await Request.get('/api/product/barcode', { barcode });
      this.setData({
        scanResult: product,
        quantity: 1
      });
    } catch (err) {
      wx.showToast({
        title: '商品未找到',
        icon: 'none'
      });
    } finally {
      wx.hideLoading();
    }
  },

  // 提交出入库
  async handleSubmit() {
    const { scanResult, warehouseId, quantity, type } = this.data;

    if (!scanResult) {
      return wx.showToast({ title: '请先扫描商品', icon: 'none' });
    }
    if (!warehouseId) {
      return wx.showToast({ title: '请选择仓库', icon: 'none' });
    }

    wx.showLoading({ title: '提交中...' });
    try {
      await Request.post('/api/inventory/record', {
        productId: scanResult.id,
        warehouseId,
        quantity,
        type,
        operator: getApp().globalData.userInfo.name
      });

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

      // 重置表单
      this.setData({
        scanResult: null,
        quantity: 1
      });
    } catch (err) {
      wx.showToast({
        title: err.message || '操作失败',
        icon: 'none'
      });
    } finally {
      wx.hideLoading();
    }
  },

  // 加载仓库列表
  async loadWarehouses() {
    try {
      const list = await Request.get('/api/warehouse/list');
      this.setData({
        warehouseList: list,
        warehouseId: list[0]?.id || ''
      });
    } catch (err) {
      console.error('加载仓库失败', err);
    }
  }
});

4. 库存查询页面

// pages/inventory/inventory.js
import Request from '../../utils/request';

Page({
  data: {
    inventoryList: [],
    searchKeyword: '',
    loading: false,
    page: 1,
    pageSize: 20,
    hasMore: true
  },

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

  // 下拉刷新
  async onPullDownRefresh() {
    this.setData({ page: 1, inventoryList: [], hasMore: true });
    await this.loadInventory();
    wx.stopPullDownRefresh();
  },

  // 上拉加载更多
  async onReachBottom() {
    if (!this.data.hasMore) return;
    this.setData({ page: this.data.page + 1 });
    await this.loadInventory();
  },

  // 搜索
  onSearch(e) {
    this.setData({
      searchKeyword: e.detail.value,
      page: 1,
      inventoryList: [],
      hasMore: true
    });
    this.loadInventory();
  },

  async loadInventory() {
    const { page, pageSize, searchKeyword } = this.data;

    this.setData({ loading: true });
    try {
      const list = await Request.get('/api/inventory/list', {
        page,
        pageSize,
        keyword: searchKeyword
      });

      this.setData({
        inventoryList: page === 1
          ? list
          : [...this.data.inventoryList, ...list],
        hasMore: list.length >= pageSize
      });
    } catch (err) {
      wx.showToast({ title: '加载失败', icon: 'none' });
    } finally {
      this.setData({ loading: false });
    }
  },

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

后端 API 接口

接口 说明
GET /api/product/barcode 根据条码查询商品
POST /api/inventory/record 记录出入库
GET /api/inventory/list 库存列表查询
GET /api/warehouse/list 仓库列表

总结

通过小程序对接,PSI 可以实现移动办公的需求。扫码出入库功能特别适合仓库操作人员使用,可以大幅提高工作效率。后端采用 RESTful API 设计,支持未来扩展更多移动端功能。

← 下一篇:PSI 打印模板自定义配置指南