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, }; }