| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- /* ============================== src/page-records.js ==============================
- * 运行记录(规格书 D1/D2/D3,设计稿 30:871 / 30:958 / 30:1241)。
- *
- * D1 是「记录」tab 的一级页,D3 是它的二级页(点任一条进)。
- * D2 是 D1 的空态 —— **空态是设计稿专门画的一屏**,不是"没数据就不显示":
- * 一个空页面看起来像坏了,空态告诉用户"去哪能产生记录"。
- *
- * 记录本身由 `Records` 模块负责读写(见 src/records.js),
- * 这一层只负责画:不猜字段、不编文案 —— 没有的东西就不渲染那一行。
- */
- var Pages = Pages || {};
- /** 「脚本一 · 粉丝页提 ID」→「脚本一」。卡上只放得下短名,全名在详情页。 */
- Pages.shortScriptName = function (name) {
- var text = String(name || "");
- var cut = text.indexOf(" \u00B7 ");
- return cut > 0 ? text.substring(0, cut) : text;
- };
- /** 记录条目 / 详情页统一用「任务 · 脚本」当标题。 */
- Pages.recordTitle = function (record) {
- var task = record.taskName || Pages.taskName(record.taskKey);
- var script = Pages.shortScriptName(record.scriptName);
- return script === "" ? task : (task + " \u00B7 " + script);
- };
- /** 记录用的任务色/图标(与任务卡一致,列表里一眼能对上号)。 */
- function recordVisual(record) {
- var task = Pages.taskOf(record.taskKey);
- return {
- colorHex: task ? task.colorHex : Theme.hex.accent,
- iconChar: task ? task.icon : "\u25A3"
- };
- }
- /* ------------------------------------------------------------ 日志行 */
- /**
- * 日志行的配色(规格书:INFO 灰 / WARN 琥珀 / ERROR 玫瑰红)。
- * STEP 归到 INFO:它是流程节点,不是异常。
- */
- function logColor(level) {
- if (level === "WARN") { return Theme.hex.warn; }
- if (level === "ERROR") { return Theme.hex.danger; }
- return Theme.hex.textFaint;
- }
- function pad2(n) { return n < 10 ? "0" + n : String(n); }
- /** 时间戳 → HH:MM:SS(日志行前缀,等宽显示才对齐)。 */
- function logClock(ms) {
- var d = new Date(ms || 0);
- return pad2(d.getHours()) + ":" + pad2(d.getMinutes()) + ":" + pad2(d.getSeconds());
- }
- /**
- * 一条日志行。
- *
- * 用**等宽字体**并且手动补空格对齐 level:设计稿里 `10:24:01 INFO 启动脚本`
- * 的 level 是定宽的,用比例字体会让每一行的正文起点参差不齐。
- */
- function buildLogLine(entry) {
- var raw = String(entry.level || "INFO").toUpperCase();
- var level = raw;
- while (level.length < 5) { level += " "; }
- var text = logClock(entry.t) + " " + level + " " + String(entry.text || "");
- var view = U.monoText(text, Theme.m.logSize, logColor(raw));
- U.params(view, -1, -2, 0);
- return view;
- }
- /* ------------------------------------------------------------ D1 记录列表 */
- function buildRecordRow(record, snap) {
- var visual = recordVisual(record);
- var result = Records.RESULT[record.result] || Records.RESULT.failed;
- var resumable = Resume.isRecordResumable(record, snap);
- return W.recordRow({
- title: Pages.recordTitle(record),
- iconChar: visual.iconChar,
- colorHex: visual.colorHex,
- resultLabel: result.label,
- resultTone: result.tone,
- timeLine: Records.timeLine(record),
- resultLine: Records.resultLine(record),
- /* 只有"这条记录就是那个没跑完的断点"时才给「继续」。
- * 判据在 Resume.isRecordResumable 里(含 runId 精确匹配)——
- * 否则一串历史「手动停止」记录上都会冒出「继续」,而只有最后一条能真续。 */
- actionLabel: resumable ? "\u7EE7\u7EED" : "",
- actionTone: "accent",
- onAction: function () { App.resumeRun(record); },
- onOpen: function () { App.openRecord(record.id); }
- });
- }
- /** D1:记录列表(有记录时)。 */
- Pages.records = function () {
- var inner = U.column();
- var blocks = [];
- var list = Records.list();
- /* 断点只读一次:50 条记录各读一次盘是没必要的浪费 */
- var snap = Resume.snapshot();
- /* 标题区由路由层统一加(见 App.renderContent)——页面再加一次就会渲染两遍 */
- var section = U.column();
- section.addView(W.sectionTitle("\u8FD0\u884C\u8BB0\u5F55"));
- section.addView(U.spacer(4));
- /* 「共 N 条 · 点任一条查看执行日志」——条数是真实的,空列表走 D2 那一屏 */
- section.addView(U.text("\u5171 " + list.length + " \u6761 \u00B7 \u70B9\u4EFB\u4E00\u6761\u67E5\u770B\u6267\u884C\u65E5\u5FD7",
- Theme.m.hintSize, Theme.hex.textDim, false));
- blocks.push(section);
- blocks.push(U.spacer(Theme.m.gapCard));
- for (var i = 0; i < list.length; i++) {
- blocks.push(buildRecordRow(list[i], snap));
- if (i < list.length - 1) { blocks.push(U.spacer(Theme.m.gapCard)); }
- }
- blocks.push(U.spacer(8));
- W.stack(inner, blocks, 0);
- var scroller = W.scroller();
- scroller.addView(inner);
- return scroller;
- };
- /** D2:记录空态。 */
- Pages.recordsEmpty = function () {
- var inner = U.column();
- /* 标题区同样由路由层提供;空态只剩「区标题 + 空态块」 */
- inner.addView(W.sectionTitle("\u8FD0\u884C\u8BB0\u5F55"));
- inner.addView(W.emptyState("\u25F4",
- "\u8FD8\u6CA1\u6709\u8FD0\u884C\u8BB0\u5F55",
- "\u53BB\u300C\u4EFB\u52A1\u300D\u6807\u7B7E\u9009\u4E00\u4E2A\u811A\u672C\u8DD1\u8D77\u6765"));
- return inner;
- };
- /* ------------------------------------------------------------ D3 运行详情 */
- /**
- * 结果摘要卡。
- * 三行都是**有条件渲染**的:结果文案/结果文件只有引擎回传了才有,
- * 没有就不画那一行 —— 空着的一行会让用户以为"数据丢了"。
- */
- function buildSummaryCard(record) {
- var card = W.card(false);
- var rows = [];
- var result = Records.RESULT[record.result] || Records.RESULT.failed;
- var titleRow = U.row();
- var title = U.text(Pages.recordTitle(record), Theme.m.taskNameSize,
- Theme.hex.textPrimary, true);
- title.setSingleLine(true);
- title.setEllipsize(android.text.TextUtils.TruncateAt.END);
- U.params(title, 0, -2, 1);
- titleRow.addView(title);
- titleRow.addView(U.hgap(8));
- titleRow.addView(W.badge(result.label, result.tone).root);
- rows.push(titleRow);
- rows.push(U.text(Records.timeLine(record), Theme.m.chipSize, Theme.hex.textFaint, false));
- if (record.resultText) {
- rows.push(U.text(record.resultText, Theme.m.chipSize, Theme.hex.textDim, false));
- }
- if (record.resultFile) {
- pushFileRows(rows, "\u7ED3\u679C\u6587\u4EF6", record.resultFile);
- }
- if (record.mapFile) {
- pushFileRows(rows, "uid \u6620\u5C04\uFF08username\u2192\u6570\u5B57 uid\uFF09", record.mapFile);
- }
- W.stack(card, rows, 10);
- return card;
- }
- /**
- * 一份产物文件:说明行 + 路径行(点路径打开)+ 打开/分享两个按钮。
- *
- * 抽成函数是因为现在有两份产物(结果文件 + uid 映射文件),
- * 各自都要能打开和分享 —— 光在界面上写一句「已生成 uid 映射」而取不到文件,
- * 等于让用户自己去翻 /sdcard。
- */
- function pushFileRows(rows, caption, path) {
- rows.push(U.text(caption, Theme.m.chipSize, Theme.hex.textFaint, false));
- var fileRow = U.row();
- var pathText = U.text(path, Theme.m.chipSize, Theme.hex.accentText, false);
- pathText.setSingleLine(true);
- pathText.setEllipsize(android.text.TextUtils.TruncateAt.MIDDLE);
- U.params(pathText, 0, -2, 1);
- fileRow.addView(pathText);
- fileRow.addView(U.hgap(8));
- fileRow.addView(W.smallButton("\u6253\u5F00", "accent", function () {
- App.openResultFile(path);
- }).root);
- fileRow.addView(U.hgap(6));
- fileRow.addView(W.smallButton("\u5206\u4EAB", "default", function () {
- App.shareResultFile(path);
- }).root);
- rows.push(fileRow);
- }
- /** 日志卡:标题行(执行日志 + 复制)+ 日志行。 */
- function buildLogCard(record) {
- var card = W.card(false);
- var logs = record.logs || [];
- var head = U.row();
- head.addView(U.text("\u6267\u884C\u65E5\u5FD7", Theme.m.labelSize, Theme.hex.textSecondary, true));
- head.addView(U.flexible());
- head.addView(W.smallButton("\u590D\u5236", "default", function () {
- App.copyLogs(record);
- }).root);
- card.addView(head);
- card.addView(U.spacer(10));
- if (logs.length === 0) {
- /* 如实说明,而不是画一张空卡片。日志来自引擎的 --log 通道(见 Engine.readLog),
- * 没接引擎的任务本来就不会有日志。 */
- card.addView(U.text("\u672C\u6B21\u8FD0\u884C\u6CA1\u6709\u56DE\u4F20\u65E5\u5FD7",
- Theme.m.chipSize, Theme.hex.textDim, false));
- return card;
- }
- for (var i = 0; i < logs.length; i++) {
- if (i > 0) { card.addView(U.spacer(6)); }
- card.addView(buildLogLine(logs[i]));
- }
- return card;
- }
- /**
- * D3:运行详情。
- * 记录被清理掉(超过 Records.MAX 或被手工清空)时,**不能白屏** ——
- * 给一句"这条记录已不在本地"和一条回到列表的路。
- */
- Pages.recordDetail = function (record) {
- var inner = U.column();
- inner.addView(W.backHeader("\u8FD0\u884C\u8BE6\u60C5", function () { App.closeRecord(); }));
- if (!record) {
- inner.addView(U.spacer(Theme.m.gapPage));
- inner.addView(W.emptyState("\u25F4",
- "\u8FD9\u6761\u8BB0\u5F55\u5DF2\u4E0D\u5728\u672C\u5730",
- "\u8BB0\u5F55\u4EC5\u4FDD\u7559\u6700\u8FD1 " + Records.MAX + " \u6761"));
- var scroller0 = W.scroller();
- scroller0.addView(inner);
- return scroller0;
- }
- inner.addView(U.spacer(Theme.m.gapCard));
- /* 这条记录还能接着跑 → 在看日志的地方也放一个入口(列表行那个是"一眼可见",
- * 这个是"看完日志顺手继续")。判据与列表行同一份。 */
- var snap = Resume.snapshot();
- if (Resume.isRecordResumable(record, snap)) {
- inner.addView(W.noticeBar({
- title: "\u8FD9\u6B21\u8FD0\u884C\u6CA1\u8DD1\u5B8C\uFF08\u5DF2\u6536 " + snap.extracted + " \u4E2A\uFF09",
- detail: Resume.detail(snap),
- tone: "accent",
- actions: [{
- label: "\u7EE7\u7EED",
- tone: "accent",
- onClick: function () { App.resumeRun(record); }
- }]
- }).root);
- inner.addView(U.spacer(Theme.m.gapCard));
- }
- inner.addView(buildSummaryCard(record));
- inner.addView(U.spacer(Theme.m.gapCard));
- inner.addView(buildLogCard(record));
- inner.addView(U.spacer(8));
- var scroller = W.scroller();
- scroller.addView(inner);
- return scroller;
- };
|