html逻辑优化
This commit is contained in:
@@ -8,6 +8,7 @@ BOM 成本 - 终极树状结构抓取脚本 (全站 1400+ 父件及 5 层嵌套
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import subprocess
|
||||||
import random
|
import random
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -219,6 +220,21 @@ def fetch_bom_cost_tree():
|
|||||||
log("INFO", f"💾 进度已实时保存至 JSON ({index+1}/{len(clean_parents_list)})")
|
log("INFO", f"💾 进度已实时保存至 JSON ({index+1}/{len(clean_parents_list)})")
|
||||||
|
|
||||||
log("OK", f"=== 🏆 终极 BOM 成本多层树状抓取完成!文件路径: {TREE_FILE_PATH} ===")
|
log("OK", f"=== 🏆 终极 BOM 成本多层树状抓取完成!文件路径: {TREE_FILE_PATH} ===")
|
||||||
|
|
||||||
|
# 抓取完成后,自动调用入库脚本将 JSON 导入 SQLite
|
||||||
|
log("INFO", "⏳ 正在自动将 JSON 数据同步至 SQLite 数据库...")
|
||||||
|
try:
|
||||||
|
import_script = Path(__file__).parent / "import_to_sqlite.py"
|
||||||
|
# 使用 sys.executable 确保使用当前的 Python 环境
|
||||||
|
import sys
|
||||||
|
result = subprocess.run([sys.executable, str(import_script), "--bom-only"], capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
log("OK", "✅ 数据库同步成功!")
|
||||||
|
print(result.stdout)
|
||||||
|
else:
|
||||||
|
log("ERR", f"❌ 数据库同步失败: {result.stderr}")
|
||||||
|
except Exception as db_err:
|
||||||
|
log("ERR", f"❌ 自动触发入库脚本失败: {db_err}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("ERR", f"发生异常: {e}")
|
log("ERR", f"发生异常: {e}")
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import subprocess
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
import sqlite3
|
import sqlite3
|
||||||
@@ -216,7 +217,7 @@ def fetch_receipt_details_incremental():
|
|||||||
total_inserted += 1
|
total_inserted += 1
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
log("OK", f"第 {current_page} 页处理完毕,成功入库 {inserted_this_page} 条新数据。")
|
log("OK", f"第 {current_page} 页处理完毕,成功截获 {inserted_this_page} 条数据并存入数据库。")
|
||||||
|
|
||||||
# 还有下一页则继续点击
|
# 还有下一页则继续点击
|
||||||
if current_page < end_page:
|
if current_page < end_page:
|
||||||
@@ -250,8 +251,8 @@ def fetch_receipt_details_incremental():
|
|||||||
|
|
||||||
current_page += 1
|
current_page += 1
|
||||||
|
|
||||||
log("OK", f"🎉 增量同步大功告成!总计入库 {total_inserted} 条全新数据!")
|
log("OK", f"🎉 增量同步大功告成!总计向数据库执行了 {total_inserted} 次插入/更新操作!")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("ERR", f"发生全局异常: {e}")
|
log("ERR", f"发生全局异常: {e}")
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -214,9 +214,21 @@ def import_bom_data(conn):
|
|||||||
print(f"成功导入 {parent_count} 个 BOM 父件,包含 {child_count} 个子件节点!")
|
print(f"成功导入 {parent_count} 个 BOM 父件,包含 {child_count} 个子件节点!")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
print(f"数据库文件将保存在: {DB_PATH}")
|
print(f"数据库文件将保存在: {DB_PATH}")
|
||||||
conn = init_db()
|
conn = init_db()
|
||||||
import_receipt_details(conn)
|
|
||||||
import_bom_data(conn)
|
# 允许通过命令行参数单独导入某一部分数据
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
if "--bom-only" in args:
|
||||||
|
import_bom_data(conn)
|
||||||
|
elif "--receipt-only" in args:
|
||||||
|
import_receipt_details(conn)
|
||||||
|
else:
|
||||||
|
# 默认全量导入
|
||||||
|
import_receipt_details(conn)
|
||||||
|
import_bom_data(conn)
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
print("全部导入完成!你可以使用 SQLite 客户端连接 erp_data.db 查看数据。")
|
print("全部导入完成!你可以使用 SQLite 客户端连接 erp_data.db 查看数据。")
|
||||||
151
web_ui/app.py
151
web_ui/app.py
@@ -40,6 +40,25 @@ DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|||||||
# 抓取脚本目录 (属于代码文件,从 BASE_DIR 加载)
|
# 抓取脚本目录 (属于代码文件,从 BASE_DIR 加载)
|
||||||
BROWSER_LOGIN_DIR = BASE_DIR / "browser_login"
|
BROWSER_LOGIN_DIR = BASE_DIR / "browser_login"
|
||||||
|
|
||||||
|
# 全局互斥锁机制:保证同一时间只有一个任务在控制浏览器
|
||||||
|
browser_lock = threading.Lock()
|
||||||
|
is_browser_busy = False
|
||||||
|
current_task_name = ""
|
||||||
|
|
||||||
|
def set_browser_busy(task_name):
|
||||||
|
"""设置浏览器为忙碌状态"""
|
||||||
|
global is_browser_busy, current_task_name
|
||||||
|
with browser_lock:
|
||||||
|
is_browser_busy = True
|
||||||
|
current_task_name = task_name
|
||||||
|
|
||||||
|
def release_browser():
|
||||||
|
"""释放浏览器控制权"""
|
||||||
|
global is_browser_busy, current_task_name
|
||||||
|
with browser_lock:
|
||||||
|
is_browser_busy = False
|
||||||
|
current_task_name = ""
|
||||||
|
|
||||||
def auto_init_db():
|
def auto_init_db():
|
||||||
"""如果是新环境首次运行,自动初始化数据库表结构"""
|
"""如果是新环境首次运行,自动初始化数据库表结构"""
|
||||||
if str(BROWSER_LOGIN_DIR) not in sys.path:
|
if str(BROWSER_LOGIN_DIR) not in sys.path:
|
||||||
@@ -56,7 +75,16 @@ auto_init_db()
|
|||||||
|
|
||||||
def background_sync_job():
|
def background_sync_job():
|
||||||
"""APScheduler 后台定时任务执行增量抓取"""
|
"""APScheduler 后台定时任务执行增量抓取"""
|
||||||
|
global is_browser_busy
|
||||||
print("[定时任务] 正在执行后台增量数据同步...")
|
print("[定时任务] 正在执行后台增量数据同步...")
|
||||||
|
|
||||||
|
# 检查浏览器是否被其他任务占用
|
||||||
|
if is_browser_busy:
|
||||||
|
print(f"[定时任务] ⚠️ 浏览器当前正被 '{current_task_name}' 占用,本次增量同步跳过。")
|
||||||
|
return
|
||||||
|
|
||||||
|
set_browser_busy("定时后台增量同步")
|
||||||
|
|
||||||
if str(BROWSER_LOGIN_DIR) not in sys.path:
|
if str(BROWSER_LOGIN_DIR) not in sys.path:
|
||||||
sys.path.insert(0, str(BROWSER_LOGIN_DIR))
|
sys.path.insert(0, str(BROWSER_LOGIN_DIR))
|
||||||
|
|
||||||
@@ -69,6 +97,8 @@ def background_sync_job():
|
|||||||
print(f"[定时任务] 同步完成。")
|
print(f"[定时任务] 同步完成。")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[定时任务] 同步失败: {str(e)}")
|
print(f"[定时任务] 同步失败: {str(e)}")
|
||||||
|
finally:
|
||||||
|
release_browser()
|
||||||
|
|
||||||
# 初始化定时调度器
|
# 初始化定时调度器
|
||||||
scheduler = BackgroundScheduler()
|
scheduler = BackgroundScheduler()
|
||||||
@@ -145,19 +175,42 @@ def get_receipts():
|
|||||||
"rows": [dict(ix) for ix in receipts]
|
"rows": [dict(ix) for ix in receipts]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@app.route('/api/task_status')
|
||||||
|
def get_task_status():
|
||||||
|
"""获取当前浏览器控制任务的状态"""
|
||||||
|
return jsonify({
|
||||||
|
"is_busy": is_browser_busy,
|
||||||
|
"task_name": current_task_name
|
||||||
|
})
|
||||||
|
|
||||||
@app.route('/api/sync_receipts', methods=['POST'])
|
@app.route('/api/sync_receipts', methods=['POST'])
|
||||||
def sync_receipts():
|
def sync_receipts():
|
||||||
"""触发后台运行收货明细增量抓取脚本(修复跨机器路径问题)"""
|
"""触发后台运行收货明细增量抓取脚本(修复跨机器路径问题)"""
|
||||||
|
global is_browser_busy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
if is_browser_busy:
|
||||||
|
return jsonify({
|
||||||
|
"success": False,
|
||||||
|
"message": f"系统忙碌:当前正在执行 '{current_task_name}',请稍后再试。"
|
||||||
|
}), 409
|
||||||
|
|
||||||
if str(BROWSER_LOGIN_DIR) not in sys.path:
|
if str(BROWSER_LOGIN_DIR) not in sys.path:
|
||||||
sys.path.insert(0, str(BROWSER_LOGIN_DIR))
|
sys.path.insert(0, str(BROWSER_LOGIN_DIR))
|
||||||
|
|
||||||
|
def run_receipt_sync():
|
||||||
|
set_browser_busy("手动收货明细增量同步")
|
||||||
|
try:
|
||||||
|
from fetch_receipt_details_incremental import fetch_receipt_details_incremental
|
||||||
|
fetch_receipt_details_incremental()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"手动增量同步失败: {e}")
|
||||||
|
finally:
|
||||||
|
release_browser()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from fetch_receipt_details_incremental import fetch_receipt_details_incremental
|
|
||||||
|
|
||||||
# 将长时间运行的抓取任务放入后台线程,防止前端请求超时
|
# 将长时间运行的抓取任务放入后台线程,防止前端请求超时
|
||||||
threading.Thread(target=fetch_receipt_details_incremental, daemon=True).start()
|
threading.Thread(target=run_receipt_sync, daemon=True).start()
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
@@ -172,16 +225,31 @@ def sync_receipts():
|
|||||||
@app.route('/api/sync_bom', methods=['POST'])
|
@app.route('/api/sync_bom', methods=['POST'])
|
||||||
def sync_bom():
|
def sync_bom():
|
||||||
"""触发后台运行 BOM 成本全量抓取脚本(修复跨机器路径问题)"""
|
"""触发后台运行 BOM 成本全量抓取脚本(修复跨机器路径问题)"""
|
||||||
|
global is_browser_busy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
if is_browser_busy:
|
||||||
|
return jsonify({
|
||||||
|
"success": False,
|
||||||
|
"message": f"系统忙碌:当前正在执行 '{current_task_name}',请稍后再试。"
|
||||||
|
}), 409
|
||||||
|
|
||||||
if str(BROWSER_LOGIN_DIR) not in sys.path:
|
if str(BROWSER_LOGIN_DIR) not in sys.path:
|
||||||
sys.path.insert(0, str(BROWSER_LOGIN_DIR))
|
sys.path.insert(0, str(BROWSER_LOGIN_DIR))
|
||||||
|
|
||||||
|
def run_bom_sync():
|
||||||
|
set_browser_busy("手动 BOM 全量树抓取")
|
||||||
|
try:
|
||||||
|
from fetch_bom_cost_full_tree import fetch_bom_cost_tree
|
||||||
|
fetch_bom_cost_tree()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"BOM 抓取失败: {e}")
|
||||||
|
finally:
|
||||||
|
release_browser()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from fetch_bom_cost_full_tree import fetch_bom_cost_tree
|
|
||||||
|
|
||||||
# 将长时间运行的抓取任务放入后台线程,防止前端请求超时
|
# 将长时间运行的抓取任务放入后台线程,防止前端请求超时
|
||||||
threading.Thread(target=fetch_bom_cost_tree, daemon=True).start()
|
threading.Thread(target=run_bom_sync, daemon=True).start()
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
@@ -359,13 +427,14 @@ def get_bom_tree(parent_code):
|
|||||||
if parent_id in nodes_map:
|
if parent_id in nodes_map:
|
||||||
nodes_map[parent_id]['children'].append(node_data)
|
nodes_map[parent_id]['children'].append(node_data)
|
||||||
|
|
||||||
# 5. 递归处理:计算树的成本,并且根据是否有子件修改圆圈的实心/空心样式
|
# 5. 递归处理:计算树的成本,并且根据是否有子件修改圆圈的实心/空心样式
|
||||||
def process_tree_nodes(node, prices_dict):
|
def process_tree_nodes(node, prices_dict):
|
||||||
node_cost = 0.0
|
node_cost = 0.0
|
||||||
price_record = prices_dict.get(node['materialCode'])
|
price_record = prices_dict.get(node['materialCode'])
|
||||||
own_price = price_record['receive_price'] if price_record and isinstance(price_record['receive_price'], (int, float)) else 0.0
|
own_price = price_record['receive_price'] if price_record and isinstance(price_record['receive_price'], (int, float)) else 0.0
|
||||||
|
|
||||||
has_children = bool(node.get('children'))
|
has_children = bool(node.get('children'))
|
||||||
|
code = node.get('materialCode', '')
|
||||||
|
|
||||||
# 【核心视觉区分:还能不能点开?】
|
# 【核心视觉区分:还能不能点开?】
|
||||||
# 如果它有子件(还能点开展开),就让它变成实心的(填充颜色与边框一致)
|
# 如果它有子件(还能点开展开),就让它变成实心的(填充颜色与边框一致)
|
||||||
@@ -379,18 +448,29 @@ def get_bom_tree(parent_code):
|
|||||||
node['itemStyle']['color'] = '#FFFFFF' # 白色填充(空心)
|
node['itemStyle']['color'] = '#FFFFFF' # 白色填充(空心)
|
||||||
node['itemStyle']['borderWidth'] = 2 # 加粗彩色边框
|
node['itemStyle']['borderWidth'] = 2 # 加粗彩色边框
|
||||||
|
|
||||||
|
# 乘以该节点的耗用量,得到该节点的总成本贡献
|
||||||
|
usage_qty = float(node.get('usageQty', 1.0))
|
||||||
|
|
||||||
if not has_children:
|
if not has_children:
|
||||||
node_cost = own_price
|
node_cost = own_price
|
||||||
|
node_cost = node_cost * usage_qty
|
||||||
else:
|
else:
|
||||||
for child in node['children']:
|
for child in node['children']:
|
||||||
node_cost += process_tree_nodes(child, prices_dict)
|
node_cost += process_tree_nodes(child, prices_dict)
|
||||||
|
|
||||||
if node_cost == 0.0 and own_price > 0:
|
if node_cost == 0.0 and own_price > 0:
|
||||||
node_cost = own_price
|
node_cost = own_price
|
||||||
|
|
||||||
# 乘以该节点的耗用量,得到该节点的总成本贡献
|
# 【工序件特殊处理逻辑】
|
||||||
usage_qty = float(node.get('usageQty', 1.0))
|
# 如果当前节点是 2 或 3 开头的加工工序件,由于物理上是同一个东西的流转,
|
||||||
node_cost = node_cost * usage_qty
|
# BOM 表中的用量只是为了匹配底层的原材料用量,不能再重复相乘。
|
||||||
|
# 所以强行忽略 2 或 3 开头工序件的自身 BOM 用量,直接 1:1 继承其子件汇总的成本。
|
||||||
|
if code.startswith('2') or code.startswith('3'):
|
||||||
|
# 保持 node_cost 不变,相当于乘以 1
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# 正常的装配组件,需要将子件汇总成本乘以自身用量
|
||||||
|
node_cost = node_cost * usage_qty
|
||||||
|
|
||||||
node['totalCost'] = round(node_cost, 2)
|
node['totalCost'] = round(node_cost, 2)
|
||||||
node['ownPrice'] = round(own_price, 2)
|
node['ownPrice'] = round(own_price, 2)
|
||||||
@@ -652,9 +732,16 @@ def build_bom_compare_tree(parent_code, start_a, end_a, start_b, end_b):
|
|||||||
total_a += child['totalPeriodA']
|
total_a += child['totalPeriodA']
|
||||||
total_b += child['totalPeriodB']
|
total_b += child['totalPeriodB']
|
||||||
|
|
||||||
node['totalLatest'] = (total_latest if total_latest > 0 else node['latestUnitPrice']) * usage_qty
|
# 【工序件特殊处理逻辑】
|
||||||
node['totalPeriodA'] = (total_a if total_a > 0 else node['periodAUnitPrice']) * usage_qty
|
# 同样在成本对比页面,2或3开头的工序件不能重复乘以用量,直接继承子件成本
|
||||||
node['totalPeriodB'] = (total_b if total_b > 0 else node['periodBUnitPrice']) * usage_qty
|
if code.startswith('2') or code.startswith('3'):
|
||||||
|
node['totalLatest'] = total_latest if total_latest > 0 else node['latestUnitPrice']
|
||||||
|
node['totalPeriodA'] = total_a if total_a > 0 else node['periodAUnitPrice']
|
||||||
|
node['totalPeriodB'] = total_b if total_b > 0 else node['periodBUnitPrice']
|
||||||
|
else:
|
||||||
|
node['totalLatest'] = (total_latest if total_latest > 0 else node['latestUnitPrice']) * usage_qty
|
||||||
|
node['totalPeriodA'] = (total_a if total_a > 0 else node['periodAUnitPrice']) * usage_qty
|
||||||
|
node['totalPeriodB'] = (total_b if total_b > 0 else node['periodBUnitPrice']) * usage_qty
|
||||||
|
|
||||||
node['showAStatus'] = 'normal'
|
node['showAStatus'] = 'normal'
|
||||||
node['showBStatus'] = 'normal'
|
node['showBStatus'] = 'normal'
|
||||||
@@ -811,27 +898,33 @@ def find_free_port(start_port=5050, max_port=5100):
|
|||||||
return s.getsockname()[1]
|
return s.getsockname()[1]
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# 动态获取一个可用端口
|
# 智能判断:如果是通过 PyInstaller 打包运行的,或者是 Werkzeug 重载进程,则控制浏览器打开行为
|
||||||
try:
|
|
||||||
port = find_free_port()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ 无法自动获取端口,回退到默认端口: {e}")
|
|
||||||
port = 5050
|
|
||||||
|
|
||||||
# 启动前开启一个线程去拉起浏览器
|
|
||||||
threading.Thread(target=open_browser, args=(port,), daemon=True).start()
|
|
||||||
|
|
||||||
# 启动后端服务
|
|
||||||
print(f"🚀 前端展示后端服务已启动!请在浏览器访问: http://127.0.0.1:{port}")
|
|
||||||
|
|
||||||
# 智能判断:如果是通过 PyInstaller 打包运行的,则关闭热加载以避免多进程冲突和双开浏览器
|
|
||||||
is_frozen = getattr(sys, 'frozen', False)
|
is_frozen = getattr(sys, 'frozen', False)
|
||||||
|
# 当 Werkzeug (Flask内置服务器) 使用热加载时,它会启动一个监控主进程和一个运行子进程。
|
||||||
|
# 子进程会有 WERKZEUG_RUN_MAIN 这个环境变量。
|
||||||
|
is_werkzeug_reloader = os.environ.get('WERKZEUG_RUN_MAIN') == 'true'
|
||||||
|
|
||||||
|
# 只有在不需要热加载,或者是热加载的真正工作子进程中,才去寻找端口并打开浏览器
|
||||||
|
if is_frozen or not app.debug or is_werkzeug_reloader:
|
||||||
|
# 动态获取一个可用端口
|
||||||
|
try:
|
||||||
|
port = find_free_port()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 无法自动获取端口,回退到默认端口: {e}")
|
||||||
|
port = 5050
|
||||||
|
|
||||||
|
# 启动前开启一个线程去拉起浏览器
|
||||||
|
threading.Thread(target=open_browser, args=(port,), daemon=True).start()
|
||||||
|
print(f"🚀 前端展示后端服务已启动!请在浏览器访问: http://127.0.0.1:{port}")
|
||||||
|
else:
|
||||||
|
# 如果是热加载的主控进程,随便给个默认端口(反正它不干活),并且不打开浏览器
|
||||||
|
port = 5050
|
||||||
|
|
||||||
# 更改为动态端口,避开被占用的端口。修改 host 为 127.0.0.1 避免 Windows 权限拦截
|
# 更改为动态端口,避开被占用的端口。修改 host 为 127.0.0.1 避免 Windows 权限拦截
|
||||||
app.run(
|
app.run(
|
||||||
debug=not is_frozen,
|
debug=not is_frozen,
|
||||||
host='127.0.0.1',
|
host='127.0.0.1',
|
||||||
port=port,
|
port=port,
|
||||||
threaded=True,
|
threaded=True,
|
||||||
use_reloader=not is_frozen
|
use_reloader=False # 彻底关闭 Flask 内置的热加载,避免双进程互相影响
|
||||||
)
|
)
|
||||||
@@ -425,10 +425,10 @@
|
|||||||
originalTableData: [],
|
originalTableData: [],
|
||||||
|
|
||||||
// 默认设定期间,方便测试
|
// 默认设定期间,方便测试
|
||||||
periodA_start: '2023-01-01',
|
periodA_start: '2025-01-01',
|
||||||
periodA_end: '2023-12-31',
|
periodA_end: '2025-12-31',
|
||||||
periodB_start: '2024-01-01',
|
periodB_start: '2026-01-01',
|
||||||
periodB_end: '2024-12-31'
|
periodB_end: '2026-12-31'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -145,22 +145,26 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
|
<div v-if="globalTaskName" style="position: absolute; top: -30px; width: 100%; text-align: center; color: #E6A23C; font-weight: bold; font-size: 14px;">
|
||||||
|
<i class="el-icon-loading"></i> 系统忙碌中:正在执行 {{ globalTaskName }},在此期间无法发起新的抓取。
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-button
|
<el-button
|
||||||
type="success"
|
type="success"
|
||||||
:icon="syncing ? '' : 'el-icon-refresh'"
|
:icon="isSystemBusy ? 'el-icon-loading' : 'el-icon-refresh'"
|
||||||
:loading="syncing"
|
:disabled="isSystemBusy"
|
||||||
@click="syncReceipts"
|
@click="syncReceipts"
|
||||||
round>
|
round>
|
||||||
<span v-text="syncing ? '正在后台同步增量数据,请稍候...' : '读取最新收货明细报表'"></span>
|
<span v-text="syncing ? '请求已发送...' : '读取最新收货明细报表'"></span>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:icon="syncingBom ? '' : 'el-icon-refresh-right'"
|
:icon="isSystemBusy ? 'el-icon-loading' : 'el-icon-refresh-right'"
|
||||||
:loading="syncingBom"
|
:disabled="isSystemBusy"
|
||||||
@click="syncBom"
|
@click="syncBom"
|
||||||
round>
|
round>
|
||||||
<span v-text="syncingBom ? '正在后台抓取 BOM 树,耗时较长,请稍候...' : '读取最新 BOM 表'"></span>
|
<span v-text="syncingBom ? '请求已发送...' : '读取最新 BOM 表'"></span>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,24 +175,53 @@
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
syncing: false,
|
syncing: false,
|
||||||
syncingBom: false
|
syncingBom: false,
|
||||||
|
isSystemBusy: false,
|
||||||
|
globalTaskName: "",
|
||||||
|
statusTimer: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 页面加载时立刻检查一次
|
||||||
|
this.checkTaskStatus();
|
||||||
|
// 之后每隔 3 秒轮询一次后端状态
|
||||||
|
this.statusTimer = setInterval(this.checkTaskStatus, 3000);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.statusTimer) {
|
||||||
|
clearInterval(this.statusTimer);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
checkTaskStatus() {
|
||||||
|
axios.get('/api/task_status')
|
||||||
|
.then(res => {
|
||||||
|
this.isSystemBusy = res.data.is_busy;
|
||||||
|
this.globalTaskName = res.data.task_name;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// 忽略检查错误
|
||||||
|
});
|
||||||
|
},
|
||||||
syncReceipts() {
|
syncReceipts() {
|
||||||
this.syncing = true;
|
this.syncing = true;
|
||||||
|
|
||||||
axios.post('/api/sync_receipts')
|
axios.post('/api/sync_receipts')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
this.$message.success('明细同步成功!' + res.data.message);
|
this.$message.success('已触发!' + res.data.message);
|
||||||
|
// 立即主动检查一次状态更新按钮
|
||||||
|
setTimeout(this.checkTaskStatus, 500);
|
||||||
} else {
|
} else {
|
||||||
this.$message.error('同步失败:' + res.data.message);
|
this.$message.error('触发失败:' + res.data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.$message.error('请求发生异常,请检查后端日志。');
|
if (err.response && err.response.status === 409) {
|
||||||
console.error(err);
|
this.$message.warning(err.response.data.message);
|
||||||
|
} else {
|
||||||
|
this.$message.error('请求发生异常,请检查后端日志。');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.syncing = false;
|
this.syncing = false;
|
||||||
@@ -200,14 +233,18 @@
|
|||||||
axios.post('/api/sync_bom')
|
axios.post('/api/sync_bom')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
this.$message.success('BOM 同步成功!' + res.data.message);
|
this.$message.success('已触发!' + res.data.message);
|
||||||
|
setTimeout(this.checkTaskStatus, 500);
|
||||||
} else {
|
} else {
|
||||||
this.$message.error('同步失败:' + res.data.message);
|
this.$message.error('触发失败:' + res.data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.$message.error('请求发生异常,这可能需要较长时间,请检查控制台日志。');
|
if (err.response && err.response.status === 409) {
|
||||||
console.error(err);
|
this.$message.warning(err.response.data.message);
|
||||||
|
} else {
|
||||||
|
this.$message.error('请求发生异常,请检查后端日志。');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.syncingBom = false;
|
this.syncingBom = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user