feat: 应用列表按包名合并 + 剪贴板/安装/版本管理显示设备名

两件事都是"列表看不清楚"的问题:

1) **同一个应用传多个版本会并排显示多行**(v1.2 / v1.3 各占一行),看着像重复。
   现在按 **package_name 合并成一个应用一行**,显示最新版:
   - `apk_manager.list_latest()`:版本按 version_code 排序取最新,其余版本放进
     `versions` 字段;解析不出包名的 APK 各自独立成行(不硬合并)
   - 管理页版本列显示 `1.3(另有 1 个旧版本)`,多一个「清理旧版本」按钮
   - 「删除」语义改为**删掉这个应用的所有版本**(行=应用),另给
     `?scope=older` 只清理旧版本;老的单条删除行为保留(不带 scope)
   - **设备端商店也走同一个列表** —— 手机上不会再看到同一个 App 的 v1.2/v1.3 两行

2) **设备只显示地址**(`192.168.20.100:5555`),同型号多台时分不清是哪一台。
   三处补齐设备名(平台给设备起的名字,如 A08),统一「名称 · 地址」:
   - 剪贴板注入的设备勾选列表(`/api/adb/devices` 经 `_merged_device_list` 带 name)
   - 应用管理的安装弹窗(`/api/apks/install/devices` 带 name)
   - 应用版本管理表格(`/api/tools/appver` 结果里带 name)
   - 顺带:安装进度表里的「设备」列原来显示**型号**(而且是 STF 时代残留的空 dict),
     改为显示设备名
   没名字的设备(不在池里)自动回退只显示地址,不显示空占位

验证:上传同包名 v1.1/v1.2/v1.3 三个版本 → 列表只出现一行(v1.3,标注共 4 个版本)、
设备端 bootstrap 也只剩 2 个应用;`scope=older` 只留最新、`scope=package` 整包删除;
三个面板在浏览器里渲染无 JS 报错,devText 有名字/没名字两种形态都正确。
This commit is contained in:
2026-09-13 16:29:38 +08:00
parent fa07ed0804
commit 25e4fbb053
7 changed files with 165 additions and 27 deletions
+12 -2
View File
@@ -7,10 +7,12 @@ _log = get_logger("web")
def _merged_device_list():
"""本地 adb(含 USB)+ 设备池的合并列表([{serial, state}])。
"""本地 adb(含 USB)+ 设备池的合并列表([{serial, name, state}])。
供维护终端/剪贴板注入/应用版本管理等设备选择场景共用。
状态:device/offline=本机 adb 实际状态;pool=设备池已配置但本机未连接。
name 来自设备池(平台给设备起的名字,如 A08)—— 界面上统一按「名称 · 地址」
显示,同型号多台时才分得清是哪一台;池里没有的设备 name 为空,界面回退显示地址。
"""
states = {}
out = _adb("devices")
@@ -18,13 +20,21 @@ def _merged_device_list():
parts = line.split()
if len(parts) >= 2 and parts[0] and not parts[0].startswith("*"):
states[parts[0]] = parts[1]
names = {}
try:
for d in device_pool.list_configured(enabled_only=False):
if d.get("serial"):
names[d["serial"]] = d.get("name") or ""
except Exception:
pass
try:
for serial in device_pool.list_configured():
if serial not in states:
states[serial] = "pool"
except Exception:
pass
return [{"serial": s, "state": st} for s, st in sorted(states.items())]
return [{"serial": s, "name": names.get(s, ""), "state": st}
for s, st in sorted(states.items())]
def _device_screen_state(serial):