Compare commits
14 Commits
f41d4186ea
...
a58845121b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a58845121b | ||
|
|
a4167c3cf3 | ||
|
|
9db20749e2 | ||
|
|
eb0fb12c8d | ||
|
|
2996e670e0 | ||
|
|
36d74c3f0d | ||
|
|
9e545ee0ec | ||
|
|
66c7c0f4f6 | ||
|
|
075b6bf1ea | ||
|
|
d1d995c698 | ||
|
|
fa6c3f473e | ||
|
|
2c8fa689a5 | ||
|
|
b22d55a380 | ||
|
|
7f1a413172 |
57
.claude/skills/zentao-bug/SKILL.md
Normal file
57
.claude/skills/zentao-bug/SKILL.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
name: zentao-bug
|
||||
description: 读取禅道(njlkairport.chandao.net,开源版 18.8)中的 Bug 单——我的 Bug 列表、单个 Bug 的标题/描述/截图/评论/修改历史/附件——并按 AirLogistics 项目规范定位代码完成修复。禅道 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 <账号>
|
||||
```
|
||||
|
||||
密码交互输入、不回显;默认存入 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>`。不确定截图意图时先问用户,禁止臆测。
|
||||
|
||||
## 写回禅道
|
||||
|
||||
暂未实现(写操作会改动线上 Bug 状态,需用户确认后再加)。修复完成后由用户在禅道手动"解决",
|
||||
可附上提交号与改动摘要供用户粘贴。
|
||||
|
||||
## 实现说明
|
||||
|
||||
- 认证:`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>`。
|
||||
- REST v1 的 `/products/<id>/bugs` 只返回激活中的 Bug,不能替代"我的 Bug"页面。
|
||||
- 凭据:Token 在 `~/.config/zentao-lite/config.json`(0600),密码只在钥匙串(服务名 `zentao-lite`)。`logout` 一并清除。
|
||||
528
.claude/skills/zentao-bug/zentao.py
Normal file
528
.claude/skills/zentao-bug/zentao.py
Normal file
@@ -0,0 +1,528 @@
|
||||
#!/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 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 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)
|
||||
|
||||
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()
|
||||
@@ -118,34 +118,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"
|
||||
|
||||
Binary file not shown.
@@ -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
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
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
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"
|
||||
|
||||
@@ -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)
|
||||
val adapter = rvImages?.commonAdapter() ?: return
|
||||
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
|
||||
|
||||
@@ -235,43 +235,35 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 卡片三:备注 + 交接图片(编辑态可改) ==================== -->
|
||||
<!-- ==================== 卡片三:交接图片(Bug #632:样式与进港移库「异常照片」一致——橙色表头 + 三列虚线加号槽位,长按图片出删除角标) ==================== -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
|
||||
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:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/bg_phone_card_header_orange"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="交接图片"
|
||||
android:textColor="@color/phone_text_label"
|
||||
android:textSize="15sp" />
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_images"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:overScrollMode="never"
|
||||
android:padding="10dp"
|
||||
itemLayoutId="@{viewModel.imageItemLayoutId}"
|
||||
viewHolder="@{viewModel.imageItemViewHolder}"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="4" />
|
||||
app:spanCount="3" />
|
||||
|
||||
<!-- 详情模式下无图片时的占位提示 -->
|
||||
<TextView
|
||||
@@ -286,6 +278,39 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 卡片四:补充信息(Bug #632:备注录入与保存,表头底色 #F3F4F6) ==================== -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bg_phone_card"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- 表头文字色:Bug 单写的是白色,但白字在 #F3F4F6 上不可读,暂用标题深色,待确认 -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_phone_card_header_gray"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="补充信息"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.lukouguoji.module_base.ui.weight.phone.PhoneFormRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingBottom="4dp"
|
||||
enable="@{viewModel.pageType != DetailsPageType.Details}"
|
||||
hint='@{"请输入备注"}'
|
||||
title='@{"备注"}'
|
||||
type="@{viewModel.pageType == DetailsPageType.Details ? PhoneFormRowType.TEXT : PhoneFormRowType.INPUT}"
|
||||
value="@={viewModel.remark}" />
|
||||
|
||||
</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:件重信息 ==================== -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -185,12 +185,13 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 字段网格:海关放行 / 运单类型(与平板卡片第二行一致) -->
|
||||
<!-- 字段网格:海关放行 / 运单类型(Bug #626:待运抵不显示,仅已运抵显示,与原型一致) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:visibility="@{`shipped`.equals(viewModel.currentTab) ? View.VISIBLE : View.GONE}">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -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"
|
||||
@@ -233,6 +240,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
||||
|
||||
@@ -212,16 +212,32 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 清仓备注(与平板列表第三行一致,无备注时不显示) -->
|
||||
<TextView
|
||||
<!-- 清仓备注(与平板列表第三行一致,无备注时不显示;图标与上方字段行同规格) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
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="备注:正常清仓" />
|
||||
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"
|
||||
tools:text="备注:正常清仓" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 操作按钮行:清仓 / 修改库位 / 出库 / 入库 ==================== -->
|
||||
<LinearLayout
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -203,7 +203,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,16 +212,32 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 清仓备注(与平板列表第三行一致,无备注时不显示) -->
|
||||
<TextView
|
||||
<!-- 清仓备注(与平板列表第三行一致,无备注时不显示;图标与上方字段行同规格) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
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="备注:正常清仓" />
|
||||
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"
|
||||
tools:text="备注:正常清仓" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ==================== 操作按钮行:清仓 / 修改库位 / 出库 / 入库 ==================== -->
|
||||
<LinearLayout
|
||||
|
||||
Reference in New Issue
Block a user