feat: card repository and home three-column layout

This commit is contained in:
2026-08-16 16:43:30 +08:00
parent 065625843d
commit fdaa8d0516
9 changed files with 332 additions and 0 deletions
+45
View File
@@ -60,6 +60,51 @@ body {
.page { display: none; height: 100%; }
.page.active { display: flex; flex-direction: column; }
/* 首页三栏布局:三等分纵向列,卡片向下堆叠 */
/* Home three-column layout: three equal vertical columns, cards stack downward */
.tri-col {
flex: 1;
min-height: 0;
display: flex;
gap: 16px;
padding: 16px;
}
.col {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 16px;
overflow-y: auto;
}
/* 卡片:柔光玻璃拟态(复用 Task 4 token,非纯色背景) */
/* Card: soft-glass morphism (reuse Task 4 tokens, not a solid color) */
.card {
background: var(--glass-bg);
backdrop-filter: blur(20px) saturate(1.2) brightness(1.05) contrast(1.1);
-webkit-backdrop-filter: blur(20px) saturate(1.2) brightness(1.05) contrast(1.1);
border: 1px solid var(--glass-border);
border-radius: 14px;
padding: 20px;
color: var(--text);
}
/* 大字时间卡:主时间大字 + 日期副行 */
/* Big time card: large clock + date subtitle */
.time-card .big {
font-size: 56px;
font-weight: 300;
line-height: 1;
}
.time-card .sub {
margin-top: 8px;
font-size: 14px;
color: var(--text-dim);
}
/* 安卓 APP 列表页:搜索框 + 网格 */
/* Android app list page: search box + grid */
.search { padding: 16px; }
+2
View File
@@ -22,9 +22,11 @@
</main>
</div>
<script src="js/bridge.js"></script>
<script src="js/cards/cards.js"></script>
<script src="js/router.js"></script>
<script src="js/pages/applist.js"></script>
<script src="js/pages/webapplist.js"></script>
<script src="js/pages/home.js"></script>
<script src="js/webapp/tabs.js"></script>
<script src="js/webapp/topbar.js"></script>
<script>
+20
View File
@@ -0,0 +1,20 @@
// 卡片三栏降级布局纯函数(UMD:Node require / 浏览器挂 window.cards
// Card three-column fallback layout pure function (UMD: Node require / browser window.cards)
(function (root, factory) {
if (typeof module === 'object' && module.exports) module.exports = factory();
else root.cards = factory();
})(this, function () {
// cards: [{id, priority, enabled}]time 卡为内置始终保留
// 返回三栏分配结果,优先级数字小者优先;disabled 的卡不占位
// cards: [{id, priority, enabled}], time card is builtin and always kept
// Returns the three-column assignment; smaller priority comes first; disabled cards are skipped
function layout(cards) {
const columns = [[], [], []];
const active = cards
.filter((c) => c.enabled !== false)
.sort((a, b) => a.priority - b.priority);
active.forEach((c, i) => columns[i % 3].push(c.id));
return columns;
}
return { layout };
});
+70
View File
@@ -0,0 +1,70 @@
// 首页三栏卡片:拉取目录 -> cards.layout 分配三栏 -> 渲染(time 卡大字 + 其他占位)
// Home three-column cards: fetch catalog -> cards.layout assigns 3 columns -> render
// (big time card + generic placeholders for the rest)
window.updateHomeCards = async function () {
const page = document.querySelector('[data-page="home"]');
page.innerHTML = `<div class="tri-col" id="tri-col">
<div class="col" id="col-0"></div><div class="col" id="col-1"></div><div class="col" id="col-2"></div>
</div>`;
let catalog = [];
try {
catalog = JSON.parse(await bridge.call('fetchCards'));
} catch (e) {
// 桥接不可用(如未接线)时降级为内置时间卡
// Degrade to builtin time card when the bridge is unavailable (e.g. not wired yet)
catalog = [{ id: 'time', priority: 0, enabled: true }];
}
const cols = cards.layout(catalog);
cols.forEach((ids, i) => {
const col = document.getElementById(`col-${i}`);
ids.forEach((id) => {
if (id === 'time') col.appendChild(renderTimeCard());
else col.appendChild(renderGenericCard(id));
});
});
startTimeCardTicker();
};
// 内置大字时间卡:当前时间 + 日期
// Builtin big time card: current time + date
function renderTimeCard() {
const el = document.createElement('div');
el.className = 'card time-card';
const now = new Date();
el.innerHTML = `<div class="big">${fmtTime(now)}</div><div class="sub">${fmtDate(now)}</div>`;
return el;
}
// 其他卡片占位:仅显示 id,后续 Task 接具体卡片渲染
// Other cards placeholder: show id only; concrete renderers land in later tasks
function renderGenericCard(id) {
const el = document.createElement('div');
el.className = 'card';
el.textContent = id;
return el;
}
function fmtTime(d) {
const hh = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
return `${hh}:${mm}`;
}
function fmtDate(d) {
const days = ['日', '一', '二', '三', '四', '五', '六'];
return `${d.getMonth() + 1}${d.getDate()}日 周${days[d.getDay()]}`;
}
// 每 10s 刷新一次大字时间卡
// Refresh the big time card every 10 seconds
let timeTicker = null;
function startTimeCardTicker() {
if (timeTicker) return;
timeTicker = setInterval(() => {
const card = document.querySelector('.time-card');
if (!card) return;
const now = new Date();
card.querySelector('.big').textContent = fmtTime(now);
card.querySelector('.sub').textContent = fmtDate(now);
}, 10000);
}