从零开始构建新零售供应链采购审批流程界面——React与原生HTML表单的完美融合

举报
叶一一 发表于 2025/12/20 15:46:58 2025/12/20
【摘要】 引言采购审批流程作为供应链的第一道关口,不仅需要保证合规性和安全性,还要兼顾操作便捷性和用户体验。本文将带领大家从零开始,采用现代前端技术栈,结合HTML表单的原生特性和React组件化思想,打造一个高效、美观且易于维护的采购审批界面。一、项目架构设计与技术选型1.1 整体架构规划在开始编码之前,我们需要明确项目的整体架构。采购审批系统主要包括以下几个核心模块:采购审批系统登录认证模块采购申...

引言

采购审批流程作为供应链的第一道关口,不仅需要保证合规性和安全性,还要兼顾操作便捷性和用户体验。

本文将带领大家从零开始,采用现代前端技术栈,结合HTML表单的原生特性和React组件化思想,打造一个高效、美观且易于维护的采购审批界面。

一、项目架构设计与技术选型

1.1 整体架构规划

在开始编码之前,我们需要明确项目的整体架构。采购审批系统主要包括以下几个核心模块:

  • 采购审批系统
    • 登录认证模块
    • 采购申请模块
      • 基本信息填写
      • 商品明细录入
      • 附件上传
    • 审批流程模块
      • 一级审批
      • 二级审批
      • 最终确认
    • 数据展示模块
    • 通知提醒模块

1.2 技术栈选择理由

我们选择React作为主要框架,原因如下:

  • 组件化开发模式提高代码复用性
  • 虚拟DOM提升页面渲染性能
  • 丰富的生态系统支持快速开发
  • 原生HTML表单功能保证数据验证可靠性

二、基础环境搭建与项目初始化

首先创建React项目并安装必要依赖:

npx create-react-app procurement-approval-system
cd procurement-approval-system
npm start

清理默认模板文件后,建立基本目录结构:

src/
├── components/          # 公共组件
├── pages/              # 页面组件
├── styles/             # 样式文件
├── utils/              # 工具函数
└── App.js

三、核心组件设计与实现

3.1 采购申请表单组件

让我们从最核心的采购申请表单开始。这个组件将充分利用HTML5表单验证特性,并结合React的状态管理。

import React, { useState } from 'react';
import '../styles/ProcurementForm.css';

const ProcurementForm = () => {
  // 表单状态管理
  const [formData, setFormData] = useState({
    applicant: '',
    department: '',
    applyDate: '',
    expectedDeliveryDate: '',
    supplier: '',
    totalAmount: '',
    description: ''
  });

  // 商品明细状态
  const [items, setItems] = useState([
    { id: 1, name: '', quantity: '', unitPrice: '', totalPrice: '' }
  ]);

  // 处理输入变化
  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  // 处理商品明细变化
  const handleItemChange = (id, field, value) => {
    setItems(prev => prev.map(item => {
      if (item.id === id) {
        const updatedItem = { ...item, [field]: value };
        
        // 自动计算总价
        if (field === 'quantity' || field === 'unitPrice') {
          const quantity = field === 'quantity' ? value : item.quantity;
          const unitPrice = field === 'unitPrice' ? value : item.unitPrice;
          updatedItem.totalPrice = (quantity * unitPrice).toFixed(2);
        }
        
        return updatedItem;
      }
      return item;
    }));
  };

  // 添加新商品项
  const addItem = () => {
    setItems(prev => [
      ...prev,
      { 
        id: prev.length > 0 ? Math.max(...prev.map(i => i.id)) + 1 : 1,
        name: '', 
        quantity: '', 
        unitPrice: '', 
        totalPrice: '' 
      }
    ]);
  };

  // 删除商品项
  const removeItem = (id) => {
    if (items.length > 1) {
      setItems(prev => prev.filter(item => item.id !== id));
    }
  };

  // 提交表单
  const handleSubmit = (e) => {
    e.preventDefault();
    // 这里可以添加表单提交逻辑
    console.log('提交数据:', { ...formData, items });
  };

  return (
    <form className="procurement-form" onSubmit={handleSubmit}>
      <div className="form-section">
        <h2>基础信息</h2>
        <div className="form-row">
          <div className="form-group">
            <label htmlFor="applicant">申请人 *</label>
            <input
              type="text"
              id="applicant"
              name="applicant"
              value={formData.applicant}
              onChange={handleInputChange}
              required
            />
          </div>
          
          <div className="form-group">
            <label htmlFor="department">所属部门 *</label>
            <select
              id="department"
              name="department"
              value={formData.department}
              onChange={handleInputChange}
              required
            >
              <option value="">请选择部门</option>
              <option value="采购部">采购部</option>
              <option value="运营部">运营部</option>
              <option value="市场部">市场部</option>
              <option value="财务部">财务部</option>
            </select>
          </div>
        </div>

        <div className="form-row">
          <div className="form-group">
            <label htmlFor="applyDate">申请日期 *</label>
            <input
              type="date"
              id="applyDate"
              name="applyDate"
              value={formData.applyDate}
              onChange={handleInputChange}
              required
            />
          </div>
          
          <div className="form-group">
            <label htmlFor="expectedDeliveryDate">期望到货日期 *</label>
            <input
              type="date"
              id="expectedDeliveryDate"
              name="expectedDeliveryDate"
              value={formData.expectedDeliveryDate}
              onChange={handleInputChange}
              required
            />
          </div>
        </div>

        <div className="form-row">
          <div className="form-group">
            <label htmlFor="supplier">供应商 *</label>
            <input
              type="text"
              id="supplier"
              name="supplier"
              value={formData.supplier}
              onChange={handleInputChange}
              required
            />
          </div>
          
          <div className="form-group">
            <label htmlFor="totalAmount">总金额</label>
            <input
              type="number"
              id="totalAmount"
              name="totalAmount"
              value={formData.totalAmount}
              onChange={handleInputChange}
              readOnly
            />
          </div>
        </div>

        <div className="form-group full-width">
          <label htmlFor="description">采购说明</label>
          <textarea
            id="description"
            name="description"
            value={formData.description}
            onChange={handleInputChange}
            rows="3"
          />
        </div>
      </div>

      <div className="form-section">
        <div className="section-header">
          <h2>商品明细</h2>
          <button type="button" onClick={addItem} className="add-item-btn">
            + 添加商品
          </button>
        </div>
        
        <div className="items-table">
          <div className="table-header">
            <div className="table-cell">商品名称</div>
            <div className="table-cell">数量</div>
            <div className="table-cell">单价</div>
            <div className="table-cell">总价</div>
            <div className="table-cell">操作</div>
          </div>
          
          {items.map((item) => (
            <div key={item.id} className="table-row">
              <div className="table-cell">
                <input
                  type="text"
                  value={item.name}
                  onChange={(e) => handleItemChange(item.id, 'name', e.target.value)}
                  required
                />
              </div>
              <div className="table-cell">
                <input
                  type="number"
                  value={item.quantity}
                  onChange={(e) => handleItemChange(item.id, 'quantity', e.target.value)}
                  required
                />
              </div>
              <div className="table-cell">
                <input
                  type="number"
                  step="0.01"
                  value={item.unitPrice}
                  onChange={(e) => handleItemChange(item.id, 'unitPrice', e.target.value)}
                  required
                />
              </div>
              <div className="table-cell">
                <input
                  type="text"
                  value={item.totalPrice}
                  readOnly
                />
              </div>
              <div className="table-cell">
                <button 
                  type="button" 
                  onClick={() => removeItem(item.id)}
                  disabled={items.length <= 1}
                  className="remove-btn"
                >
                  删除
                </button>
              </div>
            </div>
          ))}
        </div>
      </div>

      <div className="form-actions">
        <button type="button" className="cancel-btn">取消</button>
        <button type="submit" className="submit-btn">提交申请</button>
      </div>
    </form>
  );
};

export default ProcurementForm;

这段代码实现了完整的采购申请表单,具有以下特点:

  • 状态管理:使用useState钩子管理表单数据和商品明细
  • 动态表单:支持动态添加和删除商品项
  • 自动计算:商品数量和单价变化时自动计算总价
  • 表单验证:利用HTML5原生验证属性确保必填项

3.2 审批流程组件

接下来实现审批流程组件,展示审批状态和操作按钮:

import React, { useState } from 'react';
import '../styles/ApprovalFlow.css';

const ApprovalFlow = () => {
  const [currentStep, setCurrentStep] = useState(1);
  const [approvals, setApprovals] = useState([
    { id: 1, name: '部门主管', status: 'pending', approved: false },
    { id: 2, name: '财务经理', status: 'pending', approved: false },
    { id: 3, name: '总经理', status: 'pending', approved: false }
  ]);

  const handleApprove = (id) => {
    setApprovals(prev => prev.map(approval => 
      approval.id === id 
        ? { ...approval, status: 'approved', approved: true } 
        : approval
    ));
    
    // 如果不是最后一个审批人,则进入下一步
    if (id < approvals.length) {
      setCurrentStep(id + 1);
    }
  };

  const handleReject = (id) => {
    setApprovals(prev => prev.map(approval => 
      approval.id === id 
        ? { ...approval, status: 'rejected' } 
        : approval
    ));
  };

  return (
    <div className="approval-flow">
      <h2>审批流程</h2>
      
      <div className="progress-tracker">
        {approvals.map((approval, index) => (
          <div key={approval.id} className="step">
            <div className={`step-icon ${approval.status}`}>
              {approval.status === 'approved' ? '✓' : 
               approval.status === 'rejected' ? '✗' : 
               index + 1}
            </div>
            <div className="step-label">{approval.name}</div>
            <div className="step-status">{approval.status === 'approved' ? '已批准' :
                                          approval.status === 'rejected' ? '已拒绝' : 
                                          '待审批'}</div>
          </div>
        ))}
      </div>

      <div className="current-approval">
        <h3>当前审批</h3>
        {approvals
          .filter(approval => approval.id === currentStep)
          .map(approval => (
            <div key={approval.id} className="approval-card">
              <div className="approval-info">
                <h4>{approval.name}</h4>
                <p>请审核采购申请并做出决定</p>
              </div>
              <div className="approval-actions">
                <button 
                  className="approve-btn"
                  onClick={() => handleApprove(approval.id)}
                  disabled={approval.status !== 'pending'}
                >
                  批准
                </button>
                <button 
                  className="reject-btn"
                  onClick={() => handleReject(approval.id)}
                  disabled={approval.status !== 'pending'}
                >
                  拒绝
                </button>
              </div>
            </div>
          ))
        }
      </div>
    </div>
  );
};

export default ApprovalFlow;

四、样式设计与美化

4.1 采购表单样式

.procurement-form {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.form-section {
  margin-bottom: 30px;
  padding: 20px;
  border: 1px solid #e0e0e0;
  border-radius: 6px;
}

.form-section h2 {
  margin-top: 0;
  color: #333;
  border-bottom: 2px solid #007bff;
  padding-bottom: 10px;
}

.form-row {
  display: flex;
  gap: 20px;
  margin-bottom: 15px;
}

.form-group {
  flex: 1;
}

.form-group.full-width {
  flex: 1 0 100%;
}

.form-group label {
  display: block;
  margin-bottom: 5px;
  font-weight: 500;
  color: #555;
}

.form-group input,
.form-group select,
.form-group textarea {
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
  transition: border-color 0.3s;
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
  outline: none;
  border-color: #007bff;
  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}

.form-group input:invalid {
  border-color: #dc3545;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.add-item-btn {
  background: #28a745;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}

.add-item-btn:hover {
  background: #218838;
}

.items-table {
  border: 1px solid #ddd;
  border-radius: 4px;
  overflow: hidden;
}

.table-header {
  display: flex;
  background: #f8f9fa;
  font-weight: bold;
  border-bottom: 1px solid #ddd;
}

.table-row {
  display: flex;
  border-bottom: 1px solid #eee;
}

.table-row:last-child {
  border-bottom: none;
}

.table-cell {
  flex: 1;
  padding: 12px;
  display: flex;
  align-items: center;
}

.table-cell input {
  width: 100%;
  padding: 6px;
  border: 1px solid #ddd;
  border-radius: 3px;
}

.remove-btn {
  background: #dc3545;
  color: white;
  border: none;
  padding: 6px 12px;
  border-radius: 3px;
  cursor: pointer;
  font-size: 12px;
}

.remove-btn:hover:not(:disabled) {
  background: #c82333;
}

.remove-btn:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 15px;
  margin-top: 30px;
}

.cancel-btn, .submit-btn {
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

.cancel-btn {
  background: #6c757d;
  color: white;
}

.cancel-btn:hover {
  background: #5a6268;
}

.submit-btn {
  background: #007bff;
  color: white;
}

.submit-btn:hover {
  background: #0069d9;
}

@media (max-width: 768px) {
  .form-row {
    flex-direction: column;
    gap: 10px;
  }
  
  .table-header,
  .table-row {
    flex-wrap: wrap;
  }
  
  .table-cell {
    flex: 0 0 50%;
  }
}

4.2 审批流程样式

.approval-flow {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.progress-tracker {
  display: flex;
  justify-content: space-between;
  margin: 30px 0;
  position: relative;
}

.progress-tracker::before {
  content: '';
  position: absolute;
  top: 20px;
  left: 0;
  right: 0;
  height: 2px;
  background: #e0e0e0;
  z-index: 1;
}

.step {
  text-align: center;
  position: relative;
  z-index: 2;
}

.step-icon {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 10px;
  font-weight: bold;
  color: #fff;
}

.step-icon.pending {
  background: #6c757d;
}

.step-icon.approved {
  background: #28a745;
}

.step-icon.rejected {
  background: #dc3545;
}

.step-label {
  font-weight: 500;
  margin-bottom: 5px;
}

.step-status {
  font-size: 14px;
  color: #666;
}

.current-approval {
  margin-top: 30px;
  padding: 20px;
  background: #f8f9fa;
  border-radius: 6px;
}

.approval-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px;
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.approval-info h4 {
  margin: 0 0 5px 0;
  color: #333;
}

.approval-info p {
  margin: 0;
  color: #666;
}

.approval-actions {
  display: flex;
  gap: 10px;
}

.approve-btn, .reject-btn {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}

.approve-btn {
  background: #28a745;
  color: white;
}

.approve-btn:hover:not(:disabled) {
  background: #218838;
}

.reject-btn {
  background: #dc3545;
  color: white;
}

.reject-btn:hover:not(:disabled) {
  background: #c82333;
}

.approve-btn:disabled,
.reject-btn:disabled {
  background: #ccc;
  cursor: not-allowed;
}

五、主应用组件整合

现在让我们创建主应用组件,整合所有功能模块:

import React from 'react';
import ProcurementForm from './components/ProcurementForm';
import ApprovalFlow from './components/ApprovalFlow';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="app-header">
        <h1>新零售供应链采购审批系统</h1>
      </header>
      
      <main className="app-main">
        <div className="container">
          <ProcurementForm />
          <ApprovalFlow />
        </div>
      </main>
    </div>
  );
}

export default App;
css

/* src/App.css */
.App {
  min-height: 100vh;
  background: #f5f5f5;
}

.app-header {
  background: #007bff;
  color: white;
  padding: 20px 0;
  text-align: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.app-header h1 {
  margin: 0;
  font-size: 24px;
}

.app-main {
  padding: 30px 0;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
}

@media (max-width: 768px) {
  .app-header h1 {
    font-size: 20px;
  }
  
  .app-main {
    padding: 20px 0;
  }
}

总结

本文从零开始构建了一个完整的新零售供应链采购审批界面,涵盖了以下关键技术要点:

  • 表单设计:充分利用HTML5原生表单验证功能,确保数据的有效性和完整性
  • 状态管理:合理运用React的useState钩子管理复杂表单状态
  • 组件化开发:将功能拆分为独立组件,提高代码可维护性
  • 响应式设计:通过CSS媒体查询适配不同屏幕尺寸
  • 用户体验:注重交互细节,提供良好的操作反馈

通过本项目的实践,我们不仅掌握了React组件开发的核心技能,还深入理解了如何结合原生HTML表单功能构建企业级应用。这套解决方案可以轻松扩展到其他类似的审批流程场景中,为企业的数字化转型提供坚实的技术支撑。

希望这篇文章能帮助读者深入理解现代前端开发的技术要点,并能在实际项目中灵活应用这些知识。在未来的开发工作中,建议继续关注Web标准的发展和React生态的变化,持续提升技术水平。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。