chore: update h5 center static pages
13
h5/KYC/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>KYC</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"KYC","mode":"bankCards"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,35 +1,67 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
>
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>Yumi H5</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.__H5_ROUTE_PATH__ = "/admin-center";
|
||||
</script>
|
||||
<script src="/assets/yumi-route-entry.js"></script>
|
||||
<script type="module" crossorigin src="/js/index-CIAVq8iD-1776148658686.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/css/index-VRlNQ74k-1776148661679.css">
|
||||
<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>Admin Center</title>
|
||||
<link rel="stylesheet" href="../center-static/center-static.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div class="center-page">
|
||||
<header class="page-header">
|
||||
<button class="icon-button" type="button" data-action="back" aria-label="Back"><span>‹</span></button>
|
||||
<h1 id="pageTitle">Admin Center</h1>
|
||||
<button class="language-button" type="button" data-action="language">EN</button>
|
||||
</header>
|
||||
<main class="content">
|
||||
<section class="surface-card hero-card">
|
||||
<div class="hero-row">
|
||||
<div class="hero-title" id="heroTitle">My available salary</div>
|
||||
<button class="hero-link" id="heroLink" type="button">Details</button>
|
||||
</div>
|
||||
<div class="hero-value" id="heroValue">$0</div>
|
||||
</section>
|
||||
<section class="summary-grid" id="summaryGrid"></section>
|
||||
<div id="sections"></div>
|
||||
</main>
|
||||
<div class="toast" id="toast" hidden></div>
|
||||
</div>
|
||||
<script>
|
||||
window.CENTER_PAGE = {
|
||||
title: "Admin Center",
|
||||
identity: "ADMIN",
|
||||
heroTitle: "My available salary",
|
||||
heroLink: "Available income",
|
||||
heroRoute: "/available-income",
|
||||
balanceType: "ADMIN_SALARY",
|
||||
summaries: [
|
||||
{ label: "Role", value: "Admin" },
|
||||
{ label: "Scope", value: "BD / Agency" }
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
title: "Operations",
|
||||
items: [
|
||||
{ label: "Item distribution", route: "/item-distribution", icon: "I" },
|
||||
{ label: "Admin policy", route: "/admin-policy", icon: "P" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Teams",
|
||||
items: [
|
||||
{ label: "Invite BD Leader", route: "/invite-bd-leader", icon: "+" },
|
||||
{ label: "My BD Leader teams", route: "/my-BDLeader-teams", icon: "L" },
|
||||
{ label: "Invite BD", route: "/invite-bd", icon: "+" },
|
||||
{ label: "My BD teams", route: "/my-BD-teams", icon: "B" },
|
||||
{ label: "Invite Agency", route: "/invite-agency", icon: "+" },
|
||||
{ label: "My Agency teams", route: "/my-agency-teams", icon: "A" },
|
||||
{ label: "Invite Recharge Agency", route: "/invite-recharge-agency", icon: "+" },
|
||||
{ label: "My Recharge Agency", route: "/my-recharge-agency", icon: "R" }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
</script>
|
||||
<script src="../center-static/center-static.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
13
h5/admin-policy/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Admin Policy</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Admin Policy","mode":"policy"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
414
h5/agency-center/agency-center.css
Normal file
@ -0,0 +1,414 @@
|
||||
@font-face {
|
||||
font-family: "Baloo 2";
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Baloo 2";
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #21342a;
|
||||
background: #edf8f1;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.agency-center {
|
||||
min-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background-color: #edf8f1;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.agency-center::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr auto;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: calc(48px + env(safe-area-inset-top));
|
||||
padding: env(safe-area-inset-top) 10px 0;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
min-width: 0;
|
||||
font-size: 1.35em;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #153d28;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.icon-button,
|
||||
.message-button,
|
||||
.language-button {
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
color: #1f6f46;
|
||||
}
|
||||
|
||||
.icon-button span {
|
||||
display: block;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.message-button {
|
||||
width: 36px;
|
||||
border-radius: 50%;
|
||||
background: #fff3c8;
|
||||
color: #7d5b09;
|
||||
}
|
||||
|
||||
.message-button span {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
background: #f3c74e;
|
||||
color: #5d4200;
|
||||
font-size: 0.8em;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.language-button {
|
||||
min-width: 42px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: #dff5e7;
|
||||
color: #24724a;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.surface-card {
|
||||
position: relative;
|
||||
border: 1px solid #b7e0c6;
|
||||
border-radius: 18px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
}
|
||||
|
||||
.salary-card {
|
||||
padding: 18px;
|
||||
background: #f8fff9;
|
||||
border-color: #94d9ae;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.salary-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 20px;
|
||||
background: #d7f5e2;
|
||||
border: 10px solid #ecfbf1;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.salary-title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.salary-title span {
|
||||
min-width: 0;
|
||||
font-weight: 600;
|
||||
color: #244735;
|
||||
}
|
||||
|
||||
.salary-title button {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: #fff3c8;
|
||||
color: #846216;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.salary-amount {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
color: #105c36;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
margin: 0 1%;
|
||||
padding: 16px;
|
||||
background: #fffaf0;
|
||||
border-color: #e7c777;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
margin-bottom: 12px;
|
||||
font-weight: 700;
|
||||
color: #3f3826;
|
||||
}
|
||||
|
||||
.task-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.task-cell {
|
||||
min-width: 0;
|
||||
padding: 8px;
|
||||
border-radius: 12px;
|
||||
background: #fffdf7;
|
||||
border: 1px solid #f0dfaa;
|
||||
}
|
||||
|
||||
.task-label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 0.86em;
|
||||
font-weight: 700;
|
||||
color: #4b4536;
|
||||
}
|
||||
|
||||
.task-value {
|
||||
font-size: 0.95em;
|
||||
font-weight: 800;
|
||||
color: #1f3528;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.menu-card {
|
||||
margin: 0 1% 12px;
|
||||
padding: 10px;
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.menu-row {
|
||||
width: 100%;
|
||||
min-height: 4.5em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 1) {
|
||||
background: #eefbf3;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 3) {
|
||||
background: #f7fbec;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 5) {
|
||||
background: #eef7fb;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 7) {
|
||||
background: #fff5ed;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.menu-label img,
|
||||
.menu-symbol {
|
||||
display: flex;
|
||||
width: 2.7em;
|
||||
padding: 0.55em;
|
||||
aspect-ratio: 1 / 1;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 14px;
|
||||
background: #d7f5e2;
|
||||
border: 1px solid #bce5ca;
|
||||
color: #1e6b43;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 5) .menu-label img,
|
||||
.menu-row:nth-of-type(4n + 5) .menu-symbol {
|
||||
background: #dff2fb;
|
||||
border-color: #c0e1f1;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 7) .menu-label img,
|
||||
.menu-row:nth-of-type(4n + 7) .menu-symbol {
|
||||
background: #ffe6d6;
|
||||
border-color: #ffd2b7;
|
||||
}
|
||||
|
||||
.menu-label span:last-child {
|
||||
min-width: 0;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
color: #244735;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu-row > img {
|
||||
display: block;
|
||||
width: 1.35em;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
margin: 0 12px;
|
||||
background: #dbece0;
|
||||
}
|
||||
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: min(80vw, 320px);
|
||||
padding: 14px;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 700;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-hint {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-weight: 600;
|
||||
font-size: 0.85em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-action {
|
||||
width: 50%;
|
||||
min-width: 120px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: #f3c74e;
|
||||
color: #3f2b00;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: calc(22px + env(safe-area-inset-bottom));
|
||||
z-index: 40;
|
||||
max-width: calc(100% - 40px);
|
||||
transform: translateX(-50%);
|
||||
padding: 9px 13px;
|
||||
border-radius: 999px;
|
||||
background: rgba(22, 56, 36, 0.92);
|
||||
color: #fff;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[dir="rtl"] .flip-img {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
703
h5/agency-center/agency-center.js
Normal file
@ -0,0 +1,703 @@
|
||||
import { _ as policyIcon, a as transferIcon, b as bankIcon } from "../js/database-export-CGwyHyM--1776148658686.js";
|
||||
import { _ as coinsIcon, a as cashIcon } from "../js/database-dollar-DDq4bT-e-1776148658686.js";
|
||||
import { _ as arrowIcon } from "../js/arrow-Cf0JWzXk-1776148658686.js";
|
||||
import { _ as helpIcon } from "../js/help-BgsLqEJs-1776148658686.js";
|
||||
|
||||
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
|
||||
const labels = {
|
||||
en: {
|
||||
agency_center: "Agency Center",
|
||||
my_available_salary: "My available salary",
|
||||
my_salary_details: "Salary details",
|
||||
today_host_task: "My task as a host today is:",
|
||||
host_type: "Host type",
|
||||
minutes: "Minutes",
|
||||
gift_task: "Gift Task",
|
||||
platform_policy: "Platform policy",
|
||||
exchange_coins: "Exchange coins",
|
||||
transfer: "Transfer",
|
||||
cash_withdraw: "Cash withdraw",
|
||||
bank_accounts: "Bank Accounts",
|
||||
team_member: "Team Member",
|
||||
team_bill: "Team Bill",
|
||||
invite_members: "Invite Members",
|
||||
prompt: "Prompt",
|
||||
fill_info_tips: "Please complete your withdrawal information first.",
|
||||
card_issue_tips: "Make sure the bank card and identity information are valid.",
|
||||
to_improve: "To improve",
|
||||
completed: "Completed",
|
||||
in_progress: "In progress",
|
||||
out_of_account: "Out of account",
|
||||
failed_to_load_data: "Failed to load data. Please try again."
|
||||
},
|
||||
ar: {},
|
||||
tr: {},
|
||||
bn: {}
|
||||
};
|
||||
|
||||
let commonHeaders = {
|
||||
authorization: "Bearer undefined",
|
||||
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
|
||||
"req-client": "Android",
|
||||
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
|
||||
"req-lang": "id-ID",
|
||||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
|
||||
"req-version": "V2",
|
||||
"req-zone": "Asia/Shanghai"
|
||||
};
|
||||
|
||||
const state = {
|
||||
lang: resolveLanguage(),
|
||||
userProfile: null,
|
||||
teamInfo: null,
|
||||
identityInfo: null,
|
||||
salaryInfo: {
|
||||
currentSalary: "0.00"
|
||||
},
|
||||
taskInfo: {
|
||||
anchorType: "Chat",
|
||||
minutesProgress: 0,
|
||||
minutesTotal: 120,
|
||||
giftTask: 0
|
||||
},
|
||||
memberQuantity: 0,
|
||||
memberWorkTotal: "0.00",
|
||||
teamBill: {
|
||||
date: "–",
|
||||
hostSalary: "–",
|
||||
agentSalary: "–",
|
||||
total: "–",
|
||||
status: "–"
|
||||
},
|
||||
bankCards: [],
|
||||
defaultBankCard: null,
|
||||
withdrawInfo: null,
|
||||
withdrawEnabled: false,
|
||||
showFillInfoModal: false
|
||||
};
|
||||
|
||||
const rolePriority = {
|
||||
guest: 0,
|
||||
anchor: 1,
|
||||
agent: 2,
|
||||
bd: 3,
|
||||
bdLeader: 4,
|
||||
freightAgent: 5,
|
||||
admin: 6
|
||||
};
|
||||
|
||||
const roleDefaultPages = {
|
||||
admin: "/admin-center",
|
||||
freightAgent: "/coin-seller",
|
||||
agent: "/agency-center",
|
||||
bdLeader: "/bd-leader-center",
|
||||
bd: "/bd-center",
|
||||
anchor: "/host-center",
|
||||
guest: "/apply"
|
||||
};
|
||||
|
||||
const rolePages = {
|
||||
admin: [
|
||||
"/admin-center",
|
||||
"/invite-bd-leader",
|
||||
"/invite-bd",
|
||||
"/invite-recharge-agency",
|
||||
"/invite-agency",
|
||||
"/item-distribution",
|
||||
"/my-BDLeader-teams",
|
||||
"/my-BD-teams",
|
||||
"/my-agency-teams",
|
||||
"/my-recharge-agency",
|
||||
"/admin-policy"
|
||||
],
|
||||
freightAgent: ["/coin-seller", "/seller-records", "/coin-seller-search"],
|
||||
agent: [
|
||||
"/agency-center",
|
||||
"/transfer",
|
||||
"/exchange-gold-coins",
|
||||
"/message",
|
||||
"/platform-policy",
|
||||
"/agency-setting",
|
||||
"/team-member",
|
||||
"/information-details",
|
||||
"/history-salary",
|
||||
"/invite-members",
|
||||
"/team-bill"
|
||||
],
|
||||
bdLeader: [
|
||||
"/bd-leader-center",
|
||||
"/invite-bd-leader",
|
||||
"/invite-bd",
|
||||
"/bd-center",
|
||||
"/transfer",
|
||||
"/exchange-gold-coins",
|
||||
"/message",
|
||||
"/invite-agency",
|
||||
"/bd-item-distribution",
|
||||
"/team-member",
|
||||
"/history-salary",
|
||||
"/agency-list",
|
||||
"/available-income",
|
||||
"/income-details",
|
||||
"/policy",
|
||||
"/bd-list"
|
||||
],
|
||||
bd: [
|
||||
"/bd-center",
|
||||
"/transfer",
|
||||
"/exchange-gold-coins",
|
||||
"/message",
|
||||
"/invite-agency",
|
||||
"/bd-item-distribution",
|
||||
"/team-member",
|
||||
"/history-salary",
|
||||
"/agency-list",
|
||||
"/available-income",
|
||||
"/income-details",
|
||||
"/policy"
|
||||
],
|
||||
anchor: [
|
||||
"/host-center",
|
||||
"/transfer",
|
||||
"/exchange-gold-coins",
|
||||
"/message",
|
||||
"/platform-policy",
|
||||
"/host-setting",
|
||||
"/information-details",
|
||||
"/history-salary",
|
||||
"/invite-members",
|
||||
"/team-bill"
|
||||
],
|
||||
guest: ["/apply", "/language"]
|
||||
};
|
||||
|
||||
const publicPages = [
|
||||
"/apply",
|
||||
"/language",
|
||||
"/not_app",
|
||||
"/pay-result",
|
||||
"/map",
|
||||
"/top-list",
|
||||
"/weekly-star",
|
||||
"/ranking",
|
||||
"/games-king",
|
||||
"/couple",
|
||||
"/invitation-to-register",
|
||||
"/invitation/invite-new-user",
|
||||
"/recharge-reward",
|
||||
"/login-reward",
|
||||
"/activities/lesser-bairam",
|
||||
"/activities/lucky-dollars-season3",
|
||||
"/activities/spring-festival",
|
||||
"/recharge",
|
||||
"/recharge-pay-way",
|
||||
"/recharge-guide",
|
||||
"/recharge-agency-recruit",
|
||||
"/search-payee",
|
||||
"/cash-withdraw",
|
||||
"/cash-out",
|
||||
"/cash-out-details",
|
||||
"/bank-cards",
|
||||
"/bank-card",
|
||||
"/KYC",
|
||||
"/good-ID"
|
||||
];
|
||||
|
||||
function resolveLanguage() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||||
const hashParams = new URLSearchParams(hashQuery);
|
||||
const lang = params.get("lang") || hashParams.get("lang") || navigator.language || "en";
|
||||
|
||||
if (lang.startsWith("ar")) return "ar";
|
||||
if (lang.startsWith("tr")) return "tr";
|
||||
if (lang.startsWith("bn")) return "bn";
|
||||
return "en";
|
||||
}
|
||||
|
||||
function t(key) {
|
||||
return labels[state.lang]?.[key] || labels.en[key] || key;
|
||||
}
|
||||
|
||||
function setLanguage() {
|
||||
document.documentElement.lang = state.lang;
|
||||
document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr";
|
||||
document.querySelector("[data-action='language']").textContent = state.lang.toUpperCase();
|
||||
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
||||
node.textContent = t(node.dataset.i18n);
|
||||
});
|
||||
}
|
||||
|
||||
function setIcons() {
|
||||
const iconMap = {
|
||||
policy: policyIcon,
|
||||
transfer: transferIcon,
|
||||
bank: bankIcon,
|
||||
coins: coinsIcon,
|
||||
cash: cashIcon,
|
||||
arrow: arrowIcon,
|
||||
help: helpIcon
|
||||
};
|
||||
|
||||
document.querySelectorAll("[data-icon]").forEach((img) => {
|
||||
img.src = iconMap[img.dataset.icon] || "";
|
||||
});
|
||||
}
|
||||
|
||||
function isMobileDevice() {
|
||||
return /Android|iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
function isAppEnvironment() {
|
||||
return Boolean(window.app || window.webkit || window.FlutterPageControl);
|
||||
}
|
||||
|
||||
function splitHeaderPairs(value) {
|
||||
const result = {};
|
||||
if (!value) return result;
|
||||
value.split(";").forEach((item) => {
|
||||
const index = item.indexOf("=");
|
||||
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseAccessOrigin(raw) {
|
||||
const data = JSON.parse(raw);
|
||||
const appIntel = splitHeaderPairs(data["Req-App-Intel"]);
|
||||
const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]);
|
||||
|
||||
return {
|
||||
reqLang: data["Req-Lang"],
|
||||
authorization: data.Authorization,
|
||||
buildVersion: appIntel.build,
|
||||
appVersion: appIntel.version,
|
||||
appChannel: appIntel.channel,
|
||||
reqImei: data["Req-Imei"],
|
||||
sysOrigin: sysOrigin.origin,
|
||||
sysOriginChild: sysOrigin.child
|
||||
};
|
||||
}
|
||||
|
||||
function setHeadersFromApp(headers) {
|
||||
const next = {};
|
||||
if (headers.authorization) next.authorization = headers.authorization;
|
||||
if (headers.reqLang) next["req-lang"] = headers.reqLang;
|
||||
if (headers.appVersion || headers.buildVersion || headers.appChannel) {
|
||||
const intel = [];
|
||||
if (headers.appVersion) intel.push(`version=${headers.appVersion}`);
|
||||
if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`);
|
||||
if (headers.appChannel) intel.push(`channel=${headers.appChannel}`);
|
||||
next["req-app-intel"] = intel.join(";");
|
||||
}
|
||||
if (headers.reqImei) next["req-imei"] = headers.reqImei;
|
||||
if (headers.sysOrigin) {
|
||||
const origin = [`origin=${headers.sysOrigin}`];
|
||||
if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`);
|
||||
next["req-sys-origin"] = origin.join(";");
|
||||
}
|
||||
commonHeaders = { ...commonHeaders, ...next };
|
||||
}
|
||||
|
||||
function requestAccessOrigin() {
|
||||
return new Promise((resolve) => {
|
||||
window.renderData = resolve;
|
||||
window.getIosAccessOriginParam = resolve;
|
||||
|
||||
if (!isAppEnvironment()) {
|
||||
setHeadersFromApp({
|
||||
authorization: "",
|
||||
reqLang: state.lang,
|
||||
appVersion: "1.0.0",
|
||||
buildVersion: "1",
|
||||
appChannel: "Web",
|
||||
reqImei: "H5-STATIC",
|
||||
sysOrigin: "LIKEI",
|
||||
sysOriginChild: "LIKEI"
|
||||
});
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let attempt = 0;
|
||||
const poll = () => {
|
||||
attempt += 1;
|
||||
if (window.app && typeof window.app.getAccessOrigin === "function") {
|
||||
resolve(window.app.getAccessOrigin());
|
||||
return;
|
||||
}
|
||||
if (attempt < 50) {
|
||||
window.setTimeout(poll, 100);
|
||||
return;
|
||||
}
|
||||
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
|
||||
window.FlutterApp.postMessage("requestAccessOrigin");
|
||||
}
|
||||
};
|
||||
|
||||
if (isMobileDevice()) {
|
||||
poll();
|
||||
return;
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
}
|
||||
|
||||
async function connectToApp() {
|
||||
const raw = await requestAccessOrigin();
|
||||
if (raw) setHeadersFromApp(parseAccessOrigin(raw));
|
||||
}
|
||||
|
||||
async function apiGet(path) {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
method: "GET",
|
||||
headers: commonHeaders
|
||||
});
|
||||
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.response = data;
|
||||
throw error;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function readCachedJson(key) {
|
||||
const raw = localStorage.getItem(key);
|
||||
if (!raw) return null;
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to parse ${key}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeCachedJson(key, value) {
|
||||
if (value === null || value === undefined) {
|
||||
localStorage.removeItem(key);
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
function getUserId() {
|
||||
return state.userProfile?.id || readCachedJson("userProfile")?.id || null;
|
||||
}
|
||||
|
||||
function getTeamId() {
|
||||
return state.teamInfo?.id || readCachedJson("teamInfo")?.id || null;
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
if (value === null || value === undefined || value === "") return "0";
|
||||
const number = Number(value);
|
||||
if (Number.isNaN(number)) return String(value);
|
||||
return number.toFixed(2);
|
||||
}
|
||||
|
||||
function formatBillStatus(status) {
|
||||
return {
|
||||
SETTLED: t("completed"),
|
||||
UNPAID: t("in_progress"),
|
||||
HANG_UP: t("out_of_account"),
|
||||
PAY_OUT: t("completed")
|
||||
}[status] || status || "–";
|
||||
}
|
||||
|
||||
function getIdentityRoles(identity) {
|
||||
if (!identity) return ["guest"];
|
||||
const roles = [];
|
||||
if (identity.freightAgent) roles.push("freightAgent");
|
||||
if (identity.agent) roles.push("agent");
|
||||
if (identity.bdLeader) roles.push("bdLeader");
|
||||
if (identity.bd) roles.push("bd");
|
||||
if (identity.anchor) roles.push("anchor");
|
||||
if (identity.admin) roles.push("admin");
|
||||
return roles.length ? roles : ["guest"];
|
||||
}
|
||||
|
||||
function getPrimaryRole(roles) {
|
||||
return roles.reduce((primary, role) => (rolePriority[role] > rolePriority[primary] ? role : primary), "guest");
|
||||
}
|
||||
|
||||
function getAllowedPages(roles) {
|
||||
const pages = new Set([...publicPages, "/loading"]);
|
||||
roles.forEach((role) => {
|
||||
(rolePages[role] || []).forEach((page) => pages.add(page));
|
||||
});
|
||||
return pages;
|
||||
}
|
||||
|
||||
function applyPermissionRedirect() {
|
||||
const roles = getIdentityRoles(state.identityInfo);
|
||||
const primaryRole = getPrimaryRole(roles);
|
||||
const allowedPages = getAllowedPages(roles);
|
||||
const currentPath = window.location.pathname.replace(/\/$/, "") || "/";
|
||||
|
||||
if (!allowedPages.has(currentPath)) {
|
||||
navigateTo(roleDefaultPages[primaryRole] || "/apply");
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchIdentity() {
|
||||
const result = await apiGet("/app/h5/identity");
|
||||
if (result.status && result.body) state.identityInfo = result.body;
|
||||
}
|
||||
|
||||
async function fetchUserProfile() {
|
||||
const result = await apiGet("/team/member/profile");
|
||||
if (result.status && result.body) {
|
||||
state.userProfile = result.body;
|
||||
writeCachedJson("userProfile", result.body);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTeamInfo() {
|
||||
const result = await apiGet("/team/entry");
|
||||
if (result.status && result.body) {
|
||||
state.teamInfo = result.body.teamProfile || result.body;
|
||||
writeCachedJson("teamInfo", state.teamInfo);
|
||||
} else {
|
||||
state.teamInfo = null;
|
||||
writeCachedJson("teamInfo", null);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchBankBalance() {
|
||||
const result = await apiGet("/wallet/bank/balance");
|
||||
if (result.status && typeof result.body === "number") {
|
||||
state.salaryInfo.currentSalary = result.body.toFixed(2);
|
||||
} else {
|
||||
state.salaryInfo.currentSalary = "0";
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWorkStatistics() {
|
||||
const userId = getUserId();
|
||||
if (!userId) return;
|
||||
const result = await apiGet(`/team/member/work/statistics-now?dataType=DAILY&memberId=${encodeURIComponent(userId)}`);
|
||||
if (result.status && result.body) {
|
||||
const ownTime = Number(result.body.ownTime) || 0;
|
||||
const otherTime = Number(result.body.otherTime) || 0;
|
||||
const progress = ownTime + otherTime;
|
||||
state.taskInfo.anchorType = "Chat";
|
||||
state.taskInfo.minutesProgress = Math.min(progress, state.taskInfo.minutesTotal);
|
||||
state.taskInfo.giftTask = result.body.acceptGiftValue || 0;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMemberWorkTotal() {
|
||||
const userId = getUserId();
|
||||
if (!userId) return;
|
||||
const result = await apiGet(`/team/member-work?userId=${encodeURIComponent(userId)}`);
|
||||
if (!result.status || !result.body) return;
|
||||
|
||||
const targets = result.body.targets || [];
|
||||
const isAgent = Boolean(state.identityInfo?.agent);
|
||||
const isAnchor = Boolean(state.identityInfo?.anchor);
|
||||
const total = targets.reduce((sum, item, index) => {
|
||||
if (index === 0) return sum;
|
||||
const settlement = item.target?.settlementResult || {};
|
||||
if (isAgent) {
|
||||
return sum + Number(settlement.ownSalary || 0) + Number(settlement.memberSalary || 0);
|
||||
}
|
||||
if (isAnchor) {
|
||||
return sum + Number(settlement.memberSalary || 0);
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
state.memberWorkTotal = total.toFixed(2);
|
||||
}
|
||||
|
||||
async function fetchTeamMemberCount() {
|
||||
const teamId = getTeamId();
|
||||
if (!teamId) return;
|
||||
try {
|
||||
const result = await apiGet(`/team/members/count?id=${encodeURIComponent(teamId)}`);
|
||||
if (result.status && result.body) {
|
||||
state.memberQuantity = result.body.memberQuantity || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch team member count:", error);
|
||||
state.memberQuantity = 87;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTeamBill() {
|
||||
const teamId = getTeamId();
|
||||
if (!teamId) return;
|
||||
const result = await apiGet(`/team/bill?id=${encodeURIComponent(teamId)}`);
|
||||
if (!result.status || !Array.isArray(result.body) || !result.body[0]) return;
|
||||
|
||||
const bill = result.body[0];
|
||||
const billBelong = String(bill.billBelong || "");
|
||||
state.teamBill = {
|
||||
date: billBelong.length >= 6 ? `${billBelong.slice(0, 4)}.${billBelong.slice(4, 6)}` : "–",
|
||||
hostSalary: bill.settleResult?.memberSalary?.toFixed(2) || "–",
|
||||
agentSalary: bill.settleResult?.ownSalary?.toFixed(2) || "–",
|
||||
total: bill.settleResult?.totalSalary?.toFixed(2) || "–",
|
||||
status: formatBillStatus(bill.status)
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchBankCards() {
|
||||
const result = await apiGet("/wallet/bank-card/list");
|
||||
if (result.status && result.body) {
|
||||
state.bankCards = result.body;
|
||||
state.defaultBankCard = state.bankCards.find((card) => card.use) || null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWithdrawInfo() {
|
||||
const result = await apiGet("/wallet/withdraw-info/list");
|
||||
if (result.status && Array.isArray(result.body) && result.body.length > 0) {
|
||||
const info = result.body[0];
|
||||
let previewUrls = [];
|
||||
try {
|
||||
previewUrls = JSON.parse(info.passportFrontUrl || "[]").map((item) => item);
|
||||
} catch (error) {
|
||||
console.warn("Failed to parse withdraw info preview urls:", error);
|
||||
}
|
||||
state.withdrawInfo = { ...info, previewUrls };
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWithdrawEnabled() {
|
||||
const result = await apiGet("/wallet/bank/host-salary/withdraw/enabled");
|
||||
if (result.status) state.withdrawEnabled = Boolean(result.body);
|
||||
}
|
||||
|
||||
function render() {
|
||||
document.getElementById("currentSalary").textContent = `$${state.salaryInfo.currentSalary}`;
|
||||
document.getElementById("anchorType").textContent = state.taskInfo.anchorType;
|
||||
document.getElementById("minutesProgress").textContent = state.taskInfo.minutesProgress;
|
||||
document.getElementById("minutesTotal").textContent = state.taskInfo.minutesTotal;
|
||||
document.getElementById("giftTask").textContent = state.taskInfo.giftTask;
|
||||
|
||||
document.querySelectorAll(".cash-withdraw-row").forEach((node) => {
|
||||
node.hidden = !state.withdrawEnabled;
|
||||
});
|
||||
document.getElementById("fillInfoModal").hidden = !state.showFillInfoModal;
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.getElementById("toast");
|
||||
toast.textContent = message;
|
||||
toast.hidden = false;
|
||||
window.clearTimeout(showToast.timer);
|
||||
showToast.timer = window.setTimeout(() => {
|
||||
toast.hidden = true;
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function closePage() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(target) {
|
||||
const url = new URL(target, window.location.origin);
|
||||
const currentLang = new URLSearchParams(window.location.search).get("lang");
|
||||
if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang);
|
||||
window.location.href = `${url.pathname}${url.search}${url.hash}`;
|
||||
}
|
||||
|
||||
function openCashWithdraw() {
|
||||
if (!state.withdrawInfo || !state.defaultBankCard) {
|
||||
state.showFillInfoModal = true;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
navigateTo("/cash-withdraw");
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
const actions = {
|
||||
back: closePage,
|
||||
language: () => navigateTo("/language"),
|
||||
message: () => navigateTo("/message"),
|
||||
salaryDetails: () => navigateTo("/history-salary"),
|
||||
platformPolicy: () => navigateTo("/platform-policy?from=agencycenter"),
|
||||
exchangeCoins: () => navigateTo("/exchange-gold-coins"),
|
||||
transfer: () => navigateTo("/transfer"),
|
||||
cashWithdraw: openCashWithdraw,
|
||||
bankAccounts: () => navigateTo("/agency-setting"),
|
||||
teamMember: () => navigateTo(`/team-member?source=agency-center&members=${state.memberQuantity}`),
|
||||
teamBill: () => navigateTo("/team-bill"),
|
||||
inviteMembers: () => navigateTo("/invite-members"),
|
||||
completeInfo: () => navigateTo("/agency-setting")
|
||||
};
|
||||
|
||||
document.querySelectorAll("[data-action]").forEach((node) => {
|
||||
const handler = actions[node.dataset.action];
|
||||
if (handler) node.addEventListener("click", handler);
|
||||
});
|
||||
|
||||
document.getElementById("fillInfoModal").addEventListener("click", (event) => {
|
||||
if (event.target.id === "fillInfoModal") {
|
||||
state.showFillInfoModal = false;
|
||||
render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function initializeData() {
|
||||
const firstWave = [
|
||||
fetchUserProfile(),
|
||||
fetchTeamInfo(),
|
||||
fetchBankBalance(),
|
||||
fetchWithdrawEnabled(),
|
||||
fetchBankCards(),
|
||||
fetchWithdrawInfo(),
|
||||
fetchIdentity()
|
||||
];
|
||||
const firstResults = await Promise.allSettled(firstWave);
|
||||
firstResults.forEach((result) => {
|
||||
if (result.status === "rejected") console.warn("Agency center data request failed:", result.reason);
|
||||
});
|
||||
applyPermissionRedirect();
|
||||
|
||||
const secondWave = [
|
||||
fetchWorkStatistics(),
|
||||
fetchTeamMemberCount(),
|
||||
fetchTeamBill(),
|
||||
fetchMemberWorkTotal()
|
||||
];
|
||||
const secondResults = await Promise.allSettled(secondWave);
|
||||
secondResults.forEach((result) => {
|
||||
if (result.status === "rejected") console.warn("Agency center dependent request failed:", result.reason);
|
||||
});
|
||||
}
|
||||
|
||||
async function init() {
|
||||
setLanguage();
|
||||
setIcons();
|
||||
bindEvents();
|
||||
render();
|
||||
|
||||
localStorage.setItem("identity", JSON.stringify({ identity: "AGENCY" }));
|
||||
localStorage.removeItem("payee");
|
||||
|
||||
try {
|
||||
await connectToApp();
|
||||
await initializeData();
|
||||
} catch (error) {
|
||||
console.warn("Agency center initialization failed:", error);
|
||||
showToast(error.response?.errorMsg || error.message || t("failed_to_load_data"));
|
||||
} finally {
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@ -13,23 +13,149 @@
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>Yumi H5</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.__H5_ROUTE_PATH__ = "/agency-center";
|
||||
</script>
|
||||
<script src="/assets/yumi-route-entry.js"></script>
|
||||
<script type="module" crossorigin src="/js/index-CIAVq8iD-1776148658686.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/css/index-VRlNQ74k-1776148661679.css">
|
||||
<title>Agency Center</title>
|
||||
<link rel="stylesheet" href="./agency-center.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<main class="agency-center">
|
||||
<header class="page-header">
|
||||
<button class="icon-button" type="button" data-action="back" aria-label="Back">
|
||||
<span aria-hidden="true">‹</span>
|
||||
</button>
|
||||
<h1 data-i18n="agency_center">Agency Center</h1>
|
||||
<div class="header-actions">
|
||||
<button class="message-button" type="button" data-action="message" aria-label="Message">
|
||||
<span aria-hidden="true">!</span>
|
||||
</button>
|
||||
<button class="language-button" type="button" data-action="language" aria-label="Language">
|
||||
EN
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="content">
|
||||
<article class="surface-card salary-card">
|
||||
<div class="salary-title">
|
||||
<span data-i18n="my_available_salary">My available salary</span>
|
||||
<button type="button" data-action="salaryDetails" data-i18n="my_salary_details">
|
||||
Salary details
|
||||
</button>
|
||||
</div>
|
||||
<div class="salary-amount" id="currentSalary">$0.00</div>
|
||||
</article>
|
||||
|
||||
<article class="surface-card task-card">
|
||||
<div class="task-title" data-i18n="today_host_task">My task as a host today is:</div>
|
||||
<div class="task-grid">
|
||||
<div class="task-cell">
|
||||
<div class="task-label" data-i18n="host_type">Host type</div>
|
||||
<div class="task-value" id="anchorType">Chat</div>
|
||||
</div>
|
||||
<div class="task-cell">
|
||||
<div class="task-label" data-i18n="minutes">Minutes</div>
|
||||
<div class="task-value"><span id="minutesProgress">0</span>/<span id="minutesTotal">120</span></div>
|
||||
</div>
|
||||
<div class="task-cell">
|
||||
<div class="task-label" data-i18n="gift_task">Gift Task</div>
|
||||
<div class="task-value" id="giftTask">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="surface-card menu-card">
|
||||
<button type="button" class="menu-row" data-action="platformPolicy">
|
||||
<span class="menu-label">
|
||||
<img data-icon="policy" alt="">
|
||||
<span data-i18n="platform_policy">Platform policy</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img" alt="">
|
||||
</button>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-action="exchangeCoins">
|
||||
<span class="menu-label">
|
||||
<img data-icon="coins" alt="">
|
||||
<span data-i18n="exchange_coins">Exchange coins</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img" alt="">
|
||||
</button>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-action="transfer">
|
||||
<span class="menu-label">
|
||||
<img data-icon="transfer" alt="">
|
||||
<span data-i18n="transfer">Transfer</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img" alt="">
|
||||
</button>
|
||||
|
||||
<div class="divider cash-withdraw-row" hidden></div>
|
||||
|
||||
<button type="button" class="menu-row cash-withdraw-row" data-action="cashWithdraw" hidden>
|
||||
<span class="menu-label">
|
||||
<img data-icon="cash" alt="">
|
||||
<span data-i18n="cash_withdraw">Cash withdraw</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img" alt="">
|
||||
</button>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-action="bankAccounts">
|
||||
<span class="menu-label">
|
||||
<img data-icon="bank" alt="">
|
||||
<span data-i18n="bank_accounts">Bank Accounts</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img" alt="">
|
||||
</button>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-action="teamMember">
|
||||
<span class="menu-label">
|
||||
<span class="menu-symbol">T</span>
|
||||
<span data-i18n="team_member">Team Member</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img" alt="">
|
||||
</button>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-action="teamBill">
|
||||
<span class="menu-label">
|
||||
<span class="menu-symbol">B</span>
|
||||
<span data-i18n="team_bill">Team Bill</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img" alt="">
|
||||
</button>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-action="inviteMembers">
|
||||
<span class="menu-label">
|
||||
<img data-icon="help" alt="">
|
||||
<span data-i18n="invite_members">Invite Members</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img" alt="">
|
||||
</button>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="modal-mask" id="fillInfoModal" hidden>
|
||||
<div class="modal-dialog" role="dialog" aria-modal="true" aria-labelledby="fillInfoTitle">
|
||||
<div class="modal-title" id="fillInfoTitle" data-i18n="prompt">Prompt</div>
|
||||
<div class="modal-message" data-i18n="fill_info_tips">Please complete your withdrawal information first.</div>
|
||||
<div class="modal-hint" data-i18n="card_issue_tips">Make sure the bank card and identity information are valid.</div>
|
||||
<button type="button" class="modal-action" data-action="completeInfo" data-i18n="to_improve">
|
||||
To improve
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
<script type="module" src="./agency-center.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
13
h5/agency-list/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Agency List</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Agency List","mode":"teamHistory","listEndpoint":"/team/bd/member-bill/list?type=BD","historyEndpoint":"/team/bd/history","moreEndpoint":"/team/bd/history/more?billBelong=","countLabel":"Agency number","totalLabel":"Team salary","memberSide1":"Salary","memberSide2":"Host"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/agency-setting/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Agency Setting</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Agency Setting","mode":"bankCards"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
338
h5/apply/apply.css
Normal file
@ -0,0 +1,338 @@
|
||||
@font-face {
|
||||
font-family: "Baloo 2";
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Baloo 2";
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #25352b;
|
||||
background: #edf8f1;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.apply-view {
|
||||
min-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background: #edf8f1;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr 44px;
|
||||
align-items: center;
|
||||
min-height: calc(54px + env(safe-area-inset-top));
|
||||
padding: env(safe-area-inset-top) 12px 0;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
min-width: 0;
|
||||
color: #173a28;
|
||||
font-size: 1.35em;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.help-button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
justify-self: end;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #fff3c8;
|
||||
border: 1px solid #e9cc70;
|
||||
}
|
||||
|
||||
.help-button img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
padding: 18px 12px;
|
||||
}
|
||||
|
||||
.reminder-section {
|
||||
padding: 14px 10px 4px;
|
||||
}
|
||||
|
||||
.reminder-section h3 {
|
||||
margin-bottom: 10px;
|
||||
color: #788279;
|
||||
font-size: 1.05em;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.reminder-section p {
|
||||
color: #2f302d;
|
||||
font-size: 1em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.apply-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.surface-card {
|
||||
border: 1px solid #b7e0c6;
|
||||
border-radius: 18px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
}
|
||||
|
||||
.input-card {
|
||||
min-height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 14px;
|
||||
background: #fffdf7;
|
||||
border-color: #f0d97d;
|
||||
}
|
||||
|
||||
.input-card label {
|
||||
flex-shrink: 0;
|
||||
font-weight: 800;
|
||||
color: #26352c;
|
||||
}
|
||||
|
||||
.input-card input {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: #27342d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.input-card input::placeholder {
|
||||
color: #8d9088;
|
||||
}
|
||||
|
||||
.apply-button {
|
||||
min-width: 96px;
|
||||
min-height: 56px;
|
||||
padding: 0 22px;
|
||||
border-radius: 999px;
|
||||
background: #f3c74e;
|
||||
color: #3f2b00;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 8px 18px rgba(155, 119, 23, 0.18);
|
||||
}
|
||||
|
||||
.records-section {
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
background: #fbfffc;
|
||||
border: 1px solid #cce9d7;
|
||||
}
|
||||
|
||||
.records-section h3 {
|
||||
margin-bottom: 14px;
|
||||
color: #173a28;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
position: relative;
|
||||
min-height: 74px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.record-status {
|
||||
color: #d54e42;
|
||||
font-size: 0.9em;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.record-user {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.record-avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #d7f5e2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1e6b43;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.record-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.record-info {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.record-name {
|
||||
color: #25352b;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.record-id {
|
||||
color: #6d756f;
|
||||
font-size: 0.82em;
|
||||
}
|
||||
|
||||
.record-cancel {
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: #f3c74e;
|
||||
color: #3f2b00;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.modal-layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal-card {
|
||||
width: min(100%, 360px);
|
||||
padding: 16px;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.modal-card h3 {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 800;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.modal-card p {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 700;
|
||||
font-size: 1.05em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-primary {
|
||||
width: 80%;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: #f3c74e;
|
||||
color: #3f2b00;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: calc(22px + env(safe-area-inset-bottom));
|
||||
z-index: 40;
|
||||
max-width: calc(100% - 40px);
|
||||
transform: translateX(-50%);
|
||||
padding: 9px 13px;
|
||||
border-radius: 999px;
|
||||
background: rgba(22, 56, 36, 0.92);
|
||||
color: #fff;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.apply-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.apply-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
511
h5/apply/apply.js
Normal file
@ -0,0 +1,511 @@
|
||||
import { _ as helpIcon } from "../js/help-BgsLqEJs-1776148658686.js";
|
||||
|
||||
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
|
||||
const labels = {
|
||||
en: {
|
||||
apply_join_team: "Apply to join the team",
|
||||
apply_reminder: "Reminder before application:",
|
||||
apply_reminder_detail:
|
||||
"Enter the ID of your agent, and you will become the host after the host apply is approved. After becoming an hoster,your income will be entrusted to your agent",
|
||||
apply_id: "Apply ID:",
|
||||
enter_agent_id: "Please enter the agent ID",
|
||||
apply: "Apply",
|
||||
application_records: "Application records",
|
||||
pending: "Pending",
|
||||
cancel: "Cancel",
|
||||
application_help: "Application help",
|
||||
application_help_detail: "Enter your agent ID and wait for approval.",
|
||||
got_it: "Got it",
|
||||
user_id_prefix: "ID:",
|
||||
application_submitted: "Application submitted",
|
||||
application_cancelled: "Application cancelled",
|
||||
cancellation_failed: "Cancellation failed",
|
||||
application_record_not_found: "Application record not found",
|
||||
application_processed_cannot_cancel: "Application processed, cannot cancel",
|
||||
network_error: "Network error",
|
||||
cancellation_failed_try_again: "Cancellation failed, please try again"
|
||||
},
|
||||
ar: {},
|
||||
tr: {},
|
||||
bn: {}
|
||||
};
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(message, options = {}) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
this.type = options.type || "unknown";
|
||||
this.status = options.status || null;
|
||||
this.errorCode = options.errorCode || null;
|
||||
this.errorCodeName = options.errorCodeName || null;
|
||||
this.errorMsg = options.errorMsg || null;
|
||||
this.response = options.response || null;
|
||||
}
|
||||
}
|
||||
|
||||
let commonHeaders = {
|
||||
authorization: "Bearer undefined",
|
||||
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
|
||||
"req-client": "Android",
|
||||
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
|
||||
"req-lang": "id-ID",
|
||||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
|
||||
"req-version": "V2",
|
||||
"req-zone": "Asia/Shanghai"
|
||||
};
|
||||
|
||||
const state = {
|
||||
lang: resolveLanguage(),
|
||||
agencyId: "",
|
||||
inlineError: "",
|
||||
loading: false,
|
||||
records: [],
|
||||
selectedTeam: null,
|
||||
helpOpen: false
|
||||
};
|
||||
|
||||
function resolveLanguage() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||||
const hashParams = new URLSearchParams(hashQuery);
|
||||
const lang = params.get("lang") || hashParams.get("lang") || navigator.language || "en";
|
||||
|
||||
if (lang.startsWith("ar")) return "ar";
|
||||
if (lang.startsWith("tr")) return "tr";
|
||||
if (lang.startsWith("bn")) return "bn";
|
||||
return "en";
|
||||
}
|
||||
|
||||
function t(key) {
|
||||
return labels[state.lang]?.[key] || labels.en[key] || key;
|
||||
}
|
||||
|
||||
function setLanguage() {
|
||||
document.documentElement.lang = state.lang;
|
||||
document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr";
|
||||
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
||||
node.textContent = t(node.dataset.i18n);
|
||||
});
|
||||
document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => {
|
||||
node.setAttribute("placeholder", t(node.dataset.i18nPlaceholder));
|
||||
});
|
||||
}
|
||||
|
||||
function setIcons() {
|
||||
document.getElementById("helpIcon").src = helpIcon;
|
||||
}
|
||||
|
||||
function isMobileDevice() {
|
||||
return /Android|iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
function isAppEnvironment() {
|
||||
return Boolean(window.app || window.webkit || window.FlutterPageControl);
|
||||
}
|
||||
|
||||
function splitHeaderPairs(value) {
|
||||
const result = {};
|
||||
if (!value) return result;
|
||||
value.split(";").forEach((item) => {
|
||||
const index = item.indexOf("=");
|
||||
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseAccessOrigin(raw) {
|
||||
try {
|
||||
const data = JSON.parse(raw);
|
||||
const appIntel = splitHeaderPairs(data["Req-App-Intel"]);
|
||||
const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
reqLang: data["Req-Lang"],
|
||||
authorization: data.Authorization,
|
||||
buildVersion: appIntel.build,
|
||||
appVersion: appIntel.version,
|
||||
appChannel: appIntel.channel,
|
||||
reqImei: appIntel["Req-Imei"],
|
||||
sysOrigin: sysOrigin.origin,
|
||||
sysOriginChild: sysOrigin.child
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Failed to parse access origin:", error);
|
||||
return { success: false, error: "accessOrigin Error.", data: null };
|
||||
}
|
||||
}
|
||||
|
||||
function setHeadersFromApp(headers) {
|
||||
const next = {};
|
||||
if (headers.authorization) next.authorization = headers.authorization;
|
||||
if (headers.reqLang) next["req-lang"] = headers.reqLang;
|
||||
if (headers.appVersion || headers.buildVersion || headers.appChannel) {
|
||||
const intel = [];
|
||||
if (headers.appVersion) intel.push(`version=${headers.appVersion}`);
|
||||
if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`);
|
||||
if (headers.appChannel) intel.push(`channel=${headers.appChannel}`);
|
||||
next["req-app-intel"] = intel.join(";");
|
||||
}
|
||||
if (headers.reqImei) next["req-imei"] = headers.reqImei;
|
||||
if (headers.sysOrigin) {
|
||||
const origin = [`origin=${headers.sysOrigin}`];
|
||||
if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`);
|
||||
next["req-sys-origin"] = origin.join(";");
|
||||
}
|
||||
commonHeaders = { ...commonHeaders, ...next };
|
||||
}
|
||||
|
||||
function requestAccessOrigin() {
|
||||
return new Promise((resolve) => {
|
||||
window.renderData = resolve;
|
||||
window.getIosAccessOriginParam = resolve;
|
||||
|
||||
if (!isAppEnvironment()) {
|
||||
setHeadersFromApp({
|
||||
authorization: "",
|
||||
reqLang: state.lang,
|
||||
appVersion: "1.0.0",
|
||||
buildVersion: "1",
|
||||
appChannel: "Web",
|
||||
reqImei: "H5-STATIC",
|
||||
sysOrigin: "LIKEI",
|
||||
sysOriginChild: "LIKEI"
|
||||
});
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let attempt = 0;
|
||||
const poll = () => {
|
||||
attempt += 1;
|
||||
if (window.app && typeof window.app.getAccessOrigin === "function") {
|
||||
resolve(window.app.getAccessOrigin());
|
||||
return;
|
||||
}
|
||||
if (attempt < 50) {
|
||||
window.setTimeout(poll, 100);
|
||||
return;
|
||||
}
|
||||
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
|
||||
window.FlutterApp.postMessage("requestAccessOrigin");
|
||||
}
|
||||
};
|
||||
|
||||
if (isMobileDevice()) {
|
||||
poll();
|
||||
return;
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
}
|
||||
|
||||
async function connectToApp() {
|
||||
const raw = await requestAccessOrigin();
|
||||
if (!raw) return { success: true, environment: "browser" };
|
||||
const parsed = parseAccessOrigin(raw);
|
||||
if (!parsed.success) {
|
||||
navigateTo(`/not_app?message=${encodeURIComponent(parsed.error)}`);
|
||||
return { success: false, error: parsed.error };
|
||||
}
|
||||
setHeadersFromApp(parsed.data);
|
||||
return { success: true, environment: "app" };
|
||||
}
|
||||
|
||||
async function apiRequest(path, options = {}) {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
method: options.method || "GET",
|
||||
headers: {
|
||||
...commonHeaders,
|
||||
...(options.body ? { "Content-Type": "application/json" } : {})
|
||||
},
|
||||
body: options.body ? JSON.stringify(options.body) : undefined
|
||||
});
|
||||
|
||||
let data = {};
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch {
|
||||
data = {};
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(`HTTP Error: ${response.status} ${response.statusText}`, {
|
||||
type: "http",
|
||||
status: response.status,
|
||||
response: data,
|
||||
errorCode: data.errorCode,
|
||||
errorCodeName: data.errorCodeName,
|
||||
errorMsg: data.errorMsg
|
||||
});
|
||||
}
|
||||
|
||||
if (data.status === false) {
|
||||
throw new ApiError(data.errorMsg || data.message || `API Error: ${data.errorCode}`, {
|
||||
type: "business",
|
||||
status: response.status,
|
||||
response: data,
|
||||
errorCode: data.errorCode,
|
||||
errorCodeName: data.errorCodeName,
|
||||
errorMsg: data.errorMsg
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function apiGet(path) {
|
||||
return apiRequest(path);
|
||||
}
|
||||
|
||||
function apiPost(path, body) {
|
||||
return apiRequest(path, { method: "POST", body });
|
||||
}
|
||||
|
||||
async function fetchUserProfile() {
|
||||
const result = await apiGet("/team/member/profile");
|
||||
if (result.status && result.body) {
|
||||
localStorage.setItem("userProfile", JSON.stringify(result.body));
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchApplicationRecord() {
|
||||
try {
|
||||
const result = await apiGet("/team/wait-apply/profile");
|
||||
state.records = result.status && result.body ? [result.body] : [];
|
||||
} catch (error) {
|
||||
console.error("获取申请记录失败:", error);
|
||||
state.records = [];
|
||||
showToast(error.errorMsg || error.message || "");
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
async function cancelApplication(record) {
|
||||
try {
|
||||
const result = await apiPost("/team/user/join-apply-cancel", { id: record.messageId });
|
||||
if (result.status) {
|
||||
showToast(t("application_cancelled"));
|
||||
state.records = [];
|
||||
await fetchApplicationRecord();
|
||||
} else {
|
||||
showToast(result.message || t("cancellation_failed"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("撤销申请失败:", error);
|
||||
if (error instanceof ApiError) {
|
||||
if (error.type === "business") {
|
||||
switch (error.errorCode) {
|
||||
case 6404:
|
||||
showToast(t("application_record_not_found"));
|
||||
break;
|
||||
case 6003:
|
||||
showToast(t("application_processed_cannot_cancel"));
|
||||
break;
|
||||
default:
|
||||
showToast(error.errorMsg || error.message || "Cancellation failed, please try again");
|
||||
}
|
||||
} else {
|
||||
showToast(error.type === "http" ? t("network_error") : t("cancellation_failed"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
showToast(t("cancellation_failed_try_again"));
|
||||
}
|
||||
}
|
||||
|
||||
async function searchTeamAccount(account) {
|
||||
try {
|
||||
console.debug("🔍 Searching team account:", account);
|
||||
const result = await apiGet(`/team/search-account?account=${encodeURIComponent(account)}`);
|
||||
if (result.status && result.body && result.body.teamProfile) {
|
||||
state.selectedTeam = result.body;
|
||||
console.debug("✅ Team found:", result.body.teamProfile);
|
||||
return result.body.teamProfile.id;
|
||||
}
|
||||
state.selectedTeam = null;
|
||||
showToast(result.errorMsg || "");
|
||||
} catch (error) {
|
||||
state.selectedTeam = null;
|
||||
showToast(error.errorMsg || error.message || "");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function submitApplication() {
|
||||
if (!state.agencyId.trim()) {
|
||||
state.inlineError = "Please enter agency ID";
|
||||
return;
|
||||
}
|
||||
|
||||
state.loading = true;
|
||||
state.inlineError = "";
|
||||
render();
|
||||
|
||||
try {
|
||||
const teamId = await searchTeamAccount(state.agencyId.trim());
|
||||
if (!teamId) {
|
||||
state.inlineError = "Account not found or not an agency";
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug("✅ Team ID found:", teamId);
|
||||
console.debug("📝 Step 2: Submitting application...");
|
||||
const result = await apiPost("/team/user/apply", { teamId, reason: "JOIN" });
|
||||
if (result.status) {
|
||||
showToast(t("application_submitted"));
|
||||
await fetchApplicationRecord();
|
||||
state.agencyId = "";
|
||||
state.selectedTeam = null;
|
||||
} else {
|
||||
state.inlineError = result.message || "Application failed. Please try again.";
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) {
|
||||
if (error.type === "business") {
|
||||
showToast(`business error!${error.errorMsg || ""}`);
|
||||
} else if (error.type === "http") {
|
||||
showToast(error.status === 406 ? error.errorMsg : error.message || "Unknown error, please try again");
|
||||
} else {
|
||||
showToast("Unknown error, please try again");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
state.loading = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(target) {
|
||||
const url = new URL(target, window.location.origin);
|
||||
const currentLang = new URLSearchParams(window.location.search).get("lang");
|
||||
if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang);
|
||||
window.location.href = `${url.pathname}${url.search}${url.hash}`;
|
||||
}
|
||||
|
||||
function avatarText(profile) {
|
||||
const name = profile?.userNickname || profile?.account || "U";
|
||||
return name.trim().slice(0, 1).toUpperCase() || "U";
|
||||
}
|
||||
|
||||
function userAccount(profile) {
|
||||
return profile?.ownSpecialId?.account || profile?.account || "";
|
||||
}
|
||||
|
||||
function renderRecords() {
|
||||
const recordsSection = document.getElementById("recordsSection");
|
||||
const recordList = document.getElementById("recordList");
|
||||
recordList.innerHTML = "";
|
||||
recordsSection.hidden = state.records.length === 0;
|
||||
document.querySelector("[data-action='apply']").hidden = state.records.length > 0;
|
||||
|
||||
state.records.forEach((record) => {
|
||||
const profile = record.ownUserProfile || {};
|
||||
const card = document.createElement("article");
|
||||
card.className = "surface-card record-card";
|
||||
card.innerHTML = `
|
||||
<div class="record-status"></div>
|
||||
<div class="record-user">
|
||||
<div class="record-avatar"><img alt=""><span></span></div>
|
||||
<div class="record-info">
|
||||
<div class="record-name"></div>
|
||||
<div class="record-id"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="record-cancel" type="button"></button>
|
||||
`;
|
||||
|
||||
card.querySelector(".record-status").textContent = t("pending");
|
||||
const img = card.querySelector(".record-avatar img");
|
||||
const fallback = card.querySelector(".record-avatar span");
|
||||
img.src = profile.userAvatar || "";
|
||||
fallback.textContent = avatarText(profile);
|
||||
img.addEventListener("error", () => {
|
||||
img.hidden = true;
|
||||
fallback.hidden = false;
|
||||
});
|
||||
if (!profile.userAvatar) {
|
||||
img.hidden = true;
|
||||
fallback.hidden = false;
|
||||
} else {
|
||||
fallback.hidden = true;
|
||||
}
|
||||
card.querySelector(".record-name").textContent = profile.userNickname || "";
|
||||
card.querySelector(".record-id").textContent = `${t("user_id_prefix")} ${userAccount(profile)}`;
|
||||
const cancelButton = card.querySelector(".record-cancel");
|
||||
cancelButton.textContent = t("cancel");
|
||||
cancelButton.addEventListener("click", () => cancelApplication(record));
|
||||
recordList.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function render() {
|
||||
const input = document.getElementById("agencyInput");
|
||||
if (document.activeElement !== input) input.value = state.agencyId;
|
||||
document.querySelector("[data-action='apply']").textContent = state.loading ? "..." : t("apply");
|
||||
document.getElementById("helpModal").hidden = !state.helpOpen;
|
||||
renderRecords();
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
if (!message) return;
|
||||
const toast = document.getElementById("toast");
|
||||
toast.textContent = message;
|
||||
toast.hidden = false;
|
||||
window.clearTimeout(showToast.timer);
|
||||
showToast.timer = window.setTimeout(() => {
|
||||
toast.hidden = true;
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
const input = document.getElementById("agencyInput");
|
||||
input.addEventListener("input", () => {
|
||||
state.agencyId = input.value;
|
||||
render();
|
||||
});
|
||||
|
||||
document.querySelector("[data-action='apply']").addEventListener("click", submitApplication);
|
||||
document.querySelector("[data-action='help']").addEventListener("click", () => {
|
||||
state.helpOpen = true;
|
||||
render();
|
||||
});
|
||||
document.querySelector("[data-action='close-help']").addEventListener("click", () => {
|
||||
state.helpOpen = false;
|
||||
render();
|
||||
});
|
||||
document.getElementById("helpModal").addEventListener("click", (event) => {
|
||||
if (event.target.id === "helpModal") {
|
||||
state.helpOpen = false;
|
||||
render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function initializePageData() {
|
||||
try {
|
||||
await fetchUserProfile();
|
||||
} catch (error) {
|
||||
console.error("❌ Data initialization failed:", error);
|
||||
}
|
||||
await fetchApplicationRecord();
|
||||
}
|
||||
|
||||
async function init() {
|
||||
setLanguage();
|
||||
setIcons();
|
||||
bindEvents();
|
||||
render();
|
||||
|
||||
try {
|
||||
const connection = await connectToApp();
|
||||
if (connection.success) await initializePageData();
|
||||
} catch (error) {
|
||||
console.error("❌ Page initialization failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@ -13,23 +13,51 @@
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>Yumi H5</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.__H5_ROUTE_PATH__ = "/apply";
|
||||
</script>
|
||||
<script src="/assets/yumi-route-entry.js"></script>
|
||||
<script type="module" crossorigin src="/js/index-CIAVq8iD-1776148658686.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/css/index-VRlNQ74k-1776148661679.css">
|
||||
<title>Apply</title>
|
||||
<link rel="stylesheet" href="./apply.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<main class="apply-view">
|
||||
<header class="page-header">
|
||||
<div></div>
|
||||
<h1 data-i18n="apply_join_team">Apply to join the team</h1>
|
||||
<button class="help-button" type="button" data-action="help" aria-label="Help">
|
||||
<img id="helpIcon" alt="">
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<section class="content">
|
||||
<section class="reminder-section">
|
||||
<h3 data-i18n="apply_reminder">Reminder before application:</h3>
|
||||
<p data-i18n="apply_reminder_detail">
|
||||
Enter the ID of your agent, and you will become the host after the host apply is approved. After becoming an hoster,your income will be entrusted to your agent
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="apply-row">
|
||||
<div class="surface-card input-card">
|
||||
<label for="agencyInput" data-i18n="apply_id">Apply ID:</label>
|
||||
<input id="agencyInput" type="text" data-i18n-placeholder="enter_agent_id" placeholder="Please enter the agent ID" autocomplete="off">
|
||||
</div>
|
||||
<button class="apply-button" type="button" data-action="apply" data-i18n="apply">Apply</button>
|
||||
</section>
|
||||
|
||||
<section class="records-section" id="recordsSection" hidden>
|
||||
<h3 data-i18n="application_records">Application records</h3>
|
||||
<div class="record-list" id="recordList"></div>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="modal-layer" id="helpModal" hidden>
|
||||
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="helpTitle">
|
||||
<h3 id="helpTitle" data-i18n="application_help">Application help</h3>
|
||||
<p data-i18n="application_help_detail">Enter your agent ID and wait for approval.</p>
|
||||
<button class="modal-primary" type="button" data-action="close-help" data-i18n="got_it">Got it</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
<script type="module" src="./apply.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 131 KiB |
13
h5/available-income/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Available Income</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Available Income","mode":"walletAction"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/bank-card/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Bank Card</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Bank Card","mode":"bankCards"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/bank-cards/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Bank Cards</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Bank Cards","mode":"bankCards"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -248,7 +248,7 @@ button {
|
||||
|
||||
.menu-label span {
|
||||
min-width: 0;
|
||||
font-size: 1em;
|
||||
font-size: 1.6em;
|
||||
font-weight: 600;
|
||||
color: #244735;
|
||||
overflow: hidden;
|
||||
|
||||
@ -69,7 +69,12 @@ let commonHeaders = {
|
||||
const state = {
|
||||
lang: resolveLanguage(),
|
||||
balance: 0,
|
||||
hasGiftPermission: false
|
||||
hasGiftPermission: false,
|
||||
appConnected: false,
|
||||
userInfo: null,
|
||||
teamInfo: null,
|
||||
identityInfo: null,
|
||||
identity: "BD"
|
||||
};
|
||||
|
||||
function resolveLanguage() {
|
||||
@ -140,7 +145,7 @@ function isMobileDevice() {
|
||||
}
|
||||
|
||||
function isAppEnvironment() {
|
||||
return Boolean(window.app || window.webkit || window.FlutterPageControl);
|
||||
return Boolean(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp);
|
||||
}
|
||||
|
||||
function splitHeaderPairs(value) {
|
||||
@ -166,9 +171,9 @@ function parseAccessOrigin(raw) {
|
||||
buildVersion: appIntel.build,
|
||||
appVersion: appIntel.version,
|
||||
appChannel: appIntel.channel,
|
||||
reqImei: appIntel["Req-Imei"],
|
||||
reqImei: data["Req-Imei"] || appIntel["Req-Imei"],
|
||||
sysOrigin: sysOrigin.origin,
|
||||
sysOriginChild: sysOrigin.child
|
||||
sysOriginChild: sysOrigin.originChild || sysOrigin.child
|
||||
};
|
||||
}
|
||||
|
||||
@ -186,7 +191,7 @@ function setHeadersFromApp(headers) {
|
||||
if (headers.reqImei) next["req-imei"] = headers.reqImei;
|
||||
if (headers.sysOrigin) {
|
||||
const origin = [`origin=${headers.sysOrigin}`];
|
||||
if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`);
|
||||
if (headers.sysOriginChild) origin.push(`originChild=${headers.sysOriginChild}`);
|
||||
next["req-sys-origin"] = origin.join(";");
|
||||
}
|
||||
commonHeaders = { ...commonHeaders, ...next };
|
||||
@ -229,7 +234,7 @@ function requestAccessOrigin() {
|
||||
}
|
||||
};
|
||||
|
||||
if (isMobileDevice()) {
|
||||
if (isMobileDevice() || isAppEnvironment()) {
|
||||
poll();
|
||||
} else {
|
||||
resolve(null);
|
||||
@ -239,8 +244,12 @@ function requestAccessOrigin() {
|
||||
|
||||
async function connectToApp() {
|
||||
const raw = await requestAccessOrigin();
|
||||
if (!raw) return;
|
||||
if (!raw) {
|
||||
state.appConnected = true;
|
||||
return;
|
||||
}
|
||||
setHeadersFromApp(parseAccessOrigin(raw));
|
||||
state.appConnected = true;
|
||||
}
|
||||
|
||||
async function apiGet(path) {
|
||||
@ -272,6 +281,59 @@ async function fetchGiftPermission() {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUserProfile() {
|
||||
const result = await apiGet("/team/member/profile");
|
||||
if (result.status && result.body) {
|
||||
state.userInfo = result.body;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTeamInfo() {
|
||||
const result = await apiGet("/team/entry");
|
||||
if (result.status && result.body) {
|
||||
state.teamInfo = result.body.teamProfile || result.body;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchIdentity() {
|
||||
const result = await apiGet("/app/h5/identity");
|
||||
if (result.status && result.body) {
|
||||
state.identityInfo = result.body;
|
||||
}
|
||||
}
|
||||
|
||||
async function initializePageConfig() {
|
||||
const results = await Promise.allSettled([fetchUserProfile(), fetchTeamInfo(), fetchIdentity()]);
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
console.warn("BD center page config request failed:", result.reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearPayeeStore() {
|
||||
try {
|
||||
sessionStorage.removeItem("payee");
|
||||
localStorage.removeItem("payee");
|
||||
} catch (error) {
|
||||
console.debug("BD center payee cache clear skipped:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function setIdentity(identity) {
|
||||
state.identity = identity;
|
||||
localStorage.setItem("identity", JSON.stringify({ identity }));
|
||||
}
|
||||
|
||||
async function loadBDCenterData() {
|
||||
const results = await Promise.allSettled([fetchBalance(), fetchGiftPermission()]);
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
console.warn("BD center data request failed:", result.reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function navigateTo(target) {
|
||||
const url = new URL(target, window.location.origin);
|
||||
const currentLang = new URLSearchParams(window.location.search).get("lang");
|
||||
@ -310,16 +372,13 @@ async function init() {
|
||||
setIcons();
|
||||
bindEvents();
|
||||
render();
|
||||
localStorage.setItem("identity", JSON.stringify({ identity: "BD" }));
|
||||
clearPayeeStore();
|
||||
setIdentity("BD");
|
||||
|
||||
try {
|
||||
await connectToApp();
|
||||
const results = await Promise.allSettled([fetchBalance(), fetchGiftPermission()]);
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
console.warn("BD center data request failed:", result.reason);
|
||||
}
|
||||
});
|
||||
await initializePageConfig();
|
||||
await loadBDCenterData();
|
||||
} catch (error) {
|
||||
console.warn("BD center initialization failed:", error);
|
||||
showToast(error.response?.errorMsg || error.message || t("failed_to_load_balance"));
|
||||
|
||||
13
h5/bd-item-distribution/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>BD Item Distribution</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"BD Item Distribution","mode":"itemDistribution","admin":false};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
482
h5/bd-leader-center/bd-leader-center.css
Normal file
@ -0,0 +1,482 @@
|
||||
@font-face {
|
||||
font-family: "Baloo 2";
|
||||
font-weight: 400 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #1f2937;
|
||||
background: #f4fbf6;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.bd-center {
|
||||
min-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background: #f4fbf6;
|
||||
}
|
||||
|
||||
.general-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr 58px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: calc(48px + env(safe-area-inset-top));
|
||||
padding: env(safe-area-inset-top) 10px 0;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.general-header h1 {
|
||||
min-width: 0;
|
||||
color: #111827;
|
||||
font-size: 1.35em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-button,
|
||||
.language-button {
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #111827;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.header-button span {
|
||||
display: block;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.language-button {
|
||||
justify-self: end;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: #e9f7ee;
|
||||
color: #276344;
|
||||
font-size: 0.86em;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.gradient-border {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 20px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d7eadf;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.salary-card {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.salary-inner {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.salary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.salary-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.salary-link {
|
||||
flex-shrink: 0;
|
||||
max-width: 48%;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.salary-amount {
|
||||
color: #1f2937;
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
direction: ltr;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.salary-icon {
|
||||
display: block;
|
||||
width: 1.35em;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-card {
|
||||
margin: 0 1% 12px;
|
||||
}
|
||||
|
||||
.menu-inner {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 4vw;
|
||||
}
|
||||
|
||||
.menu-row {
|
||||
width: 100%;
|
||||
min-height: 1.8em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1vw;
|
||||
}
|
||||
|
||||
.menu-label img {
|
||||
display: block;
|
||||
width: 1.6em;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-label span {
|
||||
min-width: 0;
|
||||
color: #1f2937;
|
||||
font-size: 1.4em;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
display: block;
|
||||
width: 1.5em;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.mask-layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 40;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.mask-hit {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.sheet {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 41;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 20px;
|
||||
border-radius: 16px 16px 0 0;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.details-sheet {
|
||||
max-height: 60vh;
|
||||
}
|
||||
|
||||
.sheet-title {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #333333;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.summary-box,
|
||||
.details-summary {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin: 0 4px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.25);
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.details-summary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.salary-list,
|
||||
.member-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.salary-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
background: #ebf1fd;
|
||||
}
|
||||
|
||||
.salary-item.is-pending {
|
||||
background: #feebef;
|
||||
}
|
||||
|
||||
.salary-item.is-completed {
|
||||
background: #e8faed;
|
||||
}
|
||||
|
||||
.salary-item.is-out {
|
||||
background: #e9e9e9;
|
||||
}
|
||||
|
||||
.status-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
min-width: 7.5em;
|
||||
max-width: 9em;
|
||||
padding: 2px 8px;
|
||||
border-radius: 8px 0 8px 0;
|
||||
background: #3d73ff;
|
||||
color: #ffffff;
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.is-pending .status-background {
|
||||
background: #ff3d40;
|
||||
}
|
||||
|
||||
.is-completed .status-background {
|
||||
background: #3dff54;
|
||||
}
|
||||
|
||||
.is-out .status-background {
|
||||
background: #aeaeae;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.salary-item-content {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
padding: 32px 8px 30px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.pair-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pair-line > div {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.more-bt {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 6px;
|
||||
color: #4f46e5;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.member-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.member-item img {
|
||||
width: 3.5em;
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
background: #eef2f7;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.member-info div:first-child {
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.member-info div:last-child {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.member-metrics {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: calc(28px + env(safe-area-inset-bottom));
|
||||
z-index: 50;
|
||||
max-width: 86vw;
|
||||
transform: translateX(-50%);
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
color: #ffffff;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[dir="rtl"] .flip-img {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
[dir="rtl"] .menu-row {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
[dir="rtl"] .status-background {
|
||||
left: auto;
|
||||
right: 0;
|
||||
border-radius: 0 8px 0 8px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .more-bt {
|
||||
right: auto;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
.bd-center {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 360px) {
|
||||
.bd-center {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.bd-center {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
.bd-center {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
558
h5/bd-leader-center/bd-leader-center.js
Normal file
@ -0,0 +1,558 @@
|
||||
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
|
||||
const labels = {
|
||||
en: {
|
||||
bd_leader_center: "BD Leader Center",
|
||||
my_available_salary: "My available salary",
|
||||
my_salary_link: "Details",
|
||||
bd_policy_link: "BD policy",
|
||||
agency_list_link: "Agency list",
|
||||
bd_list_link: "BD List",
|
||||
invite_become_agent: "Invite to become agent",
|
||||
invite_become_BD: "Invite to become BD",
|
||||
bank_cards_link: "Bank cards",
|
||||
send_welcome_gift: "Send welcome gift",
|
||||
bd_salary: "BD salary",
|
||||
bd_leader_history: "BD Leader history",
|
||||
total_income: "Total income",
|
||||
previous_period_income: "Previous period income",
|
||||
agency_number: "Agency number",
|
||||
bd_number: "BD number",
|
||||
team_salary: "Team salary",
|
||||
team_top_up: "Team top up",
|
||||
bd_income_salary: "BD income salary",
|
||||
bd_income_top_up: "BD income top up",
|
||||
user_id_prefix: "ID: ",
|
||||
salary: "Salary",
|
||||
recharge: "Recharge",
|
||||
host: "Host",
|
||||
agencies: "Agencies",
|
||||
total_team_salary: "Total team salary",
|
||||
more: "More",
|
||||
in_progress: "In Progress",
|
||||
pending: "Pending",
|
||||
completed: "Completed",
|
||||
out_of_account: "Out of account",
|
||||
failed_to_load_balance: "Failed to load balance. Please try again.",
|
||||
failed_to_load_history: "Failed to load history. Please try again.",
|
||||
request_not_available: "Request failed. Please try again."
|
||||
},
|
||||
ar: {},
|
||||
tr: {},
|
||||
bn: {}
|
||||
};
|
||||
|
||||
["ar", "tr", "bn"].forEach((lang) => {
|
||||
labels[lang] = { ...labels.en };
|
||||
});
|
||||
|
||||
let commonHeaders = {
|
||||
authorization: "Bearer undefined",
|
||||
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
|
||||
"req-client": "Android",
|
||||
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
|
||||
"req-lang": "id-ID",
|
||||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
|
||||
"req-version": "V2",
|
||||
"req-zone": "Asia/Shanghai"
|
||||
};
|
||||
|
||||
const state = {
|
||||
lang: resolveLanguage(),
|
||||
balanceTotal: {},
|
||||
userInfo: null,
|
||||
teamInfo: null,
|
||||
identityInfo: null,
|
||||
identity: "BD_LEADER",
|
||||
modalMode: "",
|
||||
historyType: "bd",
|
||||
bdHistory: {},
|
||||
bdLeaderHistory: {},
|
||||
historyMore: {}
|
||||
};
|
||||
|
||||
function resolveLanguage() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||||
const hashParams = new URLSearchParams(hashQuery);
|
||||
const lang = params.get("lang") || hashParams.get("lang") || navigator.language || "en";
|
||||
if (lang.startsWith("ar")) return "ar";
|
||||
if (lang.startsWith("tr")) return "tr";
|
||||
if (lang.startsWith("bn")) return "bn";
|
||||
return "en";
|
||||
}
|
||||
|
||||
function t(key) {
|
||||
return labels[state.lang]?.[key] || labels.en[key] || key;
|
||||
}
|
||||
|
||||
function setLanguage() {
|
||||
document.documentElement.lang = state.lang;
|
||||
document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr";
|
||||
document.querySelector("[data-action='language']").textContent = state.lang.toUpperCase();
|
||||
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
||||
node.textContent = t(node.dataset.i18n);
|
||||
});
|
||||
}
|
||||
|
||||
function setIcons() {
|
||||
const icon = (body) => `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
${body}
|
||||
</svg>
|
||||
`)}`;
|
||||
|
||||
const iconMap = {
|
||||
policy: icon(`
|
||||
<path d="M13 8h15l7 7v25H13V8Z" fill="#E9F8EF" stroke="#22945D" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path d="M28 8v8h7" fill="#BDEBD0" stroke="#22945D" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path d="M18 32h4l2-9 4 14 2-5h4" stroke="#1766C2" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
`),
|
||||
bank: icon(`
|
||||
<path d="M8 20h32L24 10 8 20Z" fill="#FFF2C7" stroke="#C08212" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path d="M12 22v14M20 22v14M28 22v14M36 22v14" stroke="#2C7A4B" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M9 38h30" stroke="#C08212" stroke-width="3" stroke-linecap="round"/>
|
||||
`),
|
||||
gift: icon(`
|
||||
<path d="M11 22h26v18H11V22Z" fill="#FFF0B8" stroke="#C47B13" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path d="M8 15h32v7H8v-7Z" fill="#FFE4E8" stroke="#D94B63" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path d="M24 15v25" stroke="#D94B63" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M24 15c-7-1-10-3-10-6 0-2 2-4 4-4 4 0 6 6 6 10ZM24 15c7-1 10-3 10-6 0-2-2-4-4-4-4 0-6 6-6 10Z" fill="#FFFFFF" stroke="#D94B63" stroke-width="3" stroke-linejoin="round"/>
|
||||
`),
|
||||
invite: icon(`
|
||||
<circle cx="18" cy="17" r="6" fill="#E8F3FF" stroke="#2878D8" stroke-width="3"/>
|
||||
<path d="M7 37c1.5-7 6-11 11-11 2.8 0 5.4 1.2 7.3 3.4" stroke="#2878D8" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M34 19v14M27 26h14" stroke="#2C9B63" stroke-width="3" stroke-linecap="round"/>
|
||||
`),
|
||||
arrow: icon(`
|
||||
<path d="M19 12l12 12-12 12" stroke="#62A87C" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
`),
|
||||
salary: icon(`
|
||||
<circle cx="24" cy="24" r="17" fill="#E8F8EF" stroke="#2C9B63" stroke-width="3"/>
|
||||
<path d="M24 13v22" stroke="#2C9B63" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M30 17.5c-1.4-1.4-3.4-2.2-6-2.2-3.5 0-6 1.8-6 4.7 0 6.7 12 2.8 12 9.1 0 2.7-2.5 4.6-6.2 4.6-2.8 0-5.3-.9-7-2.5" stroke="#D69E17" stroke-width="3" stroke-linecap="round"/>
|
||||
`)
|
||||
};
|
||||
|
||||
document.querySelectorAll("[data-icon]").forEach((img) => {
|
||||
img.src = iconMap[img.dataset.icon] || "";
|
||||
});
|
||||
}
|
||||
|
||||
function splitHeaderPairs(value) {
|
||||
const result = {};
|
||||
if (!value) return result;
|
||||
value.split(";").forEach((item) => {
|
||||
const index = item.indexOf("=");
|
||||
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseAccessOrigin(raw) {
|
||||
const data = JSON.parse(raw);
|
||||
const appIntel = splitHeaderPairs(data["Req-App-Intel"]);
|
||||
const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]);
|
||||
return {
|
||||
reqLang: data["Req-Lang"],
|
||||
authorization: data.Authorization,
|
||||
buildVersion: appIntel.build,
|
||||
appVersion: appIntel.version,
|
||||
appChannel: appIntel.channel,
|
||||
reqImei: appIntel["Req-Imei"],
|
||||
sysOrigin: sysOrigin.origin,
|
||||
sysOriginChild: sysOrigin.child
|
||||
};
|
||||
}
|
||||
|
||||
function setHeadersFromApp(headers) {
|
||||
const next = {};
|
||||
if (headers.authorization) next.authorization = headers.authorization;
|
||||
if (headers.reqLang) next["req-lang"] = headers.reqLang;
|
||||
if (headers.appVersion || headers.buildVersion || headers.appChannel) {
|
||||
const intel = [];
|
||||
if (headers.appVersion) intel.push(`version=${headers.appVersion}`);
|
||||
if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`);
|
||||
if (headers.appChannel) intel.push(`channel=${headers.appChannel}`);
|
||||
next["req-app-intel"] = intel.join(";");
|
||||
}
|
||||
if (headers.reqImei) next["req-imei"] = headers.reqImei;
|
||||
if (headers.sysOrigin) {
|
||||
const origin = [`origin=${headers.sysOrigin}`];
|
||||
if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`);
|
||||
next["req-sys-origin"] = origin.join(";");
|
||||
}
|
||||
commonHeaders = { ...commonHeaders, ...next };
|
||||
}
|
||||
|
||||
function isMobileDevice() {
|
||||
return /Android|iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
function isAppEnvironment() {
|
||||
return Boolean(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp);
|
||||
}
|
||||
|
||||
function requestAccessOrigin() {
|
||||
return new Promise((resolve) => {
|
||||
window.renderData = resolve;
|
||||
window.getIosAccessOriginParam = resolve;
|
||||
|
||||
if (!isAppEnvironment()) {
|
||||
setHeadersFromApp({
|
||||
authorization: "",
|
||||
reqLang: state.lang,
|
||||
appVersion: "1.0.0",
|
||||
buildVersion: "1",
|
||||
appChannel: "Web",
|
||||
reqImei: "H5-STATIC",
|
||||
sysOrigin: "LIKEI",
|
||||
sysOriginChild: "LIKEI"
|
||||
});
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let attempt = 0;
|
||||
const poll = () => {
|
||||
attempt += 1;
|
||||
if (window.app && typeof window.app.getAccessOrigin === "function") {
|
||||
resolve(window.app.getAccessOrigin());
|
||||
return;
|
||||
}
|
||||
if (attempt < 50) {
|
||||
window.setTimeout(poll, 100);
|
||||
return;
|
||||
}
|
||||
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
|
||||
window.FlutterApp.postMessage("requestAccessOrigin");
|
||||
}
|
||||
};
|
||||
|
||||
if (isMobileDevice() || isAppEnvironment()) {
|
||||
poll();
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function connectToApp() {
|
||||
const raw = await requestAccessOrigin();
|
||||
if (!raw) return { success: true, environment: "browser" };
|
||||
setHeadersFromApp(parseAccessOrigin(raw));
|
||||
return { success: true, environment: "app" };
|
||||
}
|
||||
|
||||
async function apiGet(path) {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
method: "GET",
|
||||
headers: commonHeaders
|
||||
});
|
||||
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.response = data;
|
||||
throw error;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async function fetchUserProfile() {
|
||||
const result = await apiGet("/team/member/profile");
|
||||
if (result.status && result.body) state.userInfo = result.body;
|
||||
}
|
||||
|
||||
async function fetchTeamInfo() {
|
||||
try {
|
||||
const result = await apiGet("/team/entry");
|
||||
if (result.status && result.body) state.teamInfo = result.body.teamProfile || result.body;
|
||||
} catch (error) {
|
||||
state.teamInfo = null;
|
||||
console.debug("BD leader team info unavailable:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchIdentity() {
|
||||
const result = await apiGet("/app/h5/identity");
|
||||
if (result.status && result.body) state.identityInfo = result.body;
|
||||
}
|
||||
|
||||
async function fetchBalanceTotal() {
|
||||
const result = await apiGet("/wallet/salary-account/balance/total");
|
||||
if (result.status && result.body) state.balanceTotal = result.body || {};
|
||||
}
|
||||
|
||||
async function fetchBDHistoryMore(billBelong) {
|
||||
const result = await apiGet(`/team/bd/history/more?billBelong=${encodeURIComponent(billBelong)}`);
|
||||
if (result.status && result.body) state.historyMore = result.body || {};
|
||||
}
|
||||
|
||||
async function fetchBDLeaderHistoryMore(billBelong) {
|
||||
const result = await apiGet(`/team/bd/leader/history/more?billBelong=${encodeURIComponent(billBelong)}`);
|
||||
if (result.status && result.body) state.historyMore = result.body || {};
|
||||
}
|
||||
|
||||
function setIdentity(identity) {
|
||||
state.identity = identity;
|
||||
localStorage.setItem("identity", JSON.stringify({ identity }));
|
||||
}
|
||||
|
||||
function clearPayee() {
|
||||
try {
|
||||
sessionStorage.removeItem("payee");
|
||||
localStorage.removeItem("payee");
|
||||
} catch (error) {
|
||||
console.debug("Payee cache clear skipped:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
if (value === null || value === undefined || value === "") return "0";
|
||||
const number = Number(value);
|
||||
if (Number.isNaN(number)) return String(value);
|
||||
return Number.isInteger(number) ? String(number) : number.toFixed(2);
|
||||
}
|
||||
|
||||
function normalizeStatus(status) {
|
||||
if (status === "In Progress" || status === t("in_progress")) return t("in_progress");
|
||||
if (status === "Pending" || status === t("pending")) return t("pending");
|
||||
if (status === "Completed" || status === t("completed")) return t("completed");
|
||||
if (status === "Out of account" || status === t("out_of_account")) return t("out_of_account");
|
||||
return status || "";
|
||||
}
|
||||
|
||||
function statusClass(status) {
|
||||
const value = normalizeStatus(status);
|
||||
if (value === t("completed")) return "is-completed";
|
||||
if (value === t("pending")) return "is-pending";
|
||||
if (value === t("out_of_account")) return "is-out";
|
||||
return "is-progress";
|
||||
}
|
||||
|
||||
function navigateTo(target) {
|
||||
const url = new URL(target, window.location.origin);
|
||||
const currentLang = new URLSearchParams(window.location.search).get("lang");
|
||||
if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang);
|
||||
window.location.href = `${url.pathname}${url.search}${url.hash}`;
|
||||
}
|
||||
|
||||
function closePage() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
function renderBalance() {
|
||||
document.getElementById("availableBalance").textContent = `$${formatMoney(state.balanceTotal.availableBalance || 0)}`;
|
||||
}
|
||||
|
||||
function historySource() {
|
||||
return state.historyType === "bd" ? state.bdHistory : state.bdLeaderHistory;
|
||||
}
|
||||
|
||||
function historyTitle() {
|
||||
return state.historyType === "bd" ? t("bd_salary") : t("bd_leader_history");
|
||||
}
|
||||
|
||||
function renderHistorySheet() {
|
||||
const data = historySource();
|
||||
document.getElementById("historyTitle").textContent = historyTitle();
|
||||
document.getElementById("historyTotalIncome").textContent = formatMoney(data.totalIncome || 0);
|
||||
document.getElementById("historyPreviousIncome").textContent = formatMoney(data.previousPeriodIncome || 0);
|
||||
|
||||
const list = document.getElementById("historyList");
|
||||
list.innerHTML = "";
|
||||
const items = data.incomeDetails || [];
|
||||
items.forEach((item) => list.appendChild(createHistoryItem(item)));
|
||||
}
|
||||
|
||||
function createHistoryItem(item) {
|
||||
const card = document.createElement("article");
|
||||
card.className = `salary-item ${statusClass(item.statusText)}`;
|
||||
|
||||
const status = document.createElement("div");
|
||||
status.className = "status-background";
|
||||
status.textContent = normalizeStatus(item.statusText);
|
||||
card.appendChild(status);
|
||||
|
||||
const content = document.createElement("div");
|
||||
content.className = "salary-item-content";
|
||||
if (state.historyType === "bd") {
|
||||
content.append(
|
||||
textLine(item.billTitle || ""),
|
||||
textLine(`${t("agency_number")}: ${item.agencyNumber || 0}`),
|
||||
pairLine(`${t("team_salary")}: $${item.teamSalaryAmount || 0}`, `${t("bd_income_salary")}: $${item.bdIncomeSalary || item.settlementSalary || 0} (${item.bdRatioSalary || item.commissionRate || 0}%)`),
|
||||
pairLine(`${t("team_top_up")}: $${item.teamRechargeAmount || 0}`, `${t("bd_income_top_up")}: $${item.bdIncomeRecharge || item.settlementSalary || 0} (${item.bdRatioRecharge || item.commissionRate || 0}%)`)
|
||||
);
|
||||
} else {
|
||||
content.append(
|
||||
textLine(item.billTitle || ""),
|
||||
pairLine(`${t("bd_number")}: ${item.bdNumber || 0}`, `${t("team_salary")}: $${item.teamSalaryAmount || 0}`)
|
||||
);
|
||||
}
|
||||
card.appendChild(content);
|
||||
|
||||
if (normalizeStatus(item.statusText) === t("completed")) {
|
||||
const more = document.createElement("button");
|
||||
more.type = "button";
|
||||
more.className = "more-bt";
|
||||
more.textContent = t("more");
|
||||
more.addEventListener("click", () => showHistoryMore(item.billBelong));
|
||||
card.appendChild(more);
|
||||
}
|
||||
return card;
|
||||
}
|
||||
|
||||
function textLine(text) {
|
||||
const node = document.createElement("div");
|
||||
node.textContent = text;
|
||||
return node;
|
||||
}
|
||||
|
||||
function pairLine(left, right) {
|
||||
const node = document.createElement("div");
|
||||
node.className = "pair-line";
|
||||
const leftNode = document.createElement("div");
|
||||
const rightNode = document.createElement("div");
|
||||
leftNode.textContent = left;
|
||||
rightNode.textContent = right;
|
||||
node.append(leftNode, rightNode);
|
||||
return node;
|
||||
}
|
||||
|
||||
async function showHistoryMore(billBelong) {
|
||||
try {
|
||||
if (state.historyType === "bd") {
|
||||
await fetchBDHistoryMore(billBelong);
|
||||
} else {
|
||||
await fetchBDLeaderHistoryMore(billBelong);
|
||||
}
|
||||
state.modalMode = "details";
|
||||
renderModal();
|
||||
} catch (error) {
|
||||
console.warn("BD leader history more failed:", error);
|
||||
showToast(error.response?.errorMsg || error.message || t("failed_to_load_history"));
|
||||
}
|
||||
}
|
||||
|
||||
function renderDetailsSheet() {
|
||||
const data = state.historyMore || {};
|
||||
document.getElementById("detailsPeriod").textContent = data.period || "";
|
||||
const summary = document.getElementById("detailsSummary");
|
||||
summary.innerHTML = "";
|
||||
if (state.historyType === "bd") {
|
||||
summary.append(
|
||||
textLine(`${t("team_top_up")}: $${data.teamTotalRecharge || 0}`),
|
||||
textLine(`${t("total_team_salary")}: $${data.teamTotalSalary || 0}`)
|
||||
);
|
||||
} else {
|
||||
summary.append(
|
||||
textLine(`${t("team_salary")}: $${data.teamSalary || 0}`),
|
||||
textLine(`${t("bd_number")}: ${data.bdNumber || 0}`)
|
||||
);
|
||||
}
|
||||
|
||||
const list = document.getElementById("memberDetails");
|
||||
list.innerHTML = "";
|
||||
(data.memberDetails || []).forEach((item) => {
|
||||
const card = document.createElement("article");
|
||||
card.className = "member-item";
|
||||
const img = document.createElement("img");
|
||||
img.src = state.historyType === "bd" ? item.userAvatar || "" : item.bdUserAvatar || "";
|
||||
img.alt = "";
|
||||
img.addEventListener("error", () => {
|
||||
img.removeAttribute("src");
|
||||
});
|
||||
const info = document.createElement("div");
|
||||
info.className = "member-info";
|
||||
info.append(
|
||||
textLine(state.historyType === "bd" ? item.userName || "" : item.bdUserName || ""),
|
||||
textLine(`${t("user_id_prefix")}${item.account || ""}`)
|
||||
);
|
||||
const metrics = document.createElement("div");
|
||||
metrics.className = "member-metrics";
|
||||
metrics.append(textLine(`${t("salary")}: $${item.salary || 0}`));
|
||||
if (state.historyType === "bd") {
|
||||
metrics.append(textLine(`${t("recharge")}: $${item.recharge || 0}`), textLine(`${t("host")}: ${item.hostCount || 0}`));
|
||||
} else {
|
||||
metrics.append(textLine(`${t("agencies")}: ${item.agencyCount || 0}`));
|
||||
}
|
||||
card.append(img, info, metrics);
|
||||
list.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function renderModal() {
|
||||
const mask = document.getElementById("maskLayer");
|
||||
const history = document.getElementById("historySheet");
|
||||
const details = document.getElementById("detailsSheet");
|
||||
const showHistory = state.modalMode === "history";
|
||||
const showDetails = state.modalMode === "details";
|
||||
mask.hidden = !showHistory && !showDetails;
|
||||
history.hidden = !showHistory;
|
||||
details.hidden = !showDetails;
|
||||
if (showHistory) renderHistorySheet();
|
||||
if (showDetails) renderDetailsSheet();
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
state.modalMode = "";
|
||||
state.historyMore = {};
|
||||
renderModal();
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.getElementById("toast");
|
||||
toast.textContent = message;
|
||||
toast.hidden = false;
|
||||
window.clearTimeout(showToast.timer);
|
||||
showToast.timer = window.setTimeout(() => {
|
||||
toast.hidden = true;
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
document.querySelector("[data-action='back']").addEventListener("click", closePage);
|
||||
document.querySelector("[data-action='language']").addEventListener("click", () => navigateTo("/language"));
|
||||
document.querySelectorAll("[data-route]").forEach((node) => {
|
||||
node.addEventListener("click", () => navigateTo(node.dataset.route));
|
||||
});
|
||||
document.querySelector("[data-action='sendGift']").addEventListener("click", () => navigateTo("/bd-item-distribution"));
|
||||
document.querySelector("[data-action='closeModal']").addEventListener("click", closeModal);
|
||||
}
|
||||
|
||||
async function initializePageData() {
|
||||
const tasks = [fetchUserProfile(), fetchTeamInfo(), fetchIdentity()];
|
||||
await Promise.allSettled(tasks);
|
||||
}
|
||||
|
||||
async function init() {
|
||||
setLanguage();
|
||||
setIcons();
|
||||
bindEvents();
|
||||
renderBalance();
|
||||
clearPayee();
|
||||
setIdentity("BD_LEADER");
|
||||
|
||||
try {
|
||||
await connectToApp();
|
||||
await initializePageData();
|
||||
await fetchBalanceTotal();
|
||||
} catch (error) {
|
||||
console.warn("BD leader center initialization failed:", error);
|
||||
showToast(error.response?.errorMsg || error.message || t("failed_to_load_balance"));
|
||||
} finally {
|
||||
renderBalance();
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@ -13,23 +13,128 @@
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>Yumi H5</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.__H5_ROUTE_PATH__ = "/bd-leader-center";
|
||||
</script>
|
||||
<script src="/assets/yumi-route-entry.js"></script>
|
||||
<script type="module" crossorigin src="/js/index-CIAVq8iD-1776148658686.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/css/index-VRlNQ74k-1776148661679.css">
|
||||
<title>BD Leader Center</title>
|
||||
<link rel="stylesheet" href="./bd-leader-center.css?v=bdleader-static-1">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<main class="bd-center">
|
||||
<header class="general-header">
|
||||
<button class="header-button" type="button" data-action="back" aria-label="Back">
|
||||
<span aria-hidden="true">‹</span>
|
||||
</button>
|
||||
<h1 data-i18n="bd_leader_center">BD Leader Center</h1>
|
||||
<button class="language-button" type="button" data-action="language" aria-label="Language">EN</button>
|
||||
</header>
|
||||
|
||||
<section class="content">
|
||||
<article class="gradient-border salary-card">
|
||||
<div class="salary-inner">
|
||||
<div class="salary-row">
|
||||
<div class="salary-title" data-i18n="my_available_salary">My available salary</div>
|
||||
<button class="salary-link" type="button" data-route="/available-income" data-i18n="my_salary_link">
|
||||
Details
|
||||
</button>
|
||||
</div>
|
||||
<div class="salary-amount">
|
||||
<img data-icon="salary" class="salary-icon" alt="">
|
||||
<span id="availableBalance">$0</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="gradient-border menu-card">
|
||||
<div class="menu-inner">
|
||||
<button type="button" class="menu-row" data-route="/policy?from=BDLead">
|
||||
<span class="menu-label">
|
||||
<img data-icon="policy" alt="">
|
||||
<span data-i18n="bd_policy_link">BD policy</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img arrow" alt="">
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-route="/agency-list">
|
||||
<span class="menu-label">
|
||||
<img data-icon="policy" alt="">
|
||||
<span data-i18n="agency_list_link">Agency list</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img arrow" alt="">
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-route="/bd-list">
|
||||
<span class="menu-label">
|
||||
<img data-icon="policy" alt="">
|
||||
<span data-i18n="bd_list_link">BD List</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img arrow" alt="">
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-route="/invite-agency">
|
||||
<span class="menu-label">
|
||||
<img data-icon="invite" alt="">
|
||||
<span data-i18n="invite_become_agent">Invite to become agent</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img arrow" alt="">
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-route="/invite-bd">
|
||||
<span class="menu-label">
|
||||
<img data-icon="invite" alt="">
|
||||
<span data-i18n="invite_become_BD">Invite to become BD</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img arrow" alt="">
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row" data-route="/bank-cards">
|
||||
<span class="menu-label">
|
||||
<img data-icon="bank" alt="">
|
||||
<span data-i18n="bank_cards_link">Bank cards</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img arrow" alt="">
|
||||
</button>
|
||||
|
||||
<div class="divider send-row" hidden></div>
|
||||
<button type="button" class="menu-row send-row" data-action="sendGift" hidden>
|
||||
<span class="menu-label">
|
||||
<img data-icon="gift" alt="">
|
||||
<span data-i18n="send_welcome_gift">Send welcome gift</span>
|
||||
</span>
|
||||
<img data-icon="arrow" class="flip-img arrow" alt="">
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="mask-layer" id="maskLayer" hidden>
|
||||
<div class="mask-hit" data-action="closeModal"></div>
|
||||
<section class="sheet history-sheet" id="historySheet" hidden>
|
||||
<div class="sheet-title" id="historyTitle">BD salary</div>
|
||||
<div class="summary-box">
|
||||
<div>
|
||||
<span data-i18n="total_income">Total income</span>
|
||||
<span> $</span><span id="historyTotalIncome">0</span>
|
||||
</div>
|
||||
<div>
|
||||
<span data-i18n="previous_period_income">Previous period income</span>
|
||||
<span>: $</span><span id="historyPreviousIncome">0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="salary-list" id="historyList"></div>
|
||||
</section>
|
||||
|
||||
<section class="sheet details-sheet" id="detailsSheet" hidden>
|
||||
<div class="sheet-title" id="detailsPeriod"></div>
|
||||
<div class="details-summary" id="detailsSummary"></div>
|
||||
<div class="member-list" id="memberDetails"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
<script type="module" src="./bd-leader-center.js?v=bdleader-static-3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
13
h5/bd-list/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>BD List</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"BD List","mode":"teamHistory","listEndpoint":"/team/bd/member-bill/list?type=BDLEADER","historyEndpoint":"/team/bd/leader/history","moreEndpoint":"/team/bd/leader/history/more?billBelong=","countLabel":"BD number","totalLabel":"Team salary","memberSide1":"Team salary","memberSide2":"Agencies"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/cash-out-details/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Cash Out Details</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Cash Out Details","mode":"policy"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/cash-out/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Cash Out</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Cash Out","mode":"walletAction","action":"Cash out"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/cash-withdraw/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Cash Withdraw</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Cash Withdraw","mode":"walletAction","action":"Cash out"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
318
h5/center-static/center-static.css
Normal file
@ -0,0 +1,318 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #1f3528;
|
||||
background: #edf8f1;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.center-page {
|
||||
min-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background: #edf8f1;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr 58px;
|
||||
align-items: center;
|
||||
min-height: calc(48px + env(safe-area-inset-top));
|
||||
padding: env(safe-area-inset-top) 10px 0;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
min-width: 0;
|
||||
font-size: 1.35em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #153d28;
|
||||
}
|
||||
|
||||
.icon-button,
|
||||
.language-button {
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
color: #1f6f46;
|
||||
}
|
||||
|
||||
.icon-button span {
|
||||
display: block;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.language-button {
|
||||
justify-self: end;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: #dff5e7;
|
||||
color: #24724a;
|
||||
font-size: 0.86em;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.surface-card {
|
||||
border: 1px solid #b7e0c6;
|
||||
border-radius: 18px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
position: relative;
|
||||
padding: 18px;
|
||||
background: #f8fff9;
|
||||
border-color: #94d9ae;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 20px;
|
||||
background: #d7f5e2;
|
||||
border: 10px solid #ecfbf1;
|
||||
}
|
||||
|
||||
.hero-row {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-weight: 700;
|
||||
color: #244735;
|
||||
}
|
||||
|
||||
.hero-link {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: #fff3c8;
|
||||
color: #846216;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-value {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 6px;
|
||||
font-size: 1.65em;
|
||||
font-weight: 800;
|
||||
color: #105c36;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
padding: 12px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid #cce9d7;
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.summary-item:nth-child(2n) {
|
||||
background: #f7fbec;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 0.83em;
|
||||
font-weight: 700;
|
||||
color: #587060;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
margin-top: 4px;
|
||||
font-size: 1.12em;
|
||||
font-weight: 800;
|
||||
color: #153d28;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
padding: 14px 16px 16px;
|
||||
background: #fffaf0;
|
||||
border-color: #e7c777;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 800;
|
||||
color: #3f3826;
|
||||
}
|
||||
|
||||
.task-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.task-label {
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 800;
|
||||
color: #4b4536;
|
||||
}
|
||||
|
||||
.task-value {
|
||||
font-size: 0.95em;
|
||||
font-weight: 800;
|
||||
color: #1f3528;
|
||||
}
|
||||
|
||||
.menu-card {
|
||||
padding: 10px;
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 4px 4px 10px;
|
||||
font-size: 1.02em;
|
||||
font-weight: 800;
|
||||
color: #1f5134;
|
||||
}
|
||||
|
||||
.section-title:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-row {
|
||||
width: 100%;
|
||||
min-height: 4.5em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 2) {
|
||||
background: #eefbf3;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 3) {
|
||||
background: #f7fbec;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 4) {
|
||||
background: #eef7fb;
|
||||
}
|
||||
|
||||
.menu-row:nth-of-type(4n + 5) {
|
||||
background: #fff5ed;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 2.65em;
|
||||
height: 2.65em;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 14px;
|
||||
border: 1px solid #bce5ca;
|
||||
background: #d7f5e2;
|
||||
color: #1e6b43;
|
||||
font-size: 1em;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
min-width: 0;
|
||||
font-size: 1em;
|
||||
font-weight: 800;
|
||||
color: #173a28;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
flex-shrink: 0;
|
||||
color: #7fa48d;
|
||||
font-size: 1.35em;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: calc(22px + env(safe-area-inset-bottom));
|
||||
z-index: 20;
|
||||
max-width: calc(100% - 40px);
|
||||
transform: translateX(-50%);
|
||||
padding: 9px 13px;
|
||||
border-radius: 999px;
|
||||
background: rgba(22, 56, 36, 0.92);
|
||||
color: #fff;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
297
h5/center-static/center-static.js
Normal file
@ -0,0 +1,297 @@
|
||||
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
|
||||
let commonHeaders = {
|
||||
authorization: "Bearer undefined",
|
||||
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
|
||||
"req-client": "Android",
|
||||
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
|
||||
"req-lang": "id-ID",
|
||||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
|
||||
"req-version": "V2",
|
||||
"req-zone": "Asia/Shanghai"
|
||||
};
|
||||
|
||||
const fallbackConfig = {
|
||||
title: "Center",
|
||||
identity: "USER",
|
||||
heroTitle: "Available salary",
|
||||
heroLink: "Details",
|
||||
heroRoute: "/history-salary",
|
||||
balanceType: "",
|
||||
summaries: [],
|
||||
sections: []
|
||||
};
|
||||
|
||||
const config = { ...fallbackConfig, ...(window.CENTER_PAGE || {}) };
|
||||
const state = {
|
||||
lang: resolveLanguage(),
|
||||
heroValue: "0",
|
||||
summaries: [...(config.summaries || [])]
|
||||
};
|
||||
|
||||
function resolveLanguage() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||||
const hashParams = new URLSearchParams(hashQuery);
|
||||
const lang = params.get("lang") || hashParams.get("lang") || navigator.language || "en";
|
||||
if (lang.startsWith("ar")) return "ar";
|
||||
if (lang.startsWith("tr")) return "tr";
|
||||
if (lang.startsWith("bn")) return "bn";
|
||||
return "en";
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
if (value === null || value === undefined || value === "") return "0";
|
||||
const number = Number(value);
|
||||
if (Number.isNaN(number)) return String(value);
|
||||
return Number.isInteger(number) ? String(number) : number.toFixed(2);
|
||||
}
|
||||
|
||||
function splitHeaderPairs(value) {
|
||||
const result = {};
|
||||
if (!value) return result;
|
||||
value.split(";").forEach((item) => {
|
||||
const index = item.indexOf("=");
|
||||
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseAccessOrigin(raw) {
|
||||
const data = JSON.parse(raw);
|
||||
const appIntel = splitHeaderPairs(data["Req-App-Intel"]);
|
||||
const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]);
|
||||
return {
|
||||
reqLang: data["Req-Lang"],
|
||||
authorization: data.Authorization,
|
||||
buildVersion: appIntel.build,
|
||||
appVersion: appIntel.version,
|
||||
appChannel: appIntel.channel,
|
||||
reqImei: appIntel["Req-Imei"],
|
||||
sysOrigin: sysOrigin.origin,
|
||||
sysOriginChild: sysOrigin.child
|
||||
};
|
||||
}
|
||||
|
||||
function setHeadersFromApp(headers) {
|
||||
const next = {};
|
||||
if (headers.authorization) next.authorization = headers.authorization;
|
||||
if (headers.reqLang) next["req-lang"] = headers.reqLang;
|
||||
if (headers.appVersion || headers.buildVersion || headers.appChannel) {
|
||||
const intel = [];
|
||||
if (headers.appVersion) intel.push(`version=${headers.appVersion}`);
|
||||
if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`);
|
||||
if (headers.appChannel) intel.push(`channel=${headers.appChannel}`);
|
||||
next["req-app-intel"] = intel.join(";");
|
||||
}
|
||||
if (headers.reqImei) next["req-imei"] = headers.reqImei;
|
||||
if (headers.sysOrigin) {
|
||||
const origin = [`origin=${headers.sysOrigin}`];
|
||||
if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`);
|
||||
next["req-sys-origin"] = origin.join(";");
|
||||
}
|
||||
commonHeaders = { ...commonHeaders, ...next };
|
||||
}
|
||||
|
||||
function requestAccessOrigin() {
|
||||
return new Promise((resolve) => {
|
||||
window.renderData = resolve;
|
||||
window.getIosAccessOriginParam = resolve;
|
||||
|
||||
if (!(window.app || window.webkit || window.FlutterPageControl)) {
|
||||
setHeadersFromApp({
|
||||
authorization: "",
|
||||
reqLang: state.lang,
|
||||
appVersion: "1.0.0",
|
||||
buildVersion: "1",
|
||||
appChannel: "Web",
|
||||
reqImei: "H5-STATIC",
|
||||
sysOrigin: "LIKEI",
|
||||
sysOriginChild: "LIKEI"
|
||||
});
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let attempt = 0;
|
||||
const poll = () => {
|
||||
attempt += 1;
|
||||
if (window.app && typeof window.app.getAccessOrigin === "function") {
|
||||
resolve(window.app.getAccessOrigin());
|
||||
return;
|
||||
}
|
||||
if (attempt < 50) {
|
||||
window.setTimeout(poll, 100);
|
||||
return;
|
||||
}
|
||||
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
|
||||
window.FlutterApp.postMessage("requestAccessOrigin");
|
||||
}
|
||||
};
|
||||
poll();
|
||||
});
|
||||
}
|
||||
|
||||
async function connectToApp() {
|
||||
const raw = await requestAccessOrigin();
|
||||
if (raw) setHeadersFromApp(parseAccessOrigin(raw));
|
||||
}
|
||||
|
||||
async function apiGet(path) {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
method: "GET",
|
||||
headers: commonHeaders
|
||||
});
|
||||
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.response = data;
|
||||
throw error;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async function fetchHeroValue() {
|
||||
if (config.balanceType) {
|
||||
const result = await apiGet(`/wallet/salary-account/balance?salaryType=${config.balanceType}`);
|
||||
if (result.status && result.body) state.heroValue = formatMoney(result.body.availableBalance || 0);
|
||||
return;
|
||||
}
|
||||
if (config.balanceEndpoint) {
|
||||
const result = await apiGet(config.balanceEndpoint);
|
||||
const body = result.body || {};
|
||||
state.heroValue = formatMoney(body.currentSalary ?? body.availableBalance ?? body.balance ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.getElementById("toast");
|
||||
toast.textContent = message;
|
||||
toast.hidden = false;
|
||||
window.clearTimeout(showToast.timer);
|
||||
showToast.timer = window.setTimeout(() => {
|
||||
toast.hidden = true;
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function normalizeRoute(target) {
|
||||
const alias = {
|
||||
"/invite-agency": "/invite-agency/",
|
||||
"/invite-bd": "/invite-bd/",
|
||||
"/invite-bd-leader": "/invite-bd-leader/",
|
||||
"/invite-recharge-agency": "/invite-recharge-agency/"
|
||||
};
|
||||
const url = new URL(alias[target] || target, window.location.origin);
|
||||
const currentLang = new URLSearchParams(window.location.search).get("lang");
|
||||
if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang);
|
||||
return `${url.pathname}${url.search}${url.hash}`;
|
||||
}
|
||||
|
||||
function navigateTo(target) {
|
||||
window.location.href = normalizeRoute(target);
|
||||
}
|
||||
|
||||
function menuIcon(text) {
|
||||
return (text || "?").trim().slice(0, 1).toUpperCase();
|
||||
}
|
||||
|
||||
function render() {
|
||||
document.documentElement.lang = state.lang;
|
||||
document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr";
|
||||
document.getElementById("pageTitle").textContent = config.title;
|
||||
document.querySelector("[data-action='language']").textContent = state.lang.toUpperCase();
|
||||
document.getElementById("heroTitle").textContent = config.heroTitle;
|
||||
document.getElementById("heroLink").textContent = config.heroLink || "Details";
|
||||
document.getElementById("heroValue").textContent = `$${state.heroValue}`;
|
||||
|
||||
const summaryRoot = document.getElementById("summaryGrid");
|
||||
summaryRoot.innerHTML = "";
|
||||
state.summaries.forEach((item) => {
|
||||
const node = document.createElement("div");
|
||||
node.className = "summary-item";
|
||||
node.innerHTML = `<div class="summary-label"></div><div class="summary-value"></div>`;
|
||||
node.querySelector(".summary-label").textContent = item.label;
|
||||
node.querySelector(".summary-value").textContent = item.value;
|
||||
summaryRoot.appendChild(node);
|
||||
});
|
||||
summaryRoot.hidden = !state.summaries.length;
|
||||
|
||||
const oldTask = document.getElementById("taskCard");
|
||||
if (oldTask) oldTask.remove();
|
||||
if (config.task) {
|
||||
const taskCard = document.createElement("section");
|
||||
taskCard.className = "surface-card task-card";
|
||||
taskCard.id = "taskCard";
|
||||
taskCard.innerHTML = `<div class="task-title"></div><div class="task-grid"></div>`;
|
||||
taskCard.querySelector(".task-title").textContent = config.task.title || "";
|
||||
const taskGrid = taskCard.querySelector(".task-grid");
|
||||
(config.task.items || []).forEach((item) => {
|
||||
const node = document.createElement("div");
|
||||
node.innerHTML = `<div class="task-label"></div><div class="task-value"></div>`;
|
||||
node.querySelector(".task-label").textContent = item.label;
|
||||
node.querySelector(".task-value").textContent = item.value;
|
||||
taskGrid.appendChild(node);
|
||||
});
|
||||
const sections = document.getElementById("sections");
|
||||
sections.parentNode.insertBefore(taskCard, sections);
|
||||
}
|
||||
|
||||
const sectionRoot = document.getElementById("sections");
|
||||
sectionRoot.innerHTML = "";
|
||||
(config.sections || []).forEach((section) => {
|
||||
const card = document.createElement("section");
|
||||
card.className = "surface-card menu-card";
|
||||
const title = document.createElement("div");
|
||||
title.className = "section-title";
|
||||
title.textContent = section.title || "";
|
||||
card.appendChild(title);
|
||||
|
||||
(section.items || []).forEach((item) => {
|
||||
const row = document.createElement("button");
|
||||
row.type = "button";
|
||||
row.className = "menu-row";
|
||||
row.dataset.route = item.route;
|
||||
row.innerHTML = `<div class="menu-label"><span class="menu-icon"></span><span class="menu-text"></span></div><span class="arrow">›</span>`;
|
||||
row.querySelector(".menu-icon").textContent = item.icon || menuIcon(item.label);
|
||||
row.querySelector(".menu-text").textContent = item.label;
|
||||
row.addEventListener("click", () => navigateTo(item.route));
|
||||
card.appendChild(row);
|
||||
});
|
||||
sectionRoot.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
document.querySelector("[data-action='back']").addEventListener("click", () => {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
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");
|
||||
}
|
||||
});
|
||||
document.querySelector("[data-action='language']").addEventListener("click", () => navigateTo("/language"));
|
||||
document.getElementById("heroLink").addEventListener("click", () => navigateTo(config.heroRoute || "/history-salary"));
|
||||
}
|
||||
|
||||
async function init() {
|
||||
localStorage.setItem("identity", JSON.stringify({ identity: config.identity }));
|
||||
render();
|
||||
bindEvents();
|
||||
try {
|
||||
await connectToApp();
|
||||
await fetchHeroValue();
|
||||
} catch (error) {
|
||||
console.warn(`${config.title} data request failed:`, error);
|
||||
showToast(error.response?.errorMsg || error.message || "Failed to load data.");
|
||||
} finally {
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
177
h5/center-static/static-route.css
Normal file
@ -0,0 +1,177 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background: #edf8f1;
|
||||
color: #1f3528;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #edf8f1;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr 58px;
|
||||
align-items: center;
|
||||
min-height: calc(48px + env(safe-area-inset-top));
|
||||
padding: env(safe-area-inset-top) 10px 0;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
min-width: 0;
|
||||
color: #153d28;
|
||||
font-size: 1.32em;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.back-button,
|
||||
.lang-button {
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 800;
|
||||
color: #1f6f46;
|
||||
}
|
||||
|
||||
.back-button span {
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.lang-button {
|
||||
justify-self: end;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: #dff5e7;
|
||||
font-size: 0.86em;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.search-card,
|
||||
.list-card,
|
||||
.empty-card {
|
||||
border: 1px solid #b7e0c6;
|
||||
border-radius: 18px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
}
|
||||
|
||||
.search-card {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.search-card input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border: 1px solid #cce9d7;
|
||||
border-radius: 12px;
|
||||
background: #f8fff9;
|
||||
padding: 10px 12px;
|
||||
outline: none;
|
||||
color: #1f3528;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.search-card button,
|
||||
.primary-button {
|
||||
flex-shrink: 0;
|
||||
border-radius: 12px;
|
||||
background: #dff5e7;
|
||||
padding: 0 14px;
|
||||
color: #1f6f46;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.list-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
min-height: 4.2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid #e2f1e7;
|
||||
}
|
||||
|
||||
.row:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.row:nth-child(2n) {
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.row-title {
|
||||
font-weight: 800;
|
||||
color: #173a28;
|
||||
}
|
||||
|
||||
.row-subtitle {
|
||||
margin-top: 4px;
|
||||
color: #6d7e72;
|
||||
font-size: 0.86em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tag {
|
||||
flex-shrink: 0;
|
||||
border-radius: 999px;
|
||||
background: #fff3c8;
|
||||
padding: 5px 9px;
|
||||
color: #846216;
|
||||
font-size: 0.82em;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.empty-card {
|
||||
padding: 24px 16px;
|
||||
text-align: center;
|
||||
color: #6d7e72;
|
||||
font-weight: 700;
|
||||
}
|
||||
56
h5/center-static/static-route.js
Normal file
@ -0,0 +1,56 @@
|
||||
const config = window.STATIC_ROUTE || {};
|
||||
|
||||
function lang() {
|
||||
const value = new URLSearchParams(window.location.search).get("lang") || navigator.language || "en";
|
||||
if (value.startsWith("ar")) return "AR";
|
||||
if (value.startsWith("tr")) return "TR";
|
||||
if (value.startsWith("bn")) return "BN";
|
||||
return "EN";
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
function renderRows(rows) {
|
||||
const list = document.getElementById("list");
|
||||
list.innerHTML = "";
|
||||
rows.forEach((row) => {
|
||||
const node = document.createElement("div");
|
||||
node.className = "row";
|
||||
node.innerHTML = `<div><div class="row-title"></div><div class="row-subtitle"></div></div><div class="tag"></div>`;
|
||||
node.querySelector(".row-title").textContent = row.title;
|
||||
node.querySelector(".row-subtitle").textContent = row.subtitle || "";
|
||||
node.querySelector(".tag").textContent = row.tag || "Ready";
|
||||
list.appendChild(node);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.title = config.title || "Yumi H5";
|
||||
document.getElementById("title").textContent = config.title || "Yumi H5";
|
||||
document.getElementById("lang").textContent = lang();
|
||||
document.getElementById("keyword").placeholder = config.placeholder || "Search";
|
||||
document.getElementById("empty").textContent = config.empty || "No data";
|
||||
document.querySelector("[data-action='back']").addEventListener("click", goBack);
|
||||
document.getElementById("search").addEventListener("click", () => {
|
||||
const value = document.getElementById("keyword").value.trim();
|
||||
const base = config.rows || [];
|
||||
renderRows(value ? base.filter((row) => JSON.stringify(row).toLowerCase().includes(value.toLowerCase())) : base);
|
||||
});
|
||||
renderRows(config.rows || []);
|
||||
document.getElementById("list").hidden = !(config.rows || []).length;
|
||||
document.getElementById("empty").hidden = (config.rows || []).length > 0;
|
||||
}
|
||||
|
||||
init();
|
||||
13
h5/exchange-gold-coins/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Exchange Coins</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Exchange Coins","mode":"walletAction","action":"Exchange"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/history-salary/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>History Salary</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"History Salary","mode":"teamBill"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
431
h5/host-center/host-center.css
Normal file
@ -0,0 +1,431 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
background: #edf8f1;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.host-center {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background: #edf8f1;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr 58px;
|
||||
align-items: center;
|
||||
min-height: calc(48px + env(safe-area-inset-top));
|
||||
padding: env(safe-area-inset-top) 10px 0;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
min-width: 0;
|
||||
color: #153d28;
|
||||
font-size: 1.25em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-icon,
|
||||
.lang-button {
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1f6f46;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.header-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.lang-button {
|
||||
justify-self: end;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: #dff5e7;
|
||||
font-size: 0.86em;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 1%;
|
||||
color: #173a28;
|
||||
font-size: 1.25em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.surface-card {
|
||||
margin: 0 1%;
|
||||
border: 1px solid #b7e0c6;
|
||||
border-radius: 20px;
|
||||
background: #fbfffc;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
}
|
||||
|
||||
.agency-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
flex: 0 0 50px;
|
||||
overflow: hidden;
|
||||
border-radius: 25px;
|
||||
background: #91d9aa;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.avatar img,
|
||||
.avatar span {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agency-name {
|
||||
width: 100%;
|
||||
margin-bottom: 4px;
|
||||
color: #244735;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.agency-id {
|
||||
color: #647468;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.leave-button {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid #ff9500;
|
||||
border-radius: 16px;
|
||||
color: #ff9500;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.salary-card,
|
||||
.task-card,
|
||||
.menu-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card-row,
|
||||
.menu-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-title,
|
||||
.task-title {
|
||||
color: #244735;
|
||||
font-size: 1.08em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.text-link {
|
||||
color: rgba(0, 0, 0, 0.42);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.salary-value {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #105c36;
|
||||
font-size: 1.45em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.money-icon {
|
||||
width: 1.05em;
|
||||
height: 1.05em;
|
||||
display: block;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
background: #fffaf0;
|
||||
border-color: #e7c777;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.task-grid {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-label,
|
||||
.task-value {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.task-label {
|
||||
margin-bottom: 5px;
|
||||
color: #4b4536;
|
||||
}
|
||||
|
||||
.task-value {
|
||||
color: #1f3528;
|
||||
}
|
||||
|
||||
.menu-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4vw;
|
||||
}
|
||||
|
||||
.menu-row {
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1vw;
|
||||
}
|
||||
|
||||
.menu-main span {
|
||||
min-width: 0;
|
||||
color: #173a28;
|
||||
font-size: 1.4em;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
display: block;
|
||||
width: 1.6em;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
display: block;
|
||||
width: 1.5em;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-bottom: 1px solid #d9eadf;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: calc(22px + env(safe-area-inset-bottom));
|
||||
z-index: 30;
|
||||
max-width: calc(100% - 40px);
|
||||
transform: translateX(-50%);
|
||||
padding: 9px 13px;
|
||||
border-radius: 999px;
|
||||
background: rgba(22, 56, 36, 0.92);
|
||||
color: #fff;
|
||||
font-size: 0.9em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.modal-card {
|
||||
width: 80%;
|
||||
max-width: 420px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2vw;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-size: 1.2em;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-submessage {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
font-size: 0.8em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
width: 80%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.modal-primary,
|
||||
.modal-secondary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 38px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 70px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.modal-primary {
|
||||
width: 50%;
|
||||
background: #ff9500;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.modal-actions .modal-primary,
|
||||
.modal-secondary {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.modal-secondary {
|
||||
border: 1px solid #ffb805;
|
||||
background: #fff;
|
||||
color: #ff9500;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[dir="rtl"] .menu-row,
|
||||
[dir="rtl"] .page-content {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
* {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 360px) {
|
||||
* {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
* {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
653
h5/host-center/host-center.js
Normal file
@ -0,0 +1,653 @@
|
||||
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
const DEFAULT_AVATAR = "/assets/defaultAvatar-CdxWBK1k-1776148661448.png";
|
||||
const icons = {
|
||||
policy: "./icons/policy.svg",
|
||||
exchange: "./icons/exchange.svg",
|
||||
transfer: "./icons/transfer.svg",
|
||||
withdraw: "./icons/withdraw.svg",
|
||||
bank: "./icons/bank.svg",
|
||||
money: "./icons/money.svg",
|
||||
back: "./icons/back.svg",
|
||||
arrow: "./icons/arrow.svg"
|
||||
};
|
||||
|
||||
let commonHeaders = {
|
||||
authorization: "Bearer undefined",
|
||||
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
|
||||
"req-client": "Android",
|
||||
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
|
||||
"req-lang": "id-ID",
|
||||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
|
||||
"req-version": "V2",
|
||||
"req-zone": "Asia/Shanghai"
|
||||
};
|
||||
|
||||
const i18n = {
|
||||
en: {
|
||||
host_center: "Host Center",
|
||||
my_agency: "My agency",
|
||||
user_id_prefix: "ID:",
|
||||
leave: "Leave",
|
||||
my_available_salary: "My available salary",
|
||||
my_salary_details: "My salary details",
|
||||
todays_task: "Today's task",
|
||||
anchor_type: "Anchor type",
|
||||
minutes: "Minutes",
|
||||
gift_task: "Gift Task",
|
||||
platform_policy: "Platform policy",
|
||||
exchange_coins: "Exchange coins",
|
||||
transfer: "Transfer",
|
||||
cash_withdraw: "Cash withdraw",
|
||||
bank_accounts: "Bank accounts",
|
||||
prompt: "Prompt",
|
||||
tips: "Tips",
|
||||
fill_info_tips: "Please complete your information first.",
|
||||
card_issue_tips: "Please make sure your bank card and KYC information are available.",
|
||||
to_improve: "To improve",
|
||||
leave_agent_tips: "Are you sure you want to leave?",
|
||||
cancel: "Cancel",
|
||||
confirm: "Confirm",
|
||||
failed_to_load_balance: "Failed to load balance. Please try again.",
|
||||
something_went_wrong: "Something went wrong."
|
||||
},
|
||||
ar: {
|
||||
host_center: "مركز المضيف",
|
||||
my_agency: "وكالتي",
|
||||
user_id_prefix: "ID:",
|
||||
leave: "مغادرة",
|
||||
my_available_salary: "راتبي المتاح",
|
||||
my_salary_details: "تفاصيل الراتب",
|
||||
todays_task: "مهمة اليوم",
|
||||
anchor_type: "نوع المضيف",
|
||||
minutes: "الدقائق",
|
||||
gift_task: "مهمة الهدايا",
|
||||
platform_policy: "سياسة المنصة",
|
||||
exchange_coins: "استبدال العملات",
|
||||
transfer: "تحويل",
|
||||
cash_withdraw: "سحب نقدي",
|
||||
bank_accounts: "الحسابات البنكية",
|
||||
prompt: "تنبيه",
|
||||
tips: "نصائح",
|
||||
fill_info_tips: "يرجى إكمال معلوماتك أولا.",
|
||||
card_issue_tips: "يرجى التأكد من توفر البطاقة البنكية ومعلومات KYC.",
|
||||
to_improve: "إكمال",
|
||||
leave_agent_tips: "هل أنت متأكد أنك تريد المغادرة؟",
|
||||
cancel: "إلغاء",
|
||||
confirm: "تأكيد",
|
||||
failed_to_load_balance: "فشل تحميل الرصيد. يرجى المحاولة مرة أخرى.",
|
||||
something_went_wrong: "حدث خطأ ما."
|
||||
},
|
||||
tr: {
|
||||
host_center: "Host Merkezi",
|
||||
my_agency: "Ajansım",
|
||||
user_id_prefix: "ID:",
|
||||
leave: "Ayrıl",
|
||||
my_available_salary: "Kullanılabilir maaşım",
|
||||
my_salary_details: "Maaş detayları",
|
||||
todays_task: "Bugünkü görev",
|
||||
anchor_type: "Host türü",
|
||||
minutes: "Dakika",
|
||||
gift_task: "Hediye görevi",
|
||||
platform_policy: "Platform politikası",
|
||||
exchange_coins: "Coin değiştir",
|
||||
transfer: "Transfer",
|
||||
cash_withdraw: "Para çek",
|
||||
bank_accounts: "Banka hesapları",
|
||||
prompt: "Uyarı",
|
||||
tips: "İpuçları",
|
||||
fill_info_tips: "Lütfen önce bilgilerinizi tamamlayın.",
|
||||
card_issue_tips: "Lütfen banka kartı ve KYC bilgilerinizin mevcut olduğundan emin olun.",
|
||||
to_improve: "Tamamla",
|
||||
leave_agent_tips: "Ayrılmak istediğinizden emin misiniz?",
|
||||
cancel: "İptal",
|
||||
confirm: "Onayla",
|
||||
failed_to_load_balance: "Bakiye yüklenemedi. Lütfen tekrar deneyin.",
|
||||
something_went_wrong: "Bir şeyler ters gitti."
|
||||
},
|
||||
bn: {
|
||||
host_center: "হোস্ট সেন্টার",
|
||||
my_agency: "আমার এজেন্সি",
|
||||
user_id_prefix: "ID:",
|
||||
leave: "ছাড়ুন",
|
||||
my_available_salary: "আমার উপলভ্য বেতন",
|
||||
my_salary_details: "বেতনের বিবরণ",
|
||||
todays_task: "আজকের কাজ",
|
||||
anchor_type: "হোস্ট ধরন",
|
||||
minutes: "মিনিট",
|
||||
gift_task: "গিফট টাস্ক",
|
||||
platform_policy: "প্ল্যাটফর্ম নীতি",
|
||||
exchange_coins: "কয়েন এক্সচেঞ্জ",
|
||||
transfer: "ট্রান্সফার",
|
||||
cash_withdraw: "ক্যাশ উত্তোলন",
|
||||
bank_accounts: "ব্যাংক অ্যাকাউন্ট",
|
||||
prompt: "প্রম্পট",
|
||||
tips: "টিপস",
|
||||
fill_info_tips: "প্রথমে আপনার তথ্য সম্পূর্ণ করুন।",
|
||||
card_issue_tips: "আপনার ব্যাংক কার্ড এবং KYC তথ্য আছে কিনা নিশ্চিত করুন।",
|
||||
to_improve: "সম্পূর্ণ করুন",
|
||||
leave_agent_tips: "আপনি কি নিশ্চিত যে আপনি ছাড়তে চান?",
|
||||
cancel: "বাতিল",
|
||||
confirm: "নিশ্চিত",
|
||||
failed_to_load_balance: "ব্যালেন্স লোড করতে ব্যর্থ। আবার চেষ্টা করুন।",
|
||||
something_went_wrong: "কিছু ভুল হয়েছে।"
|
||||
}
|
||||
};
|
||||
|
||||
const state = {
|
||||
lang: resolveLanguage(),
|
||||
profile: {},
|
||||
teamInfo: null,
|
||||
identityInfo: null,
|
||||
agency: {},
|
||||
salaryInfo: { currentSalary: "0" },
|
||||
taskInfo: { anchorType: "Chat", minutesProgress: 0, minutesTotal: 120, giftTask: 0 },
|
||||
bankCards: [],
|
||||
activeBankCard: null,
|
||||
withdrawInfo: null,
|
||||
withdrawEnabled: false
|
||||
};
|
||||
|
||||
const $ = (selector) => document.querySelector(selector);
|
||||
const $$ = (selector) => Array.from(document.querySelectorAll(selector));
|
||||
const t = (key) => (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key;
|
||||
|
||||
function resolveLanguage() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||||
const hashParams = new URLSearchParams(hashQuery);
|
||||
const lang = params.get("lang") || hashParams.get("lang") || navigator.language || "en";
|
||||
if (lang.startsWith("ar")) return "ar";
|
||||
if (lang.startsWith("tr")) return "tr";
|
||||
if (lang.startsWith("bn")) return "bn";
|
||||
return "en";
|
||||
}
|
||||
|
||||
function splitHeaderPairs(value) {
|
||||
const result = {};
|
||||
if (!value) return result;
|
||||
value.split(";").forEach((item) => {
|
||||
const index = item.indexOf("=");
|
||||
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseAccessOrigin(raw) {
|
||||
const data = JSON.parse(raw);
|
||||
const appIntel = splitHeaderPairs(data["Req-App-Intel"]);
|
||||
const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]);
|
||||
return {
|
||||
reqLang: data["Req-Lang"],
|
||||
authorization: data.Authorization,
|
||||
buildVersion: appIntel.build,
|
||||
appVersion: appIntel.version,
|
||||
appChannel: appIntel.channel,
|
||||
reqImei: appIntel["Req-Imei"],
|
||||
sysOrigin: sysOrigin.origin,
|
||||
sysOriginChild: sysOrigin.child
|
||||
};
|
||||
}
|
||||
|
||||
function setHeadersFromApp(headers) {
|
||||
const next = {};
|
||||
if (headers.authorization) next.authorization = headers.authorization;
|
||||
if (headers.reqLang) next["req-lang"] = headers.reqLang;
|
||||
if (headers.appVersion || headers.buildVersion || headers.appChannel) {
|
||||
const intel = [];
|
||||
if (headers.appVersion) intel.push(`version=${headers.appVersion}`);
|
||||
if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`);
|
||||
if (headers.appChannel) intel.push(`channel=${headers.appChannel}`);
|
||||
next["req-app-intel"] = intel.join(";");
|
||||
}
|
||||
if (headers.reqImei) next["req-imei"] = headers.reqImei;
|
||||
if (headers.sysOrigin) {
|
||||
const origin = [`origin=${headers.sysOrigin}`];
|
||||
if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`);
|
||||
next["req-sys-origin"] = origin.join(";");
|
||||
}
|
||||
commonHeaders = { ...commonHeaders, ...next };
|
||||
}
|
||||
|
||||
function connectToApp() {
|
||||
return new Promise((resolve) => {
|
||||
window.renderData = resolve;
|
||||
window.getIosAccessOriginParam = resolve;
|
||||
|
||||
if (!(window.app || window.webkit || window.FlutterPageControl)) {
|
||||
setHeadersFromApp({
|
||||
authorization: "",
|
||||
reqLang: state.lang,
|
||||
appVersion: "1.0.0",
|
||||
buildVersion: "1",
|
||||
appChannel: "Web",
|
||||
reqImei: "H5-STATIC",
|
||||
sysOrigin: "LIKEI",
|
||||
sysOriginChild: "LIKEI"
|
||||
});
|
||||
resolve({ success: true, environment: "browser" });
|
||||
return;
|
||||
}
|
||||
|
||||
let attempt = 0;
|
||||
const poll = () => {
|
||||
attempt += 1;
|
||||
if (window.app && typeof window.app.getAccessOrigin === "function") {
|
||||
try {
|
||||
const raw = window.app.getAccessOrigin();
|
||||
if (raw) setHeadersFromApp(parseAccessOrigin(raw));
|
||||
} catch (error) {
|
||||
console.error("Failed to parse access origin:", error);
|
||||
}
|
||||
resolve({ success: true, environment: "app" });
|
||||
return;
|
||||
}
|
||||
if (attempt < 50) {
|
||||
window.setTimeout(poll, 100);
|
||||
return;
|
||||
}
|
||||
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
|
||||
window.FlutterApp.postMessage("requestAccessOrigin");
|
||||
}
|
||||
resolve({ success: true, environment: "app" });
|
||||
};
|
||||
poll();
|
||||
}).then((result) => {
|
||||
if (typeof result === "string") {
|
||||
setHeadersFromApp(parseAccessOrigin(result));
|
||||
return { success: true, environment: "app" };
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
async function apiGet(path) {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
method: "GET",
|
||||
headers: commonHeaders
|
||||
});
|
||||
return parseApiResponse(response);
|
||||
}
|
||||
|
||||
async function apiPost(path, body) {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...commonHeaders,
|
||||
"content-type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(body || {})
|
||||
});
|
||||
return parseApiResponse(response);
|
||||
}
|
||||
|
||||
async function parseApiResponse(response) {
|
||||
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.response = data;
|
||||
throw error;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
if (value === null || value === undefined || value === "") return "0";
|
||||
const number = Number(value);
|
||||
if (Number.isNaN(number)) return String(value);
|
||||
return number.toFixed(2);
|
||||
}
|
||||
|
||||
function getMemberId() {
|
||||
return state.profile.id || state.profile.userId || state.profile.account || null;
|
||||
}
|
||||
|
||||
function getTeamId() {
|
||||
if (state.teamInfo && state.teamInfo.id) return state.teamInfo.id;
|
||||
const cached = localStorage.getItem("teamInfo");
|
||||
if (cached) {
|
||||
try {
|
||||
const data = JSON.parse(cached);
|
||||
if (data && data.id) return data.id;
|
||||
} catch (error) {
|
||||
console.error("Failed to parse teamInfo:", error);
|
||||
}
|
||||
}
|
||||
return state.agency.teamId || state.agency.id || null;
|
||||
}
|
||||
|
||||
async function fetchUserProfile() {
|
||||
const cached = localStorage.getItem("userProfile");
|
||||
if (cached) {
|
||||
try {
|
||||
state.profile = JSON.parse(cached) || {};
|
||||
} catch (error) {
|
||||
console.error("Failed to parse userProfile:", error);
|
||||
}
|
||||
}
|
||||
const result = await apiGet("/team/member/profile");
|
||||
if (result.status && result.body) {
|
||||
state.profile = result.body;
|
||||
localStorage.setItem("userProfile", JSON.stringify(result.body));
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAgencyProfile() {
|
||||
const result = await apiGet("/team/team-own/profile");
|
||||
if (result.status && result.body) state.agency = result.body;
|
||||
}
|
||||
|
||||
async function fetchTeamEntry() {
|
||||
const cached = localStorage.getItem("teamInfo");
|
||||
if (cached) {
|
||||
try {
|
||||
state.teamInfo = JSON.parse(cached);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse teamInfo:", error);
|
||||
}
|
||||
}
|
||||
const result = await apiGet("/team/entry");
|
||||
if (result.status && result.body) {
|
||||
state.teamInfo = result.body.teamProfile || result.body;
|
||||
localStorage.setItem("teamInfo", JSON.stringify(state.teamInfo));
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchBankBalance() {
|
||||
const result = await apiGet("/wallet/bank/balance");
|
||||
if (result.status && typeof result.body === "number") {
|
||||
state.salaryInfo.currentSalary = result.body.toFixed(2);
|
||||
} else if (result.status && result.body) {
|
||||
state.salaryInfo.currentSalary = formatMoney(result.body.currentSalary ?? result.body.availableBalance ?? result.body.balance ?? 0);
|
||||
} else {
|
||||
state.salaryInfo.currentSalary = "0";
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWorkStatistics() {
|
||||
const memberId = getMemberId();
|
||||
if (!memberId) {
|
||||
console.warn("No user ID available");
|
||||
return;
|
||||
}
|
||||
const result = await apiGet(`/team/member/work/statistics-now?dataType=DAILY&memberId=${encodeURIComponent(memberId)}`);
|
||||
if (result.status && result.body) {
|
||||
const ownTime = Number(result.body.ownTime) || 0;
|
||||
const otherTime = Number(result.body.otherTime) || 0;
|
||||
const total = ownTime + otherTime;
|
||||
state.taskInfo.anchorType = "Chat";
|
||||
state.taskInfo.minutesProgress = total >= state.taskInfo.minutesTotal ? state.taskInfo.minutesTotal : total;
|
||||
state.taskInfo.giftTask = result.body.acceptGiftValue || 0;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMemberWork() {
|
||||
const memberId = getMemberId();
|
||||
if (!memberId) return;
|
||||
const result = await apiGet(`/team/member-work?userId=${encodeURIComponent(memberId)}`);
|
||||
if (result.status && result.body) console.debug("member work data:", result.body);
|
||||
}
|
||||
|
||||
async function fetchBankCards() {
|
||||
const result = await apiGet("/wallet/bank-card/list");
|
||||
if (result.status && result.body) {
|
||||
state.bankCards = result.body;
|
||||
state.activeBankCard = state.bankCards.find((item) => item.use) || null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWithdrawInfo() {
|
||||
const result = await apiGet("/wallet/withdraw-info/list");
|
||||
if (result.status && result.body && result.body.length > 0) {
|
||||
const first = result.body[0];
|
||||
let previewUrls = [];
|
||||
try {
|
||||
previewUrls = JSON.parse(first.passportFrontUrl || "[]").map((item) => item);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse passportFrontUrl:", error);
|
||||
}
|
||||
state.withdrawInfo = { previewUrls, ...first };
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWithdrawEnabled() {
|
||||
const result = await apiGet("/wallet/bank/host-salary/withdraw/enabled");
|
||||
if (result.status) state.withdrawEnabled = Boolean(result.body);
|
||||
}
|
||||
|
||||
function calculateRoles(identity) {
|
||||
if (!identity) return ["guest"];
|
||||
const roles = [];
|
||||
if (identity.freightAgent) roles.push("freightAgent");
|
||||
if (identity.agent) roles.push("agent");
|
||||
if (identity.bdLeader) roles.push("bdLeader");
|
||||
if (identity.bd) roles.push("bd");
|
||||
if (identity.anchor) roles.push("anchor");
|
||||
if (identity.admin) roles.push("admin");
|
||||
return roles.length ? roles : ["guest"];
|
||||
}
|
||||
|
||||
function getPrimaryRole(roles) {
|
||||
const weight = {
|
||||
guest: 0,
|
||||
anchor: 1,
|
||||
agent: 2,
|
||||
bd: 3,
|
||||
bdLeader: 4,
|
||||
freightAgent: 5,
|
||||
admin: 6
|
||||
};
|
||||
return roles.reduce((current, role) => (weight[role] > weight[current] ? role : current), "guest");
|
||||
}
|
||||
|
||||
function getDefaultPage(role) {
|
||||
return {
|
||||
admin: "/admin-center",
|
||||
freightAgent: "/coin-seller",
|
||||
agent: "/agency-center",
|
||||
bdLeader: "/bd-leader-center",
|
||||
bd: "/bd-center",
|
||||
anchor: "/host-center",
|
||||
guest: "/apply"
|
||||
}[role] || "/apply";
|
||||
}
|
||||
|
||||
async function checkIdentityAndRedirect() {
|
||||
const memberId = getMemberId();
|
||||
if (!memberId) {
|
||||
navigateTo("/apply");
|
||||
return;
|
||||
}
|
||||
const result = await apiGet("/app/h5/identity");
|
||||
if (!result.status || !result.body) return;
|
||||
state.identityInfo = result.body;
|
||||
const roles = calculateRoles(result.body);
|
||||
const primaryRole = getPrimaryRole(roles);
|
||||
if (!roles.includes("anchor")) {
|
||||
const defaultPage = getDefaultPage(primaryRole);
|
||||
if (defaultPage !== "/host-center") navigateTo(defaultPage);
|
||||
}
|
||||
}
|
||||
|
||||
async function leaveAgency() {
|
||||
await apiPost("/team/user/apply", {
|
||||
reason: "QUIT",
|
||||
teamId: getTeamId()
|
||||
});
|
||||
}
|
||||
|
||||
async function runTask(task, label) {
|
||||
try {
|
||||
await task();
|
||||
} catch (error) {
|
||||
console.error(`${label} failed:`, error);
|
||||
if (label === "bank balance") showToast(t("failed_to_load_balance"));
|
||||
if (label === "withdraw info" || label === "withdraw enabled") showToast(error.response?.errorMsg || error.message || t("something_went_wrong"));
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRoute(target) {
|
||||
const url = new URL(target, window.location.origin);
|
||||
const currentLang = new URLSearchParams(window.location.search).get("lang");
|
||||
if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang);
|
||||
return `${url.pathname}${url.search}${url.hash}`;
|
||||
}
|
||||
|
||||
function navigateTo(target) {
|
||||
window.location.href = normalizeRoute(target);
|
||||
}
|
||||
|
||||
function closePage() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
const toast = $("#toast");
|
||||
toast.textContent = message;
|
||||
toast.hidden = false;
|
||||
window.clearTimeout(showToast.timer);
|
||||
showToast.timer = window.setTimeout(() => {
|
||||
toast.hidden = true;
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function showModal(id) {
|
||||
const modal = $(`#${id}`);
|
||||
if (modal) modal.hidden = false;
|
||||
}
|
||||
|
||||
function hideModal(id) {
|
||||
const modal = $(`#${id}`);
|
||||
if (modal) modal.hidden = true;
|
||||
}
|
||||
|
||||
function renderI18n() {
|
||||
document.documentElement.lang = state.lang;
|
||||
document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr";
|
||||
$("[data-action='language']").textContent = state.lang.toUpperCase();
|
||||
$$("[data-i18n]").forEach((node) => {
|
||||
node.textContent = t(node.dataset.i18n);
|
||||
});
|
||||
}
|
||||
|
||||
function renderIcons() {
|
||||
$("#policyIcon").src = icons.policy;
|
||||
$("#exchangeIcon").src = icons.exchange;
|
||||
$("#transferIcon").src = icons.transfer;
|
||||
$("#withdrawIcon").src = icons.withdraw;
|
||||
$("#bankIcon").src = icons.bank;
|
||||
$("#moneyIcon").src = icons.money;
|
||||
$("#backIcon").src = icons.back;
|
||||
["#arrowIcon1", "#arrowIcon2", "#arrowIcon3", "#arrowIcon4", "#arrowIcon5"].forEach((selector) => {
|
||||
$(selector).src = icons.arrow;
|
||||
});
|
||||
}
|
||||
|
||||
function renderAgency() {
|
||||
const avatar = $("#agencyAvatar");
|
||||
const fallback = $("#avatarFallback");
|
||||
const displayName = state.agency.userNickname || state.agency.name || "-";
|
||||
const account = state.agency.ownSpecialId?.account || state.agency.account || "-";
|
||||
|
||||
$("#agencyName").textContent = displayName;
|
||||
$("#agencyId").textContent = account;
|
||||
fallback.textContent = (displayName || "U").slice(0, 1).toUpperCase();
|
||||
avatar.alt = displayName;
|
||||
avatar.src = state.agency.userAvatar || "";
|
||||
avatar.hidden = !state.agency.userAvatar;
|
||||
}
|
||||
|
||||
function renderData() {
|
||||
$("#currentSalary").textContent = state.salaryInfo.currentSalary;
|
||||
$("#anchorType").textContent = state.taskInfo.anchorType;
|
||||
$("#minutesProgress").textContent = state.taskInfo.minutesProgress;
|
||||
$("#minutesTotal").textContent = state.taskInfo.minutesTotal;
|
||||
$("#giftTask").textContent = state.taskInfo.giftTask;
|
||||
$("#cashWithdrawRow").hidden = !state.withdrawEnabled;
|
||||
$("#cashDivider").hidden = !state.withdrawEnabled;
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderI18n();
|
||||
renderIcons();
|
||||
renderAgency();
|
||||
renderData();
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
$("[data-action='back']").addEventListener("click", closePage);
|
||||
$("[data-action='language']").addEventListener("click", () => navigateTo("/language"));
|
||||
$("[data-action='leave']").addEventListener("click", () => showModal("leaveModal"));
|
||||
|
||||
$$("[data-route]").forEach((node) => {
|
||||
node.addEventListener("click", () => navigateTo(node.dataset.route));
|
||||
});
|
||||
|
||||
$("[data-action='cash-withdraw']").addEventListener("click", () => {
|
||||
if (!state.withdrawInfo || !state.activeBankCard) {
|
||||
showModal("infoMissingModal");
|
||||
return;
|
||||
}
|
||||
navigateTo("/cash-withdraw");
|
||||
});
|
||||
|
||||
$("#agencyAvatar").addEventListener("error", (event) => {
|
||||
event.target.onerror = null;
|
||||
event.target.src = DEFAULT_AVATAR;
|
||||
event.target.hidden = false;
|
||||
});
|
||||
|
||||
$("#infoMissingModal").addEventListener("click", () => hideModal("infoMissingModal"));
|
||||
$("#leaveModal").addEventListener("click", () => hideModal("leaveModal"));
|
||||
$$(".modal-card").forEach((node) => node.addEventListener("click", (event) => event.stopPropagation()));
|
||||
$("[data-action='improve']").addEventListener("click", () => navigateTo("/host-setting"));
|
||||
$("[data-action='cancel-leave']").addEventListener("click", () => hideModal("leaveModal"));
|
||||
$("[data-action='confirm-leave']").addEventListener("click", async () => {
|
||||
try {
|
||||
await leaveAgency();
|
||||
} catch (error) {
|
||||
showToast(error.response?.errorMsg || error.errorMsg || t("something_went_wrong"));
|
||||
} finally {
|
||||
hideModal("leaveModal");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function init() {
|
||||
localStorage.setItem("identity", JSON.stringify({ identity: "HOST" }));
|
||||
localStorage.removeItem("payee");
|
||||
bindEvents();
|
||||
render();
|
||||
|
||||
try {
|
||||
await connectToApp();
|
||||
await fetchUserProfile();
|
||||
await Promise.all([
|
||||
runTask(checkIdentityAndRedirect, "identity check"),
|
||||
runTask(fetchTeamEntry, "team entry"),
|
||||
runTask(fetchAgencyProfile, "agency profile"),
|
||||
runTask(fetchBankBalance, "bank balance"),
|
||||
runTask(fetchWorkStatistics, "work statistics"),
|
||||
runTask(fetchBankCards, "bank cards"),
|
||||
runTask(fetchWithdrawInfo, "withdraw info"),
|
||||
runTask(fetchWithdrawEnabled, "withdraw enabled"),
|
||||
runTask(fetchMemberWork, "member work")
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error("Host center data request failed:", error);
|
||||
showToast(error.response?.errorMsg || error.message || t("failed_to_load_balance"));
|
||||
} finally {
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
3
h5/host-center/icons/arrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||
<path d="M12 8l8 8-8 8" stroke="#7FA48D" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 189 B |
3
h5/host-center/icons/back.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||
<path d="M20 8l-8 8 8 8" stroke="#1F6F46" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 190 B |
5
h5/host-center/icons/bank.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
|
||||
<rect x="4" y="4" width="40" height="40" rx="14" fill="#EDF8F1"/>
|
||||
<path d="M14 21h20M16 21v11M22 21v11M28 21v11M34 34H14M12 18l12-7 12 7H12Z" fill="#FFFFFF"/>
|
||||
<path d="M14 21h20M16 21v11M22 21v11M28 21v11M34 34H14M12 18l12-7 12 7H12Z" stroke="#1F6F46" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 406 B |
6
h5/host-center/icons/exchange.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
|
||||
<rect x="4" y="4" width="40" height="40" rx="14" fill="#FFF3C8"/>
|
||||
<circle cx="24" cy="24" r="11" fill="#FFFFFF" stroke="#A07100" stroke-width="2.4"/>
|
||||
<path d="M18 21h11l-3-3M30 27H19l3 3" stroke="#A07100" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 16v16" stroke="#A07100" stroke-width="1.8" stroke-linecap="round" opacity=".45"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 456 B |
5
h5/host-center/icons/money.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" fill="none">
|
||||
<circle cx="20" cy="20" r="18" fill="#DFF5E7"/>
|
||||
<circle cx="20" cy="20" r="13" fill="#FFFFFF" stroke="#1F6F46" stroke-width="2.4"/>
|
||||
<path d="M20 11.5v17M24.5 15.5h-6.2a3 3 0 0 0 0 6h3.4a3 3 0 0 1 0 6h-6.2" stroke="#1F6F46" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 377 B |
6
h5/host-center/icons/policy.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
|
||||
<rect x="4" y="4" width="40" height="40" rx="14" fill="#DFF5E7"/>
|
||||
<path d="M17 12h10l5 5v19H17V12Z" fill="#FFFFFF" stroke="#1F6F46" stroke-width="2.4" stroke-linejoin="round"/>
|
||||
<path d="M27 12v6h6" stroke="#1F6F46" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 24h8M21 29h8M21 34h5" stroke="#1F6F46" stroke-width="2.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 467 B |
6
h5/host-center/icons/transfer.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
|
||||
<rect x="4" y="4" width="40" height="40" rx="14" fill="#EAF4FF"/>
|
||||
<path d="M14 18h17l-4-4M34 30H17l4 4" stroke="#266C9F" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="17" cy="30" r="3" fill="#FFFFFF" stroke="#266C9F" stroke-width="2"/>
|
||||
<circle cx="31" cy="18" r="3" fill="#FFFFFF" stroke="#266C9F" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 439 B |
7
h5/host-center/icons/withdraw.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
|
||||
<rect x="4" y="4" width="40" height="40" rx="14" fill="#FFF0E6"/>
|
||||
<path d="M15 18h18a3 3 0 0 1 3 3v12H15a3 3 0 0 1-3-3v-9a3 3 0 0 1 3-3Z" fill="#FFFFFF" stroke="#B9652D" stroke-width="2.4" stroke-linejoin="round"/>
|
||||
<path d="M16 18v-3h14v3" stroke="#B9652D" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="29" cy="26" r="2.5" fill="#B9652D"/>
|
||||
<path d="M18 26h6" stroke="#B9652D" stroke-width="2.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 544 B |
@ -1,35 +1,111 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
>
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>Yumi H5</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.__H5_ROUTE_PATH__ = "/host-center";
|
||||
</script>
|
||||
<script src="/assets/yumi-route-entry.js"></script>
|
||||
<script type="module" crossorigin src="/js/index-CIAVq8iD-1776148658686.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/css/index-VRlNQ74k-1776148661679.css">
|
||||
<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>Host Center</title>
|
||||
<link rel="stylesheet" href="./host-center.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div class="host-center">
|
||||
<header class="page-header">
|
||||
<button class="header-icon" type="button" data-action="back" aria-label="Back"><img id="backIcon" alt="" /></button>
|
||||
<h1 data-i18n="host_center">Host Center</h1>
|
||||
<button class="lang-button" type="button" data-action="language">EN</button>
|
||||
</header>
|
||||
|
||||
<main class="page-content">
|
||||
<section class="section-title" data-i18n="my_agency">My agency</section>
|
||||
|
||||
<section class="surface-card agency-card">
|
||||
<div class="avatar">
|
||||
<img id="agencyAvatar" src="" alt="" />
|
||||
<span id="avatarFallback">U</span>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div id="agencyName" class="agency-name">-</div>
|
||||
<div class="agency-id"><span data-i18n="user_id_prefix">ID:</span> <span id="agencyId">-</span></div>
|
||||
</div>
|
||||
<button class="leave-button" type="button" data-action="leave" data-i18n="leave">Leave</button>
|
||||
</section>
|
||||
|
||||
<section class="surface-card salary-card">
|
||||
<div class="card-row">
|
||||
<div class="card-title" data-i18n="my_available_salary">My available salary</div>
|
||||
<button class="text-link" type="button" data-route="/history-salary" data-i18n="my_salary_details">My salary details</button>
|
||||
</div>
|
||||
<div class="salary-value"><img id="moneyIcon" class="money-icon" alt="" /><span id="currentSalary">0</span></div>
|
||||
</section>
|
||||
|
||||
<section class="surface-card task-card">
|
||||
<div class="task-title" data-i18n="todays_task">Today's task</div>
|
||||
<div class="task-grid">
|
||||
<div class="task-item">
|
||||
<div class="task-label" data-i18n="anchor_type">Anchor type</div>
|
||||
<div class="task-value" id="anchorType">Chat</div>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<div class="task-label" data-i18n="minutes">Minutes</div>
|
||||
<div class="task-value"><span id="minutesProgress">0</span>/<span id="minutesTotal">120</span></div>
|
||||
</div>
|
||||
<div class="task-item">
|
||||
<div class="task-label" data-i18n="gift_task">Gift Task</div>
|
||||
<div class="task-value" id="giftTask">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="surface-card menu-card">
|
||||
<button class="menu-row" type="button" data-route="/platform-policy?from=hostcenter">
|
||||
<span class="menu-main"><img class="menu-icon" id="policyIcon" alt="" /><span data-i18n="platform_policy">Platform policy</span></span>
|
||||
<img class="arrow-icon" id="arrowIcon1" alt="" />
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
<button class="menu-row" type="button" data-route="/exchange-gold-coins">
|
||||
<span class="menu-main"><img class="menu-icon" id="exchangeIcon" alt="" /><span data-i18n="exchange_coins">Exchange coins</span></span>
|
||||
<img class="arrow-icon" id="arrowIcon2" alt="" />
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
<button class="menu-row" type="button" data-route="/transfer">
|
||||
<span class="menu-main"><img class="menu-icon" id="transferIcon" alt="" /><span data-i18n="transfer">Transfer</span></span>
|
||||
<img class="arrow-icon" id="arrowIcon3" alt="" />
|
||||
</button>
|
||||
<div class="divider" id="cashDivider" hidden></div>
|
||||
<button class="menu-row" type="button" data-action="cash-withdraw" id="cashWithdrawRow" hidden>
|
||||
<span class="menu-main"><img class="menu-icon" id="withdrawIcon" alt="" /><span data-i18n="cash_withdraw">Cash withdraw</span></span>
|
||||
<img class="arrow-icon" id="arrowIcon4" alt="" />
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
<button class="menu-row" type="button" data-route="/host-setting">
|
||||
<span class="menu-main"><img class="menu-icon" id="bankIcon" alt="" /><span data-i18n="bank_accounts">Bank accounts</span></span>
|
||||
<img class="arrow-icon" id="arrowIcon5" alt="" />
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="toast" id="toast" hidden></div>
|
||||
|
||||
<div class="modal-layer" id="infoMissingModal" hidden>
|
||||
<div class="modal-card">
|
||||
<div class="modal-title" data-i18n="prompt">Prompt</div>
|
||||
<div class="modal-message" data-i18n="fill_info_tips">Please complete your information first.</div>
|
||||
<div class="modal-submessage" data-i18n="card_issue_tips">Please make sure your bank card and KYC information are available.</div>
|
||||
<button class="modal-primary" type="button" data-action="improve" data-i18n="to_improve">To improve</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-layer" id="leaveModal" hidden>
|
||||
<div class="modal-card">
|
||||
<div class="modal-title" data-i18n="tips">Tips</div>
|
||||
<div class="modal-message" data-i18n="leave_agent_tips">Are you sure you want to leave?</div>
|
||||
<div class="modal-actions">
|
||||
<button class="modal-secondary" type="button" data-action="cancel-leave" data-i18n="cancel">Cancel</button>
|
||||
<button class="modal-primary" type="button" data-action="confirm-leave" data-i18n="confirm">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="./host-center.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
13
h5/host-setting/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Host Setting</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Host Setting","mode":"bankCards"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/income-details/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Income Details</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Income Details","mode":"policy"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/information-details/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Information Details</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Information Details","mode":"policy"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
110
h5/invite-agency/index.html
Normal file
@ -0,0 +1,110 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Invite User To Become Agent</title>
|
||||
<link rel="stylesheet" href="./yumi-invite-agency.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="fullPage" id="pageRoot">
|
||||
<div class="status-bar-placeholder" id="statusBarPlaceholder"></div>
|
||||
<div class="header-container">
|
||||
<div class="side-area">
|
||||
<button class="back-btn" id="backButton" type="button">
|
||||
<img class="flip-img" id="backButtonIcon" src="" alt="" style="width: 100%; aspect-ratio: 1 / 1; display: block" />
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="header-title" id="pageTitle" style="color: black">Invite User To Become Agent</h1>
|
||||
<div class="actions-area">
|
||||
<div class="language-entry" id="languageEntry">
|
||||
<div class="language-name" id="languageName">English</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 16px; position: relative">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px">
|
||||
<div
|
||||
class="surface-border-wrapper"
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-radius: 4px;
|
||||
"
|
||||
>
|
||||
<div class="surface-border-content">
|
||||
<div style="padding: 0 8px; display: flex; align-items: center; height: max-content">
|
||||
<input
|
||||
id="searchInput"
|
||||
type="text"
|
||||
name=""
|
||||
placeholder="Enter User ID"
|
||||
autocomplete="off"
|
||||
style="
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
padding: 8px;
|
||||
"
|
||||
/>
|
||||
<button
|
||||
id="clearButton"
|
||||
type="button"
|
||||
hidden
|
||||
style="background: none; border: none; color: #9ca3af; cursor: pointer; padding: 0 4px"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button style="font-weight: 600" class="invite-btn" id="searchButton" type="button" disabled>Search</button>
|
||||
</div>
|
||||
|
||||
<div id="resultList" style="margin: 16px 0; display: flex; flex-direction: column; gap: 12px"></div>
|
||||
<div id="noData" class="no-data">please input user id</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="userCardTemplate">
|
||||
<div
|
||||
class="surface-border-wrapper user-card"
|
||||
style="
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border-radius: 20px;
|
||||
"
|
||||
>
|
||||
<div class="surface-border-content">
|
||||
<div style="padding: 10px; transition: all 0.2s">
|
||||
<div class="status-badge" style="position: absolute; z-index: 3; top: -2px; right: -2px"></div>
|
||||
<div style="font-weight: 600">Information:</div>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 4px">
|
||||
<div style="flex: 1; min-width: 0; align-self: stretch; display: flex; align-items: center; gap: 4px">
|
||||
<div style="width: 3em; display: flex; align-items: center; justify-content: center">
|
||||
<img class="avatar-image" src="" alt="" style="width: 100%; aspect-ratio: 1/1; object-fit: cover; border-radius: 50%" />
|
||||
</div>
|
||||
<div style="flex: 1; min-width: 0; align-self: stretch">
|
||||
<div class="nickname" style="margin-bottom: 4px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap"></div>
|
||||
<div class="account" style="font-size: 0.9em; color: rgba(0, 0, 0, 0.4); font-weight: 500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: auto; min-width: 0; align-self: stretch; display: flex; justify-content: center; align-items: center">
|
||||
<div class="pending-actions"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./yumi-invite-agency.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
177
h5/invite-agency/yumi-invite-agency.css
Normal file
@ -0,0 +1,177 @@
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif;
|
||||
background: #edf8f1;
|
||||
color: #1f3528;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
button, input { font: inherit; }
|
||||
[hidden] { display: none !important; }
|
||||
|
||||
.fullPage {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background: #edf8f1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fullPage input::placeholder {
|
||||
font-weight: 700;
|
||||
color: #7b8d81;
|
||||
}
|
||||
|
||||
.invite-btn, .result-action-btn {
|
||||
padding: 7px 14px;
|
||||
border-radius: 32px;
|
||||
background: #dff5e7;
|
||||
color: #1f6f46;
|
||||
border: 1px solid #b7e0c6;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.invite-btn:disabled, .result-action-btn:disabled {
|
||||
background: #e1e7e3;
|
||||
border-color: #ccd6d0;
|
||||
color: #7b8d81;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #6d7e72;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.user-card { cursor: default; }
|
||||
|
||||
.status-bar-placeholder { display: none; height: 30px; }
|
||||
.status-bar-placeholder.is-visible { display: block; }
|
||||
|
||||
.header-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
height: 60px;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.side-area, .actions-area {
|
||||
min-width: 1.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions-area {
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
color: #153d28 !important;
|
||||
font-size: 1.2em;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.back-btn, .help-btn {
|
||||
width: 1.5em;
|
||||
border: none;
|
||||
background: transparent;
|
||||
transition: transform 0.2s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.back-btn:active, .help-btn:active { transform: scale(0.9); }
|
||||
|
||||
.language-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
background: #dff5e7;
|
||||
padding: 5px 10px;
|
||||
color: #24724a !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.language-name { font-weight: 800; }
|
||||
|
||||
.surface-border-wrapper, .gradient-border-wrapper {
|
||||
position: relative;
|
||||
border: 1px solid #b7e0c6;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
}
|
||||
|
||||
.surface-border-content, .gradient-border-content {
|
||||
position: relative;
|
||||
border-radius: inherit;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04);
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
padding: 24px 20px 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1f3528;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: #dff5e7;
|
||||
color: #1f6f46;
|
||||
padding: 10px 16px;
|
||||
font-weight: 800;
|
||||
}
|
||||
694
h5/invite-agency/yumi-invite-agency.js
Normal file
@ -0,0 +1,694 @@
|
||||
(function () {
|
||||
const API_BASE = "https://jvapi.haiyihy.com";
|
||||
const BACK_ICON =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg==";
|
||||
const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png";
|
||||
|
||||
const TEXT = {
|
||||
pageTitle: "Invite User To Become Agent",
|
||||
language: "English",
|
||||
placeholder: "Enter User ID",
|
||||
search: "Search",
|
||||
invite: "Invite",
|
||||
idlePrompt: "please input user id",
|
||||
noUsersFound: "No users found",
|
||||
information: "Information:",
|
||||
userIdPrefix: "User ID:",
|
||||
pending: "Pending",
|
||||
success: "Success",
|
||||
cancel: "Cancel",
|
||||
invitationSubmitted: "Invitation submitted",
|
||||
applicationCancelled: "Application cancelled",
|
||||
somethingWentWrong: "Something went wrong",
|
||||
cancellationFailed: "Cancellation failed, try again",
|
||||
informationTitle: "Information",
|
||||
successTitle: "Success",
|
||||
errorTitle: "Error",
|
||||
ok: "OK",
|
||||
};
|
||||
|
||||
const state = {
|
||||
query: "",
|
||||
invitedList: [],
|
||||
searchResults: [],
|
||||
showSearchResults: false,
|
||||
searching: false,
|
||||
hasSearched: false,
|
||||
};
|
||||
|
||||
const dom = {
|
||||
statusBarPlaceholder: document.getElementById("statusBarPlaceholder"),
|
||||
backButton: document.getElementById("backButton"),
|
||||
backButtonIcon: document.getElementById("backButtonIcon"),
|
||||
pageTitle: document.getElementById("pageTitle"),
|
||||
languageEntry: document.getElementById("languageEntry"),
|
||||
languageName: document.getElementById("languageName"),
|
||||
searchInput: document.getElementById("searchInput"),
|
||||
clearButton: document.getElementById("clearButton"),
|
||||
searchButton: document.getElementById("searchButton"),
|
||||
resultList: document.getElementById("resultList"),
|
||||
noData: document.getElementById("noData"),
|
||||
userCardTemplate: document.getElementById("userCardTemplate"),
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
document.title = TEXT.pageTitle;
|
||||
document.documentElement.lang = "en";
|
||||
document.documentElement.dir = resolveDocumentDirection();
|
||||
|
||||
dom.backButtonIcon.src = BACK_ICON;
|
||||
dom.pageTitle.textContent = TEXT.pageTitle;
|
||||
dom.languageName.textContent = TEXT.language;
|
||||
dom.searchInput.placeholder = TEXT.placeholder;
|
||||
dom.searchButton.textContent = TEXT.search;
|
||||
dom.noData.textContent = TEXT.idlePrompt;
|
||||
|
||||
syncStatusBarPlaceholder();
|
||||
bindEvents();
|
||||
render();
|
||||
loadInvitedList();
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
dom.backButton.addEventListener("click", handleBack);
|
||||
dom.languageEntry.addEventListener("click", handleLanguageClick);
|
||||
dom.searchInput.addEventListener("input", handleSearchInput);
|
||||
dom.clearButton.addEventListener("click", clearSearch);
|
||||
dom.searchButton.addEventListener("click", triggerSearchNow);
|
||||
}
|
||||
|
||||
function resolveDocumentDirection() {
|
||||
const lang = (new URLSearchParams(window.location.search).get("lang") || "").toLowerCase();
|
||||
return lang.startsWith("ar") ? "rtl" : "ltr";
|
||||
}
|
||||
|
||||
function syncStatusBarPlaceholder() {
|
||||
const hasAppBridge =
|
||||
Boolean(window.app) ||
|
||||
Boolean(window.FlutterPageControl) ||
|
||||
Boolean(window.webkit && window.webkit.messageHandlers);
|
||||
const isIOS = /iphone|ipad|ipod/i.test(window.navigator.userAgent || "");
|
||||
dom.statusBarPlaceholder.classList.toggle("is-visible", hasAppBridge && isIOS);
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
function handleLanguageClick() {
|
||||
window.location.href = "/language";
|
||||
}
|
||||
|
||||
function handleSearchInput(event) {
|
||||
state.query = event.target.value;
|
||||
dom.clearButton.hidden = !state.query;
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
render();
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
state.query = "";
|
||||
state.searchResults = [];
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
dom.searchInput.value = "";
|
||||
dom.clearButton.hidden = true;
|
||||
render();
|
||||
}
|
||||
|
||||
async function triggerSearchNow() {
|
||||
if (state.searching) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = state.query.trim();
|
||||
|
||||
if (!query) {
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
state.searching = true;
|
||||
state.showSearchResults = true;
|
||||
state.hasSearched = true;
|
||||
state.searchResults = [];
|
||||
render();
|
||||
|
||||
try {
|
||||
const payload = await searchUser(query);
|
||||
state.searchResults = payload ? [mergeWithInvitationStatus(payload.userProfile)] : [];
|
||||
} catch (error) {
|
||||
state.searchResults = [];
|
||||
showToast(extractErrorMessage(error), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
state.searching = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadInvitedList() {
|
||||
try {
|
||||
const response = await request("GET", "/team/bd/invite-message-own");
|
||||
state.invitedList = response && response.status && Array.isArray(response.body) ? response.body : [];
|
||||
state.searchResults = state.searchResults.map(function (item) {
|
||||
return mergeWithInvitationStatus(item.userProfile || {});
|
||||
});
|
||||
render();
|
||||
} catch (error) {
|
||||
state.invitedList = [];
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function searchUser(query) {
|
||||
try {
|
||||
const response = await request(
|
||||
"GET",
|
||||
"/user/user-profile/open-search?account=" + encodeURIComponent(query)
|
||||
);
|
||||
|
||||
if (response && response.status && response.body) {
|
||||
return { userProfile: response.body };
|
||||
}
|
||||
} catch (error) {
|
||||
if (!/^\d+$/.test(query)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(query)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fallbackResponse = await request(
|
||||
"GET",
|
||||
"/user/user-profile?userId=" + encodeURIComponent(query)
|
||||
);
|
||||
|
||||
if (fallbackResponse && fallbackResponse.status && fallbackResponse.body) {
|
||||
return { userProfile: fallbackResponse.body };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function mergeWithInvitationStatus(userProfile) {
|
||||
const matchedInvitation = state.invitedList.find(function (item) {
|
||||
return (
|
||||
item &&
|
||||
item.userProfile &&
|
||||
userProfile &&
|
||||
item.userProfile.id === userProfile.id
|
||||
);
|
||||
});
|
||||
|
||||
return Object.assign({}, matchedInvitation || {}, {
|
||||
userProfile: userProfile || {},
|
||||
});
|
||||
}
|
||||
|
||||
async function inviteUser(item) {
|
||||
if (!item || !item.userProfile || !item.userProfile.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request("POST", "/team/bd/invite-agent", {
|
||||
body: {
|
||||
inviteUserId: item.userProfile.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (response && response.status) {
|
||||
showToast(TEXT.invitationSubmitted, {
|
||||
title: TEXT.successTitle,
|
||||
type: "success",
|
||||
});
|
||||
await loadInvitedList();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(extractErrorMessage(error, TEXT.somethingWentWrong), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelInvitation(invitationId) {
|
||||
try {
|
||||
const response = await request("POST", "/team/bd/invite-agent-cancel/" + invitationId);
|
||||
|
||||
if (response && response.status) {
|
||||
showToast(TEXT.applicationCancelled, {
|
||||
title: TEXT.informationTitle,
|
||||
type: "info",
|
||||
});
|
||||
await loadInvitedList();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(extractErrorMessage(error, TEXT.cancellationFailed), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderSearchButton();
|
||||
renderList();
|
||||
}
|
||||
|
||||
function renderSearchButton() {
|
||||
dom.searchButton.disabled = !state.query.trim() || state.searching;
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const currentList = state.showSearchResults ? state.searchResults : state.invitedList;
|
||||
dom.resultList.innerHTML = "";
|
||||
|
||||
currentList.forEach(function (item) {
|
||||
dom.resultList.appendChild(createUserCard(item, state.showSearchResults ? "search" : "invited"));
|
||||
});
|
||||
|
||||
if (currentList.length > 0 || (state.searching && state.showSearchResults)) {
|
||||
dom.noData.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
dom.noData.hidden = false;
|
||||
dom.noData.textContent =
|
||||
state.showSearchResults && state.hasSearched ? TEXT.noUsersFound : TEXT.idlePrompt;
|
||||
}
|
||||
|
||||
function createUserCard(item, mode) {
|
||||
const fragment = dom.userCardTemplate.content.cloneNode(true);
|
||||
const statusBadge = fragment.querySelector(".status-badge");
|
||||
const avatarImage = fragment.querySelector(".avatar-image");
|
||||
const nickname = fragment.querySelector(".nickname");
|
||||
const account = fragment.querySelector(".account");
|
||||
const pendingActions = fragment.querySelector(".pending-actions");
|
||||
const userProfile = item.userProfile || {};
|
||||
|
||||
avatarImage.src = userProfile.userAvatar || DEFAULT_AVATAR;
|
||||
avatarImage.alt = userProfile.userNickname || "";
|
||||
avatarImage.onerror = handleAvatarError;
|
||||
nickname.textContent = userProfile.userNickname || "";
|
||||
account.textContent = TEXT.userIdPrefix + " " + (userProfile.account || userProfile.id || "");
|
||||
|
||||
if (item.status === 0) {
|
||||
statusBadge.innerHTML =
|
||||
'<div style="background: #eef7fb; border: 1px solid #c0e1f1; padding: 4px 8px; border-radius: 0 12px; color: #28627c; font-weight: 700">' +
|
||||
TEXT.pending +
|
||||
"</div>";
|
||||
} else if (item.status === 1) {
|
||||
statusBadge.innerHTML =
|
||||
'<div style="background: #dff5e7; border: 1px solid #b7e0c6; padding: 4px 8px; border-radius: 0 12px; color: #1f6f46; font-weight: 700">' +
|
||||
TEXT.success +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
if (mode === "search") {
|
||||
const inviteButton = document.createElement("button");
|
||||
inviteButton.type = "button";
|
||||
inviteButton.className = "result-action-btn";
|
||||
inviteButton.textContent = TEXT.invite;
|
||||
inviteButton.disabled = item.status === 0 || item.status === 1;
|
||||
inviteButton.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
inviteUser(item);
|
||||
});
|
||||
pendingActions.appendChild(inviteButton);
|
||||
} else if (item.status === 0) {
|
||||
const cancelButton = document.createElement("div");
|
||||
cancelButton.textContent = TEXT.cancel;
|
||||
cancelButton.style.padding = "4px 8px";
|
||||
cancelButton.style.borderRadius = "32px";
|
||||
cancelButton.style.backgroundColor = "red";
|
||||
cancelButton.style.color = "white";
|
||||
cancelButton.style.fontWeight = "600";
|
||||
cancelButton.style.zIndex = "5";
|
||||
cancelButton.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
cancelInvitation(item.id);
|
||||
});
|
||||
pendingActions.appendChild(cancelButton);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function handleAvatarError(event) {
|
||||
event.target.onerror = null;
|
||||
event.target.src = DEFAULT_AVATAR;
|
||||
}
|
||||
|
||||
function extractErrorMessage(error, fallback) {
|
||||
return (
|
||||
(error &&
|
||||
error.response &&
|
||||
(error.response.errorMsg || error.response.message || error.response.error)) ||
|
||||
(error && error.message) ||
|
||||
fallback ||
|
||||
TEXT.somethingWentWrong
|
||||
);
|
||||
}
|
||||
|
||||
function showToast(message, options) {
|
||||
const config = Object.assign(
|
||||
{
|
||||
title: TEXT.informationTitle,
|
||||
type: "info",
|
||||
confirmText: TEXT.ok,
|
||||
},
|
||||
options || {}
|
||||
);
|
||||
|
||||
const iconMap = {
|
||||
error: "✕",
|
||||
warning: "⚠",
|
||||
success: "✓",
|
||||
info: "i",
|
||||
simple: "",
|
||||
};
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "modal-overlay";
|
||||
overlay.innerHTML =
|
||||
'<div class="modal modal-enter modal-' +
|
||||
config.type +
|
||||
'">' +
|
||||
(config.type === "simple"
|
||||
? ""
|
||||
: '<div class="modal-icon"><div class="icon-circle icon-circle-' +
|
||||
config.type +
|
||||
'"><span class="icon-symbol icon-symbol-' +
|
||||
config.type +
|
||||
'">' +
|
||||
iconMap[config.type] +
|
||||
"</span></div></div>") +
|
||||
(config.type === "simple" || !config.title
|
||||
? ""
|
||||
: '<div class="modal-title">' + escapeHtml(config.title) + "</div>") +
|
||||
'<div class="modal-message">' +
|
||||
escapeHtml(message) +
|
||||
"</div>" +
|
||||
'<div class="modal-actions"><button class="modal-btn modal-btn-' +
|
||||
config.type +
|
||||
'">' +
|
||||
escapeHtml(config.confirmText) +
|
||||
"</button></div>" +
|
||||
"</div>";
|
||||
|
||||
const modal = overlay.querySelector(".modal");
|
||||
const closeButton = overlay.querySelector(".modal-btn");
|
||||
const close = function () {
|
||||
modal.classList.remove("modal-enter");
|
||||
modal.classList.add("modal-leave");
|
||||
window.setTimeout(function () {
|
||||
overlay.remove();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
closeButton.addEventListener("click", close);
|
||||
overlay.addEventListener("click", function (event) {
|
||||
if (event.target === overlay) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
async function request(method, path, options) {
|
||||
const config = options || {};
|
||||
const headers = buildRequestHeaders(Boolean(config.body), config.headers || {});
|
||||
const response = await fetch(API_BASE + path, {
|
||||
method: method,
|
||||
credentials: "include",
|
||||
headers: headers,
|
||||
body: config.body ? JSON.stringify(config.body) : undefined,
|
||||
});
|
||||
const text = await response.text();
|
||||
let payload = {};
|
||||
|
||||
if (text) {
|
||||
try {
|
||||
payload = JSON.parse(text);
|
||||
} catch (error) {
|
||||
payload = {
|
||||
status: response.ok,
|
||||
body: text,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok || payload.status === false) {
|
||||
const requestError = new Error(
|
||||
payload.errorMsg || payload.message || response.statusText || TEXT.somethingWentWrong
|
||||
);
|
||||
requestError.response = payload;
|
||||
throw requestError;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function buildRequestHeaders(hasBody, overrideHeaders) {
|
||||
const discovered = discoverAppHeaders();
|
||||
const headers = Object.assign({}, overrideHeaders || {});
|
||||
|
||||
if (hasBody) {
|
||||
headers["Content-Type"] = headers["Content-Type"] || "application/json";
|
||||
}
|
||||
|
||||
headers.Accept = headers.Accept || "application/json, text/plain, */*";
|
||||
|
||||
if (discovered.authorization) {
|
||||
headers.Authorization = headers.Authorization || discovered.authorization;
|
||||
}
|
||||
|
||||
if (discovered.reqLang) {
|
||||
headers["Req-Lang"] = headers["Req-Lang"] || discovered.reqLang;
|
||||
}
|
||||
|
||||
if (discovered.appVersion) {
|
||||
headers["App-Version"] = headers["App-Version"] || discovered.appVersion;
|
||||
}
|
||||
|
||||
if (discovered.buildVersion) {
|
||||
headers["Build-Version"] = headers["Build-Version"] || discovered.buildVersion;
|
||||
}
|
||||
|
||||
if (discovered.appChannel) {
|
||||
headers["App-Channel"] = headers["App-Channel"] || discovered.appChannel;
|
||||
}
|
||||
|
||||
if (discovered.reqImei) {
|
||||
headers["Req-Imei"] = headers["Req-Imei"] || discovered.reqImei;
|
||||
}
|
||||
|
||||
if (discovered.sysOrigin) {
|
||||
headers["Sys-Origin"] = headers["Sys-Origin"] || discovered.sysOrigin;
|
||||
}
|
||||
|
||||
if (discovered.sysOriginChild) {
|
||||
headers["Sys-Origin-Child"] =
|
||||
headers["Sys-Origin-Child"] || discovered.sysOriginChild;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
function discoverAppHeaders() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const collected = [];
|
||||
|
||||
collectCandidate(collected, window.__APP_HEADERS__);
|
||||
collectCandidate(collected, window.__HEADER_INFO__);
|
||||
collectCandidate(collected, window.headerInfo);
|
||||
collectCandidate(collected, parseHeaderObjectFromQuery(query));
|
||||
|
||||
collectFromStorage(collected, window.localStorage);
|
||||
collectFromStorage(collected, window.sessionStorage);
|
||||
|
||||
return collected.reduce(function (result, candidate) {
|
||||
return Object.assign(result, candidate);
|
||||
}, {
|
||||
reqLang: query.get("lang") || document.documentElement.lang || "en",
|
||||
});
|
||||
}
|
||||
|
||||
function parseHeaderObjectFromQuery(query) {
|
||||
const candidate = {};
|
||||
mapHeaderValue(candidate, "authorization", query.get("authorization"));
|
||||
mapHeaderValue(candidate, "authorization", query.get("token"));
|
||||
mapHeaderValue(candidate, "reqLang", query.get("lang"));
|
||||
mapHeaderValue(candidate, "appVersion", query.get("appVersion"));
|
||||
mapHeaderValue(candidate, "buildVersion", query.get("buildVersion"));
|
||||
mapHeaderValue(candidate, "appChannel", query.get("appChannel"));
|
||||
mapHeaderValue(candidate, "reqImei", query.get("reqImei"));
|
||||
mapHeaderValue(candidate, "sysOrigin", query.get("sysOrigin"));
|
||||
mapHeaderValue(candidate, "sysOriginChild", query.get("sysOriginChild"));
|
||||
return candidate;
|
||||
}
|
||||
|
||||
function collectFromStorage(collected, storage) {
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const preferredKeys = [
|
||||
"authorization",
|
||||
"Authorization",
|
||||
"token",
|
||||
"accessToken",
|
||||
"headerInfo",
|
||||
"headersFromApp",
|
||||
"appHeaders",
|
||||
"persist:root",
|
||||
"user",
|
||||
"userInfo",
|
||||
];
|
||||
|
||||
preferredKeys.forEach(function (key) {
|
||||
try {
|
||||
collectCandidate(collected, storage.getItem(key));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
for (let index = 0; index < storage.length; index += 1) {
|
||||
try {
|
||||
const key = storage.key(index);
|
||||
collectCandidate(collected, storage.getItem(key));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectCandidate(collected, value) {
|
||||
const normalized = normalizeHeaderCandidate(value, 0);
|
||||
if (normalized && Object.keys(normalized).length > 0) {
|
||||
collected.push(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHeaderCandidate(value, depth) {
|
||||
if (depth > 4 || value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trimmed[0] === "{" || trimmed[0] === "[") {
|
||||
try {
|
||||
return normalizeHeaderCandidate(JSON.parse(trimmed), depth + 1);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (/bearer\s+/i.test(trimmed)) {
|
||||
return {
|
||||
authorization: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.reduce(function (result, entry) {
|
||||
const normalized = normalizeHeaderCandidate(entry, depth + 1);
|
||||
return normalized ? Object.assign(result, normalized) : result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (typeof value !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = {};
|
||||
const aliasMap = {
|
||||
authorization: ["authorization", "Authorization", "token", "accessToken"],
|
||||
reqLang: ["reqLang", "Req-Lang", "lang", "language"],
|
||||
appVersion: ["appVersion", "App-Version"],
|
||||
buildVersion: ["buildVersion", "Build-Version"],
|
||||
appChannel: ["appChannel", "App-Channel"],
|
||||
reqImei: ["reqImei", "Req-Imei"],
|
||||
sysOrigin: ["sysOrigin", "Sys-Origin"],
|
||||
sysOriginChild: ["sysOriginChild", "Sys-Origin-Child"],
|
||||
};
|
||||
|
||||
Object.keys(aliasMap).forEach(function (targetKey) {
|
||||
aliasMap[targetKey].forEach(function (sourceKey) {
|
||||
mapHeaderValue(normalized, targetKey, value[sourceKey]);
|
||||
});
|
||||
});
|
||||
|
||||
["headerInfo", "headers", "auth", "data", "state"].forEach(function (nestedKey) {
|
||||
const nestedValue = normalizeHeaderCandidate(value[nestedKey], depth + 1);
|
||||
if (nestedValue) {
|
||||
Object.assign(normalized, nestedValue);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(value).forEach(function (key) {
|
||||
if (
|
||||
key === "headerInfo" ||
|
||||
key === "headers" ||
|
||||
key === "auth" ||
|
||||
key === "data" ||
|
||||
key === "state"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value[key] === "object") {
|
||||
const nestedValue = normalizeHeaderCandidate(value[key], depth + 1);
|
||||
if (nestedValue) {
|
||||
Object.assign(normalized, nestedValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function mapHeaderValue(target, key, value) {
|
||||
if (value == null || value === "" || target[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
target[key] = String(value);
|
||||
}
|
||||
})();
|
||||
109
h5/invite-bd-leader/index.html
Normal file
@ -0,0 +1,109 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Invite User To Become BD Leader</title>
|
||||
<link rel="stylesheet" href="./yumi-invite-bd-leader.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="fullPage" id="pageRoot">
|
||||
<div class="status-bar-placeholder" id="statusBarPlaceholder"></div>
|
||||
<div class="header-container">
|
||||
<div class="side-area">
|
||||
<button class="back-btn" id="backButton" type="button">
|
||||
<img class="flip-img" id="backButtonIcon" src="" alt="" style="width: 100%; aspect-ratio: 1 / 1; display: block" />
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="header-title" id="pageTitle" style="color: black">Invite User To Become BD Leader</h1>
|
||||
<div class="actions-area">
|
||||
<div class="language-entry" id="languageEntry">
|
||||
<div class="language-name" id="languageName">English</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 16px; position: relative">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px">
|
||||
<div
|
||||
class="surface-border-wrapper"
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-radius: 4px;
|
||||
"
|
||||
>
|
||||
<div class="surface-border-content">
|
||||
<div style="padding: 0 8px; display: flex; align-items: center; height: max-content">
|
||||
<input
|
||||
id="searchInput"
|
||||
type="text"
|
||||
placeholder="Enter User ID"
|
||||
autocomplete="off"
|
||||
style="
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
padding: 8px;
|
||||
"
|
||||
/>
|
||||
<button
|
||||
id="clearButton"
|
||||
type="button"
|
||||
hidden
|
||||
style="background: none; border: none; color: #9ca3af; cursor: pointer; padding: 0 4px"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button style="font-weight: 600" class="invite-btn" id="searchButton" type="button" disabled>Search</button>
|
||||
</div>
|
||||
|
||||
<div id="resultList" style="margin: 16px 0; display: flex; flex-direction: column; gap: 12px"></div>
|
||||
<div id="noData" class="no-data">please input user id</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="userCardTemplate">
|
||||
<div
|
||||
class="surface-border-wrapper user-card"
|
||||
style="
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border-radius: 20px;
|
||||
"
|
||||
>
|
||||
<div class="surface-border-content">
|
||||
<div style="padding: 10px; transition: all 0.2s">
|
||||
<div class="status-badge" style="position: absolute; z-index: 3; top: -2px; right: -2px"></div>
|
||||
<div style="font-weight: 600">Information:</div>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 4px">
|
||||
<div style="flex: 1; min-width: 0; align-self: stretch; display: flex; align-items: center; gap: 4px">
|
||||
<div style="width: 3em; display: flex; align-items: center; justify-content: center">
|
||||
<img class="avatar-image" src="" alt="" style="width: 100%; aspect-ratio: 1/1; object-fit: cover; border-radius: 50%" />
|
||||
</div>
|
||||
<div style="flex: 1; min-width: 0; align-self: stretch">
|
||||
<div class="nickname" style="margin-bottom: 4px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap"></div>
|
||||
<div class="account" style="font-size: 0.9em; color: rgba(0, 0, 0, 0.4); font-weight: 500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: auto; min-width: 0; align-self: stretch; display: flex; justify-content: center; align-items: center">
|
||||
<div class="pending-actions"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./yumi-invite-bd-leader.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
177
h5/invite-bd-leader/yumi-invite-bd-leader.css
Normal file
@ -0,0 +1,177 @@
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif;
|
||||
background: #edf8f1;
|
||||
color: #1f3528;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
button, input { font: inherit; }
|
||||
[hidden] { display: none !important; }
|
||||
|
||||
.fullPage {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background: #edf8f1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fullPage input::placeholder {
|
||||
font-weight: 700;
|
||||
color: #7b8d81;
|
||||
}
|
||||
|
||||
.invite-btn, .result-action-btn {
|
||||
padding: 7px 14px;
|
||||
border-radius: 32px;
|
||||
background: #dff5e7;
|
||||
color: #1f6f46;
|
||||
border: 1px solid #b7e0c6;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.invite-btn:disabled, .result-action-btn:disabled {
|
||||
background: #e1e7e3;
|
||||
border-color: #ccd6d0;
|
||||
color: #7b8d81;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #6d7e72;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.user-card { cursor: default; }
|
||||
|
||||
.status-bar-placeholder { display: none; height: 30px; }
|
||||
.status-bar-placeholder.is-visible { display: block; }
|
||||
|
||||
.header-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
height: 60px;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.side-area, .actions-area {
|
||||
min-width: 1.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions-area {
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
color: #153d28 !important;
|
||||
font-size: 1.2em;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.back-btn, .help-btn {
|
||||
width: 1.5em;
|
||||
border: none;
|
||||
background: transparent;
|
||||
transition: transform 0.2s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.back-btn:active, .help-btn:active { transform: scale(0.9); }
|
||||
|
||||
.language-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
background: #dff5e7;
|
||||
padding: 5px 10px;
|
||||
color: #24724a !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.language-name { font-weight: 800; }
|
||||
|
||||
.surface-border-wrapper, .gradient-border-wrapper {
|
||||
position: relative;
|
||||
border: 1px solid #b7e0c6;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
}
|
||||
|
||||
.surface-border-content, .gradient-border-content {
|
||||
position: relative;
|
||||
border-radius: inherit;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04);
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
padding: 24px 20px 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1f3528;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: #dff5e7;
|
||||
color: #1f6f46;
|
||||
padding: 10px 16px;
|
||||
font-weight: 800;
|
||||
}
|
||||
699
h5/invite-bd-leader/yumi-invite-bd-leader.js
Normal file
@ -0,0 +1,699 @@
|
||||
(function () {
|
||||
const API_BASE = "https://jvapi.haiyihy.com";
|
||||
const BACK_ICON =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg==";
|
||||
const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png";
|
||||
const ENDPOINTS = {
|
||||
invitedList: "/team/bd/invite-bdleader-message-own",
|
||||
invite: "/team/bd/invite-bdleader",
|
||||
cancelPrefix: "/team/bd/invite-bdleader-cancel/",
|
||||
};
|
||||
|
||||
const TEXT = {
|
||||
pageTitle: "Invite User To Become BD Leader",
|
||||
language: "English",
|
||||
placeholder: "Enter User ID",
|
||||
search: "Search",
|
||||
invite: "Invite",
|
||||
idlePrompt: "please input user id",
|
||||
noUsersFound: "No users found",
|
||||
information: "Information:",
|
||||
userIdPrefix: "User ID:",
|
||||
pending: "Pending",
|
||||
success: "Success",
|
||||
cancel: "Cancel",
|
||||
invitationSubmitted: "Invitation submitted",
|
||||
applicationCancelled: "Application cancelled",
|
||||
somethingWentWrong: "Something went wrong",
|
||||
cancellationFailed: "Cancellation failed, try again",
|
||||
informationTitle: "Information",
|
||||
successTitle: "Success",
|
||||
errorTitle: "Error",
|
||||
ok: "OK",
|
||||
};
|
||||
|
||||
const state = {
|
||||
query: "",
|
||||
invitedList: [],
|
||||
searchResults: [],
|
||||
showSearchResults: false,
|
||||
searching: false,
|
||||
hasSearched: false,
|
||||
};
|
||||
|
||||
const dom = {
|
||||
statusBarPlaceholder: document.getElementById("statusBarPlaceholder"),
|
||||
backButton: document.getElementById("backButton"),
|
||||
backButtonIcon: document.getElementById("backButtonIcon"),
|
||||
pageTitle: document.getElementById("pageTitle"),
|
||||
languageEntry: document.getElementById("languageEntry"),
|
||||
languageName: document.getElementById("languageName"),
|
||||
searchInput: document.getElementById("searchInput"),
|
||||
clearButton: document.getElementById("clearButton"),
|
||||
searchButton: document.getElementById("searchButton"),
|
||||
resultList: document.getElementById("resultList"),
|
||||
noData: document.getElementById("noData"),
|
||||
userCardTemplate: document.getElementById("userCardTemplate"),
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
document.title = TEXT.pageTitle;
|
||||
document.documentElement.lang = "en";
|
||||
document.documentElement.dir = resolveDocumentDirection();
|
||||
|
||||
dom.backButtonIcon.src = BACK_ICON;
|
||||
dom.pageTitle.textContent = TEXT.pageTitle;
|
||||
dom.languageName.textContent = TEXT.language;
|
||||
dom.searchInput.placeholder = TEXT.placeholder;
|
||||
dom.searchButton.textContent = TEXT.search;
|
||||
dom.noData.textContent = TEXT.idlePrompt;
|
||||
|
||||
syncStatusBarPlaceholder();
|
||||
bindEvents();
|
||||
render();
|
||||
loadInvitedList();
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
dom.backButton.addEventListener("click", handleBack);
|
||||
dom.languageEntry.addEventListener("click", handleLanguageClick);
|
||||
dom.searchInput.addEventListener("input", handleSearchInput);
|
||||
dom.clearButton.addEventListener("click", clearSearch);
|
||||
dom.searchButton.addEventListener("click", triggerSearchNow);
|
||||
}
|
||||
|
||||
function resolveDocumentDirection() {
|
||||
const lang = (new URLSearchParams(window.location.search).get("lang") || "").toLowerCase();
|
||||
return lang.startsWith("ar") ? "rtl" : "ltr";
|
||||
}
|
||||
|
||||
function syncStatusBarPlaceholder() {
|
||||
const hasAppBridge =
|
||||
Boolean(window.app) ||
|
||||
Boolean(window.FlutterPageControl) ||
|
||||
Boolean(window.webkit && window.webkit.messageHandlers);
|
||||
const isIOS = /iphone|ipad|ipod/i.test(window.navigator.userAgent || "");
|
||||
dom.statusBarPlaceholder.classList.toggle("is-visible", hasAppBridge && isIOS);
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
function handleLanguageClick() {
|
||||
window.location.href = "/language";
|
||||
}
|
||||
|
||||
function handleSearchInput(event) {
|
||||
state.query = event.target.value;
|
||||
dom.clearButton.hidden = !state.query;
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
render();
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
state.query = "";
|
||||
state.searchResults = [];
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
dom.searchInput.value = "";
|
||||
dom.clearButton.hidden = true;
|
||||
render();
|
||||
}
|
||||
|
||||
async function triggerSearchNow() {
|
||||
if (state.searching) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = state.query.trim();
|
||||
|
||||
if (!query) {
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
state.searching = true;
|
||||
state.showSearchResults = true;
|
||||
state.hasSearched = true;
|
||||
state.searchResults = [];
|
||||
render();
|
||||
|
||||
try {
|
||||
const payload = await searchUser(query);
|
||||
state.searchResults = payload ? [mergeWithInvitationStatus(payload.userProfile)] : [];
|
||||
} catch (error) {
|
||||
state.searchResults = [];
|
||||
showToast(extractErrorMessage(error), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
state.searching = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadInvitedList() {
|
||||
try {
|
||||
const response = await request("GET", ENDPOINTS.invitedList);
|
||||
state.invitedList = response && response.status && Array.isArray(response.body) ? response.body : [];
|
||||
state.searchResults = state.searchResults.map(function (item) {
|
||||
return mergeWithInvitationStatus(item.userProfile || {});
|
||||
});
|
||||
render();
|
||||
} catch (error) {
|
||||
state.invitedList = [];
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function searchUser(query) {
|
||||
try {
|
||||
const response = await request(
|
||||
"GET",
|
||||
"/user/user-profile/open-search?account=" + encodeURIComponent(query)
|
||||
);
|
||||
|
||||
if (response && response.status && response.body) {
|
||||
return { userProfile: response.body };
|
||||
}
|
||||
} catch (error) {
|
||||
if (!/^\d+$/.test(query)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(query)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fallbackResponse = await request(
|
||||
"GET",
|
||||
"/user/user-profile?userId=" + encodeURIComponent(query)
|
||||
);
|
||||
|
||||
if (fallbackResponse && fallbackResponse.status && fallbackResponse.body) {
|
||||
return { userProfile: fallbackResponse.body };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function mergeWithInvitationStatus(userProfile) {
|
||||
const matchedInvitation = state.invitedList.find(function (item) {
|
||||
return (
|
||||
item &&
|
||||
item.userProfile &&
|
||||
userProfile &&
|
||||
item.userProfile.id === userProfile.id
|
||||
);
|
||||
});
|
||||
|
||||
return Object.assign({}, matchedInvitation || {}, {
|
||||
userProfile: userProfile || {},
|
||||
});
|
||||
}
|
||||
|
||||
async function inviteUser(item) {
|
||||
if (!item || !item.userProfile || !item.userProfile.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request("POST", ENDPOINTS.invite, {
|
||||
body: {
|
||||
inviteUserId: item.userProfile.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (response && response.status) {
|
||||
showToast(TEXT.invitationSubmitted, {
|
||||
title: TEXT.successTitle,
|
||||
type: "success",
|
||||
});
|
||||
await loadInvitedList();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(extractErrorMessage(error, TEXT.somethingWentWrong), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelInvitation(invitationId) {
|
||||
try {
|
||||
const response = await request("POST", ENDPOINTS.cancelPrefix + invitationId);
|
||||
|
||||
if (response && response.status) {
|
||||
showToast(TEXT.applicationCancelled, {
|
||||
title: TEXT.informationTitle,
|
||||
type: "info",
|
||||
});
|
||||
await loadInvitedList();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(extractErrorMessage(error, TEXT.cancellationFailed), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderSearchButton();
|
||||
renderList();
|
||||
}
|
||||
|
||||
function renderSearchButton() {
|
||||
dom.searchButton.disabled = !state.query.trim() || state.searching;
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const currentList = state.showSearchResults ? state.searchResults : state.invitedList;
|
||||
dom.resultList.innerHTML = "";
|
||||
|
||||
currentList.forEach(function (item) {
|
||||
dom.resultList.appendChild(createUserCard(item, state.showSearchResults ? "search" : "invited"));
|
||||
});
|
||||
|
||||
if (currentList.length > 0 || (state.searching && state.showSearchResults)) {
|
||||
dom.noData.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
dom.noData.hidden = false;
|
||||
dom.noData.textContent =
|
||||
state.showSearchResults && state.hasSearched ? TEXT.noUsersFound : TEXT.idlePrompt;
|
||||
}
|
||||
|
||||
function createUserCard(item, mode) {
|
||||
const fragment = dom.userCardTemplate.content.cloneNode(true);
|
||||
const statusBadge = fragment.querySelector(".status-badge");
|
||||
const avatarImage = fragment.querySelector(".avatar-image");
|
||||
const nickname = fragment.querySelector(".nickname");
|
||||
const account = fragment.querySelector(".account");
|
||||
const pendingActions = fragment.querySelector(".pending-actions");
|
||||
const userProfile = item.userProfile || {};
|
||||
|
||||
avatarImage.src = userProfile.userAvatar || DEFAULT_AVATAR;
|
||||
avatarImage.alt = userProfile.userNickname || "";
|
||||
avatarImage.onerror = handleAvatarError;
|
||||
nickname.textContent = userProfile.userNickname || "";
|
||||
account.textContent = TEXT.userIdPrefix + " " + (userProfile.account || userProfile.id || "");
|
||||
|
||||
if (item.status === 0) {
|
||||
statusBadge.innerHTML =
|
||||
'<div style="background: #eef7fb; border: 1px solid #c0e1f1; padding: 4px 8px; border-radius: 0 12px; color: #28627c; font-weight: 700">' +
|
||||
TEXT.pending +
|
||||
"</div>";
|
||||
} else if (item.status === 1) {
|
||||
statusBadge.innerHTML =
|
||||
'<div style="background: #dff5e7; border: 1px solid #b7e0c6; padding: 4px 8px; border-radius: 0 12px; color: #1f6f46; font-weight: 700">' +
|
||||
TEXT.success +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
if (mode === "search") {
|
||||
const inviteButton = document.createElement("button");
|
||||
inviteButton.type = "button";
|
||||
inviteButton.className = "result-action-btn";
|
||||
inviteButton.textContent = TEXT.invite;
|
||||
inviteButton.disabled = item.status === 0 || item.status === 1;
|
||||
inviteButton.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
inviteUser(item);
|
||||
});
|
||||
pendingActions.appendChild(inviteButton);
|
||||
} else if (item.status === 0) {
|
||||
const cancelButton = document.createElement("div");
|
||||
cancelButton.textContent = TEXT.cancel;
|
||||
cancelButton.style.padding = "4px 8px";
|
||||
cancelButton.style.borderRadius = "32px";
|
||||
cancelButton.style.backgroundColor = "red";
|
||||
cancelButton.style.color = "white";
|
||||
cancelButton.style.fontWeight = "600";
|
||||
cancelButton.style.zIndex = "5";
|
||||
cancelButton.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
cancelInvitation(item.id);
|
||||
});
|
||||
pendingActions.appendChild(cancelButton);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function handleAvatarError(event) {
|
||||
event.target.onerror = null;
|
||||
event.target.src = DEFAULT_AVATAR;
|
||||
}
|
||||
|
||||
function extractErrorMessage(error, fallback) {
|
||||
return (
|
||||
(error &&
|
||||
error.response &&
|
||||
(error.response.errorMsg || error.response.message || error.response.error)) ||
|
||||
(error && error.message) ||
|
||||
fallback ||
|
||||
TEXT.somethingWentWrong
|
||||
);
|
||||
}
|
||||
|
||||
function showToast(message, options) {
|
||||
const config = Object.assign(
|
||||
{
|
||||
title: TEXT.informationTitle,
|
||||
type: "info",
|
||||
confirmText: TEXT.ok,
|
||||
},
|
||||
options || {}
|
||||
);
|
||||
|
||||
const iconMap = {
|
||||
error: "✕",
|
||||
warning: "⚠",
|
||||
success: "✓",
|
||||
info: "i",
|
||||
simple: "",
|
||||
};
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "modal-overlay";
|
||||
overlay.innerHTML =
|
||||
'<div class="modal modal-enter modal-' +
|
||||
config.type +
|
||||
'">' +
|
||||
(config.type === "simple"
|
||||
? ""
|
||||
: '<div class="modal-icon"><div class="icon-circle icon-circle-' +
|
||||
config.type +
|
||||
'"><span class="icon-symbol icon-symbol-' +
|
||||
config.type +
|
||||
'">' +
|
||||
iconMap[config.type] +
|
||||
"</span></div></div>") +
|
||||
(config.type === "simple" || !config.title
|
||||
? ""
|
||||
: '<div class="modal-title">' + escapeHtml(config.title) + "</div>") +
|
||||
'<div class="modal-message">' +
|
||||
escapeHtml(message) +
|
||||
"</div>" +
|
||||
'<div class="modal-actions"><button class="modal-btn modal-btn-' +
|
||||
config.type +
|
||||
'">' +
|
||||
escapeHtml(config.confirmText) +
|
||||
"</button></div>" +
|
||||
"</div>";
|
||||
|
||||
const modal = overlay.querySelector(".modal");
|
||||
const closeButton = overlay.querySelector(".modal-btn");
|
||||
const close = function () {
|
||||
modal.classList.remove("modal-enter");
|
||||
modal.classList.add("modal-leave");
|
||||
window.setTimeout(function () {
|
||||
overlay.remove();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
closeButton.addEventListener("click", close);
|
||||
overlay.addEventListener("click", function (event) {
|
||||
if (event.target === overlay) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
async function request(method, path, options) {
|
||||
const config = options || {};
|
||||
const headers = buildRequestHeaders(Boolean(config.body), config.headers || {});
|
||||
const response = await fetch(API_BASE + path, {
|
||||
method: method,
|
||||
credentials: "include",
|
||||
headers: headers,
|
||||
body: config.body ? JSON.stringify(config.body) : undefined,
|
||||
});
|
||||
const text = await response.text();
|
||||
let payload = {};
|
||||
|
||||
if (text) {
|
||||
try {
|
||||
payload = JSON.parse(text);
|
||||
} catch (error) {
|
||||
payload = {
|
||||
status: response.ok,
|
||||
body: text,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok || payload.status === false) {
|
||||
const requestError = new Error(
|
||||
payload.errorMsg || payload.message || response.statusText || TEXT.somethingWentWrong
|
||||
);
|
||||
requestError.response = payload;
|
||||
throw requestError;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function buildRequestHeaders(hasBody, overrideHeaders) {
|
||||
const discovered = discoverAppHeaders();
|
||||
const headers = Object.assign({}, overrideHeaders || {});
|
||||
|
||||
if (hasBody) {
|
||||
headers["Content-Type"] = headers["Content-Type"] || "application/json";
|
||||
}
|
||||
|
||||
headers.Accept = headers.Accept || "application/json, text/plain, */*";
|
||||
|
||||
if (discovered.authorization) {
|
||||
headers.Authorization = headers.Authorization || discovered.authorization;
|
||||
}
|
||||
|
||||
if (discovered.reqLang) {
|
||||
headers["Req-Lang"] = headers["Req-Lang"] || discovered.reqLang;
|
||||
}
|
||||
|
||||
if (discovered.appVersion) {
|
||||
headers["App-Version"] = headers["App-Version"] || discovered.appVersion;
|
||||
}
|
||||
|
||||
if (discovered.buildVersion) {
|
||||
headers["Build-Version"] = headers["Build-Version"] || discovered.buildVersion;
|
||||
}
|
||||
|
||||
if (discovered.appChannel) {
|
||||
headers["App-Channel"] = headers["App-Channel"] || discovered.appChannel;
|
||||
}
|
||||
|
||||
if (discovered.reqImei) {
|
||||
headers["Req-Imei"] = headers["Req-Imei"] || discovered.reqImei;
|
||||
}
|
||||
|
||||
if (discovered.sysOrigin) {
|
||||
headers["Sys-Origin"] = headers["Sys-Origin"] || discovered.sysOrigin;
|
||||
}
|
||||
|
||||
if (discovered.sysOriginChild) {
|
||||
headers["Sys-Origin-Child"] =
|
||||
headers["Sys-Origin-Child"] || discovered.sysOriginChild;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
function discoverAppHeaders() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const collected = [];
|
||||
|
||||
collectCandidate(collected, window.__APP_HEADERS__);
|
||||
collectCandidate(collected, window.__HEADER_INFO__);
|
||||
collectCandidate(collected, window.headerInfo);
|
||||
collectCandidate(collected, parseHeaderObjectFromQuery(query));
|
||||
|
||||
collectFromStorage(collected, window.localStorage);
|
||||
collectFromStorage(collected, window.sessionStorage);
|
||||
|
||||
return collected.reduce(function (result, candidate) {
|
||||
return Object.assign(result, candidate);
|
||||
}, {
|
||||
reqLang: query.get("lang") || document.documentElement.lang || "en",
|
||||
});
|
||||
}
|
||||
|
||||
function parseHeaderObjectFromQuery(query) {
|
||||
const candidate = {};
|
||||
mapHeaderValue(candidate, "authorization", query.get("authorization"));
|
||||
mapHeaderValue(candidate, "authorization", query.get("token"));
|
||||
mapHeaderValue(candidate, "reqLang", query.get("lang"));
|
||||
mapHeaderValue(candidate, "appVersion", query.get("appVersion"));
|
||||
mapHeaderValue(candidate, "buildVersion", query.get("buildVersion"));
|
||||
mapHeaderValue(candidate, "appChannel", query.get("appChannel"));
|
||||
mapHeaderValue(candidate, "reqImei", query.get("reqImei"));
|
||||
mapHeaderValue(candidate, "sysOrigin", query.get("sysOrigin"));
|
||||
mapHeaderValue(candidate, "sysOriginChild", query.get("sysOriginChild"));
|
||||
return candidate;
|
||||
}
|
||||
|
||||
function collectFromStorage(collected, storage) {
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const preferredKeys = [
|
||||
"authorization",
|
||||
"Authorization",
|
||||
"token",
|
||||
"accessToken",
|
||||
"headerInfo",
|
||||
"headersFromApp",
|
||||
"appHeaders",
|
||||
"persist:root",
|
||||
"user",
|
||||
"userInfo",
|
||||
];
|
||||
|
||||
preferredKeys.forEach(function (key) {
|
||||
try {
|
||||
collectCandidate(collected, storage.getItem(key));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
for (let index = 0; index < storage.length; index += 1) {
|
||||
try {
|
||||
const key = storage.key(index);
|
||||
collectCandidate(collected, storage.getItem(key));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectCandidate(collected, value) {
|
||||
const normalized = normalizeHeaderCandidate(value, 0);
|
||||
if (normalized && Object.keys(normalized).length > 0) {
|
||||
collected.push(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHeaderCandidate(value, depth) {
|
||||
if (depth > 4 || value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trimmed[0] === "{" || trimmed[0] === "[") {
|
||||
try {
|
||||
return normalizeHeaderCandidate(JSON.parse(trimmed), depth + 1);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (/bearer\\s+/i.test(trimmed)) {
|
||||
return {
|
||||
authorization: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.reduce(function (result, entry) {
|
||||
const normalized = normalizeHeaderCandidate(entry, depth + 1);
|
||||
return normalized ? Object.assign(result, normalized) : result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (typeof value !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = {};
|
||||
const aliasMap = {
|
||||
authorization: ["authorization", "Authorization", "token", "accessToken"],
|
||||
reqLang: ["reqLang", "Req-Lang", "lang", "language"],
|
||||
appVersion: ["appVersion", "App-Version"],
|
||||
buildVersion: ["buildVersion", "Build-Version"],
|
||||
appChannel: ["appChannel", "App-Channel"],
|
||||
reqImei: ["reqImei", "Req-Imei"],
|
||||
sysOrigin: ["sysOrigin", "Sys-Origin"],
|
||||
sysOriginChild: ["sysOriginChild", "Sys-Origin-Child"],
|
||||
};
|
||||
|
||||
Object.keys(aliasMap).forEach(function (targetKey) {
|
||||
aliasMap[targetKey].forEach(function (sourceKey) {
|
||||
mapHeaderValue(normalized, targetKey, value[sourceKey]);
|
||||
});
|
||||
});
|
||||
|
||||
["headerInfo", "headers", "auth", "data", "state"].forEach(function (nestedKey) {
|
||||
const nestedValue = normalizeHeaderCandidate(value[nestedKey], depth + 1);
|
||||
if (nestedValue) {
|
||||
Object.assign(normalized, nestedValue);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(value).forEach(function (key) {
|
||||
if (
|
||||
key === "headerInfo" ||
|
||||
key === "headers" ||
|
||||
key === "auth" ||
|
||||
key === "data" ||
|
||||
key === "state"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value[key] === "object") {
|
||||
const nestedValue = normalizeHeaderCandidate(value[key], depth + 1);
|
||||
if (nestedValue) {
|
||||
Object.assign(normalized, nestedValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function mapHeaderValue(target, key, value) {
|
||||
if (value == null || value === "" || target[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
target[key] = String(value);
|
||||
}
|
||||
})();
|
||||
109
h5/invite-bd/index.html
Normal file
@ -0,0 +1,109 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Invite User To Become BD</title>
|
||||
<link rel="stylesheet" href="./yumi-invite-bd.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="fullPage" id="pageRoot">
|
||||
<div class="status-bar-placeholder" id="statusBarPlaceholder"></div>
|
||||
<div class="header-container">
|
||||
<div class="side-area">
|
||||
<button class="back-btn" id="backButton" type="button">
|
||||
<img class="flip-img" id="backButtonIcon" src="" alt="" style="width: 100%; aspect-ratio: 1 / 1; display: block" />
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="header-title" id="pageTitle" style="color: black">Invite User To Become BD</h1>
|
||||
<div class="actions-area">
|
||||
<div class="language-entry" id="languageEntry">
|
||||
<div class="language-name" id="languageName">English</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 16px; position: relative">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px">
|
||||
<div
|
||||
class="surface-border-wrapper"
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-radius: 4px;
|
||||
"
|
||||
>
|
||||
<div class="surface-border-content">
|
||||
<div style="padding: 0 8px; display: flex; align-items: center; height: max-content">
|
||||
<input
|
||||
id="searchInput"
|
||||
type="text"
|
||||
placeholder="Enter User ID"
|
||||
autocomplete="off"
|
||||
style="
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
padding: 8px;
|
||||
"
|
||||
/>
|
||||
<button
|
||||
id="clearButton"
|
||||
type="button"
|
||||
hidden
|
||||
style="background: none; border: none; color: #9ca3af; cursor: pointer; padding: 0 4px"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button style="font-weight: 600" class="invite-btn" id="searchButton" type="button" disabled>Search</button>
|
||||
</div>
|
||||
|
||||
<div id="resultList" style="margin: 16px 0; display: flex; flex-direction: column; gap: 12px"></div>
|
||||
<div id="noData" class="no-data">please input user id</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="userCardTemplate">
|
||||
<div
|
||||
class="surface-border-wrapper user-card"
|
||||
style="
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border-radius: 20px;
|
||||
"
|
||||
>
|
||||
<div class="surface-border-content">
|
||||
<div style="padding: 10px; transition: all 0.2s">
|
||||
<div class="status-badge" style="position: absolute; z-index: 3; top: -2px; right: -2px"></div>
|
||||
<div style="font-weight: 600">Information:</div>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 4px">
|
||||
<div style="flex: 1; min-width: 0; align-self: stretch; display: flex; align-items: center; gap: 4px">
|
||||
<div style="width: 3em; display: flex; align-items: center; justify-content: center">
|
||||
<img class="avatar-image" src="" alt="" style="width: 100%; aspect-ratio: 1/1; object-fit: cover; border-radius: 50%" />
|
||||
</div>
|
||||
<div style="flex: 1; min-width: 0; align-self: stretch">
|
||||
<div class="nickname" style="margin-bottom: 4px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap"></div>
|
||||
<div class="account" style="font-size: 0.9em; color: rgba(0, 0, 0, 0.4); font-weight: 500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: auto; min-width: 0; align-self: stretch; display: flex; justify-content: center; align-items: center">
|
||||
<div class="pending-actions"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./yumi-invite-bd.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
177
h5/invite-bd/yumi-invite-bd.css
Normal file
@ -0,0 +1,177 @@
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif;
|
||||
background: #edf8f1;
|
||||
color: #1f3528;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
button, input { font: inherit; }
|
||||
[hidden] { display: none !important; }
|
||||
|
||||
.fullPage {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background: #edf8f1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fullPage input::placeholder {
|
||||
font-weight: 700;
|
||||
color: #7b8d81;
|
||||
}
|
||||
|
||||
.invite-btn, .result-action-btn {
|
||||
padding: 7px 14px;
|
||||
border-radius: 32px;
|
||||
background: #dff5e7;
|
||||
color: #1f6f46;
|
||||
border: 1px solid #b7e0c6;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.invite-btn:disabled, .result-action-btn:disabled {
|
||||
background: #e1e7e3;
|
||||
border-color: #ccd6d0;
|
||||
color: #7b8d81;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #6d7e72;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.user-card { cursor: default; }
|
||||
|
||||
.status-bar-placeholder { display: none; height: 30px; }
|
||||
.status-bar-placeholder.is-visible { display: block; }
|
||||
|
||||
.header-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
height: 60px;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.side-area, .actions-area {
|
||||
min-width: 1.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions-area {
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
color: #153d28 !important;
|
||||
font-size: 1.2em;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.back-btn, .help-btn {
|
||||
width: 1.5em;
|
||||
border: none;
|
||||
background: transparent;
|
||||
transition: transform 0.2s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.back-btn:active, .help-btn:active { transform: scale(0.9); }
|
||||
|
||||
.language-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
background: #dff5e7;
|
||||
padding: 5px 10px;
|
||||
color: #24724a !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.language-name { font-weight: 800; }
|
||||
|
||||
.surface-border-wrapper, .gradient-border-wrapper {
|
||||
position: relative;
|
||||
border: 1px solid #b7e0c6;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
}
|
||||
|
||||
.surface-border-content, .gradient-border-content {
|
||||
position: relative;
|
||||
border-radius: inherit;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04);
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
padding: 24px 20px 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1f3528;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: #dff5e7;
|
||||
color: #1f6f46;
|
||||
padding: 10px 16px;
|
||||
font-weight: 800;
|
||||
}
|
||||
699
h5/invite-bd/yumi-invite-bd.js
Normal file
@ -0,0 +1,699 @@
|
||||
(function () {
|
||||
const API_BASE = "https://jvapi.haiyihy.com";
|
||||
const BACK_ICON =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg==";
|
||||
const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png";
|
||||
const ENDPOINTS = {
|
||||
invitedList: "/team/bd/invite-bd-message-own",
|
||||
invite: "/team/bd/invite-bd",
|
||||
cancelPrefix: "/team/bd/invite-bd-cancel/",
|
||||
};
|
||||
|
||||
const TEXT = {
|
||||
pageTitle: "Invite User To Become BD",
|
||||
language: "English",
|
||||
placeholder: "Enter User ID",
|
||||
search: "Search",
|
||||
invite: "Invite",
|
||||
idlePrompt: "please input user id",
|
||||
noUsersFound: "No users found",
|
||||
information: "Information:",
|
||||
userIdPrefix: "User ID:",
|
||||
pending: "Pending",
|
||||
success: "Success",
|
||||
cancel: "Cancel",
|
||||
invitationSubmitted: "Invitation submitted",
|
||||
applicationCancelled: "Application cancelled",
|
||||
somethingWentWrong: "Something went wrong",
|
||||
cancellationFailed: "Cancellation failed, try again",
|
||||
informationTitle: "Information",
|
||||
successTitle: "Success",
|
||||
errorTitle: "Error",
|
||||
ok: "OK",
|
||||
};
|
||||
|
||||
const state = {
|
||||
query: "",
|
||||
invitedList: [],
|
||||
searchResults: [],
|
||||
showSearchResults: false,
|
||||
searching: false,
|
||||
hasSearched: false,
|
||||
};
|
||||
|
||||
const dom = {
|
||||
statusBarPlaceholder: document.getElementById("statusBarPlaceholder"),
|
||||
backButton: document.getElementById("backButton"),
|
||||
backButtonIcon: document.getElementById("backButtonIcon"),
|
||||
pageTitle: document.getElementById("pageTitle"),
|
||||
languageEntry: document.getElementById("languageEntry"),
|
||||
languageName: document.getElementById("languageName"),
|
||||
searchInput: document.getElementById("searchInput"),
|
||||
clearButton: document.getElementById("clearButton"),
|
||||
searchButton: document.getElementById("searchButton"),
|
||||
resultList: document.getElementById("resultList"),
|
||||
noData: document.getElementById("noData"),
|
||||
userCardTemplate: document.getElementById("userCardTemplate"),
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
document.title = TEXT.pageTitle;
|
||||
document.documentElement.lang = "en";
|
||||
document.documentElement.dir = resolveDocumentDirection();
|
||||
|
||||
dom.backButtonIcon.src = BACK_ICON;
|
||||
dom.pageTitle.textContent = TEXT.pageTitle;
|
||||
dom.languageName.textContent = TEXT.language;
|
||||
dom.searchInput.placeholder = TEXT.placeholder;
|
||||
dom.searchButton.textContent = TEXT.search;
|
||||
dom.noData.textContent = TEXT.idlePrompt;
|
||||
|
||||
syncStatusBarPlaceholder();
|
||||
bindEvents();
|
||||
render();
|
||||
loadInvitedList();
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
dom.backButton.addEventListener("click", handleBack);
|
||||
dom.languageEntry.addEventListener("click", handleLanguageClick);
|
||||
dom.searchInput.addEventListener("input", handleSearchInput);
|
||||
dom.clearButton.addEventListener("click", clearSearch);
|
||||
dom.searchButton.addEventListener("click", triggerSearchNow);
|
||||
}
|
||||
|
||||
function resolveDocumentDirection() {
|
||||
const lang = (new URLSearchParams(window.location.search).get("lang") || "").toLowerCase();
|
||||
return lang.startsWith("ar") ? "rtl" : "ltr";
|
||||
}
|
||||
|
||||
function syncStatusBarPlaceholder() {
|
||||
const hasAppBridge =
|
||||
Boolean(window.app) ||
|
||||
Boolean(window.FlutterPageControl) ||
|
||||
Boolean(window.webkit && window.webkit.messageHandlers);
|
||||
const isIOS = /iphone|ipad|ipod/i.test(window.navigator.userAgent || "");
|
||||
dom.statusBarPlaceholder.classList.toggle("is-visible", hasAppBridge && isIOS);
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
function handleLanguageClick() {
|
||||
window.location.href = "/language";
|
||||
}
|
||||
|
||||
function handleSearchInput(event) {
|
||||
state.query = event.target.value;
|
||||
dom.clearButton.hidden = !state.query;
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
render();
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
state.query = "";
|
||||
state.searchResults = [];
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
dom.searchInput.value = "";
|
||||
dom.clearButton.hidden = true;
|
||||
render();
|
||||
}
|
||||
|
||||
async function triggerSearchNow() {
|
||||
if (state.searching) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = state.query.trim();
|
||||
|
||||
if (!query) {
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
state.searching = true;
|
||||
state.showSearchResults = true;
|
||||
state.hasSearched = true;
|
||||
state.searchResults = [];
|
||||
render();
|
||||
|
||||
try {
|
||||
const payload = await searchUser(query);
|
||||
state.searchResults = payload ? [mergeWithInvitationStatus(payload.userProfile)] : [];
|
||||
} catch (error) {
|
||||
state.searchResults = [];
|
||||
showToast(extractErrorMessage(error), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
state.searching = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadInvitedList() {
|
||||
try {
|
||||
const response = await request("GET", ENDPOINTS.invitedList);
|
||||
state.invitedList = response && response.status && Array.isArray(response.body) ? response.body : [];
|
||||
state.searchResults = state.searchResults.map(function (item) {
|
||||
return mergeWithInvitationStatus(item.userProfile || {});
|
||||
});
|
||||
render();
|
||||
} catch (error) {
|
||||
state.invitedList = [];
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function searchUser(query) {
|
||||
try {
|
||||
const response = await request(
|
||||
"GET",
|
||||
"/user/user-profile/open-search?account=" + encodeURIComponent(query)
|
||||
);
|
||||
|
||||
if (response && response.status && response.body) {
|
||||
return { userProfile: response.body };
|
||||
}
|
||||
} catch (error) {
|
||||
if (!/^\d+$/.test(query)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(query)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fallbackResponse = await request(
|
||||
"GET",
|
||||
"/user/user-profile?userId=" + encodeURIComponent(query)
|
||||
);
|
||||
|
||||
if (fallbackResponse && fallbackResponse.status && fallbackResponse.body) {
|
||||
return { userProfile: fallbackResponse.body };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function mergeWithInvitationStatus(userProfile) {
|
||||
const matchedInvitation = state.invitedList.find(function (item) {
|
||||
return (
|
||||
item &&
|
||||
item.userProfile &&
|
||||
userProfile &&
|
||||
item.userProfile.id === userProfile.id
|
||||
);
|
||||
});
|
||||
|
||||
return Object.assign({}, matchedInvitation || {}, {
|
||||
userProfile: userProfile || {},
|
||||
});
|
||||
}
|
||||
|
||||
async function inviteUser(item) {
|
||||
if (!item || !item.userProfile || !item.userProfile.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request("POST", ENDPOINTS.invite, {
|
||||
body: {
|
||||
inviteUserId: item.userProfile.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (response && response.status) {
|
||||
showToast(TEXT.invitationSubmitted, {
|
||||
title: TEXT.successTitle,
|
||||
type: "success",
|
||||
});
|
||||
await loadInvitedList();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(extractErrorMessage(error, TEXT.somethingWentWrong), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelInvitation(invitationId) {
|
||||
try {
|
||||
const response = await request("POST", ENDPOINTS.cancelPrefix + invitationId);
|
||||
|
||||
if (response && response.status) {
|
||||
showToast(TEXT.applicationCancelled, {
|
||||
title: TEXT.informationTitle,
|
||||
type: "info",
|
||||
});
|
||||
await loadInvitedList();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(extractErrorMessage(error, TEXT.cancellationFailed), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderSearchButton();
|
||||
renderList();
|
||||
}
|
||||
|
||||
function renderSearchButton() {
|
||||
dom.searchButton.disabled = !state.query.trim() || state.searching;
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const currentList = state.showSearchResults ? state.searchResults : state.invitedList;
|
||||
dom.resultList.innerHTML = "";
|
||||
|
||||
currentList.forEach(function (item) {
|
||||
dom.resultList.appendChild(createUserCard(item, state.showSearchResults ? "search" : "invited"));
|
||||
});
|
||||
|
||||
if (currentList.length > 0 || (state.searching && state.showSearchResults)) {
|
||||
dom.noData.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
dom.noData.hidden = false;
|
||||
dom.noData.textContent =
|
||||
state.showSearchResults && state.hasSearched ? TEXT.noUsersFound : TEXT.idlePrompt;
|
||||
}
|
||||
|
||||
function createUserCard(item, mode) {
|
||||
const fragment = dom.userCardTemplate.content.cloneNode(true);
|
||||
const statusBadge = fragment.querySelector(".status-badge");
|
||||
const avatarImage = fragment.querySelector(".avatar-image");
|
||||
const nickname = fragment.querySelector(".nickname");
|
||||
const account = fragment.querySelector(".account");
|
||||
const pendingActions = fragment.querySelector(".pending-actions");
|
||||
const userProfile = item.userProfile || {};
|
||||
|
||||
avatarImage.src = userProfile.userAvatar || DEFAULT_AVATAR;
|
||||
avatarImage.alt = userProfile.userNickname || "";
|
||||
avatarImage.onerror = handleAvatarError;
|
||||
nickname.textContent = userProfile.userNickname || "";
|
||||
account.textContent = TEXT.userIdPrefix + " " + (userProfile.account || userProfile.id || "");
|
||||
|
||||
if (item.status === 0) {
|
||||
statusBadge.innerHTML =
|
||||
'<div style="background: #eef7fb; border: 1px solid #c0e1f1; padding: 4px 8px; border-radius: 0 12px; color: #28627c; font-weight: 700">' +
|
||||
TEXT.pending +
|
||||
"</div>";
|
||||
} else if (item.status === 1) {
|
||||
statusBadge.innerHTML =
|
||||
'<div style="background: #dff5e7; border: 1px solid #b7e0c6; padding: 4px 8px; border-radius: 0 12px; color: #1f6f46; font-weight: 700">' +
|
||||
TEXT.success +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
if (mode === "search") {
|
||||
const inviteButton = document.createElement("button");
|
||||
inviteButton.type = "button";
|
||||
inviteButton.className = "result-action-btn";
|
||||
inviteButton.textContent = TEXT.invite;
|
||||
inviteButton.disabled = item.status === 0 || item.status === 1;
|
||||
inviteButton.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
inviteUser(item);
|
||||
});
|
||||
pendingActions.appendChild(inviteButton);
|
||||
} else if (item.status === 0) {
|
||||
const cancelButton = document.createElement("div");
|
||||
cancelButton.textContent = TEXT.cancel;
|
||||
cancelButton.style.padding = "4px 8px";
|
||||
cancelButton.style.borderRadius = "32px";
|
||||
cancelButton.style.backgroundColor = "red";
|
||||
cancelButton.style.color = "white";
|
||||
cancelButton.style.fontWeight = "600";
|
||||
cancelButton.style.zIndex = "5";
|
||||
cancelButton.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
cancelInvitation(item.id);
|
||||
});
|
||||
pendingActions.appendChild(cancelButton);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function handleAvatarError(event) {
|
||||
event.target.onerror = null;
|
||||
event.target.src = DEFAULT_AVATAR;
|
||||
}
|
||||
|
||||
function extractErrorMessage(error, fallback) {
|
||||
return (
|
||||
(error &&
|
||||
error.response &&
|
||||
(error.response.errorMsg || error.response.message || error.response.error)) ||
|
||||
(error && error.message) ||
|
||||
fallback ||
|
||||
TEXT.somethingWentWrong
|
||||
);
|
||||
}
|
||||
|
||||
function showToast(message, options) {
|
||||
const config = Object.assign(
|
||||
{
|
||||
title: TEXT.informationTitle,
|
||||
type: "info",
|
||||
confirmText: TEXT.ok,
|
||||
},
|
||||
options || {}
|
||||
);
|
||||
|
||||
const iconMap = {
|
||||
error: "✕",
|
||||
warning: "⚠",
|
||||
success: "✓",
|
||||
info: "i",
|
||||
simple: "",
|
||||
};
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "modal-overlay";
|
||||
overlay.innerHTML =
|
||||
'<div class="modal modal-enter modal-' +
|
||||
config.type +
|
||||
'">' +
|
||||
(config.type === "simple"
|
||||
? ""
|
||||
: '<div class="modal-icon"><div class="icon-circle icon-circle-' +
|
||||
config.type +
|
||||
'"><span class="icon-symbol icon-symbol-' +
|
||||
config.type +
|
||||
'">' +
|
||||
iconMap[config.type] +
|
||||
"</span></div></div>") +
|
||||
(config.type === "simple" || !config.title
|
||||
? ""
|
||||
: '<div class="modal-title">' + escapeHtml(config.title) + "</div>") +
|
||||
'<div class="modal-message">' +
|
||||
escapeHtml(message) +
|
||||
"</div>" +
|
||||
'<div class="modal-actions"><button class="modal-btn modal-btn-' +
|
||||
config.type +
|
||||
'">' +
|
||||
escapeHtml(config.confirmText) +
|
||||
"</button></div>" +
|
||||
"</div>";
|
||||
|
||||
const modal = overlay.querySelector(".modal");
|
||||
const closeButton = overlay.querySelector(".modal-btn");
|
||||
const close = function () {
|
||||
modal.classList.remove("modal-enter");
|
||||
modal.classList.add("modal-leave");
|
||||
window.setTimeout(function () {
|
||||
overlay.remove();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
closeButton.addEventListener("click", close);
|
||||
overlay.addEventListener("click", function (event) {
|
||||
if (event.target === overlay) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
async function request(method, path, options) {
|
||||
const config = options || {};
|
||||
const headers = buildRequestHeaders(Boolean(config.body), config.headers || {});
|
||||
const response = await fetch(API_BASE + path, {
|
||||
method: method,
|
||||
credentials: "include",
|
||||
headers: headers,
|
||||
body: config.body ? JSON.stringify(config.body) : undefined,
|
||||
});
|
||||
const text = await response.text();
|
||||
let payload = {};
|
||||
|
||||
if (text) {
|
||||
try {
|
||||
payload = JSON.parse(text);
|
||||
} catch (error) {
|
||||
payload = {
|
||||
status: response.ok,
|
||||
body: text,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok || payload.status === false) {
|
||||
const requestError = new Error(
|
||||
payload.errorMsg || payload.message || response.statusText || TEXT.somethingWentWrong
|
||||
);
|
||||
requestError.response = payload;
|
||||
throw requestError;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function buildRequestHeaders(hasBody, overrideHeaders) {
|
||||
const discovered = discoverAppHeaders();
|
||||
const headers = Object.assign({}, overrideHeaders || {});
|
||||
|
||||
if (hasBody) {
|
||||
headers["Content-Type"] = headers["Content-Type"] || "application/json";
|
||||
}
|
||||
|
||||
headers.Accept = headers.Accept || "application/json, text/plain, */*";
|
||||
|
||||
if (discovered.authorization) {
|
||||
headers.Authorization = headers.Authorization || discovered.authorization;
|
||||
}
|
||||
|
||||
if (discovered.reqLang) {
|
||||
headers["Req-Lang"] = headers["Req-Lang"] || discovered.reqLang;
|
||||
}
|
||||
|
||||
if (discovered.appVersion) {
|
||||
headers["App-Version"] = headers["App-Version"] || discovered.appVersion;
|
||||
}
|
||||
|
||||
if (discovered.buildVersion) {
|
||||
headers["Build-Version"] = headers["Build-Version"] || discovered.buildVersion;
|
||||
}
|
||||
|
||||
if (discovered.appChannel) {
|
||||
headers["App-Channel"] = headers["App-Channel"] || discovered.appChannel;
|
||||
}
|
||||
|
||||
if (discovered.reqImei) {
|
||||
headers["Req-Imei"] = headers["Req-Imei"] || discovered.reqImei;
|
||||
}
|
||||
|
||||
if (discovered.sysOrigin) {
|
||||
headers["Sys-Origin"] = headers["Sys-Origin"] || discovered.sysOrigin;
|
||||
}
|
||||
|
||||
if (discovered.sysOriginChild) {
|
||||
headers["Sys-Origin-Child"] =
|
||||
headers["Sys-Origin-Child"] || discovered.sysOriginChild;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
function discoverAppHeaders() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const collected = [];
|
||||
|
||||
collectCandidate(collected, window.__APP_HEADERS__);
|
||||
collectCandidate(collected, window.__HEADER_INFO__);
|
||||
collectCandidate(collected, window.headerInfo);
|
||||
collectCandidate(collected, parseHeaderObjectFromQuery(query));
|
||||
|
||||
collectFromStorage(collected, window.localStorage);
|
||||
collectFromStorage(collected, window.sessionStorage);
|
||||
|
||||
return collected.reduce(function (result, candidate) {
|
||||
return Object.assign(result, candidate);
|
||||
}, {
|
||||
reqLang: query.get("lang") || document.documentElement.lang || "en",
|
||||
});
|
||||
}
|
||||
|
||||
function parseHeaderObjectFromQuery(query) {
|
||||
const candidate = {};
|
||||
mapHeaderValue(candidate, "authorization", query.get("authorization"));
|
||||
mapHeaderValue(candidate, "authorization", query.get("token"));
|
||||
mapHeaderValue(candidate, "reqLang", query.get("lang"));
|
||||
mapHeaderValue(candidate, "appVersion", query.get("appVersion"));
|
||||
mapHeaderValue(candidate, "buildVersion", query.get("buildVersion"));
|
||||
mapHeaderValue(candidate, "appChannel", query.get("appChannel"));
|
||||
mapHeaderValue(candidate, "reqImei", query.get("reqImei"));
|
||||
mapHeaderValue(candidate, "sysOrigin", query.get("sysOrigin"));
|
||||
mapHeaderValue(candidate, "sysOriginChild", query.get("sysOriginChild"));
|
||||
return candidate;
|
||||
}
|
||||
|
||||
function collectFromStorage(collected, storage) {
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const preferredKeys = [
|
||||
"authorization",
|
||||
"Authorization",
|
||||
"token",
|
||||
"accessToken",
|
||||
"headerInfo",
|
||||
"headersFromApp",
|
||||
"appHeaders",
|
||||
"persist:root",
|
||||
"user",
|
||||
"userInfo",
|
||||
];
|
||||
|
||||
preferredKeys.forEach(function (key) {
|
||||
try {
|
||||
collectCandidate(collected, storage.getItem(key));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
for (let index = 0; index < storage.length; index += 1) {
|
||||
try {
|
||||
const key = storage.key(index);
|
||||
collectCandidate(collected, storage.getItem(key));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectCandidate(collected, value) {
|
||||
const normalized = normalizeHeaderCandidate(value, 0);
|
||||
if (normalized && Object.keys(normalized).length > 0) {
|
||||
collected.push(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHeaderCandidate(value, depth) {
|
||||
if (depth > 4 || value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trimmed[0] === "{" || trimmed[0] === "[") {
|
||||
try {
|
||||
return normalizeHeaderCandidate(JSON.parse(trimmed), depth + 1);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (/bearer\\s+/i.test(trimmed)) {
|
||||
return {
|
||||
authorization: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.reduce(function (result, entry) {
|
||||
const normalized = normalizeHeaderCandidate(entry, depth + 1);
|
||||
return normalized ? Object.assign(result, normalized) : result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (typeof value !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = {};
|
||||
const aliasMap = {
|
||||
authorization: ["authorization", "Authorization", "token", "accessToken"],
|
||||
reqLang: ["reqLang", "Req-Lang", "lang", "language"],
|
||||
appVersion: ["appVersion", "App-Version"],
|
||||
buildVersion: ["buildVersion", "Build-Version"],
|
||||
appChannel: ["appChannel", "App-Channel"],
|
||||
reqImei: ["reqImei", "Req-Imei"],
|
||||
sysOrigin: ["sysOrigin", "Sys-Origin"],
|
||||
sysOriginChild: ["sysOriginChild", "Sys-Origin-Child"],
|
||||
};
|
||||
|
||||
Object.keys(aliasMap).forEach(function (targetKey) {
|
||||
aliasMap[targetKey].forEach(function (sourceKey) {
|
||||
mapHeaderValue(normalized, targetKey, value[sourceKey]);
|
||||
});
|
||||
});
|
||||
|
||||
["headerInfo", "headers", "auth", "data", "state"].forEach(function (nestedKey) {
|
||||
const nestedValue = normalizeHeaderCandidate(value[nestedKey], depth + 1);
|
||||
if (nestedValue) {
|
||||
Object.assign(normalized, nestedValue);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(value).forEach(function (key) {
|
||||
if (
|
||||
key === "headerInfo" ||
|
||||
key === "headers" ||
|
||||
key === "auth" ||
|
||||
key === "data" ||
|
||||
key === "state"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value[key] === "object") {
|
||||
const nestedValue = normalizeHeaderCandidate(value[key], depth + 1);
|
||||
if (nestedValue) {
|
||||
Object.assign(normalized, nestedValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function mapHeaderValue(target, key, value) {
|
||||
if (value == null || value === "" || target[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
target[key] = String(value);
|
||||
}
|
||||
})();
|
||||
13
h5/invite-members/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Invite Members</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Invite Members","mode":"inviteProcess"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
109
h5/invite-recharge-agency/index.html
Normal file
@ -0,0 +1,109 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Invite User To Become Recharge Agent</title>
|
||||
<link rel="stylesheet" href="./yumi-invite-recharge-agency.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="fullPage" id="pageRoot">
|
||||
<div class="status-bar-placeholder" id="statusBarPlaceholder"></div>
|
||||
<div class="header-container">
|
||||
<div class="side-area">
|
||||
<button class="back-btn" id="backButton" type="button">
|
||||
<img class="flip-img" id="backButtonIcon" src="" alt="" style="width: 100%; aspect-ratio: 1 / 1; display: block" />
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="header-title" id="pageTitle" style="color: black">Invite User To Become Recharge Agent</h1>
|
||||
<div class="actions-area">
|
||||
<div class="language-entry" id="languageEntry">
|
||||
<div class="language-name" id="languageName">English</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 16px; position: relative">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px">
|
||||
<div
|
||||
class="surface-border-wrapper"
|
||||
style="
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-radius: 4px;
|
||||
"
|
||||
>
|
||||
<div class="surface-border-content">
|
||||
<div style="padding: 0 8px; display: flex; align-items: center; height: max-content">
|
||||
<input
|
||||
id="searchInput"
|
||||
type="text"
|
||||
placeholder="Enter User ID"
|
||||
autocomplete="off"
|
||||
style="
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
padding: 8px;
|
||||
"
|
||||
/>
|
||||
<button
|
||||
id="clearButton"
|
||||
type="button"
|
||||
hidden
|
||||
style="background: none; border: none; color: #9ca3af; cursor: pointer; padding: 0 4px"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button style="font-weight: 600" class="invite-btn" id="searchButton" type="button" disabled>Search</button>
|
||||
</div>
|
||||
|
||||
<div id="resultList" style="margin: 16px 0; display: flex; flex-direction: column; gap: 12px"></div>
|
||||
<div id="noData" class="no-data">please input user id</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="userCardTemplate">
|
||||
<div
|
||||
class="surface-border-wrapper user-card"
|
||||
style="
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border-radius: 20px;
|
||||
"
|
||||
>
|
||||
<div class="surface-border-content">
|
||||
<div style="padding: 10px; transition: all 0.2s">
|
||||
<div class="status-badge" style="position: absolute; z-index: 3; top: -2px; right: -2px"></div>
|
||||
<div style="font-weight: 600">Information:</div>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 4px">
|
||||
<div style="flex: 1; min-width: 0; align-self: stretch; display: flex; align-items: center; gap: 4px">
|
||||
<div style="width: 3em; display: flex; align-items: center; justify-content: center">
|
||||
<img class="avatar-image" src="" alt="" style="width: 100%; aspect-ratio: 1/1; object-fit: cover; border-radius: 50%" />
|
||||
</div>
|
||||
<div style="flex: 1; min-width: 0; align-self: stretch">
|
||||
<div class="nickname" style="margin-bottom: 4px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap"></div>
|
||||
<div class="account" style="font-size: 0.9em; color: rgba(0, 0, 0, 0.4); font-weight: 500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: auto; min-width: 0; align-self: stretch; display: flex; justify-content: center; align-items: center">
|
||||
<div class="pending-actions"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./yumi-invite-recharge-agency.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
177
h5/invite-recharge-agency/yumi-invite-recharge-agency.css
Normal file
@ -0,0 +1,177 @@
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif;
|
||||
background: #edf8f1;
|
||||
color: #1f3528;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
button, input { font: inherit; }
|
||||
[hidden] { display: none !important; }
|
||||
|
||||
.fullPage {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background: #edf8f1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fullPage input::placeholder {
|
||||
font-weight: 700;
|
||||
color: #7b8d81;
|
||||
}
|
||||
|
||||
.invite-btn, .result-action-btn {
|
||||
padding: 7px 14px;
|
||||
border-radius: 32px;
|
||||
background: #dff5e7;
|
||||
color: #1f6f46;
|
||||
border: 1px solid #b7e0c6;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.invite-btn:disabled, .result-action-btn:disabled {
|
||||
background: #e1e7e3;
|
||||
border-color: #ccd6d0;
|
||||
color: #7b8d81;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #6d7e72;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.user-card { cursor: default; }
|
||||
|
||||
.status-bar-placeholder { display: none; height: 30px; }
|
||||
.status-bar-placeholder.is-visible { display: block; }
|
||||
|
||||
.header-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
height: 60px;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.side-area, .actions-area {
|
||||
min-width: 1.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions-area {
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
color: #153d28 !important;
|
||||
font-size: 1.2em;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.back-btn, .help-btn {
|
||||
width: 1.5em;
|
||||
border: none;
|
||||
background: transparent;
|
||||
transition: transform 0.2s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.back-btn:active, .help-btn:active { transform: scale(0.9); }
|
||||
|
||||
.language-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
background: #dff5e7;
|
||||
padding: 5px 10px;
|
||||
color: #24724a !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.language-name { font-weight: 800; }
|
||||
|
||||
.surface-border-wrapper, .gradient-border-wrapper {
|
||||
position: relative;
|
||||
border: 1px solid #b7e0c6;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
}
|
||||
|
||||
.surface-border-content, .gradient-border-content {
|
||||
position: relative;
|
||||
border-radius: inherit;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04);
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
padding: 24px 20px 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1f3528;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: #dff5e7;
|
||||
color: #1f6f46;
|
||||
padding: 10px 16px;
|
||||
font-weight: 800;
|
||||
}
|
||||
699
h5/invite-recharge-agency/yumi-invite-recharge-agency.js
Normal file
@ -0,0 +1,699 @@
|
||||
(function () {
|
||||
const API_BASE = "https://jvapi.haiyihy.com";
|
||||
const BACK_ICON =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGXSURBVHgB7dvBTcNAFIThB5xTQFwADVBBmqEnUgZQAAXAjRqgACgAj2wfECbJZXZnrPmkVRT5lF9ODn4vVRERERERETEaxnNb8cd+PA/jeZ3PY02xqG7KA+Ic6/eds5vfPxfRdelb4uxXrt0VmXqgU3Hgo8iUA52LA9SvF1yVpkviHOdDpRhIJg6oBZKKA0qB5OKASiDJOKAQSDYO9A4kHQd6BpKPA70CWcSBHoFs4kDrQFZxoGUguzjQKpBlHGgRyDYOsANZxwFmIPs4wAq0iTjACvRUG4gDjEeumDRsIg4wAn3VhjDmYt81jWOGf64vo5q3MsAaHOLDH2oa7q2xicQKhLvopTYQiTl63kQk9mzePlKL5QXrSK22O2wjtVx/sYzUej/ILlKPBSqrSL02zGwi9VzBs4jUe0dRPpLCEqd0JJUtV9lISmvAkpHU9qTlIikukktFUt20vzQSHu++F5HqGvDi3PgIgQ5FpL5p/zme+/l1za7IHP6rcSoSfdPeIRCsRcIPtM18rSUMJoeKiIiIiIjw9QNKh3OJhFQBoAAAAABJRU5ErkJggg==";
|
||||
const DEFAULT_AVATAR = "../assets/defaultAvatar-CdxWBK1k-1776148661448.png";
|
||||
const ENDPOINTS = {
|
||||
invitedList: "/team/bd/invite-recharge-agent-message-own",
|
||||
invite: "/team/bd/invite-recharge-agent",
|
||||
cancelPrefix: "/team/bd/invite-recharge-agent-cancel/",
|
||||
};
|
||||
|
||||
const TEXT = {
|
||||
pageTitle: "Invite User To Become Recharge Agent",
|
||||
language: "English",
|
||||
placeholder: "Enter User ID",
|
||||
search: "Search",
|
||||
invite: "Invite",
|
||||
idlePrompt: "please input user id",
|
||||
noUsersFound: "No users found",
|
||||
information: "Information:",
|
||||
userIdPrefix: "User ID:",
|
||||
pending: "Pending",
|
||||
success: "Success",
|
||||
cancel: "Cancel",
|
||||
invitationSubmitted: "Invitation submitted",
|
||||
applicationCancelled: "Application cancelled",
|
||||
somethingWentWrong: "Something went wrong",
|
||||
cancellationFailed: "Cancellation failed, try again",
|
||||
informationTitle: "Information",
|
||||
successTitle: "Success",
|
||||
errorTitle: "Error",
|
||||
ok: "OK",
|
||||
};
|
||||
|
||||
const state = {
|
||||
query: "",
|
||||
invitedList: [],
|
||||
searchResults: [],
|
||||
showSearchResults: false,
|
||||
searching: false,
|
||||
hasSearched: false,
|
||||
};
|
||||
|
||||
const dom = {
|
||||
statusBarPlaceholder: document.getElementById("statusBarPlaceholder"),
|
||||
backButton: document.getElementById("backButton"),
|
||||
backButtonIcon: document.getElementById("backButtonIcon"),
|
||||
pageTitle: document.getElementById("pageTitle"),
|
||||
languageEntry: document.getElementById("languageEntry"),
|
||||
languageName: document.getElementById("languageName"),
|
||||
searchInput: document.getElementById("searchInput"),
|
||||
clearButton: document.getElementById("clearButton"),
|
||||
searchButton: document.getElementById("searchButton"),
|
||||
resultList: document.getElementById("resultList"),
|
||||
noData: document.getElementById("noData"),
|
||||
userCardTemplate: document.getElementById("userCardTemplate"),
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
document.title = TEXT.pageTitle;
|
||||
document.documentElement.lang = "en";
|
||||
document.documentElement.dir = resolveDocumentDirection();
|
||||
|
||||
dom.backButtonIcon.src = BACK_ICON;
|
||||
dom.pageTitle.textContent = TEXT.pageTitle;
|
||||
dom.languageName.textContent = TEXT.language;
|
||||
dom.searchInput.placeholder = TEXT.placeholder;
|
||||
dom.searchButton.textContent = TEXT.search;
|
||||
dom.noData.textContent = TEXT.idlePrompt;
|
||||
|
||||
syncStatusBarPlaceholder();
|
||||
bindEvents();
|
||||
render();
|
||||
loadInvitedList();
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
dom.backButton.addEventListener("click", handleBack);
|
||||
dom.languageEntry.addEventListener("click", handleLanguageClick);
|
||||
dom.searchInput.addEventListener("input", handleSearchInput);
|
||||
dom.clearButton.addEventListener("click", clearSearch);
|
||||
dom.searchButton.addEventListener("click", triggerSearchNow);
|
||||
}
|
||||
|
||||
function resolveDocumentDirection() {
|
||||
const lang = (new URLSearchParams(window.location.search).get("lang") || "").toLowerCase();
|
||||
return lang.startsWith("ar") ? "rtl" : "ltr";
|
||||
}
|
||||
|
||||
function syncStatusBarPlaceholder() {
|
||||
const hasAppBridge =
|
||||
Boolean(window.app) ||
|
||||
Boolean(window.FlutterPageControl) ||
|
||||
Boolean(window.webkit && window.webkit.messageHandlers);
|
||||
const isIOS = /iphone|ipad|ipod/i.test(window.navigator.userAgent || "");
|
||||
dom.statusBarPlaceholder.classList.toggle("is-visible", hasAppBridge && isIOS);
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
function handleLanguageClick() {
|
||||
window.location.href = "/language";
|
||||
}
|
||||
|
||||
function handleSearchInput(event) {
|
||||
state.query = event.target.value;
|
||||
dom.clearButton.hidden = !state.query;
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
render();
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
state.query = "";
|
||||
state.searchResults = [];
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
dom.searchInput.value = "";
|
||||
dom.clearButton.hidden = true;
|
||||
render();
|
||||
}
|
||||
|
||||
async function triggerSearchNow() {
|
||||
if (state.searching) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = state.query.trim();
|
||||
|
||||
if (!query) {
|
||||
state.showSearchResults = false;
|
||||
state.hasSearched = false;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
state.searching = true;
|
||||
state.showSearchResults = true;
|
||||
state.hasSearched = true;
|
||||
state.searchResults = [];
|
||||
render();
|
||||
|
||||
try {
|
||||
const payload = await searchUser(query);
|
||||
state.searchResults = payload ? [mergeWithInvitationStatus(payload.userProfile)] : [];
|
||||
} catch (error) {
|
||||
state.searchResults = [];
|
||||
showToast(extractErrorMessage(error), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
state.searching = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadInvitedList() {
|
||||
try {
|
||||
const response = await request("GET", ENDPOINTS.invitedList);
|
||||
state.invitedList = response && response.status && Array.isArray(response.body) ? response.body : [];
|
||||
state.searchResults = state.searchResults.map(function (item) {
|
||||
return mergeWithInvitationStatus(item.userProfile || {});
|
||||
});
|
||||
render();
|
||||
} catch (error) {
|
||||
state.invitedList = [];
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function searchUser(query) {
|
||||
try {
|
||||
const response = await request(
|
||||
"GET",
|
||||
"/user/user-profile/open-search?account=" + encodeURIComponent(query)
|
||||
);
|
||||
|
||||
if (response && response.status && response.body) {
|
||||
return { userProfile: response.body };
|
||||
}
|
||||
} catch (error) {
|
||||
if (!/^\d+$/.test(query)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(query)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fallbackResponse = await request(
|
||||
"GET",
|
||||
"/user/user-profile?userId=" + encodeURIComponent(query)
|
||||
);
|
||||
|
||||
if (fallbackResponse && fallbackResponse.status && fallbackResponse.body) {
|
||||
return { userProfile: fallbackResponse.body };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function mergeWithInvitationStatus(userProfile) {
|
||||
const matchedInvitation = state.invitedList.find(function (item) {
|
||||
return (
|
||||
item &&
|
||||
item.userProfile &&
|
||||
userProfile &&
|
||||
item.userProfile.id === userProfile.id
|
||||
);
|
||||
});
|
||||
|
||||
return Object.assign({}, matchedInvitation || {}, {
|
||||
userProfile: userProfile || {},
|
||||
});
|
||||
}
|
||||
|
||||
async function inviteUser(item) {
|
||||
if (!item || !item.userProfile || !item.userProfile.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request("POST", ENDPOINTS.invite, {
|
||||
body: {
|
||||
inviteUserId: item.userProfile.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (response && response.status) {
|
||||
showToast(TEXT.invitationSubmitted, {
|
||||
title: TEXT.successTitle,
|
||||
type: "success",
|
||||
});
|
||||
await loadInvitedList();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(extractErrorMessage(error, TEXT.somethingWentWrong), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelInvitation(invitationId) {
|
||||
try {
|
||||
const response = await request("POST", ENDPOINTS.cancelPrefix + invitationId);
|
||||
|
||||
if (response && response.status) {
|
||||
showToast(TEXT.applicationCancelled, {
|
||||
title: TEXT.informationTitle,
|
||||
type: "info",
|
||||
});
|
||||
await loadInvitedList();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(extractErrorMessage(error, TEXT.cancellationFailed), {
|
||||
title: TEXT.errorTitle,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderSearchButton();
|
||||
renderList();
|
||||
}
|
||||
|
||||
function renderSearchButton() {
|
||||
dom.searchButton.disabled = !state.query.trim() || state.searching;
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const currentList = state.showSearchResults ? state.searchResults : state.invitedList;
|
||||
dom.resultList.innerHTML = "";
|
||||
|
||||
currentList.forEach(function (item) {
|
||||
dom.resultList.appendChild(createUserCard(item, state.showSearchResults ? "search" : "invited"));
|
||||
});
|
||||
|
||||
if (currentList.length > 0 || (state.searching && state.showSearchResults)) {
|
||||
dom.noData.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
dom.noData.hidden = false;
|
||||
dom.noData.textContent =
|
||||
state.showSearchResults && state.hasSearched ? TEXT.noUsersFound : TEXT.idlePrompt;
|
||||
}
|
||||
|
||||
function createUserCard(item, mode) {
|
||||
const fragment = dom.userCardTemplate.content.cloneNode(true);
|
||||
const statusBadge = fragment.querySelector(".status-badge");
|
||||
const avatarImage = fragment.querySelector(".avatar-image");
|
||||
const nickname = fragment.querySelector(".nickname");
|
||||
const account = fragment.querySelector(".account");
|
||||
const pendingActions = fragment.querySelector(".pending-actions");
|
||||
const userProfile = item.userProfile || {};
|
||||
|
||||
avatarImage.src = userProfile.userAvatar || DEFAULT_AVATAR;
|
||||
avatarImage.alt = userProfile.userNickname || "";
|
||||
avatarImage.onerror = handleAvatarError;
|
||||
nickname.textContent = userProfile.userNickname || "";
|
||||
account.textContent = TEXT.userIdPrefix + " " + (userProfile.account || userProfile.id || "");
|
||||
|
||||
if (item.status === 0) {
|
||||
statusBadge.innerHTML =
|
||||
'<div style="background: #eef7fb; border: 1px solid #c0e1f1; padding: 4px 8px; border-radius: 0 12px; color: #28627c; font-weight: 700">' +
|
||||
TEXT.pending +
|
||||
"</div>";
|
||||
} else if (item.status === 1) {
|
||||
statusBadge.innerHTML =
|
||||
'<div style="background: #dff5e7; border: 1px solid #b7e0c6; padding: 4px 8px; border-radius: 0 12px; color: #1f6f46; font-weight: 700">' +
|
||||
TEXT.success +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
if (mode === "search") {
|
||||
const inviteButton = document.createElement("button");
|
||||
inviteButton.type = "button";
|
||||
inviteButton.className = "result-action-btn";
|
||||
inviteButton.textContent = TEXT.invite;
|
||||
inviteButton.disabled = item.status === 0 || item.status === 1;
|
||||
inviteButton.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
inviteUser(item);
|
||||
});
|
||||
pendingActions.appendChild(inviteButton);
|
||||
} else if (item.status === 0) {
|
||||
const cancelButton = document.createElement("div");
|
||||
cancelButton.textContent = TEXT.cancel;
|
||||
cancelButton.style.padding = "4px 8px";
|
||||
cancelButton.style.borderRadius = "32px";
|
||||
cancelButton.style.backgroundColor = "red";
|
||||
cancelButton.style.color = "white";
|
||||
cancelButton.style.fontWeight = "600";
|
||||
cancelButton.style.zIndex = "5";
|
||||
cancelButton.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
cancelInvitation(item.id);
|
||||
});
|
||||
pendingActions.appendChild(cancelButton);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function handleAvatarError(event) {
|
||||
event.target.onerror = null;
|
||||
event.target.src = DEFAULT_AVATAR;
|
||||
}
|
||||
|
||||
function extractErrorMessage(error, fallback) {
|
||||
return (
|
||||
(error &&
|
||||
error.response &&
|
||||
(error.response.errorMsg || error.response.message || error.response.error)) ||
|
||||
(error && error.message) ||
|
||||
fallback ||
|
||||
TEXT.somethingWentWrong
|
||||
);
|
||||
}
|
||||
|
||||
function showToast(message, options) {
|
||||
const config = Object.assign(
|
||||
{
|
||||
title: TEXT.informationTitle,
|
||||
type: "info",
|
||||
confirmText: TEXT.ok,
|
||||
},
|
||||
options || {}
|
||||
);
|
||||
|
||||
const iconMap = {
|
||||
error: "✕",
|
||||
warning: "⚠",
|
||||
success: "✓",
|
||||
info: "i",
|
||||
simple: "",
|
||||
};
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "modal-overlay";
|
||||
overlay.innerHTML =
|
||||
'<div class="modal modal-enter modal-' +
|
||||
config.type +
|
||||
'">' +
|
||||
(config.type === "simple"
|
||||
? ""
|
||||
: '<div class="modal-icon"><div class="icon-circle icon-circle-' +
|
||||
config.type +
|
||||
'"><span class="icon-symbol icon-symbol-' +
|
||||
config.type +
|
||||
'">' +
|
||||
iconMap[config.type] +
|
||||
"</span></div></div>") +
|
||||
(config.type === "simple" || !config.title
|
||||
? ""
|
||||
: '<div class="modal-title">' + escapeHtml(config.title) + "</div>") +
|
||||
'<div class="modal-message">' +
|
||||
escapeHtml(message) +
|
||||
"</div>" +
|
||||
'<div class="modal-actions"><button class="modal-btn modal-btn-' +
|
||||
config.type +
|
||||
'">' +
|
||||
escapeHtml(config.confirmText) +
|
||||
"</button></div>" +
|
||||
"</div>";
|
||||
|
||||
const modal = overlay.querySelector(".modal");
|
||||
const closeButton = overlay.querySelector(".modal-btn");
|
||||
const close = function () {
|
||||
modal.classList.remove("modal-enter");
|
||||
modal.classList.add("modal-leave");
|
||||
window.setTimeout(function () {
|
||||
overlay.remove();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
closeButton.addEventListener("click", close);
|
||||
overlay.addEventListener("click", function (event) {
|
||||
if (event.target === overlay) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
async function request(method, path, options) {
|
||||
const config = options || {};
|
||||
const headers = buildRequestHeaders(Boolean(config.body), config.headers || {});
|
||||
const response = await fetch(API_BASE + path, {
|
||||
method: method,
|
||||
credentials: "include",
|
||||
headers: headers,
|
||||
body: config.body ? JSON.stringify(config.body) : undefined,
|
||||
});
|
||||
const text = await response.text();
|
||||
let payload = {};
|
||||
|
||||
if (text) {
|
||||
try {
|
||||
payload = JSON.parse(text);
|
||||
} catch (error) {
|
||||
payload = {
|
||||
status: response.ok,
|
||||
body: text,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok || payload.status === false) {
|
||||
const requestError = new Error(
|
||||
payload.errorMsg || payload.message || response.statusText || TEXT.somethingWentWrong
|
||||
);
|
||||
requestError.response = payload;
|
||||
throw requestError;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function buildRequestHeaders(hasBody, overrideHeaders) {
|
||||
const discovered = discoverAppHeaders();
|
||||
const headers = Object.assign({}, overrideHeaders || {});
|
||||
|
||||
if (hasBody) {
|
||||
headers["Content-Type"] = headers["Content-Type"] || "application/json";
|
||||
}
|
||||
|
||||
headers.Accept = headers.Accept || "application/json, text/plain, */*";
|
||||
|
||||
if (discovered.authorization) {
|
||||
headers.Authorization = headers.Authorization || discovered.authorization;
|
||||
}
|
||||
|
||||
if (discovered.reqLang) {
|
||||
headers["Req-Lang"] = headers["Req-Lang"] || discovered.reqLang;
|
||||
}
|
||||
|
||||
if (discovered.appVersion) {
|
||||
headers["App-Version"] = headers["App-Version"] || discovered.appVersion;
|
||||
}
|
||||
|
||||
if (discovered.buildVersion) {
|
||||
headers["Build-Version"] = headers["Build-Version"] || discovered.buildVersion;
|
||||
}
|
||||
|
||||
if (discovered.appChannel) {
|
||||
headers["App-Channel"] = headers["App-Channel"] || discovered.appChannel;
|
||||
}
|
||||
|
||||
if (discovered.reqImei) {
|
||||
headers["Req-Imei"] = headers["Req-Imei"] || discovered.reqImei;
|
||||
}
|
||||
|
||||
if (discovered.sysOrigin) {
|
||||
headers["Sys-Origin"] = headers["Sys-Origin"] || discovered.sysOrigin;
|
||||
}
|
||||
|
||||
if (discovered.sysOriginChild) {
|
||||
headers["Sys-Origin-Child"] =
|
||||
headers["Sys-Origin-Child"] || discovered.sysOriginChild;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
function discoverAppHeaders() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const collected = [];
|
||||
|
||||
collectCandidate(collected, window.__APP_HEADERS__);
|
||||
collectCandidate(collected, window.__HEADER_INFO__);
|
||||
collectCandidate(collected, window.headerInfo);
|
||||
collectCandidate(collected, parseHeaderObjectFromQuery(query));
|
||||
|
||||
collectFromStorage(collected, window.localStorage);
|
||||
collectFromStorage(collected, window.sessionStorage);
|
||||
|
||||
return collected.reduce(function (result, candidate) {
|
||||
return Object.assign(result, candidate);
|
||||
}, {
|
||||
reqLang: query.get("lang") || document.documentElement.lang || "en",
|
||||
});
|
||||
}
|
||||
|
||||
function parseHeaderObjectFromQuery(query) {
|
||||
const candidate = {};
|
||||
mapHeaderValue(candidate, "authorization", query.get("authorization"));
|
||||
mapHeaderValue(candidate, "authorization", query.get("token"));
|
||||
mapHeaderValue(candidate, "reqLang", query.get("lang"));
|
||||
mapHeaderValue(candidate, "appVersion", query.get("appVersion"));
|
||||
mapHeaderValue(candidate, "buildVersion", query.get("buildVersion"));
|
||||
mapHeaderValue(candidate, "appChannel", query.get("appChannel"));
|
||||
mapHeaderValue(candidate, "reqImei", query.get("reqImei"));
|
||||
mapHeaderValue(candidate, "sysOrigin", query.get("sysOrigin"));
|
||||
mapHeaderValue(candidate, "sysOriginChild", query.get("sysOriginChild"));
|
||||
return candidate;
|
||||
}
|
||||
|
||||
function collectFromStorage(collected, storage) {
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const preferredKeys = [
|
||||
"authorization",
|
||||
"Authorization",
|
||||
"token",
|
||||
"accessToken",
|
||||
"headerInfo",
|
||||
"headersFromApp",
|
||||
"appHeaders",
|
||||
"persist:root",
|
||||
"user",
|
||||
"userInfo",
|
||||
];
|
||||
|
||||
preferredKeys.forEach(function (key) {
|
||||
try {
|
||||
collectCandidate(collected, storage.getItem(key));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
for (let index = 0; index < storage.length; index += 1) {
|
||||
try {
|
||||
const key = storage.key(index);
|
||||
collectCandidate(collected, storage.getItem(key));
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectCandidate(collected, value) {
|
||||
const normalized = normalizeHeaderCandidate(value, 0);
|
||||
if (normalized && Object.keys(normalized).length > 0) {
|
||||
collected.push(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHeaderCandidate(value, depth) {
|
||||
if (depth > 4 || value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trimmed[0] === "{" || trimmed[0] === "[") {
|
||||
try {
|
||||
return normalizeHeaderCandidate(JSON.parse(trimmed), depth + 1);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (/bearer\\s+/i.test(trimmed)) {
|
||||
return {
|
||||
authorization: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.reduce(function (result, entry) {
|
||||
const normalized = normalizeHeaderCandidate(entry, depth + 1);
|
||||
return normalized ? Object.assign(result, normalized) : result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (typeof value !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = {};
|
||||
const aliasMap = {
|
||||
authorization: ["authorization", "Authorization", "token", "accessToken"],
|
||||
reqLang: ["reqLang", "Req-Lang", "lang", "language"],
|
||||
appVersion: ["appVersion", "App-Version"],
|
||||
buildVersion: ["buildVersion", "Build-Version"],
|
||||
appChannel: ["appChannel", "App-Channel"],
|
||||
reqImei: ["reqImei", "Req-Imei"],
|
||||
sysOrigin: ["sysOrigin", "Sys-Origin"],
|
||||
sysOriginChild: ["sysOriginChild", "Sys-Origin-Child"],
|
||||
};
|
||||
|
||||
Object.keys(aliasMap).forEach(function (targetKey) {
|
||||
aliasMap[targetKey].forEach(function (sourceKey) {
|
||||
mapHeaderValue(normalized, targetKey, value[sourceKey]);
|
||||
});
|
||||
});
|
||||
|
||||
["headerInfo", "headers", "auth", "data", "state"].forEach(function (nestedKey) {
|
||||
const nestedValue = normalizeHeaderCandidate(value[nestedKey], depth + 1);
|
||||
if (nestedValue) {
|
||||
Object.assign(normalized, nestedValue);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(value).forEach(function (key) {
|
||||
if (
|
||||
key === "headerInfo" ||
|
||||
key === "headers" ||
|
||||
key === "auth" ||
|
||||
key === "data" ||
|
||||
key === "state"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value[key] === "object") {
|
||||
const nestedValue = normalizeHeaderCandidate(value[key], depth + 1);
|
||||
if (nestedValue) {
|
||||
Object.assign(normalized, nestedValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function mapHeaderValue(target, key, value) {
|
||||
if (value == null || value === "" || target[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
target[key] = String(value);
|
||||
}
|
||||
})();
|
||||
13
h5/item-distribution/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Item Distribution</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Item Distribution","mode":"itemDistribution","admin":true};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
35
h5/language/index.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
>
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>Yumi H5</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.__H5_ROUTE_PATH__ = "/language";
|
||||
</script>
|
||||
<script src="/assets/yumi-route-entry.js"></script>
|
||||
<script type="module" crossorigin src="/js/index-CIAVq8iD-1776148658686.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/css/index-VRlNQ74k-1776148661679.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
355
h5/logic-pages/app.css
Normal file
@ -0,0 +1,355 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Baloo 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background: #edf8f1;
|
||||
color: #1f3528;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #edf8f1;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr 58px;
|
||||
align-items: center;
|
||||
min-height: calc(48px + env(safe-area-inset-top));
|
||||
padding: env(safe-area-inset-top) 10px 0;
|
||||
background: #f8fff9;
|
||||
border-bottom: 1px solid #cce9d7;
|
||||
box-shadow: 0 4px 14px rgba(75, 137, 95, 0.14);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
min-width: 0;
|
||||
color: #153d28;
|
||||
font-size: 1.28em;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.back,
|
||||
.lang {
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 800;
|
||||
color: #1f6f46;
|
||||
}
|
||||
|
||||
.back span {
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.lang {
|
||||
justify-self: end;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: #dff5e7;
|
||||
font-size: 0.86em;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 1px solid #b7e0c6;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
box-shadow: 0 8px 20px rgba(69, 129, 89, 0.12);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-pad {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.metric-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.metric {
|
||||
padding: 12px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid #cce9d7;
|
||||
background: #fbfffc;
|
||||
}
|
||||
|
||||
.metric:nth-child(2n) {
|
||||
background: #f7fbec;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.84em;
|
||||
color: #5f7468;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.value {
|
||||
margin-top: 4px;
|
||||
color: #153d28;
|
||||
font-size: 1.12em;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: #1f5134;
|
||||
font-size: 1.04em;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 13px 14px;
|
||||
border-bottom: 1px solid #e2f1e7;
|
||||
}
|
||||
|
||||
.row:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: #dff5e7;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #173a28;
|
||||
font-weight: 900;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sub {
|
||||
margin-top: 4px;
|
||||
color: #6d7e72;
|
||||
font-size: 0.84em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.side {
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
font-size: 0.84em;
|
||||
font-weight: 800;
|
||||
color: #375744;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
background: #fff3c8;
|
||||
padding: 5px 9px;
|
||||
color: #846216;
|
||||
font-size: 0.82em;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.toolbar,
|
||||
.tabs,
|
||||
.form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.toolbar input,
|
||||
.form-row input,
|
||||
.input {
|
||||
width: 100%;
|
||||
border: 1px solid #cce9d7;
|
||||
border-radius: 12px;
|
||||
background: #f8fff9;
|
||||
padding: 10px 12px;
|
||||
outline: none;
|
||||
color: #1f3528;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.button,
|
||||
.primary,
|
||||
.ghost {
|
||||
min-height: 38px;
|
||||
border-radius: 12px;
|
||||
padding: 8px 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: #dff5e7;
|
||||
border: 1px solid #b7e0c6;
|
||||
color: #1f6f46;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
border: 1px solid #cce9d7;
|
||||
color: #375744;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.tabs button {
|
||||
flex: 1;
|
||||
border: 1px solid #cce9d7;
|
||||
background: #fff;
|
||||
color: #375744;
|
||||
border-radius: 999px;
|
||||
padding: 9px 8px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.tabs button.active {
|
||||
background: #dff5e7;
|
||||
color: #1f6f46;
|
||||
border-color: #94d9ae;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.grid.three {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.tile {
|
||||
min-height: 96px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border: 1px solid #cce9d7;
|
||||
border-radius: 14px;
|
||||
background: #fbfffc;
|
||||
color: #173a28;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tile.selected {
|
||||
background: #dff5e7;
|
||||
border-color: #74c993;
|
||||
}
|
||||
|
||||
.tile img {
|
||||
width: 54%;
|
||||
max-height: 64px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.empty,
|
||||
.loading {
|
||||
padding: 28px 16px;
|
||||
text-align: center;
|
||||
color: #6d7e72;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
display: none;
|
||||
align-items: flex-end;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.modal-mask.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal {
|
||||
width: 100%;
|
||||
max-height: 82vh;
|
||||
overflow: auto;
|
||||
border-radius: 18px 18px 0 0;
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.modal.center {
|
||||
width: calc(100% - 32px);
|
||||
margin: auto;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin-bottom: 12px;
|
||||
color: #153d28;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: calc(22px + env(safe-area-inset-bottom));
|
||||
z-index: 80;
|
||||
max-width: calc(100% - 40px);
|
||||
transform: translateX(-50%);
|
||||
padding: 9px 13px;
|
||||
border-radius: 999px;
|
||||
background: rgba(22, 56, 36, 0.92);
|
||||
color: #fff;
|
||||
font-size: 0.9em;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
662
h5/logic-pages/app.js
Normal file
@ -0,0 +1,662 @@
|
||||
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
|
||||
let headers = {
|
||||
authorization: "Bearer undefined",
|
||||
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
|
||||
"req-client": "Android",
|
||||
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
|
||||
"req-lang": "id-ID",
|
||||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
|
||||
"req-version": "V2",
|
||||
"req-zone": "Asia/Shanghai"
|
||||
};
|
||||
|
||||
const cfg = window.PAGE_CONFIG || {};
|
||||
const $ = (selector, root = document) => root.querySelector(selector);
|
||||
const money = (v) => {
|
||||
const n = Number(v || 0);
|
||||
return Number.isFinite(n) ? n.toFixed(2).replace(/\.00$/, "") : String(v || 0);
|
||||
};
|
||||
|
||||
function lang() {
|
||||
const value = new URLSearchParams(location.search).get("lang") || navigator.language || "en";
|
||||
if (value.startsWith("ar")) return "AR";
|
||||
if (value.startsWith("tr")) return "TR";
|
||||
if (value.startsWith("bn")) return "BN";
|
||||
return "EN";
|
||||
}
|
||||
|
||||
function splitPairs(value) {
|
||||
const result = {};
|
||||
String(value || "").split(";").forEach((item) => {
|
||||
const index = item.indexOf("=");
|
||||
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function setAppHeaders(raw) {
|
||||
if (!raw) return;
|
||||
const data = typeof raw === "string" ? JSON.parse(raw) : raw;
|
||||
const appIntel = splitPairs(data["Req-App-Intel"]);
|
||||
const sysOrigin = splitPairs(data["Req-Sys-Origin"]);
|
||||
headers = {
|
||||
...headers,
|
||||
authorization: data.Authorization || headers.authorization,
|
||||
"req-lang": data["Req-Lang"] || headers["req-lang"],
|
||||
"req-app-intel": [`version=${appIntel.version || "1.0.0"}`, `build=${appIntel.build || "1"}`, `channel=${appIntel.channel || "Web"}`].join(";"),
|
||||
"req-imei": appIntel["Req-Imei"] || headers["req-imei"],
|
||||
"req-sys-origin": [`origin=${sysOrigin.origin || "LIKEI"}`, `child=${sysOrigin.child || "LIKEI"}`].join(";")
|
||||
};
|
||||
}
|
||||
|
||||
async function connectApp() {
|
||||
if (!(window.app || window.webkit || window.FlutterPageControl)) {
|
||||
headers["req-lang"] = lang().toLowerCase();
|
||||
return;
|
||||
}
|
||||
const raw = await new Promise((resolve) => {
|
||||
window.renderData = resolve;
|
||||
window.getIosAccessOriginParam = resolve;
|
||||
let attempt = 0;
|
||||
const poll = () => {
|
||||
attempt += 1;
|
||||
if (window.app && typeof window.app.getAccessOrigin === "function") return resolve(window.app.getAccessOrigin());
|
||||
if (attempt < 50) return setTimeout(poll, 100);
|
||||
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") window.FlutterApp.postMessage("requestAccessOrigin");
|
||||
resolve(null);
|
||||
};
|
||||
poll();
|
||||
});
|
||||
if (raw) setAppHeaders(raw);
|
||||
}
|
||||
|
||||
async function request(method, path, body) {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: { ...headers, "content-type": "application/json" },
|
||||
body: body === undefined ? undefined : JSON.stringify(body)
|
||||
});
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok || data.status === false) {
|
||||
const error = new Error(data.errorMsg || data.message || `HTTP ${response.status}`);
|
||||
error.response = data;
|
||||
throw error;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const get = (path) => request("GET", path);
|
||||
const post = (path, body) => request("POST", path, body);
|
||||
|
||||
function getTeamId() {
|
||||
const keys = ["teamId", "TEAM_ID", "team_id"];
|
||||
for (const key of keys) {
|
||||
const value = localStorage.getItem(key);
|
||||
if (value) return value.replace(/^"|"$/g, "");
|
||||
}
|
||||
for (const key of ["teamInfo", "userInfo", "identity"]) {
|
||||
try {
|
||||
const item = JSON.parse(localStorage.getItem(key) || "{}");
|
||||
if (item.teamId || item.id) return item.teamId || item.id;
|
||||
} catch {}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getIdentity() {
|
||||
try {
|
||||
const value = JSON.parse(localStorage.getItem("identity") || "{}");
|
||||
return value.identity || value.role || "BD";
|
||||
} catch {
|
||||
return "BD";
|
||||
}
|
||||
}
|
||||
|
||||
function toast(message) {
|
||||
const node = $("#toast");
|
||||
node.textContent = message || "Request failed";
|
||||
node.hidden = false;
|
||||
clearTimeout(toast.timer);
|
||||
toast.timer = setTimeout(() => (node.hidden = true), 2500);
|
||||
}
|
||||
|
||||
function setLoading(text = "Loading...") {
|
||||
$("#app").innerHTML = `<div class="loading">${text}</div>`;
|
||||
}
|
||||
|
||||
function row({ avatar, title, sub, side, tag, onClick }) {
|
||||
const node = document.createElement("button");
|
||||
node.type = "button";
|
||||
node.className = "row";
|
||||
node.innerHTML = `${avatar !== undefined ? `<img class="avatar" src="${avatar || ""}" alt="">` : ""}<div class="main"><div class="title"></div><div class="sub"></div></div><div class="side"></div>${tag ? `<span class="tag"></span>` : ""}`;
|
||||
$(".title", node).textContent = title || "-";
|
||||
$(".sub", node).textContent = sub || "";
|
||||
$(".side", node).textContent = side || "";
|
||||
if (tag) $(".tag", node).textContent = tag;
|
||||
if (onClick) node.addEventListener("click", onClick);
|
||||
return node;
|
||||
}
|
||||
|
||||
function metrics(items) {
|
||||
return `<section class="metric-grid">${items.map((item) => `<div class="metric"><div class="label">${item.label}</div><div class="value">${item.value}</div></div>`).join("")}</section>`;
|
||||
}
|
||||
|
||||
function showModal(title, content, center = false) {
|
||||
const mask = $("#modalMask");
|
||||
mask.innerHTML = `<div class="modal ${center ? "center" : ""}"><div class="modal-title"></div><div class="modal-body"></div></div>`;
|
||||
$(".modal-title", mask).textContent = title || "";
|
||||
$(".modal-body", mask).append(content);
|
||||
mask.classList.add("show");
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
$("#modalMask").classList.remove("show");
|
||||
}
|
||||
|
||||
function navigate(route) {
|
||||
const url = new URL(route, location.origin);
|
||||
const langParam = new URLSearchParams(location.search).get("lang");
|
||||
if (langParam && !url.searchParams.has("lang")) url.searchParams.set("lang", langParam);
|
||||
location.href = `${url.pathname}${url.search}${url.hash}`;
|
||||
}
|
||||
|
||||
function renderShell() {
|
||||
document.title = cfg.title || "Yumi H5";
|
||||
document.body.innerHTML = `
|
||||
<div class="page">
|
||||
<header class="header">
|
||||
<button class="back" type="button" data-back aria-label="Back"><span>‹</span></button>
|
||||
<h1>${cfg.title || "Yumi H5"}</h1>
|
||||
<button class="lang" type="button">${lang()}</button>
|
||||
</header>
|
||||
<main class="content" id="app"></main>
|
||||
<div class="modal-mask" id="modalMask"></div>
|
||||
<div class="toast" id="toast" hidden></div>
|
||||
</div>`;
|
||||
$("[data-back]").addEventListener("click", () => history.length > 1 ? history.back() : window.app?.closePage?.());
|
||||
$("#modalMask").addEventListener("click", (event) => {
|
||||
if (event.target.id === "modalMask") closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
async function renderTeamHistory() {
|
||||
setLoading();
|
||||
const [listRes, historyRes] = await Promise.allSettled([get(cfg.listEndpoint), get(cfg.historyEndpoint)]);
|
||||
const bill = listRes.value?.body || {};
|
||||
const history = historyRes.value?.body || {};
|
||||
$("#app").innerHTML = `
|
||||
${metrics([
|
||||
{ label: cfg.countLabel, value: bill.memberCount || 0 },
|
||||
{ label: cfg.totalLabel, value: `$${money(bill.totalSalary || bill.teamSalary || bill.totalRecharge || 0)}` }
|
||||
])}
|
||||
<section class="card">
|
||||
<div class="row">
|
||||
<div class="main"><div class="title">${bill.billTitle || cfg.title}</div><div class="sub">${cfg.totalLabel}: $${money(bill.totalSalary || 0)}</div></div>
|
||||
<button class="primary" id="historyBtn" type="button">History</button>
|
||||
</div>
|
||||
<div id="list"></div>
|
||||
</section>`;
|
||||
const list = $("#list");
|
||||
(bill.memberBillList || []).forEach((item) => {
|
||||
list.appendChild(row({
|
||||
avatar: item.agentAvatar || item.userAvatar,
|
||||
title: item.agentName || item.userName,
|
||||
sub: `ID: ${item.agentAccount || item.account || "-"}`,
|
||||
side: `${cfg.memberSide1}: $${money(item.teamSalaryAmount || item.salary || 0)}\n${cfg.memberSide2}: ${item.teamMemberCount || item.hostCount || item.agencyCount || 0}`
|
||||
}));
|
||||
});
|
||||
if (!list.children.length) list.innerHTML = `<div class="empty">No data</div>`;
|
||||
$("#historyBtn").addEventListener("click", () => {
|
||||
const body = document.createElement("div");
|
||||
body.innerHTML = metrics([
|
||||
{ label: "Total income", value: `$${money(history.totalIncome || 0)}` },
|
||||
{ label: "Previous period", value: `$${money(history.previousPeriodIncome || 0)}` }
|
||||
]);
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "card";
|
||||
(history.incomeDetails || []).forEach((item) => {
|
||||
wrap.appendChild(row({
|
||||
title: item.billTitle || item.settlementPeriod,
|
||||
sub: `${cfg.countLabel}: ${item.agencyNumber || item.bdNumber || item.memberCount || 0}`,
|
||||
side: `$${money(item.teamSalaryAmount || item.teamTotalIncome || item.settlementSalary || 0)}`,
|
||||
tag: item.statusText || item.status,
|
||||
onClick: async () => {
|
||||
if (!cfg.moreEndpoint || !item.billBelong) return;
|
||||
try {
|
||||
const more = await get(`${cfg.moreEndpoint}${item.billBelong}`);
|
||||
const detail = document.createElement("div");
|
||||
const card = document.createElement("div");
|
||||
card.className = "card";
|
||||
(more.body?.memberDetails || more.body?.members || []).forEach((member) => {
|
||||
card.appendChild(row({
|
||||
avatar: member.userAvatar || member.bdUserAvatar || member.avatar,
|
||||
title: member.userName || member.bdUserName || member.account,
|
||||
sub: `ID: ${member.account || "-"}`,
|
||||
side: `$${money(member.salary || member.income || 0)}`
|
||||
}));
|
||||
});
|
||||
detail.appendChild(card);
|
||||
showModal(more.body?.period || item.billTitle, detail);
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
if (!wrap.children.length) wrap.innerHTML = `<div class="empty">No data</div>`;
|
||||
body.appendChild(wrap);
|
||||
showModal("History", body);
|
||||
});
|
||||
}
|
||||
|
||||
async function renderAdminTeam() {
|
||||
setLoading();
|
||||
const res = await post("/bd/team/overview", { type: cfg.teamType });
|
||||
const data = res.body || {};
|
||||
$("#app").innerHTML = `
|
||||
${metrics([
|
||||
{ label: "Total income", value: `$${money(data.totalIncome || 0)}` },
|
||||
{ label: "Previous period", value: `$${money(data.previousPeriodIncome || 0)}` },
|
||||
{ label: cfg.memberTitle, value: data.memberCount || 0 },
|
||||
{ label: "Team total income", value: `$${money(data.teamTotalIncome || 0)}` }
|
||||
])}
|
||||
<section class="card" id="members"></section>`;
|
||||
const members = $("#members");
|
||||
(data.members || []).forEach((item) => {
|
||||
members.appendChild(row({
|
||||
avatar: item.avatar,
|
||||
title: item.userName || "-",
|
||||
sub: `ID: ${item.account || "-"}`,
|
||||
side: `${cfg.subMemberLabel}: ${item.subMemberCount || 0}\n$${money(item.totalIncome || 0)}`,
|
||||
tag: "More",
|
||||
onClick: () => showAdminTeamDetail(item)
|
||||
}));
|
||||
});
|
||||
if (!members.children.length) members.innerHTML = `<div class="empty">No data</div>`;
|
||||
}
|
||||
|
||||
async function showAdminTeamDetail(item) {
|
||||
try {
|
||||
const res = await post("/bd/team/member/detail", { targetId: item.userId, type: cfg.detailType || cfg.teamType });
|
||||
const data = res.body || {};
|
||||
const body = document.createElement("div");
|
||||
body.innerHTML = metrics([
|
||||
{ label: "Total income", value: `$${money(data.totalIncome || 0)}` },
|
||||
{ label: "Previous period", value: `$${money(data.previousPeriodIncome || 0)}` }
|
||||
]);
|
||||
const card = document.createElement("div");
|
||||
card.className = "card";
|
||||
(data.incomeDetails || []).forEach((detail) => {
|
||||
card.appendChild(row({
|
||||
title: detail.settlementPeriod,
|
||||
sub: `${cfg.subMemberLabel}: ${detail.memberCount || 0}`,
|
||||
side: `$${money(detail.teamTotalIncome || 0)}`,
|
||||
tag: detail.status || "More",
|
||||
onClick: () => {
|
||||
const detailBody = document.createElement("div");
|
||||
const detailCard = document.createElement("div");
|
||||
detailCard.className = "card";
|
||||
(detail.members || []).forEach((member) => detailCard.appendChild(row({
|
||||
avatar: member.avatar,
|
||||
title: member.userName,
|
||||
sub: `ID: ${member.account || "-"}`,
|
||||
side: `$${money(member.income || 0)}`
|
||||
})));
|
||||
detailBody.appendChild(detailCard);
|
||||
showModal(detail.settlementPeriod, detailBody);
|
||||
}
|
||||
}));
|
||||
});
|
||||
if (!card.children.length) card.innerHTML = `<div class="empty">No data</div>`;
|
||||
body.appendChild(card);
|
||||
showModal(`${cfg.memberTitle} ${item.account || ""}`, body);
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function renderRechargeAgency() {
|
||||
setLoading();
|
||||
const [summary, list] = await Promise.allSettled([
|
||||
get("/app/h5/recharge-summary"),
|
||||
get("/app/h5/recharge-page?cursor=1&limit=20")
|
||||
]);
|
||||
const data = summary.value?.body || {};
|
||||
const records = list.value?.body?.records || [];
|
||||
$("#app").innerHTML = `${metrics([
|
||||
{ label: "My total income", value: `$${money(data.totalIncome || 0)}` },
|
||||
{ label: "This period recharge", value: `$${money(data.currentMonthRecharge || 0)}` }
|
||||
])}<div class="section-title">Recharge agency list</div><section class="card" id="records"></section>`;
|
||||
const root = $("#records");
|
||||
records.forEach((item) => root.appendChild(row({
|
||||
avatar: item.userAvatar,
|
||||
title: item.userNickname,
|
||||
sub: `ID: ${item.account || "-"}`,
|
||||
side: `This: $${money(item.thisMonth || 0)}\nLast: $${money(item.lastMonth || 0)}`
|
||||
})));
|
||||
if (!root.children.length) root.innerHTML = `<div class="empty">No data</div>`;
|
||||
}
|
||||
|
||||
async function renderMessage() {
|
||||
const teamId = getTeamId();
|
||||
setLoading();
|
||||
const res = await get(`/team/user/apply/record?teamId=${teamId}&status=WAIT`);
|
||||
const records = (res.body || []).map((item) => ({
|
||||
id: item.id,
|
||||
account: item.createUserProfile?.account,
|
||||
name: item.createUserProfile?.userNickname,
|
||||
avatar: item.createUserProfile?.userAvatar
|
||||
}));
|
||||
$("#app").innerHTML = `<section class="card" id="messages"></section>`;
|
||||
const root = $("#messages");
|
||||
records.forEach((item) => {
|
||||
const node = row({ avatar: item.avatar, title: item.name || "Unknown user", sub: `ID: ${item.account || "-"}` });
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "toolbar";
|
||||
actions.innerHTML = `<button class="primary" type="button">Agree</button><button class="ghost" type="button">Refuse</button>`;
|
||||
actions.children[0].addEventListener("click", async () => updateApply(item.id, "AGREE", node));
|
||||
actions.children[1].addEventListener("click", async () => updateApply(item.id, "REJECT", node));
|
||||
node.appendChild(actions);
|
||||
root.appendChild(node);
|
||||
});
|
||||
if (!root.children.length) root.innerHTML = `<div class="empty">No data</div>`;
|
||||
}
|
||||
|
||||
async function updateApply(id, status, node) {
|
||||
try {
|
||||
await post("/team/user/apply/record/update", { id, status });
|
||||
node.remove();
|
||||
toast(status === "AGREE" ? "Approved" : "Rejected");
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function renderTeamMembers() {
|
||||
const teamId = getTeamId();
|
||||
setLoading();
|
||||
const res = await get(`/team/members?id=${teamId}`);
|
||||
const records = res.body || [];
|
||||
$("#app").innerHTML = `
|
||||
<div class="toolbar"><input id="search" placeholder="Enter host ID"><button class="primary" id="searchBtn">Search</button><button class="ghost" id="cancelBtn">Cancel</button></div>
|
||||
<section class="card" id="members"></section>`;
|
||||
const root = $("#members");
|
||||
const draw = (items) => {
|
||||
root.innerHTML = "";
|
||||
items.forEach((member, index) => root.appendChild(row({
|
||||
avatar: member.userProfile?.userAvatar,
|
||||
title: `${index + 1}. ${member.userProfile?.userNickname || "-"}`,
|
||||
sub: `ID: ${member.userProfile?.ownSpecialId?.account || member.userProfile?.account || "-"}`,
|
||||
side: `${member.targetFormat || "-"}\n${member.timeHours || "-"}`
|
||||
})));
|
||||
if (!root.children.length) root.innerHTML = `<div class="empty">No data</div>`;
|
||||
};
|
||||
draw(records);
|
||||
$("#searchBtn").addEventListener("click", async () => {
|
||||
const account = $("#search").value.trim();
|
||||
if (!account) return draw(records);
|
||||
try {
|
||||
const searched = await get(`/team/search-members?account=${account}`);
|
||||
draw(searched.body || []);
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
});
|
||||
$("#cancelBtn").addEventListener("click", () => {
|
||||
$("#search").value = "";
|
||||
draw(records);
|
||||
});
|
||||
}
|
||||
|
||||
async function renderTeamBill() {
|
||||
const teamId = getTeamId();
|
||||
setLoading();
|
||||
const res = await get(`/team/bill?id=${teamId}`);
|
||||
const records = res.body || [];
|
||||
let total = 0;
|
||||
records.forEach((item) => {
|
||||
if (item.status === "SETTLED") total += Number(item.settleResult?.totalSalary || 0);
|
||||
});
|
||||
$("#app").innerHTML = `${metrics([{ label: "History team income", value: `$${money(total)}` }])}<div class="section-title">Work report</div><section class="card" id="bills"></section>`;
|
||||
const root = $("#bills");
|
||||
records.forEach((item) => root.appendChild(row({
|
||||
title: item.billTitle,
|
||||
sub: `Host salary: $${money(item.settleResult?.memberSalary || 0)} | Agent salary: $${money(item.settleResult?.ownSalary || 0)}`,
|
||||
side: `$${money(item.settleResult?.totalSalary || 0)}`,
|
||||
tag: ({ SETTLED: "Completed", UNPAID: "In Progress", HANG_UP: "Out of account", PAY_OUT: "Completed" })[item.status] || item.status,
|
||||
onClick: async () => {
|
||||
try {
|
||||
const detail = await get(`/team/bill/work-members?id=${item.id}`);
|
||||
const body = document.createElement("div");
|
||||
const card = document.createElement("div");
|
||||
card.className = "card";
|
||||
(detail.body || []).forEach((member) => card.appendChild(row({
|
||||
avatar: member.memberProfile?.userAvatar,
|
||||
title: member.memberProfile?.userNickname,
|
||||
sub: `ID: ${member.memberProfile?.account || "-"}`,
|
||||
side: `Target: ${member.target?.acceptGiftValue || 0}\nDays: ${member.target?.effectiveDay || 0}`
|
||||
})));
|
||||
body.appendChild(card);
|
||||
showModal(item.billTitle, body);
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
}
|
||||
})));
|
||||
if (!root.children.length) root.innerHTML = `<div class="empty">No data</div>`;
|
||||
}
|
||||
|
||||
function renderInviteProcess() {
|
||||
$("#app").innerHTML = `<section class="card card-pad">
|
||||
<div class="row"><div class="tag">1</div><div class="main"><div class="title">Invite users to download the app.</div></div></div>
|
||||
<div class="row"><div class="tag">2</div><div class="main"><div class="title">Invited users log in.</div></div></div>
|
||||
<div class="row"><div class="tag">3</div><div class="main"><div class="title">Users join your team.</div></div></div>
|
||||
<div class="row"><div class="tag">4</div><div class="main"><div class="title">Enter team ID when joining.</div></div></div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
async function renderBankCards() {
|
||||
setLoading();
|
||||
const [cards, kyc] = await Promise.allSettled([get("/wallet/bank-card/list"), get("/wallet/withdraw-info/list")]);
|
||||
const current = (cards.value?.body || []).find((item) => item.use);
|
||||
const info = kyc.value?.body?.[0];
|
||||
$("#app").innerHTML = `
|
||||
<div class="section-title">Bank card</div>
|
||||
<section class="card" id="cardBox"></section>
|
||||
<div class="section-title">KYC</div>
|
||||
<section class="card" id="kycBox"></section>`;
|
||||
$("#cardBox").appendChild(current ? row({ title: current.cardType, sub: `Card: ${current.cardNo}`, side: current.payee }) : row({ title: "Add payment method", sub: "No bank cards in use", tag: "Add", onClick: () => navigate("/bank-card") }));
|
||||
$("#kycBox").appendChild(info ? row({ title: `Status: ${info.status}`, sub: `Contact: ${info.contactNumber || "-"}`, side: info.otherDescription || "", tag: "KYC", onClick: () => navigate("/KYC") }) : row({ title: "Complete information", sub: "No KYC info", tag: "KYC", onClick: () => navigate("/KYC") }));
|
||||
}
|
||||
|
||||
async function renderWalletAction() {
|
||||
setLoading();
|
||||
const action = new URLSearchParams(location.search).get("activeAction") || cfg.action || "Exchange";
|
||||
const balanceEndpoint = cfg.salaryType ? `/wallet/salary-account/balance?salaryType=${cfg.salaryType}` : "/wallet/bank/balance";
|
||||
const balanceRes = await get(balanceEndpoint).catch(() => ({ body: 0 }));
|
||||
const balance = typeof balanceRes.body === "number" ? balanceRes.body : balanceRes.body?.availableBalance || balanceRes.body?.balance || 0;
|
||||
const amounts = [1, 5, 10, 50, 100, 200];
|
||||
$("#app").innerHTML = `
|
||||
${metrics([{ label: "Available salaries", value: `$${money(balance)}` }])}
|
||||
<div class="tabs">
|
||||
<button class="${action === "Exchange" ? "active" : ""}" data-act="Exchange">Exchange</button>
|
||||
<button class="${action === "Transfer" ? "active" : ""}" data-act="Transfer">Transfer</button>
|
||||
<button class="${action === "Cash out" ? "active" : ""}" data-act="Cash out">Cash out</button>
|
||||
</div>
|
||||
<section class="card card-pad" id="walletPanel"></section>`;
|
||||
document.querySelectorAll("[data-act]").forEach((btn) => btn.addEventListener("click", () => navigate(`${location.pathname}?activeAction=${encodeURIComponent(btn.dataset.act)}&lang=${lang().toLowerCase()}`)));
|
||||
const panel = $("#walletPanel");
|
||||
if (action === "Cash out") return renderCashOutPanel(panel);
|
||||
if (action === "Transfer") return renderTransferPanel(panel, amounts);
|
||||
renderExchangePanel(panel, amounts);
|
||||
}
|
||||
|
||||
function renderExchangePanel(panel, amounts) {
|
||||
panel.innerHTML = `<div class="grid three">${amounts.map((amount) => `<button class="tile" data-amount="${amount}">$${amount}</button>`).join("")}</div><button class="primary" id="submit">Exchange</button>`;
|
||||
let selected = null;
|
||||
panel.querySelectorAll("[data-amount]").forEach((node) => node.addEventListener("click", () => {
|
||||
selected = Number(node.dataset.amount);
|
||||
panel.querySelectorAll(".tile").forEach((tile) => tile.classList.remove("selected"));
|
||||
node.classList.add("selected");
|
||||
}));
|
||||
$("#submit").addEventListener("click", async () => {
|
||||
if (!selected) return toast("Please select amount");
|
||||
try {
|
||||
const path = cfg.salaryType ? "/wallet/salary-account/exchange/gold" : "/wallet/bank/exchange/gold";
|
||||
await post(path, cfg.salaryType ? { amount: selected, salaryType: cfg.salaryType } : { amount: selected });
|
||||
toast("Exchange success");
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderTransferPanel(panel, amounts) {
|
||||
panel.innerHTML = `<div class="form-row"><input id="payee" placeholder="Enter user ID"><button class="primary" id="findPayee">Search</button></div><div id="payeeInfo"></div><div class="grid three">${amounts.map((amount) => `<button class="tile" data-amount="${amount}">$${amount}</button>`).join("")}</div><button class="primary" id="submit">Transfer</button>`;
|
||||
let payee = null;
|
||||
let selected = null;
|
||||
$("#findPayee").addEventListener("click", async () => {
|
||||
try {
|
||||
const res = await get(`/user/user-profile/open-search?account=${encodeURIComponent($("#payee").value.trim())}`);
|
||||
payee = res.body;
|
||||
$("#payeeInfo").innerHTML = "";
|
||||
$("#payeeInfo").appendChild(row({ avatar: payee.userAvatar, title: payee.userNickname || payee.name, sub: `ID: ${payee.account}`, side: "" }));
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
});
|
||||
panel.querySelectorAll("[data-amount]").forEach((node) => node.addEventListener("click", () => {
|
||||
selected = Number(node.dataset.amount);
|
||||
panel.querySelectorAll(".tile").forEach((tile) => tile.classList.remove("selected"));
|
||||
node.classList.add("selected");
|
||||
}));
|
||||
$("#submit").addEventListener("click", async () => {
|
||||
if (!selected || !payee?.id) return toast("Please select amount and payee");
|
||||
try {
|
||||
const path = cfg.salaryType ? "/wallet/salary-account/transferGold" : "/wallet/bank/transferGold";
|
||||
await post(path, cfg.salaryType ? { amount: selected, acceptUserId: payee.id, salaryType: cfg.salaryType } : { amount: selected, acceptUserId: payee.id, acceptMethod: "BANK_TRANSFER" });
|
||||
toast("Transfer success");
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function renderCashOutPanel(panel) {
|
||||
const [cards, kyc] = await Promise.allSettled([get("/wallet/bank-card/list"), get("/wallet/withdraw-info/list")]);
|
||||
const card = (cards.value?.body || []).find((item) => item.use);
|
||||
const info = kyc.value?.body?.[0];
|
||||
panel.innerHTML = `<div class="form-row"><input id="amount" placeholder="50" inputmode="decimal"></div><div id="card"></div><div id="kyc"></div><button class="primary" id="submit">Cash out</button>`;
|
||||
$("#card").appendChild(card ? row({ title: card.cardType, sub: `Card: ${card.cardNo}`, side: card.payee }) : row({ title: "No bank cards in use", sub: "Add bank card", tag: "Add", onClick: () => navigate("/bank-card") }));
|
||||
$("#kyc").appendChild(info ? row({ title: `KYC: ${info.status}`, sub: `Contact: ${info.contactNumber || "-"}`, tag: "KYC" }) : row({ title: "Complete KYC", sub: "No KYC information", tag: "KYC", onClick: () => navigate("/KYC") }));
|
||||
$("#submit").addEventListener("click", async () => {
|
||||
const amount = Number($("#amount").value);
|
||||
if (!card?.id || !amount || amount < 50) return toast("Please complete information and enter at least 50");
|
||||
try {
|
||||
await post("/wallet/bank/withdraw", { amount, acceptBankCardId: card.id, salaryType: cfg.salaryType || (getIdentity() === "HOST" ? "HOST_SALARY" : "BD_SALARY") });
|
||||
toast("Cash withdrawal submitted");
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function renderItemDistribution() {
|
||||
setLoading();
|
||||
const permissionEndpoint = cfg.admin ? "/app/h5/own-permission?propsType=NOBLE_VIP" : "/sys/bd/list-permission";
|
||||
const propsBase = cfg.admin ? "/sys/manager/props" : "/sys/bd/props";
|
||||
const sendEndpoint = cfg.admin ? "/sys/manager/send" : "/sys/bd/send";
|
||||
let tabs = cfg.admin ? [{ name: "frames", type: "AVATAR_FRAME" }, { name: "vehicles", type: "RIDE" }, { name: "vip", type: "NOBLE_VIP" }] : [];
|
||||
if (!cfg.admin) {
|
||||
const res = await get(permissionEndpoint);
|
||||
const body = res.body || [];
|
||||
if (body.includes("AVATAR_FRAME")) tabs.push({ name: "frames", type: "AVATAR_FRAME" });
|
||||
if (body.includes("RIDE")) tabs.push({ name: "vehicles", type: "RIDE" });
|
||||
if (body.some((x) => String(x).startsWith("NOBLE_VIP"))) tabs.push({ name: "vip", type: "NOBLE_VIP" });
|
||||
}
|
||||
if (!tabs.length) tabs = [{ name: "frames", type: "AVATAR_FRAME" }];
|
||||
$("#app").innerHTML = `<div class="tabs">${tabs.map((tab, index) => `<button data-tab="${index}" class="${index === 0 ? "active" : ""}">${tab.name}</button>`).join("")}</div><section class="grid" id="props"></section>`;
|
||||
const load = async (tab) => {
|
||||
const res = await get(`${propsBase}?propsType=${tab.type}¤cyType=GOLD`);
|
||||
const root = $("#props");
|
||||
root.innerHTML = "";
|
||||
(res.body || []).forEach((item) => {
|
||||
const tile = document.createElement("button");
|
||||
tile.type = "button";
|
||||
tile.className = "tile";
|
||||
tile.innerHTML = `<img src="${item.cover || ""}" alt=""><span>${item.name || "Item"}</span><span class="tag">Send</span>`;
|
||||
tile.addEventListener("click", () => openSendProps(item, sendEndpoint));
|
||||
root.appendChild(tile);
|
||||
});
|
||||
if (!root.children.length) root.innerHTML = `<div class="empty">No data</div>`;
|
||||
};
|
||||
document.querySelectorAll("[data-tab]").forEach((btn) => btn.addEventListener("click", () => {
|
||||
document.querySelectorAll("[data-tab]").forEach((b) => b.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
load(tabs[Number(btn.dataset.tab)]);
|
||||
}));
|
||||
await load(tabs[0]);
|
||||
}
|
||||
|
||||
function openSendProps(item, endpoint) {
|
||||
const body = document.createElement("div");
|
||||
body.innerHTML = `<div class="form-row"><input id="targetUser" placeholder="Enter user ID"><button class="primary" id="confirmSend">Confirm</button></div>`;
|
||||
$("#confirmSend", body).addEventListener("click", async () => {
|
||||
try {
|
||||
const user = await get(`/user/user-profile/open-search?account=${encodeURIComponent($("#targetUser", body).value.trim())}`);
|
||||
await post(endpoint, { propsId: item.id, acceptUserId: user.body.id });
|
||||
closeModal();
|
||||
toast("Gift delivery successful");
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
});
|
||||
showModal(item.name || "Send", body, true);
|
||||
}
|
||||
|
||||
function renderPolicy() {
|
||||
$("#app").innerHTML = "";
|
||||
}
|
||||
|
||||
async function renderPlatformPolicy() {
|
||||
const teamId = getTeamId();
|
||||
setLoading();
|
||||
const res = await get(`/team/release/policy?id=${teamId}`);
|
||||
const policies = res.body?.policy || [];
|
||||
$("#app").innerHTML = `<section class="card" id="policyList"></section>`;
|
||||
const root = $("#policyList");
|
||||
policies.forEach((item) => root.appendChild(row({
|
||||
title: `Lv.${item.level}`,
|
||||
sub: `Time: ${item.onlineTime || 0}h | Target: ${item.target || 0}`,
|
||||
side: `Host: $${money(item.memberSalary || 0)}\nAgent: $${money(item.ownSalary || 0)}`
|
||||
})));
|
||||
if (!root.children.length) root.innerHTML = `<div class="empty">No policy data available</div>`;
|
||||
}
|
||||
|
||||
async function init() {
|
||||
renderShell();
|
||||
try {
|
||||
await connectApp();
|
||||
const handlers = {
|
||||
teamHistory: renderTeamHistory,
|
||||
adminTeam: renderAdminTeam,
|
||||
rechargeAgency: renderRechargeAgency,
|
||||
message: renderMessage,
|
||||
teamMembers: renderTeamMembers,
|
||||
teamBill: renderTeamBill,
|
||||
inviteProcess: renderInviteProcess,
|
||||
bankCards: renderBankCards,
|
||||
walletAction: renderWalletAction,
|
||||
itemDistribution: renderItemDistribution,
|
||||
platformPolicy: renderPlatformPolicy,
|
||||
policy: renderPolicy
|
||||
};
|
||||
await (handlers[cfg.mode] || renderPolicy)();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
$("#app").innerHTML = `<div class="empty">${error.message || "Failed to load data"}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
13
h5/message/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Message</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Message","mode":"message"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/my-BD-teams/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>My BD Teams</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"My BD Teams","mode":"adminTeam","teamType":"BD","detailType":"BD","memberTitle":"BDs","subMemberLabel":"Agencies"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/my-BDLeader-teams/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>My BD Leader Teams</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"My BD Leader Teams","mode":"adminTeam","teamType":"BD_LEADER","detailType":"BD","memberTitle":"BD leaders","subMemberLabel":"Agencies"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/my-agency-teams/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>My Agency Teams</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"My Agency Teams","mode":"adminTeam","teamType":"AGENCY","detailType":"AGENCY","memberTitle":"Agencies","subMemberLabel":"Hosts"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/my-recharge-agency/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>My Recharge Agency</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"My Recharge Agency","mode":"rechargeAgency"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/platform-policy/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Platform Policy</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Platform Policy","mode":"platformPolicy"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/policy/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>BD Policy</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"BD Policy","mode":"policy"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/search-payee/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Search Payee</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Search Payee","mode":"teamMembers"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/team-bill/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Team Bill</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Team Bill","mode":"teamBill"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/team-member/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Team Member</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Team Member","mode":"teamMembers"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
h5/transfer/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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>Transfer</title>
|
||||
<link rel="stylesheet" href="../logic-pages/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>window.PAGE_CONFIG = {"title":"Transfer","mode":"walletAction","action":"Transfer"};</script>
|
||||
<script src="../logic-pages/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||