commit 952d3be4a98f28e6e2e348239f1f23867fe50a47
Author: whitechiina <1293616053@qq.com>
Date: Mon Apr 27 16:48:12 2026 +0800
first commit
diff --git a/.lanhu-home.png b/.lanhu-home.png
new file mode 100644
index 0000000..60f80f8
Binary files /dev/null and b/.lanhu-home.png differ
diff --git a/App.vue b/App.vue
new file mode 100644
index 0000000..ba00ee5
--- /dev/null
+++ b/App.vue
@@ -0,0 +1,78 @@
+
+
+
diff --git a/api/assets.js b/api/assets.js
new file mode 100644
index 0000000..0e44df0
--- /dev/null
+++ b/api/assets.js
@@ -0,0 +1,850 @@
+import serviceConfig from "../config/service";
+import request from "../utils/request";
+
+function createError(message, raw) {
+ return {
+ message: message || "接口请求失败",
+ raw: raw,
+ };
+}
+
+function toNumber(value) {
+ const number = Number(value || 0);
+ return Number.isFinite(number) ? number : 0;
+}
+
+function toFixedNumber(value, digits) {
+ return toNumber(value).toFixed(digits);
+}
+
+function formatHomeNumber(value, digits) {
+ const number = toNumber(value);
+
+ if (digits > 0) {
+ const fixed = number.toFixed(digits);
+ const parts = fixed.split(".");
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+ return parts.join(".");
+ }
+
+ return Math.round(number)
+ .toString()
+ .replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+}
+
+function isSuccessPayload(payload) {
+ if (!payload || typeof payload !== "object") {
+ return false;
+ }
+
+ const statusCode =
+ payload.code !== undefined ? Number(payload.code) : Number(payload.status);
+
+ return statusCode === 200;
+}
+
+function unwrapPayload(payload, fallbackMessage) {
+ if (isSuccessPayload(payload)) {
+ return payload.data;
+ }
+
+ if (payload && typeof payload === "object") {
+ throw createError(
+ payload.msg || payload.message || fallbackMessage || "接口请求失败",
+ payload,
+ );
+ }
+
+ throw createError(fallbackMessage || "接口返回异常", payload);
+}
+
+async function fetchPayload(options, fallbackMessage) {
+ try {
+ const payload = await request(options);
+ return unwrapPayload(payload, fallbackMessage);
+ } catch (error) {
+ if (error && error.message) {
+ throw error;
+ }
+
+ throw createError(fallbackMessage || "接口请求失败", error);
+ }
+}
+
+function createRequestOptions(baseOptions, requestOptions) {
+ return Object.assign({}, baseOptions, requestOptions || {});
+}
+
+function normalizeTicker(data) {
+ const close = toNumber(data && (data.close || data.cnyPrice));
+ const lastDayClose = toNumber(data && data.lastDayClose);
+ const rawChange = data && data.change;
+ let change = typeof rawChange === "string" ? rawChange : "";
+
+ if (!change) {
+ if (close && lastDayClose) {
+ const percent = ((close - lastDayClose) / lastDayClose) * 100;
+ const prefix = percent >= 0 ? "+" : "";
+ change = prefix + percent.toFixed(2) + "%";
+ } else {
+ change = "0.00%";
+ }
+ }
+
+ return {
+ symbol: (data && data.symbol) || "BMT/CNY",
+ close: close,
+ cnyPrice:
+ (data && data.cnyPrice) || (close ? close.toFixed(2) : "0.00"),
+ lastDayClose: lastDayClose,
+ change: change,
+ };
+}
+
+function normalizeBalances(data) {
+ return {
+ points: toNumber(data && data.point),
+ power: toNumber(data && data.c_power),
+ bmt: toNumber(data && data.bmt_num),
+ withdrawableBmt: toNumber(data && data.bmt_num),
+ voucher: toNumber(data && data.coin),
+ coupon: toNumber(data && data.diamond_balance),
+ };
+}
+
+function buildHomeOverview(balanceData, tickerData) {
+ const balances = normalizeBalances(balanceData);
+ const ticker = normalizeTicker(tickerData);
+
+ return {
+ title: "数字资产",
+ ticker: ticker,
+ topStats: [
+ {
+ key: "wallet-bmt",
+ title: "可提取BMT",
+ value: toFixedNumber(balances.withdrawableBmt, 2),
+ unit: "BMT",
+ accent: "gold",
+ },
+ {
+ key: "ticker",
+ title: "BMT实时价格",
+ value: toFixedNumber(ticker.close || ticker.cnyPrice, 3),
+ unit: "CNY/BMT",
+ accent: "green",
+ },
+ ],
+ quickAssets: [
+ {
+ key: "points",
+ title: "积分",
+ value: formatHomeNumber(balances.points, 0),
+ accent: "gold",
+ },
+ {
+ key: "voucher",
+ title: "抵用券",
+ value: formatHomeNumber(balances.voucher, 2),
+ accent: "rose",
+ },
+ {
+ key: "coupon",
+ title: "消费券",
+ value: formatHomeNumber(balances.coupon, 0),
+ accent: "teal",
+ },
+ {
+ key: "power",
+ title: "算力",
+ value: formatHomeNumber(balances.power, 0),
+ accent: "violet",
+ },
+ ],
+ features: [
+ {
+ key: "bmt-exchange",
+ title: "BMT兑换",
+ desc: "积分与算力兑换 BMT",
+ accent: "mint",
+ },
+ {
+ key: "power-exchange",
+ title: "算力兑换",
+ desc: "抵用券与消费券兑换算力",
+ accent: "amber",
+ },
+ {
+ key: "transfer",
+ title: "转赠中心",
+ desc: "积分或算力转赠好友",
+ accent: "indigo",
+ },
+ {
+ key: "withdraw",
+ title: "BMT提取",
+ desc: "钱包中的 BMT 可提取到交易所进行交易",
+ accent: "pink",
+ },
+ {
+ key: "points-convert",
+ title: "积分转换",
+ desc: "释放中的积分转换为可用积分",
+ accent: "pink",
+ },
+ ],
+ notice:
+ "数字资产是您在平台上的虚拟资产,请谨慎管理;BMT可在交易所中进行交易。",
+ };
+}
+
+function buildTransferTips(feePercent) {
+ const percentText = toNumber(feePercent) || 10;
+
+ return {
+ points: [
+ "只能转赠100的整数倍",
+ "凌晨0点-凌晨01点系统维护不可赠送",
+ "转赠系统会扣除" + percentText + "%的手续费",
+ ],
+ power: [
+ "只能转赠1的整数倍",
+ "转赠系统会扣除" + percentText + "%的手续费",
+ ],
+ };
+}
+
+function buildWalletList(address) {
+ const normalizedAddress = String(address || "").trim();
+
+ if (!normalizedAddress) {
+ return [];
+ }
+
+ return [
+ {
+ id: "default-wallet",
+ name: serviceConfig.WALLET_NAME,
+ address: normalizedAddress,
+ isDefault: true,
+ },
+ ];
+}
+
+function buildWalletPayload(address) {
+ return {
+ wallets: buildWalletList(address),
+ instructions: [
+ "点击交易所 App 底部“资产”进入钱包页",
+ "搜索或输入大写字母 BMT",
+ "点击“充币 / 充值”进入收款地址页面",
+ "复制钱包地址后回填到当前页面",
+ ],
+ };
+}
+
+function formatTransferRecordNumber(value) {
+ const number = toNumber(value);
+ return formatHomeNumber(number, Number.isInteger(number) ? 0 : 2);
+}
+
+function getTransferRecordUnit(item) {
+ return Number(item && item.type) === 0 ? "算力" : "积分";
+}
+
+function getTransferRecordTone(item) {
+ return Number(item && item.io_type) === 1 ? "success" : "danger";
+}
+
+function getTransferRecordTag(item) {
+ return Number(item && item.io_type) === 1 ? "收" : "赠";
+}
+
+function getTransferRecordDirection(item) {
+ return Number(item && item.io_type) === 1 ? "转入" : "转出";
+}
+
+function getTransferRecordTitle(item) {
+ const unit = getTransferRecordUnit(item);
+ return Number(item && item.io_type) === 1 ? unit + "获赠" : unit + "转赠";
+}
+
+function getTransferRecordSymbol(item) {
+ return Number(item && item.io_type) === 1 ? "+" : "-";
+}
+
+function getTransferRecordAmount(item) {
+ const numberText = formatTransferRecordNumber(item && item.num);
+ return getTransferRecordSymbol(item) + numberText;
+}
+
+function getTransferRecordFee(item) {
+ if (
+ item &&
+ item.fee !== undefined &&
+ item.fee !== null &&
+ String(item.fee).trim() !== ""
+ ) {
+ return toNumber(item.fee);
+ }
+
+ return (toNumber(item && item.num) * toNumber(item && item.fee_percent)) / 100;
+}
+
+function getTransferRecordFeeText(item) {
+ const percent = toNumber(item && item.fee_percent);
+ const unit = getTransferRecordUnit(item);
+ const feeText = formatTransferRecordNumber(getTransferRecordFee(item));
+
+ if (percent > 0) {
+ return (
+ "手续费 " +
+ feeText +
+ " " +
+ unit +
+ " (" +
+ formatTransferRecordNumber(percent) +
+ "%)"
+ );
+ }
+
+ return "手续费 " + feeText + " " + unit;
+}
+
+function getTransferRecordBalance(item) {
+ const unit = getTransferRecordUnit(item);
+ return "结余 " + toFixedNumber(item && item.balance, 2) + " " + unit;
+}
+
+function getTransferRecordBalanceLabel(item) {
+ const unit = getTransferRecordUnit(item);
+ return "剩余" + unit + ":" + formatTransferRecordNumber(item && item.balance);
+}
+
+function mapTransferRecords(list) {
+ return (Array.isArray(list) ? list : []).map(function (item) {
+ return {
+ id: item.order_sn || item.id || String(Math.random()),
+ title: item.title || getTransferRecordTitle(item),
+ subtitle: item.order_sn ? "单号 " + item.order_sn : getTransferRecordDirection(item),
+ time: item.add_time || "",
+ amount: getTransferRecordAmount(item),
+ balance: getTransferRecordBalance(item),
+ balanceLabel: getTransferRecordBalanceLabel(item),
+ assetLabel: getTransferRecordUnit(item),
+ feeText: getTransferRecordFeeText(item),
+ directionLabel: getTransferRecordDirection(item),
+ actionSymbol: getTransferRecordSymbol(item),
+ orderSn: item.order_sn || "",
+ tag: getTransferRecordTag(item),
+ tone: getTransferRecordTone(item),
+ cardTone: getTransferRecordTone(item),
+ };
+ });
+}
+
+function mapPointsConvertRecords(list) {
+ return (Array.isArray(list) ? list : []).map(function (item) {
+ const numberText = toFixedNumber(item.number, 2);
+ const transferCoinText = toFixedNumber(item.transfer_coin_num, 0);
+ const releaseTotal =
+ item &&
+ item.userBillRelease &&
+ item.userBillRelease.total !== undefined &&
+ item.userBillRelease.total !== null
+ ? String(item.userBillRelease.total)
+ : "";
+
+ return {
+ id: String(item.id || ""),
+ title: item.title || "积分记录",
+ subtitle: "可转数量 " + transferCoinText,
+ time: item.add_time || "",
+ amount: "+" + numberText,
+ balance: releaseTotal ? "释放总量 " + releaseTotal : "",
+ tag: "积",
+ tone: "success",
+ };
+ });
+}
+
+function buildDefaultLedger(type, balances) {
+ const map = {
+ power: {
+ title: "兑换记录",
+ subtitle: "算力兑换记录",
+ },
+ bmt: {
+ title: "兑换记录",
+ subtitle: "BMT兑换记录",
+ },
+ withdraw: {
+ title: "提取记录",
+ subtitle: "BMT提取流水",
+ },
+ coupon: {
+ title: "消费券",
+ subtitle: "当前消费券 " + toFixedNumber(balances.coupon, 2),
+ },
+ voucher: {
+ title: "抵用券记录",
+ subtitle: "可用抵用券 " + toFixedNumber(balances.voucher, 2),
+ },
+ };
+
+ return {
+ type: type,
+ title: map[type].title,
+ subtitle: map[type].subtitle,
+ records: [],
+ };
+}
+
+function normalizeTransferTarget(data, fallbackId) {
+ if (data && typeof data === "object" && !Array.isArray(data)) {
+ const id = String(data.uid || data.id || fallbackId || "").trim();
+ if (!id) {
+ throw createError("未查询到好友", data);
+ }
+
+ return {
+ id: id,
+ nickname:
+ data.nickname || data.nick_name || data.username || "用户" + id,
+ phone: data.mobile || data.phone || "ID已通过校验",
+ avatar: data.avatar || data.headimg || "",
+ };
+ }
+
+ if (data === true) {
+ const id = String(fallbackId || "").trim();
+ if (!id) {
+ throw createError("未查询到好友", data);
+ }
+
+ return {
+ id: id,
+ nickname: "用户" + id,
+ phone: "ID已通过校验",
+ avatar: "",
+ };
+ }
+
+ throw createError("未查询到好友", data);
+}
+
+function sumBy(list, key) {
+ return (Array.isArray(list) ? list : []).reduce(function (total, item) {
+ return total + toNumber(item && item[key]);
+ }, 0);
+}
+
+async function fetchPriceData(requestOptions) {
+ return fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.price,
+ }, requestOptions),
+ "实时价格加载失败",
+ );
+}
+
+async function fetchHomeBalanceData(requestOptions) {
+ return fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.homeBalance,
+ }, requestOptions),
+ "首页资产加载失败",
+ );
+}
+
+async function fetchBmtPowerRateData(requestOptions) {
+ return fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.bmtRedeemPowerRate,
+ }, requestOptions),
+ "兑换比例加载失败",
+ );
+}
+
+async function fetchTransferFeeData(requestOptions) {
+ return fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.transferFee,
+ }, requestOptions),
+ "手续费比例加载失败",
+ );
+}
+
+async function fetchWalletAddressData(requestOptions) {
+ return fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.walletDetail,
+ }, requestOptions),
+ "钱包加载失败",
+ );
+}
+
+async function fetchPointsConvertList(requestOptions) {
+ return fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.pointsConvertList,
+ data: {
+ interval: serviceConfig.POINTS_CONVERT_INTERVAL,
+ },
+ }, requestOptions),
+ "积分转换列表加载失败",
+ );
+}
+
+async function fetchTransferLedgerData(requestOptions) {
+ return fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.transferLedger,
+ }, requestOptions),
+ "转赠记录加载失败",
+ );
+}
+
+export async function fetchAssetHome(requestOptions) {
+ const result = await Promise.all([
+ fetchPriceData(requestOptions),
+ fetchHomeBalanceData(requestOptions),
+ ]);
+ return buildHomeOverview(result[1], result[0]);
+}
+
+export async function fetchPointsConvertDetail(requestOptions) {
+ const result = await Promise.all([
+ fetchHomeBalanceData(requestOptions),
+ fetchPointsConvertList(requestOptions),
+ ]);
+ const balances = normalizeBalances(result[0]);
+ const pointList = Array.isArray(result[1] && result[1].list)
+ ? result[1].list
+ : [];
+
+ return {
+ availablePoints: toFixedNumber(balances.points, 2),
+ pendingPoints: toFixedNumber(sumBy(pointList, "number"), 0),
+ ids: pointList
+ .map(function (item) {
+ return item && item.id;
+ })
+ .filter(Boolean),
+ tips: [
+ "释放中的积分,转换成可用积分后,方可兑换BMT;",
+ "凌晨0点-凌晨1点积分系统维护不可兑换。",
+ ],
+ };
+}
+
+export async function submitAssetPointsConvert(payload, requestOptions) {
+ const ids = Array.isArray(payload && payload.ids) ? payload.ids : [];
+
+ if (!ids.length) {
+ throw createError("暂无可转换积分");
+ }
+
+ await fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.pointsConvertSubmit,
+ method: "POST",
+ data: {
+ ids: ids.join(","),
+ type: 1,
+ },
+ }, requestOptions),
+ "积分转换失败",
+ );
+
+ return {
+ success: true,
+ message: "转换成功",
+ };
+}
+
+export async function fetchTransferDetail(requestOptions) {
+ const result = await Promise.all([
+ fetchHomeBalanceData(requestOptions),
+ fetchTransferFeeData(requestOptions),
+ ]);
+ const balances = normalizeBalances(result[0]);
+ const feePercent = toNumber(result[1] && result[1].r) || 10;
+
+ return {
+ balances: {
+ points: toFixedNumber(balances.points, 0),
+ power: toFixedNumber(balances.power, 0),
+ },
+ feePercent: feePercent,
+ tips: buildTransferTips(feePercent),
+ };
+}
+
+export async function searchTransferUser(uid, requestOptions) {
+ const keyword = String(uid || "").trim();
+ if (!keyword) {
+ throw createError("请输入被赠人ID");
+ }
+
+ const data = await fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.transferUser,
+ method: "POST",
+ data: {
+ uid: keyword,
+ },
+ }, requestOptions),
+ "查询好友失败",
+ );
+
+ return normalizeTransferTarget(data, keyword);
+}
+
+export async function submitAssetTransfer(payload, requestOptions) {
+ const transferType = payload && payload.type === "power" ? "power" : "points";
+ const targetId = String(payload && payload.targetId ? payload.targetId : "").trim();
+ const amount = toNumber(payload && payload.amount);
+
+ if (!targetId) {
+ throw createError("请选择被赠送人");
+ }
+
+ if (!amount) {
+ throw createError("请输入转赠数量");
+ }
+
+ const result = await Promise.all([
+ fetchTransferFeeData(requestOptions),
+ fetchPayload(
+ createRequestOptions({
+ url:
+ transferType === "power"
+ ? serviceConfig.ENDPOINTS.transferPowerSubmit
+ : serviceConfig.ENDPOINTS.transferPointsSubmit,
+ method: "POST",
+ data: {
+ uid: targetId,
+ number: String(amount),
+ },
+ }, requestOptions),
+ "转赠失败",
+ ),
+ ]);
+ const feePercent = toNumber(result[0] && result[0].r) || 10;
+ const fee = (amount * feePercent) / 100;
+ const received = amount - fee;
+
+ return {
+ success: true,
+ fee: toFixedNumber(fee, 2),
+ received: toFixedNumber(received, 2),
+ };
+}
+
+export async function fetchPowerExchangeDetail(requestOptions) {
+ const result = await Promise.all([
+ fetchPriceData(requestOptions),
+ fetchHomeBalanceData(requestOptions),
+ ]);
+ const ticker = normalizeTicker(result[0]);
+ const balances = normalizeBalances(result[1]);
+
+ return {
+ ticker: ticker,
+ balances: {
+ coupon: toFixedNumber(balances.coupon, 2),
+ voucher: toFixedNumber(balances.voucher, 2),
+ power: toFixedNumber(balances.power, 0),
+ },
+ tips: [
+ "算力 = 抵用券或消费券 ÷ BMT实时价格;",
+ "抵用券和消费券总数小于100券的不可兑换;",
+ "算力用于兑换BMT使用。",
+ ],
+ };
+}
+
+export async function submitAssetPowerExchange(payload, requestOptions) {
+ const mode = payload && payload.mode === "coupon" ? "coupon" : "voucher";
+ const amount = toNumber(payload && payload.amount);
+
+ if (!amount) {
+ throw createError("请输入兑换数量");
+ }
+
+ await fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.powerExchangeSubmit,
+ method: "POST",
+ data: {
+ type: mode === "coupon" ? 1 : 0,
+ number: String(amount),
+ },
+ }, requestOptions),
+ "算力兑换失败",
+ );
+
+ return {
+ success: true,
+ };
+}
+
+export async function fetchBmtExchangeDetail(requestOptions) {
+ const result = await Promise.all([
+ fetchPriceData(requestOptions),
+ fetchHomeBalanceData(requestOptions),
+ fetchBmtPowerRateData(requestOptions),
+ ]);
+ const ticker = normalizeTicker(result[0]);
+ const balances = normalizeBalances(result[1]);
+ const powerRate = toNumber(result[2]);
+
+ return {
+ ticker: ticker,
+ powerRate: powerRate,
+ balances: {
+ points: toFixedNumber(balances.points, 0),
+ power: toFixedNumber(balances.power, 2),
+ bmt: toFixedNumber(balances.bmt, 2),
+ voucher: toFixedNumber(balances.voucher, 2),
+ coupon: toFixedNumber(balances.coupon, 2),
+ },
+ tips: [
+ "BMT=输入的积分数量,提交时会同步校验所需算力。",
+ "兑换所需算力按后端返回比例实时计算。",
+ "只能兑换100的整数倍,小于100积分不可兑换。",
+ "凌晨0点至凌晨1点积分系统维护期间不可兑换。",
+ ],
+ };
+}
+
+export async function submitAssetBmtExchange(payload, requestOptions) {
+ const amount = toNumber(payload && payload.amount);
+
+ if (!amount) {
+ throw createError("请输入积分数量");
+ }
+
+ await fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.bmtExchangeSubmit,
+ method: "POST",
+ data: {
+ number: String(amount),
+ },
+ }, requestOptions),
+ "BMT兑换失败",
+ );
+
+ return {
+ success: true,
+ };
+}
+
+export async function fetchWithdrawDetail(requestOptions) {
+ const result = await Promise.all([
+ fetchPriceData(requestOptions),
+ fetchHomeBalanceData(requestOptions),
+ fetchWalletAddressData(requestOptions),
+ ]);
+ const ticker = normalizeTicker(result[0]);
+ const balances = normalizeBalances(result[1]);
+ const walletPayload = buildWalletPayload(result[2] && result[2].address);
+
+ return {
+ ticker: ticker,
+ withdrawableBmt: toFixedNumber(balances.withdrawableBmt, 2),
+ wallets: walletPayload.wallets,
+ defaultWallet: walletPayload.wallets[0] || null,
+ };
+}
+
+export function submitAssetWithdraw(payload, requestOptions) {
+ return Promise.reject(
+ createError("当前接口文档未提供 BMT 提取提交接口"),
+ );
+}
+
+export async function fetchLedgerDetail(type, requestOptions) {
+ if (type === "transfer") {
+ const data = await fetchTransferLedgerData(requestOptions);
+ return {
+ type: type,
+ title: "转赠记录",
+ subtitle: "积分与算力转赠流水",
+ records: mapTransferRecords(data && data.list),
+ };
+ }
+
+ if (type === "points") {
+ const result = await Promise.all([
+ fetchHomeBalanceData(requestOptions),
+ fetchPointsConvertList(requestOptions),
+ ]);
+ const balances = normalizeBalances(result[0]);
+ const pointList = Array.isArray(result[1] && result[1].list)
+ ? result[1].list
+ : [];
+
+ return {
+ type: type,
+ title: "我的积分",
+ subtitle: "可转换积分记录",
+ summary: {
+ label: "有效积分",
+ value: toFixedNumber(balances.points, 0),
+ },
+ records: mapPointsConvertRecords(pointList),
+ };
+ }
+
+ const homeData = await fetchHomeBalanceData(requestOptions);
+ const balances = normalizeBalances(homeData);
+ return buildDefaultLedger(type, balances);
+}
+
+export async function fetchWalletDetail(requestOptions) {
+ const data = await fetchWalletAddressData(requestOptions);
+ return buildWalletPayload(data && data.address);
+}
+
+export async function saveAssetWallet(payload, requestOptions) {
+ const address = String(payload && payload.address ? payload.address : "").trim();
+
+ if (!address) {
+ throw createError("请输入钱包地址");
+ }
+
+ await fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.walletSave,
+ method: "POST",
+ data: {
+ address: address,
+ },
+ }, requestOptions),
+ "保存失败",
+ );
+
+ return {
+ success: true,
+ };
+}
+
+export async function deleteAssetWallet(id, requestOptions) {
+ await fetchPayload(
+ createRequestOptions({
+ url: serviceConfig.ENDPOINTS.walletSave,
+ method: "POST",
+ data: {
+ address: "",
+ },
+ }, requestOptions),
+ "删除失败",
+ );
+
+ return {
+ success: true,
+ };
+}
diff --git a/components/asset-confirm-popup.vue b/components/asset-confirm-popup.vue
new file mode 100644
index 0000000..263c96b
--- /dev/null
+++ b/components/asset-confirm-popup.vue
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+
diff --git a/components/asset-page-shell.vue b/components/asset-page-shell.vue
new file mode 100644
index 0000000..243b573
--- /dev/null
+++ b/components/asset-page-shell.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+ {{ rightText }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/asset-record-list.vue b/components/asset-record-list.vue
new file mode 100644
index 0000000..ff489be
--- /dev/null
+++ b/components/asset-record-list.vue
@@ -0,0 +1,184 @@
+
+
+
+
+
+ {{ item.tag || "记" }}
+
+
+
+ {{ item.title }}
+
+ {{ item.amount }}
+
+
+ {{
+ item.subtitle
+ }}
+
+ {{ item.time }}
+ {{
+ item.balance
+ }}
+
+
+
+
+
+ {{ emptyTitle }}
+ {{ emptyDesc }}
+
+
+
+
+
+
+
diff --git a/components/wallet-action-popup.vue b/components/wallet-action-popup.vue
new file mode 100644
index 0000000..97e09c2
--- /dev/null
+++ b/components/wallet-action-popup.vue
@@ -0,0 +1,101 @@
+
+
+
+ {{ message }}
+ {{ address }}
+ {{
+ description
+ }}
+
+
+
+
+
+
+
diff --git a/config/service.js b/config/service.js
new file mode 100644
index 0000000..74edb41
--- /dev/null
+++ b/config/service.js
@@ -0,0 +1,25 @@
+const serviceConfig = {
+ BASE_URL: "https://tpoint.agrimedia.cn",
+ TIMEOUT: 10000,
+ WALLET_NAME: "海南农综交易所",
+ POINTS_CONVERT_INTERVAL: "0,999999999",
+ ENDPOINTS: {
+ price: "/api/hn/getPrice",
+ homeBalance: "/api/hn/getAllBalance",
+ powerExchangeSubmit: "/api/hn/redeem/power",
+ bmtRedeemPowerRate: "/api/hn/redeem/getRedeemPowerRate",
+ bmtExchangeSubmit: "/api/hn/redeem/redeem_bmt",
+ transferFee: "/api/hn/transfer/getProp",
+ transferUser: "/api/hn/transfer/getUserInfo",
+ transferPowerSubmit: "/api/hn/transfer/transferPower",
+ transferPointsSubmit: "/api/hn/transfer/transferPoint",
+ transferLedger: "/api/hn/transfer/transferList",
+ walletDetail: "/api/hn/wallet/getWalletAddress",
+ walletSave: "/api/hn/wallet/saveAddress",
+ pointsConvertList: "/api/integral/transferList",
+ pointsConvertSubmit: "/api/integral/doTransfer",
+ pointsConvertInfo: "/api/integral/transferInfo",
+ },
+};
+
+export default serviceConfig;
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..2501934
--- /dev/null
+++ b/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..c1caf36
--- /dev/null
+++ b/main.js
@@ -0,0 +1,22 @@
+import App from './App'
+
+// #ifndef VUE3
+import Vue from 'vue'
+import './uni.promisify.adaptor'
+Vue.config.productionTip = false
+App.mpType = 'app'
+const app = new Vue({
+ ...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import { createSSRApp } from 'vue'
+export function createApp() {
+ const app = createSSRApp(App)
+ return {
+ app
+ }
+}
+// #endif
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..721e850
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,72 @@
+{
+ "name" : "白马交易所",
+ "appid" : "__UNI__3EC3CC8",
+ "description" : "",
+ "versionName" : "1.0.0",
+ "versionCode" : "100",
+ "transformPx" : false,
+ /* 5+App特有相关 */
+ "app-plus" : {
+ "usingComponents" : true,
+ "nvueStyleCompiler" : "uni-app",
+ "compilerVersion" : 3,
+ "splashscreen" : {
+ "alwaysShowBeforeRender" : true,
+ "waiting" : true,
+ "autoclose" : true,
+ "delay" : 0
+ },
+ /* 模块配置 */
+ "modules" : {},
+ /* 应用发布信息 */
+ "distribute" : {
+ /* android打包配置 */
+ "android" : {
+ "permissions" : [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ]
+ },
+ /* ios打包配置 */
+ "ios" : {},
+ /* SDK配置 */
+ "sdkConfigs" : {}
+ }
+ },
+ /* 快应用特有相关 */
+ "quickapp" : {},
+ /* 小程序特有相关 */
+ "mp-weixin" : {
+ "appid" : "",
+ "setting" : {
+ "urlCheck" : false
+ },
+ "usingComponents" : true
+ },
+ "mp-alipay" : {
+ "usingComponents" : true
+ },
+ "mp-baidu" : {
+ "usingComponents" : true
+ },
+ "mp-toutiao" : {
+ "usingComponents" : true
+ },
+ "uniStatistics" : {
+ "enable" : false
+ },
+ "vueVersion" : "2"
+}
diff --git a/pages.json b/pages.json
new file mode 100644
index 0000000..e3ae41e
--- /dev/null
+++ b/pages.json
@@ -0,0 +1,75 @@
+{
+ "pages": [
+ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+ {
+ "path": "pages/index/index",
+ "style": {
+ "navigationStyle": "custom",
+ "backgroundColor": "#191E32"
+ }
+ },
+ {
+ "path": "pages/assets/transfer",
+ "style": {
+ "navigationStyle": "custom",
+ "backgroundColor": "#191E32"
+ }
+ },
+ {
+ "path": "pages/assets/power-exchange",
+ "style": {
+ "navigationStyle": "custom",
+ "backgroundColor": "#191E32"
+ }
+ },
+ {
+ "path": "pages/assets/bmt-exchange",
+ "style": {
+ "navigationStyle": "custom",
+ "backgroundColor": "#191E32"
+ }
+ },
+ {
+ "path": "pages/assets/withdraw",
+ "style": {
+ "navigationStyle": "custom",
+ "backgroundColor": "#191E32"
+ }
+ },
+ {
+ "path": "pages/assets/points-convert",
+ "style": {
+ "navigationStyle": "custom",
+ "backgroundColor": "#191E32"
+ }
+ },
+ {
+ "path": "pages/assets/ledger",
+ "style": {
+ "navigationStyle": "custom",
+ "backgroundColor": "#191E32"
+ }
+ },
+ {
+ "path": "pages/assets/wallet",
+ "style": {
+ "navigationStyle": "custom",
+ "backgroundColor": "#191E32"
+ }
+ },
+ {
+ "path": "pages/assets/wallet-form",
+ "style": {
+ "navigationStyle": "custom",
+ "backgroundColor": "#191E32"
+ }
+ }
+ ],
+ "globalStyle": {
+ "navigationBarTextStyle": "white",
+ "navigationBarTitleText": "数字资产",
+ "navigationBarBackgroundColor": "#191E32",
+ "backgroundColor": "#191E32"
+ },
+ "uniIdRouter": {}
+}
diff --git a/pages/assets/bmt-exchange.vue b/pages/assets/bmt-exchange.vue
new file mode 100644
index 0000000..c47bc32
--- /dev/null
+++ b/pages/assets/bmt-exchange.vue
@@ -0,0 +1,694 @@
+
+
+
+
+
+
+
+
+
+
+
+ BMT
+
+ 可用BMT:
+ {{ displayBmt }}
+
+
+
+
+
+
+
+ 我的抵用券
+
+ {{ displayVoucher }}
+
+
+
+
+
+ 我的消费券
+
+ {{ displayCoupon }}
+
+
+
+
+
+ BMT实时价格
+
+
+ {{ displayPrice }}
+ CNY/BMT
+
+
+
+
+ 全部兑换预估可得
+ {{ allEstimateBmt }}
+ BMT
+
+
+
+
+
+
+ BMT兑换
+
+
+
+
+ 积分
+
+
+
+
+
+ 预估兑换BMT
+
+ {{ estimateBmt }}
+
+
+
+ 消耗算力
+
+ {{
+ powerCost
+ }}
+
+
+
+
+ 兑换说明:
+
+ {{ index + 1 }}.
+ {{ tip }}
+
+
+
+
+
+ 确认兑换
+ 兑换记录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/assets/ledger.vue b/pages/assets/ledger.vue
new file mode 100644
index 0000000..5534109
--- /dev/null
+++ b/pages/assets/ledger.vue
@@ -0,0 +1,474 @@
+
+
+
+
+
+
+ 最近记录
+ {{
+ ledger.subtitle || "积分与算力转赠流水"
+ }}
+
+
+
+
+
+ {{
+ item.actionSymbol || "·"
+ }}
+
+
+
+
+
+ {{
+ item.title || "转赠记录"
+ }}
+
+ {{ item.orderSn ? "单号 " + item.orderSn : item.subtitle }}
+
+
+
+
+ {{
+ item.assetLabel
+ }}
+
+ {{ item.amount }}
+
+
+
+
+
+ {{
+ item.balanceLabel || item.balance
+ }}
+ {{ item.feeText }}
+
+
+
+ {{ item.time }}
+
+ {{ item.directionLabel }}
+
+
+
+
+
+
+
+ 暂无转赠记录
+
+ 当前还没有积分或算力的转赠流水。
+
+
+
+
+
+
+
+
+
+ {{ pageMark }}
+
+ {{ ledger.title || "记录" }}
+ {{
+ ledger.subtitle
+ }}
+
+
+
+ {{ ledger.title }}
+ {{ ledger.subtitle }}
+
+
+
+ {{ ledger.summary.label }}
+ {{ ledger.summary.value }}
+
+
+
+
+
+ {{ tab.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/assets/points-convert.vue b/pages/assets/points-convert.vue
new file mode 100644
index 0000000..559f9f8
--- /dev/null
+++ b/pages/assets/points-convert.vue
@@ -0,0 +1,337 @@
+
+
+
+
+
+
+
+
+
+
+
+ 积分
+
+ 可用积分:
+ {{ displayAvailablePoints }}
+
+
+
+
+ 未转换积分(释放中的积分)
+ {{ displayPendingPoints }}
+
+
+
+
+ {{ index + 1 }}.
+ {{ tip }}
+
+
+
+
+
+ {{ submitting ? "转换中..." : "转换可用积分" }}
+
+
+
+
+
+
+
+
+
diff --git a/pages/assets/power-exchange.vue b/pages/assets/power-exchange.vue
new file mode 100644
index 0000000..ad6ba08
--- /dev/null
+++ b/pages/assets/power-exchange.vue
@@ -0,0 +1,743 @@
+
+
+
+
+
+
+
+
+
+
+
+ 算力
+
+ 已有算力:
+ {{ displayPower }}
+
+
+
+
+
+
+
+ 我的抵用券
+
+ {{ displayVoucher }}
+
+
+
+
+
+ 我的消费券
+
+ {{ displayCoupon }}
+
+
+
+
+
+ BMT实时价格
+
+
+ {{ displayPrice }}
+ RMB/BMT
+
+
+
+
+ 全部兑换预估可得
+ {{ allEstimatePower }}
+ 算力
+
+
+
+
+
+
+ 算力兑换
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+ {{ currentModeLabel }}
+
+
+
+
+
+ 预估兑换算力
+
+ {{ estimatePower }}
+
+
+
+
+ 兑换说明:
+
+ {{ index + 1 }}.
+ {{ tip }}
+
+
+
+
+
+ 确认兑换
+ 兑换记录
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/assets/transfer.vue b/pages/assets/transfer.vue
new file mode 100644
index 0000000..276e9f7
--- /dev/null
+++ b/pages/assets/transfer.vue
@@ -0,0 +1,793 @@
+
+
+
+
+
+
+
+ {{ tab.label }}
+
+
+
+
+
+
+ {{ currentTabLabel }}转赠
+ (可用{{ currentBalanceDisplay }})
+
+
+
+
+ 全部赠送
+
+
+
+
+ {{ index + 1 }}.
+ {{ tip }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 被赠送人
+
+
+
+
+ 查询
+
+
+
+
+ ID: {{ currentTarget.id }}
+
+ 选择
+ ✓
+
+
+
+
+
+
+
+ {{
+ targetInitial(currentTarget.nickname)
+ }}
+
+ {{
+ currentTarget.nickname
+ }}
+
+ {{ currentTarget.phone }}
+
+
+
+
+
+ 确认转赠
+ 转赠记录
+
+
+
+
+
+
+ 转赠{{ currentTabLabel }}:{{ amountDisplay }}
+ (实际到账{{ receivedDisplay }})
+
+
+
+ ID: {{ currentTarget.id }}
+
+
+
+
+
+ {{
+ targetInitial(currentTarget.nickname)
+ }}
+
+ {{
+ currentTarget.nickname
+ }}
+
+ {{ currentTarget.phone }}
+
+
+
+
+ 请仔细校对被赠送账号信息,转赠成功后无法追回
+
+
+
+
+
+
+
+
+
diff --git a/pages/assets/wallet-form.vue b/pages/assets/wallet-form.vue
new file mode 100644
index 0000000..078f732
--- /dev/null
+++ b/pages/assets/wallet-form.vue
@@ -0,0 +1,367 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 当前钱包地址
+
+ 我在交易所的钱包地址
+
+
+
+
+ 粘贴
+
+
+
+ 钱包地址请在【海南农综】APP中获取
+
+
+
+
+
+ 保存
+
+
+ 返回
+
+
+
+
+
+
+
+
+ {{
+ form.id ? "修改成功!" : "保存成功!"
+ }}
+
+
+
+
+
+
+
diff --git a/pages/assets/wallet.vue b/pages/assets/wallet.vue
new file mode 100644
index 0000000..a0b3fc0
--- /dev/null
+++ b/pages/assets/wallet.vue
@@ -0,0 +1,371 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 当前钱包地址
+
+ 我在交易所的钱包地址
+
+
+
+ {{
+ currentWallet.address
+ }}
+
+
+
+
+ 暂未设置钱包地址
+
+ 新增后即可用于 BMT 提取和后续交易所对接。
+
+
+
+
+
+
+ {{ currentWallet ? "修改钱包地址" : "新增钱包地址" }}
+
+
+ 返回
+
+
+
+
+ 删除地址
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/assets/withdraw.vue b/pages/assets/withdraw.vue
new file mode 100644
index 0000000..18a8037
--- /dev/null
+++ b/pages/assets/withdraw.vue
@@ -0,0 +1,667 @@
+
+
+
+
+
+
+
+
+
+
+
+ BMT
+
+ 本地钱包可提取BMT:
+ {{ displayWithdrawable }}
+ (BMT实时价格: {{ displayPrice }}CNY/BMT)
+
+
+
+
+
+
+ BMT提取数量
+
+
+
+
+ 全部提取
+
+
+
+ 提取手续费(1%)
+ {{ feeText }}
+
+
+
+
+
+
+
+
+
+
+ 钱包地址
+
+ 修改钱包地址
+
+
+
+
+ {{
+ detail.defaultWallet.name
+ }}
+
+ {{
+ walletVisible ? "隐藏" : "显示"
+ }}
+ 复制
+
+
+ {{ walletDisplayAddress }}
+
+
+
+ +
+ 添加钱包地址
+
+
+
+ 请仔细核对钱包地址,一经转出将无法追回。
+
+
+
+
+ 确定
+ 提取记录
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/index.vue b/pages/index/index.vue
new file mode 100644
index 0000000..83e0c71
--- /dev/null
+++ b/pages/index/index.vue
@@ -0,0 +1,620 @@
+
+
+
+
+ {{ overview.title || "数字资产" }}
+
+
+
+
+ 钱包
+
+
+
+
+
+
+
+
+
+
+ {{ item.title }}
+
+ {{ item.value }}
+ {{ item.unit }}
+
+
+
+
+
+
+
+
+ 我的资产
+
+
+
+
+
+
+
+
+ {{ item.title }}
+
+ {{ item.value }}
+
+
+
+
+
+
+ 功能中心
+
+
+
+
+
+
+
+ {{ feature.title }}
+
+
+
+
+
+
+
+
+ {{ overview.notice }}
+
+
+
+
+
+
+
+
diff --git a/static/logo.png b/static/logo.png
new file mode 100644
index 0000000..b5771e2
Binary files /dev/null and b/static/logo.png differ
diff --git a/styles/common.scss b/styles/common.scss
new file mode 100644
index 0000000..59f6316
--- /dev/null
+++ b/styles/common.scss
@@ -0,0 +1,509 @@
+@import "./tokens.scss";
+
+.asset-page {
+ background: #191e32;
+ color: $asset-text-main;
+}
+
+.section-label {
+ margin: 0 0 20rpx;
+ font-size: 36rpx;
+ font-weight: 600;
+ color: $asset-text-main;
+}
+
+.section-subtitle {
+ margin-top: 8rpx;
+ font-size: 24rpx;
+ line-height: 1.6;
+ color: $asset-text-muted;
+}
+
+.glass-panel {
+ border: 1px solid rgba(143, 167, 207, 0.16);
+ border-radius: 16rpx;
+ background: linear-gradient(
+ 180deg,
+ rgba(54, 62, 96, 0.94),
+ rgba(39, 50, 84, 0.92)
+ );
+ box-shadow: $asset-shadow;
+}
+
+.paper-panel {
+ border: 1px solid rgba(149, 162, 220, 0.16);
+ border-radius: 16rpx;
+ background: linear-gradient(
+ 180deg,
+ rgba(54, 62, 96, 0.94),
+ rgba(39, 50, 84, 0.92)
+ );
+ box-shadow: $asset-shadow;
+ color: $asset-text-main;
+}
+
+.panel-block {
+ padding: 20rpx 18rpx;
+}
+
+.hero-chip-row {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 20rpx 0 0;
+}
+
+.hero-chip {
+ display: inline-flex;
+ align-items: center;
+ padding: 12rpx 18rpx;
+ margin: 0 16rpx 16rpx 0;
+ border-radius: 999rpx;
+ background: rgba(255, 255, 255, 0.08);
+ font-size: 22rpx;
+ color: $asset-text-main;
+}
+
+.hero-chip__dot {
+ width: 12rpx;
+ height: 12rpx;
+ margin-right: 12rpx;
+ border-radius: 50%;
+ background: $asset-accent;
+ box-shadow: 0 0 18rpx rgba(76, 201, 255, 0.6);
+}
+
+.summary-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ grid-gap: 20rpx;
+}
+
+.summary-card {
+ padding: 24rpx;
+ border-radius: 28rpx;
+ background: rgba(255, 255, 255, 0.06);
+ border: 1px solid rgba(143, 167, 207, 0.12);
+}
+
+.summary-card__title {
+ font-size: 24rpx;
+ color: $asset-text-muted;
+}
+
+.summary-card__value {
+ display: flex;
+ align-items: baseline;
+ margin-top: 14rpx;
+ font-size: 44rpx;
+ font-weight: 700;
+ line-height: 1;
+}
+
+.summary-card__unit {
+ margin-left: 10rpx;
+ font-size: 24rpx;
+ font-weight: 500;
+ color: $asset-text-subtle;
+}
+
+.summary-card__desc {
+ margin-top: 14rpx;
+ font-size: 22rpx;
+ line-height: 1.5;
+ color: $asset-text-subtle;
+}
+
+.feature-list {
+ margin-top: 8rpx;
+}
+
+.feature-cell {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 24rpx 0;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.feature-cell:last-child {
+ border-bottom: 0;
+ padding-bottom: 0;
+}
+
+.feature-cell__main {
+ flex: 1;
+ min-width: 0;
+}
+
+.feature-cell__title {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: $asset-text-main;
+}
+
+.feature-cell__desc {
+ margin-top: 8rpx;
+ font-size: 22rpx;
+ line-height: 1.5;
+ color: rgba(255, 255, 255, 0.6);
+}
+
+.feature-cell__arrow {
+ margin-left: 20rpx;
+ font-size: 36rpx;
+ font-weight: 600;
+ color: rgba(255, 255, 255, 0.4);
+}
+
+.action-row {
+ display: flex;
+ align-items: center;
+}
+
+.tab-row {
+ display: flex;
+ padding: 6rpx;
+ border-radius: 999rpx;
+ background: rgba(17, 27, 54, 0.54);
+}
+
+.tab-chip {
+ flex: 1;
+ padding: 16rpx 0;
+ border-radius: 999rpx;
+ text-align: center;
+ font-size: 24rpx;
+ color: rgba(255, 255, 255, 0.64);
+}
+
+.tab-chip.is-active {
+ background: linear-gradient(135deg, #7e6cff 0%, #6b56f6 100%);
+ color: #ffffff;
+ box-shadow: 0 14rpx 22rpx rgba(93, 79, 225, 0.22);
+}
+
+.info-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ grid-gap: 18rpx;
+}
+
+.info-box {
+ padding: 20rpx;
+ border-radius: 14rpx;
+ background: rgba(29, 39, 68, 0.52);
+ border: 1px solid rgba(255, 255, 255, 0.06);
+}
+
+.info-box__label {
+ font-size: 22rpx;
+ color: $asset-text-subtle;
+}
+
+.info-box__value {
+ margin-top: 10rpx;
+ font-size: 34rpx;
+ font-weight: 700;
+ color: $asset-text-main;
+}
+
+.field-card {
+ padding: 18rpx;
+ border-radius: 14rpx;
+ background: rgba(29, 39, 68, 0.52);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.field-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.field-label {
+ font-size: 24rpx;
+ color: rgba(255, 255, 255, 0.62);
+}
+
+.field-value {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: $asset-text-main;
+}
+
+.field-helper {
+ margin-top: 10rpx;
+ font-size: 22rpx;
+ line-height: 1.6;
+ color: rgba(255, 255, 255, 0.6);
+}
+
+.text-input {
+ width: 100%;
+ margin-top: 16rpx;
+ padding: 20rpx 22rpx;
+ border-radius: 14rpx;
+ background: rgba(18, 25, 48, 0.72);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ font-size: 30rpx;
+ color: $asset-text-main;
+}
+
+.chip-selector {
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 16rpx;
+}
+
+.chip-selector__item {
+ padding: 14rpx 24rpx;
+ margin: 0 16rpx 16rpx 0;
+ border-radius: 999rpx;
+ background: rgba(18, 25, 48, 0.72);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ font-size: 24rpx;
+ color: rgba(255, 255, 255, 0.64);
+}
+
+.chip-selector__item.is-active {
+ background: rgba(126, 108, 255, 0.22);
+ border-color: rgba(126, 108, 255, 0.28);
+ color: $asset-text-main;
+}
+
+.hint-list {
+ margin: 8rpx 0 0;
+}
+
+.hint-item {
+ display: flex;
+ margin-top: 14rpx;
+ font-size: 22rpx;
+ line-height: 1.7;
+ color: rgba(255, 255, 255, 0.62);
+}
+
+.hint-item__index {
+ margin-right: 10rpx;
+ color: $asset-accent-strong;
+ font-weight: 600;
+}
+
+.button-row {
+ display: flex;
+ align-items: center;
+}
+
+.button-row .secondary-button {
+ margin-right: 20rpx;
+}
+
+.primary-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 84rpx;
+ border: 0;
+ border-radius: 999rpx;
+ background: linear-gradient(135deg, #7e6cff 0%, #6b56f6 100%);
+ box-shadow: 0 14rpx 24rpx rgba(93, 79, 225, 0.22);
+ font-size: 30rpx;
+ font-weight: 700;
+ color: #ffffff;
+}
+
+.secondary-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 84rpx;
+ border-radius: 999rpx;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ background: rgba(255, 255, 255, 0.06);
+ font-size: 30rpx;
+ font-weight: 600;
+ color: $asset-text-main;
+}
+
+.button-link {
+ font-size: 24rpx;
+ font-weight: 600;
+ color: rgba(221, 230, 255, 0.86);
+}
+
+.meta-pair {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 18rpx;
+ font-size: 24rpx;
+}
+
+.meta-pair__label {
+ color: rgba(255, 255, 255, 0.62);
+}
+
+.meta-pair__value {
+ color: $asset-text-main;
+ font-weight: 600;
+}
+
+.danger-text {
+ color: $asset-danger;
+}
+
+.success-text {
+ color: $asset-success;
+}
+
+.warning-text {
+ color: $asset-warning;
+}
+
+.wallet-card {
+ padding: 20rpx 18rpx;
+ border-radius: 16rpx;
+ background: linear-gradient(
+ 180deg,
+ rgba(54, 62, 96, 0.94),
+ rgba(39, 50, 84, 0.92)
+ );
+ border: 1px solid rgba(149, 162, 220, 0.16);
+}
+
+.wallet-name {
+ font-size: 28rpx;
+ font-weight: 700;
+ color: $asset-text-main;
+}
+
+.wallet-address {
+ margin-top: 14rpx;
+ font-size: 24rpx;
+ line-height: 1.7;
+ word-break: break-all;
+ color: $asset-text-muted;
+}
+
+.wallet-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 20rpx;
+}
+
+.wallet-badge {
+ padding: 8rpx 16rpx;
+ border-radius: 999rpx;
+ background: rgba(126, 108, 255, 0.18);
+ font-size: 20rpx;
+ color: #dfe7ff;
+}
+
+.empty-panel {
+ padding: 36rpx 28rpx;
+ border: 1px solid rgba(149, 162, 220, 0.16);
+ border-radius: 16rpx;
+ background: linear-gradient(
+ 180deg,
+ rgba(54, 62, 96, 0.94),
+ rgba(39, 50, 84, 0.92)
+ );
+ text-align: center;
+}
+
+.empty-title {
+ font-size: 30rpx;
+ font-weight: 700;
+ color: $asset-text-main;
+}
+
+.empty-desc {
+ margin-top: 14rpx;
+ font-size: 24rpx;
+ line-height: 1.7;
+ color: $asset-text-muted;
+}
+
+.page-hero {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 12rpx 0 20rpx;
+}
+
+.page-hero__mark {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 110rpx;
+ height: 110rpx;
+ border-radius: 50%;
+ background: linear-gradient(180deg, #1f4a72 0%, #1a3464 100%);
+}
+
+.page-hero__ring {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ border-radius: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.page-hero__ring--outer {
+ width: 110rpx;
+ height: 110rpx;
+ border: 4rpx solid rgba(63, 220, 255, 0.9);
+ box-shadow: 0 0 0 6rpx rgba(80, 225, 255, 0.08);
+}
+
+.page-hero__ring--inner {
+ width: 88rpx;
+ height: 88rpx;
+ border: 3rpx solid rgba(98, 231, 255, 0.42);
+}
+
+.page-hero__text {
+ position: relative;
+ z-index: 1;
+ font-size: 52rpx;
+ font-weight: 800;
+ font-style: italic;
+ color: #42d9ff;
+}
+
+.page-hero__value {
+ margin-top: 16rpx;
+ font-size: 24rpx;
+ font-weight: 700;
+ color: $asset-text-main;
+}
+
+.page-hero__desc {
+ margin-top: 8rpx;
+ font-size: 20rpx;
+ line-height: 1.6;
+ color: rgba(255, 255, 255, 0.66);
+ text-align: center;
+}
+
+.submit-wrap {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 24rpx 0 8rpx;
+}
+
+.submit-wrap .primary-button {
+ width: 360rpx;
+}
+
+.record-link {
+ margin-top: 18rpx;
+ font-size: 22rpx;
+ text-decoration: underline;
+ color: rgba(222, 232, 255, 0.86);
+}
diff --git a/styles/tokens.scss b/styles/tokens.scss
new file mode 100644
index 0000000..c05b4c3
--- /dev/null
+++ b/styles/tokens.scss
@@ -0,0 +1,27 @@
+$asset-bg-deep: #08162e;
+$asset-bg-mid: #10264a;
+$asset-bg-soft: #173664;
+$asset-panel: rgba(18, 39, 77, 0.96);
+$asset-panel-soft: rgba(21, 45, 88, 0.9);
+$asset-paper: #edf4ff;
+$asset-paper-card: #ffffff;
+$asset-paper-line: rgba(34, 64, 110, 0.12);
+$asset-text-main: #ffffff;
+$asset-text-muted: rgba(255, 255, 255, 0.78);
+$asset-text-subtle: #8fa7cf;
+$asset-text-dark: #112446;
+$asset-text-grey: #6e83a7;
+$asset-accent: #4cc9ff;
+$asset-accent-strong: #5a71ff;
+$asset-accent-soft: rgba(76, 201, 255, 0.12);
+$asset-success: #5ad7a1;
+$asset-warning: #ffbf66;
+$asset-danger: #ff7285;
+$asset-radius-lg: 28rpx;
+$asset-radius-xl: 36rpx;
+$asset-shadow: 0 24rpx 60rpx rgba(0, 0, 0, 0.18);
+$asset-shadow-soft: 0 18rpx 40rpx rgba(8, 22, 46, 0.12);
+$asset-space-sm: 16rpx;
+$asset-space-md: 24rpx;
+$asset-space-lg: 32rpx;
+$asset-space-xl: 40rpx;
diff --git a/uni.promisify.adaptor.js b/uni.promisify.adaptor.js
new file mode 100644
index 0000000..5fec4f3
--- /dev/null
+++ b/uni.promisify.adaptor.js
@@ -0,0 +1,13 @@
+uni.addInterceptor({
+ returnValue (res) {
+ if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
+ return res;
+ }
+ return new Promise((resolve, reject) => {
+ res.then((res) => {
+ if (!res) return resolve(res)
+ return res[0] ? reject(res[0]) : resolve(res[1])
+ });
+ });
+ },
+});
\ No newline at end of file
diff --git a/uni.scss b/uni.scss
new file mode 100644
index 0000000..62eb87b
--- /dev/null
+++ b/uni.scss
@@ -0,0 +1,76 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16px;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;
diff --git a/utils/request.js b/utils/request.js
new file mode 100644
index 0000000..b79ec37
--- /dev/null
+++ b/utils/request.js
@@ -0,0 +1,100 @@
+import serviceConfig from "../config/service";
+import { getCurrentWebviewToken } from "./webview-token";
+
+let loadingCount = 0;
+
+function isAbsoluteUrl(url) {
+ return /^https?:\/\//i.test(url || "");
+}
+
+function buildRequestUrl(url) {
+ if (isAbsoluteUrl(url)) {
+ return url;
+ }
+
+ return serviceConfig.BASE_URL + url;
+}
+
+function showGlobalLoading(options) {
+ if (!options || !options.showLoading) {
+ return false;
+ }
+
+ loadingCount += 1;
+
+ if (loadingCount === 1) {
+ uni.showLoading({
+ title: options.loadingText || "加载中",
+ mask: options.loadingMask !== false,
+ });
+ }
+
+ return true;
+}
+
+function hideGlobalLoading(shouldHide) {
+ if (!shouldHide) {
+ return;
+ }
+
+ loadingCount = Math.max(0, loadingCount - 1);
+
+ if (loadingCount === 0) {
+ uni.hideLoading();
+ }
+}
+
+export default function request(options) {
+ const method = options.method || "GET";
+ const defaultContentType =
+ method === "POST"
+ ? "application/x-www-form-urlencoded; charset=UTF-8"
+ : "application/json";
+ const token = getCurrentWebviewToken();
+ const requestHeader = Object.assign(
+ {
+ "Content-Type": defaultContentType,
+ },
+ options.header || {},
+ );
+
+ if (token) {
+ const bearerToken = "Bearer " + token;
+ requestHeader["Authori-zation"] = bearerToken;
+ requestHeader.Authorization = bearerToken;
+ }
+ const shouldHandleLoading = showGlobalLoading(options);
+
+ return new Promise(function (resolve, reject) {
+ uni.request({
+ url: buildRequestUrl(options.url),
+ method: method,
+ data: options.data || {},
+ header: requestHeader,
+ timeout: serviceConfig.TIMEOUT,
+ success: function (response) {
+ const statusCode = response.statusCode || 0;
+ if (statusCode >= 200 && statusCode < 300) {
+ resolve(response.data);
+ return;
+ }
+
+ reject({
+ statusCode: statusCode,
+ message: (response.data && response.data.message) || "接口请求失败",
+ raw: response,
+ });
+ },
+ fail: function (error) {
+ reject({
+ statusCode: 0,
+ message: error.errMsg || "网络异常",
+ raw: error,
+ });
+ },
+ complete: function () {
+ hideGlobalLoading(shouldHandleLoading);
+ },
+ });
+ });
+}
diff --git a/utils/webview-token.js b/utils/webview-token.js
new file mode 100644
index 0000000..027dc48
--- /dev/null
+++ b/utils/webview-token.js
@@ -0,0 +1,62 @@
+function findTokenInText(text) {
+ if (!text) {
+ return "";
+ }
+
+ const queryIndex = text.indexOf("?");
+ if (queryIndex === -1) {
+ return "";
+ }
+
+ const rawQueryText = text.slice(queryIndex + 1);
+ const hashIndex = rawQueryText.indexOf("#");
+ const queryText =
+ hashIndex === -1 ? rawQueryText : rawQueryText.slice(0, hashIndex);
+ const search = new URLSearchParams(queryText);
+ return search.get("token") || "";
+}
+
+export function extractTokenFromUrl(url) {
+ if (!url) {
+ return "";
+ }
+
+ const directToken = findTokenInText(url);
+ if (directToken) {
+ return directToken;
+ }
+
+ const hashIndex = url.indexOf("#");
+ if (hashIndex === -1) {
+ return "";
+ }
+
+ return findTokenInText(url.slice(hashIndex + 1));
+}
+
+export function getCurrentWebviewUrl() {
+ // #ifdef H5
+ return window.location.href || "";
+ // #endif
+
+ // #ifdef APP-PLUS
+ if (typeof plus === "undefined" || !plus.webview) {
+ return "";
+ }
+
+ const currentWebview =
+ plus.webview.currentWebview() || plus.webview.getLaunchWebview();
+
+ if (!currentWebview || typeof currentWebview.getURL !== "function") {
+ return "";
+ }
+
+ return currentWebview.getURL() || "";
+ // #endif
+
+ return "";
+}
+
+export function getCurrentWebviewToken() {
+ return extractTokenFromUrl(getCurrentWebviewUrl());
+}