docs(contact): 落定渠道二级页设计 —— 统一单表 + LINE 灰置

拍板:不展示成员数;群组=「加入」其余=「打开」;独立 GET /v1/contact;
全渠道(Telegram/LINE/邮箱/发卡)统一一张 contact_link 表,靠 url 协议区分
(mailto:/https:/tg://),邮箱=kind=link+mailto。L1 平台图标/名/强调色由前端
platform 注册表内置(唯一不入库的皮)。LINE 也做二级、同构,但先灰置「即将开放」
(registry comingSoon=[line],配好去灰即用)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-02 12:30:24 +08:00
parent 91a890428f
commit 14bcdc2e79
2 changed files with 50 additions and 29 deletions
+49 -28
View File
@@ -90,6 +90,9 @@
.p-detail-hd .nm{font-size:18px;font-weight:700;color:var(--p-fg1)}
.p-detail-hd .sb{color:var(--p-fg3);font-size:12.5px}
.hide{display:none!important}
.p-card.disabled{opacity:.5;cursor:not-allowed}
.p-card.disabled:hover{border-color:var(--p-border);background:var(--p-surface)}
.p-soon{font-size:10px;font-weight:600;padding:1px 7px;border-radius:999px;background:var(--p-bg-subtle);color:var(--p-fg3);border:1px solid var(--p-border)}
.flag{margin-left:auto;color:var(--p-fg3);font-size:12px}
.toggles{display:flex;gap:8px;margin:10px 0 2px}
.toggles button{background:var(--panel2);color:var(--fg2);border:1px solid var(--border);border-radius:8px;padding:6px 12px;font-size:13px;cursor:pointer}
@@ -141,9 +144,9 @@
<span class="p-chev"></span>
</div>
<div class="p-card" onclick="showLine()">
<div class="p-card disabled" title="即将开放,先灰置">
<div class="p-ic">💬</div>
<div class="p-tx"><div class="nm">LINE</div><div class="sb">官方账号 · 中文/日文群</div></div>
<div class="p-tx"><div class="nm">LINE <span class="p-soon">即将开放</span></div><div class="sb">官方账号 · 中文/日文群</div></div>
<span class="p-chev"></span>
</div>
@@ -172,13 +175,11 @@
<div class="p-card">
<div class="p-ic accent">📣</div>
<div class="p-tx"><div class="nm">穿山甲 · 官方频道 <span class="p-verify"></span></div><div class="sb">产品更新与公告 · @PangolinVPN</div></div>
<span class="p-count">👥 12.3k</span>
<button class="p-btn">打开</button>
</div>
<div class="p-card">
<div class="p-ic accent">📶</div>
<div class="p-tx"><div class="nm">穿山甲 · 节点状态</div><div class="sb">节点/故障实时播报 · @PangolinStatus</div></div>
<span class="p-count">👥 3.1k</span>
<button class="p-btn">打开</button>
</div>
@@ -186,13 +187,11 @@
<div class="p-card">
<div class="p-ic">👥</div>
<div class="p-tx"><div class="nm">穿山甲 · 用户交流群</div><div class="sb">使用问题互助交流 · @PangolinChat</div></div>
<span class="p-count">👥 8.7k</span>
<button class="p-btn solid">加入</button>
</div>
<div class="p-card">
<div class="p-ic">🌏</div>
<div class="p-tx"><div class="nm">Pangolin · English Group</div><div class="sb">English support &amp; chat · @PangolinEN</div></div>
<span class="p-count">👥 1.2k</span>
<button class="p-btn solid">加入</button>
</div>
@@ -229,9 +228,10 @@
<div class="toggles">
<button onclick="showList()">L1 渠道列表</button>
<button onclick="showTg()">L2 · Telegram</button>
<button onclick="showLine()">L2 · LINE</button>
<button onclick="showLine()">L2 · LINE(预留结构预览)</button>
</div>
<p class="capt">同一原型内切换三态 · 真机为路由 push(移动端整页)/ 内容区替换(桌面)</p>
<p class="capt">同一原型内切换三态 · 真机为路由 push(移动端整页)/ 内容区替换(桌面)
L1 上 LINE 为<b>灰置「即将开放」不可点</b>;「L2 · LINE」按钮仅用于预览其<b>预留的二级结构</b>(去灰后即此样式)。</p>
<h2>交互规则</h2>
<ul>
@@ -246,29 +246,45 @@
优先 <code>tg://resolve?domain=…</code> 唤起已装 Telegram,失败回退 <code>https://t.me/…</code>(浏览器)。</li>
<li><b>桌面 vs 移动</b>:桌面在右侧内容区做<b>视图替换</b>(标题栏文案随之切到「Telegram」,← 返回回列表);
移动端为<b>整页 push 路由</b>,系统返回手势/返回键回列表。两端同一份数据与卡片组件。</li>
<li><b>空/禁用</b>:某平台所有 link <code>enabled=0</code> → L1 不展示该平台;二级页某分组为空 → 不渲染该分组标题。
全部为空的平台不出现在联系页。</li>
<li><b>空/禁用</b>:某平台所有 link <code>enabled=0</code>(或库里无该平台行)→ 默认 L1 <b>不展示</b>该平台;二级页某分组为空 → 不渲染该分组标题。</li>
<li><b>「即将开放」灰置</b>:前端 platform 注册表有一个 <code>comingSoon</code> 列表(当前 = <code>[line]</code>)。列在其中且<b>暂无 enabled 链接</b>的平台,L1 <b>置灰 + 「即将开放」角标、不可点进</b>(占位预告,不隐藏)。
运营在库里配好该平台链接后,从 <code>comingSoon</code> 列表移除该项(一行代码)→ 自动变可点二级页。LINE 即走此路:结构与 Telegram 同构,先灰、内容就绪即上。</li>
</ul>
<h2>数据模型(DB 配置)</h2>
<p>单表承载 L1 平台入口与 L2 子项:一行 = 一条可点链接。L1 由 <code>platform</code> 聚合而来,二级由 <code>kind</code> 分组。</p>
<h2>数据模型(DB 配置)· 全渠道统一一张表</h2>
<p><b>一张表覆盖所有联系方式</b> —— Telegram 频道/群组/Bot、LINE、邮箱、发卡商店都是 <code>contact_link</code> 里的一行,
差别只在 <code>platform</code>(分到哪个 L1 卡)+ <code>kind</code>(二级分到哪组 &amp; 动作样式)+ <code>url</code> 协议。
一行 = 一条可点链接;L1 按 <code>platform</code> 聚合,L2 按 <code>kind</code> 分组。</p>
<pre>contact_link
─────────────────────────────────────────────────────────────
id INTEGER PK
platform TEXT -- telegram | line | email | store | ...(L1 分组键)
platform TEXT -- telegram | line | email | store | whatsapp | ...(L1 分组键)
kind TEXT -- channel | group | bot | link(L2 分组键 & 动作样式)
title TEXT -- "穿山甲 · 官方频道"
handle TEXT -- "@PangolinVPN"(展示;可空)
url TEXT -- "https://t.me/PangolinVPN"(点击目标;App 侧优先转 tg://
title TEXT -- "穿山甲 · 官方频道" / "邮箱客服" / "自助发卡商店"
handle TEXT -- "@PangolinVPN"(展示;可空,邮箱/商店留空
url TEXT -- 点击目标(见下表);App 侧对 telegram 优先转 tg://
description TEXT -- 一句说明(可空)
member_hint TEXT -- "12.3k"(纯展示缓存,可空;不实时拉)
verified INTEGER -- 0/1 认证勾
sort_order INTEGER -- 组内排序
enabled INTEGER -- 0/1 下线开关
locale TEXT -- "zh"|"en"|NULL(全部) 可选按语言过滤
</pre>
<p class="small">L1 平台的<b>图标 / 显示名 / 强调色</b>由前端按 <code>platform</code> 枚举内置(Telegram=✈+强调、LINE=💬…),
不入库,避免图标资源塞数据库。库里只配<b>内容</b>。单链接平台(email/store)建议各配 1 行 <code>kind=link</code></p>
<p><b>邮箱之类怎么进这张表 —— 就是把 <code>url</code> 换个协议、<code>kind=link</code></b></p>
<table>
<tr><th>渠道</th><th>platform</th><th>kind</th><th>url 示例</th><th>动作</th></tr>
<tr><td>Telegram 频道</td><td><code>telegram</code></td><td><code>channel</code></td><td><code>https://t.me/PangolinVPN</code>App 转 <code>tg://</code></td><td>打开</td></tr>
<tr><td>Telegram 群组</td><td><code>telegram</code></td><td><code>group</code></td><td><code>https://t.me/PangolinChat</code></td><td>加入</td></tr>
<tr><td>Telegram Bot</td><td><code>telegram</code></td><td><code>bot</code></td><td><code>https://t.me/PangolinVPN_bot</code></td><td>打开</td></tr>
<tr><td>LINE 账号</td><td><code>line</code></td><td><code>link</code></td><td><code>https://line.me/R/ti/p/@pangolinvpn</code></td><td>打开</td></tr>
<tr><td>邮箱客服</td><td><code>email</code></td><td><code>link</code></td><td><code>mailto:support@pangolin.vpn</code></td><td>打开(拉起邮件)</td></tr>
<tr><td>自助发卡商店</td><td><code>store</code></td><td><code>link</code></td><td><code>https://shop.pangolin.vpn</code></td><td>打开(浏览器)</td></tr>
<tr><td>WhatsApp(将来)</td><td><code>whatsapp</code></td><td><code>link</code></td><td><code>https://wa.me/…</code></td><td>打开</td></tr>
</table>
<p class="small">动作按钮文案由 <code>kind</code> 决定:<code>group</code><b>「加入」</b>(实心强调);其余(<code>channel/bot/link</code>)→ <b>「打开」</b>(描边)。
点击一律「用系统方式打开 <code>url</code>」,App 侧仅对 <code>telegram</code> 平台做 <code>https://t.me/x → tg://resolve?domain=x</code> 的唤起优化,失败回退原 url。</p>
<p class="small"><b>唯一不入库的是「皮」</b>L1 平台卡的<b>图标 / 强调色 / 默认显示名</b>由前端一个小 <b>platform 注册表</b><code>platform</code> 键内置
<code>telegram</code>=✈+强调色、<code>line</code>=💬、<code>email</code>=✉、<code>store</code>=🛍…),避免把图标资源塞进数据库;未知 platform 用通用图标兜底。
库里只配<b>内容</b>。加已有平台的新频道/群 = 纯 DB,无需发版;加一个全新平台类型(新图标)= 注册表加一行 + 发版。</p>
<h2>接口</h2>
<pre>GET /v1/contact # 无需登录亦可(客户端普通请求)
@@ -278,10 +294,9 @@ locale TEXT -- "zh"|"en"|NULL(全部) 可选按语言过滤
{ "platform":"telegram",
"links":[
{"kind":"channel","title":"穿山甲 · 官方频道","handle":"@PangolinVPN",
"url":"https://t.me/PangolinVPN","description":"产品更新与公告",
"member_hint":"12.3k","verified":true},
"url":"https://t.me/PangolinVPN","description":"产品更新与公告","verified":true},
{"kind":"group","title":"穿山甲 · 用户交流群","handle":"@PangolinChat",
"url":"https://t.me/PangolinChat","description":"使用问题互助","member_hint":"8.7k"},
"url":"https://t.me/PangolinChat","description":"使用问题互助"},
{"kind":"bot","title":"客服机器人","handle":"@PangolinVPN_bot",
"url":"https://t.me/PangolinVPN_bot","description":"自动答疑/提交工单"}
]},
@@ -296,13 +311,19 @@ locale TEXT -- "zh"|"en"|NULL(全部) 可选按语言过滤
<li>迁移:<code>server/migrations/{mysql,sqlite}/</code> 各加建表 + 种子数据(把现有 4 渠道灌入,Telegram 先补真实频道/群)。</li>
</ul>
<h2>待你拍板</h2>
<h2>已定(本轮拍板</h2>
<ul>
<li><b>成员数</b>是否展示?(要真实数需定期抓取;建议先用 <code>member_hint</code> 手工填/留空)</li>
<li>群组动作叫<b>加入</b>」还是统一「<b>打开</b>」?(原型里频道=打开、群组=加入)</li>
<li>LINE 是否同样做二级(配了多账号才需要),还是暂时仍单链接?</li>
<li>是否要<b>认证勾</b>与分组标题(频道/群组/Bot),还是拍平成一个列表?</li>
<li>接口走<b>独立 <code>/v1/contact</code></b> 还是并进现有配置引导(如 <code>/me</code> bootstrap)一起下发?</li>
<li><span class="tag ok"></span> <b>不展示成员数</b> —— 已从模型与 UI 移除 <code>member_hint</code></li>
<li><span class="tag ok"></span> 群组动作叫 <b>加入</b><code>kind=group</code>,实心强调);频道/Bot/普通链接 <b>打开</b>(描边)。</li>
<li><span class="tag ok"></span> 接口走 <b>独立 <code>GET /v1/contact</code></b>(不并进 <code>/me</code> 引导)。</li>
<li><span class="tag ok"></span> <b>全渠道统一一张 <code>contact_link</code></b>:邮箱/发卡/LINE 与 Telegram 同表,靠 <code>url</code> 协议区分(<code>mailto:</code> / <code>https:</code> / <code>tg://</code>)。</li>
<li><span class="tag ok"></span> <b>LINE 也做二级</b>(与 Telegram 同构),但<b>先灰置</b>——L1 上 LINE 卡置灰 + 「即将开放」,不可点进;二级页结构预留,等运营在库里配好 LINE 链接、去灰即用。</li>
</ul>
<h2>仍可再定(不阻塞,先给默认)</h2>
<ul>
<li><b>认证勾 <span class="p-verify" style="color:var(--accent)"></span> 与分组标题</b>:默认<b>保留</b>(频道/群组/Bot 分组 + 官方项带勾)。若想更简可拍平成单列表 —— 说一声即可。</li>
<li><b>成员数字段是否保留在库里(仅不展示)</b>:默认<b>删列</b>,需要时再加回不迟。</li>
</ul>
</div>
+1 -1
View File
@@ -46,7 +46,7 @@
<h2>设计方案 / Specs</h2>
<a class="doc" href="contact-telegram-channels-design.html">
<div class="t">联系我们 · 渠道二级页(Telegram 频道/群组,DB 配置)<span class="tag html">HTML</span></div>
<div class="d">点 Telegram/LINE 进二级页,按 kind 分组(频道/群组/Bot)列多条链接,含 @handle/说明/成员数/认证/「打开·加入」动作,全部 DB 配置。含可点原型 + 单表 contact_link 数据模型 + GET /v1/contact 接口。规则:平台 >1 条链接才进二级,否则点击直达。</div>
<div class="d">点 Telegram 进二级页,按 kind 分组(频道/群组/Bot)列多条链接,含 @handle/说明/认证/「打开·加入」动作,全渠道(含邮箱/发卡)统一一张 contact_link 表(靠 url 协议区分 mailto/https/tg)。独立 GET /v1/contact。规则:平台 >1 条链接才进二级,否则点击直达。LINE 同构但先灰置「即将开放」(registry comingSoon 开关,配好即去灰)。不展示成员数、群组=加入。含可点原型。</div>
<div class="path">docs/contact-telegram-channels-design.html</div>
</a>
<a class="doc" href="free-quota-ad.html">