This commit is contained in:
oneao committed 2025-11-20 17:30:28 +08:00
1 parent 63b168affc
commit 069b4506e4
29 files changed
+1077 -1116

No files matched your search

+301 -196
View File
@@ -1,232 +1,337 @@
<!DOCTYPE html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>共享空间</title>
<style>
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto;
background: #f6f6f7;
color: #111;
}
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<title>共享空间 — 邀请 / 加入</title>
<style>
:root{
--bg: #f6f7fb;
--card: #ffffff;
--muted: #9aa4b2;
--accent: #5b8cff;
--danger: #ff6b6b;
--radius: 12px;
--gap: 12px;
--shadow: 0 6px 18px rgba(22,27,40,0.06);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial;
}
.page {
max-width: 420px;
margin: 0 auto;
padding: 16px;
background: #fff;
min-height: 100vh;
box-sizing: border-box;
}
/* Reset / basic */
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
background:linear-gradient(180deg, var(--bg), #eef2ff 60%);
color:#0f1724;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
padding:16px;
}
h1 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
}
/* Container */
.screen{
max-width:480px;
margin:0 auto;
}
/* 按钮 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 40px;
padding: 0 14px;
border-radius: 8px;
background: #111;
color: #fff;
font-size: 14px;
cursor: pointer;
margin-top: 12px;
border: 1px solid #111;
}
header.appbar{
display:flex;
align-items:center;
gap:12px;
padding:8px 4px;
margin-bottom:12px;
}
.title{
font-size:18px;
font-weight:600;
}
.subtitle{
display:block;
font-size:12px;
color:var(--muted);
margin-top:2px;
}
.btn-outline {
background: transparent;
color: #111;
}
/* Cards */
.card{
background:var(--card);
border-radius:var(--radius);
box-shadow:var(--shadow);
padding:14px;
margin-bottom:var(--gap);
}
.input-box {
width: 100%;
padding: 10px;
border-radius: 6px;
border: 1px solid #ddd;
margin-top: 12px;
font-size: 14px;
}
/* Invite area */
.invite-top{
display:flex;
gap:12px;
align-items:center;
}
.space-avatar{
width:56px;height:56px;border-radius:12px;background:linear-gradient(135deg,#7fb6ff,#5b8cff);
display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:20px;
}
.space-info{
flex:1;
}
.space-name{font-weight:600;font-size:16px}
.space-meta{font-size:12px;color:var(--muted);margin-top:4px}
.list-item {
display: flex;
justify-content: space-between;
padding: 14px 0;
border-bottom: 1px solid #eee;
}
.invite-row{
margin-top:12px;display:flex;gap:8px;
}
.btn{
flex:1;
border:0;padding:10px 12px;border-radius:10px;font-weight:600;
background:var(--accent);color:#fff;cursor:pointer;
box-shadow:0 6px 12px rgba(91,140,255,0.18);
}
.btn.ghost{background:transparent;color:var(--accent);border:1px solid rgba(91,140,255,0.12);box-shadow:none}
.btn.danger{background:var(--danger);box-shadow:0 6px 12px rgba(255,107,107,0.16)}
.switcher {
display: flex;
gap: 12px;
margin-bottom: 20px;
}
/* Invite code box */
.code-box{display:flex;align-items:center;justify-content:space-between;padding:10px;border-radius:10px;background:#f4f7ff;border:1px dashed rgba(91,140,255,0.18)}
.code-text{font-weight:700;letter-spacing:2px;font-size:16px}
.code-actions{display:flex;gap:8px}
.switcher button {
flex: 1;
height: 36px;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
border: 1px solid #ccc;
background: #fafafa;
}
/* Join section */
.join-input{
display:flex;gap:8px;margin-top:8px;
}
.input{
flex:1;padding:10px;border-radius:10px;border:1px solid #e6e9ef;background:#fff;
font-size:14px;
}
.help-line{font-size:12px;color:var(--muted);margin-top:8px}
.switcher button.active {
background: #111;
color: #fff;
border-color: #111;
}
/* Recent invites / list */
.list-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-top:1px solid #f2f4f8}
.list-left{display:flex;align-items:center;gap:10px}
.small-avatar{width:36px;height:36px;border-radius:8px;background:#eef4ff;display:flex;align-items:center;justify-content:center;color:#3759c8;font-weight:700}
.list-title{font-weight:600}
.list-sub{font-size:12px;color:var(--muted)}
/* 弹窗 */
.modal-mask {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.45);
display: none;
align-items: center;
justify-content: center;
}
/* Modal (simple) */
.overlay{position:fixed;left:0;right:0;top:0;bottom:0;background:rgba(8,10,15,0.45);display:flex;align-items:center;justify-content:center;padding:20px;z-index:80}
.modal{width:100%;max-width:420px;border-radius:14px;background:var(--card);padding:16px;box-shadow:var(--shadow)}
.modal .h1{font-weight:700;margin-bottom:8px}
.modal .muted{color:var(--muted);font-size:13px;margin-bottom:12px}
.share-grid{display:flex;gap:8px;flex-wrap:wrap}
.modal {
background: #fff;
padding: 20px;
width: 100%;
max-width: 360px;
text-align: center;
border: 1px solid #ddd;
border-radius: 0;
}
.share-chip{flex:0 0 30%;min-width:84px;padding:10px;border-radius:10px;background:#f7f9ff;text-align:center;font-size:13px;color:#123;background:linear-gradient(180deg,#fff,#f7f9ff);cursor:pointer}
.copy-toast{position:fixed;left:50%;transform:translateX(-50%);bottom:22px;padding:8px 14px;background:rgba(0,0,0,0.7);color:#fff;border-radius:20px;font-size:13px;z-index:90}
.qr-box {
width: 180px;
height: 180px;
background: #eee;
margin: 16px auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
font-size: 12px;
color: #666;
}
</style>
/* Footer hint */
.hint{font-size:12px;color:var(--muted);text-align:center;margin-top:12px}
/* small screens */
@media (max-width:360px){
.btn{padding:10px}
.space-avatar{width:48px;height:48px;font-size:18px}
}
</style>
</head>
<body>
<div class="screen">
<header class="appbar">
<div style="width:44px;"></div>
<div>
<div class="title">共享空间</div>
<div class="subtitle">邀请别人加入你的空间,或输入邀请码加入别人空间</div>
</div>
</header>
<div class="page">
<h1>共享空间(预览)</h1>
<!-- Invite card -->
<section class="card">
<div class="invite-top">
<div class="space-avatar" id="spaceAvatar">A</div>
<div class="space-info">
<div class="space-name" id="spaceName">工作协作空间</div>
<div class="space-meta">成员 12 • 权限 管理者</div>
</div>
<div style="width:68px;text-align:right"><button class="btn ghost" id="manageBtn">管理</button></div>
</div>
<div class="switcher">
<button id="btnNotJoined" class="active" onclick="showPage('not')">未加入</button>
<button id="btnJoined" onclick="showPage('yes')">已加入</button>
<div style="margin-top:14px">
<div class="code-box" id="codeBox">
<div class="code-text" id="inviteCode">AB12-CD34</div>
<div class="code-actions">
<button class="btn ghost" id="copyBtn">复制</button>
<button class="btn" id="shareBtn">分享</button>
</div>
</div>
<div class="invite-row">
<button class="btn ghost" id="qrBtn">二维码</button>
<button class="btn ghost" id="linkBtn">链接</button>
<button class="btn danger" id="revokeBtn">撤销并重置</button>
</div>
</div>
<div class="hint">邀请码有效期:24小时 · 点击分享可选择渠道</div>
</section>
<!-- Join other -->
<section class="card" aria-label="加入他人空间">
<div style="display:flex;align-items:center;justify-content:space-between;">
<div style="font-weight:700">加入别人的空间</div>
<div class="list-sub">通过邀请码或扫码加入</div>
</div>
<div class="join-input">
<input class="input" id="joinInput" placeholder="输入邀请码或链接(例如:AB12-CD34)" />
<button class="btn" id="joinBtn">加入</button>
</div>
<div class="help-line">若对方使用链接邀请,可直接粘贴链接;扫码请使用 App 内相机。</div>
<div style="margin-top:12px">
<div style="font-weight:600;margin-bottom:8px">最近加入的空间</div>
<div class="list-row">
<div class="list-left">
<div class="small-avatar">P</div>
<div>
<div class="list-title">产品设计空间</div>
<div class="list-sub">邀请人:小李 · 2 天前</div>
</div>
</div>
<div style="text-align:right;">
<div class="list-sub">成员 8</div>
</div>
</div>
<div class="list-row">
<div class="list-left">
<div class="small-avatar">M</div>
<div>
<div class="list-title">市场推广</div>
<div class="list-sub">邀请人:小王 · 1 周前</div>
</div>
</div>
<div style="text-align:right;">
<div class="list-sub">成员 20</div>
</div>
</div>
</div>
</section>
<!-- footer actions -->
<div style="display:flex;gap:8px;margin-top:8px">
<button class="btn ghost" style="flex:1" id="createSpace">创建新空间</button>
<button class="btn" style="flex:1" id="explore">发现空间</button>
</div>
<!-- 未加入 -->
<div id="notJoined">
<p>你尚未加入共享空间,你可以创建自己的空间或加入他人的。</p>
<div style="height:40px"></div>
</div>
<div class="btn" onclick="openInviteModal()">生成邀请码</div>
<div class="btn-outline btn" onclick="openScanModal()">扫码加入</div>
<!-- Modal markup (hidden initially) -->
<div id="modalOverlay" class="overlay" style="display:none;">
<div class="modal" role="dialog" aria-modal="true">
<div class="h1">分享邀请</div>
<div class="muted">选择一种方式邀请你的成员</div>
<div class="btn btn-outline" onclick="toggleJoinInput()">输入邀请码加入</div>
<div class="share-grid" style="margin-bottom:12px;">
<div class="share-chip" data-action="wechat">微信</div>
<div class="share-chip" data-action="qq">QQ</div>
<div class="share-chip" data-action="link">复制链接</div>
<div class="share-chip" data-action="sms">短信</div>
<div class="share-chip" data-action="email">邮件</div>
<div class="share-chip" data-action="more">更多</div>
</div>
<input id="inviteInput" type="text" class="input-box" placeholder="邀请码" style="display:none;" />
<div id="joinBtn" class="btn" style="display:none;">加入</div>
</div>
<!-- 已加入 -->
<div id="joined" style="display:none;">
<p>你已加入共享空间,可查看成员、权限和邀请他人加入。</p>
<h3 style="margin-top:20px; font-size:16px; font-weight:600;">成员列表</h3>
<div class="list-item"><span>张三</span><span>管理员</span></div>
<div class="list-item"><span>李四</span><span>成员</span></div>
<div class="list-item"><span>王五</span><span>成员</span></div>
<div class="btn" onclick="openInviteModal()">邀请新成员</div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button class="btn ghost" onclick="closeModal()">取消</button>
<button class="btn" id="modalShare">分享</button>
</div>
</div>
</div>
<!-- 邀请二维码弹窗 -->
<div id="inviteModal" class="modal-mask">
<div class="modal">
<h3>邀请码 / 二维码</h3>
<div id="qrBox" class="qr-box">二维码生成中…</div>
<div class="btn" onclick="closeInviteModal()">关闭</div>
</div>
</div>
<!-- 扫码加入弹窗(假装相机界面) -->
<div id="scanModal" class="modal-mask">
<div class="modal">
<h3>扫码加入</h3>
<div class="qr-box">[ 相机画面模拟区域 ]</div>
<p style="font-size:12px;color:#666;">打开摄像头并扫描共享空间二维码</p>
<div class="btn" onclick="closeScanModal()">关闭</div>
</div>
</div>
<!-- toast -->
<div id="copyToast" class="copy-toast" style="display:none;">已复制到剪贴板</div>
<script>
function showPage(type) {
const not = document.getElementById("notJoined");
const yes = document.getElementById("joined");
const btnNot = document.getElementById("btnNotJoined");
const btnYes = document.getElementById("btnJoined");
// demo interactions
const inviteCodeEl = document.getElementById('inviteCode');
const copyBtn = document.getElementById('copyBtn');
const shareBtn = document.getElementById('shareBtn');
const modal = document.getElementById('modalOverlay');
const copyToast = document.getElementById('copyToast');
const joinInput = document.getElementById('joinInput');
const joinBtn = document.getElementById('joinBtn');
if (type === 'not') {
not.style.display = 'block';
yes.style.display = 'none';
btnNot.classList.add('active');
btnYes.classList.remove('active');
} else {
not.style.display = 'none';
yes.style.display = 'block';
btnYes.classList.add('active');
btnNot.classList.remove('active');
// copy code
copyBtn.addEventListener('click', async () => {
const code = inviteCodeEl.textContent.trim();
try {
await navigator.clipboard.writeText(code);
showToast('已复制邀请码');
} catch (e) {
showToast('复制失败,请手动复制');
}
});
// open share modal
shareBtn.addEventListener('click', () => openModal());
document.getElementById('qrBtn').addEventListener('click', ()=> {
alert('打开二维码生成器(示例占位)');
});
document.getElementById('linkBtn').addEventListener('click', ()=> {
alert('打开链接分享(示例占位)');
});
document.getElementById('revokeBtn').addEventListener('click', ()=> {
const newCode = randomCode();
inviteCodeEl.textContent = newCode;
showToast('邀请码已重置');
});
// join
joinBtn.addEventListener('click', ()=> {
const v = (joinInput.value || '').trim();
if (!v) return showToast('请输入邀请码或链接');
// 简单验证格式示例
const matched = /^[A-Z0-9]{2,}-[A-Z0-9]{2,}$/.test(v) || v.includes('http');
if (!matched) return showToast('邀请码格式不正确');
showToast('加入请求已发送');
// TODO: 调用后端 API 加入空间
});
// modal actions
function openModal(){ modal.style.display='flex'; }
function closeModal(){ modal.style.display='none'; }
document.getElementById('modalShare').addEventListener('click', ()=>{
// 示例:复制链接并关闭
const fakeLink = location.href + '?invite=' + inviteCodeEl.textContent.trim();
navigator.clipboard.writeText(fakeLink).catch(()=>{});
showToast('邀请链接已复制');
closeModal();
});
// copy chips
document.querySelectorAll('.share-chip').forEach(el=>{
el.addEventListener('click', ()=> {
const action = el.getAttribute('data-action');
if (action === 'link') {
const fakeLink = location.href + '?invite=' + inviteCodeEl.textContent.trim();
navigator.clipboard.writeText(fakeLink).then(()=> showToast('链接已复制'));
} else {
showToast('已选择:' + action);
}
})
});
// small utilities
function showToast(text){
copyToast.textContent = text;
copyToast.style.display = 'block';
setTimeout(()=> copyToast.style.display='none', 1500);
}
// ----------------- 邀请码 / 二维码弹窗 -----------------
function openInviteModal() {
const modal = document.getElementById('inviteModal');
const qr = document.getElementById('qrBox');
const code = Math.random().toString(36).substring(2, 8).toUpperCase();
qr.innerHTML = `二维码:${code}`;
modal.style.display = 'flex';
}
function closeInviteModal() {
document.getElementById('inviteModal').style.display = 'none';
}
// ----------------- 扫码加入弹窗 -----------------
function openScanModal() {
document.getElementById('scanModal').style.display = 'flex';
}
function closeScanModal() {
document.getElementById('scanModal').style.display = 'none';
}
function toggleJoinInput() {
const input = document.getElementById("inviteInput");
const btn = document.getElementById("joinBtn");
const show = input.style.display === 'none';
input.style.display = show ? 'block' : 'none';
btn.style.display = show ? 'block' : 'none';
function randomCode(){
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const pick = (n)=> Array.from({length:n}).map(()=>chars[Math.floor(Math.random()*chars.length)]).join('');
return pick(2)+'-'+pick(4);
}
</script>
</body>
</html>