1218 lines
76 KiB
HTML
1218 lines
76 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>App 技术栈介绍 — T-Systems</title>
|
||
<style>
|
||
/* ============================================================
|
||
Telekom Scale design tokens — 取自
|
||
unpkg.com/@telekom/design-tokens/dist/telekom/css/telekom-design-tokens.all.css
|
||
============================================================ */
|
||
:root{
|
||
--tk-magenta:#e20074; --tk-magenta-hover:#c00063; --tk-magenta-press:#9e0051;
|
||
--tk-text:#000000; --tk-text-add:hsla(0,0%,0%,.65); --tk-text-dis:hsla(0,0%,0%,.4);
|
||
--tk-inverted:#ffffff;
|
||
--tk-canvas:#ffffff; --tk-canvas-subtle:#fbfbfb;
|
||
--tk-surface-subtle:#efeff0; --tk-surface-hl:#242426;
|
||
--tk-link:#2238df;
|
||
--tk-info:#2238df; --tk-info-subtle:#d3d7f9;
|
||
--tk-success:#008653; --tk-success-subtle:#ccf0e1;
|
||
--tk-danger:#d91308; --tk-warning:#b63d00;
|
||
--tk-neutral:#8e8e93; --tk-neutral-subtle:#e5e5e7;
|
||
--tk-r-sm:.25rem; --tk-r:.5rem; --tk-r-lg:.75rem; --tk-r-pill:62.4375rem;
|
||
--tk-sh-raised:0 8px 32px 0 hsla(0,0%,0%,.1), 0 4px 8px 0 hsla(0,0%,0%,.1);
|
||
--tk-sh-float:0 12px 48px 0 hsla(0,0%,0%,.1), 0 6px 12px 0 hsla(0,0%,0%,.1);
|
||
--tk-font:'TeleNeoWeb','PingFang SC','Microsoft YaHei','Noto Sans SC','Hiragino Sans GB',sans-serif;
|
||
--tk-mono:'SF Mono','Cascadia Mono','Consolas','Menlo',monospace;
|
||
}
|
||
|
||
/* TeleNeoWeb 是授权品牌字体,公开包里不含文件。
|
||
放进 assets/fonts/ 即生效,缺失则静默回退。见 assets/fonts/README.md */
|
||
@font-face{font-family:'TeleNeoWeb';src:url('./assets/fonts/TeleNeoWeb-Regular.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap}
|
||
@font-face{font-family:'TeleNeoWeb';src:url('./assets/fonts/TeleNeoWeb-Medium.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap}
|
||
@font-face{font-family:'TeleNeoWeb';src:url('./assets/fonts/TeleNeoWeb-Bold.woff2') format('woff2');font-weight:700;font-style:normal;font-display:swap}
|
||
@font-face{font-family:'TeleNeoWeb';src:url('./assets/fonts/TeleNeoWeb-ExtraBold.woff2') format('woff2');font-weight:800;font-style:normal;font-display:swap}
|
||
|
||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||
html,body{height:100%;overflow:hidden;background:#1a1a1c}
|
||
body{font-family:var(--tk-font);color:var(--tk-text);-webkit-font-smoothing:antialiased}
|
||
|
||
/* ---------- 舞台:固定 1280×720,等比缩放 ---------- */
|
||
#viewport{position:fixed;inset:0;display:flex;align-items:center;justify-content:center}
|
||
#stage{width:1280px;height:720px;background:var(--tk-canvas);position:relative;overflow:hidden;
|
||
transform-origin:center center;box-shadow:0 0 0 1px rgba(0,0,0,.35),0 24px 96px rgba(0,0,0,.5)}
|
||
|
||
#deck{position:absolute;inset:0}
|
||
.holder{position:absolute;inset:0}
|
||
.slide{position:absolute;inset:0;padding:56px 72px 64px;display:flex;flex-direction:column;
|
||
background:var(--tk-canvas);opacity:0;transform:translateX(48px);pointer-events:none;
|
||
transition:opacity .3s ease,transform .32s cubic-bezier(.4,0,.2,1)}
|
||
.slide.active{opacity:1;transform:none;pointer-events:auto}
|
||
.slide.before{transform:translateX(-48px)}
|
||
@media (prefers-reduced-motion:reduce){.slide{transition:opacity .01s}}
|
||
|
||
/* ---------- 页眉页脚 ---------- */
|
||
#chrome{position:absolute;inset:0;pointer-events:none;z-index:20;opacity:0;transition:opacity .25s}
|
||
#chrome.on{opacity:1}
|
||
#brand{position:absolute;top:26px;left:72px;display:flex;align-items:center;gap:10px;
|
||
font-size:15px;font-weight:700;letter-spacing:.01em}
|
||
#brand::before{content:"";width:5px;height:19px;background:var(--tk-magenta);border-radius:1px}
|
||
#meta{position:absolute;top:28px;right:72px;font-size:13px;font-weight:500;color:var(--tk-text-dis);
|
||
display:flex;align-items:center;gap:14px}
|
||
#meta .num{font-variant-numeric:tabular-nums;color:var(--tk-text-add);font-weight:700}
|
||
/* 进度条独立于页眉:分隔页/封面隐藏页眉,但进度条始终可见 */
|
||
#bar{position:absolute;left:0;bottom:0;height:3px;background:var(--tk-magenta);width:0;z-index:21;
|
||
transition:width .32s cubic-bezier(.4,0,.2,1),opacity .25s}
|
||
#bar.off{opacity:0}
|
||
|
||
/* ---------- 排版原语 ---------- */
|
||
.eyebrow{font-size:13px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
|
||
color:var(--tk-magenta);margin-bottom:10px}
|
||
h1{font-size:54px;font-weight:800;line-height:1.14;letter-spacing:-.01em}
|
||
h2{font-size:38px;font-weight:800;line-height:1.16;letter-spacing:-.005em}
|
||
h3{font-size:23px;font-weight:800;line-height:1.28}
|
||
h4{font-size:17px;font-weight:800;line-height:1.35}
|
||
.lead{font-size:19px;font-weight:400;line-height:1.5;color:var(--tk-text-add)}
|
||
.sub{font-size:16px;line-height:1.55;color:var(--tk-text-add);margin-top:12px;max-width:1010px}
|
||
.body{font-size:15px;line-height:1.6}
|
||
.hd{margin-bottom:26px;flex-shrink:0}
|
||
.bd{flex:1;min-height:0;display:flex;flex-direction:column;justify-content:center}
|
||
.bd.top{justify-content:flex-start}
|
||
em{font-style:normal;color:var(--tk-magenta);font-weight:700}
|
||
strong{font-weight:700}
|
||
code{font-family:var(--tk-mono);font-size:.88em;background:var(--tk-surface-subtle);
|
||
padding:1px 6px;border-radius:var(--tk-r-sm)}
|
||
.note{font-size:12.5px;line-height:1.5;color:var(--tk-text-dis);margin-top:16px;flex-shrink:0}
|
||
.note b{color:var(--tk-text-add);font-weight:700}
|
||
|
||
/* ---------- 封面 / 分隔 / 断言 ---------- */
|
||
.cover{justify-content:center;padding-left:96px}
|
||
.cover::before{content:"";position:absolute;left:0;top:0;bottom:0;width:10px;background:var(--tk-magenta)}
|
||
.cover .kicker{font-size:14px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
|
||
color:var(--tk-magenta);margin-bottom:22px}
|
||
.cover h1{max-width:900px}
|
||
.cover .foot{position:absolute;left:96px;bottom:64px;display:flex;align-items:flex-end;
|
||
justify-content:space-between;width:calc(100% - 168px)}
|
||
.cover .co{font-size:26px;font-weight:800;letter-spacing:.005em}
|
||
.cover .co span{color:var(--tk-magenta)}
|
||
|
||
.divider{justify-content:center;background:var(--tk-canvas-subtle)}
|
||
.divider::before{content:"";position:absolute;left:0;top:0;bottom:0;width:10px;background:var(--tk-magenta)}
|
||
.divider .no{font-size:132px;font-weight:800;line-height:.9;color:var(--tk-magenta);
|
||
letter-spacing:-.03em;margin-bottom:16px}
|
||
.divider h2{font-size:46px;max-width:820px}
|
||
.divider .pts{margin-top:28px;display:flex;gap:10px;flex-wrap:wrap;max-width:840px}
|
||
.divider .pts span{font-size:13.5px;font-weight:500;color:var(--tk-text-add);
|
||
border:1px solid var(--tk-neutral-subtle);background:#fff;padding:6px 14px;border-radius:var(--tk-r-pill)}
|
||
|
||
.statement{justify-content:center;align-items:flex-start}
|
||
.statement .big{font-size:42px;font-weight:800;line-height:1.3;max-width:1060px;letter-spacing:-.005em}
|
||
.statement .big em{color:var(--tk-magenta)}
|
||
|
||
/* ---------- 卡片 ---------- */
|
||
.grid{display:grid;gap:16px}
|
||
.g2{grid-template-columns:1fr 1fr}
|
||
.g3{grid-template-columns:repeat(3,1fr)}
|
||
.g4{grid-template-columns:repeat(4,1fr)}
|
||
.card{background:var(--tk-canvas-subtle);border:1px solid var(--tk-neutral-subtle);
|
||
border-radius:var(--tk-r-lg);padding:20px 22px;display:flex;flex-direction:column}
|
||
.card.hi{background:#fff;border-color:var(--tk-magenta);box-shadow:var(--tk-sh-raised)}
|
||
.card.ok{background:var(--tk-success-subtle);border-color:#9fdcc2}
|
||
.card .n{font-size:12px;font-weight:800;color:var(--tk-magenta);letter-spacing:.08em;margin-bottom:8px}
|
||
.card h4{margin-bottom:8px}
|
||
.card p{font-size:13.5px;line-height:1.55;color:var(--tk-text-add)}
|
||
.card p+p{margin-top:7px}
|
||
|
||
/* ---------- 表格 ---------- */
|
||
table{width:100%;border-collapse:collapse;font-size:14px}
|
||
th,td{text-align:left;padding:9px 14px;border-bottom:1px solid var(--tk-neutral-subtle);vertical-align:top;line-height:1.45}
|
||
thead th{font-size:12px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;
|
||
color:var(--tk-text-dis);border-bottom:2px solid var(--tk-text);padding-bottom:8px}
|
||
tbody tr:last-child td{border-bottom:none}
|
||
td.k{font-weight:700;width:1%;white-space:nowrap}
|
||
.col-f{color:var(--tk-text);font-weight:700}
|
||
.col-f.mark{color:var(--tk-magenta)}
|
||
.col-u{color:var(--tk-text-add)}
|
||
table.compact th,table.compact td{padding:7px 12px;font-size:13px}
|
||
table.tiny th,table.tiny td{padding:5.5px 11px;font-size:12.5px}
|
||
/* 表格密集的几页收一档行高 */
|
||
.slide.dense table.tiny th,.slide.dense table.tiny td{padding:3.5px 10px;font-size:12px;line-height:1.4}
|
||
.slide.dense table.compact th,.slide.dense table.compact td{padding:5px 11px;font-size:12.5px;line-height:1.4}
|
||
.slide.dense .hd{margin-bottom:14px}
|
||
.slide.dense .card{padding:11px 14px}
|
||
.slide.dense .card p{font-size:12.5px;line-height:1.5}
|
||
.slide.dense .quote{font-size:13.5px;line-height:1.55}
|
||
.slide.dense .brow{margin-bottom:6px}
|
||
|
||
/* ---------- 条形图 ---------- */
|
||
.chart{display:flex;flex-direction:column;gap:20px}
|
||
.cgroup .ctitle{font-size:13px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;
|
||
color:var(--tk-text-dis);margin-bottom:11px;display:flex;align-items:baseline;gap:12px}
|
||
.cgroup .ctitle .x{color:var(--tk-magenta);font-size:14px;letter-spacing:0;text-transform:none}
|
||
.brow{display:flex;align-items:center;gap:14px;margin-bottom:8px}
|
||
.brow .bl{width:96px;flex-shrink:0;font-size:13.5px;font-weight:700;text-align:right}
|
||
.brow .bt{flex:1;height:26px;background:var(--tk-surface-subtle);border-radius:var(--tk-r-sm);overflow:hidden}
|
||
.brow .bf{height:100%;width:0;border-radius:var(--tk-r-sm);
|
||
transition:width .75s cubic-bezier(.25,.8,.35,1) .12s}
|
||
.slide.active .brow .bf{width:var(--w)}
|
||
.bf.f{background:var(--tk-magenta)}
|
||
.bf.u{background:var(--tk-neutral)}
|
||
.bf.g{background:var(--tk-success)}
|
||
.brow .bv{width:104px;flex-shrink:0;font-size:15px;font-weight:800;font-variant-numeric:tabular-nums}
|
||
.brow .bv.dim{color:var(--tk-text-add);font-weight:700}
|
||
|
||
/* ---------- 大数字 ---------- */
|
||
.stats{display:flex;gap:20px}
|
||
.stat{flex:1;background:var(--tk-canvas-subtle);border:1px solid var(--tk-neutral-subtle);
|
||
border-radius:var(--tk-r-lg);padding:18px 20px}
|
||
.stat .v{font-size:42px;font-weight:800;line-height:1;color:var(--tk-magenta);
|
||
font-variant-numeric:tabular-nums;letter-spacing:-.02em}
|
||
.stat .v small{font-size:19px;font-weight:800;margin-left:3px}
|
||
.stat .l{font-size:12.5px;line-height:1.45;color:var(--tk-text-add);margin-top:9px}
|
||
/* 案例页的大数字与图片并排,收窄一档 */
|
||
.case .stat{padding:11px 16px}
|
||
.case .stat .v{font-size:34px}
|
||
.case .stat .v small{font-size:16px}
|
||
.case .stat .l{font-size:12px;margin-top:7px}
|
||
|
||
/* ---------- chip ---------- */
|
||
.chips{display:flex;flex-wrap:wrap;gap:8px}
|
||
.chip{font-size:13.5px;font-weight:500;padding:7px 14px;border-radius:var(--tk-r-pill);
|
||
border:1px solid var(--tk-neutral-subtle);background:#fff;white-space:nowrap}
|
||
.chip.f{border-color:var(--tk-magenta);color:var(--tk-magenta);font-weight:700;background:#fff}
|
||
.chip.n{background:var(--tk-surface-subtle);border-color:transparent}
|
||
.chip.d{color:var(--tk-text-dis)}
|
||
|
||
/* ---------- 引用块 ---------- */
|
||
.quote{border-left:4px solid var(--tk-magenta);padding:4px 0 4px 18px;font-size:15.5px;
|
||
line-height:1.6;color:var(--tk-text)}
|
||
.quote.warn{border-left-color:var(--tk-warning)}
|
||
.quote.ok{border-left-color:var(--tk-success)}
|
||
.quote .src{display:block;font-size:12.5px;color:var(--tk-text-dis);margin-top:8px}
|
||
|
||
/* ---------- 架构栈图 ----------
|
||
每一列 = 一条技术路线 / 一个框架:代码层 → 中间层 → 绘制层。
|
||
.sb 用等分栅格排层,各列的 .sh / .sf 高度锁死,
|
||
这样「界面由谁绘制」在四列里落在同一条水平线上,可以横着扫。
|
||
.on 用洋红点出这一层,全篇图示的主语都是它。 */
|
||
.stacks{display:flex;gap:14px;align-items:stretch;min-height:400px}
|
||
.stk{flex:1;display:flex;flex-direction:column;border:1px solid var(--tk-neutral-subtle);
|
||
border-radius:var(--tk-r-lg);overflow:hidden;background:#fff;min-width:0}
|
||
.stk.on{border-color:var(--tk-magenta);box-shadow:var(--tk-sh-raised)}
|
||
.stk .sh{padding:10px 12px;background:var(--tk-surface-subtle);text-align:center;min-height:58px;
|
||
display:flex;flex-direction:column;justify-content:center}
|
||
.stk.on .sh{background:var(--tk-magenta);color:#fff}
|
||
.stk .sh .no{font-size:11px;font-weight:800;letter-spacing:.12em;opacity:.55;display:block}
|
||
.stk .sh .nm{font-size:14.5px;font-weight:800;margin-top:2px;display:block;line-height:1.25}
|
||
.stk .sb{flex:1;padding:14px 12px;display:grid;grid-template-rows:1fr 16px 1fr 16px 1fr}
|
||
.stk .sb.four{grid-template-rows:1fr 16px 1fr 16px 1fr 16px 1fr}
|
||
.lyr{border-radius:var(--tk-r-sm);padding:6px 9px;text-align:center;font-size:12.5px;line-height:1.35;font-weight:700;
|
||
display:flex;flex-direction:column;align-items:center;justify-content:center}
|
||
.lyr .t{font-size:10px;font-weight:800;letter-spacing:.06em;color:var(--tk-text-dis);
|
||
text-transform:uppercase;display:block;margin-bottom:3px}
|
||
.lyr.code{background:var(--tk-surface-subtle)}
|
||
.lyr.mid{background:#fff;border:1px dashed var(--tk-neutral-subtle);color:var(--tk-text-add);font-weight:500}
|
||
.lyr.draw{background:var(--tk-canvas-subtle);border:1px solid var(--tk-neutral-subtle)}
|
||
.stk.on .lyr.draw{background:#fce6f1;border-color:var(--tk-magenta);color:var(--tk-magenta-press)}
|
||
.lyr.draw .t{color:inherit;opacity:.65}
|
||
.dn{display:flex;align-items:center;justify-content:center;color:var(--tk-text-dis);font-size:12px;line-height:1}
|
||
.stk .sf{padding:9px 12px;border-top:1px solid var(--tk-neutral-subtle);min-height:60px;
|
||
font-size:11.5px;line-height:1.45;color:var(--tk-text-add);background:var(--tk-canvas-subtle);
|
||
display:flex;align-items:center}
|
||
.stk.on .sf{color:var(--tk-text);background:#fff}
|
||
|
||
/* ---------- 开源分层 ---------- */
|
||
.layers{display:flex;gap:24px}
|
||
.lcol{flex:1}
|
||
.lcol .lh{font-size:15px;font-weight:800;margin-bottom:10px;padding-bottom:7px;border-bottom:2px solid var(--tk-text)}
|
||
.layer{padding:11px 14px;border-radius:var(--tk-r);margin-bottom:7px;font-size:13.5px;font-weight:700;
|
||
display:flex;justify-content:space-between;align-items:center;gap:10px}
|
||
.layer .tag{font-size:11px;font-weight:800;letter-spacing:.04em;padding:3px 9px;border-radius:var(--tk-r-pill);white-space:nowrap}
|
||
.layer.open{background:var(--tk-success-subtle);color:#04543a}
|
||
.layer.open .tag{background:var(--tk-success);color:#fff}
|
||
.layer.closed{color:#5f5f63;
|
||
background:repeating-linear-gradient(45deg,#efeff0,#efeff0 7px,#e0e0e3 7px,#e0e0e3 14px)}
|
||
.layer.closed .tag{background:var(--tk-surface-hl);color:#fff}
|
||
|
||
/* ---------- 互斥示意 ---------- */
|
||
.dead{display:flex;align-items:center;gap:22px}
|
||
.dead .side{flex:1;background:#fff;border:2px solid var(--tk-neutral-subtle);border-radius:var(--tk-r-lg);padding:18px}
|
||
.dead .side h4{margin-bottom:9px}
|
||
.dead .side .req{font-size:13px;line-height:1.55;color:var(--tk-text-add)}
|
||
.dead .side .verdict{margin-top:12px;font-size:13px;font-weight:800;padding:8px 12px;border-radius:var(--tk-r);
|
||
background:var(--tk-surface-subtle)}
|
||
.dead .side.a .verdict{color:var(--tk-magenta-press);background:#fce6f1}
|
||
.dead .side.b .verdict{color:#7a2900;background:#fdeade}
|
||
.dead .x{width:88px;flex-shrink:0;text-align:center}
|
||
.dead .x .sym{font-size:44px;font-weight:800;color:var(--tk-danger);line-height:1}
|
||
.dead .x .lb{font-size:12.5px;font-weight:800;color:var(--tk-danger);margin-top:6px}
|
||
|
||
/* ---------- 成本堆叠条 ---------- */
|
||
.cost{display:flex;height:70px;border-radius:var(--tk-r);overflow:hidden;border:1px solid var(--tk-neutral-subtle)}
|
||
.cost .seg{display:flex;flex-direction:column;align-items:center;justify-content:center;
|
||
border-right:1px solid #fff;padding:0 8px;text-align:center}
|
||
.cost .seg:last-child{border-right:none}
|
||
.cost .seg .p{font-size:19px;font-weight:800;line-height:1}
|
||
.cost .seg .t{font-size:11.5px;line-height:1.3;margin-top:6px;font-weight:500}
|
||
.cost .s1{background:var(--tk-surface-subtle);color:var(--tk-text-add)}
|
||
.cost .s2{background:var(--tk-success-subtle);color:#04543a}
|
||
.cost .s3{background:#fce6f1;color:var(--tk-magenta-press)}
|
||
.cost .s4{background:#f9d5e7;color:var(--tk-magenta-press)}
|
||
.costlegend{display:flex;gap:26px;margin-top:16px;font-size:13px}
|
||
.costlegend div{display:flex;align-items:center;gap:8px;color:var(--tk-text-add)}
|
||
.costlegend .ar{font-size:15px;font-weight:800}
|
||
.costlegend .up{color:var(--tk-magenta)}
|
||
.costlegend .dn{color:var(--tk-success)}
|
||
|
||
/* ---------- 案例图片 ---------- */
|
||
.case{display:flex;gap:30px;align-items:stretch;flex:1;min-height:0}
|
||
.case .txt{flex:1;display:flex;flex-direction:column;justify-content:center;min-width:0}
|
||
.case .shots{display:flex;gap:12px;align-items:center;flex-shrink:0}
|
||
.shot{width:150px;border-radius:12px;overflow:hidden;border:1px solid var(--tk-neutral-subtle);
|
||
box-shadow:var(--tk-sh-raised);background:#fff}
|
||
.shot img{width:100%;display:block}
|
||
.shot .cap{font-size:10.5px;line-height:1.35;padding:7px 8px;color:var(--tk-text-add);
|
||
background:#fff;text-align:center;font-weight:500}
|
||
|
||
/* ---------- 证据分级 A / B / C ---------- */
|
||
.grade{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;
|
||
border-radius:var(--tk-r-sm);font-size:11.5px;font-weight:800;flex-shrink:0}
|
||
.grade.a{background:var(--tk-magenta);color:#fff}
|
||
.grade.b{background:var(--tk-info);color:#fff}
|
||
.grade.c{background:var(--tk-neutral);color:#fff}
|
||
.legend{display:flex;gap:18px;font-size:12.5px;color:var(--tk-text-add);align-items:center;flex-wrap:wrap}
|
||
.legend span{display:flex;align-items:center;gap:7px}
|
||
|
||
/* ---------- 缩略图总览 ---------- */
|
||
#deck.grid-mode{--ts:.18;display:grid;grid-template-columns:repeat(5,1fr);gap:13px;padding:22px;
|
||
overflow-y:auto;background:var(--tk-surface-subtle);align-content:start}
|
||
#deck.grid-mode .holder{position:relative;inset:auto;aspect-ratio:16/9;overflow:hidden;cursor:pointer;
|
||
border-radius:6px;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.14);
|
||
outline:2px solid transparent;transition:outline-color .12s}
|
||
#deck.grid-mode .holder:hover{outline-color:var(--tk-neutral)}
|
||
#deck.grid-mode .holder.cur{outline-color:var(--tk-magenta)}
|
||
#deck.grid-mode .holder::after{content:attr(data-n);position:absolute;right:4px;bottom:3px;z-index:3;
|
||
font-size:9px;font-weight:800;color:var(--tk-text-dis);background:rgba(255,255,255,.9);
|
||
padding:1px 4px;border-radius:3px}
|
||
#deck.grid-mode .slide{width:1280px;height:720px;inset:auto;top:0;left:0;opacity:1;transform:scale(var(--ts));
|
||
transform-origin:0 0;pointer-events:none;transition:none}
|
||
#deck.grid-mode::-webkit-scrollbar{width:9px}
|
||
#deck.grid-mode::-webkit-scrollbar-thumb{background:var(--tk-neutral);border-radius:5px}
|
||
|
||
/* ---------- 提示 ---------- */
|
||
#hint{position:absolute;left:50%;bottom:22px;transform:translateX(-50%);z-index:30;
|
||
background:rgba(36,36,38,.9);color:#fff;font-size:12px;font-weight:500;padding:8px 18px;
|
||
border-radius:var(--tk-r-pill);white-space:nowrap;opacity:0;transition:opacity .4s;pointer-events:none}
|
||
#hint.on{opacity:1}
|
||
#hint b{color:#f61488;font-weight:800}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div id="viewport">
|
||
<div id="stage">
|
||
<div id="deck">
|
||
|
||
<!-- ══════════════ 1 · 封面 ══════════════ -->
|
||
<div class="holder"><section class="slide cover" data-ch="">
|
||
<div class="kicker">技术方案说明</div>
|
||
<h1>App 技术栈介绍</h1>
|
||
<p class="lead" style="margin-top:26px;max-width:760px">Flutter 与 uni-app / uni-app x 的技术路线、生态与落地情况</p>
|
||
<div class="foot">
|
||
<div class="co">T<span>-</span>Systems</div>
|
||
<div style="font-size:13px;color:var(--tk-text-dis);text-align:right;line-height:1.6">2026 年 8 月</div>
|
||
</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 2 · 目录 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="目录">
|
||
<div class="hd"><div class="eyebrow">目录</div><h2>本次介绍分五个部分</h2></div>
|
||
<div class="bd">
|
||
<table class="compact">
|
||
<tbody>
|
||
<tr><td class="k" style="color:var(--tk-magenta);font-size:19px;width:70px">01</td>
|
||
<td style="width:290px"><strong style="font-size:16px">跨端开发的技术路线</strong></td>
|
||
<td class="col-u">市面上的方案归类,四条路线的工作方式,界面由谁绘制</td></tr>
|
||
<tr><td class="k" style="color:var(--tk-magenta);font-size:19px">02</td>
|
||
<td><strong style="font-size:16px">三个框架的定位</strong></td>
|
||
<td class="col-u">Flutter、uni-app、uni-app x 各走哪条路线,各自为谁设计</td></tr>
|
||
<tr><td class="k" style="color:var(--tk-magenta);font-size:19px">03</td>
|
||
<td><strong style="font-size:16px">生态与落地规模</strong></td>
|
||
<td class="col-u">开源程度、社区体量、在用的团队,以及两个整车厂案例</td></tr>
|
||
<tr><td class="k" style="color:var(--tk-magenta);font-size:19px">04</td>
|
||
<td><strong style="font-size:16px">产品能力与工程链路</strong></td>
|
||
<td class="col-u">小组件与系统能力、原生工程的开放程度、构建发版、AI 工具链</td></tr>
|
||
<tr><td class="k" style="color:var(--tk-magenta);font-size:19px">05</td>
|
||
<td><strong style="font-size:16px">结论</strong></td>
|
||
<td class="col-u">成本构成、uni-app x 更优的项、推荐方案</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 3 · 结论摘要 · 推荐 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="结论摘要">
|
||
<div class="hd">
|
||
<div class="eyebrow">结论摘要</div>
|
||
<h2>技术栈推荐 <em>Flutter</em></h2>
|
||
</div>
|
||
<div class="bd">
|
||
<div class="grid g2" style="gap:16px">
|
||
<div class="card hi">
|
||
<div class="n">01</div><h4>界面在两端完全一致</h4>
|
||
<p>Flutter 用自己的渲染引擎绘制界面,不依赖系统控件,也不依赖各机型的浏览器内核。同一份设计稿在 iOS 和 Android 上呈现一致,不需要为两端分别调样式。</p>
|
||
</div>
|
||
<div class="card hi">
|
||
<div class="n">02</div><h4>系统能力没有额外限制</h4>
|
||
<p>原生侧就是标准的 Xcode 与 Android Studio 工程。桌面小组件、Siri、系统级推送这类能力按常规原生方式实现,不需要再学一套框架方的插件规则。</p>
|
||
</div>
|
||
<div class="card">
|
||
<div class="n">03</div><h4>大型 App 上有充分先例</h4>
|
||
<p>BMW、大众汽车(安徽)、字节跳动、阿里巴巴、腾讯、美团、京东、贝壳、携程都在核心业务中使用。引擎与框架代码全部开源。</p>
|
||
</div>
|
||
<div class="card">
|
||
<div class="n">04</div><h4>技术栈稳定</h4>
|
||
<p>Flutter 自 2015 年起是同一条技术主线,没有发生过技术栈更替。uni-app 系历经 5+ App → nvue → uni-app x 两次更替,前两代已于 2024 年停止官方维护。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="note">uni-app 与 uni-app x 是成熟且被广泛使用的框架,本次不做优劣评判,只对照两者与本项目形态的匹配程度。uni-app x 更优的三项在第 5 章列出。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 4 · 结论摘要 · 三方对照 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="结论摘要">
|
||
<div class="hd"><div class="eyebrow">结论摘要</div><h2>三个框架的核心对照</h2></div>
|
||
<div class="bd">
|
||
<table class="compact">
|
||
<thead><tr><th style="width:16%">维度</th><th style="width:29%">Flutter</th><th style="width:26%">uni-app</th><th>uni-app x</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="k">界面由谁绘制</td><td class="col-f mark">框架自带的渲染引擎</td><td class="col-u">机型自带的浏览器内核</td><td class="col-u">系统原生控件</td></tr>
|
||
<tr><td class="k">两端一致性</td><td class="col-f">逐像素一致</td><td class="col-u">受各机型内核版本影响</td><td class="col-u">用系统控件,两端有差异</td></tr>
|
||
<tr><td class="k">主要面向</td><td class="col-f">独立 App</td><td class="col-u">小程序为主、App 为辅</td><td class="col-u">已有小程序团队转做 App</td></tr>
|
||
<tr><td class="k">嵌入 F6 网页</td><td class="col-f mark">通道由 App 侧建立,F6 无需改动</td><td class="col-u">同左</td><td class="col-u">需 F6 引入 DCloud 的 JS 文件</td></tr>
|
||
<tr><td class="k">原生能力开发</td><td class="col-f">强类型代码生成,支持命令行</td><td class="col-u">原生插件已停止新增上架</td><td class="col-u">只能经 HBuilderX,不支持命令行</td></tr>
|
||
<tr><td class="k">开源程度</td><td class="col-f mark">引擎 + 框架全开源(BSD)</td><td class="col-u">框架层开源(Apache-2.0)</td><td class="col-u">框架层开源,Runtime / SDK 闭源</td></tr>
|
||
<tr><td class="k">大型 App 案例</td><td class="col-f">字节 90+ 款、闲鱼、美团、京东、贝壳、携程、BMW、大众安徽 ID. UNYX</td><td class="col-u">—</td><td class="col-u">未检索到同等量级公开案例</td></tr>
|
||
<tr><td class="k">鸿蒙</td><td class="col-u">社区分支,3.41 已发布</td><td class="col-u">支持</td><td style="color:var(--tk-success);font-weight:700">一等公民 ✓</td></tr>
|
||
<tr><td class="k">安装包与内存</td><td class="col-u">约多 10 MB / 35 MB</td><td class="col-u">—</td><td style="color:var(--tk-success);font-weight:700">更小 ✓</td></tr>
|
||
<tr><td class="k">首版投入</td><td class="col-f">基准</td><td class="col-u">—</td><td class="col-u">差异在 ±10% 以内</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="note">最后三行是 uni-app x 占优的项。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 5 · 分隔 01 ══════════════ -->
|
||
<div class="holder"><section class="slide divider" data-ch="">
|
||
<div class="no">01</div>
|
||
<h2>跨端开发的技术路线</h2>
|
||
<div class="pts"><span>市面上的方案</span><span>四条路线的工作方式</span><span>界面由谁绘制</span></div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 6 · 1.1 市面上的方案 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 1 章 · 技术路线">
|
||
<div class="hd">
|
||
<div class="eyebrow">1.1 方案总览</div>
|
||
<h2>市面上的方案,按底层原理只有四类</h2>
|
||
<p class="sub">框架名字很多,但决定 App 能力和体验的是底层怎么把界面画到屏幕上。按这一点归类,选择面并不大。</p>
|
||
</div>
|
||
<div class="bd">
|
||
<table class="compact">
|
||
<thead><tr><th style="width:15%">路线</th><th style="width:51%">代表方案</th><th>什么情况下选它</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td class="k"><span style="color:var(--tk-text-dis);font-weight:800">①</span> 原生双端</td>
|
||
<td><div class="chips"><span class="chip n">Swift / SwiftUI</span><span class="chip n">Kotlin / Compose</span><span class="chip n">Kotlin Multiplatform</span></div></td>
|
||
<td class="col-u">能力上限优先,接受两端各写一套 UI</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="k"><span style="color:var(--tk-text-dis);font-weight:800">②</span> 网页套壳</td>
|
||
<td><div class="chips"><span class="chip">Cordova</span><span class="chip">Capacitor</span><span class="chip">uni-app</span><span class="chip">Taro</span><span class="chip d">5+ App / nvue</span></div></td>
|
||
<td class="col-u">小程序 / H5 是主要入口,App 是补充</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="k"><span style="color:var(--tk-text-dis);font-weight:800">③</span> 编译到系统控件</td>
|
||
<td><div class="chips"><span class="chip">React Native</span><span class="chip">uni-app x</span><span class="chip">Kuikly(腾讯)</span><span class="chip">.NET MAUI</span></div></td>
|
||
<td class="col-u">框架的翻译表能覆盖住需求范围</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="k"><span style="color:var(--tk-magenta);font-weight:800">④</span> 自绘引擎</td>
|
||
<td><div class="chips"><span class="chip f">Flutter</span></div></td>
|
||
<td class="col-f">要做两端一致、长期迭代的独立 App</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="note"><b>本次不展开的几个:</b>React Native 的前提是团队已有 React 技术栈;Taro、Cordova、Capacitor 的重心仍是网页路线;Kotlin Multiplatform 只共享业务逻辑,界面仍由两端各自实现,严格说不属于跨端 UI 方案;Kuikly 是腾讯 2025 年开源的自研框架,目前主要在腾讯内部使用,对外生态尚早。灰色的 5+ App / nvue 是 DCloud 的上一代方案,已停止维护。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 7 · 1.2 四条路线图示 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 1 章 · 技术路线">
|
||
<div class="hd">
|
||
<div class="eyebrow">1.2 工作方式</div>
|
||
<h2>四条路线,从代码到屏幕</h2>
|
||
</div>
|
||
<div class="bd">
|
||
<div class="stacks">
|
||
<div class="stk">
|
||
<div class="sh"><span class="no">路线 ①</span><span class="nm">原生双端</span></div>
|
||
<div class="sb">
|
||
<div class="lyr code"><span class="t">代码</span>Swift + Kotlin<br>两套</div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">无中间层<br><span style="font-size:11px">直接调用系统 API</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr draw"><span class="t">界面由谁绘制</span>系统控件</div>
|
||
</div>
|
||
<div class="sf">系统给什么就能用什么,能力上限最高;两端各写一遍。</div>
|
||
</div>
|
||
<div class="stk">
|
||
<div class="sh"><span class="no">路线 ②</span><span class="nm">网页套壳</span></div>
|
||
<div class="sb">
|
||
<div class="lyr code"><span class="t">代码</span>HTML / CSS / JS<br>一套</div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">WebView<br><span style="font-size:11px">(机型自带的浏览器)</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr draw"><span class="t">界面由谁绘制</span>浏览器内核</div>
|
||
</div>
|
||
<div class="sf">开发最快;界面随机型内核版本变化,系统能力要靠插件从网页环境打通。</div>
|
||
</div>
|
||
<div class="stk">
|
||
<div class="sh"><span class="no">路线 ③</span><span class="nm">编译到系统控件</span></div>
|
||
<div class="sb">
|
||
<div class="lyr code"><span class="t">代码</span>JS / UTS<br>一套</div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">框架桥接层<br><span style="font-size:11px">(翻译成原生控件)</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr draw"><span class="t">界面由谁绘制</span>系统控件</div>
|
||
</div>
|
||
<div class="sf">代码经框架的翻译表转成 Kotlin / Swift;表里没有的能力要自己补齐。</div>
|
||
</div>
|
||
<div class="stk on">
|
||
<div class="sh"><span class="no">路线 ④</span><span class="nm">自绘引擎</span></div>
|
||
<div class="sb">
|
||
<div class="lyr code"><span class="t">代码</span>Dart<br>一套</div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">Flutter 渲染引擎<br><span style="font-size:11px">(自带一块画布)</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr draw"><span class="t">界面由谁绘制</span>引擎自己绘制</div>
|
||
</div>
|
||
<div class="sf">界面自己画,不受机型影响;系统能力经通道调用两端的原生代码。</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="note">路线 ② 与 ③ 的界面最终都交给别人来画:一个交给浏览器内核,一个交给系统控件。路线 ④ 把这一层拿回到框架自己手里。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 8 · 1.3 界面由谁绘制的影响 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 1 章 · 技术路线">
|
||
<div class="hd">
|
||
<div class="eyebrow">1.3 影响</div>
|
||
<h2>"界面由谁绘制"影响三件事</h2>
|
||
</div>
|
||
<div class="bd">
|
||
<table class="compact">
|
||
<thead><tr><th style="width:19%"></th><th style="width:20%">① 原生双端</th><th style="width:20%">② 网页套壳</th><th style="width:20%">③ 编译到系统控件</th><th>④ 自绘引擎</th></tr></thead>
|
||
<tbody>
|
||
<tr>
|
||
<td class="k">两端界面一致性</td>
|
||
<td class="col-u">两套代码各自维护</td>
|
||
<td class="col-u">受机型内核版本影响</td>
|
||
<td class="col-u">控件本身两端有差异</td>
|
||
<td class="col-f mark">逐像素一致</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="k">做一个设计稿上的特殊效果</td>
|
||
<td class="col-u">自己实现,无限制</td>
|
||
<td class="col-u">用 CSS 实现,复杂动效性能受限</td>
|
||
<td class="col-u">受系统控件能力限制</td>
|
||
<td class="col-f mark">自己实现,无限制</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="k">用一个新的系统能力</td>
|
||
<td class="col-u">自己写</td>
|
||
<td class="col-u">写原生插件</td>
|
||
<td class="col-u">按框架方的插件规则写</td>
|
||
<td class="col-f mark">写插件,原生工程完全开放</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="grid g2" style="margin-top:20px;gap:18px">
|
||
<div class="card">
|
||
<h4>本项目的形态</h4>
|
||
<p>要嵌入 F6 的网页业务、要用到扫码、定位、推送、相机、蓝牙等一批系统能力,同时要在两端保持一致的品牌体验。</p>
|
||
</div>
|
||
<div class="card hi">
|
||
<h4>对应到上表</h4>
|
||
<p>一致性和系统能力这两列同时重要。路线 ④ 在这两列上都不受第三方约束,这是选择 Flutter 的技术出发点。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 9 · 分隔 02 ══════════════ -->
|
||
<div class="holder"><section class="slide divider" data-ch="">
|
||
<div class="no">02</div>
|
||
<h2>三个框架的定位</h2>
|
||
<div class="pts"><span>各走哪条路线</span><span>为谁设计</span><span>适用场景</span></div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 10 · 2.1 三者的技术路线 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 2 章 · 框架定位">
|
||
<div class="hd">
|
||
<div class="eyebrow">2.1 技术路线</div>
|
||
<h2>三个框架各走哪条路线</h2>
|
||
</div>
|
||
<div class="bd">
|
||
<div class="stacks">
|
||
<div class="stk on">
|
||
<div class="sh"><span class="no">路线 ④ · 自绘引擎</span><span class="nm">Flutter</span></div>
|
||
<div class="sb four">
|
||
<div class="lyr code"><span class="t">开发语言</span>Dart</div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">编译为机器码<br><span style="font-size:11px">AOT,无 JS 解释器</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">Flutter 渲染引擎<br><span style="font-size:11px">Impeller / Skia</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr draw"><span class="t">界面</span>引擎自己绘制</div>
|
||
</div>
|
||
<div class="sf">Google 主导,2015 年开源。引擎与框架代码全部公开(BSD-3-Clause)。</div>
|
||
</div>
|
||
<div class="stk">
|
||
<div class="sh"><span class="no">路线 ② · 网页套壳</span><span class="nm">uni-app</span></div>
|
||
<div class="sb four">
|
||
<div class="lyr code"><span class="t">开发语言</span>Vue / JavaScript</div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">编译为小程序 / H5 / App<br><span style="font-size:11px">一套代码多端发布</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">App 端:WebView<br><span style="font-size:11px">或已停维护的 nvue</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr draw"><span class="t">界面</span>浏览器内核</div>
|
||
</div>
|
||
<div class="sf">DCloud 出品,2018 年开源。核心场景是同时发布小程序、H5 与 App。</div>
|
||
</div>
|
||
<div class="stk">
|
||
<div class="sh"><span class="no">路线 ③ · 编译到系统控件</span><span class="nm">uni-app x</span></div>
|
||
<div class="sb four">
|
||
<div class="lyr code"><span class="t">开发语言</span>UTS / UVue</div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">编译为 Kotlin / Swift<br><span style="font-size:11px">保留 Vue 写法</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr mid">DCloud Runtime<br><span style="font-size:11px">闭源</span></div>
|
||
<div class="dn">↓</div>
|
||
<div class="lyr draw"><span class="t">界面</span>系统控件</div>
|
||
</div>
|
||
<div class="sf">DCloud 的新一代方案,让已有的 Vue / 小程序团队用熟悉的写法产出原生 App。</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="note">uni-app x 与 uni-app 是两套独立的技术栈,语言、运行时和渲染方式都不同,不是版本升级关系。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 11 · 2.2 为谁设计 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 2 章 · 框架定位">
|
||
<div class="hd">
|
||
<div class="eyebrow">2.2 设计目标</div>
|
||
<h2>三个框架各自为谁设计</h2>
|
||
</div>
|
||
<div class="bd">
|
||
<table class="compact">
|
||
<thead><tr><th style="width:17%"></th><th style="width:28%">Flutter</th><th style="width:27%">uni-app</th><th>uni-app x</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="k">最初要解决的问题</td><td class="col-f">一套代码做出体验接近原生的独立 App</td><td class="col-u">一套 Vue 代码同时发布小程序、H5 与 App</td><td class="col-u">让 Vue / 小程序团队产出真正的原生 App</td></tr>
|
||
<tr><td class="k">典型使用者</td><td class="col-f">已有原生团队,要统一两端体验</td><td class="col-u">以小程序为主要渠道的业务</td><td class="col-u">熟悉 Vue、希望不换语言做 App 的团队</td></tr>
|
||
<tr><td class="k">最擅长的场景</td><td class="col-f">界面复杂、系统能力多、长期迭代的主力 App</td><td class="col-u">多渠道同时上线、以内容和交易页面为主</td><td class="col-u">交互不复杂、以页面和接口为主的 App</td></tr>
|
||
<tr><td class="k">发布渠道</td><td class="col-f">iOS、Android(鸿蒙有社区分支)</td><td class="col-u">各家小程序、H5、iOS、Android、鸿蒙</td><td class="col-u">iOS、Android、鸿蒙、部分小程序</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="grid g2" style="margin-top:20px;gap:18px">
|
||
<div class="card">
|
||
<h4>本项目的渠道范围</h4>
|
||
<p>首版只发 iOS 与 Android 两个应用商店,不发小程序,也不需要 H5 版本。</p>
|
||
</div>
|
||
<div class="card">
|
||
<h4>已确定的能力清单</h4>
|
||
<p>扫码、定位、推送、相机、蓝牙、文件、车牌与 VIN 识别等系统能力,以及对 F6 网页业务的嵌入与双向通信。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 12 · 分隔 03 ══════════════ -->
|
||
<div class="holder"><section class="slide divider" data-ch="">
|
||
<div class="no">03</div>
|
||
<h2>生态与落地规模</h2>
|
||
<div class="pts"><span>开源程度</span><span>社区体量</span><span>谁在用</span><span>两个整车厂案例</span></div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 13 · 3.1 开源程度与社区体量 ══════════════ -->
|
||
<div class="holder"><section class="slide dense" data-ch="第 3 章 · 生态与落地规模">
|
||
<div class="hd">
|
||
<div class="eyebrow">3.1 开源程度与社区体量</div>
|
||
<h2>代码开到哪一层,社区有多大</h2>
|
||
</div>
|
||
<div class="bd">
|
||
<div class="layers" style="margin-bottom:22px">
|
||
<div class="lcol">
|
||
<div class="lh">Flutter</div>
|
||
<div class="layer open">业务框架层 <span class="tag">开源</span></div>
|
||
<div class="layer open">渲染引擎 <span class="tag">开源</span></div>
|
||
<div class="layer open">命令行与构建工具 <span class="tag">开源</span></div>
|
||
</div>
|
||
<div class="lcol">
|
||
<div class="lh">uni-app x</div>
|
||
<div class="layer open">业务框架层 <span class="tag">开源</span></div>
|
||
<div class="layer closed">Runtime 与原生 SDK <span class="tag">闭源</span></div>
|
||
<div class="layer closed">HBuilderX 与打包服务 <span class="tag">闭源</span></div>
|
||
</div>
|
||
</div>
|
||
<div class="chart" style="gap:12px">
|
||
<div class="cgroup">
|
||
<div class="ctitle">主仓库提交数<span class="x">约 8.1 倍</span></div>
|
||
<div class="brow"><span class="bl">Flutter</span><div class="bt"><div class="bf f" style="--w:100%"></div></div><span class="bv">91,171</span></div>
|
||
<div class="brow"><span class="bl">uni-app</span><div class="bt"><div class="bf u" style="--w:12.3%"></div></div><span class="bv dim">11,216</span></div>
|
||
</div>
|
||
<div class="cgroup">
|
||
<div class="ctitle">Star 数<span class="x">约 4.3 倍</span></div>
|
||
<div class="brow"><span class="bl">Flutter</span><div class="bt"><div class="bf f" style="--w:100%"></div></div><span class="bv">178,643</span></div>
|
||
<div class="brow"><span class="bl">uni-app</span><div class="bt"><div class="bf u" style="--w:23.3%"></div></div><span class="bv dim">41,600</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="note">GitHub 数据核验于 2026-08-25,取自 <b>flutter/flutter</b> 与 <b>dcloudio/uni-app</b>;Fork 数为 31,002 与 3,709。star 数反映的是全球开源关注度,uni-app 在国内企业与政企项目中的落地量不体现在这项数据里。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 14 · 3.2 谁在用 ══════════════ -->
|
||
<div class="holder"><section class="slide dense" data-ch="第 3 章 · 生态与落地规模">
|
||
<div class="hd">
|
||
<div class="eyebrow">3.2 落地情况</div>
|
||
<h2>哪些团队在主力 App 上用 Flutter</h2>
|
||
<div class="legend" style="margin-top:12px">
|
||
<span><i class="grade a">A</i> Flutter 官方站与 Showcase 收录</span>
|
||
<span><i class="grade b">B</i> 使用方自己公开披露</span>
|
||
<span><i class="grade c">C</i> 第三方对应用安装包的扫描统计</span>
|
||
</div>
|
||
</div>
|
||
<div class="bd">
|
||
<table class="tiny">
|
||
<thead><tr><th style="width:14%">使用方</th><th style="width:44%">App / 业务</th><th style="width:8%">证据</th><th>补充</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="k">字节跳动</td><td class="col-u">90 多款应用:抖音火山版、飞书 Lark、Coze、掘金等</td><td><i class="grade a">A</i></td><td class="col-u">官方开发者故事,2026-06 更新</td></tr>
|
||
<tr><td class="k">阿里巴巴</td><td class="col-u">闲鱼,已推进到线上主链路</td><td><i class="grade a">A</i></td><td class="col-u">并开源了 FlutterBoost 等基建</td></tr>
|
||
<tr><td class="k">腾讯</td><td class="col-u">多个团队将应用完整迁移到 Flutter</td><td><i class="grade a">A</i></td><td class="col-u">官方页:90% 代码多端复用</td></tr>
|
||
<tr><td class="k">贝壳找房</td><td class="col-u">主业务多平台</td><td><i class="grade a">A</i></td><td class="col-u">Showcase 收录,2021-03</td></tr>
|
||
<tr style="background:#fdf2f8"><td class="k" style="color:var(--tk-magenta)">BMW</td><td class="col-f">My BMW App,覆盖 47 个国家</td><td><i class="grade a">A</i></td><td class="col-u">见下一页</td></tr>
|
||
<tr style="background:#fdf2f8"><td class="k" style="color:var(--tk-magenta)">大众汽车(安徽)</td><td class="col-f">ID. UNYX App:购车定制、车联车控、社区、商城</td><td><i class="grade b">B</i></td><td class="col-u">见下下页</td></tr>
|
||
<tr><td class="k">美团</td><td class="col-u">外卖商家端 App,覆盖 90% 以上业务</td><td><i class="grade b">B</i></td><td class="col-u">美团技术团队博客,2021-03</td></tr>
|
||
<tr><td class="k">京东</td><td class="col-u">JDFlutter:容器、工具链与组件平台</td><td><i class="grade b">B</i></td><td class="col-u">京东 EMOP 官方文档</td></tr>
|
||
<tr><td class="k">携程</td><td class="col-u">火车票、酒店等十多个核心业务的列表页与主流程</td><td><i class="grade b">B</i></td><td class="col-u">原生 + React Native + Flutter 混合</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="grid g2" style="margin-top:14px;gap:16px">
|
||
<div class="card">
|
||
<h4>落地形态一致</h4>
|
||
<p>这些团队都不是把整个 App 用跨端框架重写,而是<strong>原生工程为主体、Flutter 承载业务页面、系统能力仍走原生</strong>。本项目采用同样的形态。</p>
|
||
</div>
|
||
<div class="card hi">
|
||
<h4>第三方统计 <i class="grade c" style="width:auto;padding:0 6px">C</i></h4>
|
||
<p>Appfigures 对新上架应用的 SDK 扫描:<strong>Flutter 检出率 11.07%,React Native 6.75%</strong>(2024 年 1–10 月;2022 年同项为 10.15% 与 4.73%)。该统计口径不含 uni-app 系,不作对比。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 15 · 3.3 案例 A · BMW ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 3 章 · 生态与落地规模">
|
||
<div class="hd">
|
||
<div class="eyebrow">3.3 案例一 · <i class="grade a" style="width:auto;padding:0 6px">A</i> Flutter 官方 Showcase 收录</div>
|
||
<h2>BMW:My BMW App</h2>
|
||
</div>
|
||
<div class="case">
|
||
<div class="txt">
|
||
<p class="body" style="color:var(--tk-text-add)">BMW 原先 iOS 与 Android 各有一套 BMW Connected / MINI Connected,两端功能与设计逐渐分叉。2018 年启动跨端方案评估,选型时明确排除了基于浏览器的方案,最终选定 Flutter。</p>
|
||
<div class="stats" style="margin:10px 0 8px">
|
||
<div class="stat"><div class="v"><span data-to="47">47</span></div><div class="l">个国家、五大洲<br>双端合一,同时服务 BMW 与 MINI</div></div>
|
||
<div class="stat"><div class="v"><span data-to="96">96</span></div><div class="l">每个构建自动生成、测试<br>并部署的<strong>变体数</strong></div></div>
|
||
<div class="stat"><div class="v"><small>></small><span data-to="10000">10,000</span></div><div class="l">截至 2021-10<br><strong>每个变体</strong>累计构建的版本数</div></div>
|
||
</div>
|
||
<table class="tiny" style="margin-bottom:10px">
|
||
<tbody>
|
||
<tr><td class="k" style="width:22%">2018 年</td><td>确认双端分叉问题,启动跨端方案评估</td></tr>
|
||
<tr><td class="k">2019 年 10 月</td><td>慕尼黑工程会议敲定方案</td></tr>
|
||
<tr style="background:#fdf2f8"><td class="k" style="color:var(--tk-magenta)">2020 年 7 月</td><td><strong>My BMW App 上线,从定案到发布不到一年</strong></td></tr>
|
||
<tr><td class="k">团队结构</td><td>按域拆分:车辆连接与通信抽象层 / 共享应用平台 / 面向客户的功能</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="quote">
|
||
远程车控、实时车况回传、车辆查找、充电桩与地图,全部落在系统 API 和车联网长连接上。<strong>这是一个原生能力密集、且要在多品牌多市场维持同一套体验的 App。</strong>
|
||
</div>
|
||
</div>
|
||
<div class="shots">
|
||
<div class="shot"><img src="assets/bmw3.png" alt="My BMW App"><div class="cap">品牌主页</div></div>
|
||
<div class="shot"><img src="assets/bmw4.png" alt="My BMW 操控"><div class="cap">远程车控 · 电量与续航回传</div></div>
|
||
<div class="shot"><img src="assets/bmw5.png" alt="My BMW 充电"><div class="cap">公共充电 · 实时功率与费用</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="note">配图为 My BMW App 官方商店截图,<b>用于说明该 App 的功能面,技术栈依据是 Flutter 官方 Showcase 页</b>。BMW 集团 Offboard Platform 副总裁 Dr. Nicolai Kraemer 表述:新应用平台建立在三根支柱之上——<b>易用性、安全性与可靠性</b>。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 16 · 3.4 案例 B · ID. UNYX ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 3 章 · 生态与落地规模">
|
||
<div class="hd">
|
||
<div class="eyebrow">3.4 案例二 · <i class="grade b" style="width:auto;padding:0 6px">B</i> 使用方官网合规披露</div>
|
||
<h2>大众汽车(安徽):ID. UNYX App</h2>
|
||
</div>
|
||
<div class="case">
|
||
<div class="txt">
|
||
<p class="body" style="color:var(--tk-text-add)">ID. UNYX 是大众汽车(安徽)面向国内市场的品牌 App,覆盖购车定制、车联车控、社区与商城。</p>
|
||
<div class="quote" style="margin:16px 0">
|
||
大众安徽官网《第三方个人信息共享清单》列明 <strong>Flutter SDK(Google LLC)</strong>,使用情形写作
|
||
<strong>「App 所有的 L1、L2、L3 页面开发语言」</strong>。
|
||
<span class="src">即:一级、二级、三级页面均由 Flutter 实现。</span>
|
||
</div>
|
||
<div class="grid g2" style="gap:14px">
|
||
<div class="card">
|
||
<h4>为什么用这条作证据</h4>
|
||
<p>合规披露是法定义务,内容需与实际使用一致,可信度高于技术文章的转述。</p>
|
||
</div>
|
||
<div class="card">
|
||
<h4>同站另一份说明</h4>
|
||
<p>另列有 React Native SDK,说明该 App 是混合形态,不是纯 Flutter。此处一并注明。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="shots">
|
||
<div class="shot"><img src="assets/vw1.png" alt="ID. UNYX 资讯"><div class="cap">资讯与购车转化</div></div>
|
||
<div class="shot"><img src="assets/vw3.png" alt="ID. UNYX 车型"><div class="cap">车型与在线配置</div></div>
|
||
<div class="shot"><img src="assets/vw4.png" alt="ID. UNYX 车控"><div class="cap">车联车控</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="note">配图取自 ID. UNYX 应用商店官方截图,<b>用于说明该 App 的功能面</b>。与 BMW 一样,这类 App 的主干功能建立在定位、推送与车联网长连接之上。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 17 · 分隔 04 ══════════════ -->
|
||
<div class="holder"><section class="slide divider" data-ch="">
|
||
<div class="no">04</div>
|
||
<h2>产品能力与工程链路</h2>
|
||
<div class="pts"><span>小组件与系统能力</span><span>原生工程的开放程度</span><span>构建与发版</span><span>AI 工具链</span></div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 18 · 4.1 App 能做到哪些形态 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 4 章 · 产品能力与工程链路">
|
||
<div class="hd">
|
||
<div class="eyebrow">4.1 扩展能力</div>
|
||
<h2>App 的形态不止"页面 + 接口"</h2>
|
||
<p class="sub">桌面与锁屏小组件、系统语音助手、系统级多媒体与进度通知、各类 App Extension——这些是后续容易提出来的需求。</p>
|
||
</div>
|
||
<div class="bd">
|
||
<table class="compact">
|
||
<thead><tr><th style="width:22%">能力</th><th style="width:39%">Flutter</th><th>uni-app x</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="k">iOS 桌面小组件</td><td class="col-f">在自己的 Xcode 工程里按标准方式做</td><td class="col-u">无内置 API。需在 Xcode 里做成 <code>.appex</code>,再放进 UTS 插件目录并额外配置</td></tr>
|
||
<tr><td class="k">Siri / App Intents</td><td class="col-f">标准原生实现</td><td class="col-u">官方说明:仅靠 UTS 插件无法实现,建议走离线打包的原生工程</td></tr>
|
||
<tr><td class="k">安卓桌面小组件</td><td class="col-f">标准原生实现</td><td class="col-u">三个 Kotlin 文件 + Manifest 注册 + 三个 XML 布局 + UTS 桥接层</td></tr>
|
||
<tr><td class="k">改一次要重来多少</td><td class="col-f mark">一次增量编译</td><td class="col-u">官方教程:每次修改这些文件后必须重新制作自定义基座</td></tr>
|
||
<tr><td class="k">取资源 ID 的方式</td><td class="col-f">直接引用,编译期检查</td><td class="col-u">不能直接引用 <code>R</code> 类,需在运行时按字符串查找</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="quote" style="margin-top:20px">
|
||
两条路线都做得到,区别在做的方式:<strong>Flutter 侧就是打开 Xcode 和 Android Studio 写原生代码</strong>;uni-app x 侧同样要写原生代码,并且还要额外走一遍 DCloud 的插件与打包规则。
|
||
</div>
|
||
</div>
|
||
<div class="note">uni-app x 侧内容取自 DCloud 官方文档,安卓小组件清单取自社区 2026-05 的一篇 UTS 插件实战教程,<b>后者为社区记录,非官方文档</b>。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 19 · 4.2 打包方式 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 4 章 · 产品能力与工程链路">
|
||
<div class="hd">
|
||
<div class="eyebrow">4.2 打包方式</div>
|
||
<h2>两项官方限制,同时满足不了</h2>
|
||
<p class="sub">uni-app x 有云端打包与离线打包两种方式,部分能力只支持其中一种。以下两条都取自 DCloud 官方文档。</p>
|
||
</div>
|
||
<div class="bd">
|
||
<div class="dead">
|
||
<div class="side a">
|
||
<div class="eyebrow" style="font-size:11.5px;margin-bottom:6px">情形 A</div>
|
||
<h4>要用插件市场上的加密付费插件</h4>
|
||
<div class="req">官方文档:「uts 加密插件只支持云端传统打包,不支持离线打包、也不支持安心打包」</div>
|
||
<div class="verdict">⇒ 只能走云端打包</div>
|
||
</div>
|
||
<div class="x"><div class="sym">✕</div><div class="lb">互斥</div></div>
|
||
<div class="side b">
|
||
<div class="eyebrow" style="font-size:11.5px;margin-bottom:6px;color:var(--tk-warning)">情形 B</div>
|
||
<h4>要做 Siri 快捷指令这类扩展</h4>
|
||
<div class="req">官方建议的实现路径:在离线打包的原生工程里实现 <code>AppShortcutsProvider</code>,再用 UTS 插件衔接业务逻辑</div>
|
||
<div class="verdict">⇒ 要求离线打包</div>
|
||
</div>
|
||
</div>
|
||
<div class="grid g2" style="margin-top:20px;gap:18px">
|
||
<div>
|
||
<h4 style="margin:0 0 8px">而离线打包给到的,是一个围绕闭源二进制的壳工程</h4>
|
||
<table class="compact">
|
||
<thead><tr><th style="width:50%">在这个工程里能改的</th><th>改不了的</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="col-u">包名、版本号、图标、启动图</td><td class="col-u">页面怎么渲染</td></tr>
|
||
<tr><td class="col-u">Gradle 依赖、ABI、SDK 版本</td><td class="col-u">页面容器与生命周期</td></tr>
|
||
<tr><td class="col-u">权限项等清单配置</td><td class="col-u">UTS 与原生之间的通信机制</td></tr>
|
||
<tr><td class="col-u">在壳外面加自己的原生模块</td><td class="col-u">运行时的任何行为</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="card hi" style="align-self:start">
|
||
<h4>Flutter 侧没有这个分支</h4>
|
||
<p>只有一种构建方式,<code>ios/</code> 与 <code>android/</code> 目录就是两个完整的原生工程,源码级可控,能力之间不存在互斥。</p>
|
||
<p style="margin-top:10px">加不加插件、上不上 Siri,都不影响出包方式。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="note">壳工程的核心是 DCloud 预先编译好的一组 AAR,没有源码;自定义的应用类必须继承 <code>DCloudApplication</code>,官方另注明离线 SDK 不支持 Kotlin。另有两条商务约束同样来自官方文档:付费插件的授权绑定唯一的 appid 和包名,更换其一需重新购买;试用版只能用于自定义基座,不能用于正式发布。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 20 · 4.3 构建与发版 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 4 章 · 产品能力与工程链路">
|
||
<div class="hd">
|
||
<div class="eyebrow">4.3 构建与发版</div>
|
||
<h2>发版链路能不能全自动</h2>
|
||
</div>
|
||
<div class="bd">
|
||
<table class="compact">
|
||
<thead><tr><th style="width:24%">环节</th><th style="width:38%">Flutter</th><th>uni-app x</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="k">出包命令</td><td class="col-f"><code>flutter build</code>,命令行完整</td><td class="col-u">原生插件与 UTS 插件的编译需经 HBuilderX</td></tr>
|
||
<tr><td class="k">接入 CI 流水线</td><td class="col-f mark">标准做法,构建机上无人值守</td><td class="col-u">图形化 IDE 环节无法在流水线中自动执行</td></tr>
|
||
<tr><td class="k">多渠道 / 多环境包</td><td class="col-f">同一条命令批量产出</td><td class="col-u">云端打包按次计费,自定义基座需重制</td></tr>
|
||
<tr><td class="k">版本与产物追溯</td><td class="col-f">产物在自己的构建机上,可完整留档</td><td class="col-u">云端打包由 DCloud 服务完成</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="quote" style="margin-top:22px">
|
||
第 3 章 BMW 的数据可以作参照:<strong>每个构建自动生成、测试并部署 96 个变体,每个变体累计构建超过 10,000 个版本。</strong>这个量级的自动化建立在命令行完整、构建可无人值守的基础上。
|
||
</div>
|
||
</div>
|
||
<div class="note">这一栏关心的不是"能不能出包",两者都能出包;而是出包这一步能否放进自动化流水线,由此决定多环境、多渠道版本的发布节奏。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 21 · 4.4 AI 工具链 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 4 章 · 产品能力与工程链路">
|
||
<div class="hd">
|
||
<div class="eyebrow">4.4 AI 工具链</div>
|
||
<h2>AI 编程工具的支持情况</h2>
|
||
<p class="sub">两边都有官方 AI 工具,区别在能装到哪些工具上,以及公开语料的体量。</p>
|
||
</div>
|
||
<div class="bd">
|
||
<table class="compact">
|
||
<thead><tr><th style="width:22%"></th><th style="width:40%">Flutter / Dart</th><th>uni-app x</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="k">官方 skills</td><td class="col-f"><code>flutter/agent-plugins</code>、<code>dart-lang/skills</code>,独立开源仓库</td><td class="col-u">内置在官方 <code>uni-agent</code> 助手中</td></tr>
|
||
<tr><td class="k">官方 MCP 服务</td><td class="col-f">Dart & Flutter MCP server</td><td class="col-u"><code>uni-app-x-mcp</code></td></tr>
|
||
<tr><td class="k">能用在哪些工具上</td><td class="col-f mark">Claude Code、Codex、Cursor 等,装到哪个都行</td><td class="col-u">主要围绕自家生态与少数几个工具</td></tr>
|
||
<tr><td class="k">能否自己扩展</td><td class="col-f">仓库开源,可 fork、可加自己项目的 skill</td><td class="col-u">受限于官方提供</td></tr>
|
||
<tr><td class="k">公开语料体量</td><td class="col-f">Dart / Flutter 近十年的全球公开代码</td><td class="col-u">UTS / UVue 为新语言,语料以中文官方文档为主</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="quote" style="margin-top:22px">
|
||
AI 生成代码的准确率与这门语言在公开代码里的沉淀量相关。<strong>uni-app x 插件市场的付费插件是加密分发的,这部分代码不会进入任何公开语料。</strong>
|
||
</div>
|
||
</div>
|
||
<div class="note">Flutter 的 skills 是任务粒度的,官方已列出十项以上:生成单元测试、生成集成测试、统计测试覆盖率、配置多语言、构建响应式布局、配置声明式路由等,仓库持续新增。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 22 · 分隔 05 ══════════════ -->
|
||
<div class="holder"><section class="slide divider" data-ch="">
|
||
<div class="no">05</div>
|
||
<h2>结论</h2>
|
||
<div class="pts"><span>成本构成</span><span>uni-app x 更优的三项</span><span>推荐方案</span></div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 23 · 5.1 成本构成 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 5 章 · 结论">
|
||
<div class="hd">
|
||
<div class="eyebrow">5.1 成本</div>
|
||
<h2>首版投入的构成</h2>
|
||
<p class="sub">占比区间是基于本项目形态的估算,不同团队会有出入。</p>
|
||
</div>
|
||
<div class="bd">
|
||
<div class="cost">
|
||
<div class="seg s1" style="width:50%"><div class="p">45–55%</div><div class="t">业务逻辑与外围系统<br>集成、联调</div></div>
|
||
<div class="seg s2" style="width:22.5%"><div class="p">20–25%</div><div class="t">UI 实现</div></div>
|
||
<div class="seg s3" style="width:13.75%"><div class="p">10–15%</div><div class="t">原生能力<br>与桥接</div></div>
|
||
<div class="seg s4" style="width:13.75%"><div class="p">10–15%</div><div class="t">工程、CI、发布<br>可观测</div></div>
|
||
</div>
|
||
<div class="costlegend">
|
||
<div><span class="ar">—</span> 业务集成:<strong>无差异</strong>(跟框架无关)</div>
|
||
<div><span class="ar dn">↓</span> UI 实现:<strong>uni-app x 略省</strong>(Vue 熟悉度高)</div>
|
||
<div><span class="ar up">↑</span> 原生桥接:<strong style="color:var(--tk-magenta)">更贵</strong></div>
|
||
<div><span class="ar up">↑</span> 工程 CI:<strong style="color:var(--tk-magenta)">更贵</strong></div>
|
||
</div>
|
||
<div class="grid g2" style="margin-top:30px;gap:18px">
|
||
<div class="card">
|
||
<h4>uni-app x 省在哪里</h4>
|
||
<p>省在 UI 实现,也就是占比第二小的那一块。团队熟悉 Vue,页面写得更快。</p>
|
||
</div>
|
||
<div class="card hi">
|
||
<h4>额外成本在哪里</h4>
|
||
<p>原生桥接与工程 CI 两块更贵。一加一减,<strong>首版总投入的差异在 ±10% 以内</strong>。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="note">占比不含后续运维与迭代。业务逻辑与外围系统的集成、联调是最大的一块,这部分与选哪个框架无关。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 24 · 5.2 uni-app x 更优的项 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 5 章 · 结论">
|
||
<div class="hd">
|
||
<div class="eyebrow">5.2 对方更优的项</div>
|
||
<h2>uni-app x 强于 Flutter 的三项</h2>
|
||
</div>
|
||
<div class="bd">
|
||
<div class="grid g3" style="gap:18px">
|
||
<div class="card ok">
|
||
<div class="n" style="color:#04543a">01</div><h4>鸿蒙是一等公民</h4>
|
||
<p>uni-app x 官方直接支持鸿蒙。Flutter 主线暂未纳入鸿蒙,目前走 OpenHarmony-SIG 的社区分支:3.41 已发布,3.44 预计 2026 年 9 月。</p>
|
||
<p><strong>本项目首版范围是 iOS 与 Android,鸿蒙不在内。</strong></p>
|
||
</div>
|
||
<div class="card ok">
|
||
<div class="n" style="color:#04543a">02</div><h4>安装包与内存更小</h4>
|
||
<p>DCloud 官方实测:安装包 <strong>8.5 MB</strong>(Flutter 约 18 MB),运行内存 <strong>103 MB</strong>(Flutter 约 138 MB)。</p>
|
||
<p>Flutter 需要带自己的渲染引擎,这部分体积无法省去。</p>
|
||
</div>
|
||
<div class="card ok">
|
||
<div class="n" style="color:#04543a">03</div><h4>热更新</h4>
|
||
<p>uni-app 系有成熟的整包与分包热更新方案。Flutter 的 Dart 代码是提前编译的机器码,同类能力受平台规则限制。</p>
|
||
</div>
|
||
</div>
|
||
<div class="quote ok" style="margin-top:26px">
|
||
这三项是真实的技术差异。<strong>是否构成决定性因素,取决于产品对鸿蒙、安装包体积和热更新的实际要求。</strong>
|
||
</div>
|
||
</div>
|
||
<div class="note">uni-app x 的离线打包与嵌入式集成会额外增加体积:iOS 约 8.7 MB,Android arm64-v8a 约 8.1 MB。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 25 · 5.3 推荐方案 ══════════════ -->
|
||
<div class="holder"><section class="slide" data-ch="第 5 章 · 结论">
|
||
<div class="hd"><div class="eyebrow">5.3 推荐</div><h2>推荐方案</h2></div>
|
||
<div class="bd">
|
||
<div class="statement" style="padding:0;margin-bottom:28px">
|
||
<div class="big" style="font-size:34px">技术栈采用 <em>Flutter</em>,原生工程为主体,<br>业务页面由 Flutter 承载,系统能力走原生。</div>
|
||
</div>
|
||
<table class="compact">
|
||
<tbody>
|
||
<tr><td class="k" style="width:22%;color:var(--tk-magenta)">界面一致性</td><td>自绘引擎,不依赖系统控件与浏览器内核,两端与各机型呈现一致</td></tr>
|
||
<tr><td class="k" style="color:var(--tk-magenta)">系统能力</td><td>原生工程完全开放,小组件、Siri、系统级能力按标准原生方式实现,无第二套规则</td></tr>
|
||
<tr><td class="k" style="color:var(--tk-magenta)">工程链路</td><td>命令行完整,可接入 CI 无人值守构建与自动分发</td></tr>
|
||
<tr><td class="k" style="color:var(--tk-magenta)">先例充分</td><td>BMW、大众安徽 ID. UNYX 等同类形态的 App 已在同一条路线上长期运行</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="note">版本基线 Flutter 3.44.9,首版发布 iOS 与 Android 双端。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 26 · 数据来源 ══════════════ -->
|
||
<div class="holder"><section class="slide dense" data-ch="附录">
|
||
<div class="hd"><div class="eyebrow">附录</div><h2>数据与事实来源</h2></div>
|
||
<div class="bd">
|
||
<table class="tiny">
|
||
<thead><tr><th style="width:26%">内容</th><th>来源</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="k">框架技术路线与能力</td><td class="col-u">Flutter 官方文档;DCloud uni-app / uni-app x 官方文档</td></tr>
|
||
<tr><td class="k">开源程度与社区数据</td><td class="col-u">GitHub <code>flutter/flutter</code> 与 <code>dcloudio/uni-app</code>,核验于 2026-08-25</td></tr>
|
||
<tr><td class="k">在用团队清单</td><td class="col-u">Flutter 官方站开发者故事与 Showcase 页;美团技术团队博客;京东 EMOP 官方文档;携程技术公众号</td></tr>
|
||
<tr><td class="k">BMW 案例</td><td class="col-u">Flutter 官方 Showcase 的 BMW 页,含时间线与工程数据;配图为 App 官方商店截图</td></tr>
|
||
<tr><td class="k">ID. UNYX 案例</td><td class="col-u">大众汽车(安徽)官网《第三方个人信息共享清单》与 SDK 权限说明;配图为 App 官方商店截图</td></tr>
|
||
<tr><td class="k">第三方框架分布统计</td><td class="col-u">Appfigures 对应用安装包的 SDK 扫描,2024 年 1–10 月与 2025 年 12 月两次口径</td></tr>
|
||
<tr><td class="k">安装包与内存实测</td><td class="col-u">DCloud 官方发布的性能对比材料</td></tr>
|
||
<tr><td class="k">打包与扩展能力限制</td><td class="col-u">DCloud 官方文档;安卓小组件交付清单取自社区 2026-05 的 UTS 插件实战教程</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="note">证据分级:<b>A</b> 框架方官方收录 · <b>B</b> 使用方自己公开披露 · <b>C</b> 第三方对应用安装包的扫描统计。凡检索不到近年官方更新的条目,均已就地注明。</div>
|
||
</section></div>
|
||
|
||
<!-- ══════════════ 27 · 收尾 ══════════════ -->
|
||
<div class="holder"><section class="slide cover" data-ch="">
|
||
<div class="kicker">谢谢</div>
|
||
<h1 style="font-size:44px">问题与讨论</h1>
|
||
<div class="foot">
|
||
<div class="co">T<span>-</span>Systems</div>
|
||
<div style="font-size:13px;color:var(--tk-text-dis);text-align:right;line-height:1.6">
|
||
App 技术栈介绍 · 2026-08<br>Flutter / uni-app / uni-app x</div>
|
||
</div>
|
||
</section></div>
|
||
|
||
</div><!-- /#deck -->
|
||
|
||
<div id="chrome">
|
||
<div id="brand">T-Systems</div>
|
||
<div id="meta"><span id="ch"></span><span class="num"><span id="cur">1</span> / <span id="tot">27</span></span></div>
|
||
</div>
|
||
<div id="bar"></div>
|
||
|
||
<div id="hint">按 <b>→</b> 翻页 · <b>O</b> 缩略图总览 · <b>F</b> 全屏</div>
|
||
|
||
</div><!-- /#stage -->
|
||
</div><!-- /#viewport -->
|
||
|
||
<script>
|
||
(function(){
|
||
var stage = document.getElementById('stage'),
|
||
deck = document.getElementById('deck'),
|
||
chrome = document.getElementById('chrome'),
|
||
holders = Array.prototype.slice.call(deck.querySelectorAll('.holder')),
|
||
slides = holders.map(function(h){ return h.querySelector('.slide'); }),
|
||
elCur = document.getElementById('cur'),
|
||
elTot = document.getElementById('tot'),
|
||
elCh = document.getElementById('ch'),
|
||
elBar = document.getElementById('bar'),
|
||
elHint = document.getElementById('hint'),
|
||
N = slides.length,
|
||
idx = 0,
|
||
grid = false,
|
||
counted = [];
|
||
|
||
elTot.textContent = N;
|
||
holders.forEach(function(h,i){ h.dataset.n = i + 1; });
|
||
|
||
/* ---------- 等比缩放 ---------- */
|
||
function fit(){
|
||
var s = Math.min(window.innerWidth / 1280, window.innerHeight / 720);
|
||
stage.style.transform = 'scale(' + s + ')';
|
||
// 缩略图缩放比:按 holder 实际宽度算,避免硬编码
|
||
if (grid && holders[0]) {
|
||
var w = holders[0].getBoundingClientRect().width / s;
|
||
deck.style.setProperty('--ts', w / 1280);
|
||
}
|
||
}
|
||
window.addEventListener('resize', fit);
|
||
|
||
/* ---------- 大数字递增 ----------
|
||
HTML 里写的就是最终值,这里从 0 动画回到它。
|
||
所以 JS 不跑 / RAF 不触发 / 用户关了动效,屏幕上留下的都是正确数字,不会是 0。 */
|
||
var noAnim = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||
function countUp(slide){
|
||
if (noAnim) return;
|
||
slide.querySelectorAll('[data-to]').forEach(function(el){
|
||
if (el.dataset.done) return;
|
||
el.dataset.done = '1';
|
||
var to = parseInt(el.dataset.to, 10), t0 = null, dur = 900, done = false;
|
||
var end = to.toLocaleString('en-US');
|
||
function step(ts){
|
||
if (done) return;
|
||
if (t0 === null) t0 = ts;
|
||
var p = Math.min((ts - t0) / dur, 1);
|
||
p = 1 - Math.pow(1 - p, 3); // easeOutCubic
|
||
el.textContent = Math.round(to * p).toLocaleString('en-US');
|
||
if (p < 1) requestAnimationFrame(step);
|
||
}
|
||
el.textContent = '0';
|
||
requestAnimationFrame(step);
|
||
// rAF 在后台标签页会被节流、甚至完全挂起,动画会卡在 9,786 这种半截数上不动。
|
||
// setTimeout 不依赖帧产出,到点写终值并置 done 收掉动画循环,
|
||
// 否则迟到的 rAF 会把数字又改回中间值。这些数字是论据,不能显示错的。
|
||
setTimeout(function(){ done = true; el.textContent = end; }, dur + 80);
|
||
});
|
||
}
|
||
|
||
/* ---------- 翻页 ---------- */
|
||
function show(n){
|
||
idx = Math.max(0, Math.min(N - 1, n));
|
||
slides.forEach(function(s, i){
|
||
s.classList.toggle('active', i === idx);
|
||
s.classList.toggle('before', i < idx);
|
||
});
|
||
var ch = slides[idx].dataset.ch || '';
|
||
elCh.textContent = ch;
|
||
elCur.textContent = idx + 1;
|
||
elBar.style.width = ((idx + 1) / N * 100) + '%';
|
||
elBar.classList.toggle('off', grid);
|
||
chrome.classList.toggle('on', !grid && ch !== '');
|
||
if (!counted[idx]) { counted[idx] = 1; countUp(slides[idx]); }
|
||
// file:// 下个别浏览器会拒绝 replaceState,静默忽略即可
|
||
try { if (history.replaceState) history.replaceState(null, '', '#' + (idx + 1)); } catch (err) {}
|
||
}
|
||
function next(){ if (idx < N - 1) show(idx + 1); }
|
||
function prev(){ if (idx > 0) show(idx - 1); }
|
||
|
||
/* ---------- 缩略图总览 ---------- */
|
||
function setGrid(on){
|
||
grid = on;
|
||
deck.classList.toggle('grid-mode', on);
|
||
chrome.classList.toggle('on', !on && (slides[idx].dataset.ch || '') !== '');
|
||
elBar.classList.toggle('off', on);
|
||
holders.forEach(function(h, i){ h.classList.toggle('cur', on && i === idx); });
|
||
if (on) {
|
||
fit();
|
||
var c = holders[idx];
|
||
if (c) c.scrollIntoView({ block: 'nearest' });
|
||
} else {
|
||
show(idx);
|
||
}
|
||
}
|
||
deck.addEventListener('click', function(e){
|
||
if (!grid) return;
|
||
var h = e.target.closest('.holder');
|
||
if (!h) return;
|
||
e.stopPropagation();
|
||
idx = holders.indexOf(h);
|
||
setGrid(false);
|
||
}, true);
|
||
|
||
/* ---------- 键盘 ---------- */
|
||
document.addEventListener('keydown', function(e){
|
||
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
||
var k = e.key;
|
||
if (grid) {
|
||
if (k === 'Escape' || k === 'o' || k === 'O') { setGrid(false); e.preventDefault(); }
|
||
else if (k === 'ArrowRight'){ idx = Math.min(N-1, idx+1); setGrid(true); e.preventDefault(); }
|
||
else if (k === 'ArrowLeft') { idx = Math.max(0, idx-1); setGrid(true); e.preventDefault(); }
|
||
else if (k === 'ArrowDown') { idx = Math.min(N-1, idx+5); setGrid(true); e.preventDefault(); }
|
||
else if (k === 'ArrowUp') { idx = Math.max(0, idx-5); setGrid(true); e.preventDefault(); }
|
||
else if (k === 'Enter') { setGrid(false); e.preventDefault(); }
|
||
return;
|
||
}
|
||
if (k === 'ArrowRight' || k === 'ArrowDown' || k === ' ' || k === 'PageDown'){ next(); e.preventDefault(); }
|
||
else if (k === 'ArrowLeft' || k === 'ArrowUp' || k === 'PageUp'){ prev(); e.preventDefault(); }
|
||
else if (k === 'Home'){ show(0); e.preventDefault(); }
|
||
else if (k === 'End'){ show(N - 1); e.preventDefault(); }
|
||
else if (k === 'o' || k === 'O'){ setGrid(true); e.preventDefault(); }
|
||
// Escape 不在这里开总览:全屏时 Escape 归浏览器管,抢过来会两件事一起发生
|
||
else if (k === 'f' || k === 'F'){
|
||
if (document.fullscreenElement) document.exitFullscreen();
|
||
else document.documentElement.requestFullscreen();
|
||
e.preventDefault();
|
||
}
|
||
});
|
||
|
||
/* ---------- 点击左右半屏 ---------- */
|
||
stage.addEventListener('click', function(e){
|
||
if (grid) return;
|
||
// 只挡链接和图片;正文表格照样可点翻页,靠下面的选区判断避免打断划词
|
||
if (e.target.closest('a, img')) return;
|
||
if (window.getSelection && String(window.getSelection())) return;
|
||
var r = stage.getBoundingClientRect();
|
||
if (e.clientX - r.left > r.width / 2) next(); else prev();
|
||
});
|
||
|
||
/* ---------- 触摸滑动 ---------- */
|
||
var tx = 0, ty = 0, tt = 0;
|
||
stage.addEventListener('touchstart', function(e){
|
||
var t = e.changedTouches[0]; tx = t.clientX; ty = t.clientY; tt = e.timeStamp;
|
||
}, { passive: true });
|
||
stage.addEventListener('touchend', function(e){
|
||
if (grid) return;
|
||
var t = e.changedTouches[0], dx = t.clientX - tx, dy = t.clientY - ty;
|
||
// 横向位移足够大、纵向位移小、时间短 —— 才算翻页
|
||
if (Math.abs(dx) > 55 && Math.abs(dx) > Math.abs(dy) * 1.8 && e.timeStamp - tt < 700) {
|
||
if (dx < 0) next(); else prev();
|
||
}
|
||
}, { passive: true });
|
||
|
||
/* ---------- 启动 ---------- */
|
||
var start = parseInt((location.hash || '').replace('#',''), 10);
|
||
fit();
|
||
show(isNaN(start) ? 0 : start - 1);
|
||
elHint.classList.add('on');
|
||
setTimeout(function(){ elHint.classList.remove('on'); }, 4200);
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|