first commit
This commit is contained in:
commit
952d3be4a9
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
|
|
@ -0,0 +1,78 @@
|
|||
<script>
|
||||
import {
|
||||
getCurrentWebviewToken,
|
||||
getCurrentWebviewUrl,
|
||||
} from "./utils/webview-token";
|
||||
|
||||
export default {
|
||||
onLaunch: function () {
|
||||
console.log("App Launch");
|
||||
this.logCurrentWebviewToken("launch");
|
||||
},
|
||||
onShow: function () {
|
||||
console.log("App Show");
|
||||
this.logCurrentWebviewToken("show");
|
||||
},
|
||||
onHide: function () {
|
||||
console.log("App Hide");
|
||||
},
|
||||
methods: {
|
||||
logCurrentWebviewToken(scene) {
|
||||
const currentUrl = getCurrentWebviewUrl();
|
||||
const token = getCurrentWebviewToken();
|
||||
|
||||
if (token) {
|
||||
console.log("[webview-token][" + scene + "]", token);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[webview-token][" + scene + "] token not found", currentUrl);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./styles/tokens.scss";
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
uni-page,
|
||||
uni-page-wrapper,
|
||||
uni-page-body {
|
||||
min-height: 100%;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
page {
|
||||
min-height: 100%;
|
||||
background: #191e32;
|
||||
color: $asset-text-main;
|
||||
font-family: "PingFang SC", "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
view,
|
||||
text,
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
scroll-view {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.asset-theme {
|
||||
--asset-accent: #4cc9ff;
|
||||
--asset-accent-strong: #5a71ff;
|
||||
--asset-success: #5ad7a1;
|
||||
--asset-danger: #ff7285;
|
||||
--asset-text-main: #ffffff;
|
||||
--asset-text-muted: rgba(255, 255, 255, 0.78);
|
||||
--asset-text-dark: #112446;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
<template>
|
||||
<view v-if="visible" class="popup-mask" @touchmove.stop.prevent="noop">
|
||||
<view class="popup-mask__backdrop" @click="handleMaskClick"></view>
|
||||
<view class="popup-panel" :class="'popup-panel--' + status">
|
||||
<view class="popup-panel__head">
|
||||
<text class="popup-panel__title">{{ title }}</text>
|
||||
<view
|
||||
v-if="showClose"
|
||||
class="popup-panel__close"
|
||||
@click="$emit('cancel')"
|
||||
>
|
||||
<image
|
||||
class="popup-panel__close-image"
|
||||
src="https://imgs.agrimedia.cn/bm-bmt/bmt-close.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="showStatusBlock" class="popup-panel__status">
|
||||
<image
|
||||
class="popup-panel__status-image"
|
||||
:src="statusIcon"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text v-if="message" class="popup-panel__status-text">{{
|
||||
message
|
||||
}}</text>
|
||||
</view>
|
||||
<text v-else-if="message" class="popup-panel__message">{{
|
||||
message
|
||||
}}</text>
|
||||
|
||||
<text v-if="description" class="popup-panel__description">{{
|
||||
description
|
||||
}}</text>
|
||||
|
||||
<view v-if="$slots.default" class="popup-panel__body">
|
||||
<slot />
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="popup-panel__footer"
|
||||
:class="{ 'popup-panel__footer--single': !showCancel }"
|
||||
>
|
||||
<view
|
||||
v-if="showCancel"
|
||||
class="popup-panel__btn popup-panel__btn--ghost"
|
||||
@click="$emit('cancel')"
|
||||
>
|
||||
{{ cancelText }}
|
||||
</view>
|
||||
<view
|
||||
class="popup-panel__btn popup-panel__btn--primary"
|
||||
@click="$emit('confirm')"
|
||||
>
|
||||
{{ confirmText }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "提示",
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: "确认",
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: "取消",
|
||||
},
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
closeOnMask: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: "default",
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showStatusBlock() {
|
||||
return this.status === "success" || this.status === "error";
|
||||
},
|
||||
statusIcon() {
|
||||
if (this.status === "success") {
|
||||
return "https://imgs.agrimedia.cn/bm-bmt/success.png";
|
||||
}
|
||||
|
||||
if (this.status === "error") {
|
||||
return "https://imgs.agrimedia.cn/bm-bmt/fail.png";
|
||||
}
|
||||
|
||||
return "";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
noop() {},
|
||||
handleMaskClick() {
|
||||
if (this.closeOnMask) {
|
||||
this.$emit("cancel");
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../styles/tokens.scss";
|
||||
|
||||
.popup-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 80;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.popup-mask__backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(5, 10, 22, 0.72);
|
||||
}
|
||||
|
||||
.popup-panel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 600rpx;
|
||||
padding: 30rpx 28rpx 32rpx;
|
||||
border-radius: 28rpx;
|
||||
border: 1px solid rgba(142, 157, 206, 0.14);
|
||||
background: #2a2f4d;
|
||||
box-shadow: 0 30rpx 70rpx rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
.popup-panel__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.popup-panel__title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.popup-panel__close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.popup-panel__close-image {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
|
||||
.popup-panel__message {
|
||||
display: block;
|
||||
margin-top: 34rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.8;
|
||||
color: #edf2ff;
|
||||
}
|
||||
|
||||
.popup-panel__status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 46rpx;
|
||||
}
|
||||
|
||||
.popup-panel__status-image {
|
||||
width: 54rpx;
|
||||
height: 54rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.popup-panel__status-text {
|
||||
margin-left: 18rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #edf2ff;
|
||||
}
|
||||
|
||||
.popup-panel__description {
|
||||
display: block;
|
||||
margin-top: 34rpx;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.7;
|
||||
text-align: center;
|
||||
color: rgba(201, 209, 233, 0.86);
|
||||
}
|
||||
|
||||
.popup-panel__body {
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.popup-panel__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.popup-panel__footer--single {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.popup-panel__btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.popup-panel__footer--single .popup-panel__btn {
|
||||
flex: 0 0 240rpx;
|
||||
}
|
||||
|
||||
.popup-panel__btn--ghost {
|
||||
border: 1px solid rgba(157, 173, 221, 0.44);
|
||||
background: rgba(77, 89, 129, 0.28);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.popup-panel__btn--primary {
|
||||
background: #02ABF1;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<template>
|
||||
<view class="asset-shell" :style="{ '--asset-shell-side-width': sideWidth }">
|
||||
<view class="asset-shell__nav">
|
||||
<view class="asset-shell__side">
|
||||
<view v-if="backable" class="asset-shell__back" @click="handleBack">
|
||||
<image
|
||||
class="asset-shell__back-image"
|
||||
src="https://imgs.agrimedia.cn/bm-bmt/bacn-icon.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
<text class="asset-shell__title">{{ title }}</text>
|
||||
<view class="asset-shell__side asset-shell__side--right">
|
||||
<slot name="right">
|
||||
<text
|
||||
v-if="rightText"
|
||||
class="asset-shell__action"
|
||||
@click="$emit('right-click')"
|
||||
>
|
||||
{{ rightText }}
|
||||
</text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
rightText: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
backable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
sideWidth: {
|
||||
type: String,
|
||||
default: "120rpx",
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleBack() {
|
||||
if (getCurrentPages().length > 1) {
|
||||
uni.navigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
uni.reLaunch({
|
||||
url: "/pages/index/index",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../styles/tokens.scss";
|
||||
|
||||
.asset-shell {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
padding: calc(env(safe-area-inset-top) + 20rpx) 14rpx 20rpx;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.asset-shell__nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 48rpx;
|
||||
}
|
||||
|
||||
.asset-shell__side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: var(--asset-shell-side-width);
|
||||
flex: 0 0 var(--asset-shell-side-width);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.asset-shell__side--right {
|
||||
justify-content: flex-end;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.asset-shell__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.asset-shell__back-image {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.asset-shell__title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.asset-shell__action {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(232, 239, 255, 0.86);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
<template>
|
||||
<view>
|
||||
<view v-if="list.length">
|
||||
<view v-for="item in list" :key="item.id" class="record-item">
|
||||
<view
|
||||
class="record-item__badge"
|
||||
:class="'record-item__badge--' + (item.tone || 'info')"
|
||||
>
|
||||
{{ item.tag || "记" }}
|
||||
</view>
|
||||
<view class="record-item__content">
|
||||
<view class="record-item__head">
|
||||
<text class="record-item__title">{{ item.title }}</text>
|
||||
<text
|
||||
class="record-item__amount"
|
||||
:class="'record-item__amount--' + (item.tone || 'info')"
|
||||
>
|
||||
{{ item.amount }}
|
||||
</text>
|
||||
</view>
|
||||
<text v-if="item.subtitle" class="record-item__subtitle">{{
|
||||
item.subtitle
|
||||
}}</text>
|
||||
<view class="record-item__meta">
|
||||
<text class="record-item__time">{{ item.time }}</text>
|
||||
<text v-if="item.balance" class="record-item__balance">{{
|
||||
item.balance
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="record-empty">
|
||||
<text class="record-empty__title">{{ emptyTitle }}</text>
|
||||
<text class="record-empty__desc">{{ emptyDesc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
emptyTitle: {
|
||||
type: String,
|
||||
default: "暂无记录",
|
||||
},
|
||||
emptyDesc: {
|
||||
type: String,
|
||||
default: "当前暂无流水数据。",
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../styles/tokens.scss";
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.record-item:last-child {
|
||||
border-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.record-item__badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
margin-right: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.record-item__badge--success {
|
||||
background: linear-gradient(135deg, #4fe0b5 0%, #36b98c 100%);
|
||||
}
|
||||
|
||||
.record-item__badge--danger {
|
||||
background: linear-gradient(135deg, #ff8091 0%, #ff5d6e 100%);
|
||||
}
|
||||
|
||||
.record-item__badge--info {
|
||||
background: linear-gradient(135deg, #5a71ff 0%, #4cc9ff 100%);
|
||||
}
|
||||
|
||||
.record-item__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.record-item__head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.record-item__title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 20rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
font-weight: 600;
|
||||
color: $asset-text-main;
|
||||
}
|
||||
|
||||
.record-item__amount {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.record-item__amount--success {
|
||||
color: $asset-success;
|
||||
}
|
||||
|
||||
.record-item__amount--danger {
|
||||
color: $asset-danger;
|
||||
}
|
||||
|
||||
.record-item__amount--info {
|
||||
color: $asset-accent-strong;
|
||||
}
|
||||
|
||||
.record-item__subtitle {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
}
|
||||
|
||||
.record-item__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 14rpx;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
}
|
||||
|
||||
.record-item__time {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.record-item__balance {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.record-empty {
|
||||
padding: 48rpx 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.record-empty__title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: $asset-text-main;
|
||||
}
|
||||
|
||||
.record-empty__desc {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
<template>
|
||||
<asset-confirm-popup
|
||||
:visible="visible"
|
||||
:title="title"
|
||||
:confirm-text="confirmText"
|
||||
:cancel-text="cancelText"
|
||||
:show-cancel="showCancel"
|
||||
:show-close="showClose"
|
||||
:close-on-mask="closeOnMask"
|
||||
@cancel="$emit('cancel')"
|
||||
@confirm="$emit('confirm')"
|
||||
>
|
||||
<view class="wallet-dialog">
|
||||
<text v-if="message" class="wallet-dialog__message">{{ message }}</text>
|
||||
<view v-if="address" class="wallet-dialog__address">{{ address }}</view>
|
||||
<text v-if="description" class="wallet-dialog__description">{{
|
||||
description
|
||||
}}</text>
|
||||
</view>
|
||||
</asset-confirm-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetConfirmPopup from "./asset-confirm-popup.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetConfirmPopup,
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "提示",
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: "确认",
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: "取消",
|
||||
},
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
closeOnMask: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wallet-dialog__message {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: #edf2ff;
|
||||
}
|
||||
|
||||
.wallet-dialog__address {
|
||||
margin-top: 20rpx;
|
||||
padding: 20rpx 18rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #191E32;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-dialog__description {
|
||||
display: block;
|
||||
margin-top: 20rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.7;
|
||||
color: rgba(201, 209, 233, 0.82);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||
CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
background: #08162e;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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
|
||||
|
|
@ -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" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
/* 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"
|
||||
}
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,694 @@
|
|||
<template>
|
||||
<view class="asset-page asset-theme bmt-page">
|
||||
<asset-page-shell title="BMT兑换" />
|
||||
|
||||
<view class="asset-scroll bmt-scroll">
|
||||
<view class="hero-card">
|
||||
<view class="hero-card__content">
|
||||
<view class="hero-card__coin-row">
|
||||
<view class="hero-card__coin">
|
||||
<image
|
||||
class="hero-card__coin-image"
|
||||
:src="pageIcon('bmt')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
<text class="hero-card__coin-text">BMT</text>
|
||||
</view>
|
||||
<text class="hero-card__label">可用BMT:</text>
|
||||
<text class="hero-card__value">{{ displayBmt }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-card info-card">
|
||||
<view class="info-row">
|
||||
<view class="info-row__left">
|
||||
<image
|
||||
class="info-row__icon"
|
||||
:src="pageIcon('voucher')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="info-row__label">我的抵用券</text>
|
||||
</view>
|
||||
<text class="info-row__value">{{ displayVoucher }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<view class="info-row__left">
|
||||
<image
|
||||
class="info-row__icon"
|
||||
:src="pageIcon('coupon')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="info-row__label">我的消费券</text>
|
||||
</view>
|
||||
<text class="info-row__value">{{ displayCoupon }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row info-row--last">
|
||||
<view class="info-row__left">
|
||||
<image
|
||||
class="info-row__icon"
|
||||
src="https://imgs.agrimedia.cn/bm-bmt/b.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="info-row__label">BMT实时价格</text>
|
||||
</view>
|
||||
<view class="info-row__price">
|
||||
<text class="info-row__value">{{ displayPrice }}</text>
|
||||
<text class="info-row__unit">CNY/BMT</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-card__estimate">
|
||||
<text>全部兑换预估可得</text>
|
||||
<text class="info-card__estimate-value">{{ allEstimateBmt }}</text>
|
||||
<text>BMT</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-card form-card">
|
||||
<view class="form-card__title">
|
||||
<image
|
||||
class="form-card__title-icon"
|
||||
src="https://imgs.agrimedia.cn/bm-bmt/b.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="form-card__title-text">BMT兑换</text>
|
||||
</view>
|
||||
|
||||
<view class="form-input">
|
||||
<input
|
||||
v-model="form.points"
|
||||
class="form-input__field"
|
||||
type="number"
|
||||
placeholder="请输入积分数量"
|
||||
placeholder-class="form-input__placeholder"
|
||||
/>
|
||||
<text class="form-input__suffix">积分</text>
|
||||
</view>
|
||||
|
||||
<view class="preview-card">
|
||||
<view class="preview-card__row">
|
||||
<view class="preview-card__left">
|
||||
<text class="preview-card__label">预估兑换BMT</text>
|
||||
</view>
|
||||
<text class="preview-card__value">{{ estimateBmt }}</text>
|
||||
</view>
|
||||
<view class="preview-card__row">
|
||||
<view class="preview-card__left">
|
||||
<text class="preview-card__label" style="color: #fff"
|
||||
>消耗算力</text
|
||||
>
|
||||
</view>
|
||||
<text class="preview-card__value preview-card__value--light">{{
|
||||
powerCost
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tips-block">
|
||||
<text class="tips-block__title">兑换说明:</text>
|
||||
<view
|
||||
v-for="(tip, index) in normalizedTips"
|
||||
:key="tip"
|
||||
class="tips-block__item"
|
||||
>
|
||||
<text class="tips-block__index">{{ index + 1 }}.</text>
|
||||
<text class="tips-block__text">{{ tip }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-wrap">
|
||||
<view class="action-button" @click="confirmVisible = true"
|
||||
>确认兑换</view
|
||||
>
|
||||
<view class="action-link" @click="openLedger">兑换记录</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<asset-confirm-popup
|
||||
:visible="confirmVisible"
|
||||
title="兑换提示"
|
||||
:message="
|
||||
'确认使用 ' + formatAmount(pointsValue, 0) + ' 积分兑换 BMT 吗?'
|
||||
"
|
||||
@cancel="confirmVisible = false"
|
||||
@confirm="submit"
|
||||
>
|
||||
<view class="popup-line">
|
||||
<text class="meta-pair__label">输入积分</text>
|
||||
<text class="meta-pair__value">{{ formatAmount(pointsValue, 0) }}</text>
|
||||
</view>
|
||||
<view class="popup-line">
|
||||
<text class="meta-pair__label">预估兑换BMT</text>
|
||||
<text class="meta-pair__value">{{ estimateBmt }}</text>
|
||||
</view>
|
||||
<view class="popup-line">
|
||||
<text class="meta-pair__label">消耗算力</text>
|
||||
<text class="meta-pair__value">{{ powerCost }}</text>
|
||||
</view>
|
||||
</asset-confirm-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetConfirmPopup from "../../components/asset-confirm-popup.vue";
|
||||
import AssetPageShell from "../../components/asset-page-shell.vue";
|
||||
import {
|
||||
fetchBmtExchangeDetail,
|
||||
submitAssetBmtExchange,
|
||||
} from "../../api/assets";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetConfirmPopup,
|
||||
AssetPageShell,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
detail: {
|
||||
ticker: {},
|
||||
balances: {},
|
||||
tips: [],
|
||||
},
|
||||
hasShown: false,
|
||||
form: {
|
||||
points: "",
|
||||
},
|
||||
confirmVisible: false,
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.loadPage(true);
|
||||
},
|
||||
onShow() {
|
||||
if (this.hasShown) {
|
||||
this.loadPage();
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasShown = true;
|
||||
},
|
||||
computed: {
|
||||
pointsValue() {
|
||||
return Number(this.form.points || 0);
|
||||
},
|
||||
powerRateNumber() {
|
||||
return Number(this.detail.powerRate || 0);
|
||||
},
|
||||
estimateBmt() {
|
||||
if (!this.pointsValue) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
return this.formatAmount(this.pointsValue, 0);
|
||||
},
|
||||
powerCost() {
|
||||
if (!this.pointsValue || !this.powerRateNumber) {
|
||||
return "0.00";
|
||||
}
|
||||
|
||||
return (this.pointsValue * this.powerRateNumber).toFixed(2);
|
||||
},
|
||||
allEstimateBmt() {
|
||||
const pointsLimit = Number(this.detail.balances.points || 0);
|
||||
const powerLimit = this.powerRateNumber
|
||||
? Number(this.detail.balances.power || 0) / this.powerRateNumber
|
||||
: pointsLimit;
|
||||
const available = Math.max(0, Math.min(pointsLimit, powerLimit || 0));
|
||||
|
||||
return this.formatAmount(available, 0);
|
||||
},
|
||||
priceNumber() {
|
||||
return Number(
|
||||
this.detail.ticker.close || this.detail.ticker.cnyPrice || 0,
|
||||
);
|
||||
},
|
||||
displayBmt() {
|
||||
return this.formatAmount(this.detail.balances.bmt, 2);
|
||||
},
|
||||
displayVoucher() {
|
||||
return this.formatAmount(this.detail.balances.voucher, 0);
|
||||
},
|
||||
displayCoupon() {
|
||||
return this.formatAmount(this.detail.balances.coupon, 2);
|
||||
},
|
||||
displayPrice() {
|
||||
return this.priceNumber ? this.priceNumber.toFixed(2) : "0.00";
|
||||
},
|
||||
normalizedTips() {
|
||||
if (this.detail.tips && this.detail.tips.length) {
|
||||
return this.detail.tips;
|
||||
}
|
||||
|
||||
return [
|
||||
"1.BMT=积分-BMT实时价格;",
|
||||
"2.只能兑换100的整数倍,小于100积分不可兑换;",
|
||||
"3.凌晨0点-凌晨1点积分系统维护不可兑换;",
|
||||
"4.可用积分不足,可以将释放中的积分转换成可用积分0。",
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pageIcon(type) {
|
||||
const iconMap = {
|
||||
power: "https://imgs.agrimedia.cn/bm-bmt/s.png",
|
||||
bmt: "https://imgs.agrimedia.cn/bm-bmt/b-w.png",
|
||||
transfer: "https://imgs.agrimedia.cn/bm-bmt/z.png",
|
||||
withdraw: "https://imgs.agrimedia.cn/bm-bmt/t.png",
|
||||
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon-g%20%281%29.png",
|
||||
points: "https://imgs.agrimedia.cn/bm-bmt/j.png",
|
||||
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon-o.png",
|
||||
};
|
||||
return iconMap[type] || "";
|
||||
},
|
||||
formatAmount(value, digits) {
|
||||
const number = Number(value || 0);
|
||||
if (!Number.isFinite(number)) {
|
||||
return digits > 0 ? Number(0).toFixed(digits) : "0";
|
||||
}
|
||||
|
||||
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.floor(number)
|
||||
.toString()
|
||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
},
|
||||
async loadPage(showLoading) {
|
||||
try {
|
||||
this.detail = await fetchBmtExchangeDetail(
|
||||
showLoading
|
||||
? {
|
||||
showLoading: true,
|
||||
loadingText: "加载中",
|
||||
}
|
||||
: null,
|
||||
);
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "页面加载失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
if (this.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.pointsValue) {
|
||||
uni.showToast({
|
||||
title: "请输入积分数量",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pointsValue < 100) {
|
||||
uni.showToast({
|
||||
title: "小于100积分不可兑换",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pointsValue % 100 !== 0) {
|
||||
uni.showToast({
|
||||
title: "只能兑换100的整数倍",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pointsValue > Number(this.detail.balances.points || 0)) {
|
||||
uni.showToast({
|
||||
title: "可用积分不足",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number(this.powerCost) > Number(this.detail.balances.power || 0)) {
|
||||
uni.showToast({
|
||||
title: "可用算力不足",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
await submitAssetBmtExchange({
|
||||
amount: this.pointsValue,
|
||||
}, {
|
||||
showLoading: true,
|
||||
loadingText: "兑换中",
|
||||
});
|
||||
this.confirmVisible = false;
|
||||
this.form.points = "";
|
||||
uni.showToast({
|
||||
title: "兑换成功",
|
||||
icon: "none",
|
||||
});
|
||||
this.loadPage();
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "兑换失败",
|
||||
icon: "none",
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
openLedger() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/assets/ledger?type=bmt",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tokens.scss";
|
||||
@import "../../styles/common.scss";
|
||||
|
||||
.bmt-page {
|
||||
min-height: 100vh;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.bmt-scroll {
|
||||
padding: 8rpx 20rpx;
|
||||
}
|
||||
|
||||
.panel-card,
|
||||
.hero-card {
|
||||
border-radius: 12rpx;
|
||||
background: #20263e;
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
position: relative;
|
||||
min-height: 240rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
overflow: hidden;
|
||||
background: #20263e url("https://imgs.agrimedia.cn/bm-bmt/bmt2-header.png")
|
||||
no-repeat center top / 100% 240rpx;
|
||||
}
|
||||
|
||||
.hero-card__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hero-card__coin-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-card__coin {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #18c5ff 0%, #1496ff 100%);
|
||||
}
|
||||
|
||||
.hero-card__coin-image {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
|
||||
.hero-card__coin-text {
|
||||
margin-left: 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero-card__label {
|
||||
margin-top: 34rpx;
|
||||
font-size: 28rpx;
|
||||
color: #9ba7ce;
|
||||
}
|
||||
|
||||
.hero-card__value {
|
||||
margin-top: 8rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.panel-card {
|
||||
margin-top: 18rpx;
|
||||
padding: 0 26rpx;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
padding-top: 2rpx;
|
||||
padding-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 96rpx;
|
||||
border-bottom: 1rpx solid #4e5a82;
|
||||
}
|
||||
|
||||
.info-row--last {
|
||||
border-bottom: 1rpx solid #4e5a82;
|
||||
}
|
||||
|
||||
.info-row__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.info-row__icon {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-row__label {
|
||||
margin-left: 20rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: #eef2ff;
|
||||
}
|
||||
|
||||
.info-row__value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.info-row__price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.info-row__unit {
|
||||
margin-left: 10rpx;
|
||||
font-size: 22rpx;
|
||||
color: rgba(184, 193, 218, 0.88);
|
||||
}
|
||||
|
||||
.info-card__estimate {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: baseline;
|
||||
padding-top: 18rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1ad296;
|
||||
}
|
||||
|
||||
.info-card__estimate-value {
|
||||
margin: 0 10rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 800;
|
||||
color: #2fe2a4;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
padding-top: 18rpx;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.form-card__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-card__title-icon {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-card__title-text {
|
||||
margin-left: 14rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 20rpx;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.form-input__icon {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
margin-right: 18rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-input__field {
|
||||
flex: 1;
|
||||
height: 98rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.form-input__placeholder {
|
||||
color: rgba(177, 187, 214, 0.42);
|
||||
}
|
||||
|
||||
.form-input__suffix {
|
||||
margin-left: 16rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.preview-card {
|
||||
margin-top: 18rpx;
|
||||
padding: 12rpx 26rpx;
|
||||
border-radius: 8rpx;
|
||||
background: rgba(26, 210, 150, 0.1);
|
||||
}
|
||||
|
||||
.preview-card__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.preview-card__row + .preview-card__row {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.preview-card__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preview-card__icon {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
margin-right: 10rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.preview-card__label {
|
||||
font-size: 26rpx;
|
||||
color: rgba(26, 210, 150, 1);
|
||||
}
|
||||
|
||||
.preview-card__value {
|
||||
font-size: 26rpx;
|
||||
font-weight: 800;
|
||||
color: #1ad296;
|
||||
}
|
||||
|
||||
.preview-card__value--light {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tips-block {
|
||||
margin-top: 24rpx;
|
||||
padding: 0rpx 10rpx;
|
||||
}
|
||||
|
||||
.tips-block__title {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: rgba(190, 197, 219, 0.92);
|
||||
}
|
||||
|
||||
.tips-block__item {
|
||||
display: flex;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.tips-block__index,
|
||||
.tips-block__text {
|
||||
font-size: 24rpx;
|
||||
color: #929dbf;
|
||||
line-height: 44rpx;
|
||||
color: rgba(166, 175, 204, 0.92);
|
||||
}
|
||||
|
||||
.tips-block__index {
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.tips-block__text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-wrap {
|
||||
padding: 36rpx 22rpx 0;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #20b6f5 0%, #1ca5e3 100%);
|
||||
box-shadow: 0 16rpx 30rpx rgba(20, 119, 214, 0.16);
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-link {
|
||||
margin-top: 26rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #1fb4ff;
|
||||
}
|
||||
|
||||
.popup-line {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,474 @@
|
|||
<template>
|
||||
<view class="asset-page asset-theme ledger-page">
|
||||
<asset-page-shell :title="ledger.title || '记录'" />
|
||||
<view class="asset-scroll" :class="{ 'ledger-scroll': isTransferLedger }">
|
||||
<template v-if="isTransferLedger">
|
||||
<view class="transfer-ledger-head">
|
||||
<text class="transfer-ledger-head__eyebrow">最近记录</text>
|
||||
<text class="transfer-ledger-head__desc">{{
|
||||
ledger.subtitle || "积分与算力转赠流水"
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="transferRecords.length" class="transfer-ledger-list">
|
||||
<view
|
||||
v-for="item in transferRecords"
|
||||
:key="item.id"
|
||||
class="transfer-record-card"
|
||||
>
|
||||
<view
|
||||
class="transfer-record-card__icon"
|
||||
:class="
|
||||
'transfer-record-card__icon--' +
|
||||
(item.cardTone || item.tone || 'info')
|
||||
"
|
||||
>
|
||||
<text class="transfer-record-card__icon-text">{{
|
||||
item.actionSymbol || "·"
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<view class="transfer-record-card__content">
|
||||
<view class="transfer-record-card__row transfer-record-card__row--top">
|
||||
<view class="transfer-record-card__main">
|
||||
<text class="transfer-record-card__title">{{
|
||||
item.title || "转赠记录"
|
||||
}}</text>
|
||||
<text
|
||||
v-if="item.orderSn || item.subtitle"
|
||||
class="transfer-record-card__order"
|
||||
>
|
||||
{{ item.orderSn ? "单号 " + item.orderSn : item.subtitle }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="transfer-record-card__amount-box">
|
||||
<text class="transfer-record-card__asset">{{
|
||||
item.assetLabel
|
||||
}}</text>
|
||||
<text
|
||||
class="transfer-record-card__amount"
|
||||
:class="
|
||||
'transfer-record-card__amount--' +
|
||||
(item.cardTone || item.tone || 'info')
|
||||
"
|
||||
>
|
||||
{{ item.amount }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="transfer-record-card__row transfer-record-card__row--middle"
|
||||
>
|
||||
<text class="transfer-record-card__balance">{{
|
||||
item.balanceLabel || item.balance
|
||||
}}</text>
|
||||
<text class="transfer-record-card__fee">{{ item.feeText }}</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="transfer-record-card__row transfer-record-card__row--bottom"
|
||||
>
|
||||
<text class="transfer-record-card__time">{{ item.time }}</text>
|
||||
<text
|
||||
class="transfer-record-card__direction"
|
||||
:class="
|
||||
'transfer-record-card__direction--' +
|
||||
(item.cardTone || item.tone || 'info')
|
||||
"
|
||||
>
|
||||
{{ item.directionLabel }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="transfer-empty">
|
||||
<text class="transfer-empty__title">暂无转赠记录</text>
|
||||
<text class="transfer-empty__desc">
|
||||
当前还没有积分或算力的转赠流水。
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<view class="page-hero">
|
||||
<view class="page-hero__mark">
|
||||
<view class="page-hero__ring page-hero__ring--outer"></view>
|
||||
<view class="page-hero__ring page-hero__ring--inner"></view>
|
||||
<text class="page-hero__text">{{ pageMark }}</text>
|
||||
</view>
|
||||
<text class="page-hero__value">{{ ledger.title || "记录" }}</text>
|
||||
<text v-if="ledger.subtitle" class="page-hero__desc">{{
|
||||
ledger.subtitle
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<view class="section glass-panel panel-block">
|
||||
<text class="section-label">{{ ledger.title }}</text>
|
||||
<text class="section-subtitle">{{ ledger.subtitle }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="ledger.summary" class="section glass-panel panel-block">
|
||||
<text class="section-label">{{ ledger.summary.label }}</text>
|
||||
<text class="summary-hero">{{ ledger.summary.value }}</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="ledger.tabs && ledger.tabs.length"
|
||||
class="section paper-panel panel-block"
|
||||
>
|
||||
<view class="tab-row tab-row--light">
|
||||
<view
|
||||
v-for="tab in ledger.tabs"
|
||||
:key="tab.key"
|
||||
class="tab-chip tab-chip--light"
|
||||
:class="{ 'is-active': activeTab === tab.key }"
|
||||
@click="activeTab = tab.key"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="record-wrapper">
|
||||
<asset-record-list :list="currentList" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="section paper-panel panel-block">
|
||||
<asset-record-list :list="ledger.records || []" />
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetPageShell from "../../components/asset-page-shell.vue";
|
||||
import AssetRecordList from "../../components/asset-record-list.vue";
|
||||
import { fetchLedgerDetail } from "../../api/assets";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetPageShell,
|
||||
AssetRecordList,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: "transfer",
|
||||
ledger: {
|
||||
title: "",
|
||||
subtitle: "",
|
||||
records: [],
|
||||
tabs: [],
|
||||
},
|
||||
activeTab: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isTransferLedger() {
|
||||
return this.type === "transfer";
|
||||
},
|
||||
currentList() {
|
||||
if (!this.ledger.recordsByTab || !this.activeTab) {
|
||||
return [];
|
||||
}
|
||||
return this.ledger.recordsByTab[this.activeTab] || [];
|
||||
},
|
||||
transferRecords() {
|
||||
return Array.isArray(this.ledger.records) ? this.ledger.records : [];
|
||||
},
|
||||
pageMark() {
|
||||
const markMap = {
|
||||
transfer: "⇄",
|
||||
power: "⚡",
|
||||
bmt: "D",
|
||||
withdraw: "B",
|
||||
coupon: "券",
|
||||
voucher: "抵",
|
||||
points: "积",
|
||||
};
|
||||
return markMap[this.type] || "记";
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.type = (options && options.type) || "transfer";
|
||||
this.loadPage(true);
|
||||
},
|
||||
methods: {
|
||||
async loadPage(showLoading) {
|
||||
try {
|
||||
const result = await fetchLedgerDetail(
|
||||
this.type,
|
||||
showLoading
|
||||
? {
|
||||
showLoading: true,
|
||||
loadingText: "加载中",
|
||||
}
|
||||
: null,
|
||||
);
|
||||
this.ledger = result;
|
||||
if (result.tabs && result.tabs.length) {
|
||||
this.activeTab = result.tabs[0].key;
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "记录加载失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tokens.scss";
|
||||
@import "../../styles/common.scss";
|
||||
|
||||
.ledger-page {
|
||||
min-height: 100vh;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.ledger-scroll {
|
||||
padding: 12rpx 14rpx calc(env(safe-area-inset-bottom) + 36rpx);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.summary-hero {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
color: $asset-text-main;
|
||||
}
|
||||
|
||||
.tab-row--light {
|
||||
background: rgba(17, 27, 54, 0.54);
|
||||
}
|
||||
|
||||
.tab-chip--light {
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
}
|
||||
|
||||
.record-wrapper {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.transfer-ledger-head {
|
||||
padding: 10rpx 6rpx 14rpx;
|
||||
}
|
||||
|
||||
.transfer-ledger-head__eyebrow {
|
||||
display: block;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.transfer-ledger-head__desc {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: rgba(188, 197, 223, 0.82);
|
||||
}
|
||||
|
||||
.transfer-ledger-list {
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.transfer-record-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-top: 18rpx;
|
||||
padding: 24rpx 20rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #242944;
|
||||
box-shadow: 0 14rpx 30rpx rgba(8, 13, 30, 0.14);
|
||||
}
|
||||
|
||||
.transfer-record-card:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.transfer-record-card__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
margin-right: 18rpx;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.transfer-record-card__icon--success {
|
||||
background: rgba(41, 215, 164, 0.16);
|
||||
color: #29d7a4;
|
||||
}
|
||||
|
||||
.transfer-record-card__icon--danger {
|
||||
background: rgba(255, 139, 93, 0.16);
|
||||
color: #ff8b5d;
|
||||
}
|
||||
|
||||
.transfer-record-card__icon--info {
|
||||
background: rgba(32, 182, 245, 0.16);
|
||||
color: #20b6f5;
|
||||
}
|
||||
|
||||
.transfer-record-card__icon-text {
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.transfer-record-card__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.transfer-record-card__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.transfer-record-card__row--top {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.transfer-record-card__row--middle,
|
||||
.transfer-record-card__row--bottom {
|
||||
align-items: center;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.transfer-record-card__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
|
||||
.transfer-record-card__title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.transfer-record-card__order {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.5;
|
||||
color: rgba(167, 177, 207, 0.9);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.transfer-record-card__amount-box {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.transfer-record-card__asset {
|
||||
margin-right: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: rgba(191, 200, 224, 0.76);
|
||||
}
|
||||
|
||||
.transfer-record-card__amount {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.transfer-record-card__amount--success {
|
||||
color: #29d7a4;
|
||||
}
|
||||
|
||||
.transfer-record-card__amount--danger {
|
||||
color: #ff8b5d;
|
||||
}
|
||||
|
||||
.transfer-record-card__amount--info {
|
||||
color: #20b6f5;
|
||||
}
|
||||
|
||||
.transfer-record-card__balance,
|
||||
.transfer-record-card__time {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
color: rgba(200, 208, 231, 0.82);
|
||||
}
|
||||
|
||||
.transfer-record-card__time {
|
||||
font-size: 22rpx;
|
||||
color: rgba(159, 170, 202, 0.88);
|
||||
}
|
||||
|
||||
.transfer-record-card__fee {
|
||||
margin-left: 20rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
text-align: right;
|
||||
color: #5ad7a1;
|
||||
}
|
||||
|
||||
.transfer-record-card__direction {
|
||||
margin-left: 20rpx;
|
||||
padding: 8rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.transfer-record-card__direction--success {
|
||||
background: rgba(41, 215, 164, 0.14);
|
||||
color: #29d7a4;
|
||||
}
|
||||
|
||||
.transfer-record-card__direction--danger {
|
||||
background: rgba(255, 139, 93, 0.14);
|
||||
color: #ff8b5d;
|
||||
}
|
||||
|
||||
.transfer-record-card__direction--info {
|
||||
background: rgba(32, 182, 245, 0.14);
|
||||
color: #20b6f5;
|
||||
}
|
||||
|
||||
.transfer-empty {
|
||||
margin-top: 20rpx;
|
||||
padding: 48rpx 28rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #242944;
|
||||
text-align: center;
|
||||
box-shadow: 0 14rpx 30rpx rgba(8, 13, 30, 0.14);
|
||||
}
|
||||
|
||||
.transfer-empty__title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.transfer-empty__desc {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: rgba(188, 197, 223, 0.78);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
<template>
|
||||
<view class="asset-page asset-theme points-page">
|
||||
<asset-page-shell title="积分转换" />
|
||||
|
||||
<view class="asset-scroll points-scroll">
|
||||
<view class="hero-card">
|
||||
<view class="hero-card__content">
|
||||
<view class="hero-card__coin-row">
|
||||
<view class="hero-card__coin">
|
||||
<image
|
||||
class="hero-card__coin-image"
|
||||
:src="pageIcon('points')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
<text class="hero-card__coin-text">积分</text>
|
||||
</view>
|
||||
<text class="hero-card__label">可用积分:</text>
|
||||
<text class="hero-card__value">{{ displayAvailablePoints }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="pending-card">
|
||||
<text class="pending-card__label">未转换积分(释放中的积分)</text>
|
||||
<text class="pending-card__value">{{ displayPendingPoints }}</text>
|
||||
</view>
|
||||
|
||||
<view class="tips-block">
|
||||
<view
|
||||
v-for="(tip, index) in detail.tips"
|
||||
:key="tip"
|
||||
class="tips-block__item"
|
||||
>
|
||||
<text class="tips-block__index">{{ index + 1 }}.</text>
|
||||
<text class="tips-block__text">{{ tip }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-wrap">
|
||||
<view
|
||||
class="action-button"
|
||||
:class="{ 'action-button--disabled': !canSubmit || submitting }"
|
||||
@click="submit"
|
||||
>
|
||||
{{ submitting ? "转换中..." : "转换可用积分" }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetPageShell from "../../components/asset-page-shell.vue";
|
||||
import {
|
||||
fetchPointsConvertDetail,
|
||||
submitAssetPointsConvert,
|
||||
} from "../../api/assets";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetPageShell,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasShown: false,
|
||||
detail: {
|
||||
availablePoints: "0.00",
|
||||
pendingPoints: "0",
|
||||
ids: [],
|
||||
tips: [],
|
||||
},
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.loadPage(true);
|
||||
},
|
||||
onShow() {
|
||||
if (this.hasShown) {
|
||||
this.loadPage();
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasShown = true;
|
||||
},
|
||||
computed: {
|
||||
pendingPointsValue() {
|
||||
return Number(this.detail.pendingPoints || 0);
|
||||
},
|
||||
canSubmit() {
|
||||
return this.pendingPointsValue > 0;
|
||||
},
|
||||
displayAvailablePoints() {
|
||||
return this.formatAmount(this.detail.availablePoints, 2);
|
||||
},
|
||||
displayPendingPoints() {
|
||||
return this.formatAmount(this.detail.pendingPoints, 0);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pageIcon(type) {
|
||||
const iconMap = {
|
||||
points: "https://imgs.agrimedia.cn/bm-bmt/j-w.png",
|
||||
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon.png",
|
||||
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon.png",
|
||||
power: "https://imgs.agrimedia.cn/bm-bmt/s.png",
|
||||
bmt: "https://imgs.agrimedia.cn/bm-bmt/b.png",
|
||||
};
|
||||
|
||||
return iconMap[type] || "";
|
||||
},
|
||||
formatAmount(value, digits) {
|
||||
const number = Number(value || 0);
|
||||
if (!Number.isFinite(number)) {
|
||||
return digits > 0 ? Number(0).toFixed(digits) : "0";
|
||||
}
|
||||
|
||||
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.floor(number)
|
||||
.toString()
|
||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
},
|
||||
async loadPage(showLoading) {
|
||||
try {
|
||||
this.detail = await fetchPointsConvertDetail(
|
||||
showLoading
|
||||
? {
|
||||
showLoading: true,
|
||||
loadingText: "加载中",
|
||||
}
|
||||
: null,
|
||||
);
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "页面加载失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
if (this.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.canSubmit) {
|
||||
uni.showToast({
|
||||
title: "暂无可转换积分",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
const result = await submitAssetPointsConvert({
|
||||
pendingPoints: this.pendingPointsValue,
|
||||
ids: this.detail.ids,
|
||||
}, {
|
||||
showLoading: true,
|
||||
loadingText: "转换中",
|
||||
});
|
||||
|
||||
uni.showToast({
|
||||
title: result.message || "转换成功",
|
||||
icon: "none",
|
||||
});
|
||||
this.loadPage();
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "转换失败",
|
||||
icon: "none",
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tokens.scss";
|
||||
@import "../../styles/common.scss";
|
||||
|
||||
.points-page {
|
||||
min-height: 100vh;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.points-scroll {
|
||||
min-height: calc(100vh - env(safe-area-inset-top) - 104rpx);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10rpx 14rpx calc(env(safe-area-inset-bottom) + 36rpx);
|
||||
}
|
||||
|
||||
.hero-card,
|
||||
.pending-card {
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.12);
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
position: relative;
|
||||
min-height: 240rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
overflow: hidden;
|
||||
background: #242944 url("https://imgs.agrimedia.cn/bm-bmt/jifen-header.png")
|
||||
no-repeat center top / 100% 240rpx;
|
||||
}
|
||||
|
||||
.hero-card__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hero-card__coin-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-card__coin {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #f85ca9 0%, #de4d95 100%);
|
||||
box-shadow: 0 12rpx 20rpx rgba(230, 90, 156, 0.2);
|
||||
}
|
||||
|
||||
.hero-card__coin-image {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
|
||||
.hero-card__coin-text {
|
||||
margin-left: 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero-card__label {
|
||||
margin-top: 38rpx;
|
||||
font-size: 28rpx;
|
||||
color: #9ba7ce;
|
||||
}
|
||||
|
||||
.hero-card__value {
|
||||
margin-top: 8rpx;
|
||||
font-size: 48rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.08;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.pending-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 20rpx;
|
||||
padding: 30rpx 26rpx;
|
||||
background: rgba(59, 49, 67, 0.92);
|
||||
}
|
||||
|
||||
.pending-card__label,
|
||||
.pending-card__value {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #ff885d;
|
||||
}
|
||||
|
||||
.pending-card__value {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.tips-block {
|
||||
margin-top: 20rpx;
|
||||
padding: 0 2rpx;
|
||||
}
|
||||
|
||||
.tips-block__item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.tips-block__item:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tips-block__index,
|
||||
.tips-block__text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 400;
|
||||
line-height: 1.75;
|
||||
color: rgba(173, 184, 216, 0.82);
|
||||
}
|
||||
|
||||
.tips-block__index {
|
||||
flex-shrink: 0;
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.action-wrap {
|
||||
margin-top: auto;
|
||||
padding: 120rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 90rpx;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #22c2ff 0%, #159de6 100%);
|
||||
box-shadow: 0 16rpx 30rpx rgba(31, 169, 243, 0.2);
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-button--disabled {
|
||||
opacity: 0.56;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,743 @@
|
|||
<template>
|
||||
<view class="asset-page asset-theme power-page">
|
||||
<asset-page-shell title="算力兑换" />
|
||||
|
||||
<view class="asset-scroll power-scroll">
|
||||
<view class="hero-card">
|
||||
<view class="hero-card__content">
|
||||
<view class="hero-card__coin-row">
|
||||
<view class="hero-card__coin">
|
||||
<image
|
||||
class="hero-card__coin-image"
|
||||
:src="pageIcon('power')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
<text class="hero-card__coin-text">算力</text>
|
||||
</view>
|
||||
<text class="hero-card__label">已有算力:</text>
|
||||
<text class="hero-card__value">{{ displayPower }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-card info-card">
|
||||
<view class="info-row">
|
||||
<view class="info-row__left">
|
||||
<image
|
||||
class="info-row__icon"
|
||||
:src="pageIcon('voucher')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="info-row__label">我的抵用券</text>
|
||||
</view>
|
||||
<text class="info-row__value">{{ displayVoucher }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<view class="info-row__left">
|
||||
<image
|
||||
class="info-row__icon"
|
||||
:src="pageIcon('coupon')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="info-row__label">我的消费券</text>
|
||||
</view>
|
||||
<text class="info-row__value">{{ displayCoupon }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row info-row--last">
|
||||
<view class="info-row__left">
|
||||
<image
|
||||
class="info-row__icon"
|
||||
:src="pageIcon('bmt')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="info-row__label">BMT实时价格</text>
|
||||
</view>
|
||||
<view class="info-row__price">
|
||||
<text class="info-row__value">{{ displayPrice }}</text>
|
||||
<text class="info-row__unit">RMB/BMT</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-card__estimate">
|
||||
<text>全部兑换预估可得</text>
|
||||
<text class="info-card__estimate-value">{{ allEstimatePower }}</text>
|
||||
<text>算力</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-card form-card">
|
||||
<view class="form-card__title">
|
||||
<image
|
||||
class="form-card__title-icon"
|
||||
:src="pageIcon('power')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="form-card__title-text">算力兑换</text>
|
||||
</view>
|
||||
|
||||
<view class="mode-tabs">
|
||||
<view
|
||||
v-for="item in modeList"
|
||||
:key="item.value"
|
||||
class="mode-tabs__item"
|
||||
:class="{ 'is-active': form.mode === item.value }"
|
||||
@click="form.mode = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-input">
|
||||
<input
|
||||
v-model="form.amount"
|
||||
class="form-input__field"
|
||||
type="number"
|
||||
:placeholder="'请输入' + currentModeLabel + '数量'"
|
||||
placeholder-class="form-input__placeholder"
|
||||
/>
|
||||
<text class="form-input__suffix">{{ currentModeLabel }}</text>
|
||||
</view>
|
||||
|
||||
<view class="preview-card">
|
||||
<view class="preview-card__row">
|
||||
<view class="preview-card__left">
|
||||
<text class="preview-card__label">预估兑换算力</text>
|
||||
</view>
|
||||
<text class="preview-card__value">{{ estimatePower }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tips-block">
|
||||
<text class="tips-block__title">兑换说明:</text>
|
||||
<view
|
||||
v-for="(tip, index) in normalizedTips"
|
||||
:key="tip"
|
||||
class="tips-block__item"
|
||||
>
|
||||
<text class="tips-block__index">{{ index + 1 }}.</text>
|
||||
<text class="tips-block__text">{{ tip }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-wrap">
|
||||
<view class="action-button" @click="openConfirmDialog">确认兑换</view>
|
||||
<view class="action-link" @click="openLedger">兑换记录</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<asset-confirm-popup
|
||||
:visible="dialog.visible"
|
||||
:title="dialog.title"
|
||||
:message="dialog.message"
|
||||
:description="dialog.description"
|
||||
:confirm-text="dialog.confirmText"
|
||||
:cancel-text="dialog.cancelText"
|
||||
:show-cancel="dialog.showCancel"
|
||||
:status="dialog.status"
|
||||
@cancel="closeDialog"
|
||||
@confirm="handleDialogConfirm"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetConfirmPopup from "../../components/asset-confirm-popup.vue";
|
||||
import AssetPageShell from "../../components/asset-page-shell.vue";
|
||||
import {
|
||||
fetchPowerExchangeDetail,
|
||||
submitAssetPowerExchange,
|
||||
} from "../../api/assets";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetConfirmPopup,
|
||||
AssetPageShell,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modeList: [
|
||||
{ label: "抵用券兑换", value: "voucher" },
|
||||
{ label: "消费券兑换", value: "coupon" },
|
||||
],
|
||||
detail: {
|
||||
ticker: {},
|
||||
balances: {},
|
||||
tips: [],
|
||||
},
|
||||
hasShown: false,
|
||||
form: {
|
||||
mode: "voucher",
|
||||
amount: "",
|
||||
},
|
||||
dialog: {
|
||||
visible: false,
|
||||
title: "确认兑换",
|
||||
message: "",
|
||||
description: "",
|
||||
confirmText: "确认",
|
||||
cancelText: "取消",
|
||||
showCancel: true,
|
||||
status: "default",
|
||||
action: "",
|
||||
},
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.loadPage(true);
|
||||
},
|
||||
onShow() {
|
||||
if (this.hasShown) {
|
||||
this.loadPage();
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasShown = true;
|
||||
},
|
||||
computed: {
|
||||
amountValue() {
|
||||
return Number(this.form.amount || 0);
|
||||
},
|
||||
currentModeLabel() {
|
||||
return this.form.mode === "coupon" ? "消费券" : "抵用券";
|
||||
},
|
||||
currentModeBalance() {
|
||||
if (this.form.mode === "coupon") {
|
||||
return Number(this.detail.balances.coupon || 0);
|
||||
}
|
||||
|
||||
return Number(this.detail.balances.voucher || 0);
|
||||
},
|
||||
priceNumber() {
|
||||
return Number(
|
||||
this.detail.ticker.close || this.detail.ticker.cnyPrice || 0,
|
||||
);
|
||||
},
|
||||
estimatePower() {
|
||||
if (!this.amountValue || !this.priceNumber) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
return this.formatAmount(this.amountValue / this.priceNumber, 0);
|
||||
},
|
||||
allEstimatePower() {
|
||||
if (!this.priceNumber) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
const totalTickets =
|
||||
Number(this.detail.balances.voucher || 0) +
|
||||
Number(this.detail.balances.coupon || 0);
|
||||
|
||||
return this.formatAmount(totalTickets / this.priceNumber, 0);
|
||||
},
|
||||
displayPower() {
|
||||
return this.formatAmount(this.detail.balances.power, 2);
|
||||
},
|
||||
displayVoucher() {
|
||||
return this.formatAmount(this.detail.balances.voucher, 0);
|
||||
},
|
||||
displayCoupon() {
|
||||
return this.formatAmount(this.detail.balances.coupon, 2);
|
||||
},
|
||||
displayPrice() {
|
||||
return this.priceNumber ? this.priceNumber.toFixed(2) : "0.00";
|
||||
},
|
||||
normalizedTips() {
|
||||
if (this.detail.tips && this.detail.tips.length) {
|
||||
return this.detail.tips;
|
||||
}
|
||||
|
||||
return [
|
||||
"算力 = 抵用券或消费券 ÷ BMT实时价格;",
|
||||
"抵用券和消费券总数小于100券的不可兑换;",
|
||||
"算力用于兑换BMT使用。",
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pageIcon(type) {
|
||||
const iconMap = {
|
||||
power: "https://imgs.agrimedia.cn/bm-bmt/s.png",
|
||||
bmt: "https://imgs.agrimedia.cn/bm-bmt/b.png",
|
||||
transfer: "https://imgs.agrimedia.cn/bm-bmt/z.png",
|
||||
withdraw: "https://imgs.agrimedia.cn/bm-bmt/t.png",
|
||||
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon-g%20%281%29.png",
|
||||
points: "https://imgs.agrimedia.cn/bm-bmt/j.png",
|
||||
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon-o.png",
|
||||
};
|
||||
|
||||
return iconMap[type] || "";
|
||||
},
|
||||
formatAmount(value, digits) {
|
||||
const number = Number(value || 0);
|
||||
if (!Number.isFinite(number)) {
|
||||
return digits > 0 ? Number(0).toFixed(digits) : "0";
|
||||
}
|
||||
|
||||
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.floor(number)
|
||||
.toString()
|
||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
},
|
||||
async loadPage(showLoading) {
|
||||
try {
|
||||
this.detail = await fetchPowerExchangeDetail(
|
||||
showLoading
|
||||
? {
|
||||
showLoading: true,
|
||||
loadingText: "加载中",
|
||||
}
|
||||
: null,
|
||||
);
|
||||
} catch (error) {
|
||||
this.openResultDialog(
|
||||
"error",
|
||||
"兑换失败!",
|
||||
error.message || "页面加载失败",
|
||||
);
|
||||
}
|
||||
},
|
||||
openDialog(payload) {
|
||||
this.dialog = {
|
||||
...this.dialog,
|
||||
...payload,
|
||||
visible: true,
|
||||
};
|
||||
},
|
||||
closeDialog() {
|
||||
this.dialog.visible = false;
|
||||
this.dialog.action = "";
|
||||
},
|
||||
openResultDialog(status, message, description) {
|
||||
this.openDialog({
|
||||
title: "确认兑换",
|
||||
message: message,
|
||||
description: description || "",
|
||||
confirmText: "确认",
|
||||
cancelText: "取消",
|
||||
showCancel: false,
|
||||
status: status,
|
||||
action: "close",
|
||||
});
|
||||
},
|
||||
openConfirmDialog() {
|
||||
if (!this.amountValue) {
|
||||
this.openResultDialog("error", "兑换失败!", "请输入兑换券数");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.amountValue < 100) {
|
||||
this.openResultDialog(
|
||||
"error",
|
||||
"兑换失败!",
|
||||
"抵用券和消费券总数小于100券的不可兑换",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.amountValue > this.currentModeBalance) {
|
||||
this.openResultDialog(
|
||||
"error",
|
||||
"兑换失败!",
|
||||
"当前可用" + this.currentModeLabel + "不足",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.openDialog({
|
||||
title: "确认兑换",
|
||||
message:
|
||||
"确认使用" +
|
||||
this.formatAmount(this.amountValue, 0) +
|
||||
this.currentModeLabel +
|
||||
"兑换算力?",
|
||||
description: "",
|
||||
confirmText: "确认",
|
||||
cancelText: "取消",
|
||||
showCancel: true,
|
||||
status: "default",
|
||||
action: "submit",
|
||||
});
|
||||
},
|
||||
async submit() {
|
||||
if (this.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
await submitAssetPowerExchange({
|
||||
mode: this.form.mode,
|
||||
amount: this.amountValue,
|
||||
}, {
|
||||
showLoading: true,
|
||||
loadingText: "兑换中",
|
||||
});
|
||||
this.form.amount = "";
|
||||
await this.loadPage();
|
||||
this.openResultDialog("success", "兑换成功!", "");
|
||||
} catch (error) {
|
||||
this.openResultDialog(
|
||||
"error",
|
||||
"兑换失败!",
|
||||
error.message || "系统繁忙,请稍后再试",
|
||||
);
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
handleDialogConfirm() {
|
||||
if (this.dialog.action === "submit") {
|
||||
this.closeDialog();
|
||||
this.submit();
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeDialog();
|
||||
},
|
||||
openLedger() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/assets/ledger?type=power",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tokens.scss";
|
||||
@import "../../styles/common.scss";
|
||||
|
||||
.power-page {
|
||||
min-height: 100vh;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.power-scroll {
|
||||
padding: 8rpx 20rpx calc(env(safe-area-inset-bottom) + 36rpx);
|
||||
}
|
||||
|
||||
.panel-card,
|
||||
.hero-card {
|
||||
border-radius: 12rpx;
|
||||
background: #20263e;
|
||||
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.12);
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
position: relative;
|
||||
min-height: 240rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
overflow: hidden;
|
||||
background: #20263e url("https://imgs.agrimedia.cn/bm-bmt/suanli-bgs.png")
|
||||
no-repeat center top / 100% 240rpx;
|
||||
}
|
||||
|
||||
.hero-card__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hero-card__coin-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-card__coin {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #33ddb0 0%, #24c38f 100%);
|
||||
}
|
||||
|
||||
.hero-card__coin-image {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
|
||||
.hero-card__coin-text {
|
||||
margin-left: 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero-card__label {
|
||||
margin-top: 34rpx;
|
||||
font-size: 28rpx;
|
||||
color: #9ba7ce;
|
||||
}
|
||||
|
||||
.hero-card__value {
|
||||
margin-top: 8rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.panel-card {
|
||||
margin-top: 18rpx;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
padding-top: 2rpx;
|
||||
padding-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 104rpx;
|
||||
border-bottom: 1px solid rgba(136, 148, 193, 0.34);
|
||||
}
|
||||
|
||||
.info-row--last {
|
||||
border-bottom: 1px solid rgba(136, 148, 193, 0.34);
|
||||
}
|
||||
|
||||
.info-row__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.info-row__icon {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-row__label {
|
||||
margin-left: 20rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: #eef2ff;
|
||||
}
|
||||
|
||||
.info-row__value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.info-row__price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.info-row__unit {
|
||||
margin-left: 10rpx;
|
||||
font-size: 22rpx;
|
||||
color: rgba(184, 193, 218, 0.88);
|
||||
}
|
||||
|
||||
.info-card__estimate {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: flex-end;
|
||||
padding-top: 18rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1ad296;
|
||||
}
|
||||
|
||||
.info-card__estimate-value {
|
||||
margin: 0 10rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 800;
|
||||
color: #1ad296;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
padding-top: 18rpx;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.form-card__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-card__title-icon {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-card__title-text {
|
||||
margin-left: 14rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.mode-tabs {
|
||||
display: flex;
|
||||
width: 400rpx;
|
||||
margin: 26rpx auto 0;
|
||||
padding: 4rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(102, 115, 160, 0.28);
|
||||
box-shadow: inset 0 0 0 1px rgba(165, 177, 216, 0.16);
|
||||
}
|
||||
|
||||
.mode-tabs__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(210, 219, 242, 0.72);
|
||||
}
|
||||
|
||||
.mode-tabs__item.is-active {
|
||||
background: #02abf1;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 34rpx;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.form-input__field {
|
||||
flex: 1;
|
||||
height: 98rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.form-input__placeholder {
|
||||
color: rgba(177, 187, 214, 0.42);
|
||||
}
|
||||
|
||||
.form-input__suffix {
|
||||
margin-left: 16rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.preview-card {
|
||||
margin-top: 18rpx;
|
||||
padding: 14rpx 18rpx;
|
||||
border-radius: 8rpx;
|
||||
background: linear-gradient(180deg, #21455a 0%, #1f4358 100%);
|
||||
}
|
||||
|
||||
.preview-card__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.preview-card__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preview-card__icon {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
margin-right: 10rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.preview-card__label {
|
||||
font-size: 26rpx;
|
||||
color: rgba(229, 252, 255, 0.94);
|
||||
}
|
||||
|
||||
.preview-card__value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
color: #1df0b8;
|
||||
}
|
||||
|
||||
.tips-block {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.tips-block__title {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: rgba(190, 197, 219, 0.92);
|
||||
}
|
||||
|
||||
.tips-block__item {
|
||||
display: flex;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.tips-block__index,
|
||||
.tips-block__text {
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
color: #929dbf;
|
||||
line-height: 44rpx;
|
||||
color: rgba(166, 175, 204, 0.92);
|
||||
}
|
||||
|
||||
.tips-block__index {
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.tips-block__text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-wrap {
|
||||
padding: 36rpx 22rpx 0;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
border-radius: 46rpx;
|
||||
background: linear-gradient(135deg, #20b6f5 0%, #1ca5e3 100%);
|
||||
box-shadow: 0 16rpx 30rpx rgba(20, 119, 214, 0.16);
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-link {
|
||||
margin-top: 26rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #1fb4ff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,793 @@
|
|||
<template>
|
||||
<view class="asset-page asset-theme transfer-page">
|
||||
<asset-page-shell title="转赠中心" />
|
||||
|
||||
<view class="asset-scroll transfer-scroll">
|
||||
<view class="mode-tabs">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
class="mode-tabs__item"
|
||||
:class="{ 'is-active': activeTab === tab.value }"
|
||||
@click="switchTab(tab.value)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-card transfer-card">
|
||||
<view class="section-head">
|
||||
<image
|
||||
class="section-head__icon"
|
||||
:src="pageIcon(activeTab === 'power' ? 'power' : 'points')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="section-head__title">{{ currentTabLabel }}转赠</text>
|
||||
<text class="section-head__meta"
|
||||
>(可用{{ currentBalanceDisplay }})</text
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="input-bar">
|
||||
<input
|
||||
v-model="form.amount"
|
||||
class="input-bar__field"
|
||||
type="number"
|
||||
:placeholder="amountPlaceholder"
|
||||
placeholder-class="input-bar__placeholder"
|
||||
/>
|
||||
<text class="input-bar__action" @click="fillAll">全部赠送</text>
|
||||
</view>
|
||||
|
||||
<view class="tips-list">
|
||||
<view
|
||||
v-for="(tip, index) in currentTips"
|
||||
:key="tip"
|
||||
class="tips-list__item"
|
||||
>
|
||||
<text class="tips-list__index">{{ index + 1 }}.</text>
|
||||
<text class="tips-list__text">{{ tip }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="transfer-split">
|
||||
<image
|
||||
class="transfer-split__icon"
|
||||
src="https://imgs.agrimedia.cn/bm-bmt/z.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
|
||||
<view class="panel-card recipient-card">
|
||||
<view class="section-head">
|
||||
<view class="recipient-card__title-icon">
|
||||
<view class="recipient-card__title-head"></view>
|
||||
<view class="recipient-card__title-body"></view>
|
||||
</view>
|
||||
<text class="section-head__title">被赠送人</text>
|
||||
</view>
|
||||
|
||||
<view class="input-bar recipient-search">
|
||||
<input
|
||||
v-model="searchId"
|
||||
class="input-bar__field"
|
||||
type="number"
|
||||
placeholder="请输入被赠人ID"
|
||||
placeholder-class="input-bar__placeholder"
|
||||
/>
|
||||
<text class="input-bar__action" @click="queryTarget">查询</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="currentTarget.id"
|
||||
class="target-card"
|
||||
@click="selectCurrent"
|
||||
>
|
||||
<view class="target-card__top">
|
||||
<text class="target-card__id">ID: {{ currentTarget.id }}</text>
|
||||
<view class="target-card__select">
|
||||
<text class="target-card__select-text">选择</text>
|
||||
<view class="target-card__select-dot">✓</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="target-card__line"></view>
|
||||
<view class="target-card__bottom">
|
||||
<view class="target-card__profile">
|
||||
<view
|
||||
class="target-card__avatar"
|
||||
:class="{
|
||||
'target-card__avatar--image': !!currentTarget.avatar,
|
||||
}"
|
||||
>
|
||||
<image
|
||||
v-if="currentTarget.avatar"
|
||||
class="target-card__avatar-image"
|
||||
:src="currentTarget.avatar"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<text v-else class="target-card__avatar-text">{{
|
||||
targetInitial(currentTarget.nickname)
|
||||
}}</text>
|
||||
</view>
|
||||
<text class="target-card__name">{{
|
||||
currentTarget.nickname
|
||||
}}</text>
|
||||
</view>
|
||||
<text class="target-card__phone">{{ currentTarget.phone }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-wrap">
|
||||
<view class="action-button" @click="openConfirm">确认转赠</view>
|
||||
<view class="action-link" @click="openLedger">转赠记录</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<asset-confirm-popup
|
||||
:visible="confirmVisible"
|
||||
title="确认转赠"
|
||||
message="确认转赠以下资产给好友?"
|
||||
confirm-text="确认"
|
||||
cancel-text="取消"
|
||||
@cancel="confirmVisible = false"
|
||||
@confirm="submit"
|
||||
>
|
||||
<view class="confirm-detail">
|
||||
<text class="confirm-detail__summary">
|
||||
转赠{{ currentTabLabel }}:{{ amountDisplay }}
|
||||
<text class="confirm-detail__summary-sub"
|
||||
>(实际到账{{ receivedDisplay }})</text
|
||||
>
|
||||
</text>
|
||||
|
||||
<view class="confirm-detail__target">
|
||||
<text class="confirm-detail__id">ID: {{ currentTarget.id }}</text>
|
||||
<view class="confirm-detail__line"></view>
|
||||
<view class="confirm-detail__profile">
|
||||
<view class="target-card__profile">
|
||||
<view
|
||||
class="target-card__avatar"
|
||||
:class="{
|
||||
'target-card__avatar--image': !!currentTarget.avatar,
|
||||
}"
|
||||
>
|
||||
<image
|
||||
v-if="currentTarget.avatar"
|
||||
class="target-card__avatar-image"
|
||||
:src="currentTarget.avatar"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<text v-else class="target-card__avatar-text">{{
|
||||
targetInitial(currentTarget.nickname)
|
||||
}}</text>
|
||||
</view>
|
||||
<text class="target-card__name">{{
|
||||
currentTarget.nickname
|
||||
}}</text>
|
||||
</view>
|
||||
<text class="target-card__phone">{{ currentTarget.phone }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="confirm-detail__desc">
|
||||
请仔细校对被赠送账号信息,转赠成功后无法追回
|
||||
</text>
|
||||
</view>
|
||||
</asset-confirm-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetConfirmPopup from "../../components/asset-confirm-popup.vue";
|
||||
import AssetPageShell from "../../components/asset-page-shell.vue";
|
||||
import {
|
||||
fetchTransferDetail,
|
||||
searchTransferUser,
|
||||
submitAssetTransfer,
|
||||
} from "../../api/assets";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetConfirmPopup,
|
||||
AssetPageShell,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabs: [
|
||||
{
|
||||
label: "积分转赠",
|
||||
value: "points",
|
||||
},
|
||||
{
|
||||
label: "算力转赠",
|
||||
value: "power",
|
||||
},
|
||||
],
|
||||
activeTab: "points",
|
||||
targets: [],
|
||||
selectedIndex: 0,
|
||||
balances: {
|
||||
points: "0",
|
||||
power: "0",
|
||||
},
|
||||
feePercent: 10,
|
||||
tips: {
|
||||
points: [],
|
||||
power: [],
|
||||
},
|
||||
form: {
|
||||
amount: "",
|
||||
},
|
||||
searchId: "",
|
||||
confirmVisible: false,
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options && options.mode === "power") {
|
||||
this.activeTab = "power";
|
||||
}
|
||||
this.loadPage(true);
|
||||
},
|
||||
computed: {
|
||||
currentTabLabel() {
|
||||
return this.activeTab === "power" ? "算力" : "积分";
|
||||
},
|
||||
currentBalance() {
|
||||
return Number(this.balances[this.activeTab] || 0);
|
||||
},
|
||||
currentBalanceDisplay() {
|
||||
return this.formatAmount(this.currentBalance, 0);
|
||||
},
|
||||
currentTips() {
|
||||
const currentTips = this.tips[this.activeTab] || [];
|
||||
if (currentTips.length) {
|
||||
return currentTips;
|
||||
}
|
||||
|
||||
if (this.activeTab === "power") {
|
||||
return [
|
||||
"只能转赠1的整数倍",
|
||||
"转赠系统会扣除" + this.feePercent + "%的手续费",
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
"只能转赠100的整数倍",
|
||||
"凌晨0点-凌晨01点系统维护不可赠送",
|
||||
"转赠系统会扣除" + this.feePercent + "%的手续费",
|
||||
];
|
||||
},
|
||||
currentTarget() {
|
||||
return this.targets[this.selectedIndex] || {};
|
||||
},
|
||||
amountValue() {
|
||||
return Number(this.form.amount || 0);
|
||||
},
|
||||
amountDisplay() {
|
||||
return this.formatAmount(this.amountValue, 0);
|
||||
},
|
||||
receivedDisplay() {
|
||||
const received = this.amountValue
|
||||
? this.amountValue * (1 - this.feePercent / 100)
|
||||
: 0;
|
||||
return this.formatAmount(received, 0);
|
||||
},
|
||||
amountPlaceholder() {
|
||||
return this.activeTab === "power"
|
||||
? "请输入1的整数倍"
|
||||
: "请输入100的整数倍";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pageIcon(type) {
|
||||
const iconMap = {
|
||||
power: "https://imgs.agrimedia.cn/bm-bmt/s.png",
|
||||
bmt: "https://imgs.agrimedia.cn/bm-bmt/b.png",
|
||||
transfer: "https://imgs.agrimedia.cn/bm-bmt/z.png",
|
||||
withdraw: "https://imgs.agrimedia.cn/bm-bmt/t.png",
|
||||
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon.png",
|
||||
points: "https://imgs.agrimedia.cn/bm-bmt/j.png",
|
||||
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon.png",
|
||||
};
|
||||
|
||||
return iconMap[type] || "";
|
||||
},
|
||||
formatAmount(value, digits) {
|
||||
const number = Number(value || 0);
|
||||
if (!Number.isFinite(number)) {
|
||||
return digits > 0 ? Number(0).toFixed(digits) : "0";
|
||||
}
|
||||
|
||||
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.floor(number)
|
||||
.toString()
|
||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
},
|
||||
targetInitial(name) {
|
||||
return String(name || "友").slice(0, 1);
|
||||
},
|
||||
async loadPage(showLoading) {
|
||||
try {
|
||||
const result = await fetchTransferDetail(
|
||||
showLoading
|
||||
? {
|
||||
showLoading: true,
|
||||
loadingText: "加载中",
|
||||
}
|
||||
: null,
|
||||
);
|
||||
this.balances = result.balances || this.balances;
|
||||
this.feePercent = Number(result.feePercent || 10);
|
||||
this.tips = result.tips || this.tips;
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "转赠页加载失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
switchTab(value) {
|
||||
this.activeTab = value;
|
||||
this.form.amount = "";
|
||||
},
|
||||
fillAll() {
|
||||
this.form.amount = String(this.currentBalance || 0);
|
||||
},
|
||||
async queryTarget() {
|
||||
const keyword = String(this.searchId || "").trim();
|
||||
if (!keyword) {
|
||||
uni.showToast({
|
||||
title: "请输入被赠人ID",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const target = await searchTransferUser(keyword);
|
||||
this.targets = [target];
|
||||
this.selectedIndex = 0;
|
||||
this.searchId = target.id;
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "未查询到好友",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
selectCurrent() {
|
||||
if (this.currentTarget && this.currentTarget.id) {
|
||||
this.searchId = this.currentTarget.id;
|
||||
}
|
||||
},
|
||||
openConfirm() {
|
||||
if (!this.currentTarget.id) {
|
||||
uni.showToast({
|
||||
title: "请先查询被赠送人",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.amountValue) {
|
||||
uni.showToast({
|
||||
title: "请输入转赠数量",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.amountValue > this.currentBalance) {
|
||||
uni.showToast({
|
||||
title: "可用数量不足",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeTab === "points" && this.amountValue % 100 !== 0) {
|
||||
uni.showToast({
|
||||
title: "积分只能转赠100的整数倍",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeTab === "power" && !Number.isInteger(this.amountValue)) {
|
||||
uni.showToast({
|
||||
title: "算力只能转赠1的整数倍",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.confirmVisible = true;
|
||||
},
|
||||
async submit() {
|
||||
if (this.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
const result = await submitAssetTransfer({
|
||||
type: this.activeTab,
|
||||
targetId: this.currentTarget.id,
|
||||
amount: this.amountValue,
|
||||
}, {
|
||||
showLoading: true,
|
||||
loadingText: "转赠中",
|
||||
});
|
||||
this.confirmVisible = false;
|
||||
uni.showToast({
|
||||
title: "转赠成功,到账 " + result.received,
|
||||
icon: "none",
|
||||
});
|
||||
this.form.amount = "";
|
||||
this.loadPage();
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "转赠失败",
|
||||
icon: "none",
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
openLedger() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/assets/ledger?type=transfer",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tokens.scss";
|
||||
@import "../../styles/common.scss";
|
||||
|
||||
.transfer-page {
|
||||
min-height: 100vh;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.transfer-scroll {
|
||||
padding: 10rpx 22rpx calc(env(safe-area-inset-bottom) + 36rpx);
|
||||
}
|
||||
|
||||
.mode-tabs {
|
||||
display: flex;
|
||||
width: 400rpx;
|
||||
margin: 26rpx auto 0;
|
||||
padding: 4rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(102, 115, 160, 0.28);
|
||||
box-shadow: inset 0 0 0 1px rgba(165, 177, 216, 0.16);
|
||||
}
|
||||
|
||||
.mode-tabs__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(210, 219, 242, 0.72);
|
||||
}
|
||||
|
||||
.mode-tabs__item.is-active {
|
||||
background: #02abf1;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.panel-card {
|
||||
margin-top: 34rpx;
|
||||
padding: 22rpx 20rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #242944;
|
||||
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.12);
|
||||
}
|
||||
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-head__icon {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-head__title {
|
||||
margin-left: 20rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.section-head__meta {
|
||||
margin-left: 12rpx;
|
||||
font-size: 26rpx;
|
||||
color: #9ba7ce;
|
||||
}
|
||||
|
||||
.input-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 22rpx;
|
||||
padding: 0 18rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.input-bar__field {
|
||||
flex: 1;
|
||||
height: 84rpx;
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.input-bar__placeholder {
|
||||
color: rgba(170, 179, 204, 0.66);
|
||||
}
|
||||
|
||||
.input-bar__action {
|
||||
margin-left: 20rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #20b6f5;
|
||||
}
|
||||
|
||||
.tips-list {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.tips-list__item {
|
||||
display: flex;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.tips-list__index,
|
||||
.tips-list__text {
|
||||
font-size: 24rpx;
|
||||
color: #929dbf;
|
||||
line-height: 44rpx;
|
||||
color: rgba(175, 184, 211, 0.92);
|
||||
}
|
||||
|
||||
.tips-list__index {
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.tips-list__text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.transfer-split {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.transfer-split__icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.recipient-card {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.recipient-card__title-icon {
|
||||
position: relative;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.recipient-card__title-head {
|
||||
position: absolute;
|
||||
left: 8rpx;
|
||||
top: 0;
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: #18aef2;
|
||||
}
|
||||
|
||||
.recipient-card__title-body {
|
||||
position: absolute;
|
||||
left: 3rpx;
|
||||
bottom: 2rpx;
|
||||
width: 22rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 12rpx 12rpx 8rpx 8rpx;
|
||||
background: #18aef2;
|
||||
}
|
||||
|
||||
.recipient-search {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.target-card {
|
||||
margin-top: 18rpx;
|
||||
padding: 18rpx 18rpx 16rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #191e32;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.target-card__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.target-card__id {
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.target-card__select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.target-card__select-text {
|
||||
margin-right: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: rgba(244, 248, 255, 0.88);
|
||||
}
|
||||
|
||||
.target-card__select-dot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 50%;
|
||||
background: #20b6f5;
|
||||
font-size: 16rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.target-card__line {
|
||||
height: 1px;
|
||||
margin: 18rpx 0 16rpx;
|
||||
background: rgba(197, 206, 233, 0.3);
|
||||
}
|
||||
|
||||
.target-card__bottom,
|
||||
.confirm-detail__profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.target-card__profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.target-card__avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #ffbf86 0%, #f38b66 100%);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.target-card__avatar--image {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.target-card__avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.target-card__avatar-text {
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.target-card__name {
|
||||
margin-left: 16rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.target-card__phone {
|
||||
margin-left: 18rpx;
|
||||
color: rgba(244, 248, 255, 0.88);
|
||||
}
|
||||
|
||||
.action-wrap {
|
||||
padding: 86rpx 20rpx 0;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
border-radius: 46rpx;
|
||||
background: linear-gradient(135deg, #20b6f5 0%, #1ca5e3 100%);
|
||||
box-shadow: 0 16rpx 30rpx rgba(20, 119, 214, 0.16);
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-link {
|
||||
margin-top: 28rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #1fb4ff;
|
||||
}
|
||||
|
||||
.confirm-detail__summary {
|
||||
display: block;
|
||||
margin-top: 6rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: #ff8b5d;
|
||||
}
|
||||
|
||||
.confirm-detail__summary-sub {
|
||||
color: #ff8b5d;
|
||||
}
|
||||
|
||||
.confirm-detail__target {
|
||||
margin-top: 18rpx;
|
||||
padding: 18rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.confirm-detail__id {
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.confirm-detail__line {
|
||||
height: 1px;
|
||||
margin: 18rpx 0 16rpx;
|
||||
background: rgba(197, 206, 233, 0.3);
|
||||
}
|
||||
|
||||
.confirm-detail__desc {
|
||||
display: block;
|
||||
margin-top: 18rpx;
|
||||
font-size: 18rpx;
|
||||
line-height: 1.65;
|
||||
color: rgba(186, 194, 218, 0.88);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,367 @@
|
|||
<template>
|
||||
<view class="asset-page asset-theme wallet-form-page">
|
||||
<asset-page-shell title="钱包地址" />
|
||||
|
||||
<view class="asset-scroll wallet-form-scroll">
|
||||
<view class="wallet-form-panel">
|
||||
<view class="wallet-form-panel__head">
|
||||
<view class="wallet-form-panel__title">
|
||||
<view class="wallet-form-panel__title-icon">
|
||||
<view class="wallet-form-panel__title-tab"></view>
|
||||
<view class="wallet-form-panel__title-body"></view>
|
||||
</view>
|
||||
<text class="wallet-form-panel__title-text">当前钱包地址</text>
|
||||
</view>
|
||||
<text class="wallet-form-panel__subtitle">我在交易所的钱包地址</text>
|
||||
</view>
|
||||
|
||||
<view class="wallet-input">
|
||||
<input
|
||||
v-model="form.address"
|
||||
class="wallet-input__field"
|
||||
type="text"
|
||||
:placeholder="form.id ? '粘贴新钱包地址' : '粘贴新钱包地址'"
|
||||
placeholder-class="wallet-input__placeholder"
|
||||
/>
|
||||
<text class="wallet-input__paste" @click="paste">粘贴</text>
|
||||
</view>
|
||||
|
||||
<text class="wallet-form-panel__helper">
|
||||
钱包地址请在【海南农综】APP中获取
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="wallet-form-actions">
|
||||
<view class="wallet-button wallet-button--primary" @click="openConfirm">
|
||||
保存
|
||||
</view>
|
||||
<view
|
||||
class="wallet-button wallet-button--secondary"
|
||||
@click="handleBack"
|
||||
>
|
||||
返回
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<wallet-action-popup
|
||||
:visible="confirmVisible"
|
||||
:title="form.id ? '修改钱包' : '新增钱包'"
|
||||
:message="form.id ? '确认修改钱包新地址' : '确认保存钱包地址'"
|
||||
:address="trimmedAddress"
|
||||
description="请仔细核对钱包地址,以免造成财产损失"
|
||||
confirm-text="确认"
|
||||
cancel-text="取消"
|
||||
@cancel="confirmVisible = false"
|
||||
@confirm="submit"
|
||||
/>
|
||||
|
||||
<view v-if="successVisible" class="wallet-success">
|
||||
<image
|
||||
class="wallet-success__icon"
|
||||
src="https://imgs.agrimedia.cn/bm-bmt/success.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="wallet-success__text">{{
|
||||
form.id ? "修改成功!" : "保存成功!"
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetPageShell from "../../components/asset-page-shell.vue";
|
||||
import WalletActionPopup from "../../components/wallet-action-popup.vue";
|
||||
import { fetchWalletDetail, saveAssetWallet } from "../../api/assets";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetPageShell,
|
||||
WalletActionPopup,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
id: "",
|
||||
name: "海南农综交易所",
|
||||
address: "",
|
||||
},
|
||||
confirmVisible: false,
|
||||
successVisible: false,
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.form.id = (options && options.id) || "";
|
||||
this.loadPage(true);
|
||||
},
|
||||
computed: {
|
||||
trimmedAddress() {
|
||||
return String(this.form.address || "").trim();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async loadPage(showLoading) {
|
||||
try {
|
||||
const result = await fetchWalletDetail(
|
||||
showLoading
|
||||
? {
|
||||
showLoading: true,
|
||||
loadingText: "加载中",
|
||||
}
|
||||
: null,
|
||||
);
|
||||
if (!this.form.id) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < result.wallets.length; i += 1) {
|
||||
if (result.wallets[i].id === this.form.id) {
|
||||
this.form.name = result.wallets[i].name;
|
||||
this.form.address = result.wallets[i].address;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "页面加载失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
handleBack() {
|
||||
if (getCurrentPages().length > 1) {
|
||||
uni.navigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
uni.reLaunch({
|
||||
url: "/pages/index/index",
|
||||
});
|
||||
},
|
||||
paste() {
|
||||
const that = this;
|
||||
uni.getClipboardData({
|
||||
success: function (result) {
|
||||
that.form.address = result.data || "";
|
||||
},
|
||||
});
|
||||
},
|
||||
openConfirm() {
|
||||
if (!this.trimmedAddress) {
|
||||
uni.showToast({
|
||||
title: "请先粘贴钱包地址",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.confirmVisible = true;
|
||||
},
|
||||
async submit() {
|
||||
if (this.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
await saveAssetWallet({
|
||||
id: this.form.id,
|
||||
name: this.form.name,
|
||||
address: this.trimmedAddress,
|
||||
}, {
|
||||
showLoading: true,
|
||||
loadingText: "保存中",
|
||||
});
|
||||
this.confirmVisible = false;
|
||||
this.successVisible = true;
|
||||
setTimeout(() => {
|
||||
this.successVisible = false;
|
||||
this.handleBack();
|
||||
}, 700);
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "保存失败",
|
||||
icon: "none",
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tokens.scss";
|
||||
@import "../../styles/common.scss";
|
||||
|
||||
.wallet-form-page {
|
||||
min-height: 100vh;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.wallet-form-scroll {
|
||||
min-height: calc(100vh - env(safe-area-inset-top) - 104rpx);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10rpx 14rpx calc(env(safe-area-inset-bottom) + 36rpx);
|
||||
}
|
||||
|
||||
.wallet-form-panel {
|
||||
padding: 22rpx 20rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #242944;
|
||||
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.12);
|
||||
}
|
||||
|
||||
.wallet-form-panel__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.wallet-form-panel__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wallet-form-panel__title-icon {
|
||||
position: relative;
|
||||
width: 34rpx;
|
||||
height: 28rpx;
|
||||
margin-right: 10rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wallet-form-panel__title-tab {
|
||||
position: absolute;
|
||||
left: 5rpx;
|
||||
top: -4rpx;
|
||||
width: 16rpx;
|
||||
height: 10rpx;
|
||||
border-radius: 6rpx 6rpx 0 0;
|
||||
background: #b48eff;
|
||||
}
|
||||
|
||||
.wallet-form-panel__title-body {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 34rpx;
|
||||
height: 22rpx;
|
||||
border-radius: 6rpx;
|
||||
background: linear-gradient(135deg, #b48eff 0%, #8c6ef0 100%);
|
||||
}
|
||||
|
||||
.wallet-form-panel__title-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-form-panel__subtitle {
|
||||
margin-left: 16rpx;
|
||||
max-width: 280rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.4;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
color: rgba(174, 183, 211, 0.88);
|
||||
}
|
||||
|
||||
.wallet-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 18rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 94rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.wallet-input__field {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 94rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-input__placeholder {
|
||||
color: rgba(154, 163, 197, 0.82);
|
||||
}
|
||||
|
||||
.wallet-input__paste {
|
||||
margin-left: 18rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #1fb7f9;
|
||||
}
|
||||
|
||||
.wallet-form-panel__helper {
|
||||
display: block;
|
||||
margin-top: 18rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
color: rgba(174, 183, 211, 0.82);
|
||||
}
|
||||
|
||||
.wallet-form-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 180rpx;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.wallet-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 92rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.wallet-button--primary {
|
||||
background: linear-gradient(135deg, #22c2ff 0%, #159de6 100%);
|
||||
box-shadow: 0 16rpx 30rpx rgba(31, 169, 243, 0.2);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-button--secondary {
|
||||
border: 1px solid rgba(154, 164, 200, 0.64);
|
||||
background: rgba(67, 77, 118, 0.92);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-success {
|
||||
position: fixed;
|
||||
left: 54rpx;
|
||||
right: 54rpx;
|
||||
bottom: 250rpx;
|
||||
z-index: 90;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx 28rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #242944;
|
||||
box-shadow: 0 24rpx 60rpx rgba(0, 0, 0, 0.26);
|
||||
}
|
||||
|
||||
.wallet-success__icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.wallet-success__text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
<template>
|
||||
<view class="asset-page asset-theme wallet-page">
|
||||
<asset-page-shell title="钱包地址" />
|
||||
|
||||
<view class="asset-scroll wallet-scroll">
|
||||
<view class="wallet-panel">
|
||||
<view class="wallet-panel__head">
|
||||
<view class="wallet-panel__title">
|
||||
<view class="wallet-panel__title-icon">
|
||||
<view class="wallet-panel__title-tab"></view>
|
||||
<view class="wallet-panel__title-body"></view>
|
||||
</view>
|
||||
<text class="wallet-panel__title-text">当前钱包地址</text>
|
||||
</view>
|
||||
<text class="wallet-panel__subtitle">我在交易所的钱包地址</text>
|
||||
</view>
|
||||
|
||||
<view v-if="currentWallet" class="wallet-panel__content">
|
||||
<view class="wallet-panel__address-box">{{
|
||||
currentWallet.address
|
||||
}}</view>
|
||||
<view class="wallet-panel__footer">
|
||||
<text class="wallet-panel__name">{{ currentWallet.name }}</text>
|
||||
<text
|
||||
class="wallet-panel__copy"
|
||||
@click="copy(currentWallet.address)"
|
||||
>复制地址</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="wallet-panel__empty">
|
||||
<text class="wallet-panel__empty-title">暂未设置钱包地址</text>
|
||||
<text class="wallet-panel__empty-desc">
|
||||
新增后即可用于 BMT 提取和后续交易所对接。
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="wallet-actions">
|
||||
<view class="wallet-button wallet-button--primary" @click="openForm()">
|
||||
{{ currentWallet ? "修改钱包地址" : "新增钱包地址" }}
|
||||
</view>
|
||||
<view
|
||||
class="wallet-button wallet-button--secondary"
|
||||
@click="handleBack"
|
||||
>
|
||||
返回
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text
|
||||
v-if="currentWallet"
|
||||
class="wallet-delete"
|
||||
@click="openDeleteDialog"
|
||||
>
|
||||
删除地址
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<wallet-action-popup
|
||||
:visible="deleteDialogVisible"
|
||||
title="删除钱包"
|
||||
message="确认删除钱包地址"
|
||||
:address="currentWallet ? currentWallet.address : ''"
|
||||
confirm-text="确认"
|
||||
cancel-text="取消"
|
||||
@cancel="deleteDialogVisible = false"
|
||||
@confirm="remove"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetPageShell from "../../components/asset-page-shell.vue";
|
||||
import WalletActionPopup from "../../components/wallet-action-popup.vue";
|
||||
import { fetchWalletDetail, deleteAssetWallet } from "../../api/assets";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetPageShell,
|
||||
WalletActionPopup,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasShown: false,
|
||||
wallets: [],
|
||||
deleteDialogVisible: false,
|
||||
removing: false,
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.loadPage(true);
|
||||
},
|
||||
onShow() {
|
||||
if (this.hasShown) {
|
||||
this.loadPage();
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasShown = true;
|
||||
},
|
||||
computed: {
|
||||
currentWallet() {
|
||||
for (let i = 0; i < this.wallets.length; i += 1) {
|
||||
if (this.wallets[i].isDefault) {
|
||||
return this.wallets[i];
|
||||
}
|
||||
}
|
||||
|
||||
return this.wallets[0] || null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async loadPage(showLoading) {
|
||||
try {
|
||||
const result = await fetchWalletDetail(
|
||||
showLoading
|
||||
? {
|
||||
showLoading: true,
|
||||
loadingText: "加载中",
|
||||
}
|
||||
: null,
|
||||
);
|
||||
this.wallets = result.wallets || [];
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "钱包加载失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
handleBack() {
|
||||
if (getCurrentPages().length > 1) {
|
||||
uni.navigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
uni.reLaunch({
|
||||
url: "/pages/index/index",
|
||||
});
|
||||
},
|
||||
copy(address) {
|
||||
uni.setClipboardData({
|
||||
data: address,
|
||||
success() {
|
||||
uni.showToast({
|
||||
title: "已复制",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
openForm() {
|
||||
let url = "/pages/assets/wallet-form";
|
||||
if (this.currentWallet && this.currentWallet.id) {
|
||||
url += "?id=" + this.currentWallet.id;
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: url,
|
||||
});
|
||||
},
|
||||
openDeleteDialog() {
|
||||
if (!this.currentWallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deleteDialogVisible = true;
|
||||
},
|
||||
async remove() {
|
||||
if (!this.currentWallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.removing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removing = true;
|
||||
|
||||
try {
|
||||
await deleteAssetWallet(this.currentWallet.id, {
|
||||
showLoading: true,
|
||||
loadingText: "删除中",
|
||||
});
|
||||
this.deleteDialogVisible = false;
|
||||
uni.showToast({
|
||||
title: "删除成功",
|
||||
icon: "none",
|
||||
});
|
||||
this.loadPage();
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "删除失败",
|
||||
icon: "none",
|
||||
});
|
||||
} finally {
|
||||
this.removing = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tokens.scss";
|
||||
@import "../../styles/common.scss";
|
||||
|
||||
.wallet-page {
|
||||
min-height: 100vh;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.wallet-scroll {
|
||||
min-height: calc(100vh - env(safe-area-inset-top) - 104rpx);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10rpx 14rpx calc(env(safe-area-inset-bottom) + 36rpx);
|
||||
}
|
||||
|
||||
.wallet-panel {
|
||||
padding: 22rpx 20rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #242944;
|
||||
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.12);
|
||||
}
|
||||
|
||||
.wallet-panel__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.wallet-panel__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wallet-panel__title-icon {
|
||||
position: relative;
|
||||
width: 34rpx;
|
||||
height: 28rpx;
|
||||
margin-right: 10rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wallet-panel__title-tab {
|
||||
position: absolute;
|
||||
left: 5rpx;
|
||||
top: -4rpx;
|
||||
width: 16rpx;
|
||||
height: 10rpx;
|
||||
border-radius: 6rpx 6rpx 0 0;
|
||||
background: #b48eff;
|
||||
}
|
||||
|
||||
.wallet-panel__title-body {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 34rpx;
|
||||
height: 22rpx;
|
||||
border-radius: 6rpx;
|
||||
background: linear-gradient(135deg, #b48eff 0%, #8c6ef0 100%);
|
||||
}
|
||||
|
||||
.wallet-panel__title-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-panel__subtitle {
|
||||
margin-left: 16rpx;
|
||||
max-width: 280rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.4;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
color: rgba(174, 183, 211, 0.88);
|
||||
}
|
||||
|
||||
.wallet-panel__content,
|
||||
.wallet-panel__empty {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.wallet-panel__address-box {
|
||||
padding: 30rpx 24rpx;
|
||||
border-radius: 8rpx;
|
||||
background: rgba(53, 62, 94, 1);
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-panel__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.wallet-panel__name {
|
||||
font-size: 26rpx;
|
||||
color: rgba(196, 205, 231, 0.88);
|
||||
}
|
||||
|
||||
.wallet-panel__copy {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #1fb7f9;
|
||||
}
|
||||
|
||||
.wallet-panel__empty-title {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-panel__empty-desc {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.7;
|
||||
color: rgba(174, 183, 211, 0.82);
|
||||
}
|
||||
|
||||
.wallet-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 180rpx;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.wallet-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 92rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.wallet-button--primary {
|
||||
background: linear-gradient(135deg, #22c2ff 0%, #159de6 100%);
|
||||
box-shadow: 0 16rpx 30rpx rgba(31, 169, 243, 0.2);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-button--secondary {
|
||||
border: 1px solid rgba(154, 164, 200, 0.64);
|
||||
background: rgba(67, 77, 118, 0.92);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wallet-delete {
|
||||
margin-top: auto;
|
||||
padding: 140rpx 0 10rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #1fb7f9;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,667 @@
|
|||
<template>
|
||||
<view class="asset-page asset-theme withdraw-page">
|
||||
<asset-page-shell title="提取BMT" />
|
||||
|
||||
<view class="asset-scroll withdraw-scroll">
|
||||
<view class="hero-card">
|
||||
<view class="hero-card__content">
|
||||
<view class="hero-card__coin-row">
|
||||
<view class="hero-card__coin">
|
||||
<image
|
||||
class="hero-card__coin-image"
|
||||
src="https://imgs.agrimedia.cn/bm-bmt/b-w.png"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
<text class="hero-card__coin-text">BMT</text>
|
||||
</view>
|
||||
<text class="hero-card__label">本地钱包可提取BMT:</text>
|
||||
<text class="hero-card__value">{{ displayWithdrawable }}</text>
|
||||
<text class="hero-card__price"
|
||||
>(BMT实时价格: {{ displayPrice }}CNY/BMT)</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-card amount-card">
|
||||
<view class="section-head">
|
||||
<image
|
||||
class="section-head__icon"
|
||||
:src="pageIcon('bmt')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="section-head__title">BMT提取数量</text>
|
||||
</view>
|
||||
|
||||
<view class="amount-input">
|
||||
<input
|
||||
v-model="form.amount"
|
||||
class="amount-input__field"
|
||||
type="number"
|
||||
placeholder="请输入提取数量"
|
||||
placeholder-class="amount-input__placeholder"
|
||||
/>
|
||||
<text class="amount-input__action" @click="fillAll">全部提取</text>
|
||||
</view>
|
||||
|
||||
<view class="fee-card">
|
||||
<text class="fee-card__label">提取手续费(1%)</text>
|
||||
<text class="fee-card__value">{{ feeText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-card wallet-card">
|
||||
<view class="wallet-card__head">
|
||||
<view class="section-head">
|
||||
<view class="wallet-card__title-icon">
|
||||
<view class="wallet-card__title-tab"></view>
|
||||
<view class="wallet-card__title-body"></view>
|
||||
</view>
|
||||
<text class="section-head__title">钱包地址</text>
|
||||
</view>
|
||||
<text class="wallet-card__link" @click="openWallet"
|
||||
>修改钱包地址</text
|
||||
>
|
||||
</view>
|
||||
|
||||
<view v-if="detail.defaultWallet" class="wallet-box">
|
||||
<view class="wallet-box__head">
|
||||
<text class="wallet-box__name">{{
|
||||
detail.defaultWallet.name
|
||||
}}</text>
|
||||
<view class="wallet-box__actions">
|
||||
<text class="wallet-box__action" @click="toggleWalletVisible">{{
|
||||
walletVisible ? "隐藏" : "显示"
|
||||
}}</text>
|
||||
<text class="wallet-box__action" @click="copyWallet">复制</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="wallet-box__address">{{ walletDisplayAddress }}</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="wallet-box wallet-box--empty" @click="openWallet">
|
||||
<text class="wallet-box__plus">+</text>
|
||||
<text class="wallet-box__empty-text">添加钱包地址</text>
|
||||
</view>
|
||||
|
||||
<text class="wallet-card__desc">
|
||||
请仔细核对钱包地址,一经转出将无法追回。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="action-wrap">
|
||||
<view class="action-button" @click="openConfirm">确定</view>
|
||||
<view class="action-link" @click="openLedger">提取记录</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<asset-confirm-popup
|
||||
:visible="confirmVisible"
|
||||
title="确认提取"
|
||||
:message="'确认提取 ' + amountDisplay + ' BMT 到以下钱包吗?'"
|
||||
confirm-text="确认"
|
||||
cancel-text="取消"
|
||||
@cancel="confirmVisible = false"
|
||||
@confirm="submit"
|
||||
>
|
||||
<view class="popup-detail">
|
||||
<view class="popup-detail__row">
|
||||
<text class="meta-pair__label">钱包地址</text>
|
||||
<text class="meta-pair__value popup-address">{{
|
||||
detail.defaultWallet ? detail.defaultWallet.address : "未设置"
|
||||
}}</text>
|
||||
</view>
|
||||
<view class="popup-detail__row">
|
||||
<text class="meta-pair__label">手续费</text>
|
||||
<text class="meta-pair__value">{{ feeText }}</text>
|
||||
</view>
|
||||
<view class="popup-detail__row">
|
||||
<text class="meta-pair__label">预计到账</text>
|
||||
<text class="meta-pair__value">{{ actualText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</asset-confirm-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetConfirmPopup from "../../components/asset-confirm-popup.vue";
|
||||
import AssetPageShell from "../../components/asset-page-shell.vue";
|
||||
import { fetchWithdrawDetail, submitAssetWithdraw } from "../../api/assets";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetConfirmPopup,
|
||||
AssetPageShell,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
detail: {
|
||||
ticker: {},
|
||||
withdrawableBmt: "0",
|
||||
defaultWallet: null,
|
||||
},
|
||||
hasShown: false,
|
||||
form: {
|
||||
amount: "",
|
||||
},
|
||||
confirmVisible: false,
|
||||
walletVisible: false,
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.loadPage(true);
|
||||
},
|
||||
onShow() {
|
||||
if (this.hasShown) {
|
||||
this.loadPage();
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasShown = true;
|
||||
},
|
||||
computed: {
|
||||
amountValue() {
|
||||
return Number(this.form.amount || 0);
|
||||
},
|
||||
amountDisplay() {
|
||||
return this.formatAmount(this.amountValue, 2);
|
||||
},
|
||||
feeText() {
|
||||
return this.amountValue
|
||||
? this.formatAmount(this.amountValue * 0.01, 2) + " BMT"
|
||||
: "0.00 BMT";
|
||||
},
|
||||
actualText() {
|
||||
return this.amountValue
|
||||
? this.formatAmount(this.amountValue * 0.99, 2) + " BMT"
|
||||
: "0.00 BMT";
|
||||
},
|
||||
displayWithdrawable() {
|
||||
return this.formatAmount(this.detail.withdrawableBmt, 2);
|
||||
},
|
||||
displayPrice() {
|
||||
const price = Number(
|
||||
this.detail.ticker.cnyPrice || this.detail.ticker.close || 0,
|
||||
);
|
||||
return price ? price.toFixed(2) : "0.00";
|
||||
},
|
||||
walletDisplayAddress() {
|
||||
if (!this.detail.defaultWallet || !this.detail.defaultWallet.address) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const address = this.detail.defaultWallet.address;
|
||||
if (this.walletVisible || address.length <= 16) {
|
||||
return address;
|
||||
}
|
||||
|
||||
return address.slice(0, 8) + "..." + address.slice(-8);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pageIcon(type) {
|
||||
const iconMap = {
|
||||
power: "https://imgs.agrimedia.cn/bm-bmt/s.png",
|
||||
bmt: "https://imgs.agrimedia.cn/bm-bmt/b.png",
|
||||
transfer: "https://imgs.agrimedia.cn/bm-bmt/z.png",
|
||||
withdraw: "https://imgs.agrimedia.cn/bm-bmt/t.png",
|
||||
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon.png",
|
||||
points: "https://imgs.agrimedia.cn/bm-bmt/j.png",
|
||||
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon.png",
|
||||
};
|
||||
|
||||
return iconMap[type] || "";
|
||||
},
|
||||
formatAmount(value, digits) {
|
||||
const number = Number(value || 0);
|
||||
if (!Number.isFinite(number)) {
|
||||
return digits > 0 ? Number(0).toFixed(digits) : "0";
|
||||
}
|
||||
|
||||
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.floor(number)
|
||||
.toString()
|
||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
},
|
||||
async loadPage(showLoading) {
|
||||
try {
|
||||
this.detail = await fetchWithdrawDetail(
|
||||
showLoading
|
||||
? {
|
||||
showLoading: true,
|
||||
loadingText: "加载中",
|
||||
}
|
||||
: null,
|
||||
);
|
||||
this.walletVisible = false;
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "页面加载失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
fillAll() {
|
||||
this.form.amount = this.detail.withdrawableBmt || "0";
|
||||
},
|
||||
toggleWalletVisible() {
|
||||
this.walletVisible = !this.walletVisible;
|
||||
},
|
||||
copyWallet() {
|
||||
if (!this.detail.defaultWallet || !this.detail.defaultWallet.address) {
|
||||
return;
|
||||
}
|
||||
|
||||
uni.setClipboardData({
|
||||
data: this.detail.defaultWallet.address,
|
||||
showToast: false,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: "钱包地址已复制",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
openConfirm() {
|
||||
if (!this.detail.defaultWallet) {
|
||||
uni.showToast({
|
||||
title: "请先添加钱包地址",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.amountValue) {
|
||||
uni.showToast({
|
||||
title: "请输入提取数量",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.amountValue > Number(this.detail.withdrawableBmt || 0)) {
|
||||
uni.showToast({
|
||||
title: "可提取BMT不足",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.confirmVisible = true;
|
||||
},
|
||||
async submit() {
|
||||
if (this.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.detail.defaultWallet) {
|
||||
uni.showToast({
|
||||
title: "请先添加钱包地址",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.amountValue) {
|
||||
uni.showToast({
|
||||
title: "请输入提取数量",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
await submitAssetWithdraw({
|
||||
amount: this.amountValue,
|
||||
walletId: this.detail.defaultWallet.id,
|
||||
}, {
|
||||
showLoading: true,
|
||||
loadingText: "提交中",
|
||||
});
|
||||
this.confirmVisible = false;
|
||||
this.form.amount = "";
|
||||
uni.showToast({
|
||||
title: "提取申请成功",
|
||||
icon: "none",
|
||||
});
|
||||
this.loadPage();
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "提取失败",
|
||||
icon: "none",
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
openWallet() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/assets/wallet",
|
||||
});
|
||||
},
|
||||
openLedger() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/assets/ledger?type=withdraw",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tokens.scss";
|
||||
@import "../../styles/common.scss";
|
||||
|
||||
.withdraw-page {
|
||||
min-height: 100vh;
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.withdraw-scroll {
|
||||
padding: 8rpx 14rpx calc(env(safe-area-inset-bottom) + 36rpx);
|
||||
}
|
||||
|
||||
.panel-card,
|
||||
.hero-card {
|
||||
border-radius: 12rpx;
|
||||
background: #242944;
|
||||
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.12);
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
position: relative;
|
||||
min-height: 240rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
overflow: hidden;
|
||||
background: #242944 url("https://imgs.agrimedia.cn/bm-bmt/qianbao-bg.png")
|
||||
no-repeat top / 100% 240rpx;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hero-card__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hero-card__coin-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-card__coin {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #18c5ff 0%, #1496ff 100%);
|
||||
}
|
||||
|
||||
.hero-card__coin-image {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
|
||||
.hero-card__coin-text {
|
||||
margin-left: 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero-card__label {
|
||||
margin-top: 34rpx;
|
||||
font-size: 28rpx;
|
||||
color: #9ba7ce;
|
||||
}
|
||||
|
||||
.hero-card__value {
|
||||
margin-top: 8rpx;
|
||||
font-size: 42rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero-card__price {
|
||||
margin-top: 14rpx;
|
||||
font-size: 28rpx;
|
||||
color: #29dca3;
|
||||
}
|
||||
|
||||
.panel-card {
|
||||
margin-top: 18rpx;
|
||||
padding: 22rpx 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-head__icon {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-head__title {
|
||||
margin-left: 14rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.amount-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 22rpx;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 10rpx;
|
||||
background: rgba(25, 30, 50, 1);
|
||||
}
|
||||
|
||||
.amount-input__field {
|
||||
flex: 1;
|
||||
height: 84rpx;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.amount-input__placeholder {
|
||||
color: rgba(170, 179, 204, 0.66);
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.amount-input__action {
|
||||
margin-left: 20rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #20b6f5;
|
||||
}
|
||||
|
||||
.fee-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 18rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
background: rgba(96, 67, 88, 0.44);
|
||||
}
|
||||
|
||||
.fee-card__label {
|
||||
font-size: 26rpx;
|
||||
color: #ff9367;
|
||||
}
|
||||
|
||||
.fee-card__value {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #ff9367;
|
||||
}
|
||||
|
||||
.wallet-card__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.wallet-card__title-icon {
|
||||
position: relative;
|
||||
width: 34rpx;
|
||||
height: 28rpx;
|
||||
margin-right: 2rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wallet-card__title-tab {
|
||||
position: absolute;
|
||||
left: 5rpx;
|
||||
top: -4rpx;
|
||||
width: 16rpx;
|
||||
height: 10rpx;
|
||||
border-radius: 6rpx 6rpx 0 0;
|
||||
background: #b48eff;
|
||||
}
|
||||
|
||||
.wallet-card__title-body {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 34rpx;
|
||||
height: 22rpx;
|
||||
border-radius: 6rpx;
|
||||
background: linear-gradient(135deg, #b48eff 0%, #8c6ef0 100%);
|
||||
}
|
||||
|
||||
.wallet-card__link {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #20b6f5;
|
||||
}
|
||||
|
||||
.wallet-box {
|
||||
margin-top: 18rpx;
|
||||
padding: 18rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #353e5e;
|
||||
}
|
||||
|
||||
.wallet-box__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.wallet-box__name {
|
||||
font-size: 26rpx;
|
||||
color: rgba(232, 239, 255, 0.88);
|
||||
}
|
||||
|
||||
.wallet-box__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wallet-box__action {
|
||||
margin-left: 24rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
color: #20b6f5;
|
||||
}
|
||||
|
||||
.wallet-box__address {
|
||||
margin-top: 18rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
color: #ffffff;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.wallet-box--empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 120rpx;
|
||||
background: #353e5e;
|
||||
}
|
||||
|
||||
.wallet-box__plus {
|
||||
margin-right: 14rpx;
|
||||
font-size: 46rpx;
|
||||
line-height: 1;
|
||||
color: #20b6f5;
|
||||
}
|
||||
|
||||
.wallet-box__empty-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #20b6f5;
|
||||
}
|
||||
|
||||
.wallet-card__desc {
|
||||
display: block;
|
||||
margin-top: 22rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.65;
|
||||
color: rgba(170, 179, 204, 0.9);
|
||||
}
|
||||
|
||||
.action-wrap {
|
||||
padding: 86rpx 20rpx 0;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
border-radius: 46rpx;
|
||||
background: linear-gradient(135deg, #20b6f5 0%, #1ca5e3 100%);
|
||||
box-shadow: 0 16rpx 30rpx rgba(20, 119, 214, 0.16);
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-link {
|
||||
margin-top: 28rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #1fb4ff;
|
||||
}
|
||||
|
||||
.popup-detail__row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.popup-address {
|
||||
max-width: 320rpx;
|
||||
text-align: right;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,620 @@
|
|||
<template>
|
||||
<view class="asset-page asset-theme home-page">
|
||||
<view class="home-topbar">
|
||||
<view class="home-topbar__side"></view>
|
||||
<text class="home-topbar__title">{{ overview.title || "数字资产" }}</text>
|
||||
<view class="home-wallet-btn" @click="openWallet">
|
||||
<view class="home-wallet-btn__icon">
|
||||
<image
|
||||
src="https://imgs.agrimedia.cn/bm-bmt/qianbao.png"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
</view>
|
||||
<text class="home-wallet-btn__text">钱包</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="asset-scroll home-scroll">
|
||||
<view class="home-hero">
|
||||
<view class="brand-banner"></view>
|
||||
|
||||
<view class="stat-panel">
|
||||
<view
|
||||
v-for="item in overview.topStats"
|
||||
:key="item.key"
|
||||
class="stat-card"
|
||||
:class="'stat-card--' + item.accent"
|
||||
>
|
||||
<view class="stat-card__body">
|
||||
<text class="stat-card__label">{{ item.title }}</text>
|
||||
<view class="stat-card__value-row">
|
||||
<text class="stat-card__value">{{ item.value }}</text>
|
||||
<text class="stat-card__unit">{{ item.unit }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="home-section">
|
||||
<view class="home-section__head">
|
||||
<text class="home-section__title">我的资产</text>
|
||||
</view>
|
||||
<view class="asset-grid">
|
||||
<view
|
||||
v-for="item in overview.quickAssets"
|
||||
:key="item.key"
|
||||
class="asset-mini-card"
|
||||
:class="'asset-mini-card--' + item.accent"
|
||||
@click="openQuickAsset(item)"
|
||||
>
|
||||
<image
|
||||
class="asset-mini-card__bg"
|
||||
:src="quickAssetBg(item.key)"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
<view class="asset-mini-card__head">
|
||||
<view class="asset-mini-card__icon">
|
||||
<image
|
||||
class="asset-mini-card__icon-image"
|
||||
:src="quickAssetIcon(item.key)"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
<text class="asset-mini-card__label">{{ item.title }}</text>
|
||||
</view>
|
||||
<text class="asset-mini-card__value">{{ item.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="home-section">
|
||||
<view class="home-section__head">
|
||||
<text class="home-section__title">功能中心</text>
|
||||
</view>
|
||||
<view class="feature-list">
|
||||
<view
|
||||
v-for="feature in overview.features"
|
||||
:key="feature.key"
|
||||
class="feature-list__item"
|
||||
@click="openFeature(feature.key)"
|
||||
>
|
||||
<view class="feature-list__main">
|
||||
<view class="feature-list__icon">
|
||||
<image
|
||||
class="feature-list__icon-image"
|
||||
:src="featureIcon(feature.key)"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
<text class="feature-list__title">{{ feature.title }}</text>
|
||||
</view>
|
||||
<image class="feature-list__arrow" src="https://imgs.agrimedia.cn/bm-bmt/more.png"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="notice-bar">
|
||||
<image
|
||||
class="notice-bar__icon"
|
||||
src="https://imgs.agrimedia.cn/bm-bmt/tips.png"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
<text class="notice-bar__text">{{ overview.notice }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchAssetHome } from "../../api/assets";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
overview: {
|
||||
title: "数字资产",
|
||||
heroTitle: "BM数字资产管理",
|
||||
topStats: [],
|
||||
quickAssets: [],
|
||||
features: [],
|
||||
notice: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.loadPage();
|
||||
},
|
||||
onShow() {
|
||||
this.loadPage();
|
||||
},
|
||||
methods: {
|
||||
async loadPage() {
|
||||
try {
|
||||
this.overview = await fetchAssetHome();
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message || "首页加载失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
statIcon(key) {
|
||||
const iconMap = {
|
||||
"wallet-bmt": "https://imgs.agrimedia.cn/bm-bmt/hong-l.png",
|
||||
ticker: "https://imgs.agrimedia.cn/bm-bmt/hong-r.png",
|
||||
};
|
||||
|
||||
return iconMap[key] || "";
|
||||
},
|
||||
quickAssetIcon(key) {
|
||||
const iconMap = {
|
||||
points: "https://imgs.agrimedia.cn/bm-bmt/jifen-icon.png",
|
||||
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon.png",
|
||||
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon.png",
|
||||
power: "https://imgs.agrimedia.cn/bm-bmt/suanli-icon.png",
|
||||
};
|
||||
|
||||
return iconMap[key] || "";
|
||||
},
|
||||
quickAssetBg(key) {
|
||||
const bgMap = {
|
||||
points: "https://imgs.agrimedia.cn/bm-bmt/jifen-bg.png",
|
||||
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-bg.png",
|
||||
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-bg.png",
|
||||
power: "https://imgs.agrimedia.cn/bm-bmt/suanli-bg.png",
|
||||
};
|
||||
|
||||
return bgMap[key] || "";
|
||||
},
|
||||
featureIcon(key) {
|
||||
const iconMap = {
|
||||
"bmt-exchange": "https://imgs.agrimedia.cn/bm-bmt/b.png",
|
||||
"power-exchange": "https://imgs.agrimedia.cn/bm-bmt/s.png",
|
||||
transfer: "https://imgs.agrimedia.cn/bm-bmt/z.png",
|
||||
withdraw: "https://imgs.agrimedia.cn/bm-bmt/t.png",
|
||||
"points-convert": "https://imgs.agrimedia.cn/bm-bmt/j.png",
|
||||
};
|
||||
|
||||
return iconMap[key] || "";
|
||||
},
|
||||
openFeature(key) {
|
||||
const urlMap = {
|
||||
transfer: "/pages/assets/transfer",
|
||||
"power-exchange": "/pages/assets/power-exchange",
|
||||
"bmt-exchange": "/pages/assets/bmt-exchange",
|
||||
withdraw: "/pages/assets/withdraw",
|
||||
"points-convert": "/pages/assets/points-convert",
|
||||
};
|
||||
|
||||
const targetUrl = urlMap[key];
|
||||
if (!targetUrl) {
|
||||
uni.showToast({
|
||||
title: "功能开发中",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: targetUrl,
|
||||
});
|
||||
},
|
||||
openQuickAsset(item) {
|
||||
const urlMap = {
|
||||
points: "/pages/assets/ledger?type=points",
|
||||
voucher: "/pages/assets/ledger?type=voucher",
|
||||
coupon: "/pages/assets/ledger?type=coupon",
|
||||
power: "/pages/assets/ledger?type=power",
|
||||
};
|
||||
|
||||
const targetUrl = urlMap[item.key];
|
||||
if (!targetUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: targetUrl,
|
||||
});
|
||||
},
|
||||
openWallet() {
|
||||
uni.navigateTo({
|
||||
url: "/pages/assets/wallet",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tokens.scss";
|
||||
@import "../../styles/common.scss";
|
||||
|
||||
.home-page {
|
||||
background: #191e32;
|
||||
}
|
||||
|
||||
.home-topbar {
|
||||
display: grid;
|
||||
grid-template-columns: 136rpx 1fr 136rpx;
|
||||
align-items: center;
|
||||
padding: calc(env(safe-area-inset-top) + 20rpx) 16rpx 8rpx;
|
||||
}
|
||||
|
||||
.home-topbar__side {
|
||||
width: 136rpx;
|
||||
height: 1rpx;
|
||||
}
|
||||
|
||||
.home-topbar__title {
|
||||
text-align: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.home-wallet-btn {
|
||||
justify-self: end;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 52rpx;
|
||||
color: rgba(229, 235, 255, 0.82);
|
||||
}
|
||||
|
||||
.home-wallet-btn__icon {
|
||||
width: 30rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 8rpx;
|
||||
image {
|
||||
width: 30rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.home-wallet-btn__text {
|
||||
font-size: 28rpx;
|
||||
color: rgba(229, 235, 255, 0.82);
|
||||
}
|
||||
|
||||
.home-scroll {
|
||||
padding: 12rpx 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.home-hero {
|
||||
position: relative;
|
||||
margin-bottom: 26rpx;
|
||||
}
|
||||
|
||||
.brand-banner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 380rpx;
|
||||
border-radius: 28rpx;
|
||||
background:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(5, 12, 28, 0.06) 0%,
|
||||
rgba(5, 12, 28, 0.16) 100%
|
||||
),
|
||||
url("https://imgs.agrimedia.cn/bm-bmt/home2-bg.png")
|
||||
no-repeat;
|
||||
border: 1px solid rgba(112, 135, 196, 0.18);
|
||||
background-size: contain;
|
||||
box-shadow: 0 24rpx 48rpx rgba(7, 12, 32, 0.28);
|
||||
}
|
||||
|
||||
.brand-banner::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent 26%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.brand-banner::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 120rpx;
|
||||
background: linear-gradient(180deg, rgba(4, 10, 24, 0), rgba(4, 10, 24, 0.18));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.stat-panel {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
bottom: 24rpx;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 20rpx;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 126rpx;
|
||||
padding: 22rpx 22rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(42, 55, 82, 0.98) 0%,
|
||||
rgba(39, 51, 76, 0.98) 100%
|
||||
);
|
||||
border: 1px solid rgba(121, 139, 190, 0.16);
|
||||
box-shadow: 0 14rpx 24rpx rgba(5, 11, 29, 0.2);
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), transparent 34%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.stat-card__icon-box {
|
||||
position: absolute;
|
||||
right: 16rpx;
|
||||
top: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 8rpx;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-card__icon-box--gold {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(20, 182, 255, 0.22) 0%,
|
||||
rgba(20, 182, 255, 0.08) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.stat-card__icon-box--green {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(46, 233, 167, 0.22) 0%,
|
||||
rgba(46, 233, 167, 0.08) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.stat-card__body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 84rpx;
|
||||
margin-left: 0;
|
||||
padding-right: 56rpx;
|
||||
}
|
||||
|
||||
.stat-card__label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stat-card--gold .stat-card__label {
|
||||
color: #19b9ff;
|
||||
}
|
||||
|
||||
.stat-card--green .stat-card__label {
|
||||
color: #28e0a6;
|
||||
}
|
||||
|
||||
.stat-card__value-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 16rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stat-card__value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-card__unit {
|
||||
margin-left: 8rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.2;
|
||||
color: rgba(170, 181, 208, 0.92);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.home-section {
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.home-section__head {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.home-section__title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.asset-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.asset-mini-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
padding: 18rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 12rpx 24rpx rgba(10, 16, 37, 0.16);
|
||||
background: rgba(35, 45, 79, 0.9);
|
||||
}
|
||||
|
||||
.asset-mini-card__bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.asset-mini-card--blue {
|
||||
background: linear-gradient(135deg, #10acee 0%, #1aa4ff 100%);
|
||||
}
|
||||
|
||||
.asset-mini-card--green {
|
||||
background: linear-gradient(135deg, #25d8a4 0%, #36e08f 100%);
|
||||
}
|
||||
|
||||
.asset-mini-card--orange {
|
||||
background: linear-gradient(135deg, #ff8d58 0%, #ff7d7d 100%);
|
||||
}
|
||||
|
||||
.asset-mini-card--violet {
|
||||
background: linear-gradient(135deg, #a667f3 0%, #b27cff 100%);
|
||||
}
|
||||
|
||||
.asset-mini-card__head,
|
||||
.asset-mini-card__value {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.asset-mini-card__head {
|
||||
display: flex;
|
||||
font-size: 26rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.asset-mini-card__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.asset-mini-card__icon-image {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.asset-mini-card__label {
|
||||
margin-left: 14rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.94);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.asset-mini-card__value {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 800;
|
||||
text-align: right;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
display: grid;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.feature-list__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 100rpx;
|
||||
padding: 0 22rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #242944;
|
||||
box-shadow: 0 12rpx 22rpx rgba(8, 13, 30, 0.14);
|
||||
}
|
||||
|
||||
.feature-list__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.feature-list__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.feature-list__icon-image {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.feature-list__title {
|
||||
margin-left: 20rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: #f3f6ff;
|
||||
}
|
||||
|
||||
.feature-list__arrow {
|
||||
margin-left: 16rpx;
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.notice-bar {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-top: 20rpx;
|
||||
padding: 0 2rpx;
|
||||
}
|
||||
|
||||
.notice-bar__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
margin-right: 8rpx;
|
||||
margin-top: 4rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(180deg, #ffd764 0%, #ffbb2e 100%);
|
||||
font-size: 16rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notice-bar__text {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.65;
|
||||
color: rgba(200, 207, 227, 0.82);
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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])
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
Loading…
Reference in New Issue