Compare commits
28 Commits
f41d4186ea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a88904f79 | ||
|
|
e828abe32f | ||
|
|
daaf651c04 | ||
|
|
8d5c37039d | ||
|
|
6fb7161e80 | ||
|
|
542c35b22d | ||
|
|
a909f96d61 | ||
|
|
00f2dc98dd | ||
|
|
2ff052c6e2 | ||
|
|
1b6f14da09 | ||
|
|
42c0e26bde | ||
|
|
39b9669a87 | ||
|
|
1488547220 | ||
|
|
d738a4acfe | ||
|
|
a58845121b | ||
|
|
a4167c3cf3 | ||
|
|
9db20749e2 | ||
|
|
eb0fb12c8d | ||
|
|
2996e670e0 | ||
|
|
36d74c3f0d | ||
|
|
9e545ee0ec | ||
|
|
66c7c0f4f6 | ||
|
|
075b6bf1ea | ||
|
|
d1d995c698 | ||
|
|
fa6c3f473e | ||
|
|
2c8fa689a5 | ||
|
|
b22d55a380 | ||
|
|
7f1a413172 |
@@ -183,14 +183,16 @@ if (DeviceUtil.isPhone()) viewModel.initPhoneExtras()
|
||||
手机端首页是 `module_p` 的 `PDAEnterActivity`,「国际」Tab 的菜单在
|
||||
`module_p/.../ui/enter/gj/GjViewModel.kt`(国内在 `gn/GnViewModel.kt`)。
|
||||
**菜单是手写清单,不是接口下发**;权限串只做过滤(`authList.contains(bean.key)`)。
|
||||
2026-10-21 起清单按 `documents/5.5寸首页新样式/` 设计稿分组(`PdaMenuGroup`),新入口加进对应分组。
|
||||
|
||||
适配完页面不回来加一行,手机上就**根本没有入口**——出库交接第一版就漏了这步,
|
||||
只能靠 adb 直启才能进去。加法:
|
||||
|
||||
```kotlin
|
||||
// 加在 GjViewModel.groups 的「国际出港」分组里
|
||||
ActionBean(
|
||||
"出港移库",
|
||||
R.drawable.gjc_yi_ku_icon, // 图标复用 Pad 同款,复制 PNG 到 module_p/res/drawable-xxhdpi/
|
||||
R.drawable.img_pda_menu_gjc_move, // 设计稿宫格图标(icons/ 源图转 PNG 放 drawable-nodpi,放 xxhdpi 会糊);没有新图标的沿用旧图标
|
||||
Constant.AuthName.GjcYiKuListActivity, // 权限串与 Pad 端菜单一致
|
||||
ARouterConstants.ACTIVITY_URL_INT_EXP_MOVE // 填了 route 就走通用跳转,无需 when 分支
|
||||
),
|
||||
|
||||
83
.claude/skills/zentao-bug/SKILL.md
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
name: zentao-bug
|
||||
description: 读取禅道(njlkairport.chandao.net,开源版 18.8)中的 Bug 单——我的 Bug 列表、单个 Bug 的标题/描述/截图/评论/修改历史/附件——并按 AirLogistics 项目规范定位代码完成修复,也可在禅道上解决、指派 Bug(如转给后端并附问题说明)。禅道 18.8 没有 API 2.0,官方 zentao-cli / MCP 不可用,必须用本 skill 的 zentao.py。当用户提到"禅道"、"bug 单"、"我的 bug"、"修 bug"、"处理 bug"、给出 Bug 编号(如 "#632"、"bug 632")或禅道链接(bug-view-xxx.html / my-work-bug.html)时使用。
|
||||
---
|
||||
|
||||
# 禅道 Bug 读取与修复
|
||||
|
||||
## 工具
|
||||
|
||||
```bash
|
||||
Z=.claude/skills/zentao-bug/zentao.py
|
||||
python3 $Z whoami # 当前账号 + 会话是否有效
|
||||
python3 $Z list # 指派给我的 Bug(= 网页「地盘 → 我的Bug」)
|
||||
python3 $Z list --status active # 只看激活的
|
||||
python3 $Z list --type resolvedBy # 由我解决 / openedBy / closedBy
|
||||
python3 $Z list --json # 结构化输出
|
||||
python3 $Z view 632 # Bug 详情(Markdown)
|
||||
```
|
||||
|
||||
`view` 输出并保存到 `~/.cache/zentao-lite/bugs/<id>/bug.md`:基本信息、重现步骤、附件、历史记录与评论(评论以 `>` 引用块呈现)。
|
||||
描述和评论中的截图下载到 `images/`,文中以绝对路径 `` 引用。
|
||||
|
||||
**截图是需求的主要载体**(团队习惯把原型/标注贴在描述和评论里,几乎不用附件),**必须逐张用 Read 查看**,不能只看文字。
|
||||
|
||||
## 登录(只能由用户本人执行)
|
||||
|
||||
退出码 2 表示未登录或会话过期。禅道会话默认空闲 24 分钟失效。让用户在自己的终端执行:
|
||||
|
||||
```bash
|
||||
python3 .claude/skills/zentao-bug/zentao.py login --account <账号>
|
||||
```
|
||||
|
||||
必须在**独立的终端窗口**里执行——Claude Code 的 `! <命令>` 没有 TTY,密码提示会直接 EOFError。
|
||||
密码交互输入、不回显;默认存入 macOS 钥匙串,此后会话过期会自动重新登录(`--no-keychain` 则不保存)。
|
||||
**不要向用户索要密码,也不要替用户在命令行里带密码登录。**
|
||||
|
||||
## 修复流程
|
||||
|
||||
1. **读单**:`view <id>`,Read 全部截图;评论里常有后续补充或改口,以最新评论为准。
|
||||
2. **定位页面**:Bug 标题/描述通常写的是菜单名(如"出港移库""进港仓库")。按 CLAUDE.md「页面定位规则」,
|
||||
以首页菜单实际跳转的 Activity 为准(平板 `app/.../HomeFragment.kt`,手机 `module_p` 的 `GnViewModel`/`GjViewModel`)。
|
||||
3. **判断端**:截图是手机竖屏(如 5.5 寸设计稿样式)还是平板横屏,决定改 `res/layout/` 还是 `res/layout-sw600dp/`;
|
||||
业务逻辑改动两端都要检查(CLAUDE.md「业务以平板为准」)。截图提到"原型"时对照 `documents/5.5寸手持端设计文件/`。
|
||||
4. **修改并验证**:遵守 CLAUDE.md 的 UI 规范;`./gradlew assembleDebug` 编译通过;能在模拟器验证的就验证并截图比对。
|
||||
5. **汇报**:按 Bug 逐条说明改了什么、改在哪里、如何验证、有无未覆盖项。多个 Bug 时每个 Bug 单独提交,
|
||||
提交信息带 `Bug #<id>`。不确定截图意图时先问用户,禁止臆测。
|
||||
|
||||
## 写回禅道
|
||||
|
||||
```bash
|
||||
python3 $Z resolve 621 622 --comment 已解决 --dry-run # 先预览
|
||||
python3 $Z resolve 621 622 --comment 已解决 # 提交
|
||||
```
|
||||
|
||||
等同网页「解决」表单,默认:解决方案 `fixed`(已解决)、解决版本 `trunk`(主干);**解决日期与指派给取网页表单渲染出的默认值**
|
||||
(服务器当前时间 / 创建人,可用 `--date` / `--assign-to` 覆盖),其余字段不动;备注默认留空,团队习惯只写「已解决」,不写业务细节。
|
||||
只处理激活状态的单子,提交后回读校验状态。
|
||||
|
||||
- **写操作会改动线上状态,只在用户明确要求时执行**;只解决确实修好、且没有待确认疑问的单子。
|
||||
无缺陷 / 需后端修复 / 有疑问的保持激活,并向用户列出原因(无缺陷应选「设计如此」等其他方案,不要标「已解决」)
|
||||
- 接口提交**不会**自动补表单默认值,所以先 GET 解决表单 HTML 读出 `resolvedDate` 与 `assignedTo` 的选中项再原样提交;
|
||||
解决日期不能用本机时间:本机可能在美西时区,禅道服务器是北京时间(#621 曾因此写成本机时间)
|
||||
|
||||
### 指派(转给后端等)
|
||||
|
||||
```bash
|
||||
python3 $Z assign 627 --to 陶雨佳 --comment-file /path/c627.txt --dry-run # 先预览
|
||||
python3 $Z assign 627 --to 陶雨佳 --comment-file /path/c627.txt # 提交
|
||||
```
|
||||
|
||||
等同网页「指派」表单:`--to` 接受账号(如 `TYJ`)或姓名,状态保持激活,提交后回读校验指派人。
|
||||
需后端修复的单子转给后端时,备注照搬 `documents/后端接口对接问题清单.xlsx` 对应行
|
||||
(接口地址 / 问题类型 / 问题描述 / 期望调整 / 前端现状),并注明清单序号;多行备注用 `--comment-file`,换行会转成 `<br />`。
|
||||
|
||||
## 实现说明
|
||||
|
||||
- 认证:`POST /api.php/v1/tokens` 换 Token;数据走页面 JSON 模式(`.html` → `.json`),Cookie `zentaosid=<Token>`。
|
||||
- 接口:`/my-work-bug-<type>-0-id_desc-0-<perPage>-<page>.json`、`/bug-view-<id>.json`、`/file-read-<fileID>.<ext>`;
|
||||
解决:GET `/bug-resolve-<id>.json` 取版本/用户选项,POST `/bug-resolve-<id>.html?onlybody=yes` 表单
|
||||
(resolution/resolvedBuild/resolvedDate/assignedTo/status=resolved/comment);
|
||||
指派:GET `/bug-assignTo-<id>.json` 取用户,POST `/bug-assignTo-<id>.html?onlybody=yes` 表单(assignedTo/status/mailto[]/comment)。
|
||||
- REST v1 的 `/products/<id>/bugs` 只返回激活中的 Bug,不能替代"我的 Bug"页面。
|
||||
- 凭据:Token 在 `~/.config/zentao-lite/config.json`(0600),密码只在钥匙串(服务名 `zentao-lite`)。`logout` 一并清除。
|
||||
678
.claude/skills/zentao-bug/zentao.py
Normal file
@@ -0,0 +1,678 @@
|
||||
#!/usr/bin/env python3
|
||||
"""禅道 18.x Bug 读取工具(仅依赖 Python 标准库)。
|
||||
|
||||
禅道 18.8 没有 RESTful API 2.0,官方 zentao-cli 无法使用;本脚本组合两条 18.x 可用的通道:
|
||||
- 认证:POST /api.php/v1/tokens 换取 Token(即会话 ID)
|
||||
- 数据:页面 JSON 模式(把页面地址 .html 换成 .json),Cookie 带 zentaosid=<Token>
|
||||
- 我的 Bug:/my-work-bug-<type>-0-id_desc-0-<perPage>-<page>.json
|
||||
- Bug 详情:/bug-view-<id>.json(含描述、修改记录、评论)
|
||||
- 图片:/file-read-<fileID>.<ext>
|
||||
|
||||
凭据:Token 存 ~/.config/zentao-lite/config.json(0600);密码只存 macOS 钥匙串,
|
||||
用于会话过期(禅道默认 24 分钟空闲失效)后自动重新登录,任何文件里都不落明文密码。
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import getpass
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from html.parser import HTMLParser
|
||||
from pathlib import Path
|
||||
|
||||
CONFIG_DIR = Path(os.environ.get("ZENTAO_LITE_HOME", Path.home() / ".config" / "zentao-lite"))
|
||||
CONFIG_FILE = CONFIG_DIR / "config.json"
|
||||
DEFAULT_OUT = Path.home() / ".cache" / "zentao-lite" / "bugs"
|
||||
KEYCHAIN_SERVICE = "zentao-lite"
|
||||
DEFAULT_URL = "https://njlkairport.chandao.net"
|
||||
|
||||
STATUS = {"active": "激活", "resolved": "已解决", "closed": "已关闭"}
|
||||
TYPES = {
|
||||
"codeerror": "代码错误", "config": "配置相关", "install": "安装部署", "security": "安全相关",
|
||||
"performance": "性能问题", "standard": "标准规范", "automation": "测试脚本",
|
||||
"designdefect": "设计缺陷", "others": "其他",
|
||||
}
|
||||
RESOLUTIONS = {
|
||||
"fixed": "已解决", "bydesign": "设计如此", "duplicate": "重复Bug", "external": "外部原因",
|
||||
"notrepro": "无法重现", "postponed": "延期处理", "willnotfix": "不予解决", "tostory": "转为需求",
|
||||
}
|
||||
ACTIONS = {
|
||||
"opened": "创建", "edited": "编辑", "commented": "备注", "resolved": "解决", "activated": "激活",
|
||||
"closed": "关闭", "assigned": "指派", "confirmed": "确认", "bugconfirmed": "确认",
|
||||
"tostory": "转需求", "totask": "转任务", "linked2build": "关联版本", "linked2release": "关联发布",
|
||||
}
|
||||
FIELDS = {
|
||||
"steps": "重现步骤", "title": "标题", "assignedTo": "指派给", "status": "状态", "severity": "严重程度",
|
||||
"pri": "优先级", "type": "类型", "resolution": "解决方案", "resolvedBuild": "解决版本",
|
||||
"module": "模块", "deadline": "截止日期", "keywords": "关键词", "openedBuild": "影响版本",
|
||||
"resolvedBy": "解决者", "resolvedDate": "解决日期", "closedBy": "关闭者", "closedDate": "关闭日期",
|
||||
"confirmed": "是否确认", "mailto": "抄送给", "linkBug": "相关Bug", "activatedCount": "激活次数",
|
||||
}
|
||||
USER_FIELDS = {"assignedTo", "resolvedBy", "closedBy", "openedBy"}
|
||||
BUG_TYPES = ("assignedTo", "openedBy", "resolvedBy", "closedBy")
|
||||
|
||||
|
||||
class AuthError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def die(msg, code=1):
|
||||
print(f"错误:{msg}", file=sys.stderr)
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 配置与凭据
|
||||
|
||||
def load_config():
|
||||
if not CONFIG_FILE.exists():
|
||||
die("尚未登录,请先执行:python3 .claude/skills/zentao-bug/zentao.py login", 2)
|
||||
return json.loads(CONFIG_FILE.read_text())
|
||||
|
||||
|
||||
def save_config(cfg):
|
||||
CONFIG_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
tmp = CONFIG_FILE.with_suffix(".tmp")
|
||||
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
json.dump(cfg, f, ensure_ascii=False, indent=2)
|
||||
os.replace(tmp, CONFIG_FILE)
|
||||
|
||||
|
||||
def keychain_get(account):
|
||||
if sys.platform != "darwin":
|
||||
return None
|
||||
r = subprocess.run(
|
||||
["security", "find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account, "-w"],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
return r.stdout.rstrip("\n") if r.returncode == 0 else None
|
||||
|
||||
|
||||
def keychain_set(account, password):
|
||||
if sys.platform != "darwin":
|
||||
return False
|
||||
r = subprocess.run(
|
||||
["security", "add-generic-password", "-U", "-s", KEYCHAIN_SERVICE, "-a", account, "-w", password],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
return r.returncode == 0
|
||||
|
||||
|
||||
def keychain_delete(account):
|
||||
if sys.platform == "darwin":
|
||||
subprocess.run(["security", "delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account],
|
||||
capture_output=True)
|
||||
|
||||
|
||||
def request_token(url, account, password):
|
||||
body = json.dumps({"account": account, "password": password}).encode()
|
||||
req = urllib.request.Request(url + "/api.php/v1/tokens", data=body,
|
||||
headers={"Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
data = json.load(resp)
|
||||
except urllib.error.HTTPError as e:
|
||||
try:
|
||||
data = json.load(e)
|
||||
except Exception:
|
||||
data = {"error": f"HTTP {e.code}"}
|
||||
if not data.get("token"):
|
||||
raise AuthError(data.get("error") or "登录失败")
|
||||
return data["token"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 请求
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.url = cfg["url"].rstrip("/")
|
||||
self.relogged = False
|
||||
|
||||
def _relogin(self):
|
||||
if self.relogged:
|
||||
raise AuthError("重新登录后仍无权限")
|
||||
password = keychain_get(self.cfg["account"])
|
||||
if not password:
|
||||
raise AuthError("会话已过期,且钥匙串中没有保存密码,请重新执行 login")
|
||||
self.cfg["token"] = request_token(self.url, self.cfg["account"], password)
|
||||
save_config(self.cfg)
|
||||
self.relogged = True
|
||||
|
||||
def _open(self, path):
|
||||
req = urllib.request.Request(self.url + path, headers={
|
||||
"Cookie": f"zentaosid={self.cfg['token']}", "Token": self.cfg["token"],
|
||||
})
|
||||
try:
|
||||
return urllib.request.urlopen(req, timeout=60)
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 401:
|
||||
raise AuthError("Token 无效")
|
||||
raise
|
||||
|
||||
def page(self, path):
|
||||
"""读取页面 JSON;会话失效时自动用钥匙串密码重登一次。"""
|
||||
for _ in range(2):
|
||||
try:
|
||||
with self._open(path) as resp:
|
||||
outer = json.load(resp)
|
||||
data = outer.get("data")
|
||||
if isinstance(data, str):
|
||||
data = json.loads(data)
|
||||
if outer.get("status") != "success" or (isinstance(data, dict) and "user-login" in str(data.get("locate", ""))):
|
||||
raise AuthError("会话已过期")
|
||||
return data
|
||||
except AuthError:
|
||||
self._relogin()
|
||||
raise AuthError("无法获取数据")
|
||||
|
||||
def post_form(self, path, fields):
|
||||
"""提交页面表单(与网页点击「保存」相同);返回响应 HTML(禅道用 <script>alert/parent 跳转回执)。"""
|
||||
body = urllib.parse.urlencode(fields).encode()
|
||||
for _ in range(2):
|
||||
req = urllib.request.Request(self.url + path, data=body, headers={
|
||||
"Cookie": f"zentaosid={self.cfg['token']}", "Token": self.cfg["token"],
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Referer": self.url + path,
|
||||
})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||
text = resp.read().decode("utf-8", "replace")
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code != 401:
|
||||
raise
|
||||
text = "user-login"
|
||||
if "user-login" not in text:
|
||||
return text
|
||||
self._relogin()
|
||||
raise AuthError("无法提交表单")
|
||||
|
||||
def download(self, path, dest):
|
||||
for _ in range(2):
|
||||
try:
|
||||
with self._open(path) as resp:
|
||||
ctype = resp.headers.get("Content-Type", "")
|
||||
content = resp.read()
|
||||
if "text/html" in ctype or "application/json" in ctype:
|
||||
if b"user-login" in content:
|
||||
raise AuthError("会话已过期")
|
||||
raise RuntimeError(f"返回的不是文件内容({ctype})")
|
||||
dest.write_bytes(content)
|
||||
return
|
||||
except AuthError:
|
||||
self._relogin()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- HTML → Markdown
|
||||
|
||||
class Html2Md(HTMLParser):
|
||||
BLOCK = {"p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "tr", "table", "blockquote", "pre"}
|
||||
|
||||
def __init__(self, on_image):
|
||||
super().__init__(convert_charrefs=True)
|
||||
self.out = []
|
||||
self.lists = []
|
||||
self.on_image = on_image
|
||||
self.href = None
|
||||
self.skip = 0
|
||||
|
||||
def _nl(self, n=1):
|
||||
text = "".join(self.out)
|
||||
have = len(text) - len(text.rstrip("\n"))
|
||||
if text.strip():
|
||||
self.out.append("\n" * max(0, n - have))
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
a = dict(attrs)
|
||||
if tag in ("style", "script"):
|
||||
self.skip += 1
|
||||
elif tag in self.BLOCK:
|
||||
self._nl(2 if tag != "tr" else 1)
|
||||
if tag[0] == "h" and tag[1:].isdigit():
|
||||
self.out.append("**") # 标题降级为加粗,避免与外层 Markdown 标题层级混淆
|
||||
elif tag == "br":
|
||||
self.out.append("\n")
|
||||
elif tag in ("ul", "ol"):
|
||||
self._nl(1)
|
||||
self.lists.append([tag, 0])
|
||||
elif tag == "li":
|
||||
self._nl(1)
|
||||
indent = " " * max(0, len(self.lists) - 1)
|
||||
if self.lists and self.lists[-1][0] == "ol":
|
||||
self.lists[-1][1] += 1
|
||||
self.out.append(f"{indent}{self.lists[-1][1]}. ")
|
||||
else:
|
||||
self.out.append(f"{indent}- ")
|
||||
elif tag in ("strong", "b"):
|
||||
self.out.append("**")
|
||||
elif tag == "td" or tag == "th":
|
||||
self.out.append("| ")
|
||||
elif tag == "a":
|
||||
self.href = a.get("href")
|
||||
self.out.append("[")
|
||||
elif tag == "img":
|
||||
src = a.get("src") or a.get("data-src") or ""
|
||||
if src:
|
||||
self.out.append(self.on_image(src))
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag in ("style", "script"):
|
||||
self.skip = max(0, self.skip - 1)
|
||||
elif tag in self.BLOCK:
|
||||
if tag[0] == "h" and tag[1:].isdigit():
|
||||
self.out.append("**")
|
||||
self._nl(2 if tag != "tr" else 1)
|
||||
elif tag in ("ul", "ol"):
|
||||
if self.lists:
|
||||
self.lists.pop()
|
||||
self._nl(2)
|
||||
elif tag in ("strong", "b"):
|
||||
self.out.append("**")
|
||||
elif tag in ("td", "th"):
|
||||
self.out.append(" ")
|
||||
elif tag == "a":
|
||||
self.out.append(f"]({self.href})" if self.href else "]")
|
||||
self.href = None
|
||||
|
||||
def handle_data(self, data):
|
||||
if not self.skip:
|
||||
self.out.append(re.sub(r"[ \t\r\n]+", " ", data) if not data.isspace() else (" " if self.out and not self.out[-1].endswith(("\n", " ")) else ""))
|
||||
|
||||
def markdown(self):
|
||||
text = "".join(self.out).replace("****", "")
|
||||
text = "\n".join(line.rstrip() for line in text.splitlines())
|
||||
return re.sub(r"\n{3,}", "\n\n", text).strip()
|
||||
|
||||
|
||||
class ImageFetcher:
|
||||
"""把富文本中的图片下载到本地,返回 Markdown 图片引用(绝对路径,可直接用 Read 查看)。"""
|
||||
|
||||
def __init__(self, client, img_dir):
|
||||
self.client = client
|
||||
self.dir = img_dir
|
||||
self.files = []
|
||||
self.errors = []
|
||||
|
||||
def __call__(self, src):
|
||||
src = html.unescape(src.strip())
|
||||
m = re.fullmatch(r"\{(\d+)\.(\w+)\}", src) # 数据库原始占位符 {1154.jpeg}
|
||||
if m:
|
||||
src = f"/file-read-{m.group(1)}.{m.group(2)}"
|
||||
try:
|
||||
dest = self._fetch(src)
|
||||
except Exception as e: # 单张图片失败不影响整体
|
||||
self.errors.append(f"{src}:{e}")
|
||||
return f""
|
||||
if dest not in self.files:
|
||||
self.files.append(dest)
|
||||
return f""
|
||||
|
||||
def _fetch(self, src):
|
||||
self.dir.mkdir(parents=True, exist_ok=True)
|
||||
if src.startswith("data:"):
|
||||
head, b64 = src.split(",", 1)
|
||||
ext = re.search(r"image/(\w+)", head)
|
||||
dest = self.dir / f"inline-{len(self.files) + 1}.{ext.group(1) if ext else 'png'}"
|
||||
dest.write_bytes(base64.b64decode(b64))
|
||||
return dest
|
||||
parsed = urllib.parse.urlparse(src)
|
||||
if parsed.netloc and parsed.netloc not in self.client.url:
|
||||
dest = self.dir / Path(parsed.path).name
|
||||
if not dest.exists():
|
||||
urllib.request.urlretrieve(src, dest)
|
||||
return dest
|
||||
path = parsed.path + (f"?{parsed.query}" if parsed.query else "")
|
||||
m = re.search(r"file-read-(\d+)(?:\.(\w+))?", path)
|
||||
name = f"{m.group(1)}.{m.group(2) or 'bin'}" if m else Path(parsed.path).name
|
||||
dest = self.dir / name
|
||||
if not dest.exists() or dest.stat().st_size == 0:
|
||||
self.client.download(path, dest)
|
||||
return dest
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 命令
|
||||
|
||||
def cmd_login(args):
|
||||
url = (args.url or os.environ.get("ZENTAO_URL") or DEFAULT_URL).rstrip("/")
|
||||
account = args.account or os.environ.get("ZENTAO_ACCOUNT") or input("禅道账号:").strip()
|
||||
password = os.environ.get("ZENTAO_PASSWORD") or getpass.getpass("禅道密码(输入不回显):")
|
||||
try:
|
||||
token = request_token(url, account, password)
|
||||
except AuthError as e:
|
||||
die(f"登录失败:{e}")
|
||||
cfg = {"url": url, "account": account, "token": token}
|
||||
save_config(cfg)
|
||||
saved = False if args.no_keychain else keychain_set(account, password)
|
||||
print(f"登录成功:{account} @ {url}")
|
||||
print(f"Token 已保存到 {CONFIG_FILE}(权限 0600)")
|
||||
print("密码已存入 macOS 钥匙串,会话过期时自动重新登录" if saved
|
||||
else "未保存密码:会话过期(默认 24 分钟空闲)后需重新 login")
|
||||
|
||||
|
||||
def cmd_logout(args):
|
||||
if CONFIG_FILE.exists():
|
||||
keychain_delete(json.loads(CONFIG_FILE.read_text()).get("account", ""))
|
||||
CONFIG_FILE.unlink()
|
||||
print("已退出并清除本地 Token 与钥匙串密码")
|
||||
|
||||
|
||||
def cmd_whoami(args):
|
||||
client = Client(load_config())
|
||||
data = client.page("/my-work-bug-assignedTo-0-id_desc-0-1-1.json") # 顺带验证会话可用
|
||||
users = data.get("users", {})
|
||||
account = client.cfg["account"]
|
||||
print(f"{account}({users.get(account, '')}) @ {client.url},会话有效")
|
||||
|
||||
|
||||
def fetch_bug_list(client, bug_type, per_page=100):
|
||||
bugs, page, users = [], 1, {}
|
||||
while True:
|
||||
data = client.page(f"/my-work-bug-{bug_type}-0-id_desc-0-{per_page}-{page}.json")
|
||||
users.update(data.get("users") or {})
|
||||
bugs.extend(data.get("bugs") or [])
|
||||
pager = data.get("pager") or {}
|
||||
if page >= int(pager.get("pageTotal") or 1):
|
||||
return bugs, users
|
||||
page += 1
|
||||
|
||||
|
||||
def cmd_list(args):
|
||||
client = Client(load_config())
|
||||
bugs, users = fetch_bug_list(client, args.type)
|
||||
if args.status != "all":
|
||||
bugs = [b for b in bugs if b.get("status") == args.status]
|
||||
if args.json:
|
||||
print(json.dumps([{
|
||||
"id": int(b["id"]), "title": html.unescape(b["title"]), "status": b["status"],
|
||||
"severity": int(b["severity"]), "pri": int(b["pri"]), "type": b["type"],
|
||||
"product": b.get("productName"), "openedBy": b["openedBy"], "openedDate": b["openedDate"],
|
||||
"assignedTo": b["assignedTo"], "deadline": b.get("deadline"),
|
||||
} for b in bugs], ensure_ascii=False, indent=2))
|
||||
return
|
||||
title = {"assignedTo": "指派给我", "openedBy": "由我创建", "resolvedBy": "由我解决", "closedBy": "由我关闭"}[args.type]
|
||||
print(f"# 我的 Bug({title},状态:{args.status})共 {len(bugs)} 条\n")
|
||||
print("| ID | 严重 | 优先级 | 状态 | 类型 | 标题 | 创建人 | 创建时间 |")
|
||||
print("|---|---|---|---|---|---|---|---|")
|
||||
for b in bugs:
|
||||
t = html.unescape(b["title"]).replace("|", "\\|")
|
||||
print(f"| {b['id']} | {b['severity']} | {b['pri']} | {STATUS.get(b['status'], b['status'])} | "
|
||||
f"{TYPES.get(b['type'], b['type'])} | {t} | {users.get(b['openedBy'], b['openedBy'])} | {b['openedDate'][:16]} |")
|
||||
|
||||
|
||||
def cmd_view(args):
|
||||
client = Client(load_config())
|
||||
data = client.page(f"/bug-view-{args.id}.json")
|
||||
bug = data.get("bug")
|
||||
if not bug:
|
||||
die(f"Bug #{args.id} 不存在或无权查看")
|
||||
users = data.get("users") or {}
|
||||
who = lambda a: f"{users.get(a, a)}({a})" if a and users.get(a) and users.get(a) != a else (a or "-")
|
||||
|
||||
def show(field, value):
|
||||
if not value:
|
||||
return "空"
|
||||
if field in USER_FIELDS:
|
||||
return who(value)
|
||||
if field == "mailto":
|
||||
return "、".join(who(v) for v in value.split(",") if v) or "空"
|
||||
if field == "confirmed":
|
||||
return "是" if value == "1" else "否"
|
||||
return {"status": STATUS, "resolution": RESOLUTIONS, "type": TYPES}.get(field, {}).get(value, value)
|
||||
|
||||
out_dir = Path(args.out or DEFAULT_OUT) / str(args.id)
|
||||
fetcher = ImageFetcher(client, out_dir / "images")
|
||||
|
||||
def md(html_text):
|
||||
p = Html2Md(fetcher)
|
||||
p.feed(html_text or "")
|
||||
p.close()
|
||||
return p.markdown()
|
||||
|
||||
module = " / ".join(m.get("name", "") for m in (data.get("modulePath") or [])) or "/"
|
||||
product = (data.get("product") or {}).get("name") or (data.get("products") or {}).get(bug.get("product"), "")
|
||||
lines = [
|
||||
f"# Bug #{bug['id']} {html.unescape(bug['title'])}", "",
|
||||
f"- 链接:{client.url}/bug-view-{bug['id']}.html",
|
||||
f"- 产品 / 模块:{product} / {module}",
|
||||
f"- 状态:{STATUS.get(bug['status'], bug['status'])}"
|
||||
+ (f"({RESOLUTIONS.get(bug['resolution'], bug['resolution'])})" if bug.get("resolution") else "")
|
||||
+ f" 确认:{'是' if bug.get('confirmed') == '1' else '否'}",
|
||||
f"- 严重程度:{bug['severity']} 优先级:{bug['pri']} 类型:{TYPES.get(bug['type'], bug['type'])}",
|
||||
f"- 创建:{who(bug['openedBy'])} {bug['openedDate']} 影响版本:{bug.get('openedBuild', '')}",
|
||||
f"- 指派给:{who(bug['assignedTo'])} {bug.get('assignedDate', '')}",
|
||||
]
|
||||
if bug.get("resolvedBy"):
|
||||
lines.append(f"- 解决:{who(bug['resolvedBy'])} {bug.get('resolvedDate', '')} 解决版本:{bug.get('resolvedBuild', '')}")
|
||||
if bug.get("deadline") and not bug["deadline"].startswith("0000"):
|
||||
lines.append(f"- 截止日期:{bug['deadline']}")
|
||||
if bug.get("keywords"):
|
||||
lines.append(f"- 关键词:{bug['keywords']}")
|
||||
lines += ["", "## 重现步骤", "", md(bug.get("steps")) or "(空)"]
|
||||
|
||||
files = list((bug.get("files") or {}).values()) if isinstance(bug.get("files"), dict) else (bug.get("files") or [])
|
||||
if files:
|
||||
lines += ["", "## 附件", ""]
|
||||
att_dir = out_dir / "attachments"
|
||||
att_dir.mkdir(parents=True, exist_ok=True)
|
||||
for f in files:
|
||||
name = f"{f['id']}-{f.get('title', 'file')}"
|
||||
if not name.lower().endswith("." + f.get("extension", "").lower()):
|
||||
name += "." + f.get("extension", "bin")
|
||||
dest = att_dir / re.sub(r"[/\\]", "_", name)
|
||||
try:
|
||||
if not dest.exists():
|
||||
client.download(f"/file-read-{f['id']}.{f.get('extension', '')}", dest)
|
||||
lines.append(f"- [{f.get('title')}]({dest})({f.get('size', '?')} 字节)")
|
||||
except Exception as e:
|
||||
lines.append(f"- {f.get('title')}:下载失败({e})")
|
||||
|
||||
actions = sorted((data.get("actions") or {}).values(), key=lambda a: (a.get("date", ""), int(a.get("id", 0))))
|
||||
comment_count = sum(1 for a in actions if (a.get("comment") or "").strip())
|
||||
lines += ["", f"## 历史记录与评论({len(actions)} 条,含评论 {comment_count} 条)", ""]
|
||||
for a in actions:
|
||||
name = ACTIONS.get(a.get("action", ""), a.get("action", ""))
|
||||
extra = a.get("extra") or ""
|
||||
if a.get("action") == "resolved" and extra:
|
||||
extra = RESOLUTIONS.get(extra.split(",")[0], extra)
|
||||
elif a.get("action") == "assigned" and extra:
|
||||
extra = f"→ {who(extra)}"
|
||||
lines.append(f"### {a.get('date', '')} {who(a.get('actor'))} {name}{(' ' + extra) if extra else ''}")
|
||||
for h in a.get("history") or []:
|
||||
field = FIELDS.get(h.get("field"), h.get("field"))
|
||||
if h.get("field") == "steps":
|
||||
lines.append(f"- 修改了 {field}(以上方当前内容为准)")
|
||||
else:
|
||||
lines.append(f"- 修改了 {field}:{show(h.get('field'), h.get('old'))} → {show(h.get('field'), h.get('new'))}")
|
||||
if (a.get("comment") or "").strip():
|
||||
lines += ["", "**评论:**", ""] + [f"> {l}" if l else ">" for l in md(a["comment"]).splitlines()]
|
||||
lines.append("")
|
||||
|
||||
if fetcher.files:
|
||||
lines += ["## 本地图片", ""] + [f"- 图{i + 1}:{p}" for i, p in enumerate(fetcher.files)] + [""]
|
||||
if fetcher.errors:
|
||||
lines += ["## 图片下载失败", ""] + [f"- {e}" for e in fetcher.errors] + [""]
|
||||
|
||||
text = "\n".join(lines).rstrip() + "\n"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
(out_dir / "bug.md").write_text(text)
|
||||
if args.json:
|
||||
bug["_images"] = [str(p) for p in fetcher.files]
|
||||
bug["_actions"] = actions
|
||||
print(json.dumps(bug, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(text)
|
||||
print(f"(已保存到 {out_dir / 'bug.md'},图片 {len(fetcher.files)} 张)")
|
||||
|
||||
|
||||
def form_defaults(client, bug_id):
|
||||
"""读取网页「解决」表单渲染出的默认值:解决日期(服务器时间,北京时间)与指派给(禅道默认选中创建人)。
|
||||
接口提交不会自动补默认值,必须先取表单再原样带上;不能用本机时间,本机可能在其他时区。"""
|
||||
with client._open(f"/bug-resolve-{bug_id}.html?onlybody=yes") as resp:
|
||||
page = resp.read().decode("utf-8", "replace")
|
||||
date = re.search(r"name='resolvedDate'[^>]*value='([^']+)'", page)
|
||||
select = re.search(r"<select name='assignedTo'.*?</select>", page, re.S)
|
||||
assigned = select and re.search(r"<option value='([^']*)' selected", select.group(0))
|
||||
if not date or not assigned or not assigned.group(1):
|
||||
die(f"#{bug_id} 未能从解决表单读取默认的解决日期 / 指派给,可用 --date / --assign-to 指定")
|
||||
return date.group(1), assigned.group(1)
|
||||
|
||||
|
||||
def cmd_resolve(args):
|
||||
"""解决 Bug:等同网页「解决」表单。默认解决方案=已解决、解决版本=主干(trunk)、
|
||||
解决日期与指派给取网页表单默认值(服务器时间 / 创建人);只处理激活状态的单子,提交后回读校验。"""
|
||||
client = Client(load_config())
|
||||
failed = 0
|
||||
for bug_id in args.ids:
|
||||
form = client.page(f"/bug-resolve-{bug_id}.json")
|
||||
bug = (form or {}).get("bug")
|
||||
if not bug:
|
||||
print(f"#{bug_id} 跳过:不存在或无权查看")
|
||||
failed += 1
|
||||
continue
|
||||
title = html.unescape(bug["title"])
|
||||
if bug["status"] != "active":
|
||||
print(f"#{bug_id} 跳过:当前状态为「{STATUS.get(bug['status'], bug['status'])}」,只解决激活的 Bug({title})")
|
||||
continue
|
||||
builds = form.get("builds") or {}
|
||||
if args.build not in builds:
|
||||
die(f"解决版本 {args.build} 不在可选列表中:{', '.join(k for k in builds if k)}")
|
||||
default_date, default_assigned = form_defaults(client, bug_id)
|
||||
assigned = args.assign_to or default_assigned
|
||||
fields = {
|
||||
"resolution": args.resolution, "duplicateBug": "", "buildExecution": "",
|
||||
"resolvedBuild": args.build, "buildName": "",
|
||||
"resolvedDate": args.date or default_date,
|
||||
"assignedTo": assigned, "status": "resolved", "comment": args.comment or "",
|
||||
}
|
||||
desc = (f"#{bug_id} {title} → {RESOLUTIONS.get(args.resolution, args.resolution)},"
|
||||
f"版本 {builds[args.build]},指派给 {(form.get('users') or {}).get(assigned, assigned)}")
|
||||
if args.dry_run:
|
||||
print(f"[dry-run] {desc}")
|
||||
continue
|
||||
resp = client.post_form(f"/bug-resolve-{bug_id}.html?onlybody=yes", fields)
|
||||
after = client.page(f"/bug-view-{bug_id}.json").get("bug") or {}
|
||||
if after.get("status") == "resolved":
|
||||
print(f"已解决 {desc}")
|
||||
else:
|
||||
alerts = re.findall(r"alert\((['\"])(.*?)\1\)", resp)
|
||||
print(f"#{bug_id} 失败:提交后状态仍为 {after.get('status')}"
|
||||
+ (f",禅道提示:{'; '.join(html.unescape(a[1]) for a in alerts)}" if alerts else ""))
|
||||
failed += 1
|
||||
if failed:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def resolve_user(users, who):
|
||||
"""按账号或姓名定位用户:先精确匹配账号,再按「姓名」匹配(禅道显示名形如 T:陶雨佳)。"""
|
||||
if who in users:
|
||||
return who
|
||||
hits = [k for k, v in users.items() if k and v.split(":", 1)[-1] == who]
|
||||
if len(hits) == 1:
|
||||
return hits[0]
|
||||
die(f"找不到唯一的用户「{who}」" + (f",候选:{', '.join(hits)}" if hits else ""))
|
||||
|
||||
|
||||
def comment_html(text):
|
||||
"""备注是富文本,纯文本换行在网页上会被合并,逐行转义后用 <br /> 连接。"""
|
||||
return "<br />".join(html.escape(line) for line in text.splitlines())
|
||||
|
||||
|
||||
def cmd_assign(args):
|
||||
"""指派 Bug:等同网页「指派」表单(指派给 + 备注),状态保持激活;提交后回读校验指派人。"""
|
||||
client = Client(load_config())
|
||||
comment = args.comment
|
||||
if args.comment_file:
|
||||
comment = Path(args.comment_file).read_text(encoding="utf-8").strip()
|
||||
failed = 0
|
||||
for bug_id in args.ids:
|
||||
form = client.page(f"/bug-assignTo-{bug_id}.json")
|
||||
bug = (form or {}).get("bug")
|
||||
if not bug:
|
||||
print(f"#{bug_id} 跳过:不存在或无权查看")
|
||||
failed += 1
|
||||
continue
|
||||
users = form.get("users") or {}
|
||||
account = resolve_user(users, args.to)
|
||||
title = html.unescape(bug["title"])
|
||||
desc = f"#{bug_id} {title} → 指派给 {users.get(account, account)}"
|
||||
if args.dry_run:
|
||||
print(f"[dry-run] {desc}")
|
||||
if comment:
|
||||
print(" 备注:\n " + comment.replace("\n", "\n "))
|
||||
continue
|
||||
fields = {"assignedTo": account, "status": bug["status"], "mailto[]": "",
|
||||
"comment": comment_html(comment) if comment else ""}
|
||||
resp = client.post_form(f"/bug-assignTo-{bug_id}.html?onlybody=yes", fields)
|
||||
after = client.page(f"/bug-view-{bug_id}.json").get("bug") or {}
|
||||
if after.get("assignedTo") == account:
|
||||
print(f"已指派 {desc}")
|
||||
else:
|
||||
alerts = re.findall(r"alert\((['\"])(.*?)\1\)", resp)
|
||||
print(f"#{bug_id} 失败:提交后指派人仍为 {after.get('assignedTo')}"
|
||||
+ (f",禅道提示:{'; '.join(html.unescape(a[1]) for a in alerts)}" if alerts else ""))
|
||||
failed += 1
|
||||
if failed:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="禅道 18.x Bug 读取工具")
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
p = sub.add_parser("login", help="登录并保存 Token(密码存 macOS 钥匙串)")
|
||||
p.add_argument("--url", help=f"禅道地址,默认 {DEFAULT_URL}")
|
||||
p.add_argument("--account", help="账号")
|
||||
p.add_argument("--no-keychain", action="store_true", help="不在钥匙串保存密码")
|
||||
p.set_defaults(func=cmd_login)
|
||||
|
||||
sub.add_parser("logout", help="清除 Token 与钥匙串密码").set_defaults(func=cmd_logout)
|
||||
sub.add_parser("whoami", help="查看当前账号并验证会话").set_defaults(func=cmd_whoami)
|
||||
|
||||
p = sub.add_parser("list", help="我的 Bug 列表")
|
||||
p.add_argument("--type", choices=BUG_TYPES, default="assignedTo", help="默认 assignedTo(指派给我)")
|
||||
p.add_argument("--status", choices=("active", "resolved", "closed", "all"), default="all")
|
||||
p.add_argument("--json", action="store_true")
|
||||
p.set_defaults(func=cmd_list)
|
||||
|
||||
p = sub.add_parser("view", help="Bug 详情(描述、图片、历史、评论、附件)")
|
||||
p.add_argument("id", type=int)
|
||||
p.add_argument("--out", help=f"输出目录,默认 {DEFAULT_OUT}")
|
||||
p.add_argument("--json", action="store_true")
|
||||
p.set_defaults(func=cmd_view)
|
||||
|
||||
p = sub.add_parser("resolve", help="解决 Bug(写操作,会改动禅道线上状态)")
|
||||
p.add_argument("ids", type=int, nargs="+")
|
||||
p.add_argument("--resolution", choices=tuple(RESOLUTIONS), default="fixed", help="默认 fixed(已解决)")
|
||||
p.add_argument("--build", default="trunk", help="解决版本,默认 trunk(主干)")
|
||||
p.add_argument("--assign-to", help="指派给(账号),默认取表单默认值(即创建人)")
|
||||
p.add_argument("--date", help="解决日期 YYYY-MM-DD HH:MM:SS,默认取服务器当前时间")
|
||||
p.add_argument("--comment", default="", help="备注,默认留空")
|
||||
p.add_argument("--dry-run", action="store_true", help="只打印将要做的修改,不提交")
|
||||
p.set_defaults(func=cmd_resolve)
|
||||
|
||||
p = sub.add_parser("assign", help="指派 Bug 并附备注(写操作,会改动禅道线上状态)")
|
||||
p.add_argument("ids", type=int, nargs="+")
|
||||
p.add_argument("--to", required=True, help="指派给:账号(如 TYJ)或姓名(如 陶雨佳)")
|
||||
p.add_argument("--comment", default="", help="备注,多行用换行分隔")
|
||||
p.add_argument("--comment-file", help="从文件读取备注(多行内容推荐用这个)")
|
||||
p.add_argument("--dry-run", action="store_true", help="只打印将要做的修改,不提交")
|
||||
p.set_defaults(func=cmd_assign)
|
||||
|
||||
args = ap.parse_args()
|
||||
try:
|
||||
args.func(args)
|
||||
except AuthError as e:
|
||||
die(f"{e}。请执行:python3 .claude/skills/zentao-bug/zentao.py login", 2)
|
||||
except urllib.error.URLError as e:
|
||||
die(f"无法访问禅道:{e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
12
CHANGELOG.md
@@ -8,6 +8,18 @@
|
||||
## [Unreleased]
|
||||
|
||||
### 变更 (Changed)
|
||||
- 【5.5 寸首页改版 · 2026-10-21】手持端首页 `PDAEnterActivity` 四个 Tab 按 `documents/5.5寸首页新样式/` 设计稿
|
||||
1:1 还原(仅 module_p,平板不经过该页,零影响):
|
||||
① 国内/国际 Tab 改为「分组标题栏 + 三列宫格」(新增 `PdaMenuGroup` / `PdaMenuRenderer`),分组与顺序按设计稿,
|
||||
20 个入口换设计稿新图标(源 GIF/PNG 统一转 PNG 放 drawable-nodpi——放 xxhdpi 实测解码后再放大发糊),
|
||||
设计稿未画的入口追加到组尾沿用旧图标;
|
||||
权限过滤 `authList.contains(key)` 与改版前逐字一致——ADMIN 账号改版前后入口集合 diff 为空(国内 9、国际 12),
|
||||
21 个入口逐个点击跳转目标与原映射一致。
|
||||
② 首页航班动态改为卡片 + 国内/国际、进港/出港分段 + 日期卡片(‹ › 前后翻日)+ 浅色表格 + 右下设置悬浮钮;
|
||||
默认方向按设计稿改为「进港」;日期改为补零格式(原选择器会写出 `2026-10-5`),选择器从当前选中日期打开。
|
||||
③ 底部 Tab 栏、消息铃铛与角标、状态栏(#3B71E8)按设计稿重做(Tab 图标 16dp 并与文字水平居中,
|
||||
不照搬设计稿偏左上的 16px 图形),「我的」顶栏同步新导航栏样式;分段控件 padding 计入 1dp 描边以上下居中;消息铃铛四个 Tab 统一按国内/国际设计稿位置(首页设计稿略有偏差),切 Tab 不再移动;
|
||||
首页 AutoSize 基准由 400 改为设计稿画布 390。删除 module_p 中不再引用的旧菜单/Tab 图标副本 19 个。
|
||||
- 【手机适配分支合入主干 · 2026-10-11】`feature/phone-ui-adaptation` 合并入 `main`,此后平板与 5.5 寸手机只在
|
||||
`main` 上同时开发。合并以平板业务为准:
|
||||
① 主干 58 个平板提交(运抵海关放行/运抵状态筛选/海关回执、出库交接装机状态/允许装机、出港移库详情与侧滑编辑、
|
||||
|
||||
38
CLAUDE.md
@@ -1012,9 +1012,11 @@ Manifest 写死 `userLandscape` 时,即使 `BaseActivity` 在 `super.onCreate`
|
||||
| 国内 | `GnViewModel` | `module_p/.../ui/enter/gn/` |
|
||||
| 国际 | `GjViewModel` | `module_p/.../ui/enter/gj/` |
|
||||
|
||||
**菜单项是手写清单,不是接口下发**。权限机制只做过滤:登录返回的权限串存在
|
||||
`SharedPreferenceUtil.getString(Constant.Share.authList)`(一个逗号分隔的长字符串),
|
||||
菜单项按 `authList.contains(bean.key)` 决定显不显示。
|
||||
**菜单项是手写清单,不是接口下发**。登录接口只下发「分组 → 权限码」(`auths`,形如
|
||||
`{"IntExp":["AppIntExpCheckWeighing",…],"Supervision":[…]}`,存在
|
||||
`SharedPreferenceUtil.getString(Constant.Share.authList)`),菜单名称/图标/跳转在平板和手机上都是前端写死的映射。
|
||||
手机端菜单项按 `authList.contains(bean.key)` 决定显不显示(`PdaMenuGroup.kt` 的 `filterByAuth()`,
|
||||
**2026-10-21 首页改版时刻意保持与改版前逐字一致**,保证同一账号的入口集合不变)。
|
||||
|
||||
所以「适配完页面但手机首页看不到入口」的原因通常**不是没权限,而是清单里没加**。
|
||||
排查顺序:先确认权限串里有对应 key,再去看清单。
|
||||
@@ -1025,25 +1027,43 @@ adb -s <serial> shell "run-as com.lukouguoji.aerologic \
|
||||
cat /data/data/com.lukouguoji.aerologic/shared_prefs/data.xml" | tr ',' '\n' | grep -oE "App[A-Za-z]+" | sort -u
|
||||
```
|
||||
|
||||
加一项就是加一行 —— `ActionBean` 的第 4 个参数填 ARouter 路由即可,无需写 when 分支
|
||||
**2026-10-21 起首页按 `documents/5.5寸首页新样式/` 设计稿改版**:清单按设计稿分组
|
||||
(`PdaMenuGroup(标题, 入口列表)`,如国际 Tab =「国际出港 / 国际进港 / 综合管理」),
|
||||
由 `PdaMenuRenderer` 渲染成「分组标题栏 + 三列宫格」,过滤后为空的分组整组隐藏。
|
||||
**分组口径以设计稿为准,不是接口的 auths 分组**(例如单证交接/转运记录在接口里属 Supervision,
|
||||
设计稿放在「国内出港」)。
|
||||
|
||||
加一项就是在对应分组里加一行 —— `ActionBean` 的第 4 个参数填 ARouter 路由即可,无需写 when 分支
|
||||
(`module_p` 只依赖 `module_base`,看不到 `module_gjc` 等业务模块,跨模块只能走路由):
|
||||
|
||||
```kotlin
|
||||
PdaMenuGroup(
|
||||
"国际出港", listOf(
|
||||
ActionBean(
|
||||
"出港移库",
|
||||
R.drawable.gjc_yi_ku_icon, // 图标复用 Pad 端同款
|
||||
R.drawable.img_pda_menu_gjc_move, // 设计稿新版宫格图标
|
||||
Constant.AuthName.GjcYiKuListActivity, // 权限串,与 Pad 端菜单保持一致
|
||||
ARouterConstants.ACTIVITY_URL_INT_EXP_MOVE // 有 route 就走通用跳转
|
||||
),
|
||||
…
|
||||
)
|
||||
),
|
||||
```
|
||||
|
||||
两点注意:
|
||||
注意:
|
||||
|
||||
- **权限串与路由都要和 Pad 端菜单对齐**:以 `app/.../HomeFragment.kt` 里该权限对应的
|
||||
`ARouter.build(...)` 为准。同一业务常有新旧两个 Activity(如移库的 `GjcYiKuListActivity`
|
||||
与 `IntExpMoveActivity`),Pad 菜单实际跳的才是要适配的那个
|
||||
- **图标**:`module_p` 看不到 `module_gjc` 的资源,把 Pad 图标 PNG 复制一份到
|
||||
`module_p/src/main/res/drawable-xxhdpi/`(该模块既有 4 个图标就是这么放的,同名不同密度桶可共存)
|
||||
- **图标**:设计稿图标源文件在 `documents/5.5寸首页新样式/icons/`(约 167px 方图,自带「国际/国内」角标),
|
||||
统一转 PNG(源文件多为单帧 GIF,GIF 只有 256 色 + 1 位透明,不进资源)后放
|
||||
`module_p/src/main/res/drawable-nodpi/img_pda_menu_*.png`,按 59.7dp 宫格居中裁剪 + 圆角显示。
|
||||
**必须放 nodpi,不能放 xxhdpi**:167px 放 xxhdpi 会在 ~295dpi 设备上解码时先缩到 ≈103px,再被拉伸到
|
||||
宫格的 110px,实测明显发糊;nodpi 原尺寸解码、由固定尺寸 ImageView 一次缩小,与浏览器渲染设计稿一致
|
||||
**设计稿没有的入口追加到所属分组末尾、沿用旧图标**(如「板箱过磅」`gjc_fu_bang_icon`)
|
||||
- **改动清单后核对入口集合**:登录后分别在「国内 / 国际」Tab 用 `ui_probe.sh texts`(需滚动)导出入口名,
|
||||
与改动前 `LC_ALL=C sort` 后 diff(CJK 文本不加 `LC_ALL=C` 排序不稳定,会 diff 出假差异)。
|
||||
ADMIN 测试账号基线:国内 9 个、国际 12 个
|
||||
|
||||
**示例:状态 Tab 零 ViewModel 改动**(`IntExpMoveActivity` 已实现)
|
||||
|
||||
@@ -1313,6 +1333,8 @@ adb shell am start -n com.lukouguoji.aerologic/com.lukouguoji.gjc.activity.IntEx
|
||||
| 布局构建报 `Error: The string "--" is not permitted within comments` | XML 注释中出现连续双横线(如写"兜底 \"--\""),XML 规范禁止 | 注释里改用文字描述(如「兜底双横线」),代码绑定表达式中的 `"--"` 不受影响 |
|
||||
| 多选页手机版点「全选」后底部「已选 N 项」不更新(单卡勾选正常) | 批量勾选在 `checkAllClick` 里直接 set,不走 FlowBus 的单卡 EVENT_CHECK_CHANGED 事件,`selectedCountText` 只在事件回调里更新 | `checkAllClick` 末尾自行同步 `selectedCountText`(平板布局不绑定该字段,无影响) |
|
||||
| 手机版数字输入框(`PhoneFormRow` 双向绑 Double 的 `xxxStr`)输入 "18" 变成 "81" | getter 由 Double 反推字符串("1" → "1.0"),与输入框文本不等触发 `setText`,光标回到开头 | Bean 里保留输入原文(`@Transient` 私有 String),getter 优先返回原文(参考 `GjcCheckInRecord.trayTotalWeightStr`) |
|
||||
| 固定 dp 尺寸显示的位图图标在手机上发糊(设计稿里很清晰) | 位图放进了比「源像素 ÷ 显示 dp」更高的密度桶(如 167px 的宫格图放 xxhdpi,在 ~295dpi 设备上解码先缩到 103px,再拉伸到 110px 显示) | 只按固定 dp 尺寸显示的设计稿位图放 `drawable-nodpi`(原尺寸解码,View 一次缩小);排查:截图放大对比设计稿,算 `源px × 设备dpi / 桶dpi` 是否小于显示 px |
|
||||
| 分段/胶囊控件里选中块上下不居中(上窄下宽) | 设计稿 CSS 的 `border` 计入盒子尺寸,而 Android shape 的 `stroke` 画在 background 上、不占 padding——照抄 CSS 的 `padding: 2px` 少了 1dp 描边 | 容器 padding = CSS padding + border 宽度(如 2 + 1 = 3dp),且容器高度用 `wrap_content`——写死 28dp 时 28/3/22dp 各自取整成 px,内框比选中块少 1px,选中块底边被截;用像素列取色(`magick … txt:-`)量上下间隙验证 |
|
||||
| XML 里 `bean.a ?? bean.b` 兜底不生效,字段明明有值却显示占位符 | 网络层转换器把 JSON null 归一成空串 `""`,DataBinding 的 `??` 只判 null 不判空串,永远取到左侧的空串 | 在 Bean 上加计算属性做 `isNullOrEmpty` 双兜底(如 `agentDisplay`),XML 绑计算属性(进港查询/出港查询代理人已踩过) |
|
||||
|
||||
---
|
||||
|
||||
@@ -76,7 +76,8 @@
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/ic_phone_chevron_right" />
|
||||
android:src="@drawable/ic_phone_chevron_right"
|
||||
android:tint="@color/phone_text_body" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -118,34 +119,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 所在港 / 位置(与平板列表一致) ==================== -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text='@{"所在港:" + (bean.nowPort.empty ? "-" : bean.nowPort)}'
|
||||
android:textColor="@color/phone_text_body"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text='@{"位置:" + (bean.nowPosition.empty ? "-" : bean.nowPosition)}'
|
||||
android:textColor="@color/phone_text_body"
|
||||
android:textSize="13sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 第三行:入库时间 ==================== -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164513_257_1.jpg
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164514_258_1.jpg
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164515_259_1.jpg
Normal file
|
After Width: | Height: | Size: 284 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164515_260_1.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164516_261_1.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164517_262_1.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164535_263_1.jpg
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164714_265_1.jpg
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164715_266_1.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164716_267_1.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
documents/5.5寸旧页面颜色调整/微信图片_20261022164716_268_1.jpg
Normal file
|
After Width: | Height: | Size: 131 KiB |
881
documents/5.5寸首页新样式/5.5设计图html/dashboard.html
Normal file
@@ -0,0 +1,881 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="w-full h-full overflow-hidden" lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport"/>
|
||||
<title>首页</title>
|
||||
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
|
||||
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #F0F3F7;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC",
|
||||
"HarmonyOS Sans SC", "MiSans", "Noto Sans SC", "Source Han Sans SC",
|
||||
"Microsoft YaHei", ui-sans-serif, system-ui, sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.app-frame,
|
||||
.app-frame *,
|
||||
.app-frame *::before,
|
||||
.app-frame *::after {
|
||||
font-family: inherit;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
|
||||
.app-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: min(393px, 100vw);
|
||||
max-height: min(852px, 100vh);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #F5F7FA;
|
||||
position: relative;
|
||||
--nav-height: 52px;
|
||||
--tab-height: 68px;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
height: var(--nav-height);
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: clamp(16px, 4.8vw, 18px);
|
||||
line-height: 1.3;
|
||||
font-weight: 600;
|
||||
letter-spacing: clamp(0px, 0.16vw, 0.3px);
|
||||
}
|
||||
|
||||
.nav-bell {
|
||||
width: clamp(30px, 9.4vw, 34px);
|
||||
height: clamp(30px, 9.4vw, 34px);
|
||||
}
|
||||
|
||||
.nav-bell iconify-icon {
|
||||
width: clamp(18px, 5.6vw, 20px);
|
||||
height: clamp(18px, 5.6vw, 20px);
|
||||
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
.nav-badge {
|
||||
min-width: clamp(21px, 6.7vw, 24px);
|
||||
font-size: clamp(10px, 2.9vw, 11px);
|
||||
line-height: clamp(14px, 4.2vw, 15px);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
height: var(--tab-height);
|
||||
}
|
||||
|
||||
.tab-bar iconify-icon {
|
||||
width: clamp(22px, 6.7vw, 24px);
|
||||
height: clamp(22px, 6.7vw, 24px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-icon-stack {
|
||||
width: clamp(25px, 7.5vw, 27px);
|
||||
height: clamp(22px, 6.7vw, 24px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-icon-stack > iconify-icon:first-child {
|
||||
width: clamp(20px, 6.1vw, 22px);
|
||||
height: clamp(20px, 6.1vw, 22px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-icon-stack > iconify-icon:last-child {
|
||||
width: clamp(15px, 4.7vw, 17px);
|
||||
height: clamp(15px, 4.7vw, 17px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-label {
|
||||
font-size: 11.5px;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.tab-bar .tab-label.is-active {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
height: clamp(26px, 8.3vw, 30px);
|
||||
}
|
||||
|
||||
.main-scroll {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
overscroll-behavior-y: contain;
|
||||
/* 为右下角悬浮的设置按钮留出避让空间,滚到底时列表不被遮挡 */
|
||||
padding-bottom: calc(52px + var(--tab-height) + 16px + 20px);
|
||||
}
|
||||
|
||||
.main-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 航班动态设置:右下角悬浮按钮,抬升到 tab 栏之上 */
|
||||
.settings-fab {
|
||||
position: fixed;
|
||||
z-index: 40;
|
||||
right: 16px;
|
||||
bottom: calc(var(--tab-height) + 16px);
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: #E8EFFD;
|
||||
color: #3B71E8;
|
||||
transform: translateZ(0);
|
||||
box-shadow: 0 4px 14px rgba(31, 42, 51, 0.14);
|
||||
cursor: pointer;
|
||||
touch-action: manipulation;
|
||||
transition: transform 140ms ease, box-shadow 140ms ease;
|
||||
}
|
||||
|
||||
.settings-fab:active {
|
||||
transform: translateZ(0) scale(0.96);
|
||||
box-shadow: 0 2px 8px rgba(31, 42, 51, 0.18);
|
||||
}
|
||||
|
||||
.settings-fab:focus-visible {
|
||||
outline: 2px solid rgba(59, 113, 232, 0.55);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.settings-fab .settings-fab-icon {
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 主卡片:白卡 + 1px 描边 + 轻阴影,与两页统一 */
|
||||
.flight-card {
|
||||
margin: clamp(10px, 3vw, 14px);
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #EDF0F4;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.card-head {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
padding: 10px clamp(10px, 3.4vw, 14px);
|
||||
border-bottom: 1px solid #EEF1F5;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
flex: none;
|
||||
font-size: clamp(13px, 3.7vw, 14px);
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.2px;
|
||||
color: #16202A;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.scope-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex: none;
|
||||
padding: 2px;
|
||||
border-radius: 999px;
|
||||
background: #F4F6F9;
|
||||
border: 1px solid #EDF0F4;
|
||||
/* 作为筛选行中的一个平级项,宽度按内容自适应,不拉伸 */
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scope-option {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
padding: 0 clamp(5px, 1.6vw, 8px);
|
||||
height: 24px;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: #6B7A8D;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: background-color 140ms ease, color 140ms ease;
|
||||
}
|
||||
|
||||
.scope-option.is-active {
|
||||
background: #3B71E8;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 筛选行:国内/国际、进港/出港、日期控件为三个平级项。
|
||||
控件本体按内容自适应,不拉伸;两段间隙为 flex 弹性间隔,
|
||||
各占剩余空间的一半,因此首项贴左内边距、末项贴右内边距 */
|
||||
.card-head > .scope-group,
|
||||
.card-head > .date-group {
|
||||
flex: 0 0 auto;
|
||||
max-width: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-head > .gap-flex {
|
||||
flex: 1 1 0%;
|
||||
min-width: 0;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.date-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1px;
|
||||
width: 30px;
|
||||
height: 34px;
|
||||
padding: 0;
|
||||
border: 1px solid #EDF0F4;
|
||||
border-radius: 7px;
|
||||
background: #FFFFFF;
|
||||
color: #3B71E8;
|
||||
font-size: 8px;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 0.2px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||
transition: border-color 140ms ease, box-shadow 140ms ease;
|
||||
}
|
||||
|
||||
.date-picker:focus-visible,
|
||||
.date-picker.is-open {
|
||||
border-color: #3B71E8;
|
||||
box-shadow: 0 0 0 2px rgba(59, 113, 232, 0.14), 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.date-picker .date-month {
|
||||
font-size: 8px;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
color: #98A4B3;
|
||||
}
|
||||
|
||||
.date-picker .date-day {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.1;
|
||||
color: #3B71E8;
|
||||
}
|
||||
|
||||
.date-step {
|
||||
flex: none;
|
||||
width: 22px;
|
||||
height: 34px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: #16202A;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.date-picker + .date-step,
|
||||
.date-step + .date-picker {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.date-step.is-next {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.date-step iconify-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-input {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dir-tab {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0 clamp(5px, 1.6vw, 8px);
|
||||
height: 24px;
|
||||
margin: 0;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #6B7A8D;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background-color 140ms ease, color 140ms ease;
|
||||
}
|
||||
|
||||
.dir-tab.is-active {
|
||||
background: #3B71E8;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dir-tab + .dir-tab {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* 导航栏右侧图标组:仅铃铛 */
|
||||
.nav-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.nav-icon-btn {
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: clamp(32px, 10vw, 34px);
|
||||
height: clamp(32px, 10vw, 34px);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
transition: background-color 140ms ease, opacity 140ms ease;
|
||||
}
|
||||
|
||||
.nav-icon-btn:active {
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.nav-icon-btn:focus-visible {
|
||||
outline: 2px solid rgba(255, 255, 255, 0.7);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.nav-icon-btn iconify-icon {
|
||||
width: clamp(17px, 5.4vw, 19px);
|
||||
height: clamp(17px, 5.4vw, 19px);
|
||||
}
|
||||
|
||||
.table-head {
|
||||
display: grid;
|
||||
grid-template-columns: 0.95fr 1fr 1.05fr 1fr;
|
||||
align-items: center;
|
||||
background: #F4F6F9;
|
||||
border-bottom: 1px solid #EEF1F5;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.table-head span {
|
||||
padding: 9px 4px;
|
||||
text-align: center;
|
||||
color: #6B7A8D;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-body {
|
||||
flex: 1;
|
||||
min-height: 120px;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.flight-row {
|
||||
display: grid;
|
||||
grid-template-columns: 0.95fr 1fr 1.05fr 1fr;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
border-bottom: 1px solid #EEF1F5;
|
||||
}
|
||||
|
||||
.flight-row span {
|
||||
padding: 9px 4px;
|
||||
text-align: center;
|
||||
color: #16202A;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #98A4B3;
|
||||
cursor: pointer;
|
||||
transition: color 140ms ease, transform 140ms ease;
|
||||
}
|
||||
|
||||
.tab-button:active {
|
||||
transform: scale(0.94);
|
||||
}
|
||||
|
||||
.tab-button.is-active {
|
||||
color: #3B71E8;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 80px;
|
||||
z-index: 60;
|
||||
max-width: calc(100% - 44px);
|
||||
width: max-content;
|
||||
padding: 9px 16px;
|
||||
border-radius: 999px;
|
||||
background: rgba(29, 41, 57, 0.9);
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, 8px);
|
||||
transition: opacity 180ms ease, transform 180ms ease;
|
||||
box-shadow: 0 8px 24px rgba(18, 33, 49, 0.18);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.toast.is-visible {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
/* 5.51 寸小屏(窄屏或矮屏)收紧纵向节奏 */
|
||||
@media (max-width: 374px), (max-height: 760px) {
|
||||
.app-frame {
|
||||
--nav-height: 46px;
|
||||
--tab-height: 58px;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tab-bar .tab-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.flight-card {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.card-head {
|
||||
gap: 0;
|
||||
padding: 8px 9px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.scope-group {
|
||||
gap: 2px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.scope-option,
|
||||
.dir-tab {
|
||||
font-size: 11.5px;
|
||||
height: 22px;
|
||||
padding: 0 clamp(4px, 1.3vw, 7px);
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 27px;
|
||||
height: 31px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.settings-fab {
|
||||
right: 12px;
|
||||
bottom: calc(var(--tab-height) + 12px);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.settings-fab .settings-fab-icon {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.date-picker .date-month {
|
||||
font-size: 7px;
|
||||
}
|
||||
|
||||
.date-picker .date-day {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.date-step {
|
||||
width: 19px;
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
.date-step iconify-icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.dir-tab {
|
||||
height: 22px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table-head span {
|
||||
padding: 8px 2px;
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
.flight-row span {
|
||||
padding: 8px 2px;
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
bottom: 72px;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.tab-button,
|
||||
.nav-icon-btn,
|
||||
.settings-fab,
|
||||
.toast {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 359px) {
|
||||
.table-head,
|
||||
.flight-row {
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.table-head span,
|
||||
.flight-row span {
|
||||
padding: 8px 1px;
|
||||
font-size: 10.5px;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-frame">
|
||||
<!-- 顶部系统状态栏 -->
|
||||
<header class="flex-none bg-[#3B71E8] text-white">
|
||||
<div class="status-bar flex items-center justify-between px-3">
|
||||
<div class="flex items-center gap-[7px]">
|
||||
<span class="text-[11.5px] font-semibold tracking-[0.2px]">16:48</span>
|
||||
</div>
|
||||
<div aria-label="Wi-Fi" class="flex min-w-0 items-center gap-[5px]">
|
||||
<iconify-icon class="opacity-95" height="14" icon="mdi:wifi" width="14"></iconify-icon>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<!-- 蓝色导航栏 -->
|
||||
<nav aria-label="首页导航栏" class="nav-bar relative z-20 flex flex-none items-center justify-center bg-[#3B71E8] text-white">
|
||||
<h1 class="nav-title">首页</h1>
|
||||
<div class="nav-actions absolute right-[8px] top-1/2 -translate-y-1/2">
|
||||
<button aria-label="通知,507 条消息" class="nav-icon-btn text-white" onclick="showNotificationToast()" type="button">
|
||||
<iconify-icon class="drop-shadow-[0_1px_1px_rgba(0,0,0,0.12)]" icon="mdi:bell-outline"></iconify-icon>
|
||||
<span class="nav-badge absolute -right-[2px] top-[0px] rounded-full bg-[#e1192d] px-[4px] text-center font-semibold text-white shadow-[0_1px_2px_rgba(145,16,31,0.35)]">507</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- 主内容滚动区 -->
|
||||
<main class="main-scroll min-h-0 flex flex-1 flex-col overflow-y-auto overflow-x-hidden bg-[#F5F7FA]" id="mainContent">
|
||||
<section aria-label="航班动态" class="flight-card">
|
||||
<!-- 首行:国内/国际 + 进港/出港分段 + 图标式日期 -->
|
||||
<div class="card-head">
|
||||
<div aria-label="国内国际" class="scope-group" role="radiogroup">
|
||||
<button aria-checked="true" class="scope-option is-active" data-scope="国内" onclick="selectScope(this)" role="radio" type="button">国内</button>
|
||||
<button aria-checked="false" class="scope-option" data-scope="国际" onclick="selectScope(this)" role="radio" type="button">国际</button>
|
||||
</div>
|
||||
<span aria-hidden="true" class="gap-flex"></span>
|
||||
<div aria-label="进港出港方向" class="scope-group" role="radiogroup">
|
||||
<button aria-checked="true" class="dir-tab is-active" data-dir="进港" onclick="selectDirection(this)" role="radio" type="button">进港</button>
|
||||
<button aria-checked="false" class="dir-tab" data-dir="出港" onclick="selectDirection(this)" role="radio" type="button">出港</button>
|
||||
</div>
|
||||
<span aria-hidden="true" class="gap-flex"></span>
|
||||
<div class="date-group">
|
||||
<button aria-label="前一天" class="date-step date-step-prev" data-date-step="-1" onclick="stepDate(-1)" type="button">
|
||||
<iconify-icon height="18" icon="mdi:chevron-left" width="18"></iconify-icon>
|
||||
</button>
|
||||
<button aria-label="选择日期" class="date-picker" id="datePicker" type="button">
|
||||
<span class="date-month" id="dateMonth">10月</span>
|
||||
<span class="date-day" id="dateDay">20</span>
|
||||
<input aria-label="日期" class="date-input" id="dateInput" type="date" value="2026-10-20"/>
|
||||
</button>
|
||||
<button aria-label="后一天" class="date-step is-next" data-date-step="1" onclick="stepDate(1)" type="button">
|
||||
<iconify-icon height="18" icon="mdi:chevron-right" width="18"></iconify-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 表头 -->
|
||||
<div class="table-head" role="row">
|
||||
<span role="columnheader">航班号</span>
|
||||
<span role="columnheader">航程</span>
|
||||
<span role="columnheader">地区类型</span>
|
||||
<span role="columnheader">计划起飞</span>
|
||||
</div>
|
||||
<!-- 数据区 -->
|
||||
<div aria-label="航班动态列表" class="table-body">
|
||||
<div class="flight-row" role="row">
|
||||
<span role="cell">MU2222</span>
|
||||
<span role="cell">HFE-PEK</span>
|
||||
<span role="cell">国内</span>
|
||||
<span role="cell">12:20</span>
|
||||
</div>
|
||||
<div class="flight-row" role="row">
|
||||
<span role="cell">MU6895</span>
|
||||
<span role="cell">HFE-LAX</span>
|
||||
<span role="cell">国际</span>
|
||||
<span role="cell">02:26</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<!-- 右下角悬浮:航班动态设置 -->
|
||||
<button aria-label="航班动态设置" class="settings-fab" onclick="showToast('航班动态设置')" type="button">
|
||||
<svg aria-hidden="true" class="settings-fab-icon" focusable="false" viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" fill="#3B71E8"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 底部 Tab 栏 -->
|
||||
<nav aria-label="底部导航" class="tab-bar relative z-30 flex flex-none items-stretch border-t border-[#EDF0F4] bg-white pb-[4px] shadow-[0_-1px_2px_rgba(16,24,40,0.04)]">
|
||||
<button aria-current="page" aria-label="首页,当前页面" class="tab-button is-active" onclick="switchTab('首页')" type="button">
|
||||
<iconify-icon icon="mdi:home"></iconify-icon>
|
||||
<span class="tab-label is-active">首页</span>
|
||||
</button>
|
||||
<button aria-label="国内" class="tab-button" onclick="switchTab('国内')" type="button">
|
||||
<iconify-icon icon="mdi:earth"></iconify-icon>
|
||||
<span class="tab-label">国内</span>
|
||||
</button>
|
||||
<button aria-label="国际" class="tab-button" onclick="switchTab('国际')" type="button">
|
||||
<span class="tab-icon-stack relative inline-flex items-center justify-center">
|
||||
<iconify-icon class="absolute bottom-0 left-0" icon="mdi:earth"></iconify-icon>
|
||||
<iconify-icon class="absolute right-0 top-0 -rotate-[14deg]" icon="mdi:airplane"></iconify-icon>
|
||||
</span>
|
||||
<span class="tab-label">国际</span>
|
||||
</button>
|
||||
<button aria-label="我的" class="tab-button" onclick="switchTab('我的')" type="button">
|
||||
<iconify-icon icon="mdi:account-circle-outline"></iconify-icon>
|
||||
<span class="tab-label">我的</span>
|
||||
</button>
|
||||
</nav>
|
||||
<div aria-live="polite" class="toast" id="toast" role="status"></div>
|
||||
</div>
|
||||
<script>
|
||||
let toastTimer = null;
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.getElementById("toast");
|
||||
if (!toast) return;
|
||||
|
||||
toast.textContent = message;
|
||||
toast.classList.add("is-visible");
|
||||
|
||||
if (toastTimer) {
|
||||
clearTimeout(toastTimer);
|
||||
}
|
||||
|
||||
toastTimer = setTimeout(function () {
|
||||
toast.classList.remove("is-visible");
|
||||
}, 1800);
|
||||
}
|
||||
|
||||
function showNotificationToast() {
|
||||
showToast("您有 507 条待处理消息");
|
||||
}
|
||||
|
||||
function selectScope(target) {
|
||||
const options = document.querySelectorAll(".scope-option");
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const isTarget = options[i] === target;
|
||||
options[i].classList.toggle("is-active", isTarget);
|
||||
options[i].setAttribute("aria-checked", isTarget ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
function selectDirection(target) {
|
||||
const tabs = document.querySelectorAll(".dir-tab");
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
const isTarget = tabs[i] === target;
|
||||
tabs[i].classList.toggle("is-active", isTarget);
|
||||
tabs[i].setAttribute("aria-checked", isTarget ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
function switchTab(name) {
|
||||
if (name === "国内") {
|
||||
window.location.href = "domestic.html";
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === "国际") {
|
||||
window.location.href = "home.html";
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === "首页") {
|
||||
const main = document.getElementById("mainContent");
|
||||
if (main) {
|
||||
main.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
showToast("当前已在首页");
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(name + "模块暂未接入");
|
||||
}
|
||||
|
||||
(function () {
|
||||
const datePicker = document.getElementById("datePicker");
|
||||
const dateInput = document.getElementById("dateInput");
|
||||
const dateMonth = document.getElementById("dateMonth");
|
||||
const dateDay = document.getElementById("dateDay");
|
||||
if (!datePicker || !dateInput || !dateMonth || !dateDay) return;
|
||||
|
||||
function renderDate(value) {
|
||||
if (!value) return;
|
||||
const parts = value.split("-");
|
||||
const month = parseInt(parts[1], 10);
|
||||
const day = parseInt(parts[2], 10);
|
||||
if (!month || !day) return;
|
||||
dateMonth.textContent = month + "月";
|
||||
dateDay.textContent = String(day);
|
||||
datePicker.setAttribute("aria-label", "选择日期,当前 " + month + "月" + day + "日");
|
||||
}
|
||||
|
||||
window.stepDate = function (offset) {
|
||||
const parts = dateInput.value.split("-");
|
||||
const current = new Date(
|
||||
parseInt(parts[0], 10) || 2026,
|
||||
(parseInt(parts[1], 10) || 1) - 1,
|
||||
parseInt(parts[2], 10) || 1
|
||||
);
|
||||
current.setDate(current.getDate() + offset);
|
||||
const y = current.getFullYear();
|
||||
const m = String(current.getMonth() + 1).padStart(2, "0");
|
||||
const d = String(current.getDate()).padStart(2, "0");
|
||||
dateInput.value = y + "-" + m + "-" + d;
|
||||
renderDate(dateInput.value);
|
||||
};
|
||||
|
||||
renderDate(dateInput.value);
|
||||
|
||||
datePicker.addEventListener("click", function (event) {
|
||||
if (event.target === dateInput) return;
|
||||
if (typeof dateInput.showPicker === "function") {
|
||||
try {
|
||||
dateInput.showPicker();
|
||||
} catch (error) {
|
||||
dateInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dateInput.addEventListener("focus", function () {
|
||||
datePicker.classList.add("is-open");
|
||||
});
|
||||
|
||||
dateInput.addEventListener("blur", function () {
|
||||
datePicker.classList.remove("is-open");
|
||||
});
|
||||
|
||||
dateInput.addEventListener("change", function () {
|
||||
if (dateInput.value) {
|
||||
renderDate(dateInput.value);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
535
documents/5.5寸首页新样式/5.5设计图html/domestic.html
Normal file
@@ -0,0 +1,535 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="w-full h-full overflow-hidden" lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport"/>
|
||||
<title>国内业务</title>
|
||||
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
|
||||
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #F0F3F7;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC",
|
||||
"HarmonyOS Sans SC", "MiSans", "Noto Sans SC", "Source Han Sans SC",
|
||||
"Microsoft YaHei", ui-sans-serif, system-ui, sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* 全页字体统一:覆盖 Tailwind 默认字体族,数字统一等宽 */
|
||||
.app-frame,
|
||||
.app-frame *,
|
||||
.app-frame *::before,
|
||||
.app-frame *::after {
|
||||
font-family: inherit;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
|
||||
.app-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: min(393px, 100vw);
|
||||
max-height: min(852px, 100vh);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #F5F7FA;
|
||||
position: relative;
|
||||
/* 以 360×640(5.51 寸 16:9)为小屏基准,宫格尺寸随视口宽度等比缩放 */
|
||||
--tile-size: clamp(48px, 15.3vw, 60px);
|
||||
--icon-size: calc(var(--tile-size) * 0.55);
|
||||
--nav-height: 52px;
|
||||
--tab-height: 68px;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
height: var(--nav-height);
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: clamp(16px, 4.8vw, 18px);
|
||||
line-height: 1.3;
|
||||
font-weight: 600;
|
||||
letter-spacing: clamp(0px, 0.16vw, 0.3px);
|
||||
}
|
||||
|
||||
.nav-bell {
|
||||
width: clamp(30px, 9.4vw, 34px);
|
||||
height: clamp(30px, 9.4vw, 34px);
|
||||
}
|
||||
|
||||
.nav-bell iconify-icon {
|
||||
width: clamp(22px, 6.9vw, 25px);
|
||||
height: clamp(22px, 6.9vw, 25px);
|
||||
}
|
||||
|
||||
.nav-badge {
|
||||
min-width: clamp(21px, 6.7vw, 24px);
|
||||
font-size: clamp(10px, 2.9vw, 11px);
|
||||
line-height: clamp(14px, 4.2vw, 15px);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
height: var(--tab-height);
|
||||
}
|
||||
|
||||
.tab-bar iconify-icon {
|
||||
width: clamp(22px, 6.7vw, 24px);
|
||||
height: clamp(22px, 6.7vw, 24px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-icon-stack {
|
||||
width: clamp(25px, 7.5vw, 27px);
|
||||
height: clamp(22px, 6.7vw, 24px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-icon-stack > iconify-icon:first-child {
|
||||
width: clamp(20px, 6.1vw, 22px);
|
||||
height: clamp(20px, 6.1vw, 22px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-icon-stack > iconify-icon:last-child {
|
||||
width: clamp(15px, 4.7vw, 17px);
|
||||
height: clamp(15px, 4.7vw, 17px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-label {
|
||||
font-size: 11.5px;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.tab-bar .tab-label.is-active {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
height: clamp(26px, 8.3vw, 30px);
|
||||
}
|
||||
|
||||
.main-scroll {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
|
||||
.main-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.group-bar {
|
||||
height: 41px;
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 24px;
|
||||
background: #F4F6F9;
|
||||
border-top: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-size: clamp(13px, 3.7vw, 14px);
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.2px;
|
||||
color: #16202A;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
column-gap: 0;
|
||||
row-gap: clamp(12px, 4.6vw, 18px);
|
||||
background: #ffffff;
|
||||
padding: clamp(14px, 4.6vw, 18px) clamp(12px, 5vw, 18px) clamp(18px, 6vw, 24px);
|
||||
}
|
||||
|
||||
.group-arrow {
|
||||
display: block;
|
||||
flex: none;
|
||||
width: auto;
|
||||
height: clamp(12px, 3.6vw, 15px);
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 1px 1px rgba(37, 99, 235, 0.28));
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
transition: transform 140ms ease;
|
||||
}
|
||||
|
||||
.feature-card:active {
|
||||
transform: scale(0.94);
|
||||
}
|
||||
|
||||
.icon-tile {
|
||||
width: var(--tile-size);
|
||||
height: var(--tile-size);
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: calc(var(--tile-size) * 0.267);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.92),
|
||||
inset 0 -1px 0 rgba(255, 255, 255, 0.45),
|
||||
0 6px 12px -7px rgba(54, 80, 99, 0.34);
|
||||
}
|
||||
|
||||
.icon-tile.is-image {
|
||||
color: transparent;
|
||||
background: transparent;
|
||||
box-shadow: 0 6px 12px -7px rgba(54, 80, 99, 0.26);
|
||||
}
|
||||
|
||||
.icon-tile.is-image::before,
|
||||
.icon-tile.is-image::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.icon-tile.is-image > .tile-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
-webkit-user-drag: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.feature-label {
|
||||
margin-top: clamp(6px, 2.2vw, 10px);
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #2d3640;
|
||||
font-size: clamp(12px, 3.4vw, 13px);
|
||||
line-height: 1.3;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #98A4B3;
|
||||
cursor: pointer;
|
||||
transition: color 140ms ease, transform 140ms ease;
|
||||
}
|
||||
|
||||
.tab-button:active {
|
||||
transform: scale(0.94);
|
||||
}
|
||||
|
||||
.tab-button.is-active {
|
||||
color: #3B71E8;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 80px;
|
||||
z-index: 60;
|
||||
max-width: calc(100% - 44px);
|
||||
width: max-content;
|
||||
padding: 9px 16px;
|
||||
border-radius: 999px;
|
||||
background: rgba(29, 41, 57, 0.9);
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, 8px);
|
||||
transition: opacity 180ms ease, transform 180ms ease;
|
||||
box-shadow: 0 8px 24px rgba(18, 33, 49, 0.18);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.toast.is-visible {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
/* 5.51 寸小屏(窄屏或矮屏)收紧纵向节奏,保证首屏完整 */
|
||||
@media (max-width: 374px), (max-height: 760px) {
|
||||
.app-frame {
|
||||
--nav-height: 46px;
|
||||
--tab-height: 58px;
|
||||
}
|
||||
|
||||
.group-bar {
|
||||
height: 35px;
|
||||
gap: 8px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.feature-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tab-bar .tab-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.group-arrow {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
row-gap: 12px;
|
||||
padding: 14px 12px 18px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
bottom: 72px;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.feature-card,
|
||||
.tab-button,
|
||||
.toast {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-frame">
|
||||
<!-- 顶部系统状态栏 -->
|
||||
<header class="flex-none bg-[#3B71E8] text-white">
|
||||
<div class="status-bar flex items-center justify-between px-3">
|
||||
<div class="flex items-center gap-[7px]">
|
||||
<span class="text-[11.5px] font-semibold tracking-[0.2px]">16:48</span>
|
||||
</div>
|
||||
<div aria-label="Wi-Fi" class="flex min-w-0 items-center gap-[5px]">
|
||||
<iconify-icon class="opacity-95" height="14" icon="mdi:wifi" width="14"></iconify-icon>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<!-- 蓝色导航栏 -->
|
||||
<nav aria-label="国内业务导航栏" class="nav-bar relative z-20 flex flex-none items-center justify-center bg-[#3B71E8] text-white">
|
||||
<h1 class="nav-title">国内业务</h1>
|
||||
<button aria-label="通知,341 条消息" class="nav-bell absolute right-[12px] top-1/2 flex -translate-y-1/2 items-center justify-center rounded-full active:bg-white/10" onclick="showNotificationToast()" type="button">
|
||||
<iconify-icon class="drop-shadow-[0_1px_1px_rgba(0,0,0,0.12)]" icon="mdi:bell-outline"></iconify-icon>
|
||||
<span class="nav-badge absolute -right-[2px] top-[0px] rounded-full bg-[#e1192d] px-[4px] text-center font-semibold text-white shadow-[0_1px_2px_rgba(145,16,31,0.35)]">341</span>
|
||||
</button>
|
||||
</nav>
|
||||
<!-- 主内容滚动区 -->
|
||||
<main class="main-scroll min-h-0 flex-1 overflow-y-auto overflow-x-hidden bg-[#F5F7FA]" id="mainContent">
|
||||
<!-- 国内出港 -->
|
||||
<section aria-labelledby="dom-outbound-title">
|
||||
<div class="group-bar">
|
||||
<img alt="" aria-hidden="true" class="group-arrow" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/f48cf8269922458abee299bf1f4bdfae.png"/>
|
||||
<h2 class="group-title" id="dom-outbound-title">国内出港</h2>
|
||||
</div>
|
||||
<div class="feature-grid">
|
||||
<button aria-label="出港收运" class="feature-card" onclick="openFeature('出港收运')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="出港收运图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/4dfe2842e40947c9916d27c6da2829a9.png"/>
|
||||
</span>
|
||||
<span class="feature-label">出港收运</span>
|
||||
</button>
|
||||
<button aria-label="出港复磅" class="feature-card" onclick="openFeature('出港复磅')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="出港复磅图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/d209b04e82874625ae8977733789486a.png"/>
|
||||
</span>
|
||||
<span class="feature-label">出港复磅</span>
|
||||
</button>
|
||||
<button aria-label="出港存放" class="feature-card" onclick="openFeature('出港存放')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="出港存放图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/3655ce17a68b4090870f929d7a510c64.png"/>
|
||||
</span>
|
||||
<span class="feature-label">出港存放</span>
|
||||
</button>
|
||||
<button aria-label="单证交接" class="feature-card" onclick="openFeature('单证交接')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="单证交接图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/934528d010e74d1690dce7b20cee545c.png"/>
|
||||
</span>
|
||||
<span class="feature-label">单证交接</span>
|
||||
</button>
|
||||
<button aria-label="货物转运" class="feature-card" onclick="openFeature('货物转运')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="货物转运图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/9c2d255f57d6416ab18713b02431023e.png"/>
|
||||
</span>
|
||||
<span class="feature-label">货物转运</span>
|
||||
</button>
|
||||
<button aria-label="转运记录" class="feature-card" onclick="openFeature('转运记录')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="转运记录图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/50e01da375de4c1bb4574401ba60500c.png"/>
|
||||
</span>
|
||||
<span class="feature-label">转运记录</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 国内进港 -->
|
||||
<section aria-labelledby="dom-inbound-title">
|
||||
<div class="group-bar">
|
||||
<img alt="" aria-hidden="true" class="group-arrow" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/f48cf8269922458abee299bf1f4bdfae.png"/>
|
||||
<h2 class="group-title" id="dom-inbound-title">国内进港</h2>
|
||||
</div>
|
||||
<div class="feature-grid">
|
||||
<button aria-label="进港卸机" class="feature-card" onclick="openFeature('进港卸机')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="进港卸机图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/153ca414db114dd487852b22727fb38f.png"/>
|
||||
</span>
|
||||
<span class="feature-label">进港卸机</span>
|
||||
</button>
|
||||
<button aria-label="进港移库" class="feature-card" onclick="openFeature('进港移库')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="进港移库图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/2dc4ad4cc1884f4692da574db217e91e.png"/>
|
||||
</span>
|
||||
<span class="feature-label">进港移库</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 综合管理 -->
|
||||
<section aria-labelledby="dom-manage-title" class="pb-2">
|
||||
<div class="group-bar">
|
||||
<img alt="" aria-hidden="true" class="group-arrow" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/f48cf8269922458abee299bf1f4bdfae.png"/>
|
||||
<h2 class="group-title" id="dom-manage-title">综合管理</h2>
|
||||
</div>
|
||||
<div class="feature-grid" style="padding-bottom: clamp(16px, 5.6vw, 22px);">
|
||||
<button aria-label="事故签证" class="feature-card" onclick="openFeature('事故签证')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="事故签证图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/2d2e97049ef44cfabbf1ed051702d567.png"/>
|
||||
</span>
|
||||
<span class="feature-label">事故签证</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<!-- 底部 Tab 栏 -->
|
||||
<nav aria-label="底部导航" class="tab-bar relative z-30 flex flex-none items-stretch border-t border-[#EDF0F4] bg-white pb-[4px] shadow-[0_-1px_2px_rgba(16,24,40,0.04)]">
|
||||
<button aria-label="首页" class="tab-button" onclick="switchTab('首页')" type="button">
|
||||
<iconify-icon icon="mdi:home-outline"></iconify-icon>
|
||||
<span class="tab-label">首页</span>
|
||||
</button>
|
||||
<button aria-current="page" aria-label="国内,当前页面" class="tab-button is-active" onclick="switchTab('国内')" type="button">
|
||||
<iconify-icon icon="mdi:earth"></iconify-icon>
|
||||
<span class="tab-label is-active">国内</span>
|
||||
</button>
|
||||
<button aria-label="国际" class="tab-button" onclick="switchTab('国际')" type="button">
|
||||
<span class="tab-icon-stack relative inline-flex items-center justify-center">
|
||||
<iconify-icon class="absolute bottom-0 left-0" icon="mdi:earth"></iconify-icon>
|
||||
<iconify-icon class="absolute right-0 top-0 -rotate-[14deg]" icon="mdi:airplane"></iconify-icon>
|
||||
</span>
|
||||
<span class="tab-label">国际</span>
|
||||
</button>
|
||||
<button aria-label="我的" class="tab-button" onclick="switchTab('我的')" type="button">
|
||||
<iconify-icon icon="mdi:account-circle-outline"></iconify-icon>
|
||||
<span class="tab-label">我的</span>
|
||||
</button>
|
||||
</nav>
|
||||
<div aria-live="polite" class="toast" id="toast" role="status"></div>
|
||||
</div>
|
||||
<script>
|
||||
let toastTimer = null;
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.getElementById("toast");
|
||||
if (!toast) return;
|
||||
|
||||
toast.textContent = message;
|
||||
toast.classList.add("is-visible");
|
||||
|
||||
if (toastTimer) {
|
||||
clearTimeout(toastTimer);
|
||||
}
|
||||
|
||||
toastTimer = setTimeout(function () {
|
||||
toast.classList.remove("is-visible");
|
||||
}, 1800);
|
||||
}
|
||||
|
||||
function openFeature(name) {
|
||||
showToast(name + " · 功能即将开放");
|
||||
}
|
||||
|
||||
function showNotificationToast() {
|
||||
showToast("您有 341 条待处理消息");
|
||||
}
|
||||
|
||||
function switchTab(name) {
|
||||
if (name === "首页") {
|
||||
window.location.href = "dashboard.html";
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === "国际") {
|
||||
window.location.href = "home.html";
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === "国内") {
|
||||
const main = document.getElementById("mainContent");
|
||||
if (main) {
|
||||
main.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
showToast("当前已在国内业务");
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(name + "模块暂未接入");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
694
documents/5.5寸首页新样式/5.5设计图html/home.html
Normal file
@@ -0,0 +1,694 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="w-full h-full overflow-hidden" lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport"/>
|
||||
<title>国际业务</title>
|
||||
<script src="https://modao.cc/agent-py/static/source/js/tailwindcss.js"></script>
|
||||
<script src="https://modao.cc/agent-py/static/source/js/iconify-icon.min.js"></script>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #F0F3F7;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC",
|
||||
"HarmonyOS Sans SC", "MiSans", "Noto Sans SC", "Source Han Sans SC",
|
||||
"Microsoft YaHei", ui-sans-serif, system-ui, sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* 全页字体统一:覆盖 Tailwind 默认字体族,数字统一等宽 */
|
||||
.app-frame,
|
||||
.app-frame *,
|
||||
.app-frame *::before,
|
||||
.app-frame *::after {
|
||||
font-family: inherit;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
|
||||
.app-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: min(393px, 100vw);
|
||||
max-height: min(852px, 100vh);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #F5F7FA;
|
||||
position: relative;
|
||||
/* 以 360×640(5.51 寸 16:9)为小屏基准,宫格尺寸随视口宽度等比缩放 */
|
||||
--tile-size: clamp(48px, 15.3vw, 60px);
|
||||
--icon-size: calc(var(--tile-size) * 0.55);
|
||||
--tag-size: clamp(28px, 8.9vw, 35px);
|
||||
--tag-offset: calc(var(--tag-size) * 0.2);
|
||||
--tag-line: calc(var(--tag-size) * 0.4);
|
||||
--nav-height: 52px;
|
||||
--tab-height: 68px;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
height: var(--nav-height);
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: clamp(16px, 4.8vw, 18px);
|
||||
line-height: 1.3;
|
||||
font-weight: 600;
|
||||
letter-spacing: clamp(0px, 0.16vw, 0.3px);
|
||||
}
|
||||
|
||||
.nav-bell {
|
||||
width: clamp(30px, 9.4vw, 34px);
|
||||
height: clamp(30px, 9.4vw, 34px);
|
||||
}
|
||||
|
||||
.nav-bell iconify-icon {
|
||||
width: clamp(22px, 6.9vw, 25px);
|
||||
height: clamp(22px, 6.9vw, 25px);
|
||||
}
|
||||
|
||||
.nav-badge {
|
||||
min-width: clamp(21px, 6.7vw, 24px);
|
||||
font-size: clamp(10px, 2.9vw, 11px);
|
||||
line-height: clamp(14px, 4.2vw, 15px);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
height: var(--tab-height);
|
||||
}
|
||||
|
||||
.tab-bar iconify-icon {
|
||||
width: clamp(22px, 6.7vw, 24px);
|
||||
height: clamp(22px, 6.7vw, 24px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-icon-stack {
|
||||
width: clamp(25px, 7.5vw, 27px);
|
||||
height: clamp(22px, 6.7vw, 24px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-icon-stack > iconify-icon:first-child {
|
||||
width: clamp(20px, 6.1vw, 22px);
|
||||
height: clamp(20px, 6.1vw, 22px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-icon-stack > iconify-icon:last-child {
|
||||
width: clamp(15px, 4.7vw, 17px);
|
||||
height: clamp(15px, 4.7vw, 17px);
|
||||
}
|
||||
|
||||
.tab-bar .tab-label {
|
||||
font-size: 11.5px;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.tab-bar .tab-label.is-active {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
height: clamp(26px, 8.3vw, 30px);
|
||||
}
|
||||
|
||||
.main-scroll {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
|
||||
.main-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.group-bar {
|
||||
height: 41px;
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 24px;
|
||||
background: #F4F6F9;
|
||||
border-top: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-size: clamp(13px, 3.7vw, 14px);
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.2px;
|
||||
color: #16202A;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
column-gap: 0;
|
||||
row-gap: clamp(12px, 4.6vw, 18px);
|
||||
background: #ffffff;
|
||||
padding: clamp(14px, 4.6vw, 18px) clamp(12px, 5vw, 18px) clamp(18px, 6vw, 24px);
|
||||
}
|
||||
|
||||
.group-arrow {
|
||||
display: block;
|
||||
flex: none;
|
||||
width: auto;
|
||||
height: clamp(12px, 3.6vw, 15px);
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 1px 1px rgba(37, 99, 235, 0.28));
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
transition: transform 140ms ease;
|
||||
}
|
||||
|
||||
.feature-card:active {
|
||||
transform: scale(0.94);
|
||||
}
|
||||
|
||||
.icon-tile {
|
||||
width: var(--tile-size);
|
||||
height: var(--tile-size);
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: calc(var(--tile-size) * 0.267);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.92),
|
||||
inset 0 -1px 0 rgba(255, 255, 255, 0.45),
|
||||
0 6px 12px -7px rgba(54, 80, 99, 0.34);
|
||||
}
|
||||
|
||||
.icon-tile::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: calc(var(--tile-size) * 0.1);
|
||||
left: calc(var(--tile-size) * 0.167);
|
||||
width: calc(var(--tile-size) * 0.3);
|
||||
height: calc(var(--tile-size) * 0.133);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.46);
|
||||
filter: blur(2px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.icon-tile::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.corner-tag {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: var(--tag-size);
|
||||
height: var(--tag-size);
|
||||
overflow: hidden;
|
||||
border-top-left-radius: inherit;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.corner-tag span {
|
||||
position: absolute;
|
||||
left: calc(var(--tag-size) * -0.429);
|
||||
top: var(--tag-offset);
|
||||
width: calc(var(--tag-size) * 1.714);
|
||||
transform: rotate(-45deg);
|
||||
background: linear-gradient(90deg, #4e97ff 0%, #2f7df6 53%, #2a73eb 100%);
|
||||
color: #ffffff;
|
||||
font-size: clamp(7px, 2.1vw, 8px);
|
||||
line-height: var(--tag-line);
|
||||
letter-spacing: 0.5px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 1px 2px rgba(37, 99, 235, 0.32);
|
||||
}
|
||||
|
||||
.icon-main {
|
||||
filter: drop-shadow(0 2px 0 rgba(255, 255, 255, 0.22))
|
||||
drop-shadow(0 3px 3px rgba(37, 56, 76, 0.18));
|
||||
}
|
||||
|
||||
.icon-stage {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
}
|
||||
|
||||
.icon-stage > iconify-icon {
|
||||
width: calc(var(--icon-size) * 0.94);
|
||||
height: calc(var(--icon-size) * 0.94);
|
||||
}
|
||||
|
||||
.icon-stage > .icon-sub {
|
||||
width: calc(var(--icon-size) * 0.62);
|
||||
height: calc(var(--icon-size) * 0.62);
|
||||
}
|
||||
|
||||
.icon-stage > .icon-mini {
|
||||
width: calc(var(--icon-size) * 0.46);
|
||||
height: calc(var(--icon-size) * 0.46);
|
||||
}
|
||||
|
||||
.icon-tile.is-image {
|
||||
color: transparent;
|
||||
background: transparent;
|
||||
box-shadow: 0 6px 12px -7px rgba(54, 80, 99, 0.26);
|
||||
}
|
||||
|
||||
.icon-tile.is-image::before,
|
||||
.icon-tile.is-image::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.icon-tile.is-image > .tile-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
-webkit-user-drag: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mini-accent {
|
||||
position: absolute;
|
||||
right: calc(var(--tile-size) * -0.13);
|
||||
bottom: calc(var(--tile-size) * -0.115);
|
||||
width: calc(var(--tile-size) * 0.283);
|
||||
height: calc(var(--tile-size) * 0.283);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 2px 5px rgba(31, 48, 67, 0.12);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.mini-accent iconify-icon {
|
||||
width: calc(var(--tile-size) * 0.19);
|
||||
height: calc(var(--tile-size) * 0.19);
|
||||
}
|
||||
|
||||
.feature-label {
|
||||
margin-top: clamp(6px, 2.2vw, 10px);
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #2d3640;
|
||||
font-size: clamp(12px, 3.4vw, 13px);
|
||||
line-height: 1.3;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tile-purple {
|
||||
color: #8b66f4;
|
||||
background:
|
||||
radial-gradient(circle at 29% 22%, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0) 42%),
|
||||
linear-gradient(145deg, #f7f2ff 0%, #ece3ff 60%, #e4d8ff 100%);
|
||||
}
|
||||
|
||||
.tile-mint {
|
||||
color: #67c8bb;
|
||||
background:
|
||||
radial-gradient(circle at 29% 22%, rgba(255, 255, 255, 0.94) 0%, rgba(255, 255, 255, 0) 42%),
|
||||
linear-gradient(145deg, #effcf9 0%, #ddf8f2 58%, #d4f4ed 100%);
|
||||
}
|
||||
|
||||
.tile-blue {
|
||||
color: #4b8df5;
|
||||
background:
|
||||
radial-gradient(circle at 29% 22%, rgba(255, 255, 255, 0.94) 0%, rgba(255, 255, 255, 0) 42%),
|
||||
linear-gradient(145deg, #f1f6ff 0%, #e6efff 60%, #dce8ff 100%);
|
||||
}
|
||||
|
||||
.tile-pink {
|
||||
color: #ee6f7d;
|
||||
background:
|
||||
radial-gradient(circle at 29% 22%, rgba(255, 255, 255, 0.94) 0%, rgba(255, 255, 255, 0) 42%),
|
||||
linear-gradient(145deg, #fff3f4 0%, #ffe6e9 58%, #ffdde2 100%);
|
||||
}
|
||||
|
||||
.tile-peach {
|
||||
color: #f2a34b;
|
||||
background:
|
||||
radial-gradient(circle at 29% 22%, rgba(255, 255, 255, 0.94) 0%, rgba(255, 255, 255, 0) 42%),
|
||||
linear-gradient(145deg, #fff8ee 0%, #ffefdd 60%, #ffe8cf 100%);
|
||||
}
|
||||
|
||||
.tile-green {
|
||||
color: #35ae8b;
|
||||
background:
|
||||
radial-gradient(circle at 29% 22%, rgba(255, 255, 255, 0.94) 0%, rgba(255, 255, 255, 0) 42%),
|
||||
linear-gradient(145deg, #f0fcf8 0%, #e0f8f1 58%, #d7f5ec 100%);
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #98A4B3;
|
||||
cursor: pointer;
|
||||
transition: color 140ms ease, transform 140ms ease;
|
||||
}
|
||||
|
||||
.tab-button:active {
|
||||
transform: scale(0.94);
|
||||
}
|
||||
|
||||
.tab-button.is-active {
|
||||
color: #3B71E8;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 80px;
|
||||
z-index: 60;
|
||||
max-width: calc(100% - 44px);
|
||||
width: max-content;
|
||||
padding: 9px 16px;
|
||||
border-radius: 999px;
|
||||
background: rgba(29, 41, 57, 0.9);
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, 8px);
|
||||
transition: opacity 180ms ease, transform 180ms ease;
|
||||
box-shadow: 0 8px 24px rgba(18, 33, 49, 0.18);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.toast.is-visible {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
/* 5.51 寸小屏(窄屏或矮屏)收紧纵向节奏,保证首屏完整 */
|
||||
@media (max-width: 374px), (max-height: 760px) {
|
||||
.app-frame {
|
||||
--nav-height: 46px;
|
||||
--tab-height: 58px;
|
||||
--tag-offset: 5.4px;
|
||||
--tag-line: 13px;
|
||||
}
|
||||
|
||||
.group-bar {
|
||||
height: 35px;
|
||||
gap: 8px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.feature-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tab-bar .tab-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.group-arrow {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
row-gap: 12px;
|
||||
padding: 14px 12px 18px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
bottom: 72px;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.feature-card,
|
||||
.tab-button,
|
||||
.toast {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-frame">
|
||||
<!-- 顶部系统状态栏 -->
|
||||
<header class="flex-none bg-[#3B71E8] text-white">
|
||||
<div class="status-bar flex items-center justify-between px-3">
|
||||
<div class="flex items-center gap-[7px]">
|
||||
<span class="text-[11.5px] font-semibold tracking-[0.2px]">16:48</span>
|
||||
</div>
|
||||
<div aria-label="Wi-Fi" class="flex min-w-0 items-center gap-[5px]">
|
||||
<iconify-icon class="opacity-95" height="14" icon="mdi:wifi" width="14"></iconify-icon>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<!-- 蓝色导航栏 -->
|
||||
<nav aria-label="国际业务导航栏" class="nav-bar relative z-20 flex flex-none items-center justify-center bg-[#3B71E8] text-white">
|
||||
<h1 class="nav-title">国际业务</h1>
|
||||
<button aria-label="通知,341 条消息" class="nav-bell absolute right-[12px] top-1/2 flex -translate-y-1/2 items-center justify-center rounded-full active:bg-white/10" onclick="showNotificationToast()" type="button">
|
||||
<iconify-icon class="drop-shadow-[0_1px_1px_rgba(0,0,0,0.12)]" icon="mdi:bell-outline"></iconify-icon>
|
||||
<span class="nav-badge absolute -right-[2px] top-[0px] rounded-full bg-[#e1192d] px-[4px] text-center font-semibold text-white shadow-[0_1px_2px_rgba(145,16,31,0.35)]">341</span>
|
||||
</button>
|
||||
</nav>
|
||||
<!-- 主内容滚动区 -->
|
||||
<main class="main-scroll min-h-0 flex-1 overflow-y-auto overflow-x-hidden bg-[#F5F7FA]" id="mainContent">
|
||||
<!-- 国际出港 -->
|
||||
<section aria-labelledby="outbound-title">
|
||||
<div class="group-bar">
|
||||
<img alt="" aria-hidden="true" class="group-arrow" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/f48cf8269922458abee299bf1f4bdfae.png"/>
|
||||
<h2 class="group-title" id="outbound-title">国际出港</h2>
|
||||
</div>
|
||||
<div class="feature-grid">
|
||||
<button aria-label="出港计重" class="feature-card" onclick="openFeature('出港计重')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="出港计重图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/1e9507a32e7c45dea0362c3cc87b98c2.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">出港计重</span>
|
||||
</button>
|
||||
<button aria-label="出港抵运" class="feature-card" onclick="openFeature('出港抵运')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="出港抵运图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/1c4971c9a2d9459da4ee19b5b5431bf1.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">出港抵运</span>
|
||||
</button>
|
||||
<button aria-label="出港交接" class="feature-card" onclick="openFeature('出港交接')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="出港交接图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/460f6760462b4690823a98613894ce6e.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">出港交接</span>
|
||||
</button>
|
||||
<button aria-label="出港移库" class="feature-card" onclick="openFeature('出港移库')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="出港移库图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/0bc7fe8fff684d748b0b27f2a120c6ed.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">出港移库</span>
|
||||
</button>
|
||||
<button aria-label="出港仓库" class="feature-card" onclick="openFeature('出港仓库')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="出港仓库图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/18258a4775944c13b120d486ea13e925.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">出港仓库</span>
|
||||
</button>
|
||||
<button aria-label="出港查询" class="feature-card" onclick="openFeature('出港查询')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="出港查询图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/6c6fa3ed9a1449f1a704dbb6415814a1.png"/>
|
||||
</span>
|
||||
<span class="feature-label">出港查询</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 国际进港 -->
|
||||
<section aria-labelledby="inbound-title">
|
||||
<div class="group-bar">
|
||||
<img alt="" aria-hidden="true" class="group-arrow" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/f48cf8269922458abee299bf1f4bdfae.png"/>
|
||||
<h2 class="group-title" id="inbound-title">国际进港</h2>
|
||||
</div>
|
||||
<div class="feature-grid">
|
||||
<button aria-label="进港理货" class="feature-card" onclick="openFeature('进港理货')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="进港理货图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/cc9965b937344aca99bdf329fea23e91.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">进港理货</span>
|
||||
</button>
|
||||
<button aria-label="提取出库" class="feature-card" onclick="openFeature('提取出库')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="提取出库图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/7bcf76ae718e4da9a784a7003c3884a7.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">提取出库</span>
|
||||
</button>
|
||||
<button aria-label="进港仓库" class="feature-card" onclick="openFeature('进港仓库')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="进港仓库图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/2d325743d7004623a87c3982190fe1d9.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">进港仓库</span>
|
||||
</button>
|
||||
<button aria-label="进港查询" class="feature-card" onclick="openFeature('进港查询')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="进港查询图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/0fd94c77b8c644b8bb37c1b5d471fcaf.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">进港查询</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 综合管理 -->
|
||||
<section aria-labelledby="manage-title" class="pb-2">
|
||||
<div class="group-bar">
|
||||
<img alt="" aria-hidden="true" class="group-arrow" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/f48cf8269922458abee299bf1f4bdfae.png"/>
|
||||
<h2 class="group-title" id="manage-title">综合管理</h2>
|
||||
</div>
|
||||
<div class="feature-grid" style="padding-bottom: clamp(16px, 5.6vw, 22px);">
|
||||
<button aria-label="ULD管理" class="feature-card" onclick="openFeature('ULD管理')" type="button">
|
||||
<span class="icon-tile is-image">
|
||||
<img alt="ULD管理图标" class="tile-img" draggable="false" src="https://modao.cc/agent-py/media/generated_images/2026-10-20/7b2321b0f277416abad0b61252c5730a.gif"/>
|
||||
</span>
|
||||
<span class="feature-label">ULD管理</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<!-- 底部 Tab 栏 -->
|
||||
<nav aria-label="底部导航" class="tab-bar relative z-30 flex flex-none items-stretch border-t border-[#EDF0F4] bg-white pb-[4px] shadow-[0_-1px_2px_rgba(16,24,40,0.04)]">
|
||||
<button aria-label="首页" class="tab-button" onclick="switchTab('首页')" type="button">
|
||||
<iconify-icon icon="mdi:home-outline"></iconify-icon>
|
||||
<span class="tab-label">首页</span>
|
||||
</button>
|
||||
<button aria-label="国内" class="tab-button" onclick="switchTab('国内')" type="button">
|
||||
<iconify-icon icon="mdi:earth"></iconify-icon>
|
||||
<span class="tab-label">国内</span>
|
||||
</button>
|
||||
<button aria-current="page" aria-label="国际,当前页面" class="tab-button is-active" onclick="switchTab('国际')" type="button">
|
||||
<span class="tab-icon-stack relative inline-flex items-center justify-center">
|
||||
<iconify-icon class="absolute bottom-0 left-0" icon="mdi:earth"></iconify-icon>
|
||||
<iconify-icon class="absolute right-0 top-0 -rotate-[14deg]" icon="mdi:airplane"></iconify-icon>
|
||||
</span>
|
||||
<span class="tab-label is-active">国际</span>
|
||||
</button>
|
||||
<button aria-label="我的" class="tab-button" onclick="switchTab('我的')" type="button">
|
||||
<iconify-icon icon="mdi:account-circle-outline"></iconify-icon>
|
||||
<span class="tab-label">我的</span>
|
||||
</button>
|
||||
</nav>
|
||||
<div aria-live="polite" class="toast" id="toast" role="status"></div>
|
||||
</div>
|
||||
<script>
|
||||
let toastTimer = null;
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.getElementById("toast");
|
||||
if (!toast) return;
|
||||
|
||||
toast.textContent = message;
|
||||
toast.classList.add("is-visible");
|
||||
|
||||
if (toastTimer) {
|
||||
clearTimeout(toastTimer);
|
||||
}
|
||||
|
||||
toastTimer = setTimeout(function () {
|
||||
toast.classList.remove("is-visible");
|
||||
}, 1800);
|
||||
}
|
||||
|
||||
function openFeature(name) {
|
||||
showToast(name + " · 功能即将开放");
|
||||
}
|
||||
|
||||
function showNotificationToast() {
|
||||
showToast("您有 341 条待处理消息");
|
||||
}
|
||||
|
||||
function switchTab(name) {
|
||||
if (name === "首页") {
|
||||
window.location.href = "dashboard.html";
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === "国内") {
|
||||
window.location.href = "domestic.html";
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === "国际") {
|
||||
const main = document.getElementById("mainContent");
|
||||
if (main) {
|
||||
main.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
showToast("当前已在国际业务");
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(name + "模块暂未接入");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
documents/5.5寸首页新样式/icons v2/国内业务/事故签证.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国内业务/出港复磅.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国内业务/出港存放.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国内业务/出港收运.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国内业务/单证交接.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国内业务/货物转运.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国内业务/转运记录.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国内业务/进港卸机.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国内业务/进港移库.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国际业务/ULD管理.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国际业务/出港移库.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 24 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国际业务/进港仓库.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
BIN
documents/5.5寸首页新样式/icons v2/国际业务/进港理货.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
documents/5.5寸首页新样式/icons/国内/guonei_03.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
documents/5.5寸首页新样式/icons/国内/guonei_05.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
documents/5.5寸首页新样式/icons/国内/guonei_07.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
documents/5.5寸首页新样式/icons/国内/guonei_13.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
documents/5.5寸首页新样式/icons/国内/guonei_15.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
documents/5.5寸首页新样式/icons/国内/guonei_17.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
documents/5.5寸首页新样式/icons/国内/guonei_22.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
documents/5.5寸首页新样式/icons/国内/guonei_23.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
documents/5.5寸首页新样式/icons/国内/guonei_26.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_03.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_07.gif
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_09.gif
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_11.gif
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_16.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_17.gif
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_19.gif
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_24.gif
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_26.gif
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_28.gif
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_33.gif
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
documents/5.5寸首页新样式/icons/国际/guoji_35.gif
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
@@ -149,7 +149,7 @@ dependencies {
|
||||
api 'com.github.li-xiaojun:XPopup:2.9.19'
|
||||
|
||||
// 屏幕适配
|
||||
api 'me.jessyan:autosize:1.2.+'
|
||||
api 'me.jessyan:autosize:1.2.1'
|
||||
|
||||
// 佳博打印SDK
|
||||
api(name: 'sdk2-2.0.4', ext: 'aar') {
|
||||
|
||||
@@ -177,6 +177,13 @@ data class GjcMaWb(
|
||||
val agentDisplay: String?
|
||||
get() = if (agentName.isNullOrEmpty()) agentCode else agentName
|
||||
|
||||
/**
|
||||
* 5.5寸手机版-进港仓库:始发站展示值。IntImpStorage/pageQuery 不返回 dep/fdep,
|
||||
* 始发站在 origin(货源地)中(Bug #635 实测)。
|
||||
*/
|
||||
val depDisplay: String?
|
||||
get() = if (dep.isNullOrEmpty()) origin else dep
|
||||
|
||||
/**
|
||||
* 5.5寸手机版-出港仓库:卡片状态标签(已清仓=绿 / 未清仓=橙,clearNormal 为空视作未清仓)。
|
||||
* 已清仓时卡片「清仓」按钮置灰不可点。
|
||||
|
||||
@@ -54,9 +54,14 @@ class GjjWarehouse(
|
||||
var splitFlag: String? = "",
|
||||
var inDate: String? = "",
|
||||
var clearNormal: String? = "",
|
||||
var range: String? = ""
|
||||
var range: String? = "",
|
||||
var fdep: String? = "" // 始发站(详情接口 warehouseList 返回 fdep,dep 恒为空)
|
||||
) {
|
||||
|
||||
/** 始发站展示:优先 fdep,兜底 dep / origin(Bug #635) */
|
||||
val depDisplay: String
|
||||
get() = listOf(fdep, dep, origin).firstOrNull { !it.isNullOrEmpty() } ?: ""
|
||||
|
||||
fun getWaybillCode() = "${prefix}${no}"
|
||||
|
||||
fun getFlightDesc() = "${fdate}/${fno}"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.lukouguoji.module_base.bean
|
||||
|
||||
import com.lukouguoji.module_base.ktx.toPlainText
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
@@ -19,5 +20,14 @@ data class GjjStorageUse(
|
||||
var outDate: String? = null, // 出库时间
|
||||
var outOpId: String? = null, // 出库人ID
|
||||
var outOpName: String? = null, // 出库人姓名
|
||||
var cargoStatus: String? = null // 货物状态
|
||||
) : Serializable
|
||||
var cargoStatus: String? = null, // 货物状态
|
||||
var inPc: Long? = null, // 入库件数
|
||||
var inWeight: Double? = null, // 入库重量
|
||||
var outPc: Long? = null, // 出库件数
|
||||
var outWeight: Double? = null // 出库重量
|
||||
) : Serializable {
|
||||
val inPcText: String get() = inPc?.toString() ?: ""
|
||||
val inWeightText: String get() = inWeight?.let { "${it.toPlainText()} kg" } ?: ""
|
||||
val outPcText: String get() = outPc?.toString() ?: ""
|
||||
val outWeightText: String get() = outWeight?.let { "${it.toPlainText()} kg" } ?: ""
|
||||
}
|
||||
|
||||
@@ -13,12 +13,10 @@ import android.widget.TextView
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import com.lukouguoji.module_base.R
|
||||
import com.lukouguoji.module_base.adapter.bindAdapter
|
||||
import com.lukouguoji.module_base.adapter.bindOnSelected
|
||||
import com.lukouguoji.module_base.interfaces.IOnSpinnerSelected
|
||||
import com.lukouguoji.module_base.ktx.formatDate
|
||||
import com.lukouguoji.module_base.ktx.getActivity
|
||||
import com.lukouguoji.module_base.util.Common
|
||||
import com.lxj.xpopup.XPopup
|
||||
import dev.utils.app.info.KeyValue
|
||||
import java.util.Calendar
|
||||
|
||||
@@ -32,7 +30,7 @@ import java.util.Calendar
|
||||
* |-----------|-----------|----------|
|
||||
* | `INPUT` | EditText | 无 |
|
||||
* | `DATE` | TextView(点击弹日历)| 日历 |
|
||||
* | `SPINNER` | Spinner | 下箭头 |
|
||||
* | `SPINNER` | TextView(点击弹底部列表,与 PhoneFormRow SELECT 同款)| 下箭头 |
|
||||
*
|
||||
* XML 用法:
|
||||
* ```xml
|
||||
@@ -58,6 +56,7 @@ class PhoneDataLayout @JvmOverloads constructor(
|
||||
val llContent: LinearLayout
|
||||
val et: EditText
|
||||
val tv: TextView
|
||||
/** 保留以兼容布局 id;Bug #623 起 SPINNER 形态改为底部弹出列表,原生 Spinner 恒隐藏 */
|
||||
val spinner: Spinner
|
||||
val iv: ImageView
|
||||
|
||||
@@ -85,7 +84,7 @@ class PhoneDataLayout @JvmOverloads constructor(
|
||||
field = v
|
||||
et.hint = v
|
||||
tv.hint = v
|
||||
bindAdapter(spinner, list, v, R.layout.item_spinner_list_14sp)
|
||||
applyDefaultSelection()
|
||||
}
|
||||
|
||||
var value: String = ""
|
||||
@@ -99,7 +98,7 @@ class PhoneDataLayout @JvmOverloads constructor(
|
||||
var list: List<KeyValue> = emptyList()
|
||||
set(v) {
|
||||
field = v
|
||||
bindAdapter(spinner, v, hint, R.layout.item_spinner_list_14sp)
|
||||
applyDefaultSelection()
|
||||
applyValue()
|
||||
}
|
||||
|
||||
@@ -114,7 +113,6 @@ class PhoneDataLayout @JvmOverloads constructor(
|
||||
field = v
|
||||
llContent.isEnabled = v
|
||||
et.isEnabled = v
|
||||
spinner.isEnabled = v
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -136,13 +134,6 @@ class PhoneDataLayout @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
bindOnSelected(spinner, object : IOnSpinnerSelected {
|
||||
override fun onSelected(position: Int) {
|
||||
value = list.getOrNull(position)?.value ?: ""
|
||||
refreshCallBack?.invoke()
|
||||
}
|
||||
})
|
||||
|
||||
applyType()
|
||||
}
|
||||
|
||||
@@ -157,6 +148,28 @@ class PhoneDataLayout @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** SPINNER:底部弹出列表(标题取 hint,无 hint 时取 title),选中后回写 value 并回调 */
|
||||
private val spinnerClick: (v: View) -> Unit = {
|
||||
if (enable && list.isNotEmpty()) {
|
||||
XPopup.Builder(context)
|
||||
.asBottomList(hint.ifEmpty { title }, list.map { it.key }.toTypedArray()) { position, _ ->
|
||||
value = list.getOrNull(position)?.value ?: ""
|
||||
refreshCallBack?.invoke()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保持原生 Spinner 的默认取值语义:无 hint 时自动选中第一项(value 为空才补),
|
||||
* 有 hint 时保持空值显示占位文字。
|
||||
*/
|
||||
private fun applyDefaultSelection() {
|
||||
if (type == PhoneDataLayoutType.SPINNER && hint.isEmpty() && value.isEmpty() && list.isNotEmpty()) {
|
||||
value = list[0].value
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyType() {
|
||||
when (type) {
|
||||
PhoneDataLayoutType.INPUT -> {
|
||||
@@ -179,12 +192,12 @@ class PhoneDataLayout @JvmOverloads constructor(
|
||||
|
||||
PhoneDataLayoutType.SPINNER -> {
|
||||
et.visibility = GONE
|
||||
tv.visibility = GONE
|
||||
spinner.visibility = VISIBLE
|
||||
tv.visibility = VISIBLE
|
||||
spinner.visibility = GONE
|
||||
iv.visibility = VISIBLE
|
||||
iv.setImageResource(R.drawable.ic_phone_chevron_down)
|
||||
llContent.setOnClickListener(null)
|
||||
llContent.isClickable = false
|
||||
llContent.setOnClickListener(spinnerClick)
|
||||
applyDefaultSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,10 +213,8 @@ class PhoneDataLayout @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
PhoneDataLayoutType.SPINNER -> {
|
||||
if (value.isNotEmpty()) {
|
||||
val position = list.indexOfFirst { it.value == value }
|
||||
if (position >= 0) spinner.setSelection(position)
|
||||
}
|
||||
// 显示选中项文案;列表里找不到就按原值显示(如后端返回的历史值),空值显示 hint
|
||||
tv.text = list.firstOrNull { it.value == value }?.key ?: value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,10 +234,10 @@ class PhoneDataLayout @JvmOverloads constructor(
|
||||
|
||||
/** 重置为空值(筛选弹层「重置」按钮使用) */
|
||||
fun reset() {
|
||||
value = ""
|
||||
if (type == PhoneDataLayoutType.SPINNER && list.isNotEmpty()) {
|
||||
spinner.setSelection(if (hint.isEmpty()) list.size - 1 else list.size)
|
||||
}
|
||||
// 与原 Spinner 版本一致:有 hint 回到占位(空值);无 hint 选中最后一项
|
||||
value = if (type == PhoneDataLayoutType.SPINNER && hint.isEmpty() && list.isNotEmpty()) {
|
||||
list.last().value
|
||||
} else ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.util.AttributeSet
|
||||
import android.util.SparseArray
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
@@ -136,6 +137,20 @@ class PhoneFormRow @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// INPUT 输入完成回调(与平板 PadDataLayoutNew 一致:失焦即视为输入完成;另加键盘「完成/下一项」)。
|
||||
// 原先 INPUT 形态从不触发 refreshCallBack,平板车号/运单号手输后不会回查(Bug #621)
|
||||
et.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus && type == PhoneFormRowType.INPUT) refreshCallBack?.invoke()
|
||||
}
|
||||
et.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (type == PhoneFormRowType.INPUT &&
|
||||
(actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT)
|
||||
) {
|
||||
refreshCallBack?.invoke()
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
applyType()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版详情卡片表头:#F3F4F6 浅灰底 + 顶部 12dp 圆角(出港移库编辑-补充信息卡片) -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_bg" />
|
||||
<corners
|
||||
android:topLeftRadius="12dp"
|
||||
android:topRightRadius="12dp" />
|
||||
</shape>
|
||||
23
module_base/src/main/res/drawable/bg_phone_stat_blue.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版三格统计卡背景:白底 12dp 圆角 + 左侧 4dp 色边(随圆角弯曲),
|
||||
对应设计稿 rounded-lg border-l-[4px](Bug #620,替代原先卡片内悬空的短竖条)
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/phone_primary" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:left="4dp">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<corners
|
||||
android:bottomLeftRadius="8dp"
|
||||
android:bottomRightRadius="12dp"
|
||||
android:topLeftRadius="8dp"
|
||||
android:topRightRadius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
23
module_base/src/main/res/drawable/bg_phone_stat_green.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版三格统计卡背景:白底 12dp 圆角 + 左侧 4dp 色边(随圆角弯曲),
|
||||
对应设计稿 rounded-lg border-l-[4px](Bug #620,替代原先卡片内悬空的短竖条)
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/phone_tag_green_text" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:left="4dp">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<corners
|
||||
android:bottomLeftRadius="8dp"
|
||||
android:bottomRightRadius="12dp"
|
||||
android:topLeftRadius="8dp"
|
||||
android:topRightRadius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
23
module_base/src/main/res/drawable/bg_phone_stat_orange.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版三格统计卡背景:白底 12dp 圆角 + 左侧 4dp 色边(随圆角弯曲),
|
||||
对应设计稿 rounded-lg border-l-[4px](Bug #620,替代原先卡片内悬空的短竖条)
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/phone_tag_orange_text" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:left="4dp">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<corners
|
||||
android:bottomLeftRadius="8dp"
|
||||
android:bottomRightRadius="12dp"
|
||||
android:topLeftRadius="8dp"
|
||||
android:topRightRadius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="bean"
|
||||
type="com.lukouguoji.module_base.bean.FileBean" />
|
||||
</data>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="5dp"
|
||||
android:background="@{bean.path.length() == 0 ? @color/color_f2 : @color/white}">
|
||||
|
||||
<ImageView
|
||||
visible="@{bean.path.length() == 0}"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/img_add" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv"
|
||||
loadImage="@{bean.path}"
|
||||
visible="@{bean.path}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<View
|
||||
visible="@{bean.canDelete}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black_tran10" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_delete"
|
||||
visible="@{bean.canDelete}"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/img_delete_red" />
|
||||
|
||||
</RelativeLayout>
|
||||
</layout>
|
||||
@@ -1,4 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版选图格子(平板版在 layout-sw600dp/,两者变量与 id 一致,共用 ImageSelectNewViewHolder)。
|
||||
加号槽位与进港移库「异常照片」一致:灰色虚线框 + 灰色加号;已选图片长按出删除角标。
|
||||
-->
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<data>
|
||||
@@ -12,15 +16,17 @@
|
||||
android:id="@+id/rl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="5dp"
|
||||
android:background="@{bean.path.length() == 0 ? @color/color_f2 : @color/white}">
|
||||
android:layout_margin="6dp"
|
||||
android:background="@drawable/bg_phone_photo_add">
|
||||
|
||||
<ImageView
|
||||
<TextView
|
||||
visible="@{bean.path.length() == 0}"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/img_add" />
|
||||
android:text="+"
|
||||
android:textColor="@color/phone_text_hint"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv"
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
android:layout_height="50dp"
|
||||
android:padding="10dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/img_add" />
|
||||
android:src="@drawable/img_add"
|
||||
android:tint="@color/phone_primary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv"
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lukouguoji.module_base.base.BaseViewModel
|
||||
import com.lukouguoji.module_base.util.DeviceUtil
|
||||
import com.lukouguoji.module_base.bean.GjcMaWb
|
||||
import com.lukouguoji.module_base.bean.GjcWeighingBean
|
||||
import com.lukouguoji.module_base.common.Constant
|
||||
@@ -120,6 +121,8 @@ class GjcWeighingStartViewModel : BaseViewModel() {
|
||||
* 自动计算运抵重量:地磅称重 - (托盘数量 × 托盘自重)
|
||||
*/
|
||||
private fun recalculateArriveWeight() {
|
||||
// 手机端无地磅重量输入(Bug #621),运抵重量手输;否则会被 0 - 托盘总重 覆盖成 "0"
|
||||
if (DeviceUtil.isPhone()) return
|
||||
val gross = diBangWeight.value?.toDoubleOrNull() ?: 0.0
|
||||
val trayTotal = getTrayTotalWeight()
|
||||
val net = gross - trayTotal
|
||||
@@ -198,6 +201,8 @@ class GjcWeighingStartViewModel : BaseViewModel() {
|
||||
onSuccess = { result ->
|
||||
maWbBean.value = result.data ?: GjcMaWb()
|
||||
pageRemark.value = maWbBean.value?.remark
|
||||
// 运单已带平板车号时直接回查自重(原先只在手输车号完成后查询,Bug #621)
|
||||
onCarIdInputComplete()
|
||||
// 更新航班日期字符串
|
||||
flightDate.value = result.data?.fdate?.formatDate() ?: ""
|
||||
}
|
||||
|
||||
@@ -5,14 +5,18 @@ import androidx.lifecycle.MutableLiveData
|
||||
import com.lukouguoji.module_base.base.BaseViewModel
|
||||
import com.lukouguoji.module_base.bean.GjcWeighingRecordBean
|
||||
import com.lukouguoji.module_base.common.Constant
|
||||
import com.lukouguoji.module_base.http.net.NetApply
|
||||
import com.lukouguoji.module_base.ktx.formatDate
|
||||
import com.lukouguoji.module_base.ktx.launchCollect
|
||||
import com.lukouguoji.module_base.ktx.showToast
|
||||
import com.lukouguoji.module_base.util.DictUtils
|
||||
|
||||
/**
|
||||
* 国际出港计重-运单详情 ViewModel(5.5寸手机版专属页面)
|
||||
*
|
||||
* 只读展示计重记录列表传入的运单信息,无接口请求。
|
||||
* 注:设计稿中的「通道号」字段在 /IntExpCheckIn/checked/pageQuery 出参中不存在(后端缺口,
|
||||
* 见接口问题清单),布局侧空值兜底显示 "-"。
|
||||
* 先展示计重记录列表传入的运单信息;/IntExpCheckIn/checked/pageQuery 出参中
|
||||
* 航班日期/航班号/航程/业务类型/备注恒为空(Bug #622),再按 11 位运单号调
|
||||
* /IntExpCheckIn/queryWbByNo 补齐这些字段(只补空值,不覆盖列表已有数据)。
|
||||
*/
|
||||
class GjcWeighingWbDetailViewModel : BaseViewModel() {
|
||||
|
||||
@@ -23,8 +27,37 @@ class GjcWeighingWbDetailViewModel : BaseViewModel() {
|
||||
val data = intent.getSerializableExtra(Constant.Key.BEAN) as? GjcWeighingRecordBean
|
||||
if (data != null) {
|
||||
bean.value = data
|
||||
loadWaybillInfo(data)
|
||||
} else {
|
||||
showToast("参数错误")
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadWaybillInfo(data: GjcWeighingRecordBean) {
|
||||
if (data.wbNo.isEmpty()) return
|
||||
launchCollect({ NetApply.api.getIntExpCheckInWbByNo(data.wbNo) }) {
|
||||
onSuccess = { result ->
|
||||
result.data?.let { wb ->
|
||||
if (data.fdate.isEmpty()) data.fdate = wb.fdate.formatDate()
|
||||
if (data.fno.isEmpty()) data.fno = wb.fno.orEmpty()
|
||||
if (data.range.isEmpty()) data.range = wb.range.orEmpty()
|
||||
if (data.businessType.isEmpty()) data.businessType = wb.businessType.orEmpty()
|
||||
if (data.businessName.isEmpty()) data.businessName = wb.businessName.orEmpty()
|
||||
if (data.remark.isEmpty()) data.remark = wb.remark.orEmpty()
|
||||
bean.value = data
|
||||
fillBusinessName(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** queryWbByNo 只返回业务类型代码(如 ANR),按国际出港业务类型字典转成中文名(与开始计重页同源) */
|
||||
private fun fillBusinessName(data: GjcWeighingRecordBean) {
|
||||
if (data.businessName.isNotEmpty() || data.businessType.isEmpty()) return
|
||||
DictUtils.getBusinessTypeList(type = "IO", addAll = false) { list ->
|
||||
val name = list.firstOrNull { it.value == data.businessType }?.key ?: return@getBusinessTypeList
|
||||
data.businessName = name
|
||||
bean.postValue(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,11 +178,18 @@ class IntExpMoveEditViewModel : BaseViewModel(), IOnItemClickListener {
|
||||
* 处理图片删除点击事件
|
||||
*/
|
||||
override fun onItemClick(position: Int, type: Int) {
|
||||
if (type == R.id.iv_delete) {
|
||||
val adapter = rvImages?.commonAdapter() ?: return
|
||||
if (position < adapter.items.size) {
|
||||
adapter.removeItem(position)
|
||||
}
|
||||
if (position >= adapter.items.size) return
|
||||
when (type) {
|
||||
// 长按已选图片:切换删除角标(Bug #632:原先未处理长按,角标出不来导致无法删除)
|
||||
R.id.rl -> {
|
||||
val bean = adapter.getItem(position) as? FileBean ?: return
|
||||
if (bean.path.isNotEmpty() && pageType.value != DetailsPageType.Details) {
|
||||
bean.canDelete.set(!bean.canDelete.get())
|
||||
}
|
||||
}
|
||||
|
||||
R.id.iv_delete -> adapter.removeItem(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,15 +78,9 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_blue"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -121,15 +115,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_orange"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_orange_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -164,15 +152,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_green"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_green_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -80,15 +80,9 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_blue"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -123,15 +117,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_orange"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_orange_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -166,15 +154,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_green"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_green_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -230,22 +212,7 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 右下 FAB:新增计重(与平板搜索栏「+」一致,空运单进入开始计重) ==================== -->
|
||||
<TextView
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="84dp"
|
||||
android:background="@drawable/bg_phone_fab"
|
||||
android:contentDescription="新增计重"
|
||||
android:elevation="6dp"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:onClick="@{()-> viewModel.addClick()}"
|
||||
android:text="+"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="32sp" />
|
||||
<!-- Bug #624:手机端去掉「新增计重」FAB(平板搜索栏「+」不变) -->
|
||||
|
||||
<!-- ==================== 右下 FAB:计重记录 ==================== -->
|
||||
<ImageView
|
||||
|
||||
@@ -74,15 +74,9 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_blue"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -118,15 +112,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_orange"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_orange_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -162,15 +150,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_green"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_green_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -176,15 +176,7 @@
|
||||
type="@{PhoneFormRowType.INPUT}"
|
||||
value="@={viewModel.arrivePc}" />
|
||||
|
||||
<!-- 地磅重量(与平板右上角地磅称重一致):运抵重量 = 地磅重量 - 托盘数量 × 托盘自重,由 ViewModel 自动计算 -->
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
hint='@{"请输入地磅重量"}'
|
||||
numeric="@{true}"
|
||||
title='@{"地磅重量"}'
|
||||
type="@{PhoneFormRowType.INPUT}"
|
||||
value="@={viewModel.diBangWeight}" />
|
||||
<!-- Bug #621:手机端去掉「地磅重量」,运抵重量手输(不再按地磅重量自动计算) -->
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
|
||||
android:layout_width="match_parent"
|
||||
@@ -267,12 +259,14 @@
|
||||
type="@{PhoneFormRowType.INPUT}"
|
||||
value="@={viewModel.maWbBean.carId}" />
|
||||
|
||||
<!-- 平板车自重:平板车号输入完成后回查,只读 -->
|
||||
<!-- 平板车自重:平板车号输入完成 / 运单自带车号加载后回查,只读;
|
||||
Bug #621:用不可编辑的 INPUT 形态,与其他行一样左对齐(TEXT 形态为右对齐) -->
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
enable="@{false}"
|
||||
title='@{"平板车自重"}'
|
||||
type="@{PhoneFormRowType.TEXT}"
|
||||
type="@{PhoneFormRowType.INPUT}"
|
||||
value="@{viewModel.carWeight}" />
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
<data>
|
||||
|
||||
<import type="android.view.View" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.lukouguoji.gjc.viewModel.IntExpArriveSubOrderViewModel" />
|
||||
@@ -60,12 +62,16 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 列表 ==================== -->
|
||||
<!-- ==================== 列表 + 空态 ==================== -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:overScrollMode="never"
|
||||
android:paddingTop="4dp"
|
||||
@@ -76,6 +82,33 @@
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/item_int_exp_arrive_sub_order" />
|
||||
|
||||
<!-- 空态(Bug #631:无分单的运单也可进入本页) -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="@{viewModel.displayList.empty ? View.VISIBLE : View.GONE}">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_phone_clipboard"
|
||||
android:tint="@color/phone_text_hint" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="暂无分单"
|
||||
android:textColor="@color/phone_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- ==================== 底部操作条 ==================== -->
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneBottomBar
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
详情(DetailsPageType.Details):列表卡片「查看详情」进入,全部只读;
|
||||
编辑(DetailsPageType.Modify):列表卡片「编辑」进入(对应平板侧滑「编辑」),仅备注与交接图片可改。
|
||||
样式沿用设计稿 05_出港移库「运单详情」:蓝色表头卡片 + 两列字段网格;移库人/移库时间取列表数据(listBean)。
|
||||
卡片样式与「出港货物」详情的运单信息 Tab(fragment_gjc_query_waybill.xml)一致:
|
||||
bg_phone_card + 左侧蓝色短竖条标题 + 分割线 + PhoneKvItem 两列字段网格;移库人/移库时间取列表数据(listBean)。
|
||||
-->
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
@@ -14,8 +15,6 @@
|
||||
|
||||
<import type="com.lukouguoji.module_base.common.DetailsPageType" />
|
||||
|
||||
<import type="com.lukouguoji.module_base.ui.weight.phone.PhoneFormRowType" />
|
||||
|
||||
<import type="android.view.View" />
|
||||
|
||||
<variable
|
||||
@@ -42,82 +41,74 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:padding="12dp">
|
||||
|
||||
<!-- ==================== 卡片一:运单基本信息 ==================== -->
|
||||
<!-- ==================== 卡片一:基本信息 ==================== -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="14dp"
|
||||
android:paddingBottom="14dp">
|
||||
|
||||
<!-- 蓝色表头:运单号 + 移交状态 -->
|
||||
<!-- 卡片头 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_phone_card_header_blue"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@{viewModel.dataBean.wbNo}"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<View
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="8dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:background="@{viewModel.dataBean.moveState == 1 ? @drawable/bg_phone_dot_green : @drawable/bg_phone_dot_gray}" />
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="16dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{viewModel.dataBean.moveStateText}"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp" />
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="基本信息"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@color/phone_divider" />
|
||||
|
||||
<!-- 运单号 | 移交状态 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- 运单类型(红标签) -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="22dp"
|
||||
android:background="@drawable/bg_phone_tag_red"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:text='@{viewModel.dataBean.awbTypeName == null || viewModel.dataBean.awbTypeName.empty ? "-" : viewModel.dataBean.awbTypeName}'
|
||||
android:textColor="@color/phone_tag_red_text"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"运单号"}'
|
||||
value="@{viewModel.dataBean.wbNo}" />
|
||||
|
||||
<!-- 品名(主字段大字) -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"移交状态"}'
|
||||
value="@{viewModel.dataBean.moveStateText}" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 件数 | 重量 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text='@{viewModel.dataBean.goods == null || viewModel.dataBean.goods.empty ? "-" : viewModel.dataBean.goods}'
|
||||
android:textColor="@color/phone_text_strong"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- 第 1 行:件数 / 重量 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
@@ -130,14 +121,13 @@
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_weight="1"
|
||||
title='@{"重量"}'
|
||||
value='@{viewModel.dataBean.weight == null ? "" : String.valueOf(viewModel.dataBean.weight) + " kg"}' />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 第 2 行:特码 / 代理人 -->
|
||||
<!-- 特码 | 代理人 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -154,14 +144,13 @@
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_weight="1"
|
||||
title='@{"代理人"}'
|
||||
value="@{viewModel.dataBean.agentCode}" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 第 3 行:出运路径 / 承运人 -->
|
||||
<!-- 出运路径 | 承运人 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -178,14 +167,27 @@
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_weight="1"
|
||||
title='@{"承运人"}'
|
||||
value="@{viewModel.dataBean.by}" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<!-- 运单类型(整行) -->
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
title='@{"运单类型"}'
|
||||
value="@{viewModel.dataBean.awbTypeName}" />
|
||||
|
||||
<!-- 品名(整行) -->
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
title='@{"品名"}'
|
||||
value="@{viewModel.dataBean.goods}" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -193,29 +195,48 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="14dp"
|
||||
android:paddingBottom="14dp"
|
||||
android:visibility="@{viewModel.listBean != null && viewModel.listBean.isMoved ? View.VISIBLE : View.GONE}">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_phone_card_header_green"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="移库信息"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="12dp">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="16dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="移库信息"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@color/phone_divider" />
|
||||
|
||||
<!-- 移库人 | 移库时间 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -226,7 +247,6 @@
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_weight="1"
|
||||
title='@{"移库时间"}'
|
||||
value="@{viewModel.listBean.opdate}" />
|
||||
@@ -235,43 +255,56 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 卡片三:备注 + 交接图片(编辑态可改) ==================== -->
|
||||
<!-- ==================== 卡片三:交接图片(三列加号槽位,长按图片出删除角标) ==================== -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingBottom="12dp">
|
||||
android:paddingHorizontal="14dp"
|
||||
android:paddingBottom="14dp">
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
enable="@{viewModel.pageType != DetailsPageType.Details}"
|
||||
hint='@{"请输入备注"}'
|
||||
title='@{"备注"}'
|
||||
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.INPUT}"
|
||||
value="@={viewModel.remark}" />
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="12dp">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="16dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="交接图片"
|
||||
android:textColor="@color/phone_text_label"
|
||||
android:textSize="15sp" />
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@color/phone_divider" />
|
||||
|
||||
<!-- 格子自带 6dp 外边距,左右各收回 6dp,让图片与上方字段左边缘对齐 -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_images"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginHorizontal="-6dp"
|
||||
android:overScrollMode="never"
|
||||
itemLayoutId="@{viewModel.imageItemLayoutId}"
|
||||
viewHolder="@{viewModel.imageItemViewHolder}"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="4" />
|
||||
app:spanCount="3" />
|
||||
|
||||
<!-- 详情模式下无图片时的占位提示 -->
|
||||
<TextView
|
||||
@@ -286,6 +319,72 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 卡片四:补充信息(Bug #632:备注录入与保存,无下划线) ==================== -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="14dp"
|
||||
android:paddingBottom="14dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="12dp">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="16dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="补充信息"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@color/phone_divider" />
|
||||
|
||||
<!-- 备注:标签/数值字号与 PhoneKvItem 一致(12sp 灰标签 + 15sp 数值),编辑态可直接输入 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="备注"
|
||||
android:textColor="@color/phone_text_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="@null"
|
||||
android:enabled="@{viewModel.pageType != DetailsPageType.Details}"
|
||||
android:hint='@{viewModel.pageType == DetailsPageType.Details ? "-" : "请输入备注"}'
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:minHeight="24dp"
|
||||
android:padding="0dp"
|
||||
android:text="@={viewModel.remark}"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textColorHint="@{viewModel.pageType == DetailsPageType.Details ? @color/phone_text_title : @color/phone_text_hint}"
|
||||
android:textSize="15sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
@@ -83,15 +83,9 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_blue"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -126,15 +120,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_orange"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_orange_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -169,15 +157,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_green"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_green_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 包装类型(半宽) -->
|
||||
<!-- 包装类型 | 业务类型(Bug #633:业务类型紧跟包装类型) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -181,20 +181,6 @@
|
||||
title='@{"包装类型"}'
|
||||
value='@{viewModel.str(viewModel.maWbData, "packageType")}' />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 业务类型 | 入库时间 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -202,13 +188,6 @@
|
||||
title='@{"业务类型"}'
|
||||
value='@{viewModel.str(viewModel.maWbData, "businessName")}' />
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"入库时间"}'
|
||||
value='@{viewModel.str(viewModel.maWbData, "opDate")}' />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 运单类型(整行) -->
|
||||
@@ -243,6 +222,14 @@
|
||||
title='@{"备注"}'
|
||||
value='@{viewModel.str(viewModel.maWbData, "remark")}' />
|
||||
|
||||
<!-- 入库时间(整行,Bug #633:单独一行放在备注下面) -->
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
title='@{"入库时间"}'
|
||||
value='@{viewModel.str(viewModel.maWbData, "opDate")}' />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 卡片2:件重信息 ==================== -->
|
||||
|
||||
@@ -104,7 +104,8 @@
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/ic_phone_chevron_right" />
|
||||
android:src="@drawable/ic_phone_chevron_right"
|
||||
android:tint="@color/phone_text_body" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -86,6 +86,29 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 入库件数 | 入库重量(Bug #633) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"入库件数"}'
|
||||
value="@{bean.inPcText}" />
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"入库重量"}'
|
||||
value="@{bean.inWeightText.empty ? `` : bean.inWeightText + ` kg`}" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 出库人 | 出库时间 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -109,5 +132,28 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 出库件数 | 出库重量(Bug #633) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"出库件数"}'
|
||||
value="@{bean.outPcText}" />
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"出库重量"}'
|
||||
value="@{bean.outWeightText.empty ? `` : bean.outWeightText + ` kg`}" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
|
||||
@@ -64,7 +64,8 @@
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/ic_phone_chevron_right" />
|
||||
android:src="@drawable/ic_phone_chevron_right"
|
||||
android:tint="@color/phone_text_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -185,34 +185,7 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 字段网格:海关放行 / 运单类型(与平板卡片第二行一致) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text='@{"海关放行: " + bean.orDash(bean.cmdStatus)}'
|
||||
android:textColor="@color/phone_text_body"
|
||||
android:textSize="13sp"
|
||||
tools:text="海关放行: 已放行" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text='@{"运单类型: " + bean.orDash(bean.awbName)}'
|
||||
android:textColor="@color/phone_text_body"
|
||||
android:textSize="13sp"
|
||||
tools:text="运单类型: 普通" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 查看分单(无分单数据时不显示) -->
|
||||
<!-- 查看分单(Bug #631:无分单也显示,数量为 0) -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
@@ -220,10 +193,9 @@
|
||||
android:background="@drawable/bg_phone_btn_weak"
|
||||
android:gravity="center"
|
||||
android:onClick="@{()-> activity.openSubOrder(bean)}"
|
||||
android:text='@{"查看分单 " + bean.haWbNumber}'
|
||||
android:text='@{"查看分单 " + (bean.haWbNumber == null ? 0 : bean.haWbNumber)}'
|
||||
android:textColor="@color/phone_primary"
|
||||
android:textSize="14sp"
|
||||
android:visibility="@{bean.haWbNumber != null && bean.haWbNumber > 0 ? View.VISIBLE : View.GONE}"
|
||||
tools:text="查看分单 3" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
@@ -79,19 +79,6 @@
|
||||
android:textSize="11sp"
|
||||
android:visibility="@{!bean.isHandovered && !bean.dgrCode.empty ? View.VISIBLE : View.GONE}" />
|
||||
|
||||
<!-- 装机状态标签(与平板「装机状态」一致:允许=绿 / 不允许=浅红,无值不显示) -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@{bean.isAllowLoad ? @drawable/bg_phone_tag_green : @drawable/bg_phone_tag_red_light}"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:text="@{bean.allowLoadText}"
|
||||
android:textColor="@{bean.isAllowLoad ? @color/phone_tag_green_text : @color/phone_danger_text}"
|
||||
android:textSize="11sp"
|
||||
android:visibility="@{bean.allowLoadText.empty ? View.GONE : View.VISIBLE}" />
|
||||
|
||||
<!-- 已交接:对勾 + 状态文字 -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
@@ -211,14 +198,34 @@
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/phone_stat_bg" />
|
||||
|
||||
<!-- 底部行:左侧装机状态标签(Bug #634:从头部移下来,头部右侧只留 IMP 代码)+ 右侧查看详情 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_detail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|end"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<!-- 装机状态标签(与平板「装机状态」一致:允许=绿 / 不允许=浅红,无值不显示) -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:background="@{bean.isAllowLoad ? @drawable/bg_phone_tag_green : @drawable/bg_phone_tag_red_light}"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:text="@{bean.allowLoadText}"
|
||||
android:textColor="@{bean.isAllowLoad ? @color/phone_tag_green_text : @color/phone_danger_text}"
|
||||
android:textSize="11sp"
|
||||
android:visibility="@{bean.allowLoadText.empty ? View.GONE : View.VISIBLE}" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_detail"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical|end"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -235,4 +242,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
||||
|
||||
@@ -95,7 +95,8 @@
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/ic_phone_chevron_right" />
|
||||
android:src="@drawable/ic_phone_chevron_right"
|
||||
android:tint="@color/phone_text_body" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -212,17 +213,33 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 清仓备注(与平板列表第三行一致,无备注时不显示) -->
|
||||
<TextView
|
||||
<!-- 清仓备注(与平板列表第三行一致,无备注时不显示;图标与上方字段行同规格) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="@{bean.clearRemark == null || bean.clearRemark.empty ? View.GONE : View.VISIBLE}">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="14dp"
|
||||
android:layout_height="14dp"
|
||||
android:src="@drawable/ic_phone_clipboard"
|
||||
android:tint="@color/phone_text_body" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_weight="1"
|
||||
android:text='@{"备注:" + bean.clearRemark}'
|
||||
android:textColor="@color/phone_text_body"
|
||||
android:textSize="13sp"
|
||||
android:visibility="@{bean.clearRemark == null || bean.clearRemark.empty ? View.GONE : View.VISIBLE}"
|
||||
tools:text="备注:正常清仓" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 操作按钮行:清仓 / 修改库位 / 出库 / 入库 ==================== -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -78,15 +78,9 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_blue"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -121,15 +115,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_orange"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_orange_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -164,15 +152,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_green"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_green_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -85,15 +85,9 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_blue"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -128,15 +122,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_orange"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_orange_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -171,15 +159,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:background="@drawable/bg_phone_stat_green"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="12dp"
|
||||
android:background="@color/phone_tag_green_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
取值统一走 viewModel.str()/dec() 帮助方法(空值兜底 "-")。
|
||||
字段 key 以平板变体既有绑定为准(awbPc/awbWeight/cashWeight/inPc/inWeight/lockStateText/
|
||||
mftStatus/tallyStatus/command);后端确认按平板端展示(问题清单 #27):
|
||||
航程 range(无经停站,替代始发站/中转站/目的站)、UN编号 unNumber、出库时间 dlvTime。
|
||||
航程 range(无经停站,替代始发站/中转站/目的站)、UN编号 unNumber;出库时间按 Bug #635 不再展示。
|
||||
-->
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 业务类型 | 出库时间 -->
|
||||
<!-- 业务类型(Bug #635:去掉出库时间,右半格留空保持两列对齐) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -181,12 +181,10 @@
|
||||
title='@{"业务类型"}'
|
||||
value='@{viewModel.str(viewModel.maWbData, "businessName")}' />
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"出库时间"}'
|
||||
value='@{viewModel.str(viewModel.maWbData, "dlvTime")}' />
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -104,7 +104,8 @@
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/ic_phone_chevron_right" />
|
||||
android:src="@drawable/ic_phone_chevron_right"
|
||||
android:tint="@color/phone_text_body" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -86,6 +86,29 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 入库件数 | 入库重量(Bug #635) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"入库件数"}'
|
||||
value="@{bean.inPcText}" />
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"入库重量"}'
|
||||
value="@{bean.inWeightText}" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 出库人 | 出库时间 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -109,5 +132,28 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 出库件数 | 出库重量(Bug #635) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"出库件数"}'
|
||||
value="@{bean.outPcText}" />
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"出库重量"}'
|
||||
value="@{bean.outWeightText}" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
title='@{"始发站"}'
|
||||
value="@{bean.dep}" />
|
||||
value="@{bean.depDisplay}" />
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -95,7 +95,8 @@
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:src="@drawable/ic_phone_chevron_right" />
|
||||
android:src="@drawable/ic_phone_chevron_right"
|
||||
android:tint="@color/phone_text_body" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -203,7 +204,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:text='@{"始发站:" + bean.orDash(bean.dep)}'
|
||||
android:text='@{"始发站:" + bean.orDash(bean.depDisplay)}'
|
||||
android:textColor="@color/phone_text_body"
|
||||
android:textSize="13sp"
|
||||
tools:text="始发站:LAX" />
|
||||
@@ -212,17 +213,33 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 清仓备注(与平板列表第三行一致,无备注时不显示) -->
|
||||
<TextView
|
||||
<!-- 清仓备注(与平板列表第三行一致,无备注时不显示;图标与上方字段行同规格) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="@{bean.clearRemark == null || bean.clearRemark.empty ? View.GONE : View.VISIBLE}">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="14dp"
|
||||
android:layout_height="14dp"
|
||||
android:src="@drawable/ic_phone_clipboard"
|
||||
android:tint="@color/phone_text_body" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_weight="1"
|
||||
android:text='@{"备注:" + bean.clearRemark}'
|
||||
android:textColor="@color/phone_text_body"
|
||||
android:textSize="13sp"
|
||||
android:visibility="@{bean.clearRemark == null || bean.clearRemark.empty ? View.GONE : View.VISIBLE}"
|
||||
tools:text="备注:正常清仓" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 操作按钮行:清仓 / 修改库位 / 出库 / 入库 ==================== -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||