增加流量卡状态信息,对流量信息上提进行调整,取消超过500MB进行的警告整行标黄和上提功能,仅保留流量数字标黄

This commit is contained in:
DXC
2026-01-22 10:55:41 +08:00
parent cb567b2c7d
commit 195c3f8fa4
3 changed files with 100 additions and 89 deletions

View File

@ -52,12 +52,11 @@ def check_data_quality(content_data, source_type, data_time_str=None):
if not content_data: if not content_data:
return 'ok' return 'ok'
# 1. [新功能] IoT 卡不需要检查数据质量 # 1. IoT 卡不需要检查数据质量
if str(source_type) == 'iot_card': if str(source_type) == 'iot_card':
return 'ok' return 'ok'
# 2. [新功能] 夜间免打扰逻辑 (08:00 - 17:00 之外不报错) # 2. 夜间免打扰逻辑 (08:00 - 17:00 之外不报错)
# 物理规律:晚上没有太阳,光谱仪数值低是正常的,不应报错
if data_time_str and data_time_str != 'N/A': if data_time_str and data_time_str != 'N/A':
try: try:
clean_time = str(data_time_str).replace('_', '-') clean_time = str(data_time_str).replace('_', '-')
@ -70,27 +69,24 @@ def check_data_quality(content_data, source_type, data_time_str=None):
except: except:
pass pass
# 如果解析成功,且不在 8点-17点之间视为夜晚直接返回 ok
if dt and (dt.hour < 8 or dt.hour >= 17): if dt and (dt.hour < 8 or dt.hour >= 17):
return 'ok' return 'ok'
except: except:
pass pass
# 3. [旧版核心] 数据异常判断逻辑 # 3. 数据异常判断逻辑
status = 'ok' status = 'ok'
source_str = str(source_type) source_str = str(source_type)
# --- Type A: 106 设备逻辑 (CSV格式) --- # --- Type A: 106 设备逻辑 (CSV格式) ---
if '106' in source_str: if '106' in source_str:
try: try:
# 兼容处理:如果 content_data 是字典,尝试取 content 字段;如果是字符串直接用
text_content = "" text_content = ""
if isinstance(content_data, dict): if isinstance(content_data, dict):
text_content = content_data.get('content', str(content_data)) text_content = content_data.get('content', str(content_data))
else: else:
text_content = str(content_data) text_content = str(content_data)
# 只要包含 OSIFBeta 就进行解析
if 'OSIFBeta' in text_content: if 'OSIFBeta' in text_content:
lines = text_content.split('\n') if '\n' in text_content else [text_content] lines = text_content.split('\n') if '\n' in text_content else [text_content]
@ -99,16 +95,12 @@ def check_data_quality(content_data, source_type, data_time_str=None):
continue continue
parts = line.split(',') parts = line.split(',')
# 简单校验列数
if len(parts) < 10: if len(parts) < 10:
continue continue
# 检查积分时间 (Index 2)
try: try:
int_time = int(parts[2]) int_time = int(parts[2])
# 旧代码逻辑:只有积分时间饱和 (>= 66534) 才检查数值
if int_time >= 66534: if int_time >= 66534:
# 数据点通常从第4个(Index 3)开始
data_points = [] data_points = []
for p in parts[3:]: for p in parts[3:]:
try: try:
@ -119,19 +111,16 @@ def check_data_quality(content_data, source_type, data_time_str=None):
if not data_points: if not data_points:
continue continue
# 规则A红色报错 (存在 < 100 的点)
for val in data_points: for val in data_points:
if val < 100: if val < 100:
return 'error' return 'error'
# 规则B黄色警告 (连续 5 个点在 100-500 之间)
consecutive_warning = 0 consecutive_warning = 0
for val in data_points: for val in data_points:
if 100 <= val <= 500: if 100 <= val <= 500:
consecutive_warning += 1 consecutive_warning += 1
if consecutive_warning >= 5: if consecutive_warning >= 5:
status = 'warning' status = 'warning'
# 注意:不立即返回,继续检查后面是否有 error
else: else:
consecutive_warning = 0 consecutive_warning = 0
except: except:
@ -142,7 +131,6 @@ def check_data_quality(content_data, source_type, data_time_str=None):
# --- Type B: 82 设备逻辑 (JSON格式) --- # --- Type B: 82 设备逻辑 (JSON格式) ---
else: else:
try: try:
# 82 设备 content_data 应该已经是字典
if not isinstance(content_data, dict): if not isinstance(content_data, dict):
return 'ok' return 'ok'
@ -153,11 +141,9 @@ def check_data_quality(content_data, source_type, data_time_str=None):
if specs and isinstance(specs, list): if specs and isinstance(specs, list):
consecutive_low = 0 consecutive_low = 0
for val in specs: for val in specs:
# 确保 val 是数字
if not isinstance(val, (int, float)): if not isinstance(val, (int, float)):
continue continue
# 旧代码逻辑: 连续2点 < 500 -> error
if val < 500: if val < 500:
consecutive_low += 1 consecutive_low += 1
if consecutive_low >= 2: if consecutive_low >= 2:
@ -174,57 +160,53 @@ def check_data_quality(content_data, source_type, data_time_str=None):
def save_iot_cards_to_db(card_list): def save_iot_cards_to_db(card_list):
""" """
[核心修复] IoT数据入库逻辑 - 增量更新模式 [核心修复] IoT数据入库逻辑 - 增量更新模式
1. 只操作 source='iot_card' 的记录。 这里负责将 iot_api.py 获取到的新字段保存到数据库的 JSON 字段中
2. 核心:使用 'update' 逻辑而不是 'replace' 逻辑。
即使自动任务运行,也不会弄丢白名单 (is_whitelist) 或其他已存在的数据。
""" """
if not card_list: return 0, None if not card_list: return 0, None
update_count = 0 update_count = 0
try: try:
for card in card_list: for card in card_list:
iccid = card.get('iccid') or card.get('card_id') # 兼容字段名 iccid = card.get('iccid') or card.get('card_id')
if not iccid: continue if not iccid: continue
# 1. 查找是否存在该 SIM 卡记录 # 1. 查找是否存在该 SIM 卡记录
sim_record = Device.query.filter_by(name=iccid, source='iot_card').first() sim_record = Device.query.filter_by(name=iccid, source='iot_card').first()
# 初始化旧数据容器
old_json = {} old_json = {}
if not sim_record: if not sim_record:
# 插入新卡片
sim_record = Device(name=iccid, source='iot_card', install_site="IoT库") sim_record = Device(name=iccid, source='iot_card', install_site="IoT库")
db.session.add(sim_record) db.session.add(sim_record)
db.session.flush() # 立即获取ID db.session.flush()
else: else:
# 旧卡:尝试读取现有 JSON确保不丢失之前的数据
try: try:
if sim_record.json_data: if sim_record.json_data:
old_json = json.loads(sim_record.json_data) old_json = json.loads(sim_record.json_data)
except: except:
old_json = {} old_json = {}
# 2. 准备需要更新的 API 数据 (只更新变动的字段) # 2. 准备需要更新的 API 数据
api_updates = { api_updates = {
"iccid": iccid, "iccid": iccid,
"usedTraffic": str(card.get('usedTraffic') or '0'), "usedTraffic": str(card.get('usedTraffic') or '0'),
"stopDate": card.get('stopDate', 'N/A'), "stopDate": card.get('stopDate', 'N/A'),
"cardStatus": card.get('cardStatus'), "cardStatus": card.get('cardStatus'),
"tag": card.get('tag', '') "tag": card.get('tag', ''),
# === [新增] 这里保存刚才在 iot_api.py 里生成的中文状态描述 ===
"statusDesc": card.get('statusDesc', '未知')
# ========================================================
} }
# 3. [关键步骤] 合并数据 # 3. 合并数据 (保留 is_whitelist)
# 如果 old_json 里有 is_whitelistupdate 不会覆盖它,因为 api_updates 里没有这个key
old_json.update(api_updates) old_json.update(api_updates)
# 4. 兜底保障:如果这是新卡,或者旧卡丢失了 whitelist 字段,默认设为 False
if 'is_whitelist' not in old_json: if 'is_whitelist' not in old_json:
old_json['is_whitelist'] = False old_json['is_whitelist'] = False
# 5. 更新数据库字段 # 4. 更新数据库字段
sim_record.status = str(card.get('cardStatus', '')) sim_record.status = str(card.get('cardStatus', ''))
# 序列化并写回
sim_record.json_data = json.dumps(old_json, ensure_ascii=False) sim_record.json_data = json.dumps(old_json, ensure_ascii=False)
sim_record.check_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") sim_record.check_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@ -258,7 +240,7 @@ def login():
@api_bp.route('/devices_overview', methods=['GET']) @api_bp.route('/devices_overview', methods=['GET'])
def devices_overview(): def devices_overview():
try: try:
# A. 获取 IoT卡表 (source='iot_card') # A. 获取 IoT卡表
iot_records = Device.query.filter_by(source='iot_card').all() iot_records = Device.query.filter_by(source='iot_card').all()
iot_map = {} iot_map = {}
for rec in iot_records: for rec in iot_records:
@ -268,30 +250,24 @@ def devices_overview():
except: except:
pass pass
# B. 获取 真实设备 (source != 'iot_card') # B. 获取 真实设备
devices = Device.query.filter(Device.source != 'iot_card').all() devices = Device.query.filter(Device.source != 'iot_card').all()
data_list = [] data_list = []
for d in devices: for d in devices:
item = d.to_dict() item = d.to_dict()
# =========== 【新增修复】强制格式化时间 =========== # 强制格式化时间
# 无论模型返回什么,这里都强制从数据库原始字段获取,并确保包含时分秒
raw_time = d.latest_time raw_time = d.latest_time
if raw_time: if raw_time:
# 1. 如果是 datetime 对象 (防万一)
if hasattr(raw_time, 'strftime'): if hasattr(raw_time, 'strftime'):
item['latest_time'] = raw_time.strftime("%Y-%m-%d %H:%M:%S") item['latest_time'] = raw_time.strftime("%Y-%m-%d %H:%M:%S")
# 2. 如果是字符串
else: else:
s = str(raw_time).strip() s = str(raw_time).strip()
# 如果只有日期且用下划线 (如 2026_01_14),且没有冒号,则补全
if '_' in s and ':' not in s: if '_' in s and ':' not in s:
item['latest_time'] = s.replace('_', '-') + " 00:00:00" item['latest_time'] = s.replace('_', '-') + " 00:00:00"
else: else:
# 已经是正常字符串(如 2026-01-14 13:49:26直接使用
item['latest_time'] = s item['latest_time'] = s
# ===============================================
parsed_content = {} parsed_content = {}
if d.json_data: if d.json_data:
@ -300,29 +276,34 @@ def devices_overview():
except: except:
pass pass
# --- 绑定逻辑 (将IoT卡信息注入到设备) --- # --- 绑定逻辑 ---
bound_iccid = parsed_content.get('bound_iccid') bound_iccid = parsed_content.get('bound_iccid')
item['usedTraffic'] = None item['usedTraffic'] = None
item['stopDate'] = None item['stopDate'] = None
item['statusDesc'] = None # 初始化字段
item['isBound'] = False item['isBound'] = False
item['bound_iccid'] = bound_iccid item['bound_iccid'] = bound_iccid
item['is_whitelist'] = False # 默认无白名单 item['is_whitelist'] = False
# 如果有绑定,且卡片存在,则注入卡片信息 # 如果有绑定,注入卡片信息
if bound_iccid and bound_iccid in iot_map: if bound_iccid and bound_iccid in iot_map:
card_info = iot_map[bound_iccid] card_info = iot_map[bound_iccid]
item['usedTraffic'] = card_info.get('usedTraffic') item['usedTraffic'] = card_info.get('usedTraffic')
item['stopDate'] = card_info.get('stopDate') item['stopDate'] = card_info.get('stopDate')
item['is_whitelist'] = card_info.get('is_whitelist', False) item['is_whitelist'] = card_info.get('is_whitelist', False)
# === [新增] 将绑定的卡片状态描述传给前端 ===
item['statusDesc'] = card_info.get('statusDesc')
# ======================================
item['isBound'] = True item['isBound'] = True
# [关键] 调用异常检测函数 (check_data_quality)
item['data_quality'] = check_data_quality(parsed_content, d.source, d.latest_time) item['data_quality'] = check_data_quality(parsed_content, d.source, d.latest_time)
data_list.append(item) data_list.append(item)
# C. 必须把 IoT卡表 的数据也传给前端 (用于计算总流量 & 绑定弹窗) # C. IoT卡表数据 (用于卡池管理界面)
for rec in iot_records: for rec in iot_records:
item = rec.to_dict() item = rec.to_dict()
try: try:
@ -333,6 +314,11 @@ def devices_overview():
item['usedTraffic'] = j.get('usedTraffic', '0') item['usedTraffic'] = j.get('usedTraffic', '0')
item['stopDate'] = j.get('stopDate', '') item['stopDate'] = j.get('stopDate', '')
item['is_whitelist'] = j.get('is_whitelist', False) item['is_whitelist'] = j.get('is_whitelist', False)
# === [新增] 将卡池列表中的状态描述传给前端 ===
item['statusDesc'] = j.get('statusDesc', '未知')
# =======================================
item['isOrphanIoT'] = True item['isOrphanIoT'] = True
item['source'] = 'iot_card' item['source'] = 'iot_card'
data_list.append(item) data_list.append(item)
@ -349,7 +335,7 @@ def devices_overview():
@api_bp.route('/device_data_by_date', methods=['GET']) @api_bp.route('/device_data_by_date', methods=['GET'])
def device_data_by_date(): def device_data_by_date():
name = request.args.get('name') name = request.args.get('name')
date_str = request.args.get('date') # 格式: 2026_01_13 或 2026-01-13 date_str = request.args.get('date')
if not name or not date_str: if not name or not date_str:
return jsonify({'code': 400, 'message': 'Missing name or date'}), 400 return jsonify({'code': 400, 'message': 'Missing name or date'}), 400
@ -359,10 +345,8 @@ def device_data_by_date():
return jsonify({'code': 404, 'message': 'Device not found'}), 404 return jsonify({'code': 404, 'message': 'Device not found'}), 404
content = None content = None
# 统一将下划线格式转换为横杠格式进行查询
query_date = date_str.replace('_', '-') query_date = date_str.replace('_', '-')
# 1. 尝试从历史记录表中查找
history_record = DeviceHistory.query.filter( history_record = DeviceHistory.query.filter(
DeviceHistory.device_id == device.id, DeviceHistory.device_id == device.id,
DeviceHistory.data_time.like(f"{query_date}%") DeviceHistory.data_time.like(f"{query_date}%")
@ -370,7 +354,6 @@ def device_data_by_date():
if history_record: if history_record:
content = history_record.json_data content = history_record.json_data
# 2. 如果历史表中没有,查当前 Device 表
elif device.latest_time and device.latest_time.startswith(query_date): elif device.latest_time and device.latest_time.startswith(query_date):
content = device.json_data content = device.json_data
@ -384,7 +367,6 @@ def device_data_by_date():
return jsonify({'code': 404, 'message': 'No data for this date'}), 404 return jsonify({'code': 404, 'message': 'No data for this date'}), 404
# 兼容旧调用的 stub
@api_bp.route('/device_data_by_date_stub', methods=['GET']) @api_bp.route('/device_data_by_date_stub', methods=['GET'])
def device_data_by_date_stub(): def device_data_by_date_stub():
return device_data_by_date() return device_data_by_date()
@ -411,25 +393,21 @@ def run_monitor():
d_name = item.get('name') d_name = item.get('name')
if not d_name: continue if not d_name: continue
# 提取原始信息
d_raw = item.get('raw_json', {}) d_raw = item.get('raw_json', {})
source = item.get('source', '') source = item.get('source', '')
target_time = item.get('target_time') # 默认时间 target_time = item.get('target_time')
# [旧代码逻辑保留] 针对 106 设备,从路径中强制解析正确的时间格式
if '106' in str(source): if '106' in str(source):
try: try:
path_str = d_raw.get('path', '') path_str = d_raw.get('path', '')
# 匹配形如 /Data/2026_01_13/xxx_15_30_00.csv
match = re.search(r'/Data/(\d{4}_\d{2}_\d{2})/\w+_(\d{2}_\d{2}_\d{2})\.csv', path_str) match = re.search(r'/Data/(\d{4}_\d{2}_\d{2})/\w+_(\d{2}_\d{2}_\d{2})\.csv', path_str)
if match: if match:
date_part = match.group(1).replace('_', '-') # 2026-01-13 date_part = match.group(1).replace('_', '-')
time_part = match.group(2).replace('_', ':') # 15:30:00 time_part = match.group(2).replace('_', ':')
target_time = f"{date_part} {time_part}" target_time = f"{date_part} {time_part}"
except: except:
pass pass
# 查找或创建设备
device = Device.query.filter_by(name=d_name).first() device = Device.query.filter_by(name=d_name).first()
if not device: if not device:
device = Device(name=d_name, source=source, install_site="") device = Device(name=d_name, source=source, install_site="")
@ -439,14 +417,11 @@ def run_monitor():
if device.source == 'iot_card': if device.source == 'iot_card':
device.source = source device.source = source
# 更新字段
device.status = item.get('status') device.status = item.get('status')
device.current_value = item.get('value') device.current_value = item.get('value')
device.latest_time = target_time device.latest_time = target_time
device.check_time = current_time device.check_time = current_time
# [关键逻辑] 合并模式 (update),防止覆盖掉 bound_iccid
# 先读取数据库里已有的 json_data
old_json = {} old_json = {}
try: try:
if device.json_data: if device.json_data:
@ -454,16 +429,13 @@ def run_monitor():
except: except:
old_json = {} old_json = {}
# 只有当 raw_json 是字典时才进行合并
new_json = d_raw if isinstance(d_raw, dict) else item.get('raw_json', {}) new_json = d_raw if isinstance(d_raw, dict) else item.get('raw_json', {})
if isinstance(new_json, dict): if isinstance(new_json, dict):
# 使用 update 方法,这样 old_json 里存在的 bound_iccid 不会被删掉
old_json.update(new_json) old_json.update(new_json)
device.json_data = json.dumps(old_json, ensure_ascii=False) device.json_data = json.dumps(old_json, ensure_ascii=False)
device.offset = calculate_offset(device.latest_time) device.offset = calculate_offset(device.latest_time)
# 写入历史记录
new_history = DeviceHistory( new_history = DeviceHistory(
device_id=device.id, device_id=device.id,
status=item.get('status'), status=item.get('status'),
@ -481,7 +453,7 @@ def run_monitor():
# --- B. 执行 IoT 同步 (写入数据库) --- # --- B. 执行 IoT 同步 (写入数据库) ---
if sync_iot_data_service: if sync_iot_data_service:
iot_list = sync_iot_data_service() iot_list = sync_iot_data_service()
# 复用已修复的 save_iot_cards_to_db,确保不会丢失数据 # 复用 save_iot_cards_to_db 保存包含 statusDesc 的新数据
c, e = save_iot_cards_to_db(iot_list) c, e = save_iot_cards_to_db(iot_list)
if e: if e:
msg_list.append(f"IoT错: {e}") msg_list.append(f"IoT错: {e}")
@ -497,7 +469,7 @@ def run_monitor():
# ========================================================= # =========================================================
# 4. 白名单、绑定与设备管理 (新功能) # 4. 白名单、绑定与设备管理
# ========================================================= # =========================================================
@api_bp.route('/toggle_whitelist', methods=['POST']) @api_bp.route('/toggle_whitelist', methods=['POST'])

View File

@ -173,29 +173,37 @@ def sync_iot_data_service():
1. 登录 1. 登录
2. 遍历所有分页获取 ICCID 2. 遍历所有分页获取 ICCID
3. 批量查询详情 3. 批量查询详情
4. 返回完整数据列表 (List[Dict]) 4. 解析 cardStatus 状态码
5. 返回完整数据列表 (List[Dict])
""" """
print("[IoT Service] 开始同步任务...") print("[IoT Service] 开始同步任务...")
# 1. 登录 # 1. 定义状态码映射表 (根据提供的需求文档)
STATUS_MAP = {
"1": "测试期",
"2": "沉默期",
"3": "在使用",
"4": "停机",
"5": "停机保号",
"6": "销户"
}
token = get_access_token() token = get_access_token()
if not token: if not token:
return [] return []
# 2. 循环翻页获取所有 ICCID
all_iccids = [] all_iccids = []
page_no = 1 page_no = 1
page_size = 100 page_size = 100
# ✅ 2. 循环翻页获取所有 ICCID
while True: while True:
res = get_iot_card_page(token, page_no, page_size) res = get_iot_card_page(token, page_no, page_size)
# 校验响应
if not res or (res.get('code') != 0 and res.get('code') != 200): if not res or (res.get('code') != 0 and res.get('code') != 200):
print(f"[IoT Service] 列表获取结束或中断: {res.get('msg') if res else 'No Response'}") print(f"[IoT Service] 列表获取结束或中断: {res.get('msg') if res else 'No Response'}")
break break
# 解析数据结构 (兼容 data 为 list 或 data.rows)
data_field = res.get('data', {}) data_field = res.get('data', {})
rows = [] rows = []
if isinstance(data_field, list): if isinstance(data_field, list):
@ -206,43 +214,47 @@ def sync_iot_data_service():
if not rows: if not rows:
break break
# 提取 ICCID
current_batch = [str(x.get('iccid')) for x in rows if x.get('iccid')] current_batch = [str(x.get('iccid')) for x in rows if x.get('iccid')]
all_iccids.extend(current_batch) all_iccids.extend(current_batch)
# print(f"DEBUG: page {page_no} done, items: {len(current_batch)}")
# 判断是否最后一页
if len(rows) < page_size: if len(rows) < page_size:
break break
page_no += 1 page_no += 1
time.sleep(0.2) # 避免请求过快 time.sleep(0.2)
total_count = len(all_iccids) total_count = len(all_iccids)
if total_count == 0: if total_count == 0:
print("[IoT Service] 未找到任何卡片") print("[IoT Service] 未找到任何卡片")
return [] return []
# 3. 分批查询详情 # 3. 分批查询详情并处理状态
final_data_list = [] final_data_list = []
batch_size = 50 batch_size = 50
# print(f"DEBUG: 开始查询 {total_count} 张卡的详情...")
for i in range(0, total_count, batch_size): for i in range(0, total_count, batch_size):
batch_iccids = all_iccids[i: i + batch_size] batch_iccids = all_iccids[i: i + batch_size]
detail_res = get_iot_card_details_batch(token, batch_iccids) detail_res = get_iot_card_details_batch(token, batch_iccids)
if detail_res and (detail_res.get('code') == 0 or detail_res.get('code') == 200): if detail_res and (detail_res.get('code') == 0 or detail_res.get('code') == 200):
details = detail_res.get('data', []) details = detail_res.get('data', [])
if isinstance(details, list): if isinstance(details, list):
final_data_list.extend(details)
# === 核心修改:增加状态解析逻辑 ===
for card in details:
# 获取原始状态码 (如 "3")
raw_status = str(card.get('cardStatus', ''))
# 匹配中文描述 (如 "在使用")
status_desc = STATUS_MAP.get(raw_status, "未知状态")
# 将描述写入新字段,前端可直接取用 card.statusDesc
card['statusDesc'] = status_desc
final_data_list.append(card)
# =================================
time.sleep(0.2) time.sleep(0.2)
print(f"[IoT Service] 同步完成,共获取 {len(final_data_list)} 条详情数据") print(f"[IoT Service] 同步完成,共获取 {len(final_data_list)} 条详情数据")
# 4. 返回列表供 api.py 写入数据库
return final_data_list return final_data_list

View File

@ -136,6 +136,22 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="卡状态" width="110" align="center">
<template #default="{ row }">
<div v-if="row.isBound">
<el-tag
:type="getCardStatusType(row.statusDesc)"
effect="light"
size="small"
style="font-weight: bold;"
>
{{ row.statusDesc || '未知' }}
</el-tag>
</div>
<span v-else style="color: #ccc;">--</span>
</template>
</el-table-column>
<el-table-column label="服务截止" width="140"> <el-table-column label="服务截止" width="140">
<template #default="{ row }"> <template #default="{ row }">
<div v-if="row.isBound && row.stopDate"> <div v-if="row.isBound && row.stopDate">
@ -277,6 +293,15 @@ const showAddDialog = ref(false)
const isAdding = ref(false) const isAdding = ref(false)
const newDeviceForm = reactive({ name: '', site: '' }) const newDeviceForm = reactive({ name: '', site: '' })
// === 辅助函数:根据中文状态返回 Tag 颜色 ===
const getCardStatusType = (status) => {
if (status === '在使用') return 'success' // 绿色
if (status === '停机' || status === '销户') return 'danger' // 红色
if (status === '停机保号' || status === '沉默期') return 'warning' // 黄色
if (status === '测试期') return 'info' // 灰色
return 'info' // 默认
}
// === 核心数据处理逻辑 === // === 核心数据处理逻辑 ===
const fetchData = async () => { const fetchData = async () => {
loading.value = true loading.value = true
@ -367,7 +392,9 @@ const fetchData = async () => {
if (isNaN(trafficNum)) trafficNum = 0 if (isNaN(trafficNum)) trafficNum = 0
} }
// === 修改处:恢复流量超标警告判断,用于标黄 ===
const trafficWarning = (trafficNum >= 500 && !isWhitelist) const trafficWarning = (trafficNum >= 500 && !isWhitelist)
let expireWarning = false let expireWarning = false
if (item.stopDate && item.stopDate !== 'N/A') { if (item.stopDate && item.stopDate !== 'N/A') {
const stopD = new Date(item.stopDate.replace(/_/g, '-')) const stopD = new Date(item.stopDate.replace(/_/g, '-'))
@ -395,10 +422,10 @@ const fetchData = async () => {
} else if (diffHours > 24) { } else if (diffHours > 24) {
statusLabel = '滞后'; statusColor = '#E6A23C'; statusType = 'warning'; statusLabel = '滞后'; statusColor = '#E6A23C'; statusType = 'warning';
statusReason = `滞后 ${Math.floor(diffDays)}`; statusReason = `滞后 ${Math.floor(diffDays)}`;
} else if (trafficWarning) {
statusLabel = '流量警告'; statusColor = '#E6A23C'; statusType = 'warning'; // === 注意:这里没有把 trafficWarning 加入到 sortWeight 或 statusType 的改变逻辑中 ===
statusReason = `流量超标`; // 从而实现了“只标黄文字,不改变行状态,不置顶”
sortWeight = 500;
} else if (expireWarning) { } else if (expireWarning) {
statusLabel = '即将过期'; statusColor = '#E6A23C'; statusType = 'warning'; statusLabel = '即将过期'; statusColor = '#E6A23C'; statusType = 'warning';
statusReason = `即将过期`; statusReason = `即将过期`;
@ -412,7 +439,7 @@ const fetchData = async () => {
return { return {
...item, ...item,
latest_time: displayTime, // <--- 这里使用了我们格式化好的漂亮时间 latest_time: displayTime,
is_hidden: isHidden, is_hidden: isHidden,
isOrphanIoT, isOrphanIoT,
isBound, isBound,