portal-home-style-demo.html 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1" />
  6. <title>门户首页启动器 · 文字卡片 Demo v3</title>
  7. <style>
  8. :root {
  9. --primary: #10b981;
  10. --primary-dark: #059669;
  11. --primary-bg: #ecfdf5;
  12. --header: #1ca851;
  13. --text: #1e293b;
  14. --muted: #64748b;
  15. --muted-soft: #94a3b8;
  16. --border: #e2e8f0;
  17. --surface: #ffffff;
  18. --page: #f8fafc;
  19. --shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  20. }
  21. * { box-sizing: border-box; }
  22. body {
  23. margin: 0;
  24. font-family: Inter, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", -apple-system, sans-serif;
  25. color: var(--text);
  26. background: var(--page);
  27. line-height: 1.5;
  28. font-size: 14px;
  29. }
  30. .page { padding: 20px 24px 64px; max-width: 1180px; margin: 0 auto; }
  31. h1 { font-size: 22px; margin: 0 0 6px; font-weight: 700; }
  32. .lead { color: var(--muted); font-size: 14px; margin: 0 0 14px; }
  33. .ok, .note {
  34. border-radius: 10px;
  35. padding: 10px 14px;
  36. font-size: 13px;
  37. margin-bottom: 12px;
  38. }
  39. .ok {
  40. background: var(--primary-bg);
  41. border: 1px solid #a7f3d0;
  42. color: #065f46;
  43. }
  44. .note {
  45. background: #fffbeb;
  46. border: 1px solid #fde68a;
  47. color: #92400e;
  48. }
  49. .tabs {
  50. display: flex;
  51. flex-wrap: wrap;
  52. gap: 8px;
  53. margin: 18px 0 14px;
  54. }
  55. .tabs button {
  56. appearance: none;
  57. border: 1px solid var(--border);
  58. background: #fff;
  59. color: var(--muted);
  60. font-size: 13px;
  61. font-weight: 600;
  62. padding: 8px 14px;
  63. border-radius: 999px;
  64. cursor: pointer;
  65. }
  66. .tabs button:hover { border-color: #a7f3d0; color: var(--primary-dark); }
  67. .tabs button.is-active {
  68. background: var(--primary-bg);
  69. border-color: #6ee7b7;
  70. color: var(--primary-dark);
  71. }
  72. .panel { display: none; }
  73. .panel.is-active { display: block; }
  74. .meta-row {
  75. display: flex;
  76. justify-content: space-between;
  77. align-items: baseline;
  78. gap: 12px;
  79. margin-bottom: 10px;
  80. }
  81. .meta-row h2 { margin: 0; font-size: 15px; }
  82. .meta-row span { font-size: 12px; color: var(--muted); }
  83. .frame {
  84. border: 1px solid var(--border);
  85. border-radius: 12px;
  86. overflow: hidden;
  87. background: var(--page);
  88. box-shadow: var(--shadow);
  89. }
  90. .portal-header {
  91. display: flex;
  92. align-items: center;
  93. gap: 10px;
  94. height: 56px;
  95. padding: 0 16px;
  96. background: var(--header);
  97. color: #fff;
  98. }
  99. .brand {
  100. display: flex;
  101. align-items: center;
  102. gap: 8px;
  103. font-weight: 600;
  104. font-size: 15px;
  105. }
  106. .brand-mark {
  107. width: 26px;
  108. height: 26px;
  109. border-radius: 7px;
  110. background: rgba(255,255,255,0.2);
  111. display: grid;
  112. place-items: center;
  113. font-size: 11px;
  114. font-weight: 700;
  115. }
  116. .spacer { flex: 1; }
  117. .user-chip {
  118. width: 30px;
  119. height: 30px;
  120. border-radius: 999px;
  121. background: rgba(255,255,255,0.22);
  122. display: grid;
  123. place-items: center;
  124. font-size: 12px;
  125. font-weight: 600;
  126. }
  127. .worktab {
  128. display: flex;
  129. align-items: center;
  130. gap: 8px;
  131. min-height: 44px;
  132. padding: 6px 12px;
  133. background: #f1f5f9;
  134. border-bottom: 1px solid var(--border);
  135. }
  136. .tab-chip {
  137. display: inline-flex;
  138. align-items: center;
  139. padding: 6px 12px;
  140. border-radius: 8px;
  141. font-size: 13px;
  142. font-weight: 600;
  143. border: 1px solid #a7f3d0;
  144. background: var(--primary-bg);
  145. color: var(--primary-dark);
  146. }
  147. .tab-chip.muted {
  148. background: #fff;
  149. border-color: var(--border);
  150. color: var(--muted);
  151. font-weight: 500;
  152. }
  153. .workspace { padding: 14px 16px 18px; min-height: 480px; }
  154. /* 卡片区域:与上方问候条拉开,左右再留白 */
  155. .b-cards-area {
  156. margin-top: 4px;
  157. padding: 12px 16px 8px;
  158. }
  159. .legend {
  160. margin-top: 16px;
  161. padding: 12px 14px;
  162. background: #fff;
  163. border: 1px solid var(--border);
  164. border-radius: 10px;
  165. font-size: 13px;
  166. color: var(--muted);
  167. }
  168. .legend ul { margin: 8px 0 0; padding-left: 18px; }
  169. .legend li { margin: 4px 0; }
  170. code {
  171. font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  172. font-size: 12px;
  173. background: #f1f5f9;
  174. padding: 1px 5px;
  175. border-radius: 4px;
  176. }
  177. /* 一级标题:绿色小竖条 */
  178. .title-bar-heading {
  179. position: relative;
  180. margin: 0;
  181. padding-left: 12px;
  182. font-size: 15px;
  183. font-weight: 600;
  184. color: var(--text);
  185. }
  186. .title-bar-heading::before {
  187. content: "";
  188. position: absolute;
  189. left: 0;
  190. top: 50%;
  191. transform: translateY(-50%);
  192. width: 3px;
  193. height: 14px;
  194. border-radius: 2px;
  195. background: var(--primary);
  196. }
  197. /* ========== A 现状 ========== */
  198. .a-section { margin-bottom: 28px; }
  199. .a-grid {
  200. display: grid;
  201. grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  202. gap: 12px;
  203. margin-top: 12px;
  204. }
  205. .a-card {
  206. position: relative;
  207. display: flex;
  208. align-items: center;
  209. min-height: 72px;
  210. padding: 12px 36px 12px 14px;
  211. text-align: left;
  212. cursor: pointer;
  213. background: #fff;
  214. border: 1px solid var(--border);
  215. border-radius: 10px;
  216. }
  217. .a-card:hover {
  218. background: var(--primary-bg);
  219. border-color: #a7f3d0;
  220. }
  221. .a-card__title { font-size: 14px; font-weight: 500; }
  222. .a-star {
  223. position: absolute;
  224. top: 8px;
  225. right: 8px;
  226. border: 0;
  227. background: transparent;
  228. color: var(--muted-soft);
  229. cursor: pointer;
  230. font-size: 14px;
  231. padding: 2px;
  232. line-height: 1;
  233. }
  234. .a-star.is-on { color: #d97706; }
  235. /* ========== B v3:紧凑纯文字 · 竖条标题 ========== */
  236. .b-hero {
  237. display: flex;
  238. align-items: center;
  239. justify-content: space-between;
  240. gap: 12px;
  241. margin-bottom: 4px;
  242. padding: 12px 14px;
  243. border-radius: 10px;
  244. background:
  245. radial-gradient(100% 120% at 100% 0%, rgba(16, 185, 129, 0.14), transparent 55%),
  246. linear-gradient(135deg, #ecfdf5 0%, #ffffff 60%, #f8fafc 100%);
  247. border: 1px solid #bbf7d0;
  248. }
  249. .b-hero h3 {
  250. margin: 0 0 2px;
  251. font-size: 16px;
  252. font-weight: 700;
  253. }
  254. .b-hero p {
  255. margin: 0;
  256. font-size: 12px;
  257. color: var(--muted);
  258. }
  259. .b-hero__chip {
  260. display: inline-flex;
  261. align-items: center;
  262. padding: 3px 9px;
  263. border-radius: 999px;
  264. background: #fff;
  265. border: 1px solid #a7f3d0;
  266. color: var(--primary-dark);
  267. font-weight: 600;
  268. font-size: 11px;
  269. white-space: nowrap;
  270. }
  271. .b-section { margin-bottom: 22px; }
  272. .b-section:last-child { margin-bottom: 4px; }
  273. .b-grid {
  274. display: grid;
  275. grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
  276. gap: 8px;
  277. margin-top: 12px;
  278. }
  279. .b-card {
  280. position: relative;
  281. display: flex;
  282. align-items: center;
  283. justify-content: center;
  284. min-height: 56px;
  285. padding: 12px 22px 12px 10px;
  286. text-align: center;
  287. cursor: pointer;
  288. background: #fff;
  289. border: 1px solid var(--border);
  290. border-radius: 10px;
  291. transition:
  292. transform .15s ease,
  293. box-shadow .15s ease,
  294. border-color .15s ease,
  295. background .15s ease;
  296. }
  297. .b-card:hover {
  298. transform: translateY(-1px);
  299. border-color: #6ee7b7;
  300. background: var(--primary-bg);
  301. box-shadow: 0 4px 12px rgba(16, 185, 129, 0.1);
  302. }
  303. .b-card:active { transform: translateY(0); }
  304. .b-card__title {
  305. width: 100%;
  306. font-size: 13px;
  307. font-weight: 600;
  308. color: var(--text);
  309. line-height: 1.35;
  310. text-align: center;
  311. display: -webkit-box;
  312. -webkit-line-clamp: 2;
  313. -webkit-box-orient: vertical;
  314. overflow: hidden;
  315. word-break: break-all;
  316. }
  317. .b-star {
  318. position: absolute;
  319. top: 4px;
  320. right: 4px;
  321. width: 22px;
  322. height: 22px;
  323. border: 0;
  324. border-radius: 6px;
  325. background: transparent;
  326. color: var(--muted-soft);
  327. cursor: pointer;
  328. display: grid;
  329. place-items: center;
  330. opacity: 0.55;
  331. transition: background .15s, color .15s, opacity .15s;
  332. }
  333. .b-card:hover .b-star,
  334. .b-star.is-on { opacity: 1; }
  335. .b-star:hover { background: #f1f5f9; color: #d97706; }
  336. .b-star.is-on { color: #d97706; }
  337. .b-star svg { width: 13px; height: 13px; }
  338. </style>
  339. </head>
  340. <body>
  341. <div class="page">
  342. <h1>门户首页启动器 · 文字卡片 Demo v3</h1>
  343. <p class="lead">
  344. 一级标题仅绿色竖条;菜单卡片<strong>无图标</strong>,纯文字且左右居中;整体保持紧凑。
  345. </p>
  346. <div class="ok">
  347. 推荐 <strong>B · v3</strong>。交互不变:点卡片开 Tab,点星收藏。
  348. </div>
  349. <div class="note">
  350. 相对 v2:去掉卡片 icon / 一级分区徽章面板;标题回归 <code>title-bar-heading</code> 竖条。
  351. </div>
  352. <div class="tabs" role="tablist">
  353. <button type="button" data-tab="a">A · 现状</button>
  354. <button type="button" class="is-active" data-tab="b">B · v3 文字居中(推荐)</button>
  355. </div>
  356. <section class="panel" id="panel-a">
  357. <div class="meta-row">
  358. <h2>方案 A · 现状</h2>
  359. <span>左对齐文字扁卡</span>
  360. </div>
  361. <div class="frame">
  362. <div class="portal-header">
  363. <div class="brand"><span class="brand-mark">峡</span> 山峡化工一体化门户</div>
  364. <div class="spacer"></div>
  365. <div class="user-chip">管</div>
  366. </div>
  367. <div class="worktab">
  368. <span class="tab-chip">首页</span>
  369. <span class="tab-chip muted">客户管理 ×</span>
  370. </div>
  371. <div class="workspace" id="mount-a"></div>
  372. </div>
  373. </section>
  374. <section class="panel is-active" id="panel-b">
  375. <div class="meta-row">
  376. <h2>方案 B · v3</h2>
  377. <span>竖条标题 · 纯文字居中卡 · 紧凑</span>
  378. </div>
  379. <div class="frame">
  380. <div class="portal-header">
  381. <div class="brand"><span class="brand-mark">峡</span> 山峡化工一体化门户</div>
  382. <div class="spacer"></div>
  383. <div class="user-chip">管</div>
  384. </div>
  385. <div class="worktab">
  386. <span class="tab-chip">首页</span>
  387. <span class="tab-chip muted">客户管理 ×</span>
  388. </div>
  389. <div class="workspace" id="mount-b"></div>
  390. </div>
  391. <div class="legend">
  392. <strong>本版约定</strong>
  393. <ul>
  394. <li>一级 / 收藏标题:<code>h2.title-bar-heading</code> + 绿色竖条(宽 3px / 高 14px / <code>#10b981</code>)。</li>
  395. <li>卡片:仅文字,无 icon;<code>text-align: center</code> + 垂直居中;高约 56px。</li>
  396. <li>网格:<code>minmax(112px)</code>、gap 8px;星标右上角保留。</li>
  397. <li>卡片区 <code>.b-cards-area</code>:与问候条之间留空,左右 <code>padding: 12px 16px</code>,避免贴边。</li>
  398. </ul>
  399. </div>
  400. </section>
  401. </div>
  402. <script>
  403. const STAR = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.5l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.8 6.2 20.4l1.1-6.5L2.6 9.3l6.5-.9L12 2.5z"/></svg>';
  404. const STAR_OUT = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3l2.6 5.3 5.9.9-4.3 4.2 1 5.8L12 16.8 6.8 19.2l1-5.8L3.5 9.2l5.9-.9L12 3z"/></svg>';
  405. const FAVORITES = ['客户管理', '销售订单', '地磅看板'];
  406. const SECTIONS = [
  407. {
  408. title: 'CRM 管理',
  409. cards: ['客户管理', '联系人', '产品管理', '销售订单', '合同管理']
  410. },
  411. {
  412. title: '报销管理',
  413. cards: ['报销申请', '报销审批', '费用类型']
  414. },
  415. {
  416. title: '地磅管理',
  417. cards: ['地磅看板', '称重记录', '车辆档案']
  418. },
  419. {
  420. title: '系统管理',
  421. cards: ['用户管理', '角色权限', '组织架构', '菜单管理']
  422. }
  423. ];
  424. function starBtn(on, cls) {
  425. return `<button type="button" class="${cls}${on ? ' is-on' : ''}" aria-label="${on ? '取消收藏' : '收藏'}">${on ? STAR : STAR_OUT}</button>`;
  426. }
  427. function isFav(title) {
  428. return FAVORITES.includes(title);
  429. }
  430. function renderA(root) {
  431. let html = '';
  432. if (FAVORITES.length) {
  433. html += `<section class="a-section"><h2 class="title-bar-heading">收藏</h2><div class="a-grid">`;
  434. FAVORITES.forEach((t) => {
  435. html += `<button type="button" class="a-card"><span class="a-card__title">${t}</span>${starBtn(true, 'a-star')}</button>`;
  436. });
  437. html += `</div></section>`;
  438. }
  439. SECTIONS.forEach((sec) => {
  440. html += `<section class="a-section"><h2 class="title-bar-heading">${sec.title}</h2><div class="a-grid">`;
  441. sec.cards.forEach((t) => {
  442. html += `<button type="button" class="a-card"><span class="a-card__title">${t}</span>${starBtn(isFav(t), 'a-star')}</button>`;
  443. });
  444. html += `</div></section>`;
  445. });
  446. root.innerHTML = html;
  447. }
  448. function cardB(title, fav) {
  449. return `<button type="button" class="b-card">
  450. <span class="b-card__title">${title}</span>
  451. ${starBtn(fav, 'b-star')}
  452. </button>`;
  453. }
  454. function renderB(root) {
  455. const now = new Date();
  456. const h = now.getHours();
  457. const greet = h < 12 ? '上午好' : h < 18 ? '下午好' : '晚上好';
  458. const date = now.toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'short' });
  459. let html = `
  460. <div class="b-hero">
  461. <div>
  462. <h3>${greet},管理员</h3>
  463. <p>${date} · 点击卡片进入业务,星标加入收藏</p>
  464. </div>
  465. <span class="b-hero__chip">菜单启动器</span>
  466. </div>
  467. `;
  468. html += `<div class="b-cards-area">`;
  469. if (FAVORITES.length) {
  470. html += `<section class="b-section">
  471. <h2 class="title-bar-heading">收藏</h2>
  472. <div class="b-grid">${FAVORITES.map((t) => cardB(t, true)).join('')}</div>
  473. </section>`;
  474. }
  475. SECTIONS.forEach((sec) => {
  476. html += `<section class="b-section">
  477. <h2 class="title-bar-heading">${sec.title}</h2>
  478. <div class="b-grid">${sec.cards.map((t) => cardB(t, isFav(t))).join('')}</div>
  479. </section>`;
  480. });
  481. html += `</div>`;
  482. root.innerHTML = html;
  483. }
  484. renderA(document.getElementById('mount-a'));
  485. renderB(document.getElementById('mount-b'));
  486. document.querySelectorAll('.tabs button').forEach((btn) => {
  487. btn.addEventListener('click', () => {
  488. document.querySelectorAll('.tabs button').forEach((b) => b.classList.remove('is-active'));
  489. document.querySelectorAll('.panel').forEach((p) => p.classList.remove('is-active'));
  490. btn.classList.add('is-active');
  491. document.getElementById('panel-' + btn.dataset.tab).classList.add('is-active');
  492. });
  493. });
  494. </script>
  495. </body>
  496. </html>