chore: publish pending h5 updates
This commit is contained in:
parent
6a6414ddc2
commit
6944cd64f8
165
h5/agency-center/index.back.html
Normal file
165
h5/agency-center/index.back.html
Normal file
@ -0,0 +1,165 @@
|
||||
<!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>Agency Center</title>
|
||||
<link rel="stylesheet" href="./agency-center.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<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>
|
||||
@ -1,165 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<!DOCTYPE html>
|
||||
<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>Agency Center</title>
|
||||
<link rel="stylesheet" href="./agency-center.css">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<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>
|
||||
<script>
|
||||
var search = window.location.search
|
||||
window.location = "https://h5.haiyihy.com/hyapp/agency-center/index.html" + search
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
97
h5/bd-center/index.back.html
Normal file
97
h5/bd-center/index.back.html
Normal file
@ -0,0 +1,97 @@
|
||||
<!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>BD Center</title>
|
||||
<link rel="stylesheet" href="./bd-center.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="bd-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="bd_center">BD Center</h1>
|
||||
<button class="language-button" type="button" data-action="language" aria-label="Language">
|
||||
EN
|
||||
</button>
|
||||
</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-route="/available-income" data-i18n="bd_salary_link">
|
||||
BD salary
|
||||
</button>
|
||||
</div>
|
||||
<div class="salary-amount" id="availableBalance">$0</div>
|
||||
</article>
|
||||
|
||||
<article class="surface-card menu-card">
|
||||
<!-- <button type="button" class="menu-row" data-route="/policy?from=BD">
|
||||
<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" 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" 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" 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" alt="">
|
||||
</button>
|
||||
<!--
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row gift-permission" data-route="/bd-item-distribution" 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" alt="">
|
||||
</button> -->
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
<script type="module" src="./bd-center.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,97 +1,14 @@
|
||||
<!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>BD Center</title>
|
||||
<link rel="stylesheet" href="./bd-center.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="bd-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="bd_center">BD Center</h1>
|
||||
<button class="language-button" type="button" data-action="language" aria-label="Language">
|
||||
EN
|
||||
</button>
|
||||
</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-route="/available-income" data-i18n="bd_salary_link">
|
||||
BD salary
|
||||
</button>
|
||||
</div>
|
||||
<div class="salary-amount" id="availableBalance">$0</div>
|
||||
</article>
|
||||
|
||||
<article class="surface-card menu-card">
|
||||
<!-- <button type="button" class="menu-row" data-route="/policy?from=BD">
|
||||
<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" 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" 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" 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" alt="">
|
||||
</button>
|
||||
<!--
|
||||
<div class="divider"></div>
|
||||
|
||||
<button type="button" class="menu-row gift-permission" data-route="/bd-item-distribution" 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" alt="">
|
||||
</button> -->
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
<script type="module" src="./bd-center.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var search = window.location.search
|
||||
window.location = "https://h5.haiyihy.com/hyapp/bd-center/index.html" + search
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
140
h5/bd-leader-center/index.back.html
Normal file
140
h5/bd-leader-center/index.back.html
Normal file
@ -0,0 +1,140 @@
|
||||
<!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>BD Leader Center</title>
|
||||
<link rel="stylesheet" href="./bd-leader-center.css?v=bdleader-static-1">
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
@ -1,140 +1,14 @@
|
||||
<!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>BD Leader Center</title>
|
||||
<link rel="stylesheet" href="./bd-leader-center.css?v=bdleader-static-1">
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var search = window.location.search
|
||||
window.location = "https://h5.haiyihy.com/hyapp/bd-leader-center/index.html" + search
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
466
h5/hyapp/agency-center/host-list.css
Normal file
466
h5/hyapp/agency-center/host-list.css
Normal file
@ -0,0 +1,466 @@
|
||||
.host-list-page {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.host-list-page[data-loading="true"] .title-bar,
|
||||
.host-list-page[data-loading="true"] .content,
|
||||
.host-list-page[data-loading="true"] .home-indicator {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.host-list-content {
|
||||
gap: 12px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.team-overview-card {
|
||||
display: grid;
|
||||
grid-template-columns: 54px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-height: 96px;
|
||||
padding: 17px 18px;
|
||||
}
|
||||
|
||||
.team-overview-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 50%;
|
||||
background: #e7fbf8;
|
||||
color: #12a99c;
|
||||
}
|
||||
|
||||
.team-overview-icon svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.team-overview-icon path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2.35;
|
||||
}
|
||||
|
||||
.team-overview-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.team-overview-title {
|
||||
overflow: hidden;
|
||||
color: #24282e;
|
||||
font-size: 18px;
|
||||
font-weight: 950;
|
||||
line-height: 1.15;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.team-overview-text {
|
||||
display: -webkit-box;
|
||||
margin-top: 6px;
|
||||
overflow: hidden;
|
||||
color: #8f9196;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.team-overview-count {
|
||||
display: grid;
|
||||
justify-items: end;
|
||||
gap: 4px;
|
||||
color: #8f9196;
|
||||
font-size: 11px;
|
||||
font-weight: 850;
|
||||
line-height: 1.1;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.team-overview-count strong {
|
||||
color: #24282e;
|
||||
font-size: 24px;
|
||||
font-weight: 950;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.team-search-card {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.team-search-form {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.team-search-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
height: 42px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #edf0f1;
|
||||
border-radius: 8px;
|
||||
background: #f8faf9;
|
||||
}
|
||||
|
||||
.team-search-control svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 20px;
|
||||
margin-right: 9px;
|
||||
color: #7f858b;
|
||||
}
|
||||
|
||||
.team-search-control path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2.2;
|
||||
}
|
||||
|
||||
.team-search-control input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: #25282e;
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.team-search-control input::placeholder {
|
||||
color: #a7aab0;
|
||||
}
|
||||
|
||||
.team-search-button,
|
||||
.team-cancel-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 64px;
|
||||
height: 42px;
|
||||
padding: 0 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 950;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.team-search-button {
|
||||
background: var(--green);
|
||||
color: #fff;
|
||||
box-shadow: 0 6px 14px rgba(37, 216, 120, 0.18);
|
||||
}
|
||||
|
||||
.team-cancel-button {
|
||||
background: #f2f4f5;
|
||||
color: #4d525a;
|
||||
}
|
||||
|
||||
.team-member-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.host-member-card {
|
||||
display: grid;
|
||||
grid-template-columns: 28px minmax(0, 1fr) auto 38px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 82px;
|
||||
padding: 13px 12px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.host-member-card.is-self {
|
||||
grid-template-columns: 28px minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.member-index {
|
||||
color: #30343a;
|
||||
font-size: 16px;
|
||||
font-weight: 950;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.member-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex: 0 0 48px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(145deg, #55f2df 0%, #d9fff5 100%);
|
||||
color: #13745f;
|
||||
font-size: 19px;
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
.member-avatar img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.member-copy {
|
||||
min-width: 0;
|
||||
margin-left: 11px;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
overflow: hidden;
|
||||
color: #24282e;
|
||||
font-size: 15px;
|
||||
font-weight: 950;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.member-account {
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
color: #8f9196;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(46px, auto));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.member-stat {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.member-stat-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: #eefbf8;
|
||||
color: #17b99a;
|
||||
}
|
||||
|
||||
.member-stat-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.member-stat-icon path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2.3;
|
||||
}
|
||||
|
||||
.member-stat strong {
|
||||
max-width: 64px;
|
||||
overflow: hidden;
|
||||
color: #24282e;
|
||||
font-size: 12px;
|
||||
font-weight: 950;
|
||||
line-height: 1.15;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.member-remove-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: #f6f7f7;
|
||||
color: #70747b;
|
||||
}
|
||||
|
||||
.member-remove-button svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.member-remove-button path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2.15;
|
||||
}
|
||||
|
||||
.team-list-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 54px;
|
||||
color: #8f9196;
|
||||
font-size: 13px;
|
||||
font-weight: 850;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.team-list-status:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.team-load-sentinel {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.team-confirm-modal[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.team-confirm-modal {
|
||||
position: fixed;
|
||||
z-index: 28;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.team-confirm-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(15, 21, 28, 0.38);
|
||||
}
|
||||
|
||||
.team-confirm-dialog {
|
||||
position: relative;
|
||||
width: min(360px, 100%);
|
||||
padding: 20px 18px 18px;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
box-shadow: 0 18px 44px rgba(23, 31, 42, 0.2);
|
||||
}
|
||||
|
||||
.team-confirm-dialog h2 {
|
||||
margin: 0;
|
||||
color: #24282e;
|
||||
font-size: 19px;
|
||||
font-weight: 950;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.team-confirm-dialog p {
|
||||
margin: 12px 0 0;
|
||||
color: #7d8188;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
line-height: 1.45;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.team-confirm-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.team-confirm-cancel,
|
||||
.team-confirm-submit {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 42px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
.team-confirm-cancel {
|
||||
border: 1px solid #e7eaec;
|
||||
background: #fff;
|
||||
color: #555a62;
|
||||
}
|
||||
|
||||
.team-confirm-submit {
|
||||
background: var(--green);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.team-confirm-submit:disabled {
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .team-search-control svg {
|
||||
margin-right: 0;
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .member-copy {
|
||||
margin-right: 11px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 370px) {
|
||||
.team-overview-card {
|
||||
grid-template-columns: 48px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.team-overview-count {
|
||||
grid-column: 2;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.team-search-form {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.team-cancel-button {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.host-member-card {
|
||||
grid-template-columns: 26px minmax(0, 1fr) 36px;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
grid-column: 2 / 4;
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
108
h5/hyapp/agency-center/host-list.html
Normal file
108
h5/hyapp/agency-center/host-list.html
Normal file
@ -0,0 +1,108 @@
|
||||
<!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 List</title>
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2320" />
|
||||
<link rel="stylesheet" href="./host-list.css?v=20260428-2320" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="agency-center host-list-page" aria-label="Host List" data-i18n-aria="page_label" data-loading="true">
|
||||
<div class="hero-bg" aria-hidden="true"></div>
|
||||
|
||||
<header class="status-bar" aria-hidden="true">
|
||||
<span class="status-time">9:41</span>
|
||||
<span class="status-icons">
|
||||
<span class="cellular"><i></i><i></i><i></i><i></i></span>
|
||||
<span class="wifi"><i></i><i></i><i></i></span>
|
||||
<span class="battery"><i></i></span>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<nav class="title-bar">
|
||||
<button class="back-button" type="button" aria-label="Back" data-i18n-aria="back">
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true">
|
||||
<path d="M20 8 12 16l8 8" />
|
||||
</svg>
|
||||
</button>
|
||||
<h1 data-i18n="title">Host List</h1>
|
||||
<div class="language-switcher">
|
||||
<button class="language-button" type="button" aria-label="Change language" data-i18n-aria="language_button_aria" aria-expanded="false">EN</button>
|
||||
<div class="language-menu" hidden>
|
||||
<button type="button" data-lang="en">EN</button>
|
||||
<button type="button" data-lang="ar">AR</button>
|
||||
<button type="button" data-lang="tr">TR</button>
|
||||
<button type="button" data-lang="id">ID</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="content host-list-content">
|
||||
<section class="card team-overview-card">
|
||||
<div class="team-overview-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 32 32">
|
||||
<path d="M12 14a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9Z" />
|
||||
<path d="M4 27c.8-5.2 3.5-7.8 8-7.8s7.2 2.6 8 7.8" />
|
||||
<path d="M22.5 15.2a3.8 3.8 0 1 0-1.6-7.2M22.3 20c3.1.5 5 2.6 5.7 6.3" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="team-overview-copy">
|
||||
<div class="team-overview-title" data-i18n="host_list">Host List</div>
|
||||
<div class="team-overview-text" data-i18n="team_member_subtitle">Manage hosts in your organization</div>
|
||||
</div>
|
||||
<div class="team-overview-count">
|
||||
<strong id="memberTotal">-</strong>
|
||||
<span data-i18n="members">Members</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card team-search-card">
|
||||
<form class="team-search-form" id="teamSearchForm">
|
||||
<div class="team-search-control">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="m20 20-4.2-4.2M10.8 18a7.2 7.2 0 1 1 0-14.4 7.2 7.2 0 0 1 0 14.4Z" />
|
||||
</svg>
|
||||
<input id="memberSearchInput" type="text" inputmode="numeric" autocomplete="off" placeholder="Enter host ID" data-i18n-placeholder="enter_host_id" />
|
||||
</div>
|
||||
<button class="team-search-button" id="memberSearchButton" type="submit" data-i18n="search">Search</button>
|
||||
<button class="team-cancel-button" id="memberCancelButton" type="button" data-i18n="cancel" hidden>Cancel</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="team-member-list" id="memberList" aria-live="polite"></section>
|
||||
<div class="team-list-status" id="listStatus" role="status"></div>
|
||||
<div class="team-load-sentinel" id="loadMoreSentinel" aria-hidden="true"></div>
|
||||
</main>
|
||||
|
||||
<div class="team-confirm-modal" id="removeConfirmModal" hidden>
|
||||
<div class="team-confirm-backdrop" data-close-remove></div>
|
||||
<section class="team-confirm-dialog" role="dialog" aria-modal="true" aria-labelledby="removeConfirmTitle">
|
||||
<h2 id="removeConfirmTitle" data-i18n="tips">Tips</h2>
|
||||
<p id="removeConfirmText">Confirm remove member?</p>
|
||||
<div class="team-confirm-actions">
|
||||
<button class="team-confirm-cancel" type="button" data-close-remove data-i18n="cancel">Cancel</button>
|
||||
<button class="team-confirm-submit" id="confirmRemoveButton" type="button" data-i18n="confirm">Confirm</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
<div class="home-indicator" aria-hidden="true"></div>
|
||||
|
||||
<div class="loading-mask" id="loadingMask" role="status" aria-live="polite" aria-busy="true">
|
||||
<div class="runner-stick" aria-hidden="true">
|
||||
<span class="runner-head"></span>
|
||||
<span class="runner-body"></span>
|
||||
<span class="runner-arm runner-arm-front"></span>
|
||||
<span class="runner-arm runner-arm-back"></span>
|
||||
<span class="runner-leg runner-leg-front"></span>
|
||||
<span class="runner-leg runner-leg-back"></span>
|
||||
<span class="runner-ground"></span>
|
||||
</div>
|
||||
<div class="loading-copy">loading....</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./host-list.js?v=20260428-2320" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
866
h5/hyapp/agency-center/host-list.js
Normal file
866
h5/hyapp/agency-center/host-list.js
Normal file
@ -0,0 +1,866 @@
|
||||
(function () {
|
||||
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
const PAGE_SIZE = 20;
|
||||
const supportedLanguages = ["en", "ar", "tr", "id"];
|
||||
const languageLabels = {
|
||||
en: "EN",
|
||||
ar: "AR",
|
||||
tr: "TR",
|
||||
id: "ID"
|
||||
};
|
||||
|
||||
const translations = {
|
||||
en: {
|
||||
document_title: "Host List",
|
||||
page_label: "Host List",
|
||||
back: "Back",
|
||||
title: "Host List",
|
||||
host_list: "Host List",
|
||||
team_member_subtitle: "Manage hosts in your organization",
|
||||
members: "Members",
|
||||
language_button_aria: "Change language",
|
||||
search: "Search",
|
||||
cancel: "Cancel",
|
||||
confirm: "Confirm",
|
||||
tips: "Tips",
|
||||
enter_host_id: "Enter host ID",
|
||||
uid_prefix: "UID:",
|
||||
target: "Target",
|
||||
work_time: "Work Time",
|
||||
loading: "Loading",
|
||||
loading_more: "Loading more",
|
||||
no_more_data: "No more data",
|
||||
no_members: "No hosts",
|
||||
no_search_result: "No matching hosts",
|
||||
remove_member: "Remove host",
|
||||
confirm_remove_member_start: "Confirm remove",
|
||||
confirm_remove_member_end: "from your organization?",
|
||||
member_removed: "Host removed",
|
||||
missing_token: "Missing token",
|
||||
missing_token_text: "Open this page from the app or add a token in the URL.",
|
||||
missing_team: "Missing team information",
|
||||
failed_to_load_data: "Failed to load data",
|
||||
failed_to_delete_member: "Failed to remove host",
|
||||
operation_failed: "Operation failed",
|
||||
unknown_user: "Unknown user"
|
||||
},
|
||||
ar: {
|
||||
document_title: "قائمة المضيفين",
|
||||
page_label: "قائمة المضيفين",
|
||||
back: "رجوع",
|
||||
title: "قائمة المضيفين",
|
||||
host_list: "قائمة المضيفين",
|
||||
team_member_subtitle: "إدارة المضيفين في منظمتك",
|
||||
members: "الأعضاء",
|
||||
language_button_aria: "تغيير اللغة",
|
||||
search: "بحث",
|
||||
cancel: "إلغاء",
|
||||
confirm: "تأكيد",
|
||||
tips: "تنبيه",
|
||||
enter_host_id: "أدخل معرف المضيف",
|
||||
uid_prefix: "UID:",
|
||||
target: "الهدف",
|
||||
work_time: "وقت العمل",
|
||||
loading: "جار التحميل",
|
||||
loading_more: "جار تحميل المزيد",
|
||||
no_more_data: "لا توجد بيانات أخرى",
|
||||
no_members: "لا يوجد مضيفون",
|
||||
no_search_result: "لا توجد نتائج مطابقة",
|
||||
remove_member: "إزالة المضيف",
|
||||
confirm_remove_member_start: "تأكيد إزالة",
|
||||
confirm_remove_member_end: "من منظمتك؟",
|
||||
member_removed: "تمت إزالة المضيف",
|
||||
missing_token: "رمز الدخول مفقود",
|
||||
missing_token_text: "افتح هذه الصفحة من التطبيق أو أضف token في الرابط.",
|
||||
missing_team: "معلومات الفريق مفقودة",
|
||||
failed_to_load_data: "فشل تحميل البيانات",
|
||||
failed_to_delete_member: "فشلت إزالة المضيف",
|
||||
operation_failed: "فشلت العملية",
|
||||
unknown_user: "مستخدم غير معروف"
|
||||
},
|
||||
tr: {
|
||||
document_title: "Host Listesi",
|
||||
page_label: "Host Listesi",
|
||||
back: "Geri",
|
||||
title: "Host Listesi",
|
||||
host_list: "Host Listesi",
|
||||
team_member_subtitle: "Organizasyonunuzdaki hostlari yonetin",
|
||||
members: "Uyeler",
|
||||
language_button_aria: "Dili degistir",
|
||||
search: "Ara",
|
||||
cancel: "Iptal",
|
||||
confirm: "Onayla",
|
||||
tips: "Ipuclari",
|
||||
enter_host_id: "Host ID girin",
|
||||
uid_prefix: "UID:",
|
||||
target: "Hedef",
|
||||
work_time: "Calisma Suresi",
|
||||
loading: "Yukleniyor",
|
||||
loading_more: "Daha fazla yukleniyor",
|
||||
no_more_data: "Baska veri yok",
|
||||
no_members: "Host yok",
|
||||
no_search_result: "Eslesen host yok",
|
||||
remove_member: "Hostu kaldir",
|
||||
confirm_remove_member_start: "Kaldirmayi onayla:",
|
||||
confirm_remove_member_end: "organizasyonunuzdan?",
|
||||
member_removed: "Host kaldirildi",
|
||||
missing_token: "Token eksik",
|
||||
missing_token_text: "Bu sayfayi uygulamadan acin veya URL'ye token ekleyin.",
|
||||
missing_team: "Takim bilgisi eksik",
|
||||
failed_to_load_data: "Veri yuklenemedi",
|
||||
failed_to_delete_member: "Host kaldirilamadi",
|
||||
operation_failed: "Islem basarisiz",
|
||||
unknown_user: "Bilinmeyen kullanici"
|
||||
},
|
||||
id: {
|
||||
document_title: "Daftar Host",
|
||||
page_label: "Daftar Host",
|
||||
back: "Kembali",
|
||||
title: "Daftar Host",
|
||||
host_list: "Daftar Host",
|
||||
team_member_subtitle: "Kelola host di organisasi Anda",
|
||||
members: "Anggota",
|
||||
language_button_aria: "Ubah bahasa",
|
||||
search: "Cari",
|
||||
cancel: "Batal",
|
||||
confirm: "Konfirmasi",
|
||||
tips: "Tips",
|
||||
enter_host_id: "Masukkan ID host",
|
||||
uid_prefix: "UID:",
|
||||
target: "Target",
|
||||
work_time: "Waktu Kerja",
|
||||
loading: "Memuat",
|
||||
loading_more: "Memuat lagi",
|
||||
no_more_data: "Tidak ada data lagi",
|
||||
no_members: "Tidak ada host",
|
||||
no_search_result: "Tidak ada host yang cocok",
|
||||
remove_member: "Hapus host",
|
||||
confirm_remove_member_start: "Konfirmasi hapus",
|
||||
confirm_remove_member_end: "dari organisasi Anda?",
|
||||
member_removed: "Host dihapus",
|
||||
missing_token: "Token tidak ditemukan",
|
||||
missing_token_text: "Buka halaman ini dari aplikasi atau tambahkan token di URL.",
|
||||
missing_team: "Informasi tim tidak ditemukan",
|
||||
failed_to_load_data: "Gagal memuat data",
|
||||
failed_to_delete_member: "Gagal menghapus host",
|
||||
operation_failed: "Operasi gagal",
|
||||
unknown_user: "Pengguna tidak dikenal"
|
||||
}
|
||||
};
|
||||
|
||||
const params = readURLParams();
|
||||
let currentLang = normalizeLanguage(params.get("lang"));
|
||||
let currentMessages = translations[currentLang] || translations.en;
|
||||
let currentTeamId = params.get("teamId") || params.get("id") || "";
|
||||
let currentTotalMembers = parseOptionalNumber(params.get("members"));
|
||||
let currentProfile = null;
|
||||
let members = [];
|
||||
let displayMembers = [];
|
||||
let lastId = "";
|
||||
let noMoreData = false;
|
||||
let loadingList = false;
|
||||
let loadingMore = false;
|
||||
let searchActive = false;
|
||||
let pendingRemoveMember = null;
|
||||
let loadObserver = null;
|
||||
|
||||
function readURLParams() {
|
||||
const result = new URLSearchParams(window.location.search);
|
||||
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||||
const hashParams = new URLSearchParams(hashQuery);
|
||||
hashParams.forEach((value, key) => {
|
||||
if (!result.has(key)) result.set(key, value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function readRawParam(name) {
|
||||
const queries = [window.location.search.replace(/^\?/, "")];
|
||||
if (window.location.hash.includes("?")) queries.push(window.location.hash.split("?")[1]);
|
||||
|
||||
for (const query of queries) {
|
||||
for (const part of query.split("&")) {
|
||||
if (!part) continue;
|
||||
const equalIndex = part.indexOf("=");
|
||||
const rawKey = equalIndex >= 0 ? part.slice(0, equalIndex) : part;
|
||||
const rawValue = equalIndex >= 0 ? part.slice(equalIndex + 1) : "";
|
||||
let decodedKey = "";
|
||||
try {
|
||||
decodedKey = decodeURIComponent(rawKey);
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
if (decodedKey !== name) continue;
|
||||
try {
|
||||
return decodeURIComponent(rawValue.replace(/\+/g, "%2B"));
|
||||
} catch (error) {
|
||||
return rawValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function normalizeLanguage(lang) {
|
||||
const value = String(lang || "en").toLowerCase();
|
||||
return supportedLanguages.includes(value) ? value : "en";
|
||||
}
|
||||
|
||||
function t(key, fallback) {
|
||||
return currentMessages[key] || translations.en[key] || fallback || key;
|
||||
}
|
||||
|
||||
function parseOptionalNumber(value) {
|
||||
if (value === null || value === undefined || value === "") return null;
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number : null;
|
||||
}
|
||||
|
||||
function apiBaseURL() {
|
||||
const override = window.HYAPP_API_BASE_URL || params.get("apiBase");
|
||||
if (override) return String(override).replace(/\/+$/, "");
|
||||
return API_BASE_URL.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function normalizeAuthorization(token) {
|
||||
const value = String(token || "").trim();
|
||||
if (!value) return "";
|
||||
return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`;
|
||||
}
|
||||
|
||||
function authHeadersFromToken(token) {
|
||||
return {
|
||||
Authorization: normalizeAuthorization(token),
|
||||
"req-lang": currentLang,
|
||||
"req-client": "H5",
|
||||
"req-version": "V2",
|
||||
"req-zone": Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai",
|
||||
"req-app-intel": "version=1.0.0;build=1;model=H5;sysVersion=Web;channel=Web",
|
||||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU"
|
||||
};
|
||||
}
|
||||
|
||||
async function requestJSON(path, options = {}) {
|
||||
const token = readRawParam("token");
|
||||
if (!token) throw new Error(t("missing_token", "Missing token"));
|
||||
|
||||
const headers = authHeadersFromToken(token);
|
||||
const method = options.method || "GET";
|
||||
const hasBody = options.body !== undefined;
|
||||
if (hasBody) headers["Content-Type"] = "application/json";
|
||||
|
||||
const response = await fetch(new URL(path, `${apiBaseURL()}/`).toString(), {
|
||||
method,
|
||||
cache: "no-store",
|
||||
headers,
|
||||
body: hasBody ? JSON.stringify(options.body) : undefined
|
||||
});
|
||||
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.body ?? data.data ?? null;
|
||||
}
|
||||
|
||||
function buildPageURL(target) {
|
||||
const url = new URL(target, window.location.href);
|
||||
const nextParams = new URLSearchParams(url.search);
|
||||
|
||||
params.forEach((value, key) => {
|
||||
if (!nextParams.has(key)) nextParams.set(key, value);
|
||||
});
|
||||
|
||||
const token = readRawParam("token");
|
||||
if (token) nextParams.set("token", token);
|
||||
if (currentLang) nextParams.set("lang", currentLang);
|
||||
if (currentTeamId) nextParams.set("teamId", currentTeamId);
|
||||
|
||||
url.search = nextParams.toString();
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function setLoading(isLoading) {
|
||||
const root = document.querySelector(".host-list-page");
|
||||
const loadingMask = document.querySelector("#loadingMask");
|
||||
if (root) root.dataset.loading = isLoading ? "true" : "false";
|
||||
if (loadingMask) {
|
||||
loadingMask.hidden = !isLoading;
|
||||
loadingMask.setAttribute("aria-busy", String(isLoading));
|
||||
}
|
||||
}
|
||||
|
||||
function renderLanguageControls() {
|
||||
const languageButton = document.querySelector(".language-button");
|
||||
if (languageButton) {
|
||||
languageButton.textContent = languageLabels[currentLang];
|
||||
languageButton.setAttribute("aria-label", t("language_button_aria"));
|
||||
}
|
||||
|
||||
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
|
||||
const isActive = button.dataset.lang === currentLang;
|
||||
button.classList.toggle("is-active", isActive);
|
||||
button.setAttribute("aria-pressed", String(isActive));
|
||||
});
|
||||
}
|
||||
|
||||
function renderMessages() {
|
||||
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
||||
const value = currentMessages[node.dataset.i18n];
|
||||
if (value) node.textContent = value;
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-i18n-aria]").forEach((node) => {
|
||||
const value = currentMessages[node.dataset.i18nAria];
|
||||
if (value) node.setAttribute("aria-label", value);
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => {
|
||||
const value = currentMessages[node.dataset.i18nPlaceholder];
|
||||
if (value) node.setAttribute("placeholder", value);
|
||||
});
|
||||
}
|
||||
|
||||
function applyLanguage(lang) {
|
||||
currentLang = normalizeLanguage(lang);
|
||||
currentMessages = translations[currentLang] || translations.en;
|
||||
document.documentElement.lang = currentLang;
|
||||
document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr";
|
||||
document.title = t("document_title");
|
||||
renderMessages();
|
||||
renderLanguageControls();
|
||||
renderTotalMembers();
|
||||
renderList();
|
||||
renderRemoveConfirm();
|
||||
}
|
||||
|
||||
function setLanguageMenuOpen(open) {
|
||||
const languageButton = document.querySelector(".language-button");
|
||||
const languageMenu = document.querySelector(".language-menu");
|
||||
if (!languageButton || !languageMenu) return;
|
||||
languageMenu.hidden = !open;
|
||||
languageButton.setAttribute("aria-expanded", String(open));
|
||||
}
|
||||
|
||||
function pickTeamInfo(body) {
|
||||
return body?.teamProfile || body || null;
|
||||
}
|
||||
|
||||
function pickTeamId(teamInfo) {
|
||||
return teamInfo?.id || teamInfo?.teamId || teamInfo?.teamProfile?.id || "";
|
||||
}
|
||||
|
||||
function pickMemberCount(body) {
|
||||
if (typeof body === "number") return body;
|
||||
if (body && typeof body === "object") {
|
||||
return body.memberQuantity ?? body.count ?? body.total ?? body.memberCount ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function pickRecords(body) {
|
||||
if (Array.isArray(body)) return body;
|
||||
if (Array.isArray(body?.records)) return body.records;
|
||||
if (Array.isArray(body?.list)) return body.list;
|
||||
if (Array.isArray(body?.items)) return body.items;
|
||||
return [];
|
||||
}
|
||||
|
||||
function pickMemberProfile(member) {
|
||||
return member?.userProfile || member?.memberProfile || member?.profile || member?.user || {};
|
||||
}
|
||||
|
||||
function pickAccount(profile, member) {
|
||||
return profile?.ownSpecialId?.account || profile?.account || member?.account || member?.userAccount || member?.userId || member?.id || "-";
|
||||
}
|
||||
|
||||
function pickMemberName(profile, member) {
|
||||
return profile?.userNickname || profile?.name || member?.userNickname || member?.name || t("unknown_user");
|
||||
}
|
||||
|
||||
function collectUserIdentifiers(profile = {}) {
|
||||
return [
|
||||
profile?.id,
|
||||
profile?.account,
|
||||
profile?.ownSpecialId?.account
|
||||
]
|
||||
.filter((value) => value !== null && value !== undefined && value !== "")
|
||||
.map((value) => String(value));
|
||||
}
|
||||
|
||||
function collectMemberUserIdentifiers(member = {}) {
|
||||
const profile = pickMemberProfile(member);
|
||||
return [
|
||||
profile?.id,
|
||||
profile?.account,
|
||||
profile?.ownSpecialId?.account,
|
||||
member?.userId,
|
||||
member?.userAccount,
|
||||
member?.account
|
||||
]
|
||||
.filter((value) => value !== null && value !== undefined && value !== "")
|
||||
.map((value) => String(value));
|
||||
}
|
||||
|
||||
function isCurrentUserMember(member) {
|
||||
const currentIds = new Set(collectUserIdentifiers(currentProfile));
|
||||
if (!currentIds.size) return false;
|
||||
return collectMemberUserIdentifiers(member).some((value) => currentIds.has(value));
|
||||
}
|
||||
|
||||
function formatCountValue(value) {
|
||||
if (value === null || value === undefined || value === "") return "-";
|
||||
const number = Number(value);
|
||||
if (Number.isFinite(number) && Number.isInteger(number)) return String(number);
|
||||
if (Number.isFinite(number)) return String(Number(number.toFixed(2)));
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function normalizeMember(member) {
|
||||
const profile = pickMemberProfile(member);
|
||||
const name = pickMemberName(profile, member);
|
||||
return {
|
||||
raw: member,
|
||||
id: member?.id || member?.memberId || member?.userId || profile?.id || "",
|
||||
name,
|
||||
avatar: profile?.userAvatar || member?.userAvatar || member?.avatar || "",
|
||||
account: pickAccount(profile, member),
|
||||
target: member?.targetFormat ?? member?.target ?? member?.giftTarget ?? member?.giftValue ?? "-",
|
||||
time: member?.timeHours ?? member?.onlineTime ?? member?.workTime ?? "-",
|
||||
isSelf: isCurrentUserMember(member)
|
||||
};
|
||||
}
|
||||
|
||||
function createSvg(paths) {
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("viewBox", "0 0 24 24");
|
||||
svg.setAttribute("aria-hidden", "true");
|
||||
paths.forEach((pathValue) => {
|
||||
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
path.setAttribute("d", pathValue);
|
||||
svg.appendChild(path);
|
||||
});
|
||||
return svg;
|
||||
}
|
||||
|
||||
function createMemberAvatar(member) {
|
||||
const avatar = document.createElement("div");
|
||||
avatar.className = "member-avatar";
|
||||
|
||||
const fallback = document.createElement("span");
|
||||
fallback.textContent = (member.name || t("unknown_user")).slice(0, 1).toUpperCase();
|
||||
avatar.appendChild(fallback);
|
||||
|
||||
if (member.avatar) {
|
||||
const image = document.createElement("img");
|
||||
image.src = member.avatar;
|
||||
image.alt = member.name;
|
||||
image.addEventListener("error", () => {
|
||||
image.remove();
|
||||
});
|
||||
avatar.appendChild(image);
|
||||
}
|
||||
|
||||
return avatar;
|
||||
}
|
||||
|
||||
function createStat(iconPaths, label, value) {
|
||||
const stat = document.createElement("div");
|
||||
stat.className = "member-stat";
|
||||
stat.setAttribute("aria-label", `${label}: ${value}`);
|
||||
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "member-stat-icon";
|
||||
icon.appendChild(createSvg(iconPaths));
|
||||
|
||||
const text = document.createElement("strong");
|
||||
text.textContent = formatCountValue(value);
|
||||
text.title = `${label}: ${formatCountValue(value)}`;
|
||||
|
||||
stat.appendChild(icon);
|
||||
stat.appendChild(text);
|
||||
return stat;
|
||||
}
|
||||
|
||||
function createMemberCard(member, index) {
|
||||
const normalized = normalizeMember(member);
|
||||
const card = document.createElement("article");
|
||||
card.className = "host-member-card";
|
||||
if (normalized.isSelf) card.classList.add("is-self");
|
||||
|
||||
const indexNode = document.createElement("div");
|
||||
indexNode.className = "member-index";
|
||||
indexNode.textContent = String(index + 1);
|
||||
|
||||
const user = document.createElement("div");
|
||||
user.className = "member-user";
|
||||
user.appendChild(createMemberAvatar(normalized));
|
||||
|
||||
const copy = document.createElement("div");
|
||||
copy.className = "member-copy";
|
||||
|
||||
const name = document.createElement("div");
|
||||
name.className = "member-name";
|
||||
name.textContent = normalized.name;
|
||||
|
||||
const account = document.createElement("div");
|
||||
account.className = "member-account";
|
||||
account.textContent = `${t("uid_prefix")} ${normalized.account}`.trim();
|
||||
|
||||
copy.appendChild(name);
|
||||
copy.appendChild(account);
|
||||
user.appendChild(copy);
|
||||
|
||||
const stats = document.createElement("div");
|
||||
stats.className = "member-stats";
|
||||
stats.appendChild(createStat(["M12 3 14.4 8.1 20 8.8 15.8 12.7 16.9 18.2 12 15.4 7.1 18.2 8.2 12.7 4 8.8 9.6 8.1Z"], t("target"), normalized.target));
|
||||
stats.appendChild(createStat(["M12 6v6l4 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"], t("work_time"), normalized.time));
|
||||
|
||||
card.appendChild(indexNode);
|
||||
card.appendChild(user);
|
||||
card.appendChild(stats);
|
||||
if (!normalized.isSelf) {
|
||||
const remove = document.createElement("button");
|
||||
remove.type = "button";
|
||||
remove.className = "member-remove-button";
|
||||
remove.setAttribute("aria-label", t("remove_member"));
|
||||
remove.appendChild(createSvg(["M4 7h16M10 11v6M14 11v6M6 7l1 14h10l1-14M9 7l1-3h4l1 3"]));
|
||||
remove.addEventListener("click", () => {
|
||||
setRemoveConfirmOpen(true, normalized);
|
||||
});
|
||||
card.appendChild(remove);
|
||||
}
|
||||
return card;
|
||||
}
|
||||
|
||||
function replaceNodeChildren(node, children) {
|
||||
while (node.firstChild) node.removeChild(node.firstChild);
|
||||
children.forEach((child) => node.appendChild(child));
|
||||
}
|
||||
|
||||
function setListStatus(message) {
|
||||
const status = document.querySelector("#listStatus");
|
||||
if (status) status.textContent = message || "";
|
||||
}
|
||||
|
||||
function renderTotalMembers() {
|
||||
const totalNode = document.querySelector("#memberTotal");
|
||||
if (!totalNode) return;
|
||||
const number = Number(currentTotalMembers);
|
||||
totalNode.textContent = Number.isFinite(number) ? String(Math.max(0, Math.trunc(number))) : "-";
|
||||
}
|
||||
|
||||
function renderSearchState() {
|
||||
const searchButton = document.querySelector("#memberSearchButton");
|
||||
const cancelButton = document.querySelector("#memberCancelButton");
|
||||
if (searchButton) searchButton.hidden = false;
|
||||
if (cancelButton) cancelButton.hidden = !searchActive;
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const list = document.querySelector("#memberList");
|
||||
if (!list) return;
|
||||
const normalizedItems = Array.isArray(displayMembers) ? displayMembers : [];
|
||||
replaceNodeChildren(list, normalizedItems.map((member, index) => createMemberCard(member, index)));
|
||||
renderSearchState();
|
||||
|
||||
if (loadingList) {
|
||||
setListStatus(`${t("loading")}...`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadingMore) {
|
||||
setListStatus(`${t("loading_more")}...`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!normalizedItems.length) {
|
||||
setListStatus(searchActive ? t("no_search_result") : t("no_members"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!searchActive && noMoreData) {
|
||||
setListStatus(t("no_more_data"));
|
||||
return;
|
||||
}
|
||||
|
||||
setListStatus("");
|
||||
}
|
||||
|
||||
function renderRemoveConfirm() {
|
||||
const text = document.querySelector("#removeConfirmText");
|
||||
if (!text) return;
|
||||
if (!pendingRemoveMember) {
|
||||
text.textContent = "";
|
||||
return;
|
||||
}
|
||||
text.textContent = `${t("confirm_remove_member_start")} ${pendingRemoveMember.account || pendingRemoveMember.name} ${t("confirm_remove_member_end")}`;
|
||||
}
|
||||
|
||||
function setRemoveConfirmOpen(open, member = null) {
|
||||
const modal = document.querySelector("#removeConfirmModal");
|
||||
if (!modal) return;
|
||||
pendingRemoveMember = open ? member : null;
|
||||
modal.hidden = !open;
|
||||
document.body.classList.toggle("daily-modal-open", open);
|
||||
renderRemoveConfirm();
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.querySelector("#toast");
|
||||
if (!toast) return;
|
||||
toast.textContent = message;
|
||||
toast.hidden = false;
|
||||
window.clearTimeout(showToast.timer);
|
||||
showToast.timer = window.setTimeout(() => {
|
||||
toast.hidden = true;
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
async function ensureTeamId() {
|
||||
if (currentTeamId) return currentTeamId;
|
||||
const body = await requestJSON("/team/entry");
|
||||
currentTeamId = pickTeamId(pickTeamInfo(body));
|
||||
return currentTeamId;
|
||||
}
|
||||
|
||||
async function fetchCurrentProfile() {
|
||||
currentProfile = await requestJSON("/team/member/profile");
|
||||
return currentProfile;
|
||||
}
|
||||
|
||||
async function fetchMemberCount(teamId) {
|
||||
if (!teamId) return;
|
||||
try {
|
||||
const query = new URLSearchParams({ id: String(teamId) });
|
||||
const body = await requestJSON(`/team/members/count?${query.toString()}`);
|
||||
currentTotalMembers = pickMemberCount(body);
|
||||
renderTotalMembers();
|
||||
} catch (error) {
|
||||
console.error("Failed to load host count:", error);
|
||||
renderTotalMembers();
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMembers(loadMore = false) {
|
||||
if (!currentTeamId || loadingList || loadingMore || searchActive) return;
|
||||
if (loadMore && noMoreData) return;
|
||||
|
||||
try {
|
||||
if (loadMore) {
|
||||
loadingMore = true;
|
||||
} else {
|
||||
loadingList = true;
|
||||
lastId = "";
|
||||
noMoreData = false;
|
||||
members = [];
|
||||
displayMembers = [];
|
||||
}
|
||||
renderList();
|
||||
|
||||
const query = new URLSearchParams({ id: String(currentTeamId) });
|
||||
if (loadMore && lastId) query.set("lastId", String(lastId));
|
||||
const body = await requestJSON(`/team/members?${query.toString()}`);
|
||||
const nextMembers = pickRecords(body);
|
||||
|
||||
if (loadMore) {
|
||||
members = members.concat(nextMembers);
|
||||
} else {
|
||||
members = nextMembers;
|
||||
}
|
||||
|
||||
displayMembers = members.slice();
|
||||
if (nextMembers.length > 0) {
|
||||
lastId = nextMembers[nextMembers.length - 1]?.id || lastId;
|
||||
}
|
||||
if (nextMembers.length < PAGE_SIZE) noMoreData = true;
|
||||
} catch (error) {
|
||||
console.error("Failed to load hosts:", error);
|
||||
showToast(error.message || t("failed_to_load_data"));
|
||||
} finally {
|
||||
loadingList = false;
|
||||
loadingMore = false;
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
async function searchMembers() {
|
||||
const input = document.querySelector("#memberSearchInput");
|
||||
const account = String(input?.value || "").trim();
|
||||
if (!account) {
|
||||
cancelSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
searchActive = true;
|
||||
loadingList = true;
|
||||
displayMembers = [];
|
||||
renderList();
|
||||
|
||||
try {
|
||||
const query = new URLSearchParams({ account });
|
||||
const body = await requestJSON(`/team/search-members?${query.toString()}`);
|
||||
displayMembers = pickRecords(body);
|
||||
} catch (error) {
|
||||
console.error("Failed to search hosts:", error);
|
||||
showToast(error.message || t("failed_to_load_data"));
|
||||
displayMembers = [];
|
||||
} finally {
|
||||
loadingList = false;
|
||||
renderList();
|
||||
}
|
||||
}
|
||||
|
||||
function cancelSearch() {
|
||||
const input = document.querySelector("#memberSearchInput");
|
||||
if (input) input.value = "";
|
||||
searchActive = false;
|
||||
displayMembers = members.slice();
|
||||
renderList();
|
||||
}
|
||||
|
||||
async function removeCurrentMember() {
|
||||
if (!pendingRemoveMember?.id) return;
|
||||
if (pendingRemoveMember.isSelf) return;
|
||||
const button = document.querySelector("#confirmRemoveButton");
|
||||
if (button) button.disabled = true;
|
||||
|
||||
try {
|
||||
await requestJSON("/team-member/client/removeMemberTeam", {
|
||||
method: "POST",
|
||||
body: {
|
||||
ids: [pendingRemoveMember.id]
|
||||
}
|
||||
});
|
||||
|
||||
const removedId = String(pendingRemoveMember.id);
|
||||
members = members.filter((member) => String(normalizeMember(member).id) !== removedId);
|
||||
displayMembers = displayMembers.filter((member) => String(normalizeMember(member).id) !== removedId);
|
||||
if (Number.isFinite(Number(currentTotalMembers))) {
|
||||
currentTotalMembers = Math.max(0, Number(currentTotalMembers) - 1);
|
||||
}
|
||||
renderTotalMembers();
|
||||
renderList();
|
||||
setRemoveConfirmOpen(false);
|
||||
showToast(t("member_removed"));
|
||||
} catch (error) {
|
||||
console.error("Failed to remove host:", error);
|
||||
showToast(error.message || t("failed_to_delete_member"));
|
||||
} finally {
|
||||
if (button) button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
window.location.href = buildPageURL("./index.html");
|
||||
}
|
||||
|
||||
function setupLoadObserver() {
|
||||
const sentinel = document.querySelector("#loadMoreSentinel");
|
||||
const root = document.querySelector(".host-list-page");
|
||||
if (!sentinel || !root || !("IntersectionObserver" in window)) return;
|
||||
|
||||
if (loadObserver) loadObserver.disconnect();
|
||||
loadObserver = new IntersectionObserver((entries) => {
|
||||
if (entries[0]?.isIntersecting && !searchActive && !loadingList && !loadingMore && !noMoreData) {
|
||||
fetchMembers(true);
|
||||
}
|
||||
}, {
|
||||
root,
|
||||
rootMargin: "140px 0px"
|
||||
});
|
||||
loadObserver.observe(sentinel);
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
document.querySelector(".back-button")?.addEventListener("click", goBack);
|
||||
|
||||
document.querySelector(".language-button")?.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
const languageMenu = document.querySelector(".language-menu");
|
||||
setLanguageMenuOpen(Boolean(languageMenu?.hidden));
|
||||
});
|
||||
|
||||
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
|
||||
button.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
setLanguageMenuOpen(false);
|
||||
applyLanguage(button.dataset.lang);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector("#teamSearchForm")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
searchMembers();
|
||||
});
|
||||
|
||||
document.querySelector("#memberCancelButton")?.addEventListener("click", cancelSearch);
|
||||
document.querySelector("#confirmRemoveButton")?.addEventListener("click", removeCurrentMember);
|
||||
|
||||
document.querySelectorAll("[data-close-remove]").forEach((node) => {
|
||||
node.addEventListener("click", () => {
|
||||
setRemoveConfirmOpen(false);
|
||||
});
|
||||
});
|
||||
|
||||
const root = document.querySelector(".host-list-page");
|
||||
root?.addEventListener("scroll", () => {
|
||||
if (searchActive || loadingList || loadingMore || noMoreData) return;
|
||||
const distanceToBottom = root.scrollHeight - root.scrollTop - root.clientHeight;
|
||||
if (distanceToBottom < 140) fetchMembers(true);
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener("click", () => {
|
||||
setLanguageMenuOpen(false);
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
setRemoveConfirmOpen(false);
|
||||
setLanguageMenuOpen(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function initializePage() {
|
||||
setLoading(true);
|
||||
applyLanguage(currentLang);
|
||||
bindEvents();
|
||||
setupLoadObserver();
|
||||
renderTotalMembers();
|
||||
|
||||
if (!readRawParam("token")) {
|
||||
setLoading(false);
|
||||
setListStatus(t("missing_token_text"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ensureTeamId();
|
||||
if (!currentTeamId) throw new Error(t("missing_team"));
|
||||
await fetchCurrentProfile();
|
||||
|
||||
await Promise.allSettled([
|
||||
fetchMemberCount(currentTeamId),
|
||||
fetchMembers(false)
|
||||
]).then((results) => {
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") console.warn("Host list request failed:", result.reason);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize host list:", error);
|
||||
showToast(error.message || t("failed_to_load_data"));
|
||||
setListStatus(error.message || t("failed_to_load_data"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
initializePage();
|
||||
})();
|
||||
235
h5/hyapp/agency-center/index.html
Normal file
235
h5/hyapp/agency-center/index.html
Normal file
@ -0,0 +1,235 @@
|
||||
<!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 Center</title>
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2320" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="agency-center" aria-label="Agency Center" data-i18n-aria="page_label" data-loading="true" data-view="loading">
|
||||
<div class="hero-bg" aria-hidden="true"></div>
|
||||
|
||||
<header class="status-bar" aria-hidden="true">
|
||||
<span class="status-time">9:41</span>
|
||||
<span class="status-icons">
|
||||
<span class="cellular"><i></i><i></i><i></i><i></i></span>
|
||||
<span class="wifi"><i></i><i></i><i></i></span>
|
||||
<span class="battery"><i></i></span>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<nav class="title-bar">
|
||||
<button class="back-button" type="button" aria-label="Back" data-i18n-aria="back">
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true">
|
||||
<path d="M20 8 12 16l8 8" />
|
||||
</svg>
|
||||
</button>
|
||||
<h1 data-i18n="title">Agency Center</h1>
|
||||
<div class="language-switcher">
|
||||
<button class="language-button" type="button" aria-label="Change language" data-i18n-aria="language_button_aria" aria-expanded="false">EN</button>
|
||||
<div class="language-menu" hidden>
|
||||
<button type="button" data-lang="en">EN</button>
|
||||
<button type="button" data-lang="ar">AR</button>
|
||||
<button type="button" data-lang="tr">TR</button>
|
||||
<button type="button" data-lang="id">ID</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="content">
|
||||
<section class="card profile-card agent-only">
|
||||
<div class="avatar-shell">
|
||||
<img class="avatar-image" id="profileAvatar" alt="" hidden />
|
||||
<div class="avatar-fallback" id="profileAvatarFallback" aria-hidden="true">A</div>
|
||||
</div>
|
||||
<div class="profile-copy">
|
||||
<div class="name" id="profileName">Agency</div>
|
||||
<div class="meta" id="profileUid">UID: -</div>
|
||||
<div class="agency-line" id="bdName">BD: -</div>
|
||||
</div>
|
||||
<button class="profile-message-button" type="button" aria-label="Open messages" data-i18n-aria="open_messages">
|
||||
<svg viewBox="0 0 28 28" aria-hidden="true">
|
||||
<path d="M5 8.5A3.5 3.5 0 0 1 8.5 5h11A3.5 3.5 0 0 1 23 8.5v7A3.5 3.5 0 0 1 19.5 19H12l-5 4v-4.5A3.5 3.5 0 0 1 5 15.3Z" />
|
||||
<path d="M9 10h10M9 14h6" />
|
||||
</svg>
|
||||
<span class="message-unread-dot" id="messageUnreadDot" hidden></span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="card access-card" id="accessCard" hidden>
|
||||
<div class="access-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 32 32">
|
||||
<path d="M8 14h16M10 14v12M22 14v12M13 14V9h6v5M6 26h20" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="access-copy">
|
||||
<div class="access-title" data-i18n="not_agency_title">Agency access unavailable</div>
|
||||
<div class="access-text" id="accessText" data-i18n="not_agency_text">This account is not an agency account.</div>
|
||||
</div>
|
||||
<a class="access-action" href="/apply" data-route="/apply" data-i18n="apply_now">Apply</a>
|
||||
</section>
|
||||
|
||||
<section class="card metrics-card agent-only">
|
||||
<div class="metric">
|
||||
<div class="label" data-i18n="streaming_duration">Work Time (min)</div>
|
||||
<div class="value" id="streamingDuration">0</div>
|
||||
</div>
|
||||
<div class="vertical-line" aria-hidden="true"></div>
|
||||
<div class="metric gift-metric">
|
||||
<div class="label" data-i18n="target">Target</div>
|
||||
<button class="gift-value" type="button" aria-live="polite" aria-label="Open daily data" data-i18n-aria="open_daily_data">
|
||||
<span class="amount-text" id="currentGiftValue">0</span>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card salary-card agent-only">
|
||||
<div class="salary-head">
|
||||
<div class="salary-title">
|
||||
<span data-i18n="salary">Salary</span>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true">
|
||||
<circle cx="10" cy="10" r="8" />
|
||||
<path d="M10 9v5M10 6.3v.2" />
|
||||
</svg>
|
||||
</div>
|
||||
<a class="salary-link" href="../withdraw-exchange/index.html" data-route="../withdraw-exchange/index.html" aria-label="Open withdraw and exchange page" data-i18n-aria="open_withdraw_exchange">
|
||||
<span data-i18n="withdraw_exchange">Withdraw/Exchange</span>
|
||||
<span class="chevron">›</span>
|
||||
</a>
|
||||
</div>
|
||||
<a class="salary-value" href="../withdraw-exchange/index.html" data-route="../withdraw-exchange/index.html" aria-label="Open withdraw and exchange page" data-i18n-aria="open_withdraw_exchange">
|
||||
<span class="salary-currency" aria-hidden="true">$</span>
|
||||
<span class="amount-text" id="currentSalary">0.00</span>
|
||||
<span class="chevron">›</span>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section class="card policy-entry-card team-member-entry-card agent-only">
|
||||
<a class="policy-entry-link" href="./host-list.html?source=agency-center" data-route="./host-list.html?source=agency-center" data-team-member="true" aria-label="Open host list" data-i18n-aria="open_host_list">
|
||||
<span class="policy-entry-left">
|
||||
<svg viewBox="0 0 28 28" aria-hidden="true">
|
||||
<path d="M10.5 13a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" />
|
||||
<path d="M3.5 23c.7-4.2 3.1-6.3 7-6.3s6.3 2.1 7 6.3" />
|
||||
<path d="M20 13.5a3.2 3.2 0 1 0-1.3-6.1M19.7 17c2.6.4 4.2 2.2 4.8 5.3" />
|
||||
</svg>
|
||||
<span data-i18n="host_list">Host List</span>
|
||||
</span>
|
||||
<span class="team-member-entry-right">
|
||||
<span id="teamMemberCount">-</span>
|
||||
<span class="chevron">›</span>
|
||||
</span>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section class="card policy-entry-card agent-only">
|
||||
<a class="policy-entry-link" href="../hosts-bill/index.html?source=agency-center" data-route="../hosts-bill/index.html?source=agency-center" data-team-bill="true" aria-label="Open salary bills" data-i18n-aria="open_team_bill">
|
||||
<span class="policy-entry-left">
|
||||
<svg viewBox="0 0 28 28" aria-hidden="true">
|
||||
<path d="M7 5h14v18H7Z" />
|
||||
<path d="M11 10h7M11 14h5M11 18h6" />
|
||||
</svg>
|
||||
<span data-i18n="team_bill">Salary Bills</span>
|
||||
</span>
|
||||
<span class="chevron">›</span>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section class="card policy-entry-card agent-only">
|
||||
<a class="policy-entry-link" href="../host-center/policy.html?from=agencycenter" data-route="../host-center/policy.html?from=agencycenter" data-policy="team">
|
||||
<span class="policy-entry-left">
|
||||
<svg viewBox="0 0 28 28" aria-hidden="true">
|
||||
<path d="M8 5h10l3 3v15H8Z" />
|
||||
<path d="M18 5v5h4M11 14h7M11 18h6" />
|
||||
</svg>
|
||||
<span data-i18n="platform_policy">Agency Policy</span>
|
||||
</span>
|
||||
<span class="chevron">›</span>
|
||||
</a>
|
||||
<div class="policy-entry-divider" aria-hidden="true"></div>
|
||||
<button class="policy-entry-link invite-help-button" type="button" aria-label="How to invite host" data-i18n-aria="open_invite_help">
|
||||
<span class="policy-entry-left">
|
||||
<span class="question-icon" aria-hidden="true">?</span>
|
||||
<span data-i18n="how_to_invite_member">How to invite host</span>
|
||||
</span>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="daily-modal" id="dailyModal" hidden>
|
||||
<div class="daily-backdrop" data-close-daily></div>
|
||||
<section class="daily-sheet" role="dialog" aria-modal="true" aria-labelledby="dailyTitle">
|
||||
<div class="daily-sheet-head">
|
||||
<h2 id="dailyTitle" data-i18n="daily_details">Daily data</h2>
|
||||
<button class="daily-close" type="button" aria-label="Close" data-i18n-aria="close" data-close-daily>×</button>
|
||||
</div>
|
||||
<div class="daily-summary">
|
||||
<span data-i18n="target">Target</span>
|
||||
<strong id="dailyCurrentGift">0</strong>
|
||||
</div>
|
||||
<div class="daily-list" id="dailyList"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="daily-modal invite-modal" id="inviteModal" hidden>
|
||||
<div class="daily-backdrop" data-close-invite></div>
|
||||
<section class="daily-sheet invite-sheet" role="dialog" aria-modal="true" aria-labelledby="inviteTitle">
|
||||
<div class="daily-sheet-head">
|
||||
<h2 id="inviteTitle" data-i18n="how_to_invite_member">How to invite host</h2>
|
||||
<button class="daily-close" type="button" aria-label="Close" data-i18n-aria="close" data-close-invite>×</button>
|
||||
</div>
|
||||
<div class="invite-steps">
|
||||
<div class="invite-step">
|
||||
<span class="invite-step-number">1</span>
|
||||
<span data-i18n="invite_download_app">Invite users to download APP.</span>
|
||||
</div>
|
||||
<div class="invite-step">
|
||||
<span class="invite-step-number">2</span>
|
||||
<span data-i18n="invited_users_login">Invited users need to log in to the APP.</span>
|
||||
</div>
|
||||
<div class="invite-step">
|
||||
<span class="invite-step-number">3</span>
|
||||
<span data-i18n="join_team_steps">Me > Host Center > Apply to join the team.</span>
|
||||
</div>
|
||||
<div class="invite-step">
|
||||
<span class="invite-step-number">4</span>
|
||||
<span data-i18n="enter_team_id">Enter the team ID, click send to join.</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="message-modal" id="messageModal" hidden>
|
||||
<div class="message-backdrop" data-close-messages></div>
|
||||
<section class="message-dialog" role="dialog" aria-modal="true" aria-labelledby="messageTitle">
|
||||
<div class="message-head">
|
||||
<h2 id="messageTitle" data-i18n="users_message">Messages</h2>
|
||||
<button class="daily-close" type="button" aria-label="Close" data-i18n-aria="close" data-close-messages>×</button>
|
||||
</div>
|
||||
<div class="message-list" id="messageList">
|
||||
<div class="message-empty" data-i18n="messages_loading">Loading...</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
<div class="home-indicator" aria-hidden="true"></div>
|
||||
|
||||
<div class="loading-mask" id="loadingMask" role="status" aria-live="polite" aria-busy="true">
|
||||
<div class="runner-stick" aria-hidden="true">
|
||||
<span class="runner-head"></span>
|
||||
<span class="runner-body"></span>
|
||||
<span class="runner-arm runner-arm-front"></span>
|
||||
<span class="runner-arm runner-arm-back"></span>
|
||||
<span class="runner-leg runner-leg-front"></span>
|
||||
<span class="runner-leg runner-leg-back"></span>
|
||||
<span class="runner-ground"></span>
|
||||
</div>
|
||||
<div class="loading-copy">loading....</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./script.js?v=20260428-2345" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
336
h5/hyapp/agency-center/original-vue-logic.md
Normal file
336
h5/hyapp/agency-center/original-vue-logic.md
Normal file
@ -0,0 +1,336 @@
|
||||
# 原 Vue Agency Center 逻辑清单
|
||||
|
||||
来源:
|
||||
- `chatapp-h5/h5/js/index--RUKA0EA-1776148658686.js`
|
||||
- `chatapp-h5/h5/js/pageConfig-BUpLPO7w-1776148658686.js`
|
||||
- `chatapp-h5/h5/js/teamBill-BWXvrc1S-1776148658686.js`
|
||||
- `chatapp-h5/h5/js/index-CIAVq8iD-1776148658686.js`
|
||||
- 旧静态复刻:`chatapp-h5/h5/agency-center/agency-center.js`
|
||||
|
||||
## 页面初始化
|
||||
|
||||
- 初始化 i18n:读取当前 locale,并调用语言切换逻辑。
|
||||
- 初始化 Vue Router。
|
||||
- 初始化 identity store,并设置身份为 `AGENCY`。
|
||||
- 清空 payee store。
|
||||
- 使用 `usePageConfig("AGENCY_CENTER", { needsTeamInfo: true, forceRefresh: true, onDataLoaded })`。
|
||||
- 页面 mounted 后触发:
|
||||
- `identity = "AGENCY"`
|
||||
- 获取银行卡列表
|
||||
- 获取提现/KYC 信息
|
||||
- 获取主播工资提现是否可用
|
||||
- 清空收款人缓存
|
||||
- `usePageConfig` 完成基础数据加载后执行:
|
||||
- 获取团队成员数量
|
||||
- 获取团队账单概要
|
||||
- 获取当前用户成员工作汇总
|
||||
|
||||
## App 连接与请求头
|
||||
|
||||
- 判断是否在 App/WebView 环境。
|
||||
- 如果已有连接且请求头缓存有效,复用缓存请求头。
|
||||
- 如果未连接,调用 App bridge 获取 access origin 参数。
|
||||
- 解析 App 返回参数后写入 HTTP 请求头。
|
||||
- 关键请求头:
|
||||
- `Authorization`
|
||||
- `Req-Lang`
|
||||
- `Req-App-Intel`
|
||||
- `Req-Imei`
|
||||
- `Req-Sys-Origin`
|
||||
- 连接失败时可能跳转 `/not_app`。
|
||||
- 新 `hyapp` 页面可沿用 Host Center 的 URL token 模式:从 `?token=` 或 hash query 读取 token,再转为 `Authorization: Bearer <token>` 请求 Java 接口。
|
||||
|
||||
## 用户与团队信息
|
||||
|
||||
### GET `/team/member/profile`
|
||||
|
||||
- 用途:获取当前用户信息。
|
||||
- 显式查询参数:无。
|
||||
- 依赖:`Authorization` 请求头。
|
||||
- 原 Vue 展示或缓存字段:
|
||||
- `id`
|
||||
- `account`
|
||||
- `userNickname`
|
||||
- `name`
|
||||
- `userAvatar`
|
||||
- `ownSpecialId.account`
|
||||
- `usePageConfig` 中写入 `userInfo`:
|
||||
- `id = body.account`
|
||||
- `account = body.account`
|
||||
- `userNickname = body.userNickname`
|
||||
- `userAvatar = body.userAvatar`
|
||||
- 旧静态复刻中保留完整 `body` 到 `state.userProfile`,并缓存到 `localStorage.userProfile`。
|
||||
|
||||
### GET `/team/entry`
|
||||
|
||||
- 用途:获取当前用户所属团队入口信息。
|
||||
- 显式查询参数:无。
|
||||
- 依赖:`Authorization` 请求头。
|
||||
- 原 Vue 使用 `body.teamProfile || body` 作为团队信息。
|
||||
- 团队 ID 来源:`teamProfile.id || body.id`。
|
||||
- 旧静态复刻缓存到 `localStorage.teamInfo`。
|
||||
- `team/entry` 失败或空返回时,团队信息置空。
|
||||
|
||||
### GET `/team/bd-profile`
|
||||
|
||||
- 用途:获取当前用户所在团队关联的 BD 用户资料。
|
||||
- 显式查询参数:无。
|
||||
- 依赖:`Authorization` 请求头。
|
||||
- 后端实现:当前登录用户 -> `TeamMember.teamId` -> `BusinessDevelopmentTeamService.getBdUserIdByTeamId(teamId)` -> `UserProfileDTO`。
|
||||
- 原 Vue agency-center 未直接调用该接口;该接口由 Java 后端已有的 `TeamRestController#getTeamBdProfile` 提供。
|
||||
|
||||
## 身份与权限
|
||||
|
||||
### GET `/app/h5/identity`
|
||||
|
||||
- 用途:获取 H5 身份和路由权限。
|
||||
- 显式查询参数:无。
|
||||
- 原逻辑根据返回字段生成 roles:
|
||||
- `freightAgent`
|
||||
- `agent`
|
||||
- `bdLeader`
|
||||
- `bd`
|
||||
- `anchor`
|
||||
- `admin`
|
||||
- 主角色优先级:
|
||||
- `guest`
|
||||
- `anchor`
|
||||
- `agent`
|
||||
- `bd`
|
||||
- `bdLeader`
|
||||
- `freightAgent`
|
||||
- `admin`
|
||||
- 默认跳转页:
|
||||
- `admin` -> `/admin-center`
|
||||
- `freightAgent` -> `/coin-seller`
|
||||
- `agent` -> `/agency-center`
|
||||
- `bdLeader` -> `/bd-leader-center`
|
||||
- `bd` -> `/bd-center`
|
||||
- `anchor` -> `/host-center`
|
||||
- `guest` -> `/apply`
|
||||
- Agency Center 允许 `agent` 访问;旧权限配置里 `freightAgent` 也允许访问 `/agency-center`。
|
||||
- 如果当前路由不在允许列表中,跳转到主角色默认页。
|
||||
|
||||
## 工资/余额
|
||||
|
||||
### GET `/wallet/bank/balance`
|
||||
|
||||
- 用途:获取可用工资余额。
|
||||
- 显式查询参数:无。
|
||||
- 原 Vue `usePageConfig` 写入 `salaryInfo.currentSalary`。
|
||||
- 展示字段:`$${salaryInfo.currentSalary}`。
|
||||
- 原逻辑只在 `body` 是 number 时使用 `body.toFixed(2)`,否则展示 `0`。
|
||||
- 请求失败时展示 `0`,并 toast:`Failed to load balance. Please try again.`
|
||||
- 新页面如果展示 Agency 可用工资,优先复用这个数据源。
|
||||
|
||||
## 今日任务/主播任务
|
||||
|
||||
### GET `/team/member/work/statistics-now`
|
||||
|
||||
- 用途:获取当前用户今日工作统计。
|
||||
- 查询参数:
|
||||
- `dataType=DAILY`
|
||||
- `memberId=<当前用户 id/account>`
|
||||
- 原 Vue `taskInfo`:
|
||||
- `anchorType = "Chat"`
|
||||
- `minutesProgress = min(ownTime + otherTime, 120)`
|
||||
- `minutesTotal = 120`
|
||||
- `giftTask = acceptGiftValue || 0`
|
||||
- 原 Agency Center 组件展示三个格子:
|
||||
- `Host type`
|
||||
- `Minutes`
|
||||
- `Gift Task`
|
||||
- 旧静态复刻里实际只展示 `Minutes`,`Host type` 和 `Gift Task` HTML 被注释。
|
||||
|
||||
## 成员工作汇总
|
||||
|
||||
### GET `/team/member-work`
|
||||
|
||||
- 用途:获取当前用户工作目标和结算汇总。
|
||||
- 查询参数:
|
||||
- `userId=<当前用户 id>`
|
||||
- 原函数名:`fetchMemberWorkTotal`。
|
||||
- 原逻辑:
|
||||
- 读取 `body.targets || []`
|
||||
- 跳过 `targets[0]`
|
||||
- 再请求或读取 identity 判断身份
|
||||
- 如果是 `agent`:累加 `target.settlementResult.ownSalary + target.settlementResult.memberSalary`
|
||||
- 如果是 `anchor`:累加 `target.settlementResult.memberSalary`
|
||||
- 写入 `memberWorkTotal = total.toFixed(2)`
|
||||
- 注意:Agency Center 主模板里没有明确展示 `memberWorkTotal`,这是页面加载时保留的汇总逻辑。
|
||||
|
||||
## 团队成员数量
|
||||
|
||||
### GET `/team/members/count`
|
||||
|
||||
- 用途:获取团队成员数量。
|
||||
- 查询参数:
|
||||
- `id=<teamId>`
|
||||
- 团队 ID 来源:`/team/entry` 的 `teamProfile.id || id`。
|
||||
- 返回使用:
|
||||
- `body.memberQuantity`
|
||||
- 原点击 Team Member 时会把成员数量带到 URL:
|
||||
- `/team-member?source=agency-center&members=<memberQuantity>`
|
||||
- 请求失败时旧静态复刻 fallback 为 `87`,Vue 构建产物 catch 中也有相同 fallback。
|
||||
|
||||
## 团队账单概要
|
||||
|
||||
### GET `/team/bill`
|
||||
|
||||
- 用途:获取团队账单列表,Agency Center 首页只取第一条作为概要。
|
||||
- 查询参数:
|
||||
- `id=<teamId>`
|
||||
- 原逻辑:
|
||||
- 读取 `body[0]`
|
||||
- `billBelong` 形如 `YYYYMM`,展示为 `YYYY.MM`
|
||||
- `hostSalary = settleResult.memberSalary.toFixed(2) || "–"`
|
||||
- `agentSalary = settleResult.ownSalary.toFixed(2) || "–"`
|
||||
- `total = settleResult.totalSalary.toFixed(2) || "–"`
|
||||
- 状态映射:
|
||||
- `SETTLED` -> `Completed`
|
||||
- `UNPAID` -> `In progress`
|
||||
- `HANG_UP` -> `Out of account`
|
||||
- `PAY_OUT` -> `Completed`
|
||||
- 状态颜色逻辑:
|
||||
- `In progress`:蓝色
|
||||
- `Pending`:红色
|
||||
- `Completed`:绿色
|
||||
- `Out of account`:灰色
|
||||
- 注意:Agency Center 首页初始化了 `teamBill`,但主模板只提供 Team Bill 入口,不直接展示账单卡片。
|
||||
|
||||
## 团队政策
|
||||
|
||||
### GET `/team/release/policy`
|
||||
|
||||
- 用途:Platform Policy 页面使用,不在 Agency Center 首页直接渲染。
|
||||
- 查询参数:
|
||||
- `id=<teamId>`
|
||||
- 原 `PlatformPolicy` 通过 URL `from=agencycenter` 判断是否展示代理字段。
|
||||
- 当 `from=agencycenter` 时,政策卡片展示:
|
||||
- `time/hours`
|
||||
- `target`
|
||||
- `host_salary`
|
||||
- `agent_salary`
|
||||
- `total_salary`
|
||||
- 线上 Java 接口需要参数名 `id`,不能用 `teamId`。
|
||||
|
||||
## 银行卡与提现信息
|
||||
|
||||
### GET `/wallet/bank-card/list`
|
||||
|
||||
- 用途:获取银行卡/收款方式列表。
|
||||
- 显式查询参数:无。
|
||||
- 原逻辑:
|
||||
- `bankCards = body`
|
||||
- `defaultBankCard = body.find(item => item.use) || null`
|
||||
|
||||
### GET `/wallet/withdraw-info/list`
|
||||
|
||||
- 用途:获取提现/KYC 信息。
|
||||
- 显式查询参数:无。
|
||||
- 原逻辑:
|
||||
- 如果 `body.length > 0`,取第一条
|
||||
- `passportFrontUrl` 按 JSON 字符串解析成 `previewUrls`
|
||||
- 合并成 `withdrawInfo`
|
||||
- 点击提现时如果 `withdrawInfo` 缺失,会弹出补全信息弹窗。
|
||||
|
||||
### GET `/wallet/bank/host-salary/withdraw/enabled`
|
||||
|
||||
- 用途:控制是否展示 `Cash withdraw` 入口。
|
||||
- 显式查询参数:无。
|
||||
- 原 Agency Center 仍然调用 `host-salary` 提现开关。
|
||||
- 返回使用:
|
||||
- `withdrawEnabled = Boolean(body)`
|
||||
|
||||
## 点击提现逻辑
|
||||
|
||||
- 计算值:`missingWithdrawInfo = withdrawInfo == null || defaultBankCard == null`
|
||||
- 点击 `Cash withdraw`:
|
||||
- 如果缺提现信息或默认银行卡,打开补全信息弹窗。
|
||||
- 否则跳转 `/cash-withdraw`。
|
||||
- 补全信息弹窗:
|
||||
- 标题:`prompt`
|
||||
- 主文案:`fill_info_tips`
|
||||
- 辅助文案:`card_issue_tips`
|
||||
- 按钮:`to_improve`
|
||||
- 按钮跳转 `/agency-setting`
|
||||
|
||||
## 页面展示结构
|
||||
|
||||
- 顶部:
|
||||
- `GeneralHeader`
|
||||
- 标题:`agency_center`
|
||||
- 右侧消息图标,点击跳转 `/message`
|
||||
- 原 Agency Center 没有未读红点逻辑。
|
||||
- 消息页:
|
||||
- `Message` 页面 mounted 后请求 `/team/user/apply/record?teamId=<teamId>&status=WAIT`。
|
||||
- 返回的 WAIT 记录作为待处理入团申请列表展示。
|
||||
- 可在新 Agency Center 中复用该 WAIT 记录数量作为消息红点依据;原 Vue 没有单独的 unread 字段或 unread 接口。
|
||||
- 原 Message 记录格式:
|
||||
- `createUserProfile.userAvatar`
|
||||
- `createUserProfile.userNickname`
|
||||
- `createUserProfile.account`
|
||||
- 操作按钮:`Agree`、`Refuse`
|
||||
- 同意/拒绝接口:`POST /team/process/user/apply`,body 为 `{ id, status }`,`status` 可取 `AGREE` 或 `REJECT`。
|
||||
- 工资卡片:
|
||||
- 标题:`my_available_salary`
|
||||
- 右侧按钮:`my_salary_details`
|
||||
- 金额:`$${salaryInfo.currentSalary}`
|
||||
- 今日任务卡片:
|
||||
- 标题:`today_host_task`
|
||||
- 原 Vue 展示 `Host type`、`Minutes`、`Gift Task`
|
||||
- 旧静态复刻只展示 `Minutes`
|
||||
- 菜单卡片:
|
||||
- `Platform policy`
|
||||
- `Exchange coins`
|
||||
- `Transfer`
|
||||
- `Cash withdraw`,受提现开关控制
|
||||
- `Bank Accounts`
|
||||
- `Team Member`
|
||||
- `Team Bill`
|
||||
- `Invite Members`
|
||||
|
||||
## 点击跳转
|
||||
|
||||
- 消息:`/message`
|
||||
- 工资详情:`/history-salary`
|
||||
- 平台政策:`/platform-policy?from=agencycenter`
|
||||
- 兑换金币:`/exchange-gold-coins`
|
||||
- 转账:`/transfer`
|
||||
- 提现:`/cash-withdraw`
|
||||
- 银行卡/收款信息:`/agency-setting`
|
||||
- 团队成员:`/team-member?source=agency-center&members=<memberQuantity>`
|
||||
- 团队账单:`/team-bill`
|
||||
- 邀请成员:`/invite-members`
|
||||
- 补全提现信息:`/agency-setting`
|
||||
|
||||
## 可复用到新 `h5/hyapp/agency-center` 的数据源
|
||||
|
||||
- 当前用户信息:`GET /team/member/profile`
|
||||
- 可展示头像、昵称、UID、靓号。
|
||||
- 当前团队信息:`GET /team/entry`
|
||||
- 可取 agency/team 名称、teamId,并作为团队成员、账单、政策接口的前置数据。
|
||||
- 当前团队关联 BD:`GET /team/bd-profile`
|
||||
- 可展示 BD 的头像/昵称/账号等用户资料;旧 Vue agency-center 未直接调用,但后端已有接口。
|
||||
- 当前余额/工资:`GET /wallet/bank/balance`
|
||||
- 可作为 Agency Center 的 Salary。
|
||||
- 今日工作数据:`GET /team/member/work/statistics-now?dataType=DAILY&memberId=<当前用户 id/account>`
|
||||
- 可展示 Minutes,是否保留 Host type/Gift Task 需要按新设计决定。
|
||||
- 团队成员数量:`GET /team/members/count?id=<teamId>`
|
||||
- 可展示 Team Member 入口右侧数量,或用于跳转参数。
|
||||
- 团队账单概要:`GET /team/bill?id=<teamId>`
|
||||
- 可展示最近账单、Agent salary、Host salary、Total。
|
||||
- 提现入口开关:`GET /wallet/bank/host-salary/withdraw/enabled`
|
||||
- 可决定是否展示 Cash withdraw。
|
||||
- 收款/提现信息:`GET /wallet/bank-card/list`、`GET /wallet/withdraw-info/list`
|
||||
- 可决定点击提现时直接进入还是提示补充信息。
|
||||
- 身份权限:`GET /app/h5/identity`
|
||||
- 可判断是否为 `agent`,非代理时应避免展示代理中心主内容。
|
||||
|
||||
## 新页面实现注意点
|
||||
|
||||
- 新页面应放在 `h5/hyapp/agency-center/`,不要改旧 Vue 构建产物和旧 `h5/agency-center`。
|
||||
- 视觉风格参考 `h5/hyapp/host-center`:浅绿色背景、圆角白色卡片、移动端固定宽度体验、URL token 请求。
|
||||
- 先完成数据加载再决定展示代理中心或非代理状态,避免接口完成前闪跳。
|
||||
- 如果需要非代理兜底页,应基于 `/app/h5/identity` 的 `agent` 字段判断。
|
||||
- 如果继续使用政策接口,Java 线上接口参数必须是 `id=<teamId>`。
|
||||
1134
h5/hyapp/agency-center/script.js
Normal file
1134
h5/hyapp/agency-center/script.js
Normal file
File diff suppressed because it is too large
Load Diff
1404
h5/hyapp/agency-center/style.css
Normal file
1404
h5/hyapp/agency-center/style.css
Normal file
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,7 @@
|
||||
"open_withdraw_exchange": "فتح صفحة السحب والاستبدال",
|
||||
"open_anchor_policy": "فتح سياسة المضيف",
|
||||
"anchor_policy": "سياسة المضيف",
|
||||
"platform_policy": "سياسة الوكالة",
|
||||
"policy_loading": "جار تحميل السياسة...",
|
||||
"policy_empty": "لا توجد بيانات سياسة",
|
||||
"policy_missing_token": "رمز الدخول مفقود",
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"open_withdraw_exchange": "Open withdraw and exchange page",
|
||||
"open_anchor_policy": "Open host policy",
|
||||
"anchor_policy": "Host Policy",
|
||||
"platform_policy": "Agency Policy",
|
||||
"policy_loading": "Loading policy...",
|
||||
"policy_empty": "No policy data",
|
||||
"policy_missing_token": "Missing token",
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"open_withdraw_exchange": "Buka halaman tarik dan tukar",
|
||||
"open_anchor_policy": "Buka kebijakan host",
|
||||
"anchor_policy": "Kebijakan Host",
|
||||
"platform_policy": "Kebijakan Agensi",
|
||||
"policy_loading": "Memuat kebijakan...",
|
||||
"policy_empty": "Tidak ada data kebijakan",
|
||||
"policy_missing_token": "Token tidak ditemukan",
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"open_withdraw_exchange": "Çekme ve değişim sayfasını aç",
|
||||
"open_anchor_policy": "Host politikasını aç",
|
||||
"anchor_policy": "Host Politikası",
|
||||
"platform_policy": "Ajans Politikası",
|
||||
"policy_loading": "Politika yükleniyor...",
|
||||
"policy_empty": "Politika verisi yok",
|
||||
"policy_missing_token": "Token eksik",
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<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 Policy</title>
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2030" />
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2100" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="host-center policy-page" aria-label="Host Policy" data-i18n-aria="anchor_policy">
|
||||
@ -25,7 +25,7 @@
|
||||
<path d="M20 8 12 16l8 8" />
|
||||
</svg>
|
||||
</button>
|
||||
<h1 data-i18n="anchor_policy">Host Policy</h1>
|
||||
<h1 data-i18n="anchor_policy" data-policy-title>Host Policy</h1>
|
||||
<div class="language-switcher">
|
||||
<button class="language-button" type="button" aria-label="Change language" data-i18n-aria="language_button_aria" aria-expanded="false">EN</button>
|
||||
<div class="language-menu" hidden>
|
||||
@ -40,7 +40,7 @@
|
||||
<main class="content policy-content">
|
||||
<section class="card policy-card" aria-labelledby="anchorPolicyTitle">
|
||||
<div class="policy-head">
|
||||
<h2 id="anchorPolicyTitle" data-i18n="anchor_policy">Host Policy</h2>
|
||||
<h2 id="anchorPolicyTitle" data-i18n="anchor_policy" data-policy-title>Host Policy</h2>
|
||||
<span class="policy-region" id="policyRegion"></span>
|
||||
</div>
|
||||
<div class="policy-status" id="policyStatus" data-i18n="policy_loading">Loading policy...</div>
|
||||
@ -50,6 +50,6 @@
|
||||
|
||||
<div class="home-indicator" aria-hidden="true"></div>
|
||||
</div>
|
||||
<script src="./policy.js?v=20260428-2030" defer></script>
|
||||
<script src="./policy.js?v=20260428-2100" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
back: "Back",
|
||||
language_button_aria: "Change language",
|
||||
anchor_policy: "Host Policy",
|
||||
platform_policy: "Agency Policy",
|
||||
policy_loading: "Loading policy...",
|
||||
policy_empty: "No policy data",
|
||||
policy_missing_token: "Missing token",
|
||||
@ -32,6 +33,7 @@
|
||||
back: "رجوع",
|
||||
language_button_aria: "تغيير اللغة",
|
||||
anchor_policy: "سياسة المضيف",
|
||||
platform_policy: "سياسة الوكالة",
|
||||
policy_loading: "جار تحميل السياسة...",
|
||||
policy_empty: "لا توجد بيانات سياسة",
|
||||
policy_missing_token: "رمز الدخول مفقود",
|
||||
@ -50,6 +52,7 @@
|
||||
back: "Geri",
|
||||
language_button_aria: "Dili değiştir",
|
||||
anchor_policy: "Host Politikası",
|
||||
platform_policy: "Ajans Politikası",
|
||||
policy_loading: "Politika yükleniyor...",
|
||||
policy_empty: "Politika verisi yok",
|
||||
policy_missing_token: "Token eksik",
|
||||
@ -68,6 +71,7 @@
|
||||
back: "Kembali",
|
||||
language_button_aria: "Ubah bahasa",
|
||||
anchor_policy: "Kebijakan Host",
|
||||
platform_policy: "Kebijakan Agensi",
|
||||
policy_loading: "Memuat kebijakan...",
|
||||
policy_empty: "Tidak ada data kebijakan",
|
||||
policy_missing_token: "Token tidak ditemukan",
|
||||
@ -89,6 +93,14 @@
|
||||
let currentPolicy = null;
|
||||
let policyStatusKey = "policy_loading";
|
||||
|
||||
function isAgencyPolicy() {
|
||||
return params.get("from") === "agencycenter";
|
||||
}
|
||||
|
||||
function policyTitleKey() {
|
||||
return isAgencyPolicy() ? "platform_policy" : "anchor_policy";
|
||||
}
|
||||
|
||||
function readURLParams() {
|
||||
const result = new URLSearchParams(window.location.search);
|
||||
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||||
@ -136,7 +148,7 @@
|
||||
if (window.location.protocol === "file:") return fallbackMessages[normalizedLang];
|
||||
|
||||
try {
|
||||
const response = await fetch(`./locales/${normalizedLang}.json?v=20260428-2030`, { cache: "no-store" });
|
||||
const response = await fetch(`./locales/${normalizedLang}.json?v=20260428-2100`, { cache: "no-store" });
|
||||
if (response.ok) return await response.json();
|
||||
} catch (error) {
|
||||
return fallbackMessages[normalizedLang];
|
||||
@ -159,6 +171,12 @@
|
||||
const value = messages[node.dataset.i18nAria];
|
||||
if (value) node.setAttribute("aria-label", value);
|
||||
});
|
||||
|
||||
const title = messages[policyTitleKey()] || messages.anchor_policy || fallbackMessages.en.anchor_policy;
|
||||
document.querySelectorAll("[data-policy-title]").forEach((node) => {
|
||||
node.textContent = title;
|
||||
});
|
||||
document.querySelector(".policy-page")?.setAttribute("aria-label", title);
|
||||
}
|
||||
|
||||
function apiBaseURL() {
|
||||
@ -381,7 +399,8 @@
|
||||
if (token) nextParams.set("token", token);
|
||||
if (currentLang) nextParams.set("lang", currentLang);
|
||||
const query = nextParams.toString();
|
||||
window.location.href = `./index.html${query ? `?${query}` : ""}`;
|
||||
const fallbackPath = isAgencyPolicy() ? "../agency-center/index.html" : "./index.html";
|
||||
window.location.href = `${fallbackPath}${query ? `?${query}` : ""}`;
|
||||
});
|
||||
}
|
||||
|
||||
@ -415,7 +434,7 @@
|
||||
|
||||
document.documentElement.lang = currentLang;
|
||||
document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr";
|
||||
document.title = messages.anchor_policy || messages.document_title || "Host Policy";
|
||||
document.title = messages[policyTitleKey()] || messages.anchor_policy || messages.document_title || "Host Policy";
|
||||
renderMessages(messages);
|
||||
renderLanguageControls(messages);
|
||||
if (currentPolicy) {
|
||||
|
||||
@ -630,8 +630,11 @@ a {
|
||||
}
|
||||
|
||||
.income-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 143px;
|
||||
padding: 19px 24px 18px;
|
||||
padding: 18px 24px;
|
||||
}
|
||||
|
||||
.income-head {
|
||||
|
||||
145
h5/hyapp/hosts-bill/index.html
Normal file
145
h5/hyapp/hosts-bill/index.html
Normal file
@ -0,0 +1,145 @@
|
||||
<!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>Salary Bills</title>
|
||||
<link rel="stylesheet" href="../agency-center/style.css?v=20260428-2355" />
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2355" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="agency-center hosts-bill-page" aria-label="Salary Bills" data-i18n-aria="page_label" data-loading="true">
|
||||
<div class="hero-bg" aria-hidden="true"></div>
|
||||
|
||||
<header class="status-bar" aria-hidden="true">
|
||||
<span class="status-time">9:41</span>
|
||||
<span class="status-icons">
|
||||
<span class="cellular"><i></i><i></i><i></i><i></i></span>
|
||||
<span class="wifi"><i></i><i></i><i></i></span>
|
||||
<span class="battery"><i></i></span>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<nav class="title-bar">
|
||||
<button class="back-button" type="button" aria-label="Back" data-i18n-aria="back">
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true">
|
||||
<path d="M20 8 12 16l8 8" />
|
||||
</svg>
|
||||
</button>
|
||||
<h1 data-i18n="title">Salary Bills</h1>
|
||||
<div class="language-switcher">
|
||||
<button class="language-button" type="button" aria-label="Change language" data-i18n-aria="language_button_aria" aria-expanded="false">EN</button>
|
||||
<div class="language-menu" hidden>
|
||||
<button type="button" data-lang="en">EN</button>
|
||||
<button type="button" data-lang="ar">AR</button>
|
||||
<button type="button" data-lang="tr">TR</button>
|
||||
<button type="button" data-lang="id">ID</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="content hosts-bill-content">
|
||||
<section class="card bill-summary-card">
|
||||
<div class="bill-summary-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 32 32">
|
||||
<path d="M8 7h16v18H8Z" />
|
||||
<path d="M12 12h8M12 17h6M12 22h5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="bill-summary-copy">
|
||||
<div class="bill-summary-head">
|
||||
<span data-i18n="history_team_income">History team income</span>
|
||||
<button class="bill-help-button" type="button" aria-label="Open help" data-i18n-aria="open_help">
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true">
|
||||
<circle cx="10" cy="10" r="8" />
|
||||
<path d="M10 13.5v.2M8.7 7.8a1.8 1.8 0 0 1 3.2 1.1c0 1.6-1.9 1.7-1.9 3" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bill-summary-value" id="historyIncome">$0.00</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bill-stat-grid" aria-label="Bill summary" data-i18n-aria="bill_summary">
|
||||
<div class="card bill-stat-card">
|
||||
<span data-i18n="total_bills">Total bills</span>
|
||||
<strong id="totalBills">0</strong>
|
||||
</div>
|
||||
<div class="card bill-stat-card">
|
||||
<span data-i18n="completed_bills">Completed</span>
|
||||
<strong id="completedBills">0</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="work-report-head">
|
||||
<h2 data-i18n="work_report">Work report</h2>
|
||||
</div>
|
||||
<section class="bill-list" id="billList" aria-live="polite"></section>
|
||||
<div class="bill-list-status" id="listStatus" role="status"></div>
|
||||
</main>
|
||||
|
||||
<div class="daily-modal bill-detail-modal" id="billDetailModal" hidden>
|
||||
<div class="daily-backdrop" data-close-detail></div>
|
||||
<section class="daily-sheet bill-detail-sheet" role="dialog" aria-modal="true" aria-labelledby="billDetailTitle">
|
||||
<div class="daily-sheet-head">
|
||||
<h2 id="billDetailTitle" data-i18n="team_member">Team member</h2>
|
||||
<button class="daily-close" type="button" aria-label="Close" data-i18n-aria="close" data-close-detail>×</button>
|
||||
</div>
|
||||
<div class="bill-detail-meta">
|
||||
<div>
|
||||
<span id="detailBillDate">-</span>
|
||||
<strong id="detailBillTotal">$0.00</strong>
|
||||
</div>
|
||||
<span class="bill-status-badge" id="detailBillStatus">-</span>
|
||||
</div>
|
||||
<form class="bill-search-form" id="memberSearchForm">
|
||||
<div class="bill-search-control">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="m20 20-4.2-4.2M10.8 18a7.2 7.2 0 1 1 0-14.4 7.2 7.2 0 0 1 0 14.4Z" />
|
||||
</svg>
|
||||
<input id="memberSearchInput" type="text" inputmode="numeric" autocomplete="off" placeholder="Enter host ID" data-i18n-placeholder="enter_host_id" />
|
||||
</div>
|
||||
<button class="bill-search-button" id="memberSearchButton" type="submit" data-i18n="search">Search</button>
|
||||
<button class="bill-cancel-button" id="memberCancelButton" type="button" data-i18n="cancel" hidden>Cancel</button>
|
||||
</form>
|
||||
<div class="bill-member-list" id="billMemberList"></div>
|
||||
<div class="bill-list-status bill-member-status" id="memberStatus" role="status"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="daily-modal bill-help-modal" id="helpModal" hidden>
|
||||
<div class="daily-backdrop" data-close-help></div>
|
||||
<section class="daily-sheet bill-help-sheet" role="dialog" aria-modal="true" aria-labelledby="helpTitle">
|
||||
<div class="daily-sheet-head">
|
||||
<h2 id="helpTitle" data-i18n="help">Help</h2>
|
||||
<button class="daily-close" type="button" aria-label="Close" data-i18n-aria="close" data-close-help>×</button>
|
||||
</div>
|
||||
<div class="bill-help-content">
|
||||
<p data-i18n="work_report_description">Work report shows each team bill cycle and salary summary.</p>
|
||||
<p data-i18n="status_description">Status describes the current settlement state.</p>
|
||||
<p data-i18n="status_completed">Completed: salary has been settled.</p>
|
||||
<p data-i18n="status_in_progress">In progress: the bill is still running.</p>
|
||||
<p data-i18n="status_out_of_account">Out of account: the bill is suspended or unavailable.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
<div class="home-indicator" aria-hidden="true"></div>
|
||||
|
||||
<div class="loading-mask" id="loadingMask" role="status" aria-live="polite" aria-busy="true">
|
||||
<div class="runner-stick" aria-hidden="true">
|
||||
<span class="runner-head"></span>
|
||||
<span class="runner-body"></span>
|
||||
<span class="runner-arm runner-arm-front"></span>
|
||||
<span class="runner-arm runner-arm-back"></span>
|
||||
<span class="runner-leg runner-leg-front"></span>
|
||||
<span class="runner-leg runner-leg-back"></span>
|
||||
<span class="runner-ground"></span>
|
||||
</div>
|
||||
<div class="loading-copy">loading....</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./script.js?v=20260428-2355" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
940
h5/hyapp/hosts-bill/script.js
Normal file
940
h5/hyapp/hosts-bill/script.js
Normal file
@ -0,0 +1,940 @@
|
||||
(function () {
|
||||
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
const supportedLanguages = ["en", "ar", "tr", "id"];
|
||||
const languageLabels = {
|
||||
en: "EN",
|
||||
ar: "AR",
|
||||
tr: "TR",
|
||||
id: "ID"
|
||||
};
|
||||
|
||||
const translations = {
|
||||
en: {
|
||||
document_title: "Salary Bills",
|
||||
page_label: "Salary Bills",
|
||||
back: "Back",
|
||||
title: "Salary Bills",
|
||||
language_button_aria: "Change language",
|
||||
history_team_income: "History team income",
|
||||
bill_summary: "Bill summary",
|
||||
total_bills: "Total bills",
|
||||
completed_bills: "Completed",
|
||||
work_report: "Work report",
|
||||
host_salary: "Host salary",
|
||||
agent_salary: "Agent salary",
|
||||
total: "Total",
|
||||
more: "More",
|
||||
team_member: "Team member",
|
||||
enter_host_id: "Enter host ID",
|
||||
search: "Search",
|
||||
cancel: "Cancel",
|
||||
close: "Close",
|
||||
help: "Help",
|
||||
open_help: "Open help",
|
||||
work_report_description: "Work report shows each team bill cycle and salary summary.",
|
||||
status_description: "Status describes the current settlement state.",
|
||||
status_completed: "Completed: salary has been settled.",
|
||||
status_in_progress: "In progress: the bill is still running.",
|
||||
status_out_of_account: "Out of account: the bill is suspended or unavailable.",
|
||||
completed: "Completed",
|
||||
in_progress: "In progress",
|
||||
out_of_account: "Out of account",
|
||||
pending: "Pending",
|
||||
target: "Target",
|
||||
days: "Days",
|
||||
uid_prefix: "UID:",
|
||||
gift_points: "Gift points",
|
||||
loading: "Loading",
|
||||
no_bills: "No bills",
|
||||
no_members: "No hosts",
|
||||
no_search_result: "No matching hosts",
|
||||
missing_token: "Missing token",
|
||||
missing_token_text: "Open this page from the app or add a token in the URL.",
|
||||
missing_team: "Missing team information",
|
||||
failed_to_load_data: "Failed to load data",
|
||||
unknown_user: "Unknown user"
|
||||
},
|
||||
ar: {
|
||||
document_title: "Salary Bills",
|
||||
page_label: "Salary Bills",
|
||||
back: "رجوع",
|
||||
title: "Salary Bills",
|
||||
language_button_aria: "تغيير اللغة",
|
||||
history_team_income: "دخل الفريق السابق",
|
||||
bill_summary: "ملخص الفواتير",
|
||||
total_bills: "إجمالي الفواتير",
|
||||
completed_bills: "مكتمل",
|
||||
work_report: "تقرير العمل",
|
||||
host_salary: "راتب المضيف",
|
||||
agent_salary: "راتب الوكالة",
|
||||
total: "الإجمالي",
|
||||
more: "المزيد",
|
||||
team_member: "عضو الفريق",
|
||||
enter_host_id: "أدخل معرف المضيف",
|
||||
search: "بحث",
|
||||
cancel: "إلغاء",
|
||||
close: "إغلاق",
|
||||
help: "مساعدة",
|
||||
open_help: "فتح المساعدة",
|
||||
work_report_description: "يعرض تقرير العمل كل دورة فاتورة للفريق وملخص الراتب.",
|
||||
status_description: "توضح الحالة وضع التسوية الحالي.",
|
||||
status_completed: "مكتمل: تم تسوية الراتب.",
|
||||
status_in_progress: "قيد التنفيذ: لا تزال الفاتورة قيد التشغيل.",
|
||||
status_out_of_account: "خارج الحساب: الفاتورة موقوفة أو غير متاحة.",
|
||||
completed: "مكتمل",
|
||||
in_progress: "قيد التنفيذ",
|
||||
out_of_account: "خارج الحساب",
|
||||
pending: "قيد الانتظار",
|
||||
target: "الهدف",
|
||||
days: "الأيام",
|
||||
uid_prefix: "UID:",
|
||||
gift_points: "نقاط الهدايا",
|
||||
loading: "جار التحميل",
|
||||
no_bills: "لا توجد فواتير",
|
||||
no_members: "لا يوجد مضيفون",
|
||||
no_search_result: "لا توجد نتائج مطابقة",
|
||||
missing_token: "رمز الدخول مفقود",
|
||||
missing_token_text: "افتح هذه الصفحة من التطبيق أو أضف token في الرابط.",
|
||||
missing_team: "معلومات الفريق مفقودة",
|
||||
failed_to_load_data: "فشل تحميل البيانات",
|
||||
unknown_user: "مستخدم غير معروف"
|
||||
},
|
||||
tr: {
|
||||
document_title: "Salary Bills",
|
||||
page_label: "Salary Bills",
|
||||
back: "Geri",
|
||||
title: "Salary Bills",
|
||||
language_button_aria: "Dili degistir",
|
||||
history_team_income: "Gecmis takim geliri",
|
||||
bill_summary: "Fatura ozeti",
|
||||
total_bills: "Toplam fatura",
|
||||
completed_bills: "Tamamlandi",
|
||||
work_report: "Calisma raporu",
|
||||
host_salary: "Host maasi",
|
||||
agent_salary: "Ajans maasi",
|
||||
total: "Toplam",
|
||||
more: "Daha fazla",
|
||||
team_member: "Takim uyesi",
|
||||
enter_host_id: "Host ID girin",
|
||||
search: "Ara",
|
||||
cancel: "Iptal",
|
||||
close: "Kapat",
|
||||
help: "Yardim",
|
||||
open_help: "Yardimi ac",
|
||||
work_report_description: "Calisma raporu her takim fatura dongusunu ve maas ozetini gosterir.",
|
||||
status_description: "Durum mevcut odeme surecini aciklar.",
|
||||
status_completed: "Tamamlandi: maas odendi.",
|
||||
status_in_progress: "Devam ediyor: fatura hala calisiyor.",
|
||||
status_out_of_account: "Hesap disi: fatura askida veya kullanilamiyor.",
|
||||
completed: "Tamamlandi",
|
||||
in_progress: "Devam ediyor",
|
||||
out_of_account: "Hesap disi",
|
||||
pending: "Beklemede",
|
||||
target: "Hedef",
|
||||
days: "Gun",
|
||||
uid_prefix: "UID:",
|
||||
gift_points: "Hediye puani",
|
||||
loading: "Yukleniyor",
|
||||
no_bills: "Fatura yok",
|
||||
no_members: "Host yok",
|
||||
no_search_result: "Eslesen host yok",
|
||||
missing_token: "Token eksik",
|
||||
missing_token_text: "Bu sayfayi uygulamadan acin veya URL'ye token ekleyin.",
|
||||
missing_team: "Takim bilgisi eksik",
|
||||
failed_to_load_data: "Veri yuklenemedi",
|
||||
unknown_user: "Bilinmeyen kullanici"
|
||||
},
|
||||
id: {
|
||||
document_title: "Salary Bills",
|
||||
page_label: "Salary Bills",
|
||||
back: "Kembali",
|
||||
title: "Salary Bills",
|
||||
language_button_aria: "Ubah bahasa",
|
||||
history_team_income: "Riwayat pendapatan tim",
|
||||
bill_summary: "Ringkasan tagihan",
|
||||
total_bills: "Total tagihan",
|
||||
completed_bills: "Selesai",
|
||||
work_report: "Laporan kerja",
|
||||
host_salary: "Gaji host",
|
||||
agent_salary: "Gaji agensi",
|
||||
total: "Total",
|
||||
more: "Lainnya",
|
||||
team_member: "Anggota tim",
|
||||
enter_host_id: "Masukkan ID host",
|
||||
search: "Cari",
|
||||
cancel: "Batal",
|
||||
close: "Tutup",
|
||||
help: "Bantuan",
|
||||
open_help: "Buka bantuan",
|
||||
work_report_description: "Laporan kerja menampilkan setiap siklus tagihan tim dan ringkasan gaji.",
|
||||
status_description: "Status menjelaskan kondisi penyelesaian saat ini.",
|
||||
status_completed: "Selesai: gaji telah diselesaikan.",
|
||||
status_in_progress: "Berjalan: tagihan masih berjalan.",
|
||||
status_out_of_account: "Di luar akun: tagihan ditangguhkan atau tidak tersedia.",
|
||||
completed: "Selesai",
|
||||
in_progress: "Berjalan",
|
||||
out_of_account: "Di luar akun",
|
||||
pending: "Tertunda",
|
||||
target: "Target",
|
||||
days: "Hari",
|
||||
uid_prefix: "UID:",
|
||||
gift_points: "Poin hadiah",
|
||||
loading: "Memuat",
|
||||
no_bills: "Tidak ada tagihan",
|
||||
no_members: "Tidak ada host",
|
||||
no_search_result: "Tidak ada host yang cocok",
|
||||
missing_token: "Token tidak ditemukan",
|
||||
missing_token_text: "Buka halaman ini dari aplikasi atau tambahkan token di URL.",
|
||||
missing_team: "Informasi tim tidak ditemukan",
|
||||
failed_to_load_data: "Gagal memuat data",
|
||||
unknown_user: "Pengguna tidak dikenal"
|
||||
}
|
||||
};
|
||||
|
||||
const params = readURLParams();
|
||||
let currentLang = normalizeLanguage(params.get("lang"));
|
||||
let currentMessages = translations[currentLang] || translations.en;
|
||||
let currentTeamId = params.get("teamId") || params.get("id") || "";
|
||||
let bills = [];
|
||||
let currentPolicy = [];
|
||||
let selectedBill = null;
|
||||
let selectedMembers = [];
|
||||
let displayMembers = [];
|
||||
let memberSearchActive = false;
|
||||
let detailLoading = false;
|
||||
|
||||
function readURLParams() {
|
||||
const result = new URLSearchParams(window.location.search);
|
||||
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||||
const hashParams = new URLSearchParams(hashQuery);
|
||||
hashParams.forEach((value, key) => {
|
||||
if (!result.has(key)) result.set(key, value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function readRawParam(name) {
|
||||
const queries = [window.location.search.replace(/^\?/, "")];
|
||||
if (window.location.hash.includes("?")) queries.push(window.location.hash.split("?")[1]);
|
||||
|
||||
for (const query of queries) {
|
||||
for (const part of query.split("&")) {
|
||||
if (!part) continue;
|
||||
const equalIndex = part.indexOf("=");
|
||||
const rawKey = equalIndex >= 0 ? part.slice(0, equalIndex) : part;
|
||||
const rawValue = equalIndex >= 0 ? part.slice(equalIndex + 1) : "";
|
||||
let decodedKey = "";
|
||||
try {
|
||||
decodedKey = decodeURIComponent(rawKey);
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
if (decodedKey !== name) continue;
|
||||
try {
|
||||
return decodeURIComponent(rawValue.replace(/\+/g, "%2B"));
|
||||
} catch (error) {
|
||||
return rawValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function normalizeLanguage(lang) {
|
||||
const value = String(lang || "en").toLowerCase();
|
||||
return supportedLanguages.includes(value) ? value : "en";
|
||||
}
|
||||
|
||||
function t(key, fallback) {
|
||||
return currentMessages[key] || translations.en[key] || fallback || key;
|
||||
}
|
||||
|
||||
function apiBaseURL() {
|
||||
const override = window.HYAPP_API_BASE_URL || params.get("apiBase");
|
||||
if (override) return String(override).replace(/\/+$/, "");
|
||||
return API_BASE_URL.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function normalizeAuthorization(token) {
|
||||
const value = String(token || "").trim();
|
||||
if (!value) return "";
|
||||
return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`;
|
||||
}
|
||||
|
||||
function authHeadersFromToken(token) {
|
||||
return {
|
||||
Authorization: normalizeAuthorization(token),
|
||||
"req-lang": currentLang,
|
||||
"req-client": "H5",
|
||||
"req-version": "V2",
|
||||
"req-zone": Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai",
|
||||
"req-app-intel": "version=1.0.0;build=1;model=H5;sysVersion=Web;channel=Web",
|
||||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU"
|
||||
};
|
||||
}
|
||||
|
||||
async function requestJSON(path, options = {}) {
|
||||
const token = readRawParam("token");
|
||||
if (!token) throw new Error(t("missing_token", "Missing token"));
|
||||
|
||||
const headers = authHeadersFromToken(token);
|
||||
const method = options.method || "GET";
|
||||
const hasBody = options.body !== undefined;
|
||||
if (hasBody) headers["Content-Type"] = "application/json";
|
||||
|
||||
const response = await fetch(new URL(path, `${apiBaseURL()}/`).toString(), {
|
||||
method,
|
||||
cache: "no-store",
|
||||
headers,
|
||||
body: hasBody ? JSON.stringify(options.body) : undefined
|
||||
});
|
||||
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.body ?? data.data ?? null;
|
||||
}
|
||||
|
||||
function buildPageURL(target) {
|
||||
const url = new URL(target, window.location.href);
|
||||
const nextParams = new URLSearchParams(url.search);
|
||||
|
||||
params.forEach((value, key) => {
|
||||
if (!nextParams.has(key)) nextParams.set(key, value);
|
||||
});
|
||||
|
||||
const token = readRawParam("token");
|
||||
if (token) nextParams.set("token", token);
|
||||
if (currentLang) nextParams.set("lang", currentLang);
|
||||
if (currentTeamId) nextParams.set("teamId", currentTeamId);
|
||||
|
||||
url.search = nextParams.toString();
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function setLoading(isLoading) {
|
||||
const root = document.querySelector(".hosts-bill-page");
|
||||
const loadingMask = document.querySelector("#loadingMask");
|
||||
if (root) root.dataset.loading = isLoading ? "true" : "false";
|
||||
if (loadingMask) {
|
||||
loadingMask.hidden = !isLoading;
|
||||
loadingMask.setAttribute("aria-busy", String(isLoading));
|
||||
}
|
||||
}
|
||||
|
||||
function renderLanguageControls() {
|
||||
const languageButton = document.querySelector(".language-button");
|
||||
if (languageButton) {
|
||||
languageButton.textContent = languageLabels[currentLang];
|
||||
languageButton.setAttribute("aria-label", t("language_button_aria"));
|
||||
}
|
||||
|
||||
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
|
||||
const isActive = button.dataset.lang === currentLang;
|
||||
button.classList.toggle("is-active", isActive);
|
||||
button.setAttribute("aria-pressed", String(isActive));
|
||||
});
|
||||
}
|
||||
|
||||
function renderMessages() {
|
||||
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
||||
const value = currentMessages[node.dataset.i18n];
|
||||
if (value) node.textContent = value;
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-i18n-aria]").forEach((node) => {
|
||||
const value = currentMessages[node.dataset.i18nAria];
|
||||
if (value) node.setAttribute("aria-label", value);
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => {
|
||||
const value = currentMessages[node.dataset.i18nPlaceholder];
|
||||
if (value) node.setAttribute("placeholder", value);
|
||||
});
|
||||
}
|
||||
|
||||
function applyLanguage(lang) {
|
||||
currentLang = normalizeLanguage(lang);
|
||||
currentMessages = translations[currentLang] || translations.en;
|
||||
document.documentElement.lang = currentLang;
|
||||
document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr";
|
||||
document.title = t("document_title");
|
||||
renderMessages();
|
||||
renderLanguageControls();
|
||||
renderSummary();
|
||||
renderBills();
|
||||
renderBillDetail();
|
||||
}
|
||||
|
||||
function setLanguageMenuOpen(open) {
|
||||
const languageButton = document.querySelector(".language-button");
|
||||
const languageMenu = document.querySelector(".language-menu");
|
||||
if (!languageButton || !languageMenu) return;
|
||||
languageMenu.hidden = !open;
|
||||
languageButton.setAttribute("aria-expanded", String(open));
|
||||
}
|
||||
|
||||
function pickTeamInfo(body) {
|
||||
return body?.teamProfile || body || null;
|
||||
}
|
||||
|
||||
function pickTeamId(teamInfo) {
|
||||
return teamInfo?.id || teamInfo?.teamId || teamInfo?.teamProfile?.id || "";
|
||||
}
|
||||
|
||||
function pickRecords(body) {
|
||||
if (Array.isArray(body)) return body;
|
||||
if (Array.isArray(body?.records)) return body.records;
|
||||
if (Array.isArray(body?.list)) return body.list;
|
||||
if (Array.isArray(body?.items)) return body.items;
|
||||
return [];
|
||||
}
|
||||
|
||||
function pickPolicyRecords(body) {
|
||||
if (Array.isArray(body)) return body;
|
||||
if (Array.isArray(body?.policy)) return body.policy;
|
||||
if (Array.isArray(body?.policies)) return body.policies;
|
||||
return pickRecords(body);
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? `$${number.toFixed(2)}` : "$0.00";
|
||||
}
|
||||
|
||||
function formatPlainMoney(value) {
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number.toFixed(2) : "0.00";
|
||||
}
|
||||
|
||||
function formatCount(value) {
|
||||
if (value === null || value === undefined || value === "") return "0";
|
||||
const number = Number(value);
|
||||
if (!Number.isFinite(number)) return String(value);
|
||||
return Math.trunc(number).toLocaleString("en-US");
|
||||
}
|
||||
|
||||
function formatGift(value) {
|
||||
if (value === null || value === undefined || value === "") return "0";
|
||||
const number = Number(value);
|
||||
if (!Number.isFinite(number)) return String(value);
|
||||
return Math.trunc(number).toLocaleString("en-US");
|
||||
}
|
||||
|
||||
function normalizeStatus(status) {
|
||||
switch (String(status || "").toUpperCase()) {
|
||||
case "SETTLED":
|
||||
case "PAY_OUT":
|
||||
case "COMPLETED":
|
||||
return { key: "completed", className: "status-completed" };
|
||||
case "HANG_UP":
|
||||
case "OUT_OF_ACCOUNT":
|
||||
case "OUT OF ACCOUNT":
|
||||
return { key: "out_of_account", className: "status-out-of-account" };
|
||||
case "PENDING":
|
||||
return { key: "pending", className: "status-pending" };
|
||||
case "UNPAID":
|
||||
case "IN_PROGRESS":
|
||||
case "IN PROGRESS":
|
||||
default:
|
||||
return { key: "in_progress", className: "status-in-progress" };
|
||||
}
|
||||
}
|
||||
|
||||
function statusText(status) {
|
||||
const info = normalizeStatus(status);
|
||||
return t(info.key, status || "-");
|
||||
}
|
||||
|
||||
function setStatusBadge(node, status) {
|
||||
if (!node) return;
|
||||
const info = normalizeStatus(status);
|
||||
node.className = `bill-status-badge ${info.className}`;
|
||||
node.textContent = t(info.key);
|
||||
}
|
||||
|
||||
function billTitle(bill) {
|
||||
return bill?.billTitle || bill?.billDayTitle || String(bill?.billBelong || "-");
|
||||
}
|
||||
|
||||
function billTotal(bill) {
|
||||
return Number(bill?.settleResult?.totalSalary || 0);
|
||||
}
|
||||
|
||||
function billMemberSalary(bill) {
|
||||
return Number(bill?.settleResult?.memberSalary || 0);
|
||||
}
|
||||
|
||||
function billAgentSalary(bill) {
|
||||
return Number(bill?.settleResult?.ownSalary || 0);
|
||||
}
|
||||
|
||||
function isCompletedBill(bill) {
|
||||
const status = String(bill?.status || "").toUpperCase();
|
||||
return status === "SETTLED" || status === "PAY_OUT" || status === "COMPLETED";
|
||||
}
|
||||
|
||||
function calculateHistoryIncome() {
|
||||
return bills.reduce((sum, bill) => {
|
||||
if (!isCompletedBill(bill)) return sum;
|
||||
return sum + billTotal(bill);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function replaceNodeChildren(node, children) {
|
||||
while (node.firstChild) node.removeChild(node.firstChild);
|
||||
children.forEach((child) => node.appendChild(child));
|
||||
}
|
||||
|
||||
function createSvg(paths) {
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("viewBox", "0 0 24 24");
|
||||
svg.setAttribute("aria-hidden", "true");
|
||||
paths.forEach((pathValue) => {
|
||||
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
path.setAttribute("d", pathValue);
|
||||
svg.appendChild(path);
|
||||
});
|
||||
return svg;
|
||||
}
|
||||
|
||||
function renderSummary() {
|
||||
const historyIncome = document.querySelector("#historyIncome");
|
||||
const totalBills = document.querySelector("#totalBills");
|
||||
const completedBills = document.querySelector("#completedBills");
|
||||
|
||||
if (historyIncome) historyIncome.textContent = formatMoney(calculateHistoryIncome());
|
||||
if (totalBills) totalBills.textContent = formatCount(bills.length);
|
||||
if (completedBills) completedBills.textContent = formatCount(bills.filter(isCompletedBill).length);
|
||||
}
|
||||
|
||||
function createMoneyItem(label, value) {
|
||||
const item = document.createElement("div");
|
||||
item.className = "bill-money-item";
|
||||
|
||||
const labelNode = document.createElement("span");
|
||||
labelNode.textContent = label;
|
||||
|
||||
const valueNode = document.createElement("strong");
|
||||
valueNode.textContent = formatMoney(value);
|
||||
|
||||
item.appendChild(labelNode);
|
||||
item.appendChild(valueNode);
|
||||
return item;
|
||||
}
|
||||
|
||||
function createBillCard(bill) {
|
||||
const card = document.createElement("article");
|
||||
card.className = "bill-card";
|
||||
|
||||
const top = document.createElement("div");
|
||||
top.className = "bill-card-top";
|
||||
|
||||
const date = document.createElement("div");
|
||||
date.className = "bill-date";
|
||||
date.textContent = billTitle(bill);
|
||||
|
||||
const badge = document.createElement("span");
|
||||
setStatusBadge(badge, bill?.status);
|
||||
|
||||
top.appendChild(date);
|
||||
top.appendChild(badge);
|
||||
|
||||
const moneyGrid = document.createElement("div");
|
||||
moneyGrid.className = "bill-money-grid";
|
||||
moneyGrid.appendChild(createMoneyItem(t("host_salary"), billMemberSalary(bill)));
|
||||
moneyGrid.appendChild(createMoneyItem(t("agent_salary"), billAgentSalary(bill)));
|
||||
moneyGrid.appendChild(createMoneyItem(t("total"), billTotal(bill)));
|
||||
|
||||
const more = document.createElement("button");
|
||||
more.type = "button";
|
||||
more.className = "bill-more-button";
|
||||
more.textContent = t("more");
|
||||
more.addEventListener("click", () => {
|
||||
openBillDetail(bill);
|
||||
});
|
||||
|
||||
card.appendChild(top);
|
||||
card.appendChild(moneyGrid);
|
||||
card.appendChild(more);
|
||||
return card;
|
||||
}
|
||||
|
||||
function setListStatus(message) {
|
||||
const status = document.querySelector("#listStatus");
|
||||
if (status) status.textContent = message || "";
|
||||
}
|
||||
|
||||
function renderBills() {
|
||||
renderSummary();
|
||||
const list = document.querySelector("#billList");
|
||||
if (!list) return;
|
||||
replaceNodeChildren(list, bills.map(createBillCard));
|
||||
setListStatus(bills.length ? "" : t("no_bills"));
|
||||
}
|
||||
|
||||
function pickMemberProfile(member) {
|
||||
return member?.memberProfile || member?.userProfile || member?.profile || member?.user || {};
|
||||
}
|
||||
|
||||
function pickMemberTarget(member) {
|
||||
return member?.target || member?.memberTarget || {};
|
||||
}
|
||||
|
||||
function pickMemberName(profile, member) {
|
||||
return profile?.userNickname || profile?.name || member?.userNickname || member?.name || t("unknown_user");
|
||||
}
|
||||
|
||||
function pickMemberAccount(profile, member) {
|
||||
return profile?.ownSpecialId?.account || profile?.account || member?.account || member?.userAccount || profile?.id || member?.userId || "-";
|
||||
}
|
||||
|
||||
function memberIdentifiers(member) {
|
||||
const profile = pickMemberProfile(member);
|
||||
return [
|
||||
profile?.id,
|
||||
profile?.account,
|
||||
profile?.ownSpecialId?.account,
|
||||
member?.userId,
|
||||
member?.account,
|
||||
member?.userAccount
|
||||
]
|
||||
.filter((value) => value !== null && value !== undefined && value !== "")
|
||||
.map((value) => String(value));
|
||||
}
|
||||
|
||||
function calculateTargetLevel(acceptGiftValue) {
|
||||
if (!Array.isArray(currentPolicy) || !currentPolicy.length) return "N/A";
|
||||
const giftValue = Number(acceptGiftValue);
|
||||
if (!Number.isFinite(giftValue)) return "N/A";
|
||||
|
||||
const sortedPolicy = currentPolicy
|
||||
.slice()
|
||||
.filter((item) => item && item.target !== null && item.target !== undefined)
|
||||
.sort((left, right) => Number(right.target) - Number(left.target));
|
||||
|
||||
for (const policy of sortedPolicy) {
|
||||
if (giftValue >= Number(policy.target)) return policy.level ?? "N/A";
|
||||
}
|
||||
|
||||
const levels = sortedPolicy
|
||||
.map((policy) => Number(policy.level))
|
||||
.filter((level) => Number.isFinite(level));
|
||||
return levels.length ? Math.min(...levels) : "N/A";
|
||||
}
|
||||
|
||||
function createMemberAvatar(name, avatarURL) {
|
||||
const avatar = document.createElement("div");
|
||||
avatar.className = "bill-member-avatar";
|
||||
|
||||
const fallback = document.createElement("span");
|
||||
fallback.textContent = (name || t("unknown_user")).slice(0, 1).toUpperCase();
|
||||
avatar.appendChild(fallback);
|
||||
|
||||
if (avatarURL) {
|
||||
const image = document.createElement("img");
|
||||
image.src = avatarURL;
|
||||
image.alt = name;
|
||||
image.addEventListener("error", () => {
|
||||
image.remove();
|
||||
});
|
||||
avatar.appendChild(image);
|
||||
}
|
||||
|
||||
return avatar;
|
||||
}
|
||||
|
||||
function createMemberCard(member) {
|
||||
const profile = pickMemberProfile(member);
|
||||
const target = pickMemberTarget(member);
|
||||
const name = pickMemberName(profile, member);
|
||||
const account = pickMemberAccount(profile, member);
|
||||
const giftValue = target?.acceptGiftValue || 0;
|
||||
const effectiveDay = target?.effectiveDay || 0;
|
||||
|
||||
const card = document.createElement("article");
|
||||
card.className = "bill-member-card";
|
||||
|
||||
const user = document.createElement("div");
|
||||
user.className = "bill-member-user";
|
||||
user.appendChild(createMemberAvatar(name, profile?.userAvatar || member?.userAvatar || member?.avatar || ""));
|
||||
|
||||
const copy = document.createElement("div");
|
||||
copy.className = "bill-member-copy";
|
||||
|
||||
const nameNode = document.createElement("div");
|
||||
nameNode.className = "bill-member-name";
|
||||
nameNode.textContent = name;
|
||||
|
||||
const accountNode = document.createElement("div");
|
||||
accountNode.className = "bill-member-account";
|
||||
accountNode.textContent = `${t("uid_prefix")} ${account}`.trim();
|
||||
|
||||
copy.appendChild(nameNode);
|
||||
copy.appendChild(accountNode);
|
||||
user.appendChild(copy);
|
||||
|
||||
const side = document.createElement("div");
|
||||
side.className = "bill-member-side";
|
||||
|
||||
const level = document.createElement("span");
|
||||
level.className = "bill-member-target";
|
||||
level.textContent = `${t("target")}: ${calculateTargetLevel(giftValue)}`;
|
||||
|
||||
const gift = document.createElement("div");
|
||||
gift.className = "bill-member-gift";
|
||||
gift.textContent = formatGift(giftValue);
|
||||
gift.title = `${t("gift_points")}: ${formatGift(giftValue)}`;
|
||||
|
||||
const days = document.createElement("div");
|
||||
days.className = "bill-member-days";
|
||||
days.textContent = `${t("days")}: ${effectiveDay}`;
|
||||
|
||||
side.appendChild(level);
|
||||
side.appendChild(gift);
|
||||
side.appendChild(days);
|
||||
|
||||
card.appendChild(user);
|
||||
card.appendChild(side);
|
||||
return card;
|
||||
}
|
||||
|
||||
function setMemberStatus(message) {
|
||||
const status = document.querySelector("#memberStatus");
|
||||
if (status) status.textContent = message || "";
|
||||
}
|
||||
|
||||
function renderSearchState() {
|
||||
const cancelButton = document.querySelector("#memberCancelButton");
|
||||
if (cancelButton) cancelButton.hidden = !memberSearchActive;
|
||||
}
|
||||
|
||||
function renderBillDetail() {
|
||||
const date = document.querySelector("#detailBillDate");
|
||||
const total = document.querySelector("#detailBillTotal");
|
||||
const status = document.querySelector("#detailBillStatus");
|
||||
const list = document.querySelector("#billMemberList");
|
||||
|
||||
if (date) date.textContent = selectedBill ? billTitle(selectedBill) : "-";
|
||||
if (total) total.textContent = selectedBill ? formatMoney(billTotal(selectedBill)) : "$0.00";
|
||||
if (status && selectedBill) setStatusBadge(status, selectedBill.status);
|
||||
if (list) replaceNodeChildren(list, displayMembers.map(createMemberCard));
|
||||
|
||||
renderSearchState();
|
||||
|
||||
if (detailLoading) {
|
||||
setMemberStatus(`${t("loading")}...`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!displayMembers.length) {
|
||||
setMemberStatus(memberSearchActive ? t("no_search_result") : t("no_members"));
|
||||
return;
|
||||
}
|
||||
|
||||
setMemberStatus("");
|
||||
}
|
||||
|
||||
async function ensureTeamId() {
|
||||
if (currentTeamId) return currentTeamId;
|
||||
const body = await requestJSON("/team/entry");
|
||||
currentTeamId = pickTeamId(pickTeamInfo(body));
|
||||
return currentTeamId;
|
||||
}
|
||||
|
||||
async function fetchBills() {
|
||||
const query = new URLSearchParams({ id: String(currentTeamId) });
|
||||
const body = await requestJSON(`/team/bill?${query.toString()}`);
|
||||
bills = pickRecords(body);
|
||||
renderBills();
|
||||
}
|
||||
|
||||
async function fetchPolicy() {
|
||||
const query = new URLSearchParams({ id: String(currentTeamId) });
|
||||
const body = await requestJSON(`/team/release/policy?${query.toString()}`);
|
||||
currentPolicy = pickPolicyRecords(body);
|
||||
}
|
||||
|
||||
async function fetchBillMembers(billId) {
|
||||
const query = new URLSearchParams({ id: String(billId) });
|
||||
const body = await requestJSON(`/team/bill/work-members?${query.toString()}`);
|
||||
return pickRecords(body);
|
||||
}
|
||||
|
||||
async function openBillDetail(bill) {
|
||||
selectedBill = bill;
|
||||
selectedMembers = [];
|
||||
displayMembers = [];
|
||||
memberSearchActive = false;
|
||||
detailLoading = true;
|
||||
const input = document.querySelector("#memberSearchInput");
|
||||
if (input) input.value = "";
|
||||
|
||||
const modal = document.querySelector("#billDetailModal");
|
||||
if (modal) modal.hidden = false;
|
||||
document.body.classList.add("daily-modal-open");
|
||||
renderBillDetail();
|
||||
|
||||
try {
|
||||
selectedMembers = await fetchBillMembers(bill.id);
|
||||
displayMembers = selectedMembers.slice();
|
||||
} catch (error) {
|
||||
console.error("Failed to load bill members:", error);
|
||||
showToast(error.message || t("failed_to_load_data"));
|
||||
selectedMembers = [];
|
||||
displayMembers = [];
|
||||
} finally {
|
||||
detailLoading = false;
|
||||
renderBillDetail();
|
||||
}
|
||||
}
|
||||
|
||||
function setBillDetailOpen(open) {
|
||||
const modal = document.querySelector("#billDetailModal");
|
||||
if (!modal) return;
|
||||
modal.hidden = !open;
|
||||
document.body.classList.toggle("daily-modal-open", open || isHelpOpen());
|
||||
if (!open) {
|
||||
selectedBill = null;
|
||||
selectedMembers = [];
|
||||
displayMembers = [];
|
||||
memberSearchActive = false;
|
||||
detailLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function isHelpOpen() {
|
||||
return !document.querySelector("#helpModal")?.hidden;
|
||||
}
|
||||
|
||||
function setHelpOpen(open) {
|
||||
const modal = document.querySelector("#helpModal");
|
||||
if (!modal) return;
|
||||
modal.hidden = !open;
|
||||
document.body.classList.toggle("daily-modal-open", open || !document.querySelector("#billDetailModal")?.hidden);
|
||||
}
|
||||
|
||||
function searchMembers() {
|
||||
const input = document.querySelector("#memberSearchInput");
|
||||
const query = String(input?.value || "").trim();
|
||||
if (!query) {
|
||||
cancelMemberSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
memberSearchActive = true;
|
||||
displayMembers = selectedMembers.filter((member) => memberIdentifiers(member).some((id) => id === query));
|
||||
renderBillDetail();
|
||||
}
|
||||
|
||||
function cancelMemberSearch() {
|
||||
const input = document.querySelector("#memberSearchInput");
|
||||
if (input) input.value = "";
|
||||
memberSearchActive = false;
|
||||
displayMembers = selectedMembers.slice();
|
||||
renderBillDetail();
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.querySelector("#toast");
|
||||
if (!toast) return;
|
||||
toast.textContent = message;
|
||||
toast.hidden = false;
|
||||
window.clearTimeout(showToast.timer);
|
||||
showToast.timer = window.setTimeout(() => {
|
||||
toast.hidden = true;
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
return;
|
||||
}
|
||||
window.location.href = buildPageURL("../agency-center/index.html");
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
document.querySelector(".back-button")?.addEventListener("click", goBack);
|
||||
|
||||
document.querySelector(".language-button")?.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
const languageMenu = document.querySelector(".language-menu");
|
||||
setLanguageMenuOpen(Boolean(languageMenu?.hidden));
|
||||
});
|
||||
|
||||
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
|
||||
button.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
setLanguageMenuOpen(false);
|
||||
applyLanguage(button.dataset.lang);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector(".bill-help-button")?.addEventListener("click", () => {
|
||||
setHelpOpen(true);
|
||||
});
|
||||
|
||||
document.querySelector("#memberSearchForm")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
searchMembers();
|
||||
});
|
||||
|
||||
document.querySelector("#memberCancelButton")?.addEventListener("click", cancelMemberSearch);
|
||||
|
||||
document.querySelectorAll("[data-close-detail]").forEach((node) => {
|
||||
node.addEventListener("click", () => setBillDetailOpen(false));
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-close-help]").forEach((node) => {
|
||||
node.addEventListener("click", () => setHelpOpen(false));
|
||||
});
|
||||
|
||||
document.addEventListener("click", () => {
|
||||
setLanguageMenuOpen(false);
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
setLanguageMenuOpen(false);
|
||||
setBillDetailOpen(false);
|
||||
setHelpOpen(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function initializePage() {
|
||||
setLoading(true);
|
||||
applyLanguage(currentLang);
|
||||
bindEvents();
|
||||
|
||||
if (!readRawParam("token")) {
|
||||
setLoading(false);
|
||||
setListStatus(t("missing_token_text"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ensureTeamId();
|
||||
if (!currentTeamId) throw new Error(t("missing_team"));
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
fetchBills(),
|
||||
fetchPolicy()
|
||||
]);
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") console.warn("Hosts bill request failed:", result.reason);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize hosts bill:", error);
|
||||
showToast(error.message || t("failed_to_load_data"));
|
||||
setListStatus(error.message || t("failed_to_load_data"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
initializePage();
|
||||
})();
|
||||
561
h5/hyapp/hosts-bill/style.css
Normal file
561
h5/hyapp/hosts-bill/style.css
Normal file
@ -0,0 +1,561 @@
|
||||
.hosts-bill-page {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.hosts-bill-content {
|
||||
gap: 12px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.bill-summary-card {
|
||||
display: grid;
|
||||
grid-template-columns: 54px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-height: 112px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.bill-summary-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 50%;
|
||||
background: #e7fbf8;
|
||||
color: #12a99c;
|
||||
}
|
||||
|
||||
.bill-summary-icon svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.bill-summary-icon path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2.25;
|
||||
}
|
||||
|
||||
.bill-summary-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bill-summary-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: #24282e;
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.bill-help-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex: 0 0 24px;
|
||||
color: #30343a;
|
||||
}
|
||||
|
||||
.bill-help-button svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.bill-help-button circle,
|
||||
.bill-help-button path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 1.8;
|
||||
}
|
||||
|
||||
.bill-summary-value {
|
||||
margin-top: 11px;
|
||||
overflow-wrap: anywhere;
|
||||
color: #23272d;
|
||||
font-size: 32px;
|
||||
font-weight: 950;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.bill-stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.bill-stat-card {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-height: 76px;
|
||||
padding: 15px 16px;
|
||||
}
|
||||
|
||||
.bill-stat-card span {
|
||||
color: #8f9196;
|
||||
font-size: 13px;
|
||||
font-weight: 850;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.bill-stat-card strong {
|
||||
color: #24282e;
|
||||
font-size: 24px;
|
||||
font-weight: 950;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.work-report-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 4px 2px -2px;
|
||||
}
|
||||
|
||||
.work-report-head h2 {
|
||||
margin: 0;
|
||||
color: #24282e;
|
||||
font-size: 16px;
|
||||
font-weight: 950;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.bill-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.bill-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 13px;
|
||||
padding: 15px 16px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.bill-card-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.bill-date {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #24282e;
|
||||
font-size: 17px;
|
||||
font-weight: 950;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bill-status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 128px;
|
||||
min-height: 24px;
|
||||
padding: 0 8px;
|
||||
border-radius: 5px;
|
||||
color: #3d73ff;
|
||||
background: #ebf1fd;
|
||||
font-size: 12px;
|
||||
font-weight: 950;
|
||||
line-height: 1.1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bill-status-badge.status-completed {
|
||||
color: #13a046;
|
||||
background: #e8faed;
|
||||
}
|
||||
|
||||
.bill-status-badge.status-out-of-account {
|
||||
color: #7a7d83;
|
||||
background: #e9e9e9;
|
||||
}
|
||||
|
||||
.bill-status-badge.status-pending {
|
||||
color: #ff3d40;
|
||||
background: #feebef;
|
||||
}
|
||||
|
||||
.bill-money-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bill-money-item {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bill-money-item span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: #8f9196;
|
||||
font-size: 12px;
|
||||
font-weight: 850;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bill-money-item strong {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
color: #24282e;
|
||||
font-size: 15px;
|
||||
font-weight: 950;
|
||||
line-height: 1.15;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bill-more-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-self: end;
|
||||
min-height: 34px;
|
||||
padding: 0 13px;
|
||||
border-radius: 8px;
|
||||
background: #f2fbf9;
|
||||
color: #10a890;
|
||||
font-size: 13px;
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
.bill-list-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 52px;
|
||||
color: #8f9196;
|
||||
font-size: 13px;
|
||||
font-weight: 850;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bill-list-status:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bill-detail-sheet {
|
||||
gap: 10px;
|
||||
padding-bottom: max(14px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.bill-detail-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
.bill-detail-meta div {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bill-detail-meta span:first-child {
|
||||
overflow: hidden;
|
||||
color: #777c84;
|
||||
font-size: 15px;
|
||||
font-weight: 850;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bill-detail-meta strong {
|
||||
color: #24282e;
|
||||
font-size: 20px;
|
||||
font-weight: 950;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.bill-search-form {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
.bill-search-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
height: 42px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #edf0f1;
|
||||
border-radius: 8px;
|
||||
background: #f8faf9;
|
||||
}
|
||||
|
||||
.bill-search-control svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 20px;
|
||||
margin-right: 9px;
|
||||
color: #7f858b;
|
||||
}
|
||||
|
||||
.bill-search-control path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2.2;
|
||||
}
|
||||
|
||||
.bill-search-control input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: #25282e;
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.bill-search-control input::placeholder {
|
||||
color: #a7aab0;
|
||||
}
|
||||
|
||||
.bill-search-button,
|
||||
.bill-cancel-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 64px;
|
||||
height: 42px;
|
||||
padding: 0 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 950;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bill-search-button {
|
||||
background: var(--green);
|
||||
color: #fff;
|
||||
box-shadow: 0 6px 14px rgba(37, 216, 120, 0.18);
|
||||
}
|
||||
|
||||
.bill-cancel-button {
|
||||
background: #f2f4f5;
|
||||
color: #4d525a;
|
||||
}
|
||||
|
||||
.bill-member-list {
|
||||
display: grid;
|
||||
gap: 9px;
|
||||
overflow-y: auto;
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
.bill-member-card {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
min-height: 76px;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 10px rgba(28, 38, 50, 0.08);
|
||||
}
|
||||
|
||||
.bill-member-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bill-member-avatar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex: 0 0 48px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(145deg, #55f2df 0%, #d9fff5 100%);
|
||||
color: #13745f;
|
||||
font-size: 19px;
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
.bill-member-avatar img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.bill-member-copy {
|
||||
min-width: 0;
|
||||
margin-left: 11px;
|
||||
}
|
||||
|
||||
.bill-member-name {
|
||||
overflow: hidden;
|
||||
color: #24282e;
|
||||
font-size: 15px;
|
||||
font-weight: 950;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bill-member-account {
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
color: #8f9196;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bill-member-side {
|
||||
display: grid;
|
||||
justify-items: end;
|
||||
gap: 5px;
|
||||
min-width: 88px;
|
||||
}
|
||||
|
||||
.bill-member-target {
|
||||
display: inline-flex;
|
||||
max-width: 118px;
|
||||
min-height: 23px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
padding: 0 7px;
|
||||
border-radius: 5px;
|
||||
background: linear-gradient(269deg, #feb219 7.04%, #ff9326 96.78%);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
line-height: 1.1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bill-member-gift {
|
||||
max-width: 118px;
|
||||
overflow: hidden;
|
||||
color: #ffb627;
|
||||
font-size: 13px;
|
||||
font-weight: 950;
|
||||
line-height: 1.15;
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bill-member-days {
|
||||
color: #8f9196;
|
||||
font-size: 11px;
|
||||
font-weight: 850;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.bill-member-status {
|
||||
min-height: 42px;
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
.bill-help-sheet {
|
||||
max-height: 80vh;
|
||||
max-height: 80dvh;
|
||||
}
|
||||
|
||||
.bill-help-content {
|
||||
overflow-y: auto;
|
||||
padding: 0 20px 22px;
|
||||
color: #555a62;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.bill-help-content p {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .bill-search-control svg {
|
||||
margin-right: 0;
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .bill-member-copy {
|
||||
margin-right: 11px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 370px) {
|
||||
.bill-summary-card {
|
||||
grid-template-columns: 48px minmax(0, 1fr);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.bill-summary-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.bill-money-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.bill-search-form {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.bill-cancel-button {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.bill-member-card {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.bill-member-side {
|
||||
justify-items: start;
|
||||
min-width: 0;
|
||||
margin-left: 59px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .bill-member-side {
|
||||
margin-right: 59px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user