Compare commits
2 Commits
c609934156
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 95ec77afae | |||
| fc8b38d44d |
@@ -370,6 +370,10 @@ def get_opc_config():
|
|||||||
"poll_interval": opc_service.poll_interval,
|
"poll_interval": opc_service.poll_interval,
|
||||||
"signal1_node": opc_service.signal1_node,
|
"signal1_node": opc_service.signal1_node,
|
||||||
"signal2_node": opc_service.signal2_node,
|
"signal2_node": opc_service.signal2_node,
|
||||||
|
"write_counter_node": opc_service.write_counter_node,
|
||||||
|
"write_source_node": opc_service.write_source_node,
|
||||||
|
"write_target_node": opc_service.write_target_node,
|
||||||
|
"write_nodes": opc_service.write_nodes,
|
||||||
"running": opc_service.running,
|
"running": opc_service.running,
|
||||||
"last_counter": opc_service.last_counter,
|
"last_counter": opc_service.last_counter,
|
||||||
"last_update": opc_service.last_update,
|
"last_update": opc_service.last_update,
|
||||||
@@ -385,6 +389,11 @@ async def save_opc_config(config: OpcConfig):
|
|||||||
opc_service.poll_interval = config.poll_interval
|
opc_service.poll_interval = config.poll_interval
|
||||||
opc_service.signal1_node = config.signal1_node
|
opc_service.signal1_node = config.signal1_node
|
||||||
opc_service.signal2_node = config.signal2_node
|
opc_service.signal2_node = config.signal2_node
|
||||||
|
opc_service.write_counter_node = config.write_counter_node
|
||||||
|
opc_service.write_source_node = config.write_source_node
|
||||||
|
opc_service.write_target_node = config.write_target_node
|
||||||
|
opc_service.write_nodes = config.write_nodes
|
||||||
|
opc_service.write_counter_last = None
|
||||||
try:
|
try:
|
||||||
opc_service.save_config()
|
opc_service.save_config()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -402,6 +411,7 @@ def opc_status():
|
|||||||
"last_update": opc_service.last_update,
|
"last_update": opc_service.last_update,
|
||||||
"log": opc_service.event_log[-50:],
|
"log": opc_service.event_log[-50:],
|
||||||
"track_state": opc_service.track_state,
|
"track_state": opc_service.track_state,
|
||||||
|
"write_counter_last": opc_service.write_counter_last,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -184,3 +184,7 @@ class OpcConfig(BaseModel):
|
|||||||
trackmap_nodes: Dict[str, str] = {}
|
trackmap_nodes: Dict[str, str] = {}
|
||||||
signal1_node: str = ""
|
signal1_node: str = ""
|
||||||
signal2_node: str = ""
|
signal2_node: str = ""
|
||||||
|
write_counter_node: str = ""
|
||||||
|
write_source_node: str = ""
|
||||||
|
write_target_node: str = ""
|
||||||
|
write_nodes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
{
|
{
|
||||||
"opc_url": "opc.tcp://192.168.1.100:4840",
|
"opc_url": "opc.tcp://127.0.0.1:49320",
|
||||||
"counter_node": "ns=2;s=PL.TRACKMAP.COUNTER",
|
"counter_node": "ns=2;s=PL.TRACKMAP.COUNTER",
|
||||||
"poll_interval": 2,
|
"poll_interval": 2,
|
||||||
"trackmap_nodes": {},
|
"trackmap_nodes": {},
|
||||||
"signal1_node": "ns=2;s=PL.Signal.EntryCoil",
|
"signal1_node": "ns=2;s=PL.Signal.EntryCoil",
|
||||||
"signal2_node": "ns=2;s=PL.Signal.WeldDone"
|
"signal2_node": "ns=2;s=PL.Signal.WeldDone",
|
||||||
|
"write_counter_node": "",
|
||||||
|
"write_source_node": "",
|
||||||
|
"write_target_node": "",
|
||||||
|
"write_nodes": {
|
||||||
|
"1": {},
|
||||||
|
"2": {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -63,8 +63,18 @@ class OpcService:
|
|||||||
self.signal1_last: Optional[int] = None
|
self.signal1_last: Optional[int] = None
|
||||||
self.signal2_last: Optional[int] = None
|
self.signal2_last: Optional[int] = None
|
||||||
self.signal2_rise_time: Optional[datetime] = None
|
self.signal2_rise_time: Optional[datetime] = None
|
||||||
|
# 写入触发相关(三点位独立于追踪信号)
|
||||||
|
self.write_counter_node: str = os.getenv("OPC_WRITE_COUNTER_NODE", "")
|
||||||
|
self.write_source_node: str = os.getenv("OPC_WRITE_SOURCE_NODE", "")
|
||||||
|
self.write_target_node: str = os.getenv("OPC_WRITE_TARGET_NODE", "")
|
||||||
|
self.write_counter_last: Optional[Any] = None
|
||||||
|
# 写入字段映射(按目标开卷机 1/2 分组)
|
||||||
|
self.write_nodes: Dict[str, Dict[str, str]] = {}
|
||||||
self.signal1_coils: List[Dict[str, Any]] = []
|
self.signal1_coils: List[Dict[str, Any]] = []
|
||||||
self.current_seq_start: int = 1
|
self.first_coilid: str = ""
|
||||||
|
self.last_tracked_coilid: str = ""
|
||||||
|
self.end_coilid: str = ""
|
||||||
|
self.tracking_ended: bool = False
|
||||||
# 状态机: WAIT_S1=等待信号1, WAIT_S2=等待信号2
|
# 状态机: WAIT_S1=等待信号1, WAIT_S2=等待信号2
|
||||||
self.track_state: str = "WAIT_S1"
|
self.track_state: str = "WAIT_S1"
|
||||||
self.last_counter_at_state_change: Optional[Any] = None
|
self.last_counter_at_state_change: Optional[Any] = None
|
||||||
@@ -103,6 +113,10 @@ class OpcService:
|
|||||||
self.trackmap_nodes = cfg.get("trackmap_nodes", self.trackmap_nodes) or {}
|
self.trackmap_nodes = cfg.get("trackmap_nodes", self.trackmap_nodes) or {}
|
||||||
self.signal1_node = cfg.get("signal1_node", self.signal1_node)
|
self.signal1_node = cfg.get("signal1_node", self.signal1_node)
|
||||||
self.signal2_node = cfg.get("signal2_node", self.signal2_node)
|
self.signal2_node = cfg.get("signal2_node", self.signal2_node)
|
||||||
|
self.write_counter_node = cfg.get("write_counter_node", self.write_counter_node)
|
||||||
|
self.write_source_node = cfg.get("write_source_node", self.write_source_node)
|
||||||
|
self.write_target_node = cfg.get("write_target_node", self.write_target_node)
|
||||||
|
self.write_nodes = cfg.get("write_nodes", self.write_nodes) or {}
|
||||||
self._log(f"Loaded OPC config from {self.config_path}")
|
self._log(f"Loaded OPC config from {self.config_path}")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("Failed to load OPC config %s: %s", self.config_path, exc)
|
logger.warning("Failed to load OPC config %s: %s", self.config_path, exc)
|
||||||
@@ -116,6 +130,10 @@ class OpcService:
|
|||||||
"trackmap_nodes": self.trackmap_nodes,
|
"trackmap_nodes": self.trackmap_nodes,
|
||||||
"signal1_node": self.signal1_node,
|
"signal1_node": self.signal1_node,
|
||||||
"signal2_node": self.signal2_node,
|
"signal2_node": self.signal2_node,
|
||||||
|
"write_counter_node": self.write_counter_node,
|
||||||
|
"write_source_node": self.write_source_node,
|
||||||
|
"write_target_node": self.write_target_node,
|
||||||
|
"write_nodes": self.write_nodes,
|
||||||
}
|
}
|
||||||
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
|
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
|
||||||
with open(self.config_path, "w", encoding="utf-8") as f:
|
with open(self.config_path, "w", encoding="utf-8") as f:
|
||||||
@@ -166,6 +184,7 @@ class OpcService:
|
|||||||
while not self._stop_event.is_set():
|
while not self._stop_event.is_set():
|
||||||
current_counter = await self._tick_with_counter(client)
|
current_counter = await self._tick_with_counter(client)
|
||||||
await self._check_signals(client, current_counter)
|
await self._check_signals(client, current_counter)
|
||||||
|
await self._check_write_trigger(client)
|
||||||
await asyncio.sleep(self.poll_interval)
|
await asyncio.sleep(self.poll_interval)
|
||||||
finally:
|
finally:
|
||||||
client.disconnect()
|
client.disconnect()
|
||||||
@@ -254,78 +273,303 @@ class OpcService:
|
|||||||
elif self.track_state == "WAIT_S2":
|
elif self.track_state == "WAIT_S2":
|
||||||
if signal2_value is not None and self.signal2_last is not None:
|
if signal2_value is not None and self.signal2_last is not None:
|
||||||
if self.signal2_last == 0 and signal2_value == 1 and counter_changed:
|
if self.signal2_last == 0 and signal2_value == 1 and counter_changed:
|
||||||
self.signal2_rise_time = datetime.now()
|
self._log("Signal2: Weld done rising edge (0->1) with counter change, triggering update")
|
||||||
self._log("Signal2: Weld done rising edge (0->1) with counter change")
|
await self._handle_signal2()
|
||||||
|
self.track_state = "WAIT_S1"
|
||||||
|
self.signal2_rise_time = None
|
||||||
|
self.last_counter_at_state_change = current_counter
|
||||||
elif self.signal2_last == 1 and signal2_value == 0:
|
elif self.signal2_last == 1 and signal2_value == 0:
|
||||||
self.signal2_rise_time = None
|
self.signal2_rise_time = None
|
||||||
|
|
||||||
if signal2_value == 1 and self.signal2_rise_time and counter_changed:
|
|
||||||
elapsed = (datetime.now() - self.signal2_rise_time).total_seconds()
|
|
||||||
if elapsed >= 2.0:
|
|
||||||
self._log(f"Signal2: Held {elapsed:.1f}s, triggering trackmap update")
|
|
||||||
await self._handle_signal2()
|
|
||||||
self.track_state = "WAIT_S1"
|
|
||||||
self.signal2_rise_time = None
|
|
||||||
self.last_counter_at_state_change = current_counter
|
|
||||||
|
|
||||||
if signal1_value is not None:
|
if signal1_value is not None:
|
||||||
self.signal1_last = signal1_value
|
self.signal1_last = signal1_value
|
||||||
if signal2_value is not None:
|
if signal2_value is not None:
|
||||||
self.signal2_last = signal2_value
|
self.signal2_last = signal2_value
|
||||||
|
|
||||||
async def _handle_signal1(self):
|
async def _check_write_trigger(self, client):
|
||||||
"""Handle signal1: fetch next 5 coils from PDI and save to SQLite temp table."""
|
"""Check write trigger counter/source/target and write PDI head coil to OPC."""
|
||||||
from sqlite_sync import (
|
if not (self.write_counter_node and self.write_source_node and self.write_target_node):
|
||||||
sqlite_get_max_sequencenb,
|
return
|
||||||
sqlite_get_coils_by_sequencenb_range,
|
|
||||||
sqlite_save_coils_to_track
|
try:
|
||||||
|
write_counter = client.get_node(self.write_counter_node).get_value()
|
||||||
|
write_source = client.get_node(self.write_source_node).get_value()
|
||||||
|
write_target = client.get_node(self.write_target_node).get_value()
|
||||||
|
except Exception as exc:
|
||||||
|
self._log(f"Write trigger read failed: {exc}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.write_counter_last is None:
|
||||||
|
self.write_counter_last = write_counter
|
||||||
|
return
|
||||||
|
|
||||||
|
if write_counter == self.write_counter_last:
|
||||||
|
return
|
||||||
|
|
||||||
|
old_counter = self.write_counter_last
|
||||||
|
self.write_counter_last = write_counter
|
||||||
|
self._log(
|
||||||
|
f"Write counter changed: {old_counter} -> {write_counter}, "
|
||||||
|
f"source={write_source}, target={write_target}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
source_value = int(write_source)
|
||||||
|
target_value = int(write_target)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
self._log(f"Write trigger invalid source/target values: source={write_source}, target={write_target}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if source_value != 100 or target_value not in (1, 2):
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._write_entry_coil_to_uncoiler(client, target_value)
|
||||||
|
|
||||||
|
async def _write_entry_coil_to_uncoiler(self, client, target_uncoiler: int):
|
||||||
|
"""Write the smallest COILID plan to target uncoiler OPC nodes."""
|
||||||
|
|
||||||
|
def _load_next_plan():
|
||||||
|
from database import get_connection
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
COILID,
|
||||||
|
ENTRY_COIL_WEIGHT,
|
||||||
|
ENTRY_OF_COIL_LENGTH,
|
||||||
|
ENTRY_COIL_WIDTH,
|
||||||
|
ENTRY_COIL_THICKNESS,
|
||||||
|
ENTRY_OF_COIL_INNER_DIAMETER,
|
||||||
|
ENTRY_OF_COIL_OUTER_DIAMETER,
|
||||||
|
STEEL_GRADE
|
||||||
|
FROM PLTM.PDI_PLTM
|
||||||
|
ORDER BY COILID ASC
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"coilid": row[0],
|
||||||
|
"entry_coil_weight": row[1],
|
||||||
|
"entry_of_coil_length": row[2],
|
||||||
|
"entry_coil_width": row[3],
|
||||||
|
"entry_coil_thickness": row[4],
|
||||||
|
"entry_of_coil_inner_diameter": row[5],
|
||||||
|
"entry_of_coil_outer_diameter": row[6],
|
||||||
|
"alloy_code": row[7],
|
||||||
|
"material": row[7],
|
||||||
|
}
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
plan = await loop.run_in_executor(None, _load_next_plan)
|
||||||
|
if not plan:
|
||||||
|
self._log("Write trigger matched but PDI has no plan data")
|
||||||
|
return
|
||||||
|
|
||||||
|
target_cfg = self.write_nodes.get(str(target_uncoiler), {}) or {}
|
||||||
|
if not target_cfg:
|
||||||
|
self._log(f"Write nodes for uncoiler {target_uncoiler} not configured")
|
||||||
|
return
|
||||||
|
|
||||||
|
# setup_data_revision: 每次写入 +1
|
||||||
|
revision_node = target_cfg.get("setup_data_revision")
|
||||||
|
if revision_node:
|
||||||
|
try:
|
||||||
|
rev_value = client.get_node(revision_node).get_value()
|
||||||
|
next_rev = int(rev_value or 0) + 1
|
||||||
|
await self._write_node_value_by_client(client, revision_node, next_rev, "Int32")
|
||||||
|
except Exception as exc:
|
||||||
|
self._log(f"Write setup_data_revision failed (U{target_uncoiler}): {exc}")
|
||||||
|
|
||||||
|
field_variant = {
|
||||||
|
"coilid": "String",
|
||||||
|
"entry_coil_weight": "Float",
|
||||||
|
"entry_of_coil_length": "Float",
|
||||||
|
"entry_coil_width": "Float",
|
||||||
|
"entry_coil_thickness": "Float",
|
||||||
|
"entry_of_coil_inner_diameter": "Float",
|
||||||
|
"entry_of_coil_outer_diameter": "Float",
|
||||||
|
"alloy_code": "String",
|
||||||
|
"material": "String",
|
||||||
|
}
|
||||||
|
|
||||||
|
for field, value in plan.items():
|
||||||
|
node_id = target_cfg.get(field)
|
||||||
|
if not node_id or value is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
await self._write_node_value_by_client(
|
||||||
|
client,
|
||||||
|
node_id,
|
||||||
|
value,
|
||||||
|
field_variant.get(field),
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
self._log(
|
||||||
|
f"Write field failed (U{target_uncoiler}, {field}, node={node_id}): {exc}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._log(
|
||||||
|
f"Wrote plan COILID={plan['coilid']} to uncoiler {target_uncoiler}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _write_node_value_by_client(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
node_id: str,
|
||||||
|
value: Any,
|
||||||
|
variant_type: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""Write OPC node value using an existing client connection."""
|
||||||
|
from opcua import ua # type: ignore
|
||||||
|
|
||||||
|
node = client.get_node(node_id)
|
||||||
|
vt = self._normalize_variant_type(variant_type)
|
||||||
|
if vt is None:
|
||||||
|
node.set_value(value)
|
||||||
|
else:
|
||||||
|
node.set_value(ua.DataValue(ua.Variant(value, vt)))
|
||||||
|
|
||||||
|
async def _handle_signal1(self):
|
||||||
|
"""Handle signal1: fetch next 4 coils from Oracle PDI table and save to SQLite temp table."""
|
||||||
|
from sqlite_sync import sqlite_save_coils_to_track, sqlite_clear_coil_track
|
||||||
|
|
||||||
def _do_fetch():
|
def _do_fetch():
|
||||||
max_seq = sqlite_get_max_sequencenb()
|
# 如果追踪已结束,检查是否有新钢卷需要恢复追踪
|
||||||
if max_seq is None or max_seq < 1:
|
self._log(f"Signal1: Called, first_coilid={repr(self.first_coilid)}, tracking_ended={self.tracking_ended}")
|
||||||
self._log("Signal1: No PDI data available")
|
if self.tracking_ended:
|
||||||
|
try:
|
||||||
|
from database import get_connection
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("SELECT COILID FROM PLTM.PDI_PLTM ORDER BY COILID ASC")
|
||||||
|
all_coil_ids = [r[0] for r in cursor.fetchall()]
|
||||||
|
# 使用 end_coilid 判断是否有新钢卷
|
||||||
|
check_coilid = self.end_coilid or self.last_tracked_coilid
|
||||||
|
if all_coil_ids and check_coilid:
|
||||||
|
has_new = any(cid > check_coilid for cid in all_coil_ids)
|
||||||
|
if has_new:
|
||||||
|
self._log("Signal1: New coils detected, resuming tracking")
|
||||||
|
self.tracking_ended = False
|
||||||
|
# 从最后一个追踪的钢卷之后继续
|
||||||
|
self.first_coilid = check_coilid
|
||||||
|
else:
|
||||||
|
self._log("Signal1: Tracking ended, no new coils, skip")
|
||||||
|
return
|
||||||
|
elif all_coil_ids and not check_coilid:
|
||||||
|
# 没有结束点记录但有计划,按首卷重新启动追踪
|
||||||
|
self._log("Signal1: Tracking ended without checkpoint, restarting from first plan")
|
||||||
|
self.tracking_ended = False
|
||||||
|
self.first_coilid = ""
|
||||||
|
else:
|
||||||
|
# 没有计划,保持结束状态
|
||||||
|
self._log("Signal1: Tracking ended, PDI empty, skip")
|
||||||
|
return
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
except Exception as exc:
|
||||||
|
self._log(f"Signal1: Check new coils failed: {exc}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
from database import get_connection
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
if self.first_coilid:
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
|
||||||
|
FROM PLTM.PDI_PLTM
|
||||||
|
WHERE COILID >= :start_coilid
|
||||||
|
ORDER BY COILID ASC
|
||||||
|
""", {"start_coilid": self.first_coilid})
|
||||||
|
else:
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
|
||||||
|
FROM PLTM.PDI_PLTM
|
||||||
|
ORDER BY COILID ASC
|
||||||
|
""")
|
||||||
|
rows = cursor.fetchmany(4)
|
||||||
|
coils = [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
except Exception as exc:
|
||||||
|
self._log(f"Signal1: Failed to fetch from Oracle: {exc}")
|
||||||
return
|
return
|
||||||
|
|
||||||
start_seq = self.current_seq_start
|
|
||||||
end_seq = min(start_seq + 4, max_seq)
|
|
||||||
|
|
||||||
if end_seq < start_seq:
|
|
||||||
self._log(f"Signal1: Insufficient PDI data (max_seq={max_seq}, start={start_seq})")
|
|
||||||
return
|
|
||||||
|
|
||||||
coils = sqlite_get_coils_by_sequencenb_range(start_seq, end_seq)
|
|
||||||
if len(coils) == 0:
|
if len(coils) == 0:
|
||||||
self._log(f"Signal1: No coils found")
|
self._log("Signal1: No more coils in PDI, ending tracking")
|
||||||
|
sqlite_clear_coil_track()
|
||||||
|
self.tracking_ended = True
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(coils) == 1 and coils[0]["coilid"] == self.first_coilid:
|
||||||
|
self._log("Signal1: Only the same coil exists, saving it and setting for next query")
|
||||||
|
self.signal1_coils = coils
|
||||||
|
sqlite_save_coils_to_track(coils)
|
||||||
|
# 保持 first_coilid 不变,下次查询会从同一个位置继续
|
||||||
|
# Signal2 触发后,检查是否还有更多钢卷
|
||||||
return
|
return
|
||||||
|
|
||||||
self.signal1_coils = coils
|
self.signal1_coils = coils
|
||||||
sqlite_save_coils_to_track(coils)
|
sqlite_save_coils_to_track(coils)
|
||||||
|
|
||||||
self._log(f"Signal1: Saved {len(coils)} coils (seq {start_seq}-{end_seq}) to temp table")
|
if len(coils) >= 2:
|
||||||
|
self.first_coilid = coils[1]["coilid"]
|
||||||
|
else:
|
||||||
|
self.first_coilid = coils[0]["coilid"]
|
||||||
|
|
||||||
self.current_seq_start = start_seq + 1
|
self.last_tracked_coilid = coils[0]["coilid"]
|
||||||
|
|
||||||
|
self._log(f"Signal1: Saved {len(coils)} coils, next_start: {self.first_coilid}, last_tracked: {self.last_tracked_coilid}")
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
await loop.run_in_executor(None, _do_fetch)
|
await loop.run_in_executor(None, _do_fetch)
|
||||||
|
|
||||||
async def _handle_signal2(self):
|
async def _handle_signal2(self):
|
||||||
"""Handle signal2: update CMPT_PL_TRACKMAP with temp table data."""
|
"""Handle signal2: update CMPT_PL_TRACKMAP with temp table data."""
|
||||||
from sqlite_sync import sqlite_get_coil_track
|
from sqlite_sync import sqlite_clear_coil_track, sqlite_get_coil_track
|
||||||
from database import get_connection
|
from database import get_connection
|
||||||
|
|
||||||
def _do_update():
|
def _do_update():
|
||||||
coils = sqlite_get_coil_track()
|
coils = sqlite_get_coil_track()
|
||||||
if not coils:
|
|
||||||
self._log("Signal2: No coils in temp track table")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
try:
|
try:
|
||||||
|
if not coils:
|
||||||
|
self._log("Signal2: No coils in temp track table, clearing all positions")
|
||||||
|
for pos in range(1, 11):
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM PLTM.CMPT_PL_TRACKMAP WHERE POSITION = :pos", {"pos": pos})
|
||||||
|
if cursor.fetchone()[0] > 0:
|
||||||
|
cursor.execute("UPDATE PLTM.CMPT_PL_TRACKMAP SET COILID = NULL, TOM = SYSDATE WHERE POSITION = :pos", {"pos": pos})
|
||||||
|
conn.commit()
|
||||||
|
# 标记追踪结束
|
||||||
|
if self.signal1_coils:
|
||||||
|
self.end_coilid = self.signal1_coils[0]["coilid"]
|
||||||
|
self.tracking_ended = True
|
||||||
|
# 清空 first_coilid,下次 Signal1 触发时从头查询
|
||||||
|
self.first_coilid = ""
|
||||||
|
self._log(f"Signal2: Tracking ended (no coils), end_coilid={self.end_coilid}")
|
||||||
|
return
|
||||||
|
|
||||||
|
cursor.execute("SELECT COILID FROM PLTM.CMPT_PL_TRACKMAP WHERE POSITION = 1")
|
||||||
|
prev_pos1_coil = cursor.fetchone()
|
||||||
|
prev_pos1_coilid = prev_pos1_coil[0] if prev_pos1_coil else None
|
||||||
|
|
||||||
coil_count = len(coils)
|
coil_count = len(coils)
|
||||||
for i in range(5):
|
for i in range(4):
|
||||||
target_position = i + 1
|
target_position = i + 1
|
||||||
coil_index = target_position - 1
|
coil_index = target_position - 1
|
||||||
if coil_index >= 0 and coil_index < coil_count:
|
if coil_index >= 0 and coil_index < coil_count:
|
||||||
@@ -357,8 +601,63 @@ class OpcService:
|
|||||||
INSERT INTO PLTM.CMPT_PL_TRACKMAP (POSITION, COILID, TOM)
|
INSERT INTO PLTM.CMPT_PL_TRACKMAP (POSITION, COILID, TOM)
|
||||||
VALUES (:position, NULL, SYSDATE)
|
VALUES (:position, NULL, SYSDATE)
|
||||||
""", {"position": target_position})
|
""", {"position": target_position})
|
||||||
|
|
||||||
|
for pos in range(5, 11):
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM PLTM.CMPT_PL_TRACKMAP WHERE POSITION = :pos", {"pos": pos})
|
||||||
|
if cursor.fetchone()[0] > 0:
|
||||||
|
cursor.execute("UPDATE PLTM.CMPT_PL_TRACKMAP SET COILID = NULL, TOM = SYSDATE WHERE POSITION = :pos", {"pos": pos})
|
||||||
|
|
||||||
|
new_pos1_coil = coils[0]["coilid"] if coils else None
|
||||||
|
pos1_unchanged = prev_pos1_coilid and new_pos1_coil and prev_pos1_coilid == new_pos1_coil
|
||||||
|
if pos1_unchanged:
|
||||||
|
# 只在“末卷(单卷)重复信号”时清空,避免 6-7 阶段提前进入清空/重写循环
|
||||||
|
if coil_count == 1:
|
||||||
|
for pos in range(1, 11):
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM PLTM.CMPT_PL_TRACKMAP WHERE POSITION = :pos", {"pos": pos})
|
||||||
|
if cursor.fetchone()[0] > 0:
|
||||||
|
cursor.execute("UPDATE PLTM.CMPT_PL_TRACKMAP SET COILID = NULL, TOM = SYSDATE WHERE POSITION = :pos", {"pos": pos})
|
||||||
|
sqlite_clear_coil_track()
|
||||||
|
self.end_coilid = coils[0]["coilid"]
|
||||||
|
self.last_tracked_coilid = self.end_coilid
|
||||||
|
self.tracking_ended = True
|
||||||
|
self.first_coilid = ""
|
||||||
|
self._log("Signal2: Final single coil repeated, cleared all positions and ended tracking")
|
||||||
|
else:
|
||||||
|
self._log("Signal2: Coil at position 1 unchanged but not final single-coil stage, keep tracking")
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
self._log(f"Signal2: Updated 5 positions (coils: {coil_count})")
|
self._log(f"Signal2: Updated 4 positions (coils: {coil_count})")
|
||||||
|
|
||||||
|
# 检查是否还有更多钢卷可以追踪
|
||||||
|
self._log(f"Signal2: Checking remaining coils, first_coilid={repr(self.first_coilid)}")
|
||||||
|
|
||||||
|
if self.first_coilid:
|
||||||
|
try:
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COILID FROM PLTM.PDI_PLTM
|
||||||
|
WHERE COILID >= :start_coilid
|
||||||
|
ORDER BY COILID ASC
|
||||||
|
""", {"start_coilid": self.first_coilid})
|
||||||
|
all_remaining = cursor.fetchall()
|
||||||
|
remaining_count = len(all_remaining)
|
||||||
|
self._log(f"Signal2: Check remaining after {self.first_coilid}: count={remaining_count}, coils={[r[0] for r in all_remaining]}")
|
||||||
|
if remaining_count == 0:
|
||||||
|
# 没有更多钢卷了,标记追踪结束
|
||||||
|
self.end_coilid = self.first_coilid
|
||||||
|
self.last_tracked_coilid = self.first_coilid
|
||||||
|
self.tracking_ended = True
|
||||||
|
self._log(f"Signal2: No more coils, tracking ended, end_coilid={self.end_coilid}")
|
||||||
|
elif remaining_count >= 2:
|
||||||
|
# 有2个以上钢卷,保持 first_coilid 不变,让 Signal1 处理
|
||||||
|
self._log(f"Signal2: >=2 coils remain, first_coilid unchanged, waiting for Signal1")
|
||||||
|
else:
|
||||||
|
# 只有一个钢卷时先展示为“7(1个)”,下一次重复信号再清空到 NULL
|
||||||
|
self._log(
|
||||||
|
f"Signal2: One coil remains ({all_remaining[0][0]}), "
|
||||||
|
"waiting one more cycle before final clear"
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
self._log(f"Signal2: Check next coils failed: {exc}")
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ def sqlite_get_coils_by_sequencenb_range(start_seq: int, end_seq: int) -> List[D
|
|||||||
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
|
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
|
||||||
FROM PDI_PLTM
|
FROM PDI_PLTM
|
||||||
WHERE SEQUENCENB >= ? AND SEQUENCENB <= ?
|
WHERE SEQUENCENB >= ? AND SEQUENCENB <= ?
|
||||||
ORDER BY SEQUENCENB DESC
|
ORDER BY COILID ASC
|
||||||
""", (start_seq, end_seq))
|
""", (start_seq, end_seq))
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
|
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
|
||||||
@@ -345,12 +345,42 @@ def sqlite_get_coils_by_sequencenb_range(start_seq: int, end_seq: int) -> List[D
|
|||||||
sc.close()
|
sc.close()
|
||||||
|
|
||||||
|
|
||||||
|
def sqlite_get_coils_by_coilid(start_coilid: str, count: int = 4) -> List[Dict[str, Any]]:
|
||||||
|
sc = get_sqlite()
|
||||||
|
try:
|
||||||
|
cursor = sc.execute("""
|
||||||
|
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
|
||||||
|
FROM PDI_PLTM
|
||||||
|
WHERE COILID >= ?
|
||||||
|
ORDER BY COILID ASC
|
||||||
|
LIMIT ?
|
||||||
|
""", (start_coilid, count))
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
|
||||||
|
finally:
|
||||||
|
sc.close()
|
||||||
|
|
||||||
|
|
||||||
|
def sqlite_get_first_coils(count: int = 4) -> List[Dict[str, Any]]:
|
||||||
|
sc = get_sqlite()
|
||||||
|
try:
|
||||||
|
cursor = sc.execute("""
|
||||||
|
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
|
||||||
|
FROM PDI_PLTM
|
||||||
|
ORDER BY COILID ASC
|
||||||
|
LIMIT ?
|
||||||
|
""", (count,))
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
|
||||||
|
finally:
|
||||||
|
sc.close()
|
||||||
|
|
||||||
|
|
||||||
def sqlite_save_coils_to_track(coils: List[Dict[str, Any]]):
|
def sqlite_save_coils_to_track(coils: List[Dict[str, Any]]):
|
||||||
sc = get_sqlite()
|
sc = get_sqlite()
|
||||||
try:
|
try:
|
||||||
sc.execute("DELETE FROM COIL_TRACK_TEMP")
|
sc.execute("DELETE FROM COIL_TRACK_TEMP")
|
||||||
reversed_coils = list(reversed(coils))
|
for i, coil in enumerate(coils):
|
||||||
for i, coil in enumerate(reversed_coils):
|
|
||||||
sc.execute("""
|
sc.execute("""
|
||||||
INSERT INTO COIL_TRACK_TEMP (COILID, SEQUENCENB, ROLLPROGRAMNB, POSITION)
|
INSERT INTO COIL_TRACK_TEMP (COILID, SEQUENCENB, ROLLPROGRAMNB, POSITION)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
|
|||||||
@@ -7,17 +7,17 @@
|
|||||||
<el-input v-model="form.opc_url" placeholder="opc.tcp://192.168.1.100:4840" style="width:360px" />
|
<el-input v-model="form.opc_url" placeholder="opc.tcp://192.168.1.100:4840" style="width:360px" />
|
||||||
<span class="hint">opc.tcp://IP:PORT</span>
|
<span class="hint">opc.tcp://IP:PORT</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="计数器节点 ID">
|
|
||||||
<el-input v-model="form.counter_node" placeholder="ns=2;s=PL.TRACKMAP.COUNTER" style="width:360px" />
|
|
||||||
<span class="hint">节点值变化时触发采集</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="轮询间隔(秒)">
|
<el-form-item label="轮询间隔(秒)">
|
||||||
<el-input-number v-model="form.poll_interval" :min="1" :max="60" style="width:120px" />
|
<el-input-number v-model="form.poll_interval" :min="1" :max="60" style="width:120px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div class="panel-title" style="margin-top:16px">跟踪信号节点配置</div>
|
<div class="panel-title" style="margin-top:16px">追踪节点配置</div>
|
||||||
<el-form :model="form" label-width="155px" size="small">
|
<el-form :model="form" label-width="155px" size="small">
|
||||||
|
<el-form-item label="追踪计数器节点">
|
||||||
|
<el-input v-model="form.counter_node" placeholder="ns=2;s=PL.TRACKMAP.COUNTER" style="width:360px" />
|
||||||
|
<span class="hint">追踪流程计数器,节点值变化时触发采集</span>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="信号1(入口钢卷)">
|
<el-form-item label="信号1(入口钢卷)">
|
||||||
<el-input v-model="form.signal1_node" placeholder="ns=2;s=PL.Signal.EntryCoil" style="width:360px" />
|
<el-input v-model="form.signal1_node" placeholder="ns=2;s=PL.Signal.EntryCoil" style="width:360px" />
|
||||||
<span class="hint">入口钢卷信号,0→1触发</span>
|
<span class="hint">入口钢卷信号,0→1触发</span>
|
||||||
@@ -28,6 +28,36 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<div class="panel-title" style="margin-top:16px">计划写入触发节点配置</div>
|
||||||
|
<el-form :model="form" label-width="155px" size="small">
|
||||||
|
<el-form-item label="写入计数器节点">
|
||||||
|
<el-input v-model="form.write_counter_node" placeholder="ns=2;s=PL.Material.Counter" style="width:360px" />
|
||||||
|
<span class="hint">计数器变化触发写入流程(与追踪计数器分开)</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="起始位置节点">
|
||||||
|
<el-input v-model="form.write_source_node" placeholder="ns=2;s=PL.Material.Source" style="width:360px" />
|
||||||
|
<span class="hint">读到100表示来源为计划表</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="目标位置节点">
|
||||||
|
<el-input v-model="form.write_target_node" placeholder="ns=2;s=PL.Material.Target" style="width:360px" />
|
||||||
|
<span class="hint">1=一号开卷机, 2=二号开卷机</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<div class="panel-title" style="margin-top:16px">一号开卷机字段映射</div>
|
||||||
|
<div v-for="field in writeFieldDefs" :key="'u1-' + field.key" class="node-row">
|
||||||
|
<div class="field-label">{{ field.label }}</div>
|
||||||
|
<span class="arrow">→</span>
|
||||||
|
<el-input v-model="writeNodes['1'][field.key]" :placeholder="field.placeholder" size="small" style="width:320px" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-title" style="margin-top:16px">二号开卷机字段映射</div>
|
||||||
|
<div v-for="field in writeFieldDefs" :key="'u2-' + field.key" class="node-row">
|
||||||
|
<div class="field-label">{{ field.label }}</div>
|
||||||
|
<span class="arrow">→</span>
|
||||||
|
<el-input v-model="writeNodes['2'][field.key]" :placeholder="field.placeholder" size="small" style="width:320px" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="panel-title" style="margin-top:16px">跟踪图节点映射</div>
|
<div class="panel-title" style="margin-top:16px">跟踪图节点映射</div>
|
||||||
<div style="font-size:12px;color:#888;margin-bottom:10px">
|
<div style="font-size:12px;color:#888;margin-bottom:10px">
|
||||||
Oracle列名 → OPC节点ID。必须包含 <b>position</b> 列。
|
Oracle列名 → OPC节点ID。必须包含 <b>position</b> 列。
|
||||||
@@ -71,7 +101,29 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false, saving: false,
|
loading: false, saving: false,
|
||||||
form: { opc_url: '', counter_node: '', poll_interval: 2, signal1_node: '', signal2_node: '' },
|
form: {
|
||||||
|
opc_url: '',
|
||||||
|
counter_node: '',
|
||||||
|
poll_interval: 2,
|
||||||
|
signal1_node: '',
|
||||||
|
signal2_node: '',
|
||||||
|
write_counter_node: '',
|
||||||
|
write_source_node: '',
|
||||||
|
write_target_node: ''
|
||||||
|
},
|
||||||
|
writeFieldDefs: [
|
||||||
|
{ key: 'setup_data_revision', label: '设置数据修改', placeholder: 'ns=2;s=PL.Setup.DataRevision' },
|
||||||
|
{ key: 'coilid', label: '钢卷ID', placeholder: 'ns=2;s=PL.Entry1.CoilId' },
|
||||||
|
{ key: 'entry_coil_weight', label: '入口钢卷重量', placeholder: 'ns=2;s=PL.Entry1.Weight' },
|
||||||
|
{ key: 'entry_of_coil_length', label: '长度', placeholder: 'ns=2;s=PL.Entry1.Length' },
|
||||||
|
{ key: 'entry_coil_width', label: '宽度', placeholder: 'ns=2;s=PL.Entry1.Width' },
|
||||||
|
{ key: 'entry_coil_thickness', label: '厚度', placeholder: 'ns=2;s=PL.Entry1.Thickness' },
|
||||||
|
{ key: 'entry_of_coil_inner_diameter', label: '钢卷内径', placeholder: 'ns=2;s=PL.Entry1.InnerDiameter' },
|
||||||
|
{ key: 'entry_of_coil_outer_diameter', label: '钢卷外径', placeholder: 'ns=2;s=PL.Entry1.OuterDiameter' },
|
||||||
|
{ key: 'alloy_code', label: '合金代码', placeholder: 'ns=2;s=PL.Entry1.AlloyCode' },
|
||||||
|
{ key: 'material', label: '材质', placeholder: 'ns=2;s=PL.Entry1.Material' }
|
||||||
|
],
|
||||||
|
writeNodes: { '1': {}, '2': {} },
|
||||||
nodeList: [],
|
nodeList: [],
|
||||||
status: { running: false, last_counter: null, last_update: null, log: [], track_state: '' },
|
status: { running: false, last_counter: null, last_update: null, log: [], track_state: '' },
|
||||||
statusTimer: null
|
statusTimer: null
|
||||||
@@ -93,6 +145,13 @@ export default {
|
|||||||
this.form.poll_interval = cfg.poll_interval
|
this.form.poll_interval = cfg.poll_interval
|
||||||
this.form.signal1_node = cfg.signal1_node || ''
|
this.form.signal1_node = cfg.signal1_node || ''
|
||||||
this.form.signal2_node = cfg.signal2_node || ''
|
this.form.signal2_node = cfg.signal2_node || ''
|
||||||
|
this.form.write_counter_node = cfg.write_counter_node || ''
|
||||||
|
this.form.write_source_node = cfg.write_source_node || ''
|
||||||
|
this.form.write_target_node = cfg.write_target_node || ''
|
||||||
|
this.writeNodes = {
|
||||||
|
'1': { ...(cfg.write_nodes?.['1'] || {}) },
|
||||||
|
'2': { ...(cfg.write_nodes?.['2'] || {}) }
|
||||||
|
}
|
||||||
this.nodeList = Object.entries(cfg.trackmap_nodes || {}).map(([col, node]) => ({ col, node }))
|
this.nodeList = Object.entries(cfg.trackmap_nodes || {}).map(([col, node]) => ({ col, node }))
|
||||||
} catch (e) { this.$message.error('加载失败: ' + e.message) }
|
} catch (e) { this.$message.error('加载失败: ' + e.message) }
|
||||||
finally { this.loading = false }
|
finally { this.loading = false }
|
||||||
@@ -114,7 +173,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.saving = true
|
this.saving = true
|
||||||
try {
|
try {
|
||||||
await opcApi.saveConfig({ ...this.form, trackmap_nodes })
|
await opcApi.saveConfig({ ...this.form, trackmap_nodes, write_nodes: this.writeNodes })
|
||||||
this.$message.success('配置已保存,OPC服务已重启')
|
this.$message.success('配置已保存,OPC服务已重启')
|
||||||
this.loadStatus()
|
this.loadStatus()
|
||||||
} catch (e) { this.$message.error('保存失败: ' + e.message) }
|
} catch (e) { this.$message.error('保存失败: ' + e.message) }
|
||||||
@@ -134,6 +193,7 @@ export default {
|
|||||||
.hint { font-size:11px; color:#999; margin-left:10px; }
|
.hint { font-size:11px; color:#999; margin-left:10px; }
|
||||||
.node-row { display:flex; align-items:center; gap:8px; margin-bottom:7px; padding:5px 8px; background:#fafafa; border:1px solid #eee; border-radius:2px; }
|
.node-row { display:flex; align-items:center; gap:8px; margin-bottom:7px; padding:5px 8px; background:#fafafa; border:1px solid #eee; border-radius:2px; }
|
||||||
.arrow { font-size:14px; color:#888; font-weight:600; }
|
.arrow { font-size:14px; color:#888; font-weight:600; }
|
||||||
|
.field-label { width: 180px; color:#666; font-size:12px; }
|
||||||
.log-box { background:#fafafa; border:1px solid #e8e8e8; border-radius:2px; padding:8px 12px; height:180px; overflow-y:auto; font-family:monospace; }
|
.log-box { background:#fafafa; border:1px solid #e8e8e8; border-radius:2px; padding:8px 12px; height:180px; overflow-y:auto; font-family:monospace; }
|
||||||
.log-line { font-size:11px; color:#555; line-height:1.7; border-bottom:1px solid #f0f0f0; }
|
.log-line { font-size:11px; color:#555; line-height:1.7; border-bottom:1px solid #f0f0f0; }
|
||||||
</style>
|
</style>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<span style="flex:1"></span>
|
<span style="flex:1"></span>
|
||||||
<el-button size="small" type="warning" @click="triggerSignal1">模拟信号1(入口)</el-button>
|
<el-button size="small" type="warning" @click="triggerSignal1">模拟信号1(入口)</el-button>
|
||||||
<el-button size="small" type="success" @click="triggerSignal2">模拟信号2(焊接完成)</el-button>
|
<el-button size="small" type="success" @click="triggerSignal2">模拟信号2(焊接完成)</el-button>
|
||||||
<span style="font-size:12px;color:#666">提示: Position 1对应产线入口(顺序号1), Position 5对应产线出口(顺序号5)</span>
|
<span style="font-size:12px;color:#666">1.出口 2.酸洗 3.入口活套 4.开卷机 提示:(最小的先进产线)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel" style="padding:0;margin-top:10px">
|
<div class="panel" style="padding:0;margin-top:10px">
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<template slot-scope="{row}">
|
<template slot-scope="{row}">
|
||||||
<el-button type="text" size="mini" @click.stop="openEdit(row)">编辑</el-button>
|
<el-button type="text" size="mini" @click.stop="openEdit(row)">编辑</el-button>
|
||||||
<el-button type="text" size="mini" style="color:#e67e22" @click.stop="moveUp(row)" :disabled="row.position === 1">上移</el-button>
|
<el-button type="text" size="mini" style="color:#e67e22" @click.stop="moveUp(row)" :disabled="row.position === 1">上移</el-button>
|
||||||
<el-button type="text" size="mini" style="color:#e67e22" @click.stop="moveDown(row)" :disabled="row.position === 5">下移</el-button>
|
<el-button type="text" size="mini" style="color:#e67e22" @click.stop="moveDown(row)" :disabled="row.position === 4">下移</el-button>
|
||||||
<el-button type="text" size="mini" style="color:#c0392b" @click.stop="doDelete(row)">删除</el-button>
|
<el-button type="text" size="mini" style="color:#c0392b" @click.stop="doDelete(row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<el-input-number v-model="form.rollprogramnb" :controls="false" style="width:100%" />
|
<el-input-number v-model="form.rollprogramnb" :controls="false" style="width:100%" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="位置" prop="position">
|
<el-form-item label="位置" prop="position">
|
||||||
<el-input-number v-model="form.position" :min="1" :max="5" :controls="false" style="width:100%" />
|
<el-input-number v-model="form.position" :min="1" :max="4" :controls="false" style="width:100%" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div slot="footer">
|
<div slot="footer">
|
||||||
|
|||||||
Reference in New Issue
Block a user