js:// script.js
// 订单表单提交
document.querySelector('.myForm').onsubmit = function (e) {
e.preventDefault();
const fields = {
orderNum: '订单编号',
orderDate: '订单日期',
orderDeliveryDate: '订单交期',
productName: '产品名称',
productNum: '产品编号',
orderOutstandingQuantity: '订单未交数',
specificationInfo: '规格信息',
orderQuantity: '订单数量'
};
const data = { type: "order" };
const missing = [];
for (const [key, label] of Object.entries(fields)) {
const val = document.getElementsByName(key)[0]?.value.trim();
if (!val) missing.push(label);
data[key] = val || '';
}
if (missing.length > 0) {
alert(`以下字段不能为空:\n${missing.join('、')}`);
return;
}
// remarks 可为空
data.remarks = document.getElementsByName('remarks')[0]?.value.trim() || '';
fetch('/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(r => r.json())
.then(res => alert(res.message))
.catch(e => alert('提交失败: ' + e.message));
};
// 产线表单提交
document.querySelector('.myForm1').onsubmit = function (e) {
e.preventDefault();
const rules = {
productionLineName: { label: '产线名称', required: true },
productionLineNumb: { label: '产线编号', required: true },
productionCapacity: {
label: '产线生产速率',
required: true,
validate: v => !isNaN(parseFloat(v)) && parseFloat(v) >= 0,
msg: '必须为非负数'
},
yieldRate: {
label: '良品率',
required: true,
validate: v => {
const n = parseFloat(v);
return !isNaN(n) && n >= 0 && n <= 100;
},
msg: '必须是 0~100 的数字'
},
opStatus: { label: '产线状态', required: true },
productionNum: { label: '生产产品编号', required: true },
// 允许为空:
productionName: { label: '生产产品名称', required: false },
specificationInfo: { label: '产品规格', required: false }
};
const data = { type: "production_line" };
const missing = [];
const invalid = [];
for (const [key, rule] of Object.entries(rules)) {
const input = document.getElementsByName(key)[0];
if (!input) continue;
const val = input.value.trim();
data[key] = val;
if (rule.required && !val) {
missing.push(rule.label);
continue;
}
if (rule.validate && val && !rule.validate(val)) {
invalid.push(`${rule.label}: ${rule.msg}`);
}
}
if (missing.length > 0) {
alert(`以下必填字段为空:\n${missing.join('、')}`);
return;
}
if (invalid.length > 0) {
alert(`输入错误:\n${invalid.join('\n')}`);
return;
}
fetch('/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(r => r.json())
.then(res => alert(res.message))
.catch(e => alert('提交失败: ' + e.message));
};
// 上传订单 Excel
document.getElementById('excelSubmit').addEventListener('submit', function (e) {
e.preventDefault();
const file = document.getElementById('file').files[0];
if (!file) return alert("请选择一个文件");
const fd = new FormData();
fd.append('file', file);
fetch('/upload/order', {
method: 'POST',
body: fd
})
.then(r => r.json())
.then(res => alert(res.message))
.catch(() => alert("上传失败"));
});
// 上传产线 Excel
document.getElementById('excelSubmit1').addEventListener('submit', function (e) {
e.preventDefault();
const file = document.getElementById('file1').files[0];
if (!file) return alert("请选择一个文件");
const fd = new FormData();
fd.append('file1', file);
fetch('/upload/line', {
method: 'POST',
body: fd
})
.then(r => r.json())
.then(res => alert(res.message))
.catch(() => alert("上传失败"));
});
// 运行排程
document.getElementById('mat').addEventListener('click', function () {
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '<div class="loading"></div>排程中...';
fetch('/run_schedule', { method: 'POST' })
.then(r => r.json())
.then(data => {
if (data.success) {
resultDiv.innerHTML = `<a href="/downloads/${data.file_name}" target="_blank">📥 下载排程结果.xlsx</a>`;
} else {
resultDiv.innerHTML = '运行失败: ' + data.error;
}
})
.catch(err => {
resultDiv.innerHTML = '请求出错: ' + err.message;
});
});
// 清空数据按钮
document.getElementById('clearOrdersBtn').addEventListener('click', () => {
if (confirm("确定要清空所有订单数据吗?")) {
fetch('/clear/orders', { method: 'POST' })
.then(r => r.json())
.then(res => alert(res.message));
}
});
document.getElementById('clearLinesBtn').addEventListener('click', () => {
if (confirm("确定要清空所有产线数据吗?")) {
fetch('/clear/lines', { method: 'POST' })
.then(r => r.json())
.then(res => alert(res.message));
}
});
html<!-- index.html -->
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>百景作品</title>
<link rel="stylesheet" href="./static/css/style.css" />
</head>
<body>
<div class="container">
<!-- 表单区域 -->
<div class="form-section">
<!-- 订单表单 -->
<form class="myForm card">
<h3>订单录入</h3>
<div class="inputBox"><label>订单编号</label><input type="text" name="orderNum" placeholder="请输入订单号"/></div>
<div class="inputBox"><label>订单日期</label><input type="date" name="orderDate"/></div>
<div class="inputBox"><label>订单交期</label><input type="date" name="orderDeliveryDate"/></div>
<div class="inputBox"><label>产品名称</label><input type="text" name="productName" placeholder="请输入产品名称"/></div>
<div class="inputBox"><label>产品编号</label><input type="text" name="productNum" placeholder="请输入产品编号"/></div>
<div class="inputBox"><label>订单未交数</label><input type="number" name="orderOutstandingQuantity" min="0" placeholder="未交数量"/></div>
<div class="inputBox"><label>规格信息</label><input type="text" name="specificationInfo" placeholder="请输入规格信息"/></div>
<div class="inputBox"><label>备注</label><input type="text" name="remarks" placeholder="可备注"/></div>
<div class="inputBox"><label>订单数量</label><input type="number" name="orderQuantity" min="1" placeholder="总订单量"/></div>
<button class="submitButton" type="submit">提交订单</button>
</form>
<!-- 产线表单 -->
<form class="myForm1 card">
<h3>产线配置</h3>
<div class="inputBox"><label>产线名称</label><input type="text" name="productionLineName" placeholder="如:A线"/></div>
<div class="inputBox"><label>产线编号</label><input type="text" name="productionLineNumb" placeholder="PL-001"/></div>
<div class="inputBox"><label>生产速率</label><input type="number" step="0.1" name="productionCapacity" min="0" placeholder="件/小时"/></div>
<div class="inputBox"><label>良品率%</label><input type="number" step="0.1" name="yieldRate" min="0" max="100" placeholder="95.5"/></div>
<div class="inputBox"><label>产线状态</label><input type="text" name="opStatus" placeholder="正常 / 停止"/></div>
<div class="inputBox"><label>生产产品编号</label><input type="text" name="productionNum" placeholder="对应产品编号"/></div>
<div class="inputBox"><label>生产产品名称</label><input type="text" name="productionName" placeholder="如:电机"/></div>
<div class="inputBox"><label>产品规格</label><input type="text" name="specificationInfo" placeholder="尺寸/功率等"/></div>
<button class="submitButton" type="submit">提交产线</button>
</form>
</div>
<!-- 文件上传 -->
<div class="upload-section">
<form id="excelSubmit" enctype="multipart/form-data">
<input type="file" id="file" accept=".xlsx,.xls" />
<button class="xlsFile" type="submit">上传并导入订单文件</button>
</form>
<form id="excelSubmit1" enctype="multipart/form-data">
<input type="file" id="file1" accept=".xlsx,.xls" />
<button class="xlsFile1" type="submit">上传并导入产线文件</button>
</form>
</div>
<!-- 排程与清空 -->
<div class="action-section">
<button id="mat" class="mat">运行排程</button>
<div id="result"></div>
<div class="clear-buttons">
<button id="clearOrdersBtn" class="danger-btn">清空订单数据</button>
<button id="clearLinesBtn" class="danger-btn">清空产线数据</button>
</div>
</div>
</div>
<script src="./static/js/script.js"></script>
</body>
</html>
bj.py:import pymysql
import pandas as pd
import matlab.engine
import os
import threading
_eng_lock = threading.Lock()
_eng = None
def getConn():
return pymysql.connect(
host='localhost',
user='baijing',
password='12345',
database='baijing',
charset='utf8mb4',
autocommit=False
)
def insertHtmlData(orderNum, orderDate, orderDeliveryDate, productName, productNum,
orderOutstandingQuantity, specificationInfo, remarks, orderQuantity):
"""插入订单数据"""
sql = """
INSERT INTO orderinfo
(orderNumber, orderDate, orderDeliveryDate, productName, productNumber,
outstandingOrderQuantity, specificationInfo, Remarks, orderQuantity)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
conn = getConn()
try:
with conn.cursor() as cursor:
cursor.execute(sql, (
orderNum, orderDate, orderDeliveryDate, productName, productNum,
int(orderOutstandingQuantity), specificationInfo, remarks or None, int(orderQuantity)
))
conn.commit()
except Exception as e:
conn.rollback()
print(f"[订单插入失败] {e}")
raise
finally:
conn.close()
def insertHtmlData1(productionLineName, productionLineNumb, productionCapacity, yieldRate,
opStatus, productNum, productName, productSpecification):
"""插入产线数据"""
sql = """
INSERT INTO productionlineinfo
(productionLineName, productionLineNumber, productionCapacity, yieldRate,
opStatus, productNum, productName, productSpecification)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
"""
conn = getConn()
try:
with conn.cursor() as cursor:
cursor.execute(sql, (
productionLineName,
productionLineNumb,
float(productionCapacity),
float(yieldRate),
opStatus,
productNum,
productName,
productSpecification or None
))
conn.commit()
except Exception as e:
conn.rollback()
print(f"[产线插入失败] {e}")
raise
finally:
conn.close()
# 批量导入
def clean_nan(val):
"""安全转换 NaN / None / 空字符串"""
if pd.isna(val):
return None
if isinstance(val, float) and val == float('inf'):
return None
return str(int(val)) if isinstance(val, float) and val.is_integer() else str(val)
def insert_batch_data(df):
"""批量导入订单 Excel"""
required_cols = ['orderNumber', 'orderDate', 'orderDeliveryDate', 'productName',
'productNumber', 'outstandingOrderQuantity', 'specificationInfo',
'Remarks', 'orderQuantity']
if len(df.columns) < len(required_cols):
raise ValueError(f"列数不足,期望 {len(required_cols)} 列")
df = df.iloc[:, :9]
df.columns = required_cols
conn = getConn()
try:
with conn.cursor() as cursor:
sql = """
INSERT INTO orderinfo
(orderNumber, orderDate, orderDeliveryDate, productName, productNumber,
outstandingOrderQuantity, specificationInfo, Remarks, orderQuantity)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
for idx, row in df.iterrows():
try:
cleaned = [clean_nan(row[col]) for col in required_cols]
cursor.execute(sql, tuple(cleaned))
except Exception as e:
print(f"跳过第 {idx + 1} 行: {e}")
continue
conn.commit()
except Exception as e:
conn.rollback()
raise
finally:
conn.close()
def insert_batch_data1(df):
"""批量导入产线 Excel"""
required_cols = ['productionLineName', 'productionLineNumber', 'productionCapacity',
'yieldRate', 'opStatus', 'productNum', 'productName', 'productSpecification']
if len(df.columns) < len(required_cols):
raise ValueError(f"产线表列数不足,期望 {len(required_cols)} 列")
df = df.iloc[:, :8]
df.columns = required_cols
conn = getConn()
try:
with conn.cursor() as cursor:
sql = """
INSERT INTO productionlineinfo
(productionLineName, productionLineNumber, productionCapacity, yieldRate,
opStatus, productNum, productName, productSpecification)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
"""
for idx, row in df.iterrows():
try:
cleaned = [clean_nan(row[col]) for col in required_cols]
cursor.execute(sql, tuple(cleaned))
except Exception as e:
print(f"跳过第 {idx + 1} 行: {e}")
continue
conn.commit()
except Exception as e:
conn.rollback()
raise
finally:
conn.close()
# 清空表
def clear_table(table_name):
allowed_tables = ["orderinfo", "productionlineinfo"]
if table_name not in allowed_tables:
raise ValueError("非法表名")
conn = getConn()
try:
with conn.cursor() as cursor:
cursor.execute(f"TRUNCATE TABLE `{table_name}`")
conn.commit()
finally:
conn.close()
# MATLAB 调度
def get_matlab_engine():
global _eng
with _eng_lock:
if _eng is None:
_eng = matlab.engine.start_matlab()
current_dir = os.path.dirname(os.path.abspath(__file__))
_eng.addpath(current_dir, nargout=0)
return _eng
def run_matlab():
try:
eng = get_matlab_engine()
current_dir = os.path.dirname(os.path.abspath(__file__))
output_folder = os.path.join(current_dir, "downloads")
os.makedirs(output_folder, exist_ok=True)
result = eng.GA_schedule(nargout=0)
excel_path = os.path.join(output_folder, "排程结果.xlsx")
if not os.path.exists(excel_path):
raise FileNotFoundError("排程结果文件未生成")
return excel_path
except Exception as e:
print(f"MATLAB 运行失败: {e}")
raise
# 订单数据库获取
def fetch_orders():
conn = getConn()
cursor = conn.cursor()
query = "SELECT * FROM orderinfo"
cursor.execute(query)
result = cursor.fetchall()
orders_df = pd.DataFrame(result, columns=["TABLELINE_ID", "ORDER_ID", "ORDER_DATE", "DUE_DATE", "PRODUCT_NAME", "PRODUCT_ID", "OUTSTANDING_QUANTITY", "SPECIFICATION_INFO", "REMARKS", "ORDER_QUANTITY",])
cursor.close()
conn.close()
return orders_df
# 从 MySQL 中获取生产线数据
def fetch_production_lines():
# 数据库连接
conn = getConn()
# 创建指向对象
cursor = conn.cursor()
# 读取生产线数据
query = "SELECT * FROM productionlineinfo"
cursor.execute(query)
result = cursor.fetchall()
# 将结果转换为 DataFrame(使用 pandas 方便处理)
production_lines_df = pd.DataFrame(result, columns=["TABLELINE_ID", "LINE_NAME", "LINE_ID", "LINE_RATE", "YIELD_RATE", "STATUS", "PRODUCT_ID", "PRODUCT_NAME", "PRODUCT_SPECIFICATION"])
# 关闭连接
cursor.close()
conn.close()
return production_lines_df main.py:import bj
import webbrowser
import threading
import os
from flask import Flask, render_template, request, jsonify, send_from_directory
import pandas as pd
app = Flask(__name__, static_folder='static')
app.config['UPLOAD_FOLDER'] = 'uploads'
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
app.config['ALLOWED_EXTENSIONS'] = {'xls', 'xlsx'}
DOWNLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "downloads")
os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
@app.route('/')
def index():
return render_template('index.html')
# 统一提交接口
@app.route('/submit', methods=['POST'])
def submit():
data = request.json
form_type = data.get("type")
if form_type == "order":
orderNum = data.get('orderNum')
orderDate = data.get('orderDate')
orderDeliveryDate = data.get('orderDeliveryDate')
productName = data.get('productName')
productNum = data.get('productNum')
orderOutstandingQuantity = data.get('orderOutstandingQuantity')
specificationInfo = data.get('specificationInfo')
remarks = data.get('remarks')
orderQuantity = data.get('orderQuantity')
print(f"收到订单: {orderNum}, {productName}")
bj.insertHtmlData(orderNum, orderDate, orderDeliveryDate, productName, productNum,
orderOutstandingQuantity, specificationInfo, remarks, orderQuantity)
elif form_type == "production_line":
productionLineName = data.get('productionLineName')
productionLineNumb = data.get('productionLineNumb')
productionCapacity = data.get('productionCapacity')
yieldRate = data.get('yieldRate')
opStatus = data.get('opStatus')
productionNum = data.get('productionNum')
productionName = data.get('productionName')
specificationInfo = data.get('specificationInfo')
print(f"收到产线: {productionLineName}, {productionLineNumb}")
bj.insertHtmlData1(productionLineName, productionLineNumb, productionCapacity, yieldRate,
opStatus, productionNum, productionName, specificationInfo)
else:
return jsonify({"message": "未知表单类型"}), 400
return jsonify({"message": "已提交"})
# 上传订单 Excel
@app.route('/upload/order', methods=['POST'])
def upload_order_file():
file = request.files.get('file')
if not file or file.filename == '':
return jsonify({"message": "未选择订单文件"}), 400
if not allowed_file(file.filename):
return jsonify({"message": "订单文件格式不支持,仅支持 .xls 或 .xlsx"}), 400
filepath = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
file.save(filepath)
try:
df = pd.read_excel(filepath)
bj.insert_batch_data(df)
return jsonify({"message": "订单数据导入成功!", "success": True})
except Exception as e:
print(f"订单导入异常: {e}")
return jsonify({"message": f"导入失败: {str(e)}", "success": False}), 500
# 上传产线 Excel
@app.route('/upload/line', methods=['POST'])
def upload_line_file():
file = request.files.get('file1')
if not file or file.filename == '':
return jsonify({"message": "未选择产线文件"}), 400
if not allowed_file(file.filename):
return jsonify({"message": "产线文件格式不支持,仅支持 .xls 或 .xlsx"}), 400
filepath = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
file.save(filepath)
try:
df = pd.read_excel(filepath)
bj.insert_batch_data1(df)
return jsonify({"message": "产线数据导入成功!", "success": True})
except Exception as e:
print(f"产线导入异常: {e}")
return jsonify({"message": f"导入失败: {str(e)}", "success": False}), 500
def allowed_file(filename):
"""检查文件扩展名是否合法"""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
# 清空订单表
@app.route('/clear/orders', methods=['POST'])
def clear_orders():
try:
bj.clear_table("orderinfo")
return jsonify({"message": "订单表已清空", "success": True})
except Exception as e:
return jsonify({"message": f"清空失败: {e}", "success": False}), 500
# 清空产线表
@app.route('/clear/lines', methods=['POST'])
def clear_lines():
try:
bj.clear_table("productionlineinfo")
return jsonify({"message": "产线表已清空", "success": True})
except Exception as e:
return jsonify({"message": f"清空失败: {e}", "success": False}), 500
# 运行排程
@app.route('/run_schedule', methods=['POST'])
def run_schedule():
try:
excel_path = bj.run_matlab()
file_name = os.path.basename(excel_path)
return jsonify({"success": True, "file_name": file_name})
except Exception as e:
return jsonify({"success": False, "error": str(e)})
# 下载结果文件
@app.route('/downloads/<filename>')
def download_file(filename):
return send_from_directory(DOWNLOAD_FOLDER, filename, as_attachment=True)
# 自动打开浏览器
if __name__ == '__main__':
def open_browser():
webbrowser.open_new('http://127.0.0.1:5000')
threading.Timer(1.0, open_browser).start()
app.run(debug=True, threaded=True)
在哪改
最新发布