feat: add hyapp bd leader center
This commit is contained in:
parent
d72a0502ec
commit
e1abcb0b4e
204
h5/hyapp/bd-leader-center/index.html
Normal file
204
h5/hyapp/bd-leader-center/index.html
Normal file
@ -0,0 +1,204 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<title>BD Leader Center</title>
|
||||
<link rel="stylesheet" href="./style.css?v=20260428-2130" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="bd-leader-center" aria-label="BD Leader Center" 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">BD Leader 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">
|
||||
<div class="avatar-shell">
|
||||
<img class="avatar-image" id="profileAvatar" alt="" hidden />
|
||||
<span class="avatar-fallback" id="profileAvatarFallback">B</span>
|
||||
</div>
|
||||
<div class="profile-copy">
|
||||
<div class="role-pill" data-i18n="role">BD Leader</div>
|
||||
<div class="name" id="profileName">-</div>
|
||||
<div class="meta" id="profileUid">UID: -</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card salary-card">
|
||||
<div class="salary-head">
|
||||
<div>
|
||||
<div class="salary-label" data-i18n="my_available_salary">My available salary</div>
|
||||
<div class="salary-period" id="billTitle">-</div>
|
||||
</div>
|
||||
<button class="text-link" type="button" data-route="/available-income" data-i18n="details">Details</button>
|
||||
</div>
|
||||
<div class="salary-value">
|
||||
<span aria-hidden="true">$</span>
|
||||
<strong id="availableBalance">0.00</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card metrics-card">
|
||||
<div class="metric">
|
||||
<div class="label" data-i18n="bd_number">BD number</div>
|
||||
<div class="value" id="bdNumber">0</div>
|
||||
</div>
|
||||
<div class="vertical-line" aria-hidden="true"></div>
|
||||
<div class="metric">
|
||||
<div class="label" data-i18n="team_salary">Team salary</div>
|
||||
<div class="value metric-money">
|
||||
<span aria-hidden="true">$</span>
|
||||
<span id="teamSalary">0.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card menu-card">
|
||||
<button class="menu-row" type="button" data-route="/policy?from=BDLead">
|
||||
<span class="menu-left">
|
||||
<span class="menu-icon policy-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 28 28"><path d="M8 5h10l3 3v15H8Z" /><path d="M18 5v5h4M11 14h7M11 18h6" /></svg>
|
||||
</span>
|
||||
<span data-i18n="bd_policy_link">BD policy</span>
|
||||
</span>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
<button class="menu-row" type="button" data-action="history">
|
||||
<span class="menu-left">
|
||||
<span class="menu-icon history-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 28 28"><path d="M14 5a9 9 0 1 1-8.1 5.1" /><path d="M6 5v5h5M14 10v5l4 2" /></svg>
|
||||
</span>
|
||||
<span data-i18n="bd_leader_history">BD Leader history</span>
|
||||
</span>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
<button class="menu-row" type="button" data-route="/agency-list">
|
||||
<span class="menu-left">
|
||||
<span class="menu-icon list-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 28 28"><path d="M8 8h12M8 14h12M8 20h12" /><path d="M4 8h.1M4 14h.1M4 20h.1" /></svg>
|
||||
</span>
|
||||
<span data-i18n="agency_list_link">Agency list</span>
|
||||
</span>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
<button class="menu-row" type="button" data-route="/bd-list">
|
||||
<span class="menu-left">
|
||||
<span class="menu-icon bd-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 28 28"><path d="M10 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM20 12a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM3 24c1-6 4-9 7-9s6 3 7 9ZM16 22c.6-3.8 2.2-5.8 4-5.8 2.2 0 4 2.3 5 5.8" /></svg>
|
||||
</span>
|
||||
<span data-i18n="bd_list_link">BD List</span>
|
||||
</span>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
<button class="menu-row" type="button" data-route="/invite-agency">
|
||||
<span class="menu-left">
|
||||
<span class="menu-icon invite-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 28 28"><path d="M11 13a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM4 24c1-5.5 3.5-8 7-8 2 0 3.7.8 5 2.5M21 10v10M16 15h10" /></svg>
|
||||
</span>
|
||||
<span data-i18n="invite_become_agent">Invite to become agent</span>
|
||||
</span>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
<button class="menu-row" type="button" data-route="/invite-bd">
|
||||
<span class="menu-left">
|
||||
<span class="menu-icon invite-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 28 28"><path d="M11 13a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM4 24c1-5.5 3.5-8 7-8 2 0 3.7.8 5 2.5M21 10v10M16 15h10" /></svg>
|
||||
</span>
|
||||
<span data-i18n="invite_become_bd">Invite to become BD</span>
|
||||
</span>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="card member-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<div class="section-title" data-i18n="bd_list_preview">BD List</div>
|
||||
<div class="section-subtitle" data-i18n="bd_list_preview_subtitle">Current settlement period</div>
|
||||
</div>
|
||||
<button class="text-link" type="button" data-route="/bd-list" data-i18n="more">More</button>
|
||||
</div>
|
||||
<div class="member-list" id="bdMemberList">
|
||||
<div class="empty-state" data-i18n="loading_data">Loading...</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="history-modal" id="historyModal" hidden>
|
||||
<div class="history-backdrop" data-close-history></div>
|
||||
<section class="history-dialog" role="dialog" aria-modal="true" aria-labelledby="historyTitle">
|
||||
<div class="history-head">
|
||||
<h2 id="historyTitle" data-i18n="bd_leader_history">BD Leader history</h2>
|
||||
<button class="history-close" type="button" aria-label="Close" data-i18n-aria="close" data-close-history>×</button>
|
||||
</div>
|
||||
<div class="history-summary">
|
||||
<div>
|
||||
<span data-i18n="total_income">Total income</span>
|
||||
<strong id="historyTotalIncome">$0.00</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span data-i18n="previous_period_income">Previous period income</span>
|
||||
<strong id="historyPreviousIncome">$0.00</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="history-list" id="historyList"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="details-modal" id="detailsModal" hidden>
|
||||
<div class="history-backdrop" data-close-details></div>
|
||||
<section class="history-dialog" role="dialog" aria-modal="true" aria-labelledby="detailsTitle">
|
||||
<div class="history-head">
|
||||
<h2 id="detailsTitle">-</h2>
|
||||
<button class="history-close" type="button" aria-label="Close" data-i18n-aria="close" data-close-details>×</button>
|
||||
</div>
|
||||
<div class="history-summary compact" id="detailsSummary"></div>
|
||||
<div class="history-list" id="detailsList"></div>
|
||||
</section>
|
||||
</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 class="toast" id="toast" role="status" aria-live="polite" hidden></div>
|
||||
</div>
|
||||
<script src="./script.js?v=20260428-2130" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
255
h5/hyapp/bd-leader-center/original-vue-logic.md
Normal file
255
h5/hyapp/bd-leader-center/original-vue-logic.md
Normal file
@ -0,0 +1,255 @@
|
||||
# BD Leader Center 原 Vue/旧 H5 逻辑清单
|
||||
|
||||
## 页面入口
|
||||
|
||||
- 旧入口:`h5/bd-leader-center/index.html`
|
||||
- 旧逻辑:`h5/bd-leader-center/bd-leader-center.js`
|
||||
- 旧公共工具:`h5/bd-static/shared.js`
|
||||
- 相关子页面:
|
||||
- `h5/agency-list/`:Agency list
|
||||
- `h5/bd-list/`:BD List
|
||||
- `h5/invite-agency/`:Invite to become agent
|
||||
- `h5/invite-bd/`:Invite to become BD
|
||||
- `h5/bd-item-distribution/`:Send welcome gift
|
||||
- `h5/available-income/`:Available salary details
|
||||
- `h5/policy/?from=BDLead`:BD Leader policy
|
||||
|
||||
## 鉴权和 App 通讯
|
||||
|
||||
- 旧逻辑优先通过 App bridge 获取 header:
|
||||
- Android:`window.app.getAccessOrigin()`
|
||||
- Flutter:`window.FlutterApp.postMessage("requestAccessOrigin")`
|
||||
- 回调:`window.renderData`、`window.getIosAccessOriginParam`
|
||||
- Header 会被解析为:
|
||||
- `authorization`
|
||||
- `req-lang`
|
||||
- `req-app-intel`
|
||||
- `req-imei`
|
||||
- `req-sys-origin`
|
||||
- `req-client`
|
||||
- `req-version`
|
||||
- `req-zone`
|
||||
- 新 `hyapp` 页面沿用 Host Center 的 URL token 模式:
|
||||
- 从 URL `token` 读取 token
|
||||
- 请求头传 `Authorization: Bearer <token>`
|
||||
- 同时传 `req-lang`、`req-client=H5`、`req-version=V2`、`req-zone`、`req-app-intel`、`req-sys-origin`
|
||||
|
||||
## 初始化流程
|
||||
|
||||
1. 设置语言和方向。
|
||||
2. 清空旧提现收款人缓存:
|
||||
- `sessionStorage.removeItem("payee")`
|
||||
- `localStorage.removeItem("payee")`
|
||||
3. 写入身份缓存:
|
||||
- `localStorage.identity = {"identity":"BD_LEADER"}`
|
||||
4. 获取基础资料:
|
||||
- `GET /team/member/profile`
|
||||
- `GET /team/entry`
|
||||
- `GET /app/h5/identity`
|
||||
5. 获取薪资总余额:
|
||||
- `GET /wallet/salary-account/balance/total`
|
||||
6. 渲染可用余额。
|
||||
|
||||
## 用户信息
|
||||
|
||||
接口:`GET /team/member/profile`
|
||||
|
||||
用途:
|
||||
- 头像
|
||||
- 昵称
|
||||
- UID/账号/靓号
|
||||
|
||||
可参考字段:
|
||||
- `id`
|
||||
- `account`
|
||||
- `userNickname`
|
||||
- `name`
|
||||
- `userAvatar`
|
||||
- `ownSpecialId.account`
|
||||
|
||||
## 团队信息
|
||||
|
||||
接口:`GET /team/entry`
|
||||
|
||||
用途:
|
||||
- 获取当前用户关联团队资料。
|
||||
- 旧代码取值:`body.teamProfile || body`
|
||||
|
||||
可参考字段:
|
||||
- `id`
|
||||
- `teamId`
|
||||
- `teamName`
|
||||
- `name`
|
||||
|
||||
## 身份信息
|
||||
|
||||
接口:`GET /app/h5/identity`
|
||||
|
||||
用途:
|
||||
- 判断当前用户身份。
|
||||
- 旧路由配置包含:
|
||||
- `bd`
|
||||
- `bdLeader`
|
||||
- `agent`
|
||||
- `anchor`
|
||||
|
||||
BD Leader Center 旧实现不强制拦截,只把页面身份设为 `BD_LEADER`。
|
||||
|
||||
## 可用薪资
|
||||
|
||||
接口:`GET /wallet/salary-account/balance/total`
|
||||
|
||||
用途:
|
||||
- 旧页面展示 `My available salary`
|
||||
|
||||
字段:
|
||||
- `availableBalance`
|
||||
- 其他余额字段以接口返回为准
|
||||
|
||||
旧页面展示:
|
||||
- `$${availableBalance || 0}`
|
||||
|
||||
## BD Leader 成员账单 / BD List 数据
|
||||
|
||||
接口:`GET /team/bd/member-bill/list?type=BDLEADER`
|
||||
|
||||
用途:
|
||||
- BD Leader 下属 BD 列表
|
||||
- 当前账单周期标题
|
||||
- BD 数量
|
||||
- 团队薪资汇总
|
||||
|
||||
字段:
|
||||
- `billTitle`
|
||||
- `memberCount`
|
||||
- `totalSalary`
|
||||
- `memberBillList[]`
|
||||
- `memberBillList[].agentAvatar`
|
||||
- `memberBillList[].agentName`
|
||||
- `memberBillList[].agentAccount`
|
||||
- `memberBillList[].teamSalaryAmount`
|
||||
- `memberBillList[].teamMemberCount`
|
||||
- `bdHistoryCO`
|
||||
|
||||
旧 `h5/bd-list` 展示:
|
||||
- `number_of_bd: memberCount`
|
||||
- `team_salary: totalSalary`
|
||||
- 每个 BD:
|
||||
- 头像:`agentAvatar`
|
||||
- 名称:`agentName`
|
||||
- ID:`agentAccount`
|
||||
- 团队薪资:`teamSalaryAmount`
|
||||
- 代理数量:`teamMemberCount`
|
||||
|
||||
## Agency List 数据
|
||||
|
||||
接口:`GET /team/bd/member-bill/list?type=BD`
|
||||
|
||||
用途:
|
||||
- Agency list
|
||||
- BD 下属代理列表
|
||||
- 团队充值/团队薪资/主播数
|
||||
|
||||
字段:
|
||||
- `billTitle`
|
||||
- `totalRecharge`
|
||||
- `totalSalary`
|
||||
- `memberBillList[].agentAvatar`
|
||||
- `memberBillList[].agentName`
|
||||
- `memberBillList[].agentAccount`
|
||||
- `memberBillList[].teamSalaryAmount`
|
||||
- `memberBillList[].teamRechargeAmount`
|
||||
- `memberBillList[].teamMemberCount`
|
||||
- `bdHistoryCO`
|
||||
|
||||
## BD Leader 历史收入
|
||||
|
||||
接口:`GET /team/bd/leader/history`
|
||||
|
||||
用途:
|
||||
- 展示 BD Leader 历史收入弹层。
|
||||
|
||||
字段:
|
||||
- `totalIncome`
|
||||
- `previousPeriodIncome`
|
||||
- `incomeDetails[]`
|
||||
- `incomeDetails[].billTitle`
|
||||
- `incomeDetails[].statusText`
|
||||
- `incomeDetails[].billBelong`
|
||||
- `incomeDetails[].bdNumber`
|
||||
- `incomeDetails[].teamSalaryAmount`
|
||||
|
||||
状态映射:
|
||||
- `UNPAID` / `In Progress` -> `In Progress`
|
||||
- `Pending` -> `Pending`
|
||||
- `SETTLED` / `PAY_OUT` / `Completed` -> `Completed`
|
||||
- `HANG_UP` / `Out of account` / `Out Of Account` -> `Out of account`
|
||||
|
||||
## BD Leader 历史详情
|
||||
|
||||
接口:`GET /team/bd/leader/history/more?billBelong=<normalizedBillBelong>`
|
||||
|
||||
参数:
|
||||
- `billBelong`
|
||||
- 旧工具 `historyMoreUrl` 会把各种日期格式转换成:
|
||||
- `YYYY-MM-DDTHH:mm:ss`
|
||||
|
||||
字段:
|
||||
- `period`
|
||||
- `teamSalary`
|
||||
- `bdNumber`
|
||||
- `memberDetails[]`
|
||||
- `memberDetails[].bdUserAvatar`
|
||||
- `memberDetails[].bdUserName`
|
||||
- `memberDetails[].account`
|
||||
- `memberDetails[].salary`
|
||||
- `memberDetails[].agencyCount`
|
||||
|
||||
## 邀请 BD
|
||||
|
||||
入口:`/invite-bd`
|
||||
|
||||
接口:
|
||||
- 列表:`GET /team/bd/invite-bd-message-own`
|
||||
- 搜索用户:`GET /user/user-profile/open-search?account=<account>`
|
||||
- 邀请:`POST /team/bd/invite-bd`
|
||||
- body:`{ inviteUserId }`
|
||||
- 取消邀请:`POST /team/bd/invite-bd-cancel/<id>`
|
||||
|
||||
## 邀请代理
|
||||
|
||||
入口:`/invite-agency`
|
||||
|
||||
接口:
|
||||
- 列表:`GET /team/bd/invite-message-own`
|
||||
- 邀请:`POST /team/bd/invite-agent`
|
||||
- body:`{ inviteUserId }`
|
||||
- 取消邀请:`POST /team/bd/invite-agent-cancel/<id>`
|
||||
|
||||
## 其他菜单功能
|
||||
|
||||
- `Details` -> `/available-income`
|
||||
- `BD policy` -> `/policy?from=BDLead`
|
||||
- `Agency list` -> `/agency-list`
|
||||
- `BD List` -> `/bd-list`
|
||||
- `Bank cards` -> `/bank-cards`
|
||||
- `Send welcome gift` -> `/bd-item-distribution`
|
||||
|
||||
## 新 `h5/hyapp/bd-leader-center` 第一版实现范围
|
||||
|
||||
- 复用 Host Center 风格:
|
||||
- 顶部状态栏和标题栏
|
||||
- 用户资料卡
|
||||
- 可用薪资卡
|
||||
- BD 数量/团队薪资统计卡
|
||||
- 功能入口列表
|
||||
- loading mask
|
||||
- 已接入:
|
||||
- `GET /team/member/profile`
|
||||
- `GET /app/h5/identity`
|
||||
- `GET /team/entry`
|
||||
- `GET /wallet/salary-account/balance/total`
|
||||
- `GET /team/bd/member-bill/list?type=BDLEADER`
|
||||
- `GET /team/bd/leader/history`
|
||||
- `GET /team/bd/leader/history/more?billBelong=...`
|
||||
- 已保留旧页面跳转入口。
|
||||
806
h5/hyapp/bd-leader-center/script.js
Normal file
806
h5/hyapp/bd-leader-center/script.js
Normal file
@ -0,0 +1,806 @@
|
||||
(function () {
|
||||
const supportedLanguages = ["en", "ar", "tr", "id"];
|
||||
const languageLabels = {
|
||||
en: "EN",
|
||||
ar: "AR",
|
||||
tr: "TR",
|
||||
id: "ID"
|
||||
};
|
||||
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
|
||||
const fallbackMessages = {
|
||||
en: {
|
||||
document_title: "BD Leader Center",
|
||||
page_label: "BD Leader Center",
|
||||
back: "Back",
|
||||
title: "BD Leader Center",
|
||||
language_button_aria: "Change language",
|
||||
role: "BD Leader",
|
||||
uid_prefix: "UID:",
|
||||
my_available_salary: "My available salary",
|
||||
details: "Details",
|
||||
bd_number: "BD number",
|
||||
team_salary: "Team salary",
|
||||
bd_policy_link: "BD policy",
|
||||
bd_leader_history: "BD Leader history",
|
||||
agency_list_link: "Agency list",
|
||||
bd_list_link: "BD List",
|
||||
invite_become_agent: "Invite to become agent",
|
||||
invite_become_bd: "Invite to become BD",
|
||||
bd_list_preview: "BD List",
|
||||
bd_list_preview_subtitle: "Current settlement period",
|
||||
more: "More",
|
||||
total_income: "Total income",
|
||||
previous_period_income: "Previous period income",
|
||||
salary: "Salary",
|
||||
agencies: "Agencies",
|
||||
loading_data: "Loading...",
|
||||
no_data: "No data",
|
||||
in_progress: "In Progress",
|
||||
pending: "Pending",
|
||||
completed: "Completed",
|
||||
out_of_account: "Out of account",
|
||||
close: "Close",
|
||||
failed_to_load: "Failed to load data",
|
||||
failed_to_load_history: "Failed to load history"
|
||||
},
|
||||
ar: {
|
||||
document_title: "مركز قائد BD",
|
||||
page_label: "مركز قائد BD",
|
||||
back: "رجوع",
|
||||
title: "مركز قائد BD",
|
||||
language_button_aria: "تغيير اللغة",
|
||||
role: "قائد BD",
|
||||
uid_prefix: "UID:",
|
||||
my_available_salary: "راتبي المتاح",
|
||||
details: "التفاصيل",
|
||||
bd_number: "عدد BD",
|
||||
team_salary: "راتب الفريق",
|
||||
bd_policy_link: "سياسة BD",
|
||||
bd_leader_history: "سجل قائد BD",
|
||||
agency_list_link: "قائمة الوكالات",
|
||||
bd_list_link: "قائمة BD",
|
||||
invite_become_agent: "دعوة ليصبح وكيلا",
|
||||
invite_become_bd: "دعوة ليصبح BD",
|
||||
bd_list_preview: "قائمة BD",
|
||||
bd_list_preview_subtitle: "فترة التسوية الحالية",
|
||||
more: "المزيد",
|
||||
total_income: "إجمالي الدخل",
|
||||
previous_period_income: "دخل الفترة السابقة",
|
||||
salary: "الراتب",
|
||||
agencies: "الوكالات",
|
||||
loading_data: "جار التحميل...",
|
||||
no_data: "لا توجد بيانات",
|
||||
in_progress: "قيد التنفيذ",
|
||||
pending: "قيد الانتظار",
|
||||
completed: "مكتمل",
|
||||
out_of_account: "خارج الحساب",
|
||||
close: "إغلاق",
|
||||
failed_to_load: "فشل تحميل البيانات",
|
||||
failed_to_load_history: "فشل تحميل السجل"
|
||||
},
|
||||
tr: {
|
||||
document_title: "BD Leader Merkezi",
|
||||
page_label: "BD Leader Merkezi",
|
||||
back: "Geri",
|
||||
title: "BD Leader Merkezi",
|
||||
language_button_aria: "Dili değiştir",
|
||||
role: "BD Leader",
|
||||
uid_prefix: "UID:",
|
||||
my_available_salary: "Kullanılabilir maaşım",
|
||||
details: "Detaylar",
|
||||
bd_number: "BD sayısı",
|
||||
team_salary: "Takım maaşı",
|
||||
bd_policy_link: "BD politikası",
|
||||
bd_leader_history: "BD Leader geçmişi",
|
||||
agency_list_link: "Ajans listesi",
|
||||
bd_list_link: "BD Listesi",
|
||||
invite_become_agent: "Ajans olmaya davet et",
|
||||
invite_become_bd: "BD olmaya davet et",
|
||||
bd_list_preview: "BD Listesi",
|
||||
bd_list_preview_subtitle: "Mevcut ödeme dönemi",
|
||||
more: "Daha fazla",
|
||||
total_income: "Toplam gelir",
|
||||
previous_period_income: "Önceki dönem geliri",
|
||||
salary: "Maaş",
|
||||
agencies: "Ajanslar",
|
||||
loading_data: "Yükleniyor...",
|
||||
no_data: "Veri yok",
|
||||
in_progress: "Devam ediyor",
|
||||
pending: "Beklemede",
|
||||
completed: "Tamamlandı",
|
||||
out_of_account: "Hesap dışı",
|
||||
close: "Kapat",
|
||||
failed_to_load: "Veri yüklenemedi",
|
||||
failed_to_load_history: "Geçmiş yüklenemedi"
|
||||
},
|
||||
id: {
|
||||
document_title: "Pusat BD Leader",
|
||||
page_label: "Pusat BD Leader",
|
||||
back: "Kembali",
|
||||
title: "Pusat BD Leader",
|
||||
language_button_aria: "Ubah bahasa",
|
||||
role: "BD Leader",
|
||||
uid_prefix: "UID:",
|
||||
my_available_salary: "Gaji tersedia",
|
||||
details: "Detail",
|
||||
bd_number: "Jumlah BD",
|
||||
team_salary: "Gaji tim",
|
||||
bd_policy_link: "Kebijakan BD",
|
||||
bd_leader_history: "Riwayat BD Leader",
|
||||
agency_list_link: "Daftar agensi",
|
||||
bd_list_link: "Daftar BD",
|
||||
invite_become_agent: "Undang menjadi agen",
|
||||
invite_become_bd: "Undang menjadi BD",
|
||||
bd_list_preview: "Daftar BD",
|
||||
bd_list_preview_subtitle: "Periode settlement saat ini",
|
||||
more: "Lainnya",
|
||||
total_income: "Total pendapatan",
|
||||
previous_period_income: "Pendapatan periode sebelumnya",
|
||||
salary: "Gaji",
|
||||
agencies: "Agensi",
|
||||
loading_data: "Memuat...",
|
||||
no_data: "Tidak ada data",
|
||||
in_progress: "Sedang diproses",
|
||||
pending: "Menunggu",
|
||||
completed: "Selesai",
|
||||
out_of_account: "Di luar akun",
|
||||
close: "Tutup",
|
||||
failed_to_load: "Gagal memuat data",
|
||||
failed_to_load_history: "Gagal memuat riwayat"
|
||||
}
|
||||
};
|
||||
|
||||
const params = readURLParams();
|
||||
let currentLang = normalizeLanguage(params.get("lang"));
|
||||
let currentMessages = fallbackMessages[currentLang] || fallbackMessages.en;
|
||||
const state = {
|
||||
profile: null,
|
||||
identity: null,
|
||||
teamEntry: null,
|
||||
balanceTotal: {},
|
||||
bill: {},
|
||||
history: null,
|
||||
historyMore: 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();
|
||||
if (value.startsWith("ar")) return "ar";
|
||||
if (value.startsWith("tr")) return "tr";
|
||||
if (value.startsWith("id")) return "id";
|
||||
return supportedLanguages.includes(value) ? value : "en";
|
||||
}
|
||||
|
||||
function message(key, fallback = "") {
|
||||
return currentMessages[key] || fallbackMessages.en[key] || fallback || key;
|
||||
}
|
||||
|
||||
function renderMessages(messages) {
|
||||
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
||||
const value = messages[node.dataset.i18n] || fallbackMessages.en[node.dataset.i18n];
|
||||
if (value) node.textContent = value;
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-i18n-aria]").forEach((node) => {
|
||||
const value = messages[node.dataset.i18nAria] || fallbackMessages.en[node.dataset.i18nAria];
|
||||
if (value) node.setAttribute("aria-label", value);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
const token = readRawParam("token");
|
||||
if (!token) throw new Error("Missing token");
|
||||
|
||||
const response = await fetch(new URL(path, `${apiBaseURL()}/`).toString(), {
|
||||
cache: "no-store",
|
||||
headers: authHeadersFromToken(token)
|
||||
});
|
||||
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 setLoading(isLoading) {
|
||||
const root = document.querySelector(".bd-leader-center");
|
||||
const mask = document.querySelector("#loadingMask");
|
||||
if (root) root.dataset.loading = isLoading ? "true" : "false";
|
||||
if (mask) mask.hidden = !isLoading;
|
||||
}
|
||||
|
||||
function updateModalLock() {
|
||||
const hasOpen = Array.from(document.querySelectorAll(".history-modal, .details-modal")).some((modal) => !modal.hidden);
|
||||
document.body.classList.toggle("modal-open", hasOpen);
|
||||
}
|
||||
|
||||
function setModalOpen(selector, open) {
|
||||
const modal = document.querySelector(selector);
|
||||
if (!modal) return;
|
||||
modal.hidden = !open;
|
||||
updateModalLock();
|
||||
}
|
||||
|
||||
function showToast(text) {
|
||||
const toast = document.querySelector("#toast");
|
||||
if (!toast) return;
|
||||
toast.textContent = text || "";
|
||||
toast.hidden = !text;
|
||||
window.clearTimeout(showToast.timer);
|
||||
showToast.timer = window.setTimeout(() => {
|
||||
toast.hidden = true;
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
const number = Number(value);
|
||||
if (!Number.isFinite(number)) return "0.00";
|
||||
return number.toFixed(2);
|
||||
}
|
||||
|
||||
function formatCount(value) {
|
||||
if (value === null || value === undefined || value === "") return "0";
|
||||
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 pickSpecialAccount(profile) {
|
||||
return profile?.ownSpecialId?.account || profile?.specialId?.account || profile?.specialAccount || profile?.shortAccount || profile?.shortId || "";
|
||||
}
|
||||
|
||||
function pickProfileName(profile) {
|
||||
return profile?.userNickname || profile?.name || "-";
|
||||
}
|
||||
|
||||
function pickProfileAccount(profile) {
|
||||
return pickSpecialAccount(profile) || profile?.account || profile?.id || "-";
|
||||
}
|
||||
|
||||
function renderAvatar(profile) {
|
||||
const image = document.querySelector("#profileAvatar");
|
||||
const fallback = document.querySelector("#profileAvatarFallback");
|
||||
if (!image || !fallback) return;
|
||||
|
||||
const name = pickProfileName(profile);
|
||||
const avatar = profile?.userAvatar || profile?.avatar || "";
|
||||
fallback.textContent = String(name || "B").slice(0, 1).toUpperCase() || "B";
|
||||
|
||||
if (!avatar) {
|
||||
image.hidden = true;
|
||||
image.removeAttribute("src");
|
||||
fallback.hidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
image.alt = name;
|
||||
fallback.hidden = true;
|
||||
image.hidden = false;
|
||||
image.onerror = () => {
|
||||
image.hidden = true;
|
||||
image.removeAttribute("src");
|
||||
fallback.hidden = false;
|
||||
};
|
||||
image.src = avatar;
|
||||
}
|
||||
|
||||
function renderProfile(profile = state.profile) {
|
||||
const name = pickProfileName(profile);
|
||||
const account = pickProfileAccount(profile);
|
||||
const nameNode = document.querySelector("#profileName");
|
||||
const uidNode = document.querySelector("#profileUid");
|
||||
if (nameNode) nameNode.textContent = name;
|
||||
if (uidNode) uidNode.textContent = `${message("uid_prefix")} ${account}`.trim();
|
||||
renderAvatar(profile);
|
||||
}
|
||||
|
||||
function renderBalance() {
|
||||
const balance = state.balanceTotal?.availableBalance ?? state.balanceTotal?.balance ?? 0;
|
||||
const node = document.querySelector("#availableBalance");
|
||||
if (node) node.textContent = formatMoney(balance);
|
||||
}
|
||||
|
||||
function renderBill() {
|
||||
const bill = state.bill || {};
|
||||
const members = Array.isArray(bill.memberBillList) ? bill.memberBillList : [];
|
||||
const billTitle = document.querySelector("#billTitle");
|
||||
const bdNumber = document.querySelector("#bdNumber");
|
||||
const teamSalary = document.querySelector("#teamSalary");
|
||||
if (billTitle) billTitle.textContent = bill.billTitle || "-";
|
||||
if (bdNumber) bdNumber.textContent = formatCount(bill.memberCount ?? bill.bdNumber ?? members.length);
|
||||
if (teamSalary) teamSalary.textContent = formatMoney(bill.totalSalary ?? bill.teamSalary ?? 0);
|
||||
renderMembers(members);
|
||||
}
|
||||
|
||||
function createFallbackAvatar(name) {
|
||||
const span = document.createElement("span");
|
||||
span.className = "member-fallback";
|
||||
span.textContent = String(name || "B").slice(0, 1).toUpperCase() || "B";
|
||||
return span;
|
||||
}
|
||||
|
||||
function createMemberRow(item) {
|
||||
const name = item?.agentName || item?.bdUserName || "-";
|
||||
const account = item?.agentAccount || item?.account || "-";
|
||||
const row = document.createElement("article");
|
||||
row.className = "member-row";
|
||||
|
||||
const avatar = document.createElement("div");
|
||||
avatar.className = "member-avatar";
|
||||
const avatarUrl = item?.agentAvatar || item?.bdUserAvatar || "";
|
||||
if (avatarUrl) {
|
||||
const image = document.createElement("img");
|
||||
image.src = avatarUrl;
|
||||
image.alt = "";
|
||||
image.addEventListener("error", () => {
|
||||
avatar.replaceChildren(createFallbackAvatar(name));
|
||||
});
|
||||
avatar.appendChild(image);
|
||||
} else {
|
||||
avatar.appendChild(createFallbackAvatar(name));
|
||||
}
|
||||
|
||||
const main = document.createElement("div");
|
||||
main.className = "member-main";
|
||||
const nameNode = document.createElement("div");
|
||||
nameNode.className = "member-name";
|
||||
nameNode.textContent = name;
|
||||
const metaNode = document.createElement("div");
|
||||
metaNode.className = "member-meta";
|
||||
metaNode.textContent = `${message("uid_prefix")} ${account}`.trim();
|
||||
main.append(nameNode, metaNode);
|
||||
|
||||
const side = document.createElement("div");
|
||||
side.className = "member-side";
|
||||
const salary = document.createElement("div");
|
||||
salary.textContent = `${message("team_salary")}: $${formatMoney(item?.teamSalaryAmount || 0)}`;
|
||||
const agencies = document.createElement("div");
|
||||
agencies.textContent = `${message("agencies")}: ${formatCount(item?.teamMemberCount || item?.agencyCount || 0)}`;
|
||||
side.append(salary, agencies);
|
||||
|
||||
row.append(avatar, main, side);
|
||||
return row;
|
||||
}
|
||||
|
||||
function replaceChildren(node, children) {
|
||||
while (node.firstChild) node.removeChild(node.firstChild);
|
||||
children.forEach((child) => node.appendChild(child));
|
||||
}
|
||||
|
||||
function renderMembers(members) {
|
||||
const list = document.querySelector("#bdMemberList");
|
||||
if (!list) return;
|
||||
const preview = members.slice(0, 5);
|
||||
if (!preview.length) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "empty-state";
|
||||
empty.textContent = message("no_data");
|
||||
replaceChildren(list, [empty]);
|
||||
return;
|
||||
}
|
||||
replaceChildren(list, preview.map(createMemberRow));
|
||||
}
|
||||
|
||||
function normalizeStatus(status) {
|
||||
if (status === "UNPAID" || status === "In Progress" || status === message("in_progress")) return message("in_progress");
|
||||
if (status === "Pending" || status === message("pending")) return message("pending");
|
||||
if (status === "SETTLED" || status === "PAY_OUT" || status === "Completed" || status === message("completed")) return message("completed");
|
||||
if (status === "HANG_UP" || status === "Out of account" || status === "Out Of Account" || status === message("out_of_account")) return message("out_of_account");
|
||||
return status || "";
|
||||
}
|
||||
|
||||
function statusClass(status) {
|
||||
const value = normalizeStatus(status);
|
||||
if (value === message("completed")) return "is-completed";
|
||||
if (value === message("pending")) return "is-pending";
|
||||
if (value === message("out_of_account")) return "is-out";
|
||||
return "is-progress";
|
||||
}
|
||||
|
||||
function isCompletedStatus(status) {
|
||||
return normalizeStatus(status) === message("completed");
|
||||
}
|
||||
|
||||
function createHistoryCell(label, value) {
|
||||
const cell = document.createElement("div");
|
||||
cell.className = "history-cell";
|
||||
const labelNode = document.createElement("span");
|
||||
labelNode.textContent = label;
|
||||
const valueNode = document.createElement("strong");
|
||||
valueNode.textContent = value;
|
||||
cell.append(labelNode, valueNode);
|
||||
return cell;
|
||||
}
|
||||
|
||||
function createHistoryRow(item) {
|
||||
const row = document.createElement("article");
|
||||
row.className = `history-row ${statusClass(item?.statusText)}`;
|
||||
|
||||
const status = document.createElement("div");
|
||||
status.className = "status-tag";
|
||||
status.textContent = normalizeStatus(item?.statusText);
|
||||
|
||||
const title = document.createElement("div");
|
||||
title.className = "history-title";
|
||||
title.textContent = item?.billTitle || "-";
|
||||
|
||||
const grid = document.createElement("div");
|
||||
grid.className = "history-grid";
|
||||
grid.append(
|
||||
createHistoryCell(message("bd_number"), formatCount(item?.bdNumber || 0)),
|
||||
createHistoryCell(message("team_salary"), `$${formatMoney(item?.teamSalaryAmount || 0)}`)
|
||||
);
|
||||
|
||||
row.append(status, title, grid);
|
||||
if (isCompletedStatus(item?.statusText)) {
|
||||
const more = document.createElement("button");
|
||||
more.className = "more-button";
|
||||
more.type = "button";
|
||||
more.textContent = message("more");
|
||||
more.addEventListener("click", () => showHistoryMore(item?.billBelong));
|
||||
row.appendChild(more);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
function renderHistory() {
|
||||
const history = state.history || {};
|
||||
const total = document.querySelector("#historyTotalIncome");
|
||||
const previous = document.querySelector("#historyPreviousIncome");
|
||||
const list = document.querySelector("#historyList");
|
||||
if (total) total.textContent = `$${formatMoney(history.totalIncome || 0)}`;
|
||||
if (previous) previous.textContent = `$${formatMoney(history.previousPeriodIncome || 0)}`;
|
||||
if (!list) return;
|
||||
|
||||
const items = Array.isArray(history.incomeDetails) ? history.incomeDetails : [];
|
||||
if (!items.length) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "empty-state";
|
||||
empty.textContent = message("no_data");
|
||||
replaceChildren(list, [empty]);
|
||||
return;
|
||||
}
|
||||
replaceChildren(list, items.map(createHistoryRow));
|
||||
}
|
||||
|
||||
function normalizeBillBelong(value) {
|
||||
const text = String(value || "").trim();
|
||||
if (!text) return "";
|
||||
const iso = text.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/);
|
||||
if (iso) return `${iso[1]}-${iso[2]}-${iso[3]}T${iso[4] || "00"}:${iso[5] || "00"}:${iso[6] || "00"}`;
|
||||
const compact = text.match(/^(\d{4})(\d{2})(\d{2})(?:(\d{2})(\d{2})(\d{2})?)?$/);
|
||||
if (compact) return `${compact[1]}-${compact[2]}-${compact[3]}T${compact[4] || "00"}:${compact[5] || "00"}:${compact[6] || "00"}`;
|
||||
return text;
|
||||
}
|
||||
|
||||
function historyMorePath(billBelong) {
|
||||
const normalized = normalizeBillBelong(billBelong);
|
||||
return normalized ? `/team/bd/leader/history/more?billBelong=${encodeURIComponent(normalized)}` : "";
|
||||
}
|
||||
|
||||
function createDetailsCell(label, value) {
|
||||
const cell = document.createElement("div");
|
||||
cell.className = "details-cell";
|
||||
const labelNode = document.createElement("span");
|
||||
labelNode.textContent = label;
|
||||
const valueNode = document.createElement("strong");
|
||||
valueNode.textContent = value;
|
||||
cell.append(labelNode, valueNode);
|
||||
return cell;
|
||||
}
|
||||
|
||||
function renderDetails() {
|
||||
const data = state.historyMore || {};
|
||||
const title = document.querySelector("#detailsTitle");
|
||||
const summary = document.querySelector("#detailsSummary");
|
||||
const list = document.querySelector("#detailsList");
|
||||
if (title) title.textContent = data.period || "-";
|
||||
if (summary) {
|
||||
replaceChildren(summary, [
|
||||
createDetailsCell(message("team_salary"), `$${formatMoney(data.teamSalary || 0)}`),
|
||||
createDetailsCell(message("bd_number"), formatCount(data.bdNumber || 0))
|
||||
]);
|
||||
}
|
||||
if (!list) return;
|
||||
|
||||
const members = Array.isArray(data.memberDetails) ? data.memberDetails : [];
|
||||
if (!members.length) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "empty-state";
|
||||
empty.textContent = message("no_data");
|
||||
replaceChildren(list, [empty]);
|
||||
return;
|
||||
}
|
||||
|
||||
replaceChildren(list, members.map((item) => {
|
||||
const row = createMemberRow({
|
||||
agentName: item.bdUserName,
|
||||
agentAvatar: item.bdUserAvatar,
|
||||
agentAccount: item.account,
|
||||
teamSalaryAmount: item.salary,
|
||||
teamMemberCount: item.agencyCount
|
||||
});
|
||||
row.classList.add("details-row");
|
||||
return row;
|
||||
}));
|
||||
}
|
||||
|
||||
async function fetchProfile() {
|
||||
state.profile = await requestJSON("/team/member/profile");
|
||||
renderProfile();
|
||||
}
|
||||
|
||||
async function fetchIdentity() {
|
||||
state.identity = await requestJSON("/app/h5/identity");
|
||||
}
|
||||
|
||||
async function fetchTeamEntry() {
|
||||
state.teamEntry = await requestJSON("/team/entry");
|
||||
}
|
||||
|
||||
async function fetchBalanceTotal() {
|
||||
state.balanceTotal = await requestJSON("/wallet/salary-account/balance/total") || {};
|
||||
renderBalance();
|
||||
}
|
||||
|
||||
async function fetchBDLeaderBill() {
|
||||
state.bill = await requestJSON("/team/bd/member-bill/list?type=BDLEADER") || {};
|
||||
renderBill();
|
||||
}
|
||||
|
||||
async function fetchHistory() {
|
||||
const history = await requestJSON("/team/bd/leader/history") || {};
|
||||
const currentHistory = state.bill?.bdHistoryCO;
|
||||
const details = Array.isArray(history.incomeDetails) ? [...history.incomeDetails] : [];
|
||||
if (currentHistory) details.unshift(currentHistory);
|
||||
state.history = { ...history, incomeDetails: details };
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
async function showHistoryModal() {
|
||||
try {
|
||||
setModalOpen("#historyModal", true);
|
||||
const list = document.querySelector("#historyList");
|
||||
if (list) {
|
||||
const loading = document.createElement("div");
|
||||
loading.className = "empty-state";
|
||||
loading.textContent = message("loading_data");
|
||||
replaceChildren(list, [loading]);
|
||||
}
|
||||
await fetchHistory();
|
||||
} catch (error) {
|
||||
console.error("Failed to load BD leader history:", error);
|
||||
showToast(error.response?.errorMsg || error.message || message("failed_to_load_history"));
|
||||
renderHistory();
|
||||
}
|
||||
}
|
||||
|
||||
async function showHistoryMore(billBelong) {
|
||||
const path = historyMorePath(billBelong);
|
||||
if (!path) return;
|
||||
try {
|
||||
state.historyMore = await requestJSON(path) || {};
|
||||
renderDetails();
|
||||
setModalOpen("#detailsModal", true);
|
||||
} catch (error) {
|
||||
console.error("Failed to load BD leader history more:", error);
|
||||
showToast(error.response?.errorMsg || error.message || message("failed_to_load_history"));
|
||||
}
|
||||
}
|
||||
|
||||
function buildRouteURL(route) {
|
||||
const url = new URL(route, window.location.origin);
|
||||
const token = readRawParam("token");
|
||||
if (token) url.searchParams.set("token", token);
|
||||
if (currentLang) url.searchParams.set("lang", currentLang);
|
||||
return `${url.pathname}${url.search}${url.hash}`;
|
||||
}
|
||||
|
||||
function navigateTo(route) {
|
||||
window.location.href = buildRouteURL(route);
|
||||
}
|
||||
|
||||
function closePage() {
|
||||
try {
|
||||
if (window.app && typeof window.app.closePage === "function") {
|
||||
window.app.closePage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") {
|
||||
window.FlutterPageControl.postMessage("close_page");
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.history.length > 1) window.history.back();
|
||||
} catch (error) {
|
||||
console.error("close BD leader center page failed:", error);
|
||||
if (window.history.length > 1) window.history.back();
|
||||
}
|
||||
}
|
||||
|
||||
function setLanguageMenuOpen(open) {
|
||||
const button = document.querySelector(".language-button");
|
||||
const menu = document.querySelector(".language-menu");
|
||||
if (!button || !menu) return;
|
||||
menu.hidden = !open;
|
||||
button.setAttribute("aria-expanded", String(open));
|
||||
}
|
||||
|
||||
function renderLanguageControls() {
|
||||
const button = document.querySelector(".language-button");
|
||||
if (button) {
|
||||
button.textContent = languageLabels[currentLang];
|
||||
button.setAttribute("aria-label", message("language_button_aria", "Change language"));
|
||||
}
|
||||
document.querySelectorAll(".language-menu [data-lang]").forEach((node) => {
|
||||
const active = node.dataset.lang === currentLang;
|
||||
node.classList.toggle("is-active", active);
|
||||
node.setAttribute("aria-pressed", String(active));
|
||||
});
|
||||
}
|
||||
|
||||
async function applyLanguage(lang) {
|
||||
currentLang = normalizeLanguage(lang);
|
||||
currentMessages = fallbackMessages[currentLang] || fallbackMessages.en;
|
||||
document.documentElement.lang = currentLang;
|
||||
document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr";
|
||||
document.title = message("document_title", "BD Leader Center");
|
||||
renderMessages(currentMessages);
|
||||
renderLanguageControls();
|
||||
renderProfile();
|
||||
renderBalance();
|
||||
renderBill();
|
||||
renderHistory();
|
||||
renderDetails();
|
||||
}
|
||||
|
||||
function clearPayee() {
|
||||
try {
|
||||
sessionStorage.removeItem("payee");
|
||||
localStorage.removeItem("payee");
|
||||
} catch (error) {
|
||||
console.debug("payee cache clear skipped:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function setIdentity() {
|
||||
try {
|
||||
localStorage.setItem("identity", JSON.stringify({ identity: "BD_LEADER" }));
|
||||
} catch (error) {
|
||||
console.debug("identity cache write skipped:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
document.querySelector(".back-button")?.addEventListener("click", closePage);
|
||||
document.querySelector(".language-button")?.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
const menu = document.querySelector(".language-menu");
|
||||
setLanguageMenuOpen(Boolean(menu?.hidden));
|
||||
});
|
||||
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
|
||||
button.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
setLanguageMenuOpen(false);
|
||||
applyLanguage(button.dataset.lang);
|
||||
});
|
||||
});
|
||||
document.addEventListener("click", () => setLanguageMenuOpen(false));
|
||||
document.querySelectorAll("[data-route]").forEach((node) => {
|
||||
node.addEventListener("click", () => navigateTo(node.dataset.route));
|
||||
});
|
||||
document.querySelector("[data-action='history']")?.addEventListener("click", showHistoryModal);
|
||||
document.querySelectorAll("[data-close-history]").forEach((node) => {
|
||||
node.addEventListener("click", () => setModalOpen("#historyModal", false));
|
||||
});
|
||||
document.querySelectorAll("[data-close-details]").forEach((node) => {
|
||||
node.addEventListener("click", () => setModalOpen("#detailsModal", false));
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
setModalOpen("#historyModal", false);
|
||||
setModalOpen("#detailsModal", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function initializePage() {
|
||||
setLoading(true);
|
||||
clearPayee();
|
||||
setIdentity();
|
||||
bindEvents();
|
||||
renderProfile();
|
||||
renderBalance();
|
||||
|
||||
try {
|
||||
await applyLanguage(currentLang);
|
||||
const tasks = [
|
||||
fetchProfile(),
|
||||
fetchIdentity(),
|
||||
fetchTeamEntry(),
|
||||
fetchBalanceTotal(),
|
||||
fetchBDLeaderBill()
|
||||
];
|
||||
const results = await Promise.allSettled(tasks);
|
||||
const rejected = results.find((item) => item.status === "rejected");
|
||||
if (rejected) {
|
||||
console.warn("BD leader center partial load failed:", rejected.reason);
|
||||
showToast(rejected.reason?.response?.errorMsg || rejected.reason?.message || message("failed_to_load"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize BD leader center:", error);
|
||||
showToast(error.response?.errorMsg || error.message || message("failed_to_load"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
initializePage();
|
||||
})();
|
||||
1009
h5/hyapp/bd-leader-center/style.css
Normal file
1009
h5/hyapp/bd-leader-center/style.css
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user