pages.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /* 完整设计稿新增的页面与弹层:首页、页内标签页(风险监控)、清算、TRS 交易下单页与下单弹窗、票据详情抽屉、
  2. * 确认框、修改密码弹窗、个人中心、404/401、登录页、组件总览、反馈类总览。登记到 UI.pageRenderers / overlayRenderers / canvasRenderers。
  3. */
  4. (function () {
  5. "use strict";
  6. const U = window.UI;
  7. const { D, D2: P, T, esc, ic, CHECK, EMPTY, tag, ENDS, kv, tableHTML, btn, item, dgWrap, dgCard, sfDialog, stepper, closeX, btnCancel, drCard } = U;
  8. /* ================= 首页(工作台) ================= */
  9. U.pageRenderers.__home = function (st) {
  10. const e = ENDS[st.end];
  11. const H = P.home[st.end];
  12. const total = H.status.reduce((n, s) => n + s[1], 0);
  13. const hot = H.status.filter((s) => s[2]).reduce((n, s) => n + s[1], 0);
  14. const tiles = H.status.map(([n, c, h]) => `<div class="hm-tile ${h ? "hot" : ""} ${c === 0 ? "zero" : ""}" data-act="leaf" data-k="${H.listKey}" title="打开${H.noun}列表"><span class="l">${n}</span><b class="num">${c}</b></div>`).join("");
  15. const statusCard = `<div class="card hm-st"><div class="card-hd">上午好,admin<span class="sub">今日${H.noun}共 <b class="num">${total}</b> 笔,其中 <b class="num hot">${hot}</b> 笔需要处理(高亮的状态)</span><span class="sp"></span><span class="btn txt" data-act="leaf" data-k="${H.listKey}">查看全部${ic("right")}</span></div><div class="hm-tiles">${tiles}</div></div>`;
  16. const sc = H.shortcuts.map(([g, l, icn]) => `<div class="hm-sc" data-act="leaf" data-k="${esc(g + "/" + l)}">${ic(icn)}<b>${esc(l)}</b><small>${esc(g)}</small></div>`).join("");
  17. const scCard = `<div class="card hm-sc-card"><div class="card-hd">快捷入口<span class="sub">取自本系统菜单</span></div><div class="hm-scs">${sc}</div></div>`;
  18. const rows = e.rows.filter(H.pick).slice(0, 6);
  19. const cols = H.manualCols.map((c) => [c[0], c[1], c[4] === "r" ? "r" : ""]).concat([["操作", 72]]);
  20. const cell = (r, i) => {
  21. const def = H.manualCols[i];
  22. if (!def) return `<span class="btn txt" data-act="${st.end === "issue" ? "ovopen" : "leaf"}" data-o="order" data-k="${H.listKey}">详情</span>`;
  23. const key = def[2];
  24. const v = key === "stock" && st.end === "trs" ? `${r.code} ${r.sname}` : r[key];
  25. const kind = def[3] || "";
  26. if (kind.startsWith("tag:")) return v ? tag(kind.slice(4), v) : "--";
  27. return v;
  28. };
  29. const tblCard = `<div class="card tb-card hm-tb"><div class="tb-bar"><span class="ttl">${H.manualTitle}</span><span class="t3" style="font-size:12px">${H.manualSub};按下单时间倒序,最多 6 条</span><span class="sp"></span><span class="btn txt" data-act="leaf" data-k="${H.listKey}">查看全部${ic("right")}</span></div>${tableHTML(cols, rows, { cell, keep: "hm-tbl", empty: "没有需要处理的" + H.noun })}</div>`;
  30. return `<div class="hm">${statusCard}${scCard}${tblCard}</div>`;
  31. };
  32. /* ================= 页内标签页 ================= */
  33. // 页面级(ptabs)或卡片内(tabs)的页签条;选中项记在 st.tab[g]
  34. const ptabs = (st, g, tabs, cls = "ptabs") => {
  35. const cur = st.tab[g] || tabs[0][0];
  36. return `<div class="${cls}">${tabs.map(([k, l]) => `<span class="tab ${k === cur ? "on" : ""}" data-act="ptab" data-g="${g}" data-v="${k}">${l}</span>`).join("")}</div>`;
  37. };
  38. const searchCard = (fields, actions, withLabel) => {
  39. const lw = withLabel ? Math.max(...fields.map((f) => f.label.length)) * 14 : 0;
  40. const rem = 3 - (fields.length % 3);
  41. const acts = `<div class="sf-act" style="grid-column:span ${rem}">${actions.map(([t, type, icn]) => `<span class="btn ${type === "primary" ? "pri" : ""}" data-act="noop">${ic(icn)}${t}</span>`).join("")}</div>`;
  42. return `<div class="card"><div class="sf">${fields.map((f) => `<div class="sf-i">${withLabel ? `<label style="width:${lw}px">${esc(f.label)}</label>` : ""}${U.ctlHTML(f)}</div>`).join("")}${acts}</div></div>`;
  43. };
  44. // 风险监控:页面级页签,三个页签各是一套搜索 + 表格
  45. U.pageRenderers["风控管理/共赢票据风险监控"] = function (st) {
  46. const R = P.risk;
  47. const cur = st.tab.risk || "monitor";
  48. let body;
  49. if (cur === "monitor") {
  50. const cell = (r, i) => {
  51. if (i === 0) {
  52. const danger = r[2] === "危险" || r[2] === "强平";
  53. return `<span class="btns" style="gap:12px"><span class="btn txt" data-act="ovopen" data-o="note">详情</span>${danger ? `<span class="btn txt dng" data-act="ovopen" data-o="confirm" data-kind="force">强平卖出</span>` : ""}</span>`;
  54. }
  55. const v = r[i - 1];
  56. if (i === 2) return [v, "note_status"];
  57. if (i === 3) return [v, "note_risk_status"];
  58. if (i === 11) return v.startsWith("+") ? [v, "up"] : v.startsWith("-") ? [v, "dn"] : v;
  59. return v;
  60. };
  61. body = `${searchCard(R.search, R.actions, false)}<div class="card tb-card">${tableHTML(R.cols, R.rows, { cell, keep: "risk-tbl" })}${U.pagerHTML(R.rows.length)}</div>`;
  62. } else if (cur === "status-log") {
  63. const cell = (r, i) => (i === 4 || i === 5 ? [r[i], "note_risk_status"] : r[i]);
  64. body = `${searchCard(R.search.slice(2, 8), R.actions.slice(0, 2), false)}<div class="card tb-card">${tableHTML(R.statusLogCols, R.statusLog, { cell, keep: "risk-log" })}${U.pagerHTML(R.statusLog.length)}</div>`;
  65. } else {
  66. const cell = (r, i) => (i === 4 ? [r[i], "send_status"] : r[i]);
  67. body = `${searchCard(R.search.slice(2, 5), R.actions.slice(0, 2), false)}<div class="card tb-card">${tableHTML(R.notifyCols, R.notify, { cell, keep: "risk-ntf" })}${U.pagerHTML(R.notify.length)}</div>`;
  68. }
  69. return `<div class="pg-list">${ptabs(st, "risk", R.tabs)}${body}</div>`;
  70. };
  71. // 销售端同名菜单复用
  72. U.pageRenderers["风控管理/共赢票据风险监控"].sales = true;
  73. // 清算管理:页签在卡片标题栏里
  74. U.pageRenderers["清算管理/共赢票据清算"] = function (st) {
  75. const C = P.clearing;
  76. const cur = st.tab.clr || "clearing";
  77. const running = C.batchStatus === "执行中";
  78. const tools = cur === "clearing"
  79. ? `${btn(ic("refresh") + "刷新")}${btn("一键清算", running ? "pri is-dis" : "pri")}${btn("重新清算", "", "ovopen", 'data-o="confirm" data-kind="redo"')}`
  80. : `${btn(ic("refresh") + "刷新")}${running ? btn(ic("stop") + "停止当前任务", "dng", "ovopen", 'data-o="confirm" data-kind="stop"') : ""}`;
  81. const info = `<div class="clr-info"><span class="chip">市场<b>${C.market}</b></span><span class="chip">本次清算交易日<b>${C.tradeDate}</b></span><span class="chip">批次<b>${C.batchNo}</b></span><span class="chip">状态${tag("clearing_batch", C.batchStatus)}</span>${running ? `<span class="t3" style="font-size:12px;margin-left:4px">执行中不能再次发起;页面每 5 秒自动刷新</span>` : ""}</div>`;
  82. let body;
  83. if (cur === "clearing") {
  84. const cell = (r, i) => (i === 2 ? [r[i], "clearing_step"] : r[i]);
  85. const rowCls = (r) => (r[2] === "执行中" ? "is-sel" : "");
  86. body = `${info}${C.blockedReason ? `<div class="alt alt-info" style="margin-bottom:12px">${ic("info")}<span>${esc(C.blockedReason)}</span></div>` : ""}${tableHTML(C.stepCols, C.steps, { cell, rowCls, keep: "clr-steps", cls: "bd" })}${C.errorDetail ? `<div class="alt alt-danger" style="margin-top:12px">${ic("error")}<span>${esc(C.errorDetail)}</span></div>` : ""}`;
  87. } else {
  88. const cell = (r, i) => (i === 3 ? [r[i], "clearing_step"] : r[i]);
  89. const hcell = (r, i) => (i === 3 ? [r[i], "clearing_batch"] : r[i]);
  90. body = `${info}${tableHTML(C.logCols, C.logs, { cell, keep: "clr-logs", cls: "bd" })}<div class="sec-hd" style="margin:20px 0 10px"><b>${C.historyTitle}</b><small>${C.history.length} 条</small></div>${tableHTML(C.historyCols, C.history, { cell: hcell, keep: "clr-his", cls: "bd" })}`;
  91. }
  92. return `<div class="card clr"><div class="ctabs">${ptabs(st, "clr", C.tabs, "tabs")}<span class="sp"></span>${tools}</div><div class="card-bd" data-keep="clr-bd">${body}</div></div>`;
  93. };
  94. /* ================= TRS · 交易下单页 + 下单弹窗 ================= */
  95. U.pageRenderers["系统设置/交易下单"] = function (st) {
  96. const H = D.trsTrade.host;
  97. const cur = st.tab.trs || "pos";
  98. const acct = `<div class="card"><div class="trs-acct"><div class="sf-i" style="width:340px"><label>账户</label><div class="ipt"><span class="val">${H.account} · ${H.user}</span>${ic("down")}</div></div><span class="sp"></span>${btn(ic("refresh") + "刷新")}${btn("买入", "pri", "ovopen", 'data-o="trs" data-dir="买入"')}${btn("卖出", "", "ovopen", 'data-o="trs" data-dir="卖出"')}</div>
  99. <div class="card-bd" style="padding-top:0">${kv(H.accountCols.map((c, i) => [c, H.accountRows[0][i], "num"]), "bd3", 3)}</div></div>`;
  100. const tabs = [["pos", `持仓<span class="cnt">${H.posRows.length}</span>`], ["ord", `当日委托<span class="cnt">${H.orderRows.length}</span>`]];
  101. const tbl = cur === "pos"
  102. ? tableHTML(H.posCols, H.posRows, { cell: (r, i) => (i === 2 ? [r[i], "trade_channel"] : i === 10 || i === 11 ? [r[i], r[i].startsWith("+") ? "up" : "dn"] : r[i]), keep: "trs-pos" })
  103. : tableHTML([["操作", 72]].concat(H.orderCols), H.orderRows, { cell: (r, i) => (i === 0 ? (["已报", "部成"].includes(r[10]) ? `<span class="btn txt dng" data-act="ovopen" data-o="confirm" data-kind="cancel">撤单</span>` : "--") : i === 4 ? [r[i - 1], "trade_channel"] : i === 6 ? [r[i - 1], "direction"] : i === 11 ? [r[i - 1], "trs_order_status"] : r[i - 1]), keep: "trs-ord" });
  104. return `<div class="pg-list">${acct}<div class="card tb-card"><div class="ctabs">${ptabs(st, "trs", tabs, "tabs")}<span class="sp"></span>${btn(ic("refresh") + "刷新", "txt")}</div>${tbl}</div></div>`;
  105. };
  106. U.overlayRenderers.trs = function (ov) {
  107. const G = D.trsTrade.dialog;
  108. const H = D.trsTrade.host;
  109. const buy = ov.dir === "买入";
  110. const seg = (k, opts) => `<div class="segc">${opts.map((o) => `<span class="${o === ov[k] ? "on" : ""}" data-act="ovset" data-k="${k}" data-v="${o}">${o}</span>`).join("")}</div>`;
  111. const chips = `<div class="qchips">${G.quick.map(([l, v]) => `<span class="qchip ${v === ov.price ? "on" : ""}" data-act="ovset" data-k="price" data-v="${v}">${l}<b class="num">${v}</b></span>`).join("")}</div>`;
  112. const QTY = buy ? { "1/4": "25,200", "1/3": "33,600", "1/2": "50,400", "全部可买": "100,900" } : { "1/4": "3,000", "1/3": "4,000", "1/2": "6,000", "全部可卖": "12,000" };
  113. const qtyChips = `<div class="qchips">${Object.entries(QTY).map(([l, v]) => `<span class="qchip ${v === ov.qty ? "on" : ""}" data-act="ovset" data-k="qty" data-v="${v}">${l}</span>`).join("")}</div>`;
  114. const amount = parseFloat(ov.price) * parseFloat(ov.qty.replace(/,/g, ""));
  115. const fmt = (n) => n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  116. const tiles = buy ? G.strip : [G.strip[0], ["可卖股数", H.posRows[0][4], "股"]];
  117. const rows = [
  118. { label: "交易渠道", req: true, body: seg("channel", G.channels) },
  119. { label: "股票代码", req: true, body: `<div class="ipt"><span class="val num" style="flex:none">${G.stock.code}</span><span class="val" style="flex:none">${G.stock.name}</span><span class="chipv">${G.stock.mkt}</span><span class="sp"></span>${ic("down")}</div>` },
  120. { label: "委托价格", req: true, body: stepper(ov.price, G.priceCcy), extra: chips },
  121. { label: "委托数量", req: true, body: stepper(ov.qty, "股"), extra: qtyChips + `<div class="fld-tip">${buy ? G.qtyHelp : "卖出不超过可卖股数"}</div>` },
  122. ];
  123. const results = [[`${ov.dir}金额(交易币种)`, fmt(amount), "CNY"], [`${ov.dir}金额(结算币种)`, fmt(amount * 1.0944), "HKD"]];
  124. const Q = G.quote;
  125. const prev = parseFloat(Q.prev);
  126. const maxVol = Math.max(...[...Q.asks, ...Q.bids].map((r) => r[2]));
  127. const lvl = (rowsQ, cls) => rowsQ.map(([l, pr, v]) => `<div class="ql ${cls} ${pr === ov.price ? "on" : ""}" data-act="ovset" data-k="price" data-v="${pr}"><span>${l}</span><b class="num ${parseFloat(pr) > prev ? "buy" : parseFloat(pr) < prev ? "sell" : ""}">${pr}</b><span class="vol"><em style="width:${Math.round((v / maxVol) * 100)}%"></em><i class="num">${v}</i></span></div>`).join("");
  128. const aside = `<aside class="qpanel"><div class="qname"><b>${G.stock.name}</b><span class="code num">${G.stock.code}.${G.stock.mkt}</span><span class="live">${Q.source}</span></div>
  129. <div class="qprice"><b class="num last buy">${Q.last}</b><span class="num chg buy">↑${Q.chg} ${Q.pct}</span></div>
  130. <div class="qsub">昨收 <b class="num">${Q.prev}</b> · ${Q.hint}</div>
  131. ${lvl(Q.asks, "ask")}<div class="qmid"><b class="num buy">${Q.last} ↑</b><span class="num buy">${Q.chg} ${Q.pct}</span></div>${lvl(Q.bids, "bid")}</aside>`;
  132. const okBtn = `<span class="btn ${buy ? "dng-pri" : "ok-pri"}" data-act="ovtoast" data-msg="示例:不真正提交">确认${ov.dir} ${ov.qty} 股</span>`;
  133. return sfDialog({ w: 880, lw: 72, cls: "dg-trs", keep: "trs-dg", title: ov.dir, sub: `${G.account} · ${G.user}`, tiles, rows, results, aside,
  134. ft: `<span class="hint">${ic("info")}${G.footHint}</span><span class="sp"></span>${btnCancel}${okBtn}` });
  135. };
  136. /* ================= 票据详情抽屉(页签 + 描述表) ================= */
  137. U.overlayRenderers.note = function (ov) {
  138. const N = P.noteDetail;
  139. const tags = `<span class="tags">${N.tags.map(([v, dk]) => tag(dk, v)).join("")}</span>`;
  140. const rf = `<span class="btn txt" data-act="ovrf" style="color:var(--t2)">${ic("refresh", "rf")}刷新数据</span>`;
  141. const x = `<span class="icb" data-act="ovclose" title="关闭">${ic("x")}</span>`;
  142. const hb = N.buttons.map(([t, ty]) => btn(t, ty, "ovtoast", 'data-msg="示例:各操作弹窗按弹窗 J 的卡片分区实现"')).join("");
  143. const tabs = `<div class="dw-tabs">${N.tabs.map(([k, l]) => `<span class="tab ${ov.tab === k ? "on" : ""}" data-act="ovset" data-k="tab" data-v="${k}">${l}</span>`).join("")}</div>`;
  144. const subHd = (t, extra = "") => `<div class="sec-hd sub"><b>${t}</b><span class="sp"></span>${extra}</div>`;
  145. let body;
  146. if (ov.tab === "main") {
  147. const sub = N.subA;
  148. const H = N.hedge;
  149. const posCell = (r, i) => (i === 0 ? (r[0] === "合计" ? `<b>合计</b>` : [r[0], "hedge_system"]) : r[i]);
  150. const trCell = (r, i) => (i === 0 ? [r[i], "hedge_system"] : i === 6 ? `<span class="tg tg-${r[i][1]}">${r[i][0]}</span>` : i === 7 ? [r[i], "direction"] : r[i]);
  151. const dlyCols = [["操作", 100]].concat(H.tradeCols);
  152. const dlyCell = (r, i) => {
  153. if (i === 0) return r[0].length ? `<span class="btns" style="gap:12px">${r[0].map(([t, ty]) => `<span class="btn txt ${ty === "danger" ? "dng" : ""}" data-act="ovopen" data-o="confirm" data-kind="${t === "删除" ? "remove" : t === "撤单" ? "cancel" : "cancel"}">${t}</span>`).join("")}</span>` : "--";
  154. return trCell(r.slice(1), i - 1);
  155. };
  156. const dailyBtns = H.dailyButtons.map(([t, ty]) => btn(t === "刷新" ? ic("refresh") + t : t, ty === "txt" ? "txt" : "sm", t === "补录" ? "ovopen" : "ovtoast", t === "补录" ? 'data-o="fill"' : 'data-msg="示例:日常买卖的下单弹窗按「TRS 下单弹窗」的专用布局实现"')).join("");
  157. body = drCard(N.master.title, "", kv(N.master.fields, "bd3", 3)) +
  158. drCard(sub.title, "", kv(sub.fields, "bd3", 3) + subHd(sub.init.title) + kv(sub.init.fields, "bd2", 2) + subHd(sub.current.title) + kv(sub.current.fields, "bd2", 2)) +
  159. drCard(H.title, `<span class="chip">${H.ratioLabel}<b>${H.ratio}</b></span>`,
  160. subHd(H.posTitle) + tableHTML(H.posCols, H.posRows, { cell: posCell, keep: "nt-pos" }) +
  161. subHd(H.tradeTitle) + tableHTML(H.tradeCols, H.tradeRows, { cell: trCell, keep: "nt-tr" }) +
  162. subHd(H.dailyTitle, dailyBtns) + tableHTML(dlyCols, H.dailyRows, { cell: dlyCell, keep: "nt-dly" }));
  163. } else {
  164. body = N.changes.sections.map((s) => drCard(s.title, `<small>${s.rows.length} 条</small>`, tableHTML(s.cols, s.rows, { empty: s.empty, emptyH: 120, keep: "nt-" + s.title }))).join("");
  165. }
  166. return `<div class="dw dw-e dw-note" style="width:1040px" data-act="stop"><div class="dw-hd"><div class="idc"><div class="idr"><span class="no">${N.noteNo}</span>${tags}</div><div class="facts">${N.facts.map(([l, v]) => `<span>${l}<b>${v}</b></span>`).join("")}</div></div><span class="sp"></span><div class="hbtns col"><div class="r">${rf}${x}</div><div class="r">${hb}</div></div></div>${tabs}<div class="dw-bd gray" data-keep="dwbd-${ov.tab}">${body}</div></div>`;
  167. };
  168. /* ================= 确认框、修改密码弹窗 ================= */
  169. const CONFIRMS = Object.assign({}, P.feedback.confirms, {
  170. force: { title: "强平卖出", msg: "确定对该票据执行强平卖出吗?卖出后票据状态变为「已卖出」,不能恢复。", kind: "danger", ok: "确定强平", kv: [["票据编号", "NT001202608141A"], ["投资者", "陈思雨 · 安衡证券"], ["证券", "601318 中国平安 · 28,700 股"]] },
  171. redo: { title: "重新清算", msg: "将回拨到该批次的交易日并重新执行全部步骤,期间不能下单。确定重新清算吗?", kind: "warning", ok: "重新清算", kv: [["交易日", "2026-09-25"], ["批次", "CLR20260925001"]] },
  172. stop: { title: "停止当前任务", msg: "停止后本批次标记为「已停止」,已完成的步骤不会回滚。确定停止吗?", kind: "danger", ok: "停止", kv: [["批次", "CLR20260925001"], ["当前步骤", "4 · 交易交收处理"]] },
  173. });
  174. U.overlayRenderers.confirm = function (ov) {
  175. const c = CONFIRMS[ov.kind] || CONFIRMS.cancel;
  176. const icn = c.kind === "danger" ? "error" : c.kind === "warning" ? "warn" : "info";
  177. const okCls = c.kind === "danger" ? "dng-pri" : "pri";
  178. return `<div class="cfm" data-act="stop"><div class="cfm-bd"><div class="cfm-hd"><span class="cfm-ic ${c.kind}">${ic(icn)}</span><b>${esc(c.title)}</b></div><p>${esc(c.msg)}</p>${c.kv.length ? `<dl class="cfm-kv">${c.kv.map(([l, v]) => `<div><dt>${l}</dt><dd class="num">${esc(v)}</dd></div>`).join("")}</dl>` : ""}</div><div class="cfm-ft"><span class="btn" data-act="ovclose">${c.cancel || "取消"}</span><span class="btn ${okCls}" data-act="ovclose">${c.ok}</span></div></div>`;
  179. };
  180. U.overlayRenderers.pwd = function () {
  181. const rows = P.profile.pwdForm.map((f, i) => ({ label: f.label, req: f.req, body: U.ctl({ ...f, ph: i === 1 ? "请输入新密码" : f.ph }, {}), extra: i === 1 ? `<div class="fld-tip">6~20 位,至少两类字符</div>` : "" }));
  182. return sfDialog({ w: 480, lw: 72, title: "修改密码", sub: `${P.profile.username} · ${P.profile.role}`, rows, ft: `<span class="hint">${ic("info")}修改后需要重新登录</span><span class="sp"></span>${btnCancel}<span class="btn pri" data-act="ovtoast" data-msg="示例:不真正提交">确定</span>` });
  183. };
  184. /* ================= 个人中心 ================= */
  185. U.pageRenderers["个人中心"] = function () {
  186. const p = P.profile;
  187. const left = `<div class="card prof-id"><div class="prof-ava" data-act="toast" data-msg="上传头像(代码里是隐藏的文件选择)"><span class="ava big">A</span><span class="prof-cam">${ic("camera")}</span></div><div class="prof-nm"><b>${p.nickname}</b><span class="icb sm" data-act="toast" data-msg="修改昵称、性别的弹窗按弹窗 J 实现" title="修改资料">${ic("edit")}</span></div><div class="prof-role">${p.role}</div></div>`;
  188. const bind = (v, what) => (v ? `<span class="num">${v}</span>` : `<span class="t4">未绑定</span><span class="btn txt" data-act="toast" data-msg="绑定${what}的弹窗按弹窗 J 实现" style="margin-left:12px">绑定</span>`);
  189. const info = `<div class="card"><div class="card-hd">账号信息</div><div class="card-bd"><dl class="kvg bd1"><div><dt>用户名</dt><dd>${p.username}${ic("male", "gender")}</dd></div><div><dt>手机号码</dt><dd>${bind(p.mobile, "手机")}</dd></div><div><dt>邮箱</dt><dd>${bind(p.email, "邮箱")}</dd></div><div><dt>部门</dt><dd>${p.dept}<span class="t3" style="font-size:12px;margin-left:8px">示例</span></dd></div><div><dt>创建时间</dt><dd class="num">${p.createTime}</dd></div></dl></div></div>`;
  190. const sec = `<div class="card"><div class="card-hd">安全设置</div><div class="prof-sec">${p.security.map(([t, d, a], i) => `<div class="prof-row"><div><b>${t}</b><small>${d}</small></div><span class="btn txt" data-act="${i === 0 ? "ovopen" : "toast"}" data-o="pwd" data-msg="绑定弹窗按弹窗 J 实现">${a}</span></div>`).join("")}</div></div>`;
  191. return `<div class="prof">${left}<div class="prof-r">${info}${sec}</div></div>`;
  192. };
  193. /* ================= 404 / 401 ================= */
  194. function errHTML(code, st) {
  195. const is404 = code === "404";
  196. const art = `<svg class="err-art" viewBox="0 0 420 160"><text x="210" y="126" text-anchor="middle" font-size="128" font-weight="600" style="font-family:var(--font-num);fill:var(--c-primary-bg);stroke:var(--c-primary);stroke-width:2">${code}</text></svg>`;
  197. const home = ENDS[st.end].home;
  198. return `<div class="errp">${art}<div class="err-t"><b>${is404 ? "页面不存在" : "没有访问权限"}</b><p>${is404 ? "请确认输入的网址是否正确,或从左侧菜单重新进入。" : "当前账号没有「共赢票据清算」的访问权限,请联系系统管理员开通后再试。"}</p><div class="btns" style="justify-content:center">${btn("返回首页", "pri", "leaf", 'data-k="首页"')}${btn("返回上一页", "", "leaf", `data-k="${home}"`)}</div></div></div>`;
  199. }
  200. U.pageRenderers["404"] = (st) => errHTML("404", st);
  201. U.pageRenderers["401"] = (st) => errHTML("401", st);
  202. /* ================= 登录页:分项选型登录页 C「右侧浮卡·全幅底图」(2026-10-07 选定) =================
  203. * 整页是视觉 F 的渐变底色叠一层淡线条装饰;左侧一列:系统名、本系统业务模块、三端入口;右侧 440px 浮卡放表单。
  204. * 画布规格 sp 上记临时状态:cap(验证码种子)、showPw、remember。
  205. */
  206. const CAPCH = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  207. // 验证码图片示意:按种子生成 4 个字符和两条干扰线,点一下换一张
  208. function capSVG(seed, w = 120, h = 40) {
  209. let s = (seed * 7919 + 104729) % 233280;
  210. const rnd = () => (s = (s * 9301 + 49297) % 233280) / 233280;
  211. const chars = Array.from({ length: 4 }, () => CAPCH[Math.floor(rnd() * CAPCH.length)]);
  212. const fs = Math.round(h * 0.58);
  213. const txt = chars.map((ch, i) => { const x = Math.round(w * (0.13 + i * 0.22)), y = Math.round(h * 0.7), r = Math.round((rnd() - 0.5) * 28); return `<text x="${x}" y="${y}" font-size="${fs}" font-weight="600" transform="rotate(${r} ${x} ${y})" style="fill:var(${i % 2 ? "--c-primary" : "--c-link"})">${ch}</text>`; }).join("");
  214. const wave = (c) => `<path d="M0 ${(h * (0.3 + rnd() * 0.4)).toFixed(1)} C ${(w * 0.25).toFixed(0)} ${(h * rnd()).toFixed(1)}, ${(w * 0.5).toFixed(0)} ${(h * rnd()).toFixed(1)}, ${(w * 0.75).toFixed(0)} ${(h * rnd()).toFixed(1)} S ${w} ${(h * rnd()).toFixed(1)}, ${w} ${(h * (0.3 + rnd() * 0.4)).toFixed(1)}" style="stroke:var(${c});fill:none;stroke-width:1.2"/>`;
  215. return `<svg viewBox="0 0 ${w} ${h}" aria-label="验证码 ${chars.join("")}"><rect width="${w}" height="${h}" style="fill:var(--fill-2)"/>${wave("--c-primary-border")}${wave("--bd-ctl")}${txt}</svg>`;
  216. }
  217. // 底图:淡网格、几条主色曲线、右上角两个圆(内联 SVG,不用图片文件)
  218. const LG_DECO = `<svg class="lg-deco" viewBox="0 0 1440 900" preserveAspectRatio="none"><defs><pattern id="lgGrid" width="48" height="48" patternUnits="userSpaceOnUse"><path d="M48 0H0v48" fill="none" stroke="currentColor" stroke-width="1"/></pattern></defs><rect width="1440" height="900" fill="url(#lgGrid)" opacity=".05"/>
  219. <path d="M-40 640C260 560 420 760 760 660S1200 540 1480 620" opacity=".45"/><path d="M-40 700C300 640 480 820 820 700S1260 600 1480 700" opacity=".3"/><path d="M-40 560C200 500 380 640 700 580S1160 440 1480 520" opacity=".2"/><circle cx="1180" cy="150" r="220" opacity=".22"/><circle cx="1180" cy="150" r="300" opacity=".12"/></svg>`;
  220. U.canvasRenderers.login = function (sp) {
  221. const end = sp.end || "issue";
  222. const e = ENDS[end];
  223. const L = P.login;
  224. const c = L.copy;
  225. const v = sp.v || "login";
  226. const groups = D.menus[end].map((g) => g[0]).filter((n) => !L.opsGroups.includes(n));
  227. const mods = `<div class="lg-mods"><div class="lg-mods-t">本系统业务模块<span>${groups.length}</span></div><div class="lg-chips">${groups.map((n) => `<span class="lg-chip">${esc(n)}</span>`).join("")}</div></div>`;
  228. // 三端入口:当前系统标「当前」,另外两个点一下跳到对应系统的登录页(本稿里是切到那个系统)
  229. const ents = `<div class="lg-ents"><div class="lg-ents-t">三端系统<span>点击前往对应系统的登录页</span></div>${Object.entries(ENDS).map(([x, ex]) => `<div class="lg-ent ${x === end ? "on" : ""}" data-act="sys" data-end="${x}"${x === end ? "" : ` title="跳到${esc(ex.sys)}的登录地址"`}><span class="lg-logo sm">${ex.logo}</span><span class="lg-ent-n"><b>${esc(ex.sys)}</b><small>${L.nodes[x][1]}</small></span><span class="lg-ent-go">${x === end ? "当前系统" : `前往登录${ic("right")}`}</span></div>`).join("")}</div>`;
  230. const left = `<div class="lg-c-left"><div><b class="lg-c-sys">${esc(e.sys)}</b><div class="lg-c-ver">${L.version}</div></div>${mods}${ents}</div>`;
  231. const field = (label, inner) => `<div class="lg-fi"><label>${label}</label>${inner}</div>`;
  232. const pw = sp.showPw ? "123456" : "••••••";
  233. let form;
  234. if (v === "login") {
  235. form = `<div class="lg-ttl">登录</div>
  236. ${field(c.username, `<div class="ipt lg-ipt"><span class="val">admin</span></div>`)}
  237. ${field(c.password, `<div class="ipt lg-ipt"><span class="val num">${pw}</span><span class="lg-eye" data-act="eye" title="${sp.showPw ? "隐藏密码" : "显示密码"}">${ic(sp.showPw ? "eye" : "eyeoff")}</span></div>`)}
  238. ${field(c.captcha, `<div class="lg-cap-row"><div class="ipt lg-ipt" style="flex:1"><span class="ph">${c.captcha}</span></div><div class="lg-cap" title="点击换一张" data-act="cap">${capSVG((sp.cap || 0) + 3)}</div></div>`)}
  239. <div class="lg-row"><span class="chk-l" data-act="rem"><span class="ck ${sp.remember === false ? "" : "on"}">${CHECK}</span>${c.remember}</span><span class="lnk" data-act="lv" data-v="reset">${c.forget}</span></div>
  240. <span class="btn pri lg-btn" data-act="toast" data-msg="示例:不真正登录">${c.login}</span>`;
  241. } else {
  242. form = `<div class="lg-ttl">${c.reset}</div><p class="lg-p">输入账号绑定的邮箱,重置链接会发到这个邮箱。</p>
  243. ${field(c.email, `<div class="ipt lg-ipt"><span class="ph">${c.enterEmail}</span></div>`)}
  244. <span class="btn pri lg-btn" data-act="toast" data-msg="示例:验证码已发送到您的邮箱">${c.sendReset}</span>
  245. <div class="lg-row center"><span class="t3">${c.think}</span><span class="lnk" data-act="lv" data-v="login">${c.back}</span></div>`;
  246. }
  247. return `<div class="lg">${LG_DECO}<div class="lg-actions"><span class="icb" data-act="theme" title="${c.theme}:在浅色和最近看过的深色皮肤之间切换">${ic(T.dark ? "sun" : "moon")}</span><span class="icb" data-act="toast" data-msg="示例:${c.lang}" title="${c.lang}">${ic("globe")}</span></div>
  248. ${left}<div class="lg-card"><div class="lg-brand sm"><span class="lg-logo sm">${e.logo}</span><b>${esc(e.sys)}</b></div>${form}</div></div>`;
  249. };
  250. U.extraActions.cap = (host, sp) => { sp.cap = (sp.cap || 0) + 1; return true; };
  251. U.extraActions.eye = (host, sp) => { sp.showPw = !sp.showPw; return true; };
  252. U.extraActions.rem = (host, sp) => { sp.remember = sp.remember === false; return true; };
  253. U.extraActions.lv = (host, sp, el) => { sp.v = el.dataset.v; return true; };
  254. /* ================= 组件总览 ================= */
  255. U.canvasRenderers.kit = function () {
  256. const Tk = T.tokens;
  257. const dict = U.dict;
  258. const sw = (label, key) => { const val = Tk[key]; const none = val === "transparent"; return `<div class="swc"><i style="background:${none ? "repeating-linear-gradient(45deg,var(--bg-card) 0 4px,var(--fill-3) 4px 8px)" : val}"></i><b>${label}</b><span>${none ? "无" : esc(val)}</span></div>`; };
  259. const group = (items) => `<div class="sw-group">${items.map(([l, key]) => sw(l, key)).join("")}</div>`;
  260. const colors = `<div class="card"><div class="card-hd">颜色<span class="sub">主色 ${Tk["c-primary"]} 只做填充,文字链接 ${Tk["c-link"]};买入红、卖出绿语义不变</span></div><div class="card-bd">${T.palette.map(([, items]) => group(items)).join("")}</div></div>`;
  261. const ty = (spec, html) => `<div class="ty-row"><span class="spec">${spec}</span><div>${html}</div></div>`;
  262. const type = `<div class="card s6"><div class="card-hd">文字<span class="sub">中文系统字体,英文和数字 Inter</span></div><div class="card-bd" style="padding-top:8px;padding-bottom:8px">
  263. ${ty("20 / 28 · 编号标题", `<span class="num" style="font-size:20px;line-height:28px;font-weight:var(--fw-title);color:var(--t1)">ORD001202609250031</span>`)}
  264. ${ty("16 / 24 · 弹窗标题", `<span style="font-size:16px;line-height:24px;font-weight:var(--fw-title);color:var(--t1)">撤销订单</span>`)}
  265. ${ty("15 / 22 · 区块标题", `<span style="font-size:15px;font-weight:var(--fw-title);color:var(--t1)">订单信息</span>`)}
  266. ${ty("14 / 22 · 正文", `<span style="color:var(--t1)">订单已进入对冲流程,预计交收日 <span class="num">2026-09-29</span>。</span>`)}
  267. ${ty("14 / 22 · 次要", `<span style="color:var(--t2)">投资者名称、机构名称等次要信息</span>`)}
  268. ${ty("12 / 18 · 辅助", `<span style="font-size:12px;line-height:18px;color:var(--t3)">表头、键名、输入框下方的提示</span>`)}
  269. ${ty("数字", `<span class="num" style="color:var(--t1);font-size:16px">3,000,000.00 1,688.00 0.0003 2026-09-25 09:31:07</span>`)}
  270. </div></div>`;
  271. const buttons = `<div class="card s6"><div class="card-hd">按钮<span class="sub">${T.notes.buttons}</span></div><div class="card-bd">
  272. <div class="kit-row"><span class="kit-lbl">常规</span><span class="btn pri">${ic("search")}查找</span><span class="btn">${ic("refresh")}重置</span><span class="btn">${ic("download")}数据导出</span><span class="btn dng">撤单</span><span class="btn is-dis">禁用</span></div>
  273. <div class="kit-row"><span class="kit-lbl">悬停</span><span class="btn pri is-hover">${ic("plus")}新增</span><span class="btn is-hover">${ic("refresh")}重置</span><span class="btn icon">${ic("refresh")}</span><span class="btn is-loading"><i class="spin"></i>查找中</span></div>
  274. <div class="kit-row"><span class="kit-lbl">表格内</span><span class="btn txt">详情</span><span class="btn txt">编辑</span><span class="btn txt dng">撤单</span><span class="btn sm pri">确认</span><span class="btn sm">补录</span></div>
  275. <div class="kit-row"><span class="kit-lbl">方向</span><span class="btn dng-pri">确认买入 1,000 股</span><span class="btn ok-pri">确认卖出 500 股</span><span class="btn">取消</span></div>
  276. </div></div>`;
  277. const f = (label, html, extra = "") => `<div><div class="f-l">${label}</div>${html}${extra}</div>`;
  278. const forms = `<div class="card"><div class="card-hd">输入控件<span class="sub">默认 32px;登录页 40px</span></div><div class="card-bd"><div class="kit-fields">
  279. ${f("默认", `<div class="ipt"><span class="ph">主订单编号</span></div>`)}
  280. ${f("已填写", `<div class="ipt"><span class="val num">ORD001202609250031</span></div>`)}
  281. ${f("聚焦", `<div class="ipt is-focus"><span class="val">贵州茅台</span><span style="width:1px;height:16px;background:var(--t1);margin-left:-5px"></span></div>`)}
  282. ${f("下拉选择", `<div class="ipt is-focus"><span class="val">待处理</span>${ic("down")}</div>`, `<div class="pop" style="margin-top:4px"><div class="pop-i">全部</div><div class="pop-i on">待处理<span class="sp" style="flex:1"></span>${ic("check")}</div><div class="pop-i is-hover">执行中</div><div class="pop-i">已完成</div></div>`)}
  283. ${f("日期区间", `<div class="ipt">${ic("calendar")}<span class="val num">2026-09-25</span><span class="sep">~</span><span class="val num">2026-09-25</span></div>`, `<div class="f-l" style="margin-top:16px">带单位</div><div class="ipt"><span class="val num">1,000</span><span class="sfx">股</span></div><div class="f-l" style="margin-top:16px">多选</div><div class="ipt"><span class="val chips"><span class="chipv">待生效</span><span class="chipv">存续期</span></span>${ic("down")}</div>`)}
  284. ${f("校验失败", `<div class="ipt is-err"><span class="ph">请输入委托数量</span></div>`, `<div class="fld-err">${ic("error")}委托数量必须是 100 的整数倍</div><div class="f-l" style="margin-top:16px">禁用</div><div class="ipt is-dis"><span class="val">CNY</span></div><div class="f-l" style="margin-top:16px">两段切换</div><div class="segc"><span class="on">自定义</span><span>TRS</span></div>`)}
  285. ${f("勾选与开关", `<div class="kit-row" style="gap:20px"><span class="chk-l"><span class="ck on">${CHECK}</span>已勾选</span><span class="chk-l"><span class="ck">${CHECK}</span>未勾选</span><span class="chk-l"><span class="ck half"></span>部分</span></div><div class="kit-row" style="gap:20px"><span class="chk-l"><span class="rd on"></span>限价</span><span class="chk-l"><span class="rd"></span>市价</span><span class="chk-l"><span class="sw on"></span>开启</span><span class="chk-l"><span class="sw"></span>关闭</span></div>`)}
  286. </div></div></div>`;
  287. const tags = (dk, list) => (list || Object.keys(dict[dk])).map((t) => tag(dk, t)).join("");
  288. const status = `<div class="card"><div class="card-hd">状态与字典<span class="sub">${T.notes.status}</span></div><div class="card-bd">
  289. <div class="kit-row"><span class="kit-lbl">订单状态</span>${tags("note_order_status")}</div>
  290. <div class="kit-row"><span class="kit-lbl">委托状态</span>${tags("trs_order_status", ["已报", "部成", "已成", "已撤", "废单", "过期"])}</div>
  291. <div class="kit-row"><span class="kit-lbl">票据</span>${tags("note_status")}${tags("note_risk_status")}</div>
  292. <div class="kit-row"><span class="kit-lbl">方向</span>${tags("direction")}<span class="t3" style="margin-left:12px">文字形式:</span><span class="buy" style="font-weight:var(--fw-strong)">买入</span><span class="sell" style="font-weight:var(--fw-strong)">卖出</span><span class="t3" style="margin-left:12px">盈亏:</span><span class="num buy">+58,002.47</span><span class="num sell">-31,886.40</span></div>
  293. <div class="kit-row"><span class="kit-lbl">操作类型</span>${tags("op_type")}${tags("process_mode")}${tags("hedge_progress")}</div>
  294. </div></div>`;
  295. const kvs = `<div class="card s6"><div class="card-hd">描述表<span class="sub">${T.notes.kv}</span></div><div class="card-bd">
  296. <dl class="kvg bd2 n"><div><dt>主订单编号</dt><dd class="num">ORD001202609250031</dd></div><div><dt>订单状态</dt><dd>${tag("note_order_status", "待处理")}</dd></div><div><dt>订单方向</dt><dd>${tag("direction", "买入")}</dd></div><div><dt>投资者</dt><dd>张明远 · INV001100151</dd></div><div><dt>证券</dt><dd>600519 贵州茅台</dd></div><div><dt>名义本金</dt><dd class="num">3,000,000.00 CNY</dd></div><div><dt>下单时间</dt><dd class="num">2026-09-25 09:31:07</dd></div><div><dt>备注</dt><dd class="t4">--</dd></div></dl>
  297. <div class="f-l" style="margin:16px 0 8px">数字卡片(汇总)</div><div class="tiles" style="grid-template-columns:repeat(3,1fr)"><div class="tile"><div class="l">对冲比例</div><div class="v">77.78%</div><div class="bar"><i style="width:77.78%"></i></div></div><div class="tile"><div class="l">累计成交数量</div><div class="v">6,300</div></div><div class="tile"><div class="l">对冲含费成交均价</div><div class="v">245.2112<i>CNY</i></div></div></div>
  298. </div></div>`;
  299. const rows = D.issueRows.filter((r) => r.sub === 0).slice(0, 5);
  300. const table = `<div class="card"><div class="ctabs"><div class="tabs"><span class="tab on">全部<span class="cnt">12</span></span><span class="tab">待处理<span class="cnt">1</span></span><span class="tab">执行中<span class="cnt">3</span></span><span class="tab">已完成<span class="cnt">5</span></span></div><span class="sp"></span><span class="btn txt">${ic("refresh")}刷新</span></div>
  301. <div class="tbl-wrap"><table class="tbl"><thead><tr><th style="width:48px"><span class="ck half"></span></th><th>主订单编号</th><th>订单方向</th><th>订单状态</th><th>投资者名称</th><th>证券代码/名称</th><th class="r">订单名义本金(交易币种)</th><th>下单时间</th><th>操作</th></tr></thead><tbody>
  302. ${rows.map((r, i) => `<tr class="${i === 1 ? "is-sel" : ""} ${i === 3 ? "is-hover" : ""}"><td><span class="ck ${i === 1 ? "on" : ""}">${CHECK}</span></td><td><span class="lnk num">${r.no}</span></td><td>${tag("direction", r.direction)}</td><td>${tag("note_order_status", r.status)}</td><td>${r.invName}</td><td>${r.stock}</td><td class="r num">${r.notional === "--" ? '<span class="t4">--</span>' : r.notional}</td><td class="num">${r.orderTime}</td><td><span class="btns" style="gap:12px"><span class="btn txt">详情</span><span class="btn txt dng">撤单</span></span></td></tr>`).join("")}
  303. </tbody></table></div>
  304. ${U.pagerHTML(12)}
  305. <div style="padding:0 16px 12px;font-size:12px;color:var(--t3)">第 2 行为选中态,第 4 行为悬停态;卡片内页签见「清算管理」。</div></div>`;
  306. const feedback = `<div class="card s6"><div class="card-hd">提示与反馈<span class="sub">完整排布见「反馈类总览」</span></div><div class="card-bd">
  307. <div style="display:grid;grid-template-columns:1fr;gap:10px">
  308. <div class="alt alt-info">${ic("info")}<span>自开始日期(含)起同步交易日,重复同步不会产生重复数据。</span></div>
  309. <div class="alt alt-danger">${ic("error")}<span>撤单结果未知:上轮撤单未取得明确结果,可人工再次撤单。</span></div>
  310. </div>
  311. <div class="kit-row" style="margin-top:16px;gap:16px"><span class="toast">${ic("success", "ok")}<span>保存成功</span></span><span class="toast">${ic("error", "no")}<span>导出失败,请稍后重试</span></span><span class="tip">点击复制编号</span></div>
  312. </div></div>`;
  313. const dialog = `<div class="card s6"><div class="card-hd">弹窗与弹层<span class="sub">${T.notes.dialog}</span></div><div class="card-bd"><div class="kit-mask">
  314. <div class="dlg"><div class="dlg-hd">撤销订单<span class="x">${ic("x")}</span></div><div class="dlg-bd" style="display:flex;flex-direction:column;gap:14px">
  315. <div class="alt alt-warning">${ic("warn")}<span>撤销后订单进入「撤单中」,不能恢复。</span></div>
  316. <dl class="kv" style="grid-template-columns:1fr;gap:8px"><div><dt>主订单编号</dt><dd class="num">ORD001202609250031</dd></div><div><dt>证券</dt><dd>600519 贵州茅台</dd></div></dl>
  317. </div><div class="dlg-ft"><span class="btn">取消</span><span class="btn pri">确认撤销</span></div></div>
  318. </div></div></div>`;
  319. return `<div class="kit-top"><h1>组件总览 · ${T.title}</h1><span>主色 ${Tk["c-primary"]}|正文 14px · 控件 32px · 表格行高 40px · 圆角 ${Tk["r-ctl"]}/${Tk["r-card"]}|尺寸和圆角四套皮肤相同,换肤只改颜色与质感</span></div>
  320. <div class="kit-grid">${colors}${type}${buttons}${forms}${status}${table}${kvs}${feedback}${dialog}</div>`;
  321. };
  322. /* ================= 反馈类总览 ================= */
  323. U.canvasRenderers.feedback = function () {
  324. const F = P.feedback;
  325. const card = (title, sub, body, cls = "") => `<div class="card ${cls}"><div class="card-hd">${title}${sub ? `<span class="sub">${sub}</span>` : ""}</div><div class="card-bd">${body}</div></div>`;
  326. const icOf = (t) => ic(t === "success" ? "success" : t === "error" || t === "danger" ? "error" : t === "warning" ? "warn" : "info", t);
  327. const msgs = `<div class="fb-msgs">${F.messages.map(([t, m, close]) => `<div class="toast msg-${t}">${icOf(t)}<span>${esc(m)}</span>${close ? `<span class="x">${ic("x")}</span>` : ""}</div>`).join("")}</div><div class="fb-note">顶部居中,距顶 72px;多条时向下堆叠;默认 3 秒消失,带关闭按钮的停留 8 秒。</div>`;
  328. const ntf = `<div class="ntf"><div class="ntf-hd">${ic("success", "ok")}<b>${F.notification.title}</b><small>${F.notification.time}</small><span class="x">${ic("x")}</span></div><p>${F.notification.body}</p><span class="lnk">${F.notification.link}${ic("right")}</span></div><div class="fb-note">右上角,距顶 16px、距右 16px;用于后台任务完成这类不打断操作的消息。</div>`;
  329. const alerts = `<div class="fb-alts">${F.alerts.map(([t, m]) => `<div class="alt alt-${t}">${icOf(t)}<span>${esc(m)}</span></div>`).join("")}<div class="alt alt-${F.alertTitled[0]} titled">${icOf("warning")}<div><b>${F.alertTitled[1]}</b><p>${F.alertTitled[2]}</p></div><span class="x">${ic("x")}</span></div></div>`;
  330. const cfm = U.overlayRenderers.confirm({ kind: "cancel" });
  331. const confirm = `<div class="kit-mask" style="min-height:260px">${cfm}</div><div class="fb-note">在页面里的样子见「确认框」;危险操作红色实心按钮并写明动作。</div>`;
  332. const empties = `<div class="fb-empties">${F.empties.map(([n, t, d, a]) => `<div class="fb-cell"><div class="fb-lbl">${n}</div><div class="empty box">${EMPTY}<span>${esc(t)}</span>${d ? `<small>${esc(d)}</small>` : ""}${a ? `<span class="btn sm">${esc(a)}</span>` : ""}</div></div>`).join("")}</div>`;
  333. const rows = D.issueRows.filter((r) => r.sub === 0).slice(0, 4);
  334. const loadTbl = `<div class="fb-load"><div class="tbl-wrap bd" style="position:relative"><table class="tbl"><thead><tr><th>主订单编号</th><th>订单方向</th><th>订单状态</th><th>投资者名称</th><th class="r">订单名义本金</th></tr></thead><tbody>${rows.map((r) => `<tr><td class="num">${r.no}</td><td>${tag("direction", r.direction)}</td><td>${tag("note_order_status", r.status)}</td><td>${r.invName}</td><td class="r num">${r.notional}</td></tr>`).join("")}</tbody></table><div class="loading"><i class="spin big"></i><span>加载中</span></div></div>
  335. <div class="fb-load-r"><div class="f-l">按钮</div><div class="btns"><span class="btn pri is-loading"><i class="spin"></i>查找中</span><span class="btn is-loading"><i class="spin"></i>导出中</span></div><div class="f-l" style="margin-top:16px">首屏骨架</div><div class="skel" style="height:auto;gap:8px"><div class="bar" style="width:40%"></div><div class="bar" style="width:92%"></div><div class="bar" style="width:84%"></div><div class="bar" style="width:70%"></div></div></div></div>`;
  336. const results = `<div class="fb-results">${F.results.map(([t, ttl, d, bs]) => `<div class="result"><span class="res-ic ${t}">${icOf(t)}</span><b>${ttl}</b><p>${d}</p><div class="btns">${bs.map(([b, ty]) => `<span class="btn ${ty === "primary" ? "pri" : ""}">${b}</span>`).join("")}</div></div>`).join("")}</div>`;
  337. const validate = `<div class="fgrid c2" style="max-width:720px"><div class="fi fi-t"><label><i class="req">*</i>E-mail</label><div class="fc"><div class="ipt is-err"><span class="val">wanqing.li@</span></div><div class="fld-err">${ic("error")}邮箱格式不正确</div></div></div><div class="fi fi-t"><label><i class="req">*</i>发生金额(结算币种)</label><div class="fc"><div class="ipt is-err"><span class="ph">请输入</span><span class="sfx">HKD</span></div><div class="fld-err">${ic("error")}请输入发生金额(HKD)</div></div></div></div>
  338. <div class="fb-note" style="margin-top:12px">错误在失焦时校验、提交时全部校验;长表单的弹窗底部汇总「还有几项必填未填、几项有误」,第一处错误滚动到可见。</div>`;
  339. return `<div class="kit-top"><h1>反馈类总览</h1><span>全局提示、通知、提示条、确认框、空状态、加载态、结果提示、表单校验</span></div>
  340. <div class="kit-grid">${card("全局提示", "ElMessage", msgs, "s6")}${card("通知", "ElNotification", ntf, "s6")}${card("提示条", "el-alert:按状态取色;可带标题、可关闭", alerts, "s6")}${card("确认框", "ElMessageBox.confirm", confirm, "s6")}${card("空状态", "el-empty:说明原因,必要时给一个动作", empties)}${card("加载态", "v-loading、按钮 loading、骨架", loadTbl, "s6")}${card("结果提示", "el-result:整块居中", results, "s6")}${card("表单校验", "输入框变红 + 下方原因", validate)}</div>`;
  341. };
  342. })();