init: laimiao-web-new
This commit is contained in:
commit
c226cf7054
122 files changed
+18458
No files matched your search
@@ -0,0 +1,459 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="theme-color" content="#F5F8EF" />
|
||||
<meta name="format-detection" content="telephone=no,email=no,address=no" />
|
||||
<title>邀请好友|来秒AI</title>
|
||||
<link
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='9' fill='%2308482D'/%3E%3Cpath d='M18.6 3 7.5 18h6.7l-1.7 11L24.5 13h-6.6l.7-10Z' fill='%23DAE7C7'/%3E%3C/svg%3E"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.cn" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.cn/css2?family=Inter:wght@400;500;600;700;800;900&family=Noto+Sans+SC:wght@400;500;700;900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script>
|
||||
window.__TEAM_API_BASE__ = "%VITE_TEAM_API_URL%";
|
||||
</script>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #f5f8ef;
|
||||
color: #101410;
|
||||
font-family: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'PingFang SC',
|
||||
'Microsoft YaHei', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 28px;
|
||||
padding: 32px 20px 40px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #101410;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.brand svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
padding: 36px 28px 28px;
|
||||
border-radius: 28px;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(8, 72, 45, 0.08);
|
||||
box-shadow: 0 18px 48px -32px rgba(8, 72, 45, 0.45);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 76px;
|
||||
height: 76px;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
background: #eef2e6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #08482d;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin: 0 auto 18px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin: 8px 0 22px;
|
||||
color: #6b7268;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.reward {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 14px;
|
||||
background: #fffbf6;
|
||||
border: 1px solid #ffedd5;
|
||||
color: #c44714;
|
||||
font-size: 13px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.reward strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(8, 72, 45, 0.16);
|
||||
background: #fff;
|
||||
color: #08482d;
|
||||
font-family: inherit;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
border-color: rgba(8, 72, 45, 0.4);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background: #08482d;
|
||||
border-color: #08482d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn--primary:hover {
|
||||
background: #063a24;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid #08482d;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.state {
|
||||
padding: 18px 6px 4px;
|
||||
}
|
||||
|
||||
.state h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.state p {
|
||||
margin: 0;
|
||||
color: #6b7268;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin: 24px auto;
|
||||
border-radius: 999px;
|
||||
border: 3px solid #dae7c7;
|
||||
border-top-color: #08482d;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: 36px;
|
||||
transform: translateX(-50%) translateY(10px);
|
||||
padding: 10px 18px;
|
||||
border-radius: 999px;
|
||||
background: rgba(16, 20, 16, 0.88);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.toast.is-show {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<a class="brand" href="/">
|
||||
<svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
||||
<path d="M19 2 5 19h8l-2 11L27 12h-9l1-10Z" fill="#08482D" />
|
||||
<path d="M19 2 11 12" fill="none" stroke="#DAE7C7" stroke-width="1.4" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span>来秒AI</span>
|
||||
</a>
|
||||
|
||||
<section class="card">
|
||||
<div id="loading" class="spinner"></div>
|
||||
|
||||
<div id="invalid" class="state" hidden>
|
||||
<h2>邀请链接无效</h2>
|
||||
<p id="invalidText">邀请码不存在或已失效</p>
|
||||
<div class="actions" style="margin-top: 20px">
|
||||
<a class="btn btn--primary" href="/" style="display: flex; align-items: center; justify-content: center; text-decoration: none">返回首页</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="content" hidden>
|
||||
<div class="avatar" id="avatar"></div>
|
||||
<h1 class="name" id="nickname">好友</h1>
|
||||
<p class="desc" id="desc">邀请你一起用 来秒AI 把灵感变成作品</p>
|
||||
|
||||
<div class="reward">
|
||||
<span>加入即可获得</span>
|
||||
<strong>200</strong>
|
||||
<span>算力积分奖励</span>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn--primary" id="acceptBtn" type="button" hidden>开始使用来秒AI</button>
|
||||
<button class="btn btn--primary" id="loginBtn" type="button" hidden>微信一键登录</button>
|
||||
<a class="btn" href="/" style="display: flex; align-items: center; justify-content: center; text-decoration: none">了解来秒AI</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const API = (function () {
|
||||
const base = String(window.__TEAM_API_BASE__ || "");
|
||||
return base && base.charAt(0) !== "%" ? base.replace(/\/+$/, "") : "";
|
||||
})();
|
||||
|
||||
const PENDING_KEY = "invite_pending_code";
|
||||
|
||||
const loadingEl = document.getElementById("loading");
|
||||
const invalidEl = document.getElementById("invalid");
|
||||
const invalidTextEl = document.getElementById("invalidText");
|
||||
const contentEl = document.getElementById("content");
|
||||
const avatarEl = document.getElementById("avatar");
|
||||
const nicknameEl = document.getElementById("nickname");
|
||||
const descEl = document.getElementById("desc");
|
||||
const acceptBtn = document.getElementById("acceptBtn");
|
||||
const loginBtn = document.getElementById("loginBtn");
|
||||
const toastEl = document.getElementById("toast");
|
||||
|
||||
let toastTimer = 0;
|
||||
function showToast(message) {
|
||||
toastEl.textContent = message;
|
||||
toastEl.classList.add("is-show");
|
||||
window.clearTimeout(toastTimer);
|
||||
toastTimer = window.setTimeout(function () {
|
||||
toastEl.classList.remove("is-show");
|
||||
}, 2600);
|
||||
}
|
||||
|
||||
function readToken() {
|
||||
return localStorage.getItem("access_token") || "";
|
||||
}
|
||||
|
||||
function inviteCode() {
|
||||
const fromQuery = new URLSearchParams(window.location.search).get("code");
|
||||
if (fromQuery) return fromQuery.trim();
|
||||
const match = window.location.pathname.match(/\/invite\/([A-Za-z0-9]+)/);
|
||||
return match ? match[1] : "";
|
||||
}
|
||||
|
||||
function currentInviteUrl(code) {
|
||||
return window.location.origin + "/invite/?code=" + encodeURIComponent(code);
|
||||
}
|
||||
|
||||
async function parseJson(response) {
|
||||
const text = await response.text();
|
||||
try {
|
||||
return JSON.parse(text.replace(/([\[:,]\s*)(\d{16,})(\s*[,\]}])/g, '$1"$2"$3'));
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function showInvalid(message) {
|
||||
loadingEl.hidden = true;
|
||||
contentEl.hidden = true;
|
||||
invalidEl.hidden = false;
|
||||
if (message) invalidTextEl.textContent = message;
|
||||
}
|
||||
|
||||
function renderUser(info) {
|
||||
const nickname = info.nickname || "好友";
|
||||
nicknameEl.textContent = nickname;
|
||||
descEl.textContent = "邀请你一起用 来秒AI 把灵感快速变成可发布的图片";
|
||||
avatarEl.textContent = "";
|
||||
if (info.avatar) {
|
||||
const img = document.createElement("img");
|
||||
img.src = info.avatar;
|
||||
img.alt = nickname;
|
||||
img.referrerPolicy = "no-referrer";
|
||||
avatarEl.appendChild(img);
|
||||
} else {
|
||||
avatarEl.textContent = nickname.slice(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
async function acceptInvite(code) {
|
||||
const token = readToken();
|
||||
if (!token) return false;
|
||||
acceptBtn.disabled = true;
|
||||
try {
|
||||
const response = await fetch(API + "/api/user/invitation/invite", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Authorization: token },
|
||||
body: JSON.stringify({ code: code }),
|
||||
});
|
||||
const data = await parseJson(response);
|
||||
if (data.ret === 101010) {
|
||||
localStorage.removeItem("access_token");
|
||||
return false;
|
||||
}
|
||||
if (data.ret !== undefined && data.ret !== 0) {
|
||||
const message = String(data.msg || "邀请失败,请重试");
|
||||
showToast(message.includes("重复绑定") ? "你已绑定过邀请人,直接开始创作吧" : message);
|
||||
} else {
|
||||
showToast(data.msg || "邀请成功,奖励已发放");
|
||||
}
|
||||
sessionStorage.removeItem(PENDING_KEY);
|
||||
window.location.href = "/generate";
|
||||
return true;
|
||||
} catch (error) {
|
||||
showToast("邀请失败,请重试");
|
||||
return false;
|
||||
} finally {
|
||||
acceptBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function goLogin(code) {
|
||||
sessionStorage.setItem(PENDING_KEY, code);
|
||||
const target = currentInviteUrl(code);
|
||||
window.location.href = "/login/?redirect=" + encodeURIComponent(target);
|
||||
}
|
||||
|
||||
async function init() {
|
||||
const code = inviteCode();
|
||||
if (!code) {
|
||||
showInvalid("缺少邀请码,请确认链接是否完整");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(API + "/api/user/invite/role?code=" + encodeURIComponent(code));
|
||||
const data = await parseJson(response);
|
||||
if (data.ret !== undefined && data.ret !== 0) {
|
||||
showInvalid(data.msg || "邀请码不存在或已失效");
|
||||
return;
|
||||
}
|
||||
if (data.role === "team") {
|
||||
showInvalid("该邀请链接不是好友邀请");
|
||||
return;
|
||||
}
|
||||
|
||||
loadingEl.hidden = true;
|
||||
contentEl.hidden = false;
|
||||
renderUser({ nickname: data.nickname, avatar: data.avatar });
|
||||
|
||||
const loggedIn = !!readToken();
|
||||
if (loggedIn) {
|
||||
acceptBtn.hidden = false;
|
||||
acceptBtn.addEventListener("click", function () {
|
||||
void acceptInvite(code);
|
||||
});
|
||||
|
||||
if (sessionStorage.getItem(PENDING_KEY) === code) {
|
||||
void acceptInvite(code);
|
||||
}
|
||||
} else {
|
||||
loginBtn.hidden = false;
|
||||
loginBtn.addEventListener("click", function () {
|
||||
goLogin(code);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
showInvalid("无法获取邀请信息,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
void init();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user