14bcdc2e79
拍板:不展示成员数;群组=「加入」其余=「打开」;独立 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>
338 lines
23 KiB
HTML
338 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>联系我们:渠道二级页(Telegram 频道/群组,DB 配置)· 交互设计</title>
|
||
<style>
|
||
:root{
|
||
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
||
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
||
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
||
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
||
}
|
||
*{box-sizing:border-box}
|
||
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
||
.wrap{max-width:960px;margin:0 auto;padding:48px 24px 96px}
|
||
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
||
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
|
||
h2{font-size:21px;margin:44px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||
h3{font-size:16px;margin:26px 0 8px;color:var(--accent2)}
|
||
p{margin:10px 0}
|
||
code{font-family:var(--mono);font-size:.88em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
||
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:13px;line-height:1.55;color:#cdd3df}
|
||
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
|
||
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
|
||
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
|
||
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
|
||
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:16px 0}
|
||
.card.root{border-left:3px solid var(--accent)}
|
||
.card h3{margin-top:0}
|
||
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:14px}
|
||
th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:top}
|
||
th{color:var(--fg2);font-weight:600;font-size:13px}
|
||
td code{font-size:.85em}
|
||
ul,ol{padding-left:22px;margin:10px 0}
|
||
li{margin:5px 0}
|
||
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
||
.small{color:var(--fg2);font-size:13px}
|
||
a{color:var(--accent2)}
|
||
.back{display:inline-block;margin-bottom:24px;font-size:13px}
|
||
b{color:#fff}
|
||
|
||
/* ── 交互原型:用真实 App 暗色 token(design/colors_and_type.css [data-theme=dark])──*/
|
||
.proto{
|
||
--p-bg:#14110E; --p-bg-subtle:#1F1C18; --p-surface:#221E19; --p-surface2:#2A251F;
|
||
--p-fg1:#F4EFE8; --p-fg2:#B6AC9C; --p-fg3:#897F6F; --p-fg-on-accent:#1F1C18;
|
||
--p-accent:#CC8B5C; --p-accent-hover:#D9A982; --p-accent-subtle:rgba(204,139,92,.14);
|
||
--p-accent-border:rgba(204,139,92,.30);
|
||
--p-border:rgba(242,238,231,.10); --p-border-strong:rgba(242,238,231,.18);
|
||
--p-green:#7FB07A;
|
||
background:var(--p-bg);border:1px solid var(--border);border-radius:14px;overflow:hidden;
|
||
display:flex;height:520px;margin:18px 0;font-size:14px;
|
||
}
|
||
.proto *{box-sizing:border-box}
|
||
/* sidebar */
|
||
.p-side{width:190px;flex:none;background:var(--p-bg);border-right:1px solid var(--p-border);padding:18px 14px;display:flex;flex-direction:column;gap:4px}
|
||
.p-brand{display:flex;align-items:center;gap:10px;padding:4px 8px 18px}
|
||
.p-logo{width:30px;height:30px;border-radius:8px;background:var(--p-accent-subtle);display:flex;align-items:center;justify-content:center;color:var(--p-accent);font-size:16px}
|
||
.p-brand b{color:var(--p-fg1);font-size:16px;line-height:1.1}
|
||
.p-brand span{display:block;color:var(--p-fg3);font-size:9px;letter-spacing:2px}
|
||
.p-nav{display:flex;align-items:center;gap:11px;padding:9px 12px;border-radius:9px;color:var(--p-fg2);cursor:default}
|
||
.p-nav .ic{width:17px;text-align:center;opacity:.85}
|
||
.p-nav.active{background:var(--p-accent-subtle);color:var(--p-accent);font-weight:600}
|
||
/* main */
|
||
.p-main{flex:1;display:flex;flex-direction:column;min-width:0}
|
||
.p-top{display:flex;align-items:center;gap:12px;padding:16px 22px;border-bottom:1px solid var(--p-border)}
|
||
.p-top .ttl{font-size:19px;font-weight:700;color:var(--p-fg1)}
|
||
.p-top .rt{margin-left:auto;display:flex;align-items:center;gap:14px;color:var(--p-fg2);font-size:12px}
|
||
.p-dot{width:7px;height:7px;border-radius:50%;background:var(--p-green);display:inline-block;margin-right:5px}
|
||
.p-body{padding:20px 22px;overflow-y:auto}
|
||
.p-intro{color:var(--p-fg2);font-size:13.5px;margin:0 0 14px}
|
||
/* channel / item cards */
|
||
.p-card{display:flex;align-items:center;gap:13px;padding:13px 15px;background:var(--p-surface);border:1px solid var(--p-border);border-radius:13px;margin-bottom:10px;cursor:pointer;transition:border-color .12s,background .12s}
|
||
.p-card:hover{border-color:var(--p-accent-border);background:var(--p-surface2)}
|
||
.p-ic{width:38px;height:38px;flex:none;border-radius:10px;background:var(--p-bg-subtle);display:flex;align-items:center;justify-content:center;font-size:18px;color:var(--p-fg2)}
|
||
.p-ic.accent{background:var(--p-accent-subtle);color:var(--p-accent)}
|
||
.p-tx{flex:1;min-width:0}
|
||
.p-tx .nm{color:var(--p-fg1);font-weight:600;font-size:14.5px;display:flex;align-items:center;gap:6px}
|
||
.p-tx .sb{color:var(--p-fg3);font-size:12.5px;margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.p-chev{color:var(--p-fg3);font-size:17px;flex:none}
|
||
.p-verify{color:var(--p-accent);font-size:12px}
|
||
.p-count{color:var(--p-fg3);font-size:11.5px;flex:none;display:flex;align-items:center;gap:4px}
|
||
.p-btn{flex:none;font-size:12.5px;font-weight:600;padding:6px 15px;border-radius:8px;border:1px solid var(--p-accent-border);color:var(--p-accent);background:transparent}
|
||
.p-btn.solid{background:var(--p-accent);color:var(--p-fg-on-accent);border-color:transparent}
|
||
.p-sechd{color:var(--p-fg3);font-size:11px;font-weight:700;letter-spacing:1.4px;margin:18px 2px 9px}
|
||
.p-back{display:inline-flex;align-items:center;gap:8px;color:var(--p-fg2);font-size:13px;cursor:pointer;padding:2px 0;margin-bottom:2px}
|
||
.p-back:hover{color:var(--p-fg1)}
|
||
.p-detail-hd{display:flex;align-items:center;gap:12px;margin:6px 0 4px}
|
||
.p-detail-hd .p-ic{width:44px;height:44px;font-size:21px}
|
||
.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}
|
||
.toggles button:hover{color:var(--fg)}
|
||
.capt{color:var(--fg2);font-size:12.5px;text-align:center;margin-top:-4px}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<a class="back" href="index.html">← 返回文档索引</a>
|
||
|
||
<h1>联系我们 · 渠道二级页</h1>
|
||
<p class="sub">交互设计 · Telegram/LINE 多频道 · 群组 · 内容 DB 配置 · <span class="tag info">前端 + 后端 + 数据库</span></p>
|
||
|
||
<div class="lead">
|
||
现「联系我们」把每个渠道当<b>单一入口</b>(Telegram 只挂一个 <code>@PangolinVPN_bot</code>)。
|
||
需求:点 Telegram 进入<b>二级页</b>,列出该平台下的<b>多个频道 / 群组 / Bot</b>,
|
||
每项含名称、@handle、一句说明、可选成员数、认证标记与「打开 / 加入」动作,
|
||
<b>全部由数据库配置</b>(运营可随时增删改,客户端不写死)。本页给出交互原型 + 数据模型 + 接口,待你统一后再开发。
|
||
</div>
|
||
|
||
<h2>可点原型(点 Telegram 卡片进二级,← 返回)</h2>
|
||
<p class="small">下方为内嵌可交互原型,配色用 App 真实暗色 token(clay/espresso)。点 <b>Telegram</b> 或 <b>LINE</b> 卡进详情;「邮箱客服 / 自助发卡商店」为单链接,点即直接打开(原型里仅提示)。</p>
|
||
|
||
<div class="proto" id="proto">
|
||
<!-- sidebar -->
|
||
<div class="p-side">
|
||
<div class="p-brand"><div class="p-logo">🐾</div><div><b>穿山甲</b><span>PANGOLIN</span></div></div>
|
||
<div class="p-nav"><span class="ic">⏻</span>连接</div>
|
||
<div class="p-nav"><span class="ic">🌐</span>节点</div>
|
||
<div class="p-nav"><span class="ic">📊</span>统计</div>
|
||
<div class="p-nav"><span class="ic">⚙</span>设置</div>
|
||
<div class="p-nav active"><span class="ic">💬</span>联系我们</div>
|
||
</div>
|
||
<!-- main -->
|
||
<div class="p-main">
|
||
<div class="p-top">
|
||
<span class="ttl" id="p-title">联系我们</span>
|
||
<span class="rt"><span><span class="p-dot"></span>US</span><span>🌙</span></span>
|
||
</div>
|
||
|
||
<!-- L1: channel list -->
|
||
<div class="p-body" id="view-l1">
|
||
<p class="p-intro">遇到问题?通过以下任一渠道联系我们,通常数分钟内回复。</p>
|
||
|
||
<div class="p-card" onclick="showTg()">
|
||
<div class="p-ic accent">✈</div>
|
||
<div class="p-tx"><div class="nm">Telegram</div><div class="sb">官方频道 · 交流群 · 客服 Bot</div></div>
|
||
<span class="p-chev">›</span>
|
||
</div>
|
||
|
||
<div class="p-card disabled" title="即将开放,先灰置">
|
||
<div class="p-ic">💬</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>
|
||
|
||
<div class="p-card" title="单链接:直接打开邮件">
|
||
<div class="p-ic">✉</div>
|
||
<div class="p-tx"><div class="nm">邮箱客服</div><div class="sb">support@pangolin.vpn</div></div>
|
||
<span class="p-chev">›</span>
|
||
</div>
|
||
|
||
<div class="p-card" title="单链接:直接打开商店">
|
||
<div class="p-ic">🛍</div>
|
||
<div class="p-tx"><div class="nm">自助发卡商店</div><div class="sb">shop.pangolin.vpn</div></div>
|
||
<span class="p-chev">›</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- L2: Telegram detail -->
|
||
<div class="p-body hide" id="view-tg">
|
||
<div class="p-back" onclick="showList()">‹ 返回</div>
|
||
<div class="p-detail-hd">
|
||
<div class="p-ic accent">✈</div>
|
||
<div><div class="nm">Telegram</div><div class="sb">加入官方频道获取更新,进群与用户互助</div></div>
|
||
</div>
|
||
|
||
<div class="p-sechd">频道 · CHANNELS</div>
|
||
<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>
|
||
<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>
|
||
<button class="p-btn">打开</button>
|
||
</div>
|
||
|
||
<div class="p-sechd">群组 · GROUPS</div>
|
||
<div class="p-card">
|
||
<div class="p-ic">👥</div>
|
||
<div class="p-tx"><div class="nm">穿山甲 · 用户交流群</div><div class="sb">使用问题互助交流 · @PangolinChat</div></div>
|
||
<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 & chat · @PangolinEN</div></div>
|
||
<button class="p-btn solid">加入</button>
|
||
</div>
|
||
|
||
<div class="p-sechd">客服机器人 · BOT</div>
|
||
<div class="p-card">
|
||
<div class="p-ic accent">🤖</div>
|
||
<div class="p-tx"><div class="nm">客服机器人</div><div class="sb">自动答疑 / 提交工单 · @PangolinVPN_bot</div></div>
|
||
<button class="p-btn">打开</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- L2: LINE detail (示意同构) -->
|
||
<div class="p-body hide" id="view-line">
|
||
<div class="p-back" onclick="showList()">‹ 返回</div>
|
||
<div class="p-detail-hd">
|
||
<div class="p-ic">💬</div>
|
||
<div><div class="nm">LINE</div><div class="sb">官方账号与交流群</div></div>
|
||
</div>
|
||
<div class="p-sechd">官方账号 · OFFICIAL</div>
|
||
<div class="p-card">
|
||
<div class="p-ic">💬</div>
|
||
<div class="p-tx"><div class="nm">Pangolin 官方账号 <span class="p-verify">✔</span></div><div class="sb">公告与客服 · @pangolinvpn</div></div>
|
||
<button class="p-btn">打开</button>
|
||
</div>
|
||
<div class="p-sechd">群组 · GROUPS</div>
|
||
<div class="p-card">
|
||
<div class="p-ic">👥</div>
|
||
<div class="p-tx"><div class="nm">中文交流群</div><div class="sb">使用互助 · openchat</div></div>
|
||
<button class="p-btn solid">加入</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="toggles">
|
||
<button onclick="showList()">L1 渠道列表</button>
|
||
<button onclick="showTg()">L2 · Telegram</button>
|
||
<button onclick="showLine()">L2 · LINE(预留结构预览)</button>
|
||
</div>
|
||
<p class="capt">同一原型内切换三态 · 真机为路由 push(移动端整页)/ 内容区替换(桌面)。
|
||
L1 上 LINE 为<b>灰置「即将开放」不可点</b>;「L2 · LINE」按钮仅用于预览其<b>预留的二级结构</b>(去灰后即此样式)。</p>
|
||
|
||
<h2>交互规则</h2>
|
||
<ul>
|
||
<li><b>进入二级的条件</b>:某平台配置了 <b>>1 条</b>链接 → 卡片带 <code>›</code>,点击进二级页(Telegram / LINE)。
|
||
仅 <b>1 条</b>链接的平台(邮箱、发卡商店)→ 点击<b>直接执行该链接动作</b>(打开邮件 / 浏览器),不进二级。
|
||
规则统一由「该平台 link 条数」驱动,无需前端写死哪个进二级。</li>
|
||
<li><b>二级页结构</b>:顶部 <code>‹ 返回</code> + 平台头(图标 + 名 + 一句副标题);下方按 <b>kind 分组</b>展示
|
||
——<code>频道 CHANNELS</code> / <code>群组 GROUPS</code> / <code>客服机器人 BOT</code>(分组标题仅在该组有内容时出现,顺序固定)。</li>
|
||
<li><b>每一项</b>:图标/头像 · 标题(可带 <span class="p-verify" style="color:var(--accent)">✔</span> 认证)· 一句说明 + <code>@handle</code> ·
|
||
可选成员数(<b>展示用缓存文本</b>,不实时拉 Telegram)· 动作按钮。</li>
|
||
<li><b>动作</b>:频道/Bot → 「打开」(描边按钮);群组 → 「加入」(实心强调按钮)。点按钮或点整行都触发。
|
||
优先 <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 <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><b>一张表覆盖所有联系方式</b> —— Telegram 频道/群组/Bot、LINE、邮箱、发卡商店都是 <code>contact_link</code> 里的一行,
|
||
差别只在 <code>platform</code>(分到哪个 L1 卡)+ <code>kind</code>(二级分到哪组 & 动作样式)+ <code>url</code> 协议。
|
||
一行 = 一条可点链接;L1 按 <code>platform</code> 聚合,L2 按 <code>kind</code> 分组。</p>
|
||
<pre>contact_link
|
||
─────────────────────────────────────────────────────────────
|
||
id INTEGER PK
|
||
platform TEXT -- telegram | line | email | store | whatsapp | ...(L1 分组键)
|
||
kind TEXT -- channel | group | bot | link(L2 分组键 & 动作样式)
|
||
title TEXT -- "穿山甲 · 官方频道" / "邮箱客服" / "自助发卡商店"
|
||
handle TEXT -- "@PangolinVPN"(展示;可空,邮箱/商店留空)
|
||
url TEXT -- 点击目标(见下表);App 侧对 telegram 优先转 tg://
|
||
description TEXT -- 一句说明(可空)
|
||
verified INTEGER -- 0/1 认证勾
|
||
sort_order INTEGER -- 组内排序
|
||
enabled INTEGER -- 0/1 下线开关
|
||
locale TEXT -- "zh"|"en"|NULL(全部) 可选按语言过滤
|
||
</pre>
|
||
<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 # 无需登录亦可(客户端普通请求)
|
||
→ 200
|
||
{
|
||
"platforms": [
|
||
{ "platform":"telegram",
|
||
"links":[
|
||
{"kind":"channel","title":"穿山甲 · 官方频道","handle":"@PangolinVPN",
|
||
"url":"https://t.me/PangolinVPN","description":"产品更新与公告","verified":true},
|
||
{"kind":"group","title":"穿山甲 · 用户交流群","handle":"@PangolinChat",
|
||
"url":"https://t.me/PangolinChat","description":"使用问题互助"},
|
||
{"kind":"bot","title":"客服机器人","handle":"@PangolinVPN_bot",
|
||
"url":"https://t.me/PangolinVPN_bot","description":"自动答疑/提交工单"}
|
||
]},
|
||
{ "platform":"line", "links":[ ... ] },
|
||
{ "platform":"email", "links":[{"kind":"link","title":"邮箱客服","url":"mailto:support@pangolin.vpn"}] },
|
||
{ "platform":"store", "links":[{"kind":"link","title":"自助发卡商店","url":"https://shop.pangolin.vpn"}] }
|
||
]
|
||
}</pre>
|
||
<ul>
|
||
<li>后端按 <code>enabled=1</code> 过滤、按 <code>sort_order</code> 排序、按 <code>platform</code>→<code>kind</code> 分组返回;客户端只渲染。</li>
|
||
<li>客户端<b>缓存</b>上次结果(离线/弱网仍可展示),启动或进联系页时后台刷新。</li>
|
||
<li>迁移:<code>server/migrations/{mysql,sqlite}/</code> 各加建表 + 种子数据(把现有 4 渠道灌入,Telegram 先补真实频道/群)。</li>
|
||
</ul>
|
||
|
||
<h2>已定(本轮拍板)</h2>
|
||
<ul>
|
||
<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>
|
||
</body>
|
||
<script>
|
||
function _hideAll(){ ['view-l1','view-tg','view-line'].forEach(id=>document.getElementById(id).classList.add('hide')); }
|
||
function showList(){ _hideAll(); document.getElementById('view-l1').classList.remove('hide'); document.getElementById('p-title').textContent='联系我们'; }
|
||
function showTg(){ _hideAll(); document.getElementById('view-tg').classList.remove('hide'); document.getElementById('p-title').textContent='Telegram'; }
|
||
function showLine(){ _hideAll(); document.getElementById('view-line').classList.remove('hide'); document.getElementById('p-title').textContent='LINE'; }
|
||
</script>
|
||
</html>
|