隐私协议

This commit is contained in:
zhx 2026-05-11 18:14:15 +08:00
parent c62547ee2d
commit 12ebb29f02
5 changed files with 1108 additions and 11 deletions

View File

@ -1,11 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
manager-center
</body>
</html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>经理中心</title>
<link rel="stylesheet" href="./style.css?v=20260511-0102" />
</head>
<body>
<div class="manager-center" data-loading="true">
<nav class="title-bar">
<button class="back-button" type="button" aria-label="返回">
<svg viewBox="0 0 32 32" aria-hidden="true">
<path d="M20 8 12 16l8 8" />
</svg>
</button>
<h1>经理中心</h1>
<span class="title-spacer" aria-hidden="true"></span>
</nav>
<main class="content">
<section class="card profile-card">
<div class="avatar-shell">
<img id="managerAvatar" alt="" hidden />
<span id="managerAvatarFallback">M</span>
</div>
<div class="profile-copy">
<div class="eyebrow">当前经理</div>
<div class="name" id="managerName">-</div>
<div class="meta" id="managerUid">UID: -</div>
</div>
</section>
<section class="card search-card">
<div class="section-head">
<h2>用户</h2>
</div>
<form class="search-form" id="searchForm" autocomplete="off">
<input id="accountInput" type="text" inputmode="text" placeholder="输入用户 ID 或账号" />
<button id="searchButton" type="submit">搜索</button>
</form>
<div class="form-status" id="searchStatus" role="status" hidden></div>
</section>
<section class="card target-card" id="targetCard" hidden>
<div class="avatar-shell">
<img id="targetAvatar" alt="" hidden />
<span id="targetAvatarFallback">U</span>
</div>
<div class="profile-copy">
<div class="eyebrow">目标用户</div>
<div class="name" id="targetName">-</div>
<div class="meta" id="targetUid">UID: -</div>
<div class="pill-row">
<span class="status-pill" id="targetAccountStatus">-</span>
<span class="status-pill" id="targetRechargeStatus">-</span>
</div>
</div>
</section>
<section class="card gift-card">
<div class="section-head">
<h2>赠送道具</h2>
</div>
<label class="field-label" for="propsTypeSelect">礼物类型</label>
<select id="propsTypeSelect">
<option value="RIDE">座驾</option>
<option value="NOBLE_VIP">贵族</option>
<option value="AVATAR_FRAME">头像框</option>
<option value="CHAT_BUBBLE">聊天气泡</option>
<option value="DATA_CARD">资料卡</option>
</select>
<div class="props-status" id="propsStatus" role="status" hidden></div>
<div class="props-list" id="propsList"></div>
<button class="primary-action" id="sendPropsButton" type="button" disabled>赠送</button>
</section>
<section class="card ban-card" id="banCard" hidden>
<div class="section-head">
<h2>封禁用户</h2>
<span class="danger-chip" id="banEligibility">-</span>
</div>
<textarea id="banReason" maxlength="200" rows="3" placeholder="封禁原因"></textarea>
<button class="danger-action" id="banButton" type="button" disabled>封禁</button>
<div class="form-status" id="banStatus" role="status" hidden></div>
</section>
</main>
<div class="loading-mask" id="loadingMask" role="status" aria-live="polite">
<div class="loader" aria-hidden="true"></div>
<span>加载中...</span>
</div>
<div class="toast" id="toast" role="status" hidden></div>
</div>
<script src="./script.js?v=20260511-0104" defer></script>
</body>
</html>

View File

@ -0,0 +1,471 @@
(function () {
const API_BASE_URL = "https://jvapi.haiyihy.com";
const GO_API_PATH_PREFIX = "/go";
const LOCAL_GO_API_BASE_URL = "http://127.0.0.1:1100";
const defaultPropsType = "RIDE";
const state = {
manager: null,
target: null,
propsType: defaultPropsType,
props: [],
selectedPropsId: ""
};
const params = readURLParams();
function readURLParams() {
const result = new URLSearchParams(window.location.search);
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
const hashParams = new URLSearchParams(hashQuery);
hashParams.forEach((value, key) => {
if (!result.has(key)) result.set(key, value);
});
return result;
}
function readRawParam(name) {
const queries = [window.location.search.replace(/^\?/, "")];
if (window.location.hash.includes("?")) queries.push(window.location.hash.split("?")[1]);
for (const query of queries) {
for (const part of query.split("&")) {
if (!part) continue;
const equalIndex = part.indexOf("=");
const rawKey = equalIndex >= 0 ? part.slice(0, equalIndex) : part;
const rawValue = equalIndex >= 0 ? part.slice(equalIndex + 1) : "";
let decodedKey = "";
try {
decodedKey = decodeURIComponent(rawKey);
} catch (error) {
continue;
}
if (decodedKey !== name) continue;
try {
return decodeURIComponent(rawValue.replace(/\+/g, "%2B"));
} catch (error) {
return rawValue;
}
}
}
return "";
}
function $(selector) {
return document.querySelector(selector);
}
function apiBaseURL() {
const override = window.HYAPP_API_BASE_URL || params.get("apiBase");
if (override) return String(override).replace(/\/+$/, "");
return API_BASE_URL.replace(/\/+$/, "");
}
function isLocalDocumentsFile() {
if (window.location.protocol !== "file:") return false;
let pathname = window.location.pathname || "";
try {
pathname = decodeURIComponent(pathname);
} catch (error) {
return false;
}
return pathname.startsWith("/Users/hy/Documents/");
}
function goApiBaseURL() {
const override = window.HYAPP_GO_API_BASE_URL || params.get("goApiBase") || params.get("goBase");
if (override) return String(override).replace(/\/+$/, "");
if (isLocalDocumentsFile()) return `${LOCAL_GO_API_BASE_URL}${GO_API_PATH_PREFIX}`;
return `${apiBaseURL()}${GO_API_PATH_PREFIX}`;
}
function buildRequestURL(baseURL, path) {
const base = `${String(baseURL || API_BASE_URL).replace(/\/+$/, "")}/`;
const target = String(path || "").replace(/^\/+/, "");
return new URL(target, base).toString();
}
function normalizeAuthorization(token) {
const value = String(token || "").trim();
if (!value) return "";
return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`;
}
function authHeadersFromToken(token) {
const sysOrigin = params.get("sysOrigin") || params.get("origin") || "ATYOU";
return {
Authorization: normalizeAuthorization(token),
"req-lang": params.get("lang") || "zh-CN",
"req-client": "H5",
"req-version": "V2",
"req-zone": Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai",
"req-app-intel": "version=1.0.0;build=1;model=H5;sysVersion=Web;channel=Web",
"req-sys-origin": `origin=${sysOrigin};originChild=${sysOrigin}`
};
}
async function requestGoJSON(path, options = {}) {
const token = readRawParam("token");
if (!token) {
const error = new Error("缺少登录信息");
error.code = "missing_token";
throw error;
}
const headers = authHeadersFromToken(token);
const request = {
method: options.method || "GET",
cache: "no-store",
headers
};
if (options.body !== undefined) {
headers["Content-Type"] = "application/json";
request.body = JSON.stringify(options.body);
}
const response = await fetch(buildRequestURL(goApiBaseURL(), path), request);
const data = await response.json().catch(() => ({}));
if (!response.ok || data.status === false) {
const error = new Error(data.errorMsg || data.message || `Request failed: ${response.status}`);
error.code = data.code || data.errorCodeName || "";
error.response = data;
throw error;
}
return data.body ?? data.data ?? null;
}
function endpointProfile() {
return "/app/h5/manager-center/profile";
}
function endpointSearchUser(account) {
return `/app/h5/manager-center/users/search?account=${encodeURIComponent(account)}`;
}
function endpointProps(type) {
return `/app/h5/manager-center/props?type=${encodeURIComponent(type || "")}`;
}
function endpointSendProps() {
return "/app/h5/manager-center/props/send";
}
function endpointBanUser() {
return "/app/h5/manager-center/users/ban";
}
function setLoading(isLoading) {
const root = $(".manager-center");
const mask = $("#loadingMask");
if (root) root.dataset.loading = isLoading ? "true" : "false";
if (mask) mask.hidden = !isLoading;
}
function setStatus(node, text, type = "") {
if (!node) return;
node.textContent = text || "";
node.hidden = !text;
node.classList.toggle("is-error", type === "error");
}
function showToast(text) {
const toast = $("#toast");
if (!toast) return;
toast.textContent = text || "";
toast.hidden = !text;
window.clearTimeout(showToast.timer);
showToast.timer = window.setTimeout(() => {
toast.hidden = true;
}, 2300);
}
function closePage() {
try {
if (window.app && typeof window.app.closePage === "function") {
window.app.closePage();
return;
}
if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") {
window.FlutterPageControl.postMessage("close_page");
return;
}
if (window.history.length > 1) window.history.back();
} catch (error) {
console.error("close manager center failed:", error);
if (window.history.length > 1) window.history.back();
}
}
function firstLetter(name, fallback) {
const value = String(name || fallback || "U").trim();
return value ? value.slice(0, 1).toUpperCase() : "U";
}
function renderAvatar(imgSelector, fallbackSelector, url, name, fallback) {
const img = $(imgSelector);
const fallbackNode = $(fallbackSelector);
if (!img || !fallbackNode) return;
const src = String(url || "").trim();
fallbackNode.textContent = firstLetter(name, fallback);
if (src) {
img.src = src;
img.hidden = false;
fallbackNode.hidden = true;
} else {
img.removeAttribute("src");
img.hidden = true;
fallbackNode.hidden = false;
}
}
function displayName(user) {
return user?.userNickname || user?.nickname || user?.name || "-";
}
function displayUserID(user) {
return String(user?.userId || user?.id || "-");
}
function displayAccount(user) {
return user?.account ? ` (${user.account})` : "";
}
function canBanTarget(target) {
return Boolean(target) && target.canBan !== false && !target.hasRecharge;
}
function renderManager(manager) {
$("#managerName").textContent = displayName(manager);
$("#managerUid").textContent = `UID: ${displayUserID(manager)}${displayAccount(manager)}`;
renderAvatar("#managerAvatar", "#managerAvatarFallback", manager?.userAvatar, displayName(manager), "M");
}
function renderTarget() {
const card = $("#targetCard");
const banCard = $("#banCard");
if (!state.target) {
if (card) card.hidden = true;
if (banCard) banCard.hidden = true;
updateActionState();
return;
}
const target = state.target;
card.hidden = false;
banCard.hidden = false;
$("#targetName").textContent = displayName(target);
$("#targetUid").textContent = `UID: ${displayUserID(target)}${displayAccount(target)}`;
$("#targetAccountStatus").textContent = target.accountStatus || "-";
const rechargeStatus = $("#targetRechargeStatus");
const canBan = canBanTarget(target);
rechargeStatus.textContent = target.hasRecharge ? "已充值" : (canBan ? "未充值" : "充值状态未知");
rechargeStatus.classList.toggle("is-danger", Boolean(target.hasRecharge) || !canBan);
const banEligibility = $("#banEligibility");
banEligibility.textContent = canBan ? "可封禁" : "不可封禁";
banEligibility.classList.toggle("is-danger", !canBan);
renderAvatar("#targetAvatar", "#targetAvatarFallback", target.userAvatar, displayName(target), "U");
updateActionState();
}
function renderProps() {
const list = $("#propsList");
if (!list) return;
list.replaceChildren();
if (!state.props.length) {
setStatus($("#propsStatus"), "没有可赠送道具");
updateActionState();
return;
}
setStatus($("#propsStatus"), "");
state.props.forEach((item) => {
const button = document.createElement("button");
button.type = "button";
button.className = "prop-option";
button.dataset.propsId = String(item.id);
if (String(item.id) === String(state.selectedPropsId)) {
button.classList.add("is-selected");
}
const cover = document.createElement("span");
cover.className = "prop-cover";
if (item.cover || item.sourceUrl) {
const img = document.createElement("img");
img.alt = "";
img.src = item.cover || item.sourceUrl;
cover.appendChild(img);
} else {
const fallback = document.createElement("span");
fallback.textContent = firstLetter(item.name, "G");
cover.appendChild(fallback);
}
const copy = document.createElement("span");
copy.className = "prop-copy";
const name = document.createElement("span");
name.className = "prop-name";
name.textContent = item.name || `ID ${item.id}`;
const meta = document.createElement("span");
meta.className = "prop-meta";
meta.textContent = item.vipLevel ? `${item.type} · VIP ${item.vipLevel}` : item.type;
copy.append(name, meta);
const days = document.createElement("span");
days.className = "prop-days";
days.textContent = `${item.days || 0}`;
button.append(cover, copy, days);
button.addEventListener("click", () => {
state.selectedPropsId = String(item.id);
renderProps();
updateActionState();
});
list.appendChild(button);
});
updateActionState();
}
function updateActionState() {
const sendButton = $("#sendPropsButton");
const banButton = $("#banButton");
if (sendButton) sendButton.disabled = !state.target || !state.selectedPropsId;
if (banButton) banButton.disabled = !canBanTarget(state.target);
}
async function loadProfile() {
const body = await requestGoJSON(endpointProfile());
state.manager = body?.manager || body || {};
renderManager(state.manager);
}
async function loadProps() {
setStatus($("#propsStatus"), "加载中...");
state.props = [];
state.selectedPropsId = "";
renderProps();
const body = await requestGoJSON(endpointProps(state.propsType));
state.props = Array.isArray(body?.records) ? body.records : [];
state.selectedPropsId = state.props.length ? String(state.props[0].id) : "";
renderProps();
}
async function searchUser(event) {
event.preventDefault();
const input = $("#accountInput");
const account = String(input?.value || "").trim();
if (!account) {
setStatus($("#searchStatus"), "请输入用户 ID 或账号", "error");
return;
}
$("#searchButton").disabled = true;
setStatus($("#searchStatus"), "搜索中...");
try {
const body = await requestGoJSON(endpointSearchUser(account));
state.target = body?.user || body || null;
renderTarget();
setStatus($("#searchStatus"), "");
} catch (error) {
state.target = null;
renderTarget();
setStatus($("#searchStatus"), friendlyError(error), "error");
} finally {
$("#searchButton").disabled = false;
}
}
async function sendProps() {
if (!state.target || !state.selectedPropsId) return;
if (!window.confirm("确认赠送该道具?")) return;
const button = $("#sendPropsButton");
button.disabled = true;
try {
await requestGoJSON(endpointSendProps(), {
method: "POST",
body: {
acceptUserId: displayUserID(state.target),
propsId: state.selectedPropsId
}
});
showToast("赠送成功");
} catch (error) {
showToast(friendlyError(error));
} finally {
updateActionState();
}
}
async function banUser() {
if (!canBanTarget(state.target)) return;
if (!window.confirm("确认封禁该用户?")) return;
const button = $("#banButton");
button.disabled = true;
setStatus($("#banStatus"), "提交中...");
try {
await requestGoJSON(endpointBanUser(), {
method: "POST",
body: {
userId: displayUserID(state.target),
reason: $("#banReason").value || ""
}
});
state.target.accountStatus = "ARCHIVE";
renderTarget();
setStatus($("#banStatus"), "");
showToast("封禁成功");
} catch (error) {
setStatus($("#banStatus"), friendlyError(error), "error");
} finally {
updateActionState();
}
}
function friendlyError(error) {
const code = error?.code || error?.response?.code || error?.response?.errorCodeName || "";
if (code === "missing_token") return "缺少登录信息";
if (code === "not_manager") return "当前账号不是经理";
if (code === "user_not_found") return "用户不存在";
if (code === "user_recharged") return "该用户已有充值记录,不能封禁";
if (code === "prop_not_giftable") return "该道具未开放经理赠送";
if (code === "vip_not_giftable") return "该贵族道具不可赠送";
return error?.message || "请求失败";
}
function bindEvents() {
$(".back-button")?.addEventListener("click", closePage);
$("#searchForm")?.addEventListener("submit", searchUser);
$("#propsTypeSelect")?.addEventListener("change", async (event) => {
state.propsType = event.target.value || defaultPropsType;
try {
await loadProps();
} catch (error) {
setStatus($("#propsStatus"), friendlyError(error), "error");
}
});
$("#sendPropsButton")?.addEventListener("click", sendProps);
$("#banButton")?.addEventListener("click", banUser);
}
async function init() {
bindEvents();
$("#propsTypeSelect").value = state.propsType;
if (!readRawParam("token")) {
setLoading(false);
setStatus($("#searchStatus"), "缺少登录信息", "error");
return;
}
try {
await Promise.all([loadProfile(), loadProps()]);
} catch (error) {
showToast(friendlyError(error));
} finally {
setLoading(false);
}
}
init();
})();

View File

@ -0,0 +1,460 @@
:root {
--page-bg: #f5f6f8;
--card-bg: #ffffff;
--text: #20242a;
--muted: #7c8592;
--line: #e7ebf0;
--primary: #18b89f;
--primary-dark: #0b8b79;
--danger: #d83c3c;
--danger-bg: #fff0f0;
--gold: #c79633;
--shadow: 0 10px 28px rgba(31, 41, 55, 0.08);
}
* {
box-sizing: border-box;
}
html,
body {
width: 100%;
min-height: 100%;
margin: 0;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}
body {
display: flex;
justify-content: center;
background: #16191d;
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
border: 0;
}
button:disabled {
cursor: not-allowed;
opacity: 0.48;
}
[hidden] {
display: none !important;
}
.manager-center {
position: relative;
width: 100%;
max-width: 430px;
min-height: 100vh;
overflow: hidden;
background:
linear-gradient(180deg, #eaf7f4 0, #f5f6f8 210px),
var(--page-bg);
}
.manager-center[data-loading="true"] .title-bar,
.manager-center[data-loading="true"] .content {
visibility: hidden;
}
.title-bar {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: 48px 1fr 48px;
align-items: center;
min-height: 60px;
padding: env(safe-area-inset-top) 14px 0;
}
.title-bar h1 {
margin: 0;
text-align: center;
font-size: 20px;
font-weight: 800;
letter-spacing: 0;
}
.back-button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.76);
color: var(--text);
box-shadow: 0 4px 14px rgba(32, 36, 42, 0.08);
}
.back-button svg {
width: 26px;
height: 26px;
fill: none;
stroke: currentColor;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
}
.content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px 14px calc(28px + env(safe-area-inset-bottom));
}
.card {
border: 1px solid rgba(231, 235, 240, 0.9);
border-radius: 8px;
background: var(--card-bg);
box-shadow: var(--shadow);
}
.profile-card,
.target-card {
display: flex;
gap: 12px;
align-items: center;
min-height: 96px;
padding: 14px;
}
.avatar-shell {
position: relative;
flex: 0 0 58px;
width: 58px;
height: 58px;
overflow: hidden;
border-radius: 50%;
background: #dfe7ea;
}
.avatar-shell img,
.avatar-shell span {
display: block;
width: 100%;
height: 100%;
}
.avatar-shell img {
object-fit: cover;
}
.avatar-shell span {
display: flex;
align-items: center;
justify-content: center;
color: #51606d;
font-size: 24px;
font-weight: 800;
}
.profile-copy {
min-width: 0;
}
.eyebrow {
margin-bottom: 4px;
color: var(--primary-dark);
font-size: 12px;
font-weight: 800;
}
.name {
overflow: hidden;
color: var(--text);
font-size: 17px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.meta {
margin-top: 3px;
color: var(--muted);
font-size: 13px;
}
.pill-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
}
.status-pill,
.danger-chip {
display: inline-flex;
align-items: center;
min-height: 24px;
border-radius: 999px;
padding: 0 9px;
background: #edf5f2;
color: #315e56;
font-size: 12px;
font-weight: 800;
}
.status-pill.is-danger,
.danger-chip.is-danger {
background: var(--danger-bg);
color: var(--danger);
}
.search-card,
.gift-card,
.ban-card {
padding: 14px;
}
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 12px;
}
.section-head h2 {
margin: 0;
font-size: 16px;
font-weight: 900;
letter-spacing: 0;
}
.search-form {
display: grid;
grid-template-columns: 1fr 88px;
gap: 8px;
}
input,
select,
textarea {
width: 100%;
border: 1px solid var(--line);
border-radius: 8px;
outline: none;
background: #f9fafb;
color: var(--text);
}
input,
select {
height: 44px;
padding: 0 12px;
}
textarea {
min-height: 84px;
resize: none;
padding: 12px;
}
input:focus,
select:focus,
textarea:focus {
border-color: var(--primary);
background: #fff;
box-shadow: 0 0 0 3px rgba(24, 184, 159, 0.13);
}
.search-form button,
.primary-action,
.danger-action {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
border-radius: 8px;
padding: 0 16px;
color: #fff;
font-weight: 900;
}
.search-form button,
.primary-action {
background: var(--primary);
}
.danger-action {
width: 100%;
margin-top: 10px;
background: var(--danger);
}
.primary-action {
width: 100%;
margin-top: 12px;
}
.field-label {
display: block;
margin-bottom: 8px;
color: var(--muted);
font-size: 13px;
font-weight: 800;
}
.props-list {
display: grid;
gap: 8px;
margin-top: 12px;
}
.prop-option {
display: grid;
grid-template-columns: 52px 1fr auto;
align-items: center;
gap: 10px;
width: 100%;
min-height: 68px;
border: 1px solid var(--line);
border-radius: 8px;
padding: 8px;
background: #fff;
color: inherit;
text-align: left;
}
.prop-option.is-selected {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(24, 184, 159, 0.13);
}
.prop-cover {
width: 52px;
height: 52px;
overflow: hidden;
border-radius: 8px;
background: #eef2f5;
}
.prop-cover img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.prop-cover span {
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
color: var(--gold);
font-weight: 900;
}
.prop-copy {
min-width: 0;
}
.prop-name {
overflow: hidden;
font-size: 15px;
font-weight: 900;
text-overflow: ellipsis;
white-space: nowrap;
}
.prop-meta {
margin-top: 4px;
color: var(--muted);
font-size: 12px;
}
.prop-days {
border-radius: 999px;
padding: 5px 8px;
background: #f2f4f7;
color: #596273;
font-size: 12px;
font-weight: 900;
}
.form-status,
.props-status {
margin-top: 10px;
border-radius: 8px;
padding: 9px 10px;
background: #f1f5f9;
color: #475569;
font-size: 13px;
font-weight: 700;
}
.form-status.is-error,
.props-status.is-error {
background: var(--danger-bg);
color: var(--danger);
}
.loading-mask {
position: absolute;
z-index: 20;
inset: 0;
display: flex;
min-height: 100vh;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
background: rgba(245, 246, 248, 0.96);
color: var(--primary-dark);
font-weight: 900;
}
.loader {
width: 34px;
height: 34px;
border: 4px solid rgba(24, 184, 159, 0.18);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 0.82s linear infinite;
}
.toast {
position: fixed;
z-index: 40;
left: 50%;
bottom: calc(24px + env(safe-area-inset-bottom));
max-width: min(330px, calc(100vw - 40px));
transform: translateX(-50%);
border-radius: 8px;
padding: 10px 14px;
background: rgba(29, 34, 40, 0.92);
color: #fff;
font-size: 13px;
font-weight: 800;
text-align: center;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@media (min-width: 720px) {
.manager-center {
min-height: 760px;
}
}

View File

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Delete Lalu Voice Party Account | Lalu Voice Party</title>
<meta name="description" content="This page explains how Lalu Voice Party users can request permanent deletion of their account and associated data.">
<link rel="canonical" href="https://h5.haiyihy.com/laludelete-account.html">
</head>
<body>
<h1>Delete Lalu Voice Party Account</h1>
<p>Effective date: 2026-04-13</p>
<p>Contact: chekrindo@gmail.com / +85247410298</p>
<p><a href="mailto:chekrindo@gmail.com?subject=Delete%20Lalu%20Voice%20Party%20Account">Start a deletion request by email</a></p>
<p>This page explains how Lalu Voice Party users can request permanent deletion of their account and associated data.</p>
<h2>1. Who can use this page</h2>
<p>Use this page if you created a Lalu Voice Party account and want to request permanent deletion of that account and the associated user data managed by Lalu Voice Party.</p><p>You can submit a deletion request even if you no longer have access to the Lalu Voice Party app on your device.</p>
<h2>2. How to submit a deletion request</h2>
<p>Send your deletion request by email to chekrindo@gmail.com.</p><p>Please use the subject line: Delete Lalu Voice Party Account.</p>
<p>- Your Lalu Voice Party UID, if available.</p><p>- Your registered email address or phone number.</p><p>- Your Lalu Voice Party nickname or profile name.</p><p>- A clear statement that you want your Lalu Voice Party account and associated data to be deleted.</p>
<h2>3. Identity verification</h2>
<p>To protect account security and prevent unauthorized deletion, we may ask you to complete a reasonable identity-verification step before processing your request.</p>
<h2>4. What will be deleted</h2>
<p>After your request is verified and approved, we will delete your Lalu Voice Party account and the associated account data that is managed in our systems, including profile information and other user data linked to that account, unless retention is required for legitimate reasons described below.</p>
<h2>5. Data that may be retained</h2>
<p>Certain records may be retained for a limited period where reasonably necessary for fraud prevention, abuse handling, dispute resolution, financial recordkeeping, tax, security, or legal and regulatory compliance.</p>
<h2>6. Processing time</h2>
<p>We aim to complete verified deletion requests within 7 business days, although certain cases may require additional time if further verification or compliance review is necessary.</p>
<h2>7. Contact</h2>
<p>If you have questions about account deletion or personal-data handling, contact chekrindo@gmail.com or call +85247410298.</p>
</body>
</html>

41
h5/laluprivacy.html Normal file
View File

@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Privacy Agreement | Lalu Voice Party</title>
<meta name="description" content="Privacy Agreement for Lalu Voice Party. Learn how we collect, use, and protect your personal information.">
<link rel="canonical" href="https://h5.haiyihy.com/laluprivacy.html">
</head>
<body>
<h1>Privacy Agreement for Lalu Voice Party</h1>
<p>We at Lalu Voice Party value your privacy and are committed to protecting your personal information. This privacy policy explains how we collect, use, and protect your information. By using our app, you agree to the practices described in this privacy policy. Please read this policy carefully.</p>
<h2>1. Information Collection</h2>
<p>We may collect the following information:</p>
<p>Personal Information: The information you provide to us during the use of the app, including your device number, mobile phone model, version number, and other related data.</p>
<p>Use Data: Information about how you use the app, including device information and any other data generated during your interactions with the app.</p>
<h2>2. Use of Information</h2>
<p>We will use the personal information we collect for the following purposes:</p>
<p>To offer you a personalized experience and provide services tailored to your needs.</p>
<p>To understand how you use our app and make improvements based on your feedback.</p>
<p>To send you updates, offers, and notifications regarding the app.</p>
<p>We will not sell, rent, or share your personal information with third parties, unless we have your explicit consent or are required by law to do so.</p>
<h2>3. Information Protection</h2>
<p>We take the security of your personal information seriously. We employ various security technologies and procedures to protect your data from loss, misuse, unauthorized access, or disclosure. However, please be aware that no security system is completely infallible, and we cannot fully guarantee the absolute security of your information over the internet.</p>
<h2>4. Third-Party Links</h2>
<p>Our app may contain links to third-party websites or services. We are not responsible for the privacy practices or content of these external websites. Please review their privacy policies before engaging with any third-party sites.</p>
<h2>5. Updates to the Privacy Agreement</h2>
<p>We may update this Privacy Policy from time to time to reflect changes in our business practices or legal requirements. The updated policy will be posted on our website, and we will notify you of any changes before they take effect. Please review this privacy policy periodically to stay informed about any updates.</p>
<p>Last updated: April 16th, 2026</p>
<h2>6. Contact Information</h2>
<p>If you have any questions or concerns regarding our Privacy Policy or the protection of your personal information, please feel free to contact us at:</p>
<p>Email: chekrindo@gmail.com</p>
<p>We are committed to addressing your concerns in a timely and effective manner.</p>
</body>
</html>