资产模块接口与页面开发

This commit is contained in:
yankang 2026-07-27 11:30:30 +08:00
parent 4b4f852afe
commit 939fe3dc11
8 changed files with 1382 additions and 294 deletions

267
BMTR.MD Normal file
View File

@ -0,0 +1,267 @@
# BMTR 资产接入实现文档
## 一、需求概述
在首页「我的资产」区域新增 **BMTR** 卡片,展示当前登录用户的 BMTR 数量。
数据来源:
```
GET /api/mxj/api/get_user
```
返回示例:
```json
{
"status": 200,
"msg": "success",
"data": {
"uid": 94227,
"nickname": "siediyer",
"avatar": "https://thirdwx.qlogo.cn/...",
"mxj_points": "99999.00",
"bmtr": "11.0000",
"is_check": 1,
"is_idcard": 1,
"is_sign": 1,
"sign_time": 1781172245,
"status": 1
}
}
```
---
## 二、API 请求运行方式
项目中的网络请求采用三层结构:
```
页面 (pages/index/index.vue)
↓ 调用
api/assets.js 中的业务方法
↓ 调用
utils/request.js 中的通用 request 方法
↓ 调用
uni.request 发送 HTTP 请求
```
### 2.1 配置层:`config/service.js`
定义了接口基础地址和 endpoint 映射表。
### 2.2 通用请求层:`utils/request.js`
- 自动拼接 `BASE_URL` 和 `url`
- 统一注入 `Authori-zation` / `Authorization` Token
- 统一处理 `showLoading` / `hideLoading`
- 对响应进行 `statusCode` 校验,返回 `response.data`
### 2.3 业务 API 层:`api/assets.js`
- 封装 `fetchPayload` 统一处理 `{ status: 200, data: {...} }` 结构
- 提供 `createRequestOptions` 合并请求参数
- 提供 `buildHomeOverview` 将原始接口数据转换为页面渲染数据
---
## 三、本次改动文件
| 文件 | 改动内容 |
| ----------------------- | ------------------------------------------------------------------ |
| `config/service.js` | 新增 `getUser` endpoint |
| `api/assets.js` | 新增 `fetchUserData`,修改 `buildHomeOverview` 和 `fetchAssetHome` |
| `pages/index/index.vue` | 新增 BMTR 图标、背景、点击跳转 |
| `BMTR.MD` | 本文档 |
---
## 四、具体改动
### 4.1 `config/service.js`
在 `ENDPOINTS` 中新增:
```javascript
getUser: "/api/mxj/api/get_user",
```
### 4.2 `api/assets.js`
#### 4.2.1 新增获取用户数据的函数
```javascript
async function fetchUserData(requestOptions) {
return fetchPayload(
createRequestOptions(
{
url: serviceConfig.ENDPOINTS.getUser,
},
requestOptions,
),
"用户信息加载失败",
);
}
```
#### 4.2.2 修改 `buildHomeOverview`
函数签名从:
```javascript
function buildHomeOverview(balanceData, tickerData) {
```
改为:
```javascript
function buildHomeOverview(balanceData, tickerData, userData) {
```
在 `rawQuickAssets` 中新增 `bmtr`
```javascript
const rawQuickAssets = {
balance: toRawDisplayValue(balanceData && balanceData.brokerage_price),
points: toRawDisplayValue(balanceData && balanceData.point),
voucher: toRawDisplayValue(balanceData && balanceData.coin),
coupon: toRawDisplayValue(balanceData && balanceData.diamond_balance),
power: toRawDisplayValue(balanceData && balanceData.c_power),
bmtr: toRawDisplayValue(userData && userData.bmtr),
};
```
在 `quickAssets` 数组中新增 BMTR 项:
```javascript
{
key: "bmtr",
title: "BMTR",
value: rawQuickAssets.bmtr,
accent: "orange",
},
```
> `value` 直接使用接口返回的 `bmtr` 字符串。
> `accent` 设为 `orange`,与页面已有的 `.asset-mini-card--orange` 样式类对应。
#### 4.2.3 修改 `fetchAssetHome`
原来是同时请求价格 + 余额,现在增加用户信息请求:
```javascript
export async function fetchAssetHome(requestOptions) {
const result = await Promise.all([
fetchPriceData(requestOptions),
fetchHomeBalanceData(requestOptions),
fetchUserData(requestOptions),
]);
const overview = buildHomeOverview(result[1], result[0], result[2]);
setHomeTickerCache(overview.ticker);
return overview;
}
```
三个请求并行执行BMTR 接口失败不会阻塞其他两个请求(但会进入 `Promise.all` 的 catch 逻辑,页面会显示错误提示)。
### 4.3 `pages/index/index.vue`
#### 4.3.1 新增图标
在 `quickAssetIcon` 的 `iconMap` 中新增:
```javascript
bmtr: "https://imgs.agrimedia.cn/bm-bmt/qianbao.png",
```
> 当前复用已有的钱包图标,后续可替换为 BMTR 专属图标。
#### 4.3.2 新增背景
在 `quickAssetBg` 的 `bgMap` 中新增:
```javascript
bmtr: "https://imgs.agrimedia.cn/bm-bmt/suanli-bg.png",
```
> 当前复用算力卡片背景,后续可替换为 BMTR 专属背景。
#### 4.3.3 新增点击跳转
在 `openQuickAsset` 的 `urlMap` 中新增:
```javascript
bmtr: "/pages/assets/bmtr",
```
用户点击 BMTR 卡片后,会跳转到 `pages/assets/bmtr.vue` 页面。
---
## 五、首页资产卡片渲染顺序
修改后,首页「我的资产」区域共 6 个卡片,顺序如下:
1. 可用积分
2. 算力
3. 消费券
4. 抵用券
5. **BMTR新增**
6. 余额
> 由于 `balance` 卡片设置了 `grid-column: 1 / -1`,会单独占一行,所以实际布局为:
>
> - 第一行:可用积分、算力
> - 第二行:消费券、抵用券
> - 第三行BMTR
> - 第四行:余额(整行)
如果希望调整 BMTR 的位置,修改 `api/assets.js` 中 `quickAssets` 数组的顺序即可。
---
## 六、待替换项
| 项目 | 当前值 | 说明 |
| --------- | ------------------------------------------------------------ | ----------------------- |
| BMTR 图标 | `https://imgs.agrimedia.cn/webimg/202607171808386371853.svg` | BMTR 专属图标(已替换) |
| BMTR 背景 | `https://imgs.agrimedia.cn/webimg/202607201122331061368.svg` | BMTR 专属背景(已替换) |
---
## 七、接口字段说明
| 字段 | 类型 | 用途 |
| ------------ | ------ | --------------------------- |
| `uid` | number | 用户ID |
| `nickname` | string | 用户昵称 |
| `avatar` | string | 头像URL |
| `mxj_points` | string | MXJ积分 |
| `bmtr` | string | **BMTR 余额,本次使用字段** |
| `is_check` | number | 是否通过某种校验 |
| `is_idcard` | number | 是否实名认证 |
| `is_sign` | number | 是否签到 |
| `sign_time` | number | 签到时间戳 |
| `status` | number | 用户状态 |
---
## 八、验证建议
1. 进入首页,确认「我的资产」区域出现 BMTR 卡片
2. 确认 BMTR 显示的数值与 `/api/mxj/api/get_user` 返回的 `data.bmtr` 一致
3. 点击 BMTR 卡片,确认能跳转到 `pages/assets/bmtr`
4. 检查网络面板,确认三个请求并行发送:
- `/api/hn/getPrice`
- `/api/hn/getAllBalance`
- `/api/mxj/api/get_user`
5. 如果 BMTR 接口失败,首页应显示错误提示,不影响其他资产卡片的数据逻辑(但 Promise.all 会统一报错,如需降级处理可改为单独 catch
---
## 九、可能的后续优化
1. **接口降级**:如果 `/api/mxj/api/get_user` 不稳定,可将 `fetchUserData` 从 `Promise.all` 中拆出,单独 catch 并给 BMTR 一个默认值 `0`。
2. **数值格式化**:当前 `bmtr` 直接显示为字符串,如需统一显示为 `11.00` 或 `11.0000`,可在 `buildHomeOverview` 中使用 `toFixedNumber` 处理。
3. **图标替换**:拿到 BMTR 专属图标和背景后,更新 `pages/index/index.vue` 中的 `iconMap` 和 `bgMap`。

View File

@ -97,8 +97,7 @@ function normalizeTicker(data) {
return { return {
symbol: (data && data.symbol) || "BMT/CNY", symbol: (data && data.symbol) || "BMT/CNY",
close: close, close: close,
cnyPrice: cnyPrice: (data && data.cnyPrice) || (close ? close.toFixed(2) : "0.00"),
(data && data.cnyPrice) || (close ? close.toFixed(2) : "0.00"),
lastDayClose: lastDayClose, lastDayClose: lastDayClose,
change: change, change: change,
}; };
@ -158,7 +157,7 @@ function toRawDisplayValue(value) {
return String(value); return String(value);
} }
function buildHomeOverview(balanceData, tickerData) { function buildHomeOverview(balanceData, tickerData, userData) {
const balances = normalizeBalances(balanceData); const balances = normalizeBalances(balanceData);
const ticker = normalizeTicker(tickerData); const ticker = normalizeTicker(tickerData);
const rawQuickAssets = { const rawQuickAssets = {
@ -167,6 +166,7 @@ function buildHomeOverview(balanceData, tickerData) {
voucher: toRawDisplayValue(balanceData && balanceData.coin), voucher: toRawDisplayValue(balanceData && balanceData.coin),
coupon: toRawDisplayValue(balanceData && balanceData.diamond_balance), coupon: toRawDisplayValue(balanceData && balanceData.diamond_balance),
power: toRawDisplayValue(balanceData && balanceData.c_power), power: toRawDisplayValue(balanceData && balanceData.c_power),
bmtr: toRawDisplayValue(userData && userData.bmtr),
}; };
return { return {
@ -219,7 +219,12 @@ function buildHomeOverview(balanceData, tickerData) {
value: rawQuickAssets.balance, value: rawQuickAssets.balance,
accent: "blue", accent: "blue",
}, },
{
key: "bmtr",
title: "BMTR",
value: rawQuickAssets.bmtr,
accent: "orange",
},
], ],
features: [ features: [
{ {
@ -273,10 +278,7 @@ function buildTransferTips(feePercent) {
"凌晨00:00至02:00为系统维护时段不可赠送", "凌晨00:00至02:00为系统维护时段不可赠送",
"转赠系统会扣除" + percentText + "%的手续费", "转赠系统会扣除" + percentText + "%的手续费",
], ],
power: [ power: ["只能转赠1的整数倍", "转赠系统会扣除" + percentText + "%的手续费"],
"只能转赠1的整数倍",
"转赠系统会扣除" + percentText + "%的手续费",
],
}; };
} }
@ -453,7 +455,8 @@ function parseBooleanLike(value) {
function buildPaginationMeta(data, requestPagination) { function buildPaginationMeta(data, requestPagination) {
const normalizedData = data && typeof data === "object" ? data : {}; const normalizedData = data && typeof data === "object" ? data : {};
const normalizedRequestPagination = normalizePaginationOptions(requestPagination); const normalizedRequestPagination =
normalizePaginationOptions(requestPagination);
const list = normalizeListData(normalizedData); const list = normalizeListData(normalizedData);
const page = const page =
Math.floor( Math.floor(
@ -548,7 +551,12 @@ function formatLedgerRecordNumber(value) {
return formatTransferRecordNumber(Math.abs(toNumber(value))); return formatTransferRecordNumber(Math.abs(toNumber(value)));
} }
function resolveRecordDirection(item, amountValue, positiveLabel, negativeLabel) { function resolveRecordDirection(
item,
amountValue,
positiveLabel,
negativeLabel,
) {
const rawDirection = pickFirstValue(item, [ const rawDirection = pickFirstValue(item, [
"io_type", "io_type",
"in_out", "in_out",
@ -670,7 +678,8 @@ function mapWalletFlowRecords(list, meta) {
pickFirstValue(item, ["title", "name", "type_name"]) || pickFirstValue(item, ["title", "name", "type_name"]) ||
meta.title || meta.title ||
meta.unit + "记录"; meta.unit + "记录";
const isPointsLedger = String(meta && meta.key ? meta.key : "") === "points"; const isPointsLedger =
String(meta && meta.key ? meta.key : "") === "points";
const displayTitle = isPointsLedger const displayTitle = isPointsLedger
? normalizeAvailablePointsText(title) ? normalizeAvailablePointsText(title)
: title; : title;
@ -882,7 +891,9 @@ function getTransferRecordAmount(item) {
} }
function extractTransferTitleFeeValue(item) { function extractTransferTitleFeeValue(item) {
const titleText = String(pickFirstValue(item, ["title", "name"]) || "").trim(); const titleText = String(
pickFirstValue(item, ["title", "name"]) || "",
).trim();
if (!titleText) { if (!titleText) {
return ""; return "";
@ -916,7 +927,9 @@ function getTransferRecordFee(item) {
return toNumber(item.fee); return toNumber(item.fee);
} }
return (toNumber(item && item.num) * toNumber(item && item.fee_percent)) / 100; return (
(toNumber(item && item.num) * toNumber(item && item.fee_percent)) / 100
);
} }
function getTransferRecordFeeText(item) { function getTransferRecordFeeText(item) {
@ -938,7 +951,9 @@ function getTransferRecordBalance(item) {
function getTransferRecordBalanceLabel(item) { function getTransferRecordBalanceLabel(item) {
const unit = getTransferRecordUnit(item); const unit = getTransferRecordUnit(item);
return "剩余" + unit + "" + formatTransferRecordNumber(item && item.balance); return (
"剩余" + unit + "" + formatTransferRecordNumber(item && item.balance)
);
} }
function mapTransferRecords(list) { function mapTransferRecords(list) {
@ -947,7 +962,9 @@ function mapTransferRecords(list) {
return { return {
id: item.order_sn || item.id || String(Math.random()), id: item.order_sn || item.id || String(Math.random()),
title: normalizeTransferPointsText(titleText, item), title: normalizeTransferPointsText(titleText, item),
subtitle: item.order_sn ? "单号 " + item.order_sn : getTransferRecordDirection(item), subtitle: item.order_sn
? "单号 " + item.order_sn
: getTransferRecordDirection(item),
time: item.add_time || "", time: item.add_time || "",
amount: getTransferRecordAmount(item), amount: getTransferRecordAmount(item),
balance: getTransferRecordBalance(item), balance: getTransferRecordBalance(item),
@ -1031,13 +1048,9 @@ function mapPointsConvertRecords(list) {
pickFirstValue(item, ["order_sn", "trade_no", "bill_no", "sn"]) || "", pickFirstValue(item, ["order_sn", "trade_no", "bill_no", "sn"]) || "",
), ),
title: title:
pickFirstValue(item, ["title", "name", "type_name"]) || pickFirstValue(item, ["title", "name", "type_name"]) || "系统赠送积分",
"系统赠送积分",
subtitle: subtitle:
"可转数量 " + "可转数量 " + formatTransferRecordNumber(rawTransferPointValue || 0),
formatTransferRecordNumber(
rawTransferPointValue || 0,
),
time: pickFirstValue(item, [ time: pickFirstValue(item, [
"add_time", "add_time",
"create_time", "create_time",
@ -1085,9 +1098,7 @@ function extractMonthLabel(value) {
} }
return ( return (
date.getFullYear() + date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0")
"-" +
String(date.getMonth() + 1).padStart(2, "0")
); );
} }
@ -1111,7 +1122,8 @@ function buildMonthOptions(list) {
result, result,
item, item,
) { ) {
const month = item && item.month ? item.month : extractMonthLabel(item && item.time); const month =
item && item.month ? item.month : extractMonthLabel(item && item.time);
if (month && result.indexOf(month) === -1) { if (month && result.indexOf(month) === -1) {
result.push(month); result.push(month);
} }
@ -1261,21 +1273,24 @@ function buildPointsConvertDetailPayload(data, fallbackRecord) {
"created_at", "created_at",
"time", "time",
"update_time", "update_time",
]) || fallback.time || "", ]) ||
fallback.time ||
"",
}; };
}); });
if (!detailList.length) { if (!detailList.length) {
return buildPointsConvertDetailFallback({ return buildPointsConvertDetailFallback({
id: fallback.id, id: fallback.id,
orderSn: orderSn: String(
String(
pickFirstValue(summarySource, [ pickFirstValue(summarySource, [
"order_sn", "order_sn",
"trade_no", "trade_no",
"bill_no", "bill_no",
"sn", "sn",
]) || fallback.orderSn || "", ]) ||
fallback.orderSn ||
"",
), ),
title: title:
pickFirstValue(summarySource, ["title", "name", "type_name"]) || pickFirstValue(summarySource, ["title", "name", "type_name"]) ||
@ -1294,7 +1309,12 @@ function buildPointsConvertDetailPayload(data, fallbackRecord) {
return { return {
orderSn: String( orderSn: String(
pickFirstValue(summarySource, ["order_sn", "trade_no", "bill_no", "sn"]) || pickFirstValue(summarySource, [
"order_sn",
"trade_no",
"bill_no",
"sn",
]) ||
fallback.orderSn || fallback.orderSn ||
"", "",
), ),
@ -1306,7 +1326,9 @@ function buildPointsConvertDetailPayload(data, fallbackRecord) {
"created_at", "created_at",
"time", "time",
"update_time", "update_time",
]) || fallback.time || "", ]) ||
fallback.time ||
"",
details: detailList, details: detailList,
}; };
} }
@ -1352,8 +1374,7 @@ function normalizeTransferTarget(data, fallbackId) {
return { return {
id: id, id: id,
nickname: nickname: data.nickname || data.nick_name || data.username || "用户" + id,
data.nickname || data.nick_name || data.username || "用户" + id,
phone: data.mobile || data.phone || "ID已通过校验", phone: data.mobile || data.phone || "ID已通过校验",
avatar: data.avatar || data.headimg || "", avatar: data.avatar || data.headimg || "",
}; };
@ -1384,36 +1405,48 @@ function sumBy(list, key) {
async function fetchPriceData(requestOptions) { async function fetchPriceData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.price, url: serviceConfig.ENDPOINTS.price,
}, requestOptions), },
requestOptions,
),
"实时价格加载失败", "实时价格加载失败",
); );
} }
async function fetchHomeBalanceData(requestOptions) { async function fetchHomeBalanceData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.homeBalance, url: serviceConfig.ENDPOINTS.homeBalance,
}, requestOptions), },
requestOptions,
),
"首页资产加载失败", "首页资产加载失败",
); );
} }
async function fetchBmtPowerRateData(requestOptions) { async function fetchBmtPowerRateData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.bmtRedeemPowerRate, url: serviceConfig.ENDPOINTS.bmtRedeemPowerRate,
}, requestOptions), },
requestOptions,
),
"兑换比例加载失败", "兑换比例加载失败",
); );
} }
async function fetchPowerExchangeMuitData(requestOptions) { async function fetchPowerExchangeMuitData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.powerExchangeMuit, url: serviceConfig.ENDPOINTS.powerExchangeMuit,
}, requestOptions), },
requestOptions,
),
"兑换倍率加载失败", "兑换倍率加载失败",
); );
} }
@ -1438,20 +1471,38 @@ function resolveRedeemPowerRate(data) {
return normalizedRate > 0 ? normalizedRate : 0; return normalizedRate > 0 ? normalizedRate : 0;
} }
async function fetchUserData(requestOptions) {
return fetchPayload(
createRequestOptions(
{
url: serviceConfig.ENDPOINTS.getUser,
},
requestOptions,
),
"用户信息加载失败",
);
}
async function fetchTransferFeeData(requestOptions) { async function fetchTransferFeeData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.transferFee, url: serviceConfig.ENDPOINTS.transferFee,
}, requestOptions), },
requestOptions,
),
"手续费比例加载失败", "手续费比例加载失败",
); );
} }
async function fetchWithdrawRateData(requestOptions) { async function fetchWithdrawRateData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.withdrawRate, url: serviceConfig.ENDPOINTS.withdrawRate,
}, requestOptions), },
requestOptions,
),
"提现费率加载失败", "提现费率加载失败",
); );
} }
@ -1488,27 +1539,36 @@ function resolvePercentRate(data) {
async function fetchWalletAddressData(requestOptions) { async function fetchWalletAddressData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.walletDetail, url: serviceConfig.ENDPOINTS.walletDetail,
}, requestOptions), },
requestOptions,
),
"钱包加载失败", "钱包加载失败",
); );
} }
async function fetchCoinIndexData(requestOptions) { async function fetchCoinIndexData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.coinIndex, url: serviceConfig.ENDPOINTS.coinIndex,
}, requestOptions), },
requestOptions,
),
"抵用券信息加载失败", "抵用券信息加载失败",
); );
} }
async function fetchUserProfileData(requestOptions) { async function fetchUserProfileData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.userProfile, url: serviceConfig.ENDPOINTS.userProfile,
}, requestOptions), },
requestOptions,
),
"用户信息加载失败", "用户信息加载失败",
); );
} }
@ -1542,12 +1602,18 @@ function normalizePointsConvertInterval(value) {
async function fetchPointsConvertList(pagination, requestOptions, interval) { async function fetchPointsConvertList(pagination, requestOptions, interval) {
const normalizedInterval = normalizePointsConvertInterval(interval); const normalizedInterval = normalizePointsConvertInterval(interval);
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.pointsConvertAvailableList, url: serviceConfig.ENDPOINTS.pointsConvertAvailableList,
data: buildPagingRequestData({ data: buildPagingRequestData(
{
interval: normalizedInterval, interval: normalizedInterval,
}, pagination), },
}, requestOptions), pagination,
),
},
requestOptions,
),
"积分转换列表加载失败", "积分转换列表加载失败",
); );
} }
@ -1558,56 +1624,84 @@ async function fetchPointsConvertHistoryListData(
requestOptions, requestOptions,
) { ) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.pointsConvertHistoryList, url: serviceConfig.ENDPOINTS.pointsConvertHistoryList,
data: buildPagingRequestData({ data: buildPagingRequestData(
{
month: month, month: month,
}, pagination), },
}, requestOptions), pagination,
),
},
requestOptions,
),
"积分兑换列表加载失败", "积分兑换列表加载失败",
); );
} }
async function fetchTransferLedgerData(pagination, requestOptions) { async function fetchTransferLedgerData(pagination, requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.transferLedger, url: serviceConfig.ENDPOINTS.transferLedger,
data: buildPagingRequestData({}, pagination), data: buildPagingRequestData({}, pagination),
}, requestOptions), },
requestOptions,
),
"转赠记录加载失败", "转赠记录加载失败",
); );
} }
async function fetchWalletFlowListData(flowType, pagination, requestOptions) { async function fetchWalletFlowListData(flowType, pagination, requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.walletFlowList, url: serviceConfig.ENDPOINTS.walletFlowList,
data: buildPagingRequestData({ data: buildPagingRequestData(
{
type: flowType, type: flowType,
}, pagination), },
}, requestOptions), pagination,
),
},
requestOptions,
),
"资产流水加载失败", "资产流水加载失败",
); );
} }
async function fetchRedeemRecordListData(redeemType, pagination, requestOptions) { async function fetchRedeemRecordListData(
redeemType,
pagination,
requestOptions,
) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.redeemRecordList, url: serviceConfig.ENDPOINTS.redeemRecordList,
data: buildPagingRequestData({ data: buildPagingRequestData(
{
type: redeemType, type: redeemType,
}, pagination), },
}, requestOptions), pagination,
),
},
requestOptions,
),
"兑换记录加载失败", "兑换记录加载失败",
); );
} }
async function fetchPointsConvertInfoData(query, requestOptions) { async function fetchPointsConvertInfoData(query, requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.pointsConvertRecordDetail, url: serviceConfig.ENDPOINTS.pointsConvertRecordDetail,
data: query || {}, data: query || {},
}, requestOptions), },
requestOptions,
),
"积分转换详情加载失败", "积分转换详情加载失败",
); );
} }
@ -1616,8 +1710,9 @@ export async function fetchAssetHome(requestOptions) {
const result = await Promise.all([ const result = await Promise.all([
fetchPriceData(requestOptions), fetchPriceData(requestOptions),
fetchHomeBalanceData(requestOptions), fetchHomeBalanceData(requestOptions),
fetchUserData(requestOptions),
]); ]);
const overview = buildHomeOverview(result[1], result[0]); const overview = buildHomeOverview(result[1], result[0], result[2]);
setHomeTickerCache(overview.ticker); setHomeTickerCache(overview.ticker);
return overview; return overview;
} }
@ -1678,11 +1773,7 @@ export async function fetchPointsConvertHome(
const mergedRequestOptions = params.requestOptions; const mergedRequestOptions = params.requestOptions;
const result = await Promise.all([ const result = await Promise.all([
fetchHomeBalanceData(mergedRequestOptions), fetchHomeBalanceData(mergedRequestOptions),
fetchPointsConvertHistoryListData( fetchPointsConvertHistoryListData(month, pagination, mergedRequestOptions),
month,
pagination,
mergedRequestOptions,
),
]); ]);
const balances = normalizeBalances(result[0]); const balances = normalizeBalances(result[0]);
const records = buildPointsHistoryRecords(result[1]).map(function (item) { const records = buildPointsHistoryRecords(result[1]).map(function (item) {
@ -1704,8 +1795,7 @@ export async function fetchPointsConvertSelection(
requestOptions, requestOptions,
) { ) {
const interval = const interval =
paginationOrRequestOptions && paginationOrRequestOptions && typeof paginationOrRequestOptions === "object"
typeof paginationOrRequestOptions === "object"
? paginationOrRequestOptions.interval ? paginationOrRequestOptions.interval
: ""; : "";
const params = resolvePagingArguments( const params = resolvePagingArguments(
@ -1765,14 +1855,17 @@ export async function submitAssetPointsConvert(payload, requestOptions) {
} }
await fetchPayload( await fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.pointsConvertSubmit, url: serviceConfig.ENDPOINTS.pointsConvertSubmit,
method: "POST", method: "POST",
data: { data: {
ids: ids.join(","), ids: ids.join(","),
type: 1, type: 1,
}, },
}, requestOptions), },
requestOptions,
),
"积分转换失败", "积分转换失败",
); );
@ -1807,13 +1900,16 @@ export async function searchTransferUser(uid, requestOptions) {
} }
const data = await fetchPayload( const data = await fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.transferUser, url: serviceConfig.ENDPOINTS.transferUser,
method: "POST", method: "POST",
data: { data: {
uid: keyword, uid: keyword,
}, },
}, requestOptions), },
requestOptions,
),
"查询好友失败", "查询好友失败",
); );
@ -1822,7 +1918,9 @@ export async function searchTransferUser(uid, requestOptions) {
export async function submitAssetTransfer(payload, requestOptions) { export async function submitAssetTransfer(payload, requestOptions) {
const transferType = payload && payload.type === "power" ? "power" : "points"; const transferType = payload && payload.type === "power" ? "power" : "points";
const targetId = String(payload && payload.targetId ? payload.targetId : "").trim(); const targetId = String(
payload && payload.targetId ? payload.targetId : "",
).trim();
const amount = toNumber(payload && payload.amount); const amount = toNumber(payload && payload.amount);
if (!targetId) { if (!targetId) {
@ -1836,7 +1934,8 @@ export async function submitAssetTransfer(payload, requestOptions) {
const result = await Promise.all([ const result = await Promise.all([
fetchTransferFeeData(requestOptions), fetchTransferFeeData(requestOptions),
fetchPayload( fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: url:
transferType === "power" transferType === "power"
? serviceConfig.ENDPOINTS.transferPowerSubmit ? serviceConfig.ENDPOINTS.transferPowerSubmit
@ -1846,7 +1945,9 @@ export async function submitAssetTransfer(payload, requestOptions) {
uid: targetId, uid: targetId,
number: String(amount), number: String(amount),
}, },
}, requestOptions), },
requestOptions,
),
"转赠失败", "转赠失败",
), ),
]); ]);
@ -1901,14 +2002,17 @@ export async function submitAssetPowerExchange(payload, requestOptions) {
} }
await fetchPayload( await fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.powerExchangeSubmit, url: serviceConfig.ENDPOINTS.powerExchangeSubmit,
method: "POST", method: "POST",
data: { data: {
type: mode === "coupon" ? 1 : mode === "balance" ? 2 : 0, type: mode === "coupon" ? 1 : mode === "balance" ? 2 : 0,
number: String(amount), number: String(amount),
}, },
}, requestOptions), },
requestOptions,
),
"算力兑换失败", "算力兑换失败",
); );
@ -1957,14 +2061,17 @@ export async function submitAssetBmtFlashExchange(payload, requestOptions) {
} }
await fetchPayload( await fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.bmtFlashExchangeSubmit, url: serviceConfig.ENDPOINTS.bmtFlashExchangeSubmit,
method: "POST", method: "POST",
data: { data: {
type: mode === "coupon" ? 1 : mode === "balance" ? 2 : 0, type: mode === "coupon" ? 1 : mode === "balance" ? 2 : 0,
number: String(amount), number: String(amount),
}, },
}, requestOptions), },
requestOptions,
),
"BMT闪兑失败", "BMT闪兑失败",
); );
@ -1973,7 +2080,6 @@ export async function submitAssetBmtFlashExchange(payload, requestOptions) {
}; };
} }
// BMT兑换 // BMT兑换
export async function fetchBmtExchangeDetail(requestOptions) { export async function fetchBmtExchangeDetail(requestOptions) {
const result = await Promise.all([ const result = await Promise.all([
@ -2012,13 +2118,16 @@ export async function submitAssetBmtExchange(payload, requestOptions) {
} }
await fetchPayload( await fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.bmtExchangeSubmit, url: serviceConfig.ENDPOINTS.bmtExchangeSubmit,
method: "POST", method: "POST",
data: { data: {
number: String(amount), number: String(amount),
}, },
}, requestOptions), },
requestOptions,
),
"BMT兑换失败", "BMT兑换失败",
); );
@ -2070,14 +2179,17 @@ export async function submitAssetWithdraw(payload, requestOptions) {
} }
await fetchPayload( await fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.withdrawSubmit, url: serviceConfig.ENDPOINTS.withdrawSubmit,
method: "POST", method: "POST",
data: { data: {
address: address, address: address,
number: String(amount), number: String(amount),
}, },
}, requestOptions), },
requestOptions,
),
"提取失败", "提取失败",
); );
@ -2099,7 +2211,10 @@ export async function fetchLedgerDetail(
const mergedRequestOptions = params.requestOptions; const mergedRequestOptions = params.requestOptions;
if (type === "transfer") { if (type === "transfer") {
const data = await fetchTransferLedgerData(pagination, mergedRequestOptions); const data = await fetchTransferLedgerData(
pagination,
mergedRequestOptions,
);
return { return {
type: type, type: type,
title: "转赠记录", title: "转赠记录",
@ -2110,7 +2225,11 @@ export async function fetchLedgerDetail(
} }
if (type === "points") { if (type === "points") {
const data = await fetchWalletFlowListData(0, pagination, mergedRequestOptions); const data = await fetchWalletFlowListData(
0,
pagination,
mergedRequestOptions,
);
return { return {
type: type, type: type,
title: "可用积分记录", title: "可用积分记录",
@ -2126,7 +2245,11 @@ export async function fetchLedgerDetail(
} }
if (type === "power-flow") { if (type === "power-flow") {
const data = await fetchWalletFlowListData(1, pagination, mergedRequestOptions); const data = await fetchWalletFlowListData(
1,
pagination,
mergedRequestOptions,
);
return { return {
type: type, type: type,
title: "算力记录", title: "算力记录",
@ -2142,7 +2265,11 @@ export async function fetchLedgerDetail(
} }
if (type === "power") { if (type === "power") {
const data = await fetchRedeemRecordListData(0, pagination, mergedRequestOptions); const data = await fetchRedeemRecordListData(
0,
pagination,
mergedRequestOptions,
);
return { return {
type: type, type: type,
title: "兑换记录", title: "兑换记录",
@ -2167,7 +2294,11 @@ export async function fetchLedgerDetail(
} }
if (type === "bmt") { if (type === "bmt") {
const data = await fetchRedeemRecordListData(1, pagination, mergedRequestOptions); const data = await fetchRedeemRecordListData(
1,
pagination,
mergedRequestOptions,
);
return { return {
type: type, type: type,
title: "兑换记录", title: "兑换记录",
@ -2185,7 +2316,11 @@ export async function fetchLedgerDetail(
} }
if (type === "bmt-flash") { if (type === "bmt-flash") {
const data = await fetchRedeemRecordListData(1, pagination, mergedRequestOptions); const data = await fetchRedeemRecordListData(
1,
pagination,
mergedRequestOptions,
);
return { return {
type: type, type: type,
title: "闪兑记录", title: "闪兑记录",
@ -2203,7 +2338,11 @@ export async function fetchLedgerDetail(
} }
if (type === "withdraw") { if (type === "withdraw") {
const data = await fetchWalletFlowListData(2, pagination, mergedRequestOptions); const data = await fetchWalletFlowListData(
2,
pagination,
mergedRequestOptions,
);
return { return {
type: type, type: type,
title: "提取记录", title: "提取记录",
@ -2237,20 +2376,25 @@ export async function fetchWalletDetail(requestOptions) {
} }
export async function saveAssetWallet(payload, requestOptions) { export async function saveAssetWallet(payload, requestOptions) {
const address = String(payload && payload.address ? payload.address : "").trim(); const address = String(
payload && payload.address ? payload.address : "",
).trim();
if (!address) { if (!address) {
throw createError("请输入钱包地址"); throw createError("请输入钱包地址");
} }
await fetchPayload( await fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.walletSave, url: serviceConfig.ENDPOINTS.walletSave,
method: "POST", method: "POST",
data: { data: {
address: address, address: address,
}, },
}, requestOptions), },
requestOptions,
),
"保存失败", "保存失败",
); );
@ -2261,13 +2405,16 @@ export async function saveAssetWallet(payload, requestOptions) {
export async function deleteAssetWallet(id, requestOptions) { export async function deleteAssetWallet(id, requestOptions) {
await fetchPayload( await fetchPayload(
createRequestOptions({ createRequestOptions(
{
url: serviceConfig.ENDPOINTS.walletSave, url: serviceConfig.ENDPOINTS.walletSave,
method: "POST", method: "POST",
data: { data: {
address: "", address: "",
}, },
}, requestOptions), },
requestOptions,
),
"删除失败", "删除失败",
); );
@ -2275,3 +2422,91 @@ export async function deleteAssetWallet(id, requestOptions) {
success: true, success: true,
}; };
} }
function padTimeUnit(value) {
return value < 10 ? "0" + value : String(value);
}
function formatTimestamp(timestamp) {
const seconds = toNumber(timestamp);
if (!seconds) {
return "";
}
const date = new Date(seconds * 1000);
return (
date.getFullYear() +
"-" +
padTimeUnit(date.getMonth() + 1) +
"-" +
padTimeUnit(date.getDate()) +
" " +
padTimeUnit(date.getHours()) +
":" +
padTimeUnit(date.getMinutes()) +
":" +
padTimeUnit(date.getSeconds())
);
}
function mapBmtrRecords(list) {
return normalizeListData(list).map(function (item) {
const amountValue = toNumber(item && item.bmtr);
// 交易类型0 支出1 收入2 退回
const bmtrType = Number(item && item.bmtr_type);
const isIncome = bmtrType === 1 || bmtrType === 2;
return {
id: buildRecordId(item, "bmtr"),
title: String(pickFirstValue(item, ["bmtr_type_tips", "title"]) || "BMTR记录"),
time: formatTimestamp(item && item.add_time),
amount: isIncome ? Math.abs(amountValue) : -Math.abs(amountValue),
orderSn: String(pickFirstValue(item, ["order_sn", "sn"]) || ""),
};
});
}
export async function fetchBmtrDetail(requestOptions) {
const data = await fetchPayload(
createRequestOptions(
{
url: serviceConfig.ENDPOINTS.bmtrMonthPoints,
},
requestOptions,
),
"BMTR信息加载失败",
);
const bmtrValue = pickFirstValue(data, ["bmtr"]);
return {
available: bmtrValue === "" ? "0.0000" : String(bmtrValue),
address: String(pickFirstValue(data, ["address"]) || ""),
};
}
export async function fetchBmtrRecords(
paginationOrRequestOptions,
requestOptions,
) {
const params = resolvePagingArguments(
paginationOrRequestOptions,
requestOptions,
);
const data = await fetchPayload(
createRequestOptions(
{
url: serviceConfig.ENDPOINTS.bmtrChargeList,
data: buildPagingRequestData({}, params.pagination),
},
params.requestOptions,
),
"BMTR记录加载失败",
);
return {
records: mapBmtrRecords(data),
pagination: buildPaginationMeta(data, params.pagination),
};
}

View File

@ -16,6 +16,7 @@ const serviceConfig = {
powerExchangeMuit: "/api/hn/redeem/getMuit", powerExchangeMuit: "/api/hn/redeem/getMuit",
bmtRedeemPowerRate: "/api/hn/redeem/getRedeemPowerRate", bmtRedeemPowerRate: "/api/hn/redeem/getRedeemPowerRate",
bmtExchangeSubmit: "/api/hn/redeem/redeem_bmt", bmtExchangeSubmit: "/api/hn/redeem/redeem_bmt",
getUser: "/api/mxj/api/get_user",
transferFee: "/api/hn/transfer/getProp", transferFee: "/api/hn/transfer/getProp",
transferUser: "/api/hn/transfer/getUserInfo", transferUser: "/api/hn/transfer/getUserInfo",
transferPowerSubmit: "/api/hn/transfer/transferPower", transferPowerSubmit: "/api/hn/transfer/transferPower",
@ -33,6 +34,8 @@ const serviceConfig = {
pointsConvertAvailableList: "/api/integral/transferList", pointsConvertAvailableList: "/api/integral/transferList",
pointsConvertSubmit: "/api/integral/doTransfer", pointsConvertSubmit: "/api/integral/doTransfer",
pointsConvertRecordDetail: "/api/integral/transferInfo", pointsConvertRecordDetail: "/api/integral/transferInfo",
bmtrMonthPoints: "/api/mxj/api/get_month_points",
bmtrChargeList: "/api/mxj/api/get_charge_list",
}, },
}; };

View File

@ -73,6 +73,15 @@
"router" : { "router" : {
"base" : "/bmt/", "base" : "/bmt/",
"mode" : "history" "mode" : "history"
},
"devServer" : {
"proxy" : {
"/api" : {
"target" : "https://tpoint.agrimedia.cn",
"changeOrigin" : true,
"secure" : false
}
}
} }
} }
} }

View File

@ -85,6 +85,13 @@
"navigationStyle": "custom", "navigationStyle": "custom",
"backgroundColor": "#191E32" "backgroundColor": "#191E32"
} }
},
{
"path": "pages/assets/bmtr",
"style": {
"navigationStyle": "custom",
"backgroundColor": "#191E32"
}
} }
], ],
"globalStyle": { "globalStyle": {

535
pages/assets/bmtr.vue Normal file
View File

@ -0,0 +1,535 @@
<template>
<view class="asset-page asset-theme bmtr-page">
<asset-page-shell title="BMTR" />
<view class="bmtr-scroll">
<view class="bmtr-hero">
<view class="bmtr-hero__main">
<view class="bmtr-hero__head">
<view class="bmtr-hero__badge">
<text class="bmtr-hero__badge-text">R</text>
</view>
<text class="bmtr-hero__title">BMTR</text>
</view>
<text class="bmtr-hero__label">可用 BMTR</text>
<text class="bmtr-hero__value asset-number-font">{{
summary.available
}}</text>
<!-- <text class="bmtr-hero__total asset-number-font">累计{{ summary.total }}</text> -->
</view>
<!-- 设计图右侧的钱包插图替换 heroImage 为实际图片地址即可显示 -->
<image v-if="heroImage" class="bmtr-hero__image" :src="heroImage" mode="aspectFit"></image>
</view>
<view class="bmtr-address glass-panel" v-if="displayAddress">
<view class="bmtr-address__head">
<image class="bmtr-address__icon" src="https://imgs.agrimedia.cn/bm-bmt/qianbao.png" mode="aspectFit"></image>
<text class="bmtr-address__title">白马提回地址</text>
</view>
<view class="bmtr-address__field">
<view class="bmtr-address__content">
<text class="bmtr-address__label">从交易所提回BMTR的地址</text>
<text class="bmtr-address__value asset-number-font">{{ displayAddress }}</text>
</view>
<view class="bmtr-address__actions">
<text class="bmtr-address__action" @click="toggleAddress">{{
isAddressVisible ? "隐藏" : "显示"
}}</text>
<text class="bmtr-address__action" @click="copyAddress">复制</text>
</view>
</view>
</view>
<text class="bmtr-section-title">BMTR记录</text>
<view v-if="records.length" class="bmtr-records glass-panel">
<view v-for="item in records" :key="item.id" class="bmtr-record">
<view class="bmtr-record__main">
<text class="bmtr-record__title">{{ item.title }}</text>
<text class="bmtr-record__time">{{ item.time }}</text>
</view>
<text class="bmtr-record__amount asset-number-font" :class="item.amount >= 0
? 'bmtr-record__amount--in'
: 'bmtr-record__amount--out'
">{{ formatAmount(item.amount) }}</text>
</view>
</view>
<view v-else class="bmtr-records-empty glass-panel">
<text class="bmtr-records-empty__text">暂无记录</text>
</view>
<view v-if="showLoadMoreState" class="list-load-more">
<text class="list-load-more__text asset-number-font">{{ loadMoreText }}</text>
</view>
</view>
</view>
</template>
<script>
import AssetPageShell from "../../components/asset-page-shell.vue";
import { fetchBmtrDetail, fetchBmtrRecords } from "../../api/assets";
export default {
components: {
AssetPageShell,
},
data() {
return {
heroImage: "",
summary: {
available: "0.0000",
total: "0.00",
},
address: "",
isAddressVisible: false,
records: [],
page: 1,
pageSize: 10,
hasMore: false,
loadingMore: false,
hasShown: false,
};
},
computed: {
displayAddress() {
if (this.isAddressVisible) {
return this.address;
}
return this.maskAddress(this.address);
},
showLoadMoreState() {
return this.records.length > 0;
},
loadMoreText() {
if (this.loadingMore) {
return "加载中...";
}
if (this.hasMore) {
return "上拉加载更多";
}
return "没有更多了";
},
},
onLoad() {
this.resetPagingState();
this.loadDetail(true);
this.loadRecords(false, 1);
},
onShow() {
if (this.hasShown) {
this.resetPagingState();
this.loadDetail(false);
this.loadRecords(false, 1);
return;
}
this.hasShown = true;
},
onReachBottom() {
this.loadMore();
},
methods: {
resetPagingState() {
this.page = 1;
this.hasMore = false;
this.loadingMore = false;
},
updatePaging(result, requestedPage, receivedLength) {
const pagination =
result && result.pagination && typeof result.pagination === "object"
? result.pagination
: null;
this.page =
Number(pagination && pagination.page) || Number(requestedPage) || this.page || 1;
if (pagination && typeof pagination.hasMore === "boolean") {
this.hasMore = pagination.hasMore;
return;
}
this.hasMore = receivedLength >= this.pageSize;
},
mergeRecords(currentRecords, nextRecords) {
const mergedMap = {};
const mergedList = [];
const appendItem = (item) => {
const key = String(item && item.id ? item.id : "").trim();
if (!key || mergedMap[key]) {
return;
}
mergedMap[key] = true;
mergedList.push(item);
};
(Array.isArray(currentRecords) ? currentRecords : []).forEach(appendItem);
(Array.isArray(nextRecords) ? nextRecords : []).forEach(appendItem);
return mergedList;
},
async loadDetail(showLoading) {
try {
const detail = await fetchBmtrDetail(
showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null,
);
this.summary = Object.assign({}, this.summary, {
available: detail.available,
});
this.address = detail.address;
} catch (error) {
uni.showToast({
title: error.message || "页面加载失败",
icon: "none",
});
}
},
async loadRecords(showLoading, targetPage) {
const page = Number(targetPage || 1);
const isLoadMore = page > 1;
if (isLoadMore && this.loadingMore) {
return;
}
if (isLoadMore) {
this.loadingMore = true;
}
try {
const result = await fetchBmtrRecords(
{
page: page,
pageSize: this.pageSize,
},
!isLoadMore && showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null,
);
const incomingRecords = Array.isArray(result.records)
? result.records
: [];
const previousCount = this.records.length;
const mergedRecords = isLoadMore
? this.mergeRecords(this.records, incomingRecords)
: incomingRecords;
this.records = mergedRecords;
this.updatePaging(result, page, incomingRecords.length);
if (isLoadMore && mergedRecords.length <= previousCount) {
this.hasMore = false;
}
} catch (error) {
uni.showToast({
title: error.message || (isLoadMore ? "加载更多失败" : "页面加载失败"),
icon: "none",
});
} finally {
if (isLoadMore) {
this.loadingMore = false;
}
}
},
loadMore() {
if (this.loadingMore || !this.hasMore) {
return;
}
this.loadRecords(false, this.page + 1);
},
maskAddress(address) {
const safeAddress = String(address || "");
if (!safeAddress) {
return "";
}
if (safeAddress.length <= 4) {
return "****";
}
const headLength = Math.min(4, Math.ceil(safeAddress.length / 3));
const tailLength = Math.min(4, Math.floor(safeAddress.length / 3));
return (
safeAddress.slice(0, headLength) +
"****" +
safeAddress.slice(-tailLength)
);
},
toggleAddress() {
this.isAddressVisible = !this.isAddressVisible;
},
copyAddress() {
uni.setClipboardData({
data: this.address,
success() {
uni.showToast({
title: "已复制",
icon: "none",
});
},
});
},
formatAmount(amount) {
const number = Number(amount || 0);
return (number >= 0 ? "+" : "-") + Math.abs(number);
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.bmtr-page {
min-height: 100vh;
}
.bmtr-scroll {
padding: 12rpx 24rpx calc(env(safe-area-inset-bottom) + 36rpx);
}
.bmtr-hero {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-radius: 24rpx;
border: 1px solid rgba(143, 167, 207, 0.16);
background:
url("https://imgs.agrimedia.cn/webimg/202607221601148466081.png") center /
cover no-repeat,
radial-gradient(circle at 88% 30%,
rgba(76, 201, 255, 0.16),
transparent 55%),
linear-gradient(135deg, #1d2b52 0%, #14203f 100%);
overflow: hidden;
}
.bmtr-hero__main {
display: flex;
flex-direction: column;
min-width: 0;
}
.bmtr-hero__head {
display: flex;
align-items: center;
}
.bmtr-hero__badge {
display: flex;
align-items: center;
justify-content: center;
width: 64rpx;
height: 64rpx;
margin-right: 16rpx;
border-radius: 50%;
background: #ff5a5a;
}
.bmtr-hero__badge-text {
font-size: 36rpx;
font-weight: 800;
color: #ffffff;
}
.bmtr-hero__title {
font-size: 36rpx;
font-weight: 700;
color: $asset-text-main;
}
.bmtr-hero__label {
margin-top: 24rpx;
font-size: 26rpx;
color: $asset-text-subtle;
}
.bmtr-hero__value {
margin-top: 8rpx;
font-size: 56rpx;
font-weight: 800;
line-height: 1.1;
color: $asset-text-main;
}
.bmtr-hero__total {
margin-top: 12rpx;
font-size: 28rpx;
font-weight: 600;
color: #35d9a0;
}
.bmtr-hero__image {
width: 260rpx;
height: 200rpx;
flex: 0 0 auto;
margin-left: 24rpx;
}
.bmtr-address {
margin-top: 24rpx;
padding: 28rpx;
}
.bmtr-address__head {
display: flex;
align-items: center;
}
.bmtr-address__icon {
width: 40rpx;
height: 40rpx;
margin-right: 12rpx;
}
.bmtr-address__title {
font-size: 32rpx;
font-weight: 700;
color: $asset-text-main;
}
.bmtr-address__field {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 24rpx;
padding: 24rpx;
border-radius: 14rpx;
background: rgba(18, 25, 48, 0.72);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.bmtr-address__content {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
.bmtr-address__label {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.62);
}
.bmtr-address__value {
margin-top: 12rpx;
font-size: 28rpx;
font-weight: 600;
word-break: break-all;
color: $asset-text-main;
}
.bmtr-address__actions {
display: flex;
align-items: center;
flex: 0 0 auto;
margin-left: 24rpx;
}
.bmtr-address__action {
font-size: 26rpx;
font-weight: 600;
color: #38d0bd;
}
.bmtr-address__action+.bmtr-address__action {
margin-left: 28rpx;
}
.bmtr-section-title {
display: block;
margin: 36rpx 0 20rpx;
font-size: 32rpx;
font-weight: 700;
color: $asset-text-main;
}
.bmtr-records {
padding: 0 28rpx;
}
.bmtr-record {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.bmtr-record:last-child {
border-bottom: 0;
}
.bmtr-record__main {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
.bmtr-record__title {
font-size: 30rpx;
font-weight: 600;
color: $asset-text-main;
}
.bmtr-record__time {
margin-top: 10rpx;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.45);
}
.bmtr-record__amount {
flex: 0 0 auto;
margin-left: 24rpx;
font-size: 36rpx;
font-weight: 800;
}
.bmtr-record__amount--in {
color: #ff9440;
}
.bmtr-record__amount--out {
color: #35d9a0;
}
.bmtr-records-empty {
display: flex;
justify-content: center;
padding: 70rpx 24rpx;
}
.bmtr-records-empty__text {
font-size: 26rpx;
color: rgba(158, 170, 204, 0.86);
}
.list-load-more {
display: flex;
justify-content: center;
padding: 12rpx 0 22rpx;
}
.list-load-more__text {
font-size: 22rpx;
color: rgba(173, 182, 211, 0.9);
}
</style>

View File

@ -5,12 +5,9 @@
<text class="home-topbar__title">{{ overview.title || "数字资产" }}</text> <text class="home-topbar__title">{{ overview.title || "数字资产" }}</text>
<view class="home-wallet-btn" @click="openWallet"> <view class="home-wallet-btn" @click="openWallet">
<view class="home-wallet-btn__icon"> <view class="home-wallet-btn__icon">
<image <image src="https://imgs.agrimedia.cn/bm-bmt/qianbao.png" mode="widthFix"></image>
src="https://imgs.agrimedia.cn/bm-bmt/qianbao.png"
mode="widthFix"
></image>
</view> </view>
<text class="home-wallet-btn__text">钱包</text> <text class="home-wallet-btn__text">钱包1</text>
</view> </view>
</view> </view>
@ -19,7 +16,8 @@
<view class="home-skeleton__hero"> <view class="home-skeleton__hero">
<view class="home-skeleton__banner skeleton-block"></view> <view class="home-skeleton__banner skeleton-block"></view>
<view class="home-skeleton__stat-panel"> <view class="home-skeleton__stat-panel">
<view v-for="index in 2" :key="'home-skeleton-stat-' + index" class="home-skeleton__stat-card skeleton-card"> <view v-for="index in 2" :key="'home-skeleton-stat-' + index"
class="home-skeleton__stat-card skeleton-card">
<view class="home-skeleton__line home-skeleton__line--label skeleton-block"></view> <view class="home-skeleton__line home-skeleton__line--label skeleton-block"></view>
<view class="home-skeleton__line home-skeleton__line--value skeleton-block"></view> <view class="home-skeleton__line home-skeleton__line--value skeleton-block"></view>
</view> </view>
@ -29,7 +27,8 @@
<view class="home-skeleton__section"> <view class="home-skeleton__section">
<view class="home-skeleton__title skeleton-block"></view> <view class="home-skeleton__title skeleton-block"></view>
<view class="home-skeleton__asset-grid"> <view class="home-skeleton__asset-grid">
<view v-for="index in 5" :key="'home-skeleton-asset-' + index" class="home-skeleton__asset-card skeleton-card"> <view v-for="index in 5" :key="'home-skeleton-asset-' + index"
class="home-skeleton__asset-card skeleton-card">
<view class="home-skeleton__line home-skeleton__line--asset-label skeleton-block"></view> <view class="home-skeleton__line home-skeleton__line--asset-label skeleton-block"></view>
<view class="home-skeleton__line home-skeleton__line--asset-value skeleton-block"></view> <view class="home-skeleton__line home-skeleton__line--asset-value skeleton-block"></view>
</view> </view>
@ -39,7 +38,8 @@
<view class="home-skeleton__section"> <view class="home-skeleton__section">
<view class="home-skeleton__title skeleton-block"></view> <view class="home-skeleton__title skeleton-block"></view>
<view class="home-skeleton__feature-list"> <view class="home-skeleton__feature-list">
<view v-for="index in 6" :key="'home-skeleton-feature-' + index" class="home-skeleton__feature-item skeleton-card"> <view v-for="index in 6" :key="'home-skeleton-feature-' + index"
class="home-skeleton__feature-item skeleton-card">
<view class="home-skeleton__feature-main"> <view class="home-skeleton__feature-main">
<view class="home-skeleton__feature-icon skeleton-block"></view> <view class="home-skeleton__feature-icon skeleton-block"></view>
<view class="home-skeleton__line home-skeleton__line--feature skeleton-block"></view> <view class="home-skeleton__line home-skeleton__line--feature skeleton-block"></view>
@ -63,13 +63,8 @@
<view class="brand-banner"></view> <view class="brand-banner"></view>
<view class="stat-panel"> <view class="stat-panel">
<view <view v-for="item in overview.topStats" :key="item.key" class="stat-card"
v-for="item in overview.topStats" :class="'stat-card--' + item.accent" @click="openTopStat(item)">
:key="item.key"
class="stat-card"
:class="'stat-card--' + item.accent"
@click="openTopStat(item)"
>
<view class="stat-card__body"> <view class="stat-card__body">
<text class="stat-card__label">{{ item.title }}</text> <text class="stat-card__label">{{ item.title }}</text>
<view class="stat-card__value-row"> <view class="stat-card__value-row">
@ -86,21 +81,11 @@
<text class="home-section__title">我的资产</text> <text class="home-section__title">我的资产</text>
</view> </view>
<view class="asset-grid"> <view class="asset-grid">
<view <view v-for="item in overview.quickAssets" :key="item.key" class="asset-mini-card" :class="[
v-for="item in overview.quickAssets"
:key="item.key"
class="asset-mini-card"
:class="[
'asset-mini-card--' + item.accent, 'asset-mini-card--' + item.accent,
item.key === 'balance' ? 'asset-mini-card--full' : '', item.key === 'balance' ? 'asset-mini-card--full' : '',
]" ]" @click="openQuickAsset(item)">
@click="openQuickAsset(item)" <image class="asset-mini-card__bg" :src="quickAssetBg(item.key)" mode="aspectFill"></image>
>
<image
class="asset-mini-card__bg"
:src="quickAssetBg(item.key)"
mode="aspectFill"
></image>
<view class="asset-mini-card__head"> <view class="asset-mini-card__head">
<text class="asset-mini-card__label">{{ item.title }}</text> <text class="asset-mini-card__label">{{ item.title }}</text>
</view> </view>
@ -114,19 +99,11 @@
<text class="home-section__title">功能中心</text> <text class="home-section__title">功能中心</text>
</view> </view>
<view class="feature-list"> <view class="feature-list">
<view <view v-for="feature in overview.features" :key="feature.key" class="feature-list__item"
v-for="feature in overview.features" @click="openFeature(feature.key)">
:key="feature.key"
class="feature-list__item"
@click="openFeature(feature.key)"
>
<view class="feature-list__main"> <view class="feature-list__main">
<view class="feature-list__icon"> <view class="feature-list__icon">
<image <image class="feature-list__icon-image" :src="featureIcon(feature.key)" mode="aspectFit"></image>
class="feature-list__icon-image"
:src="featureIcon(feature.key)"
mode="aspectFit"
></image>
</view> </view>
<text class="feature-list__title">{{ feature.title }}</text> <text class="feature-list__title">{{ feature.title }}</text>
</view> </view>
@ -136,15 +113,12 @@
</view> </view>
<view class="notice-bar"> <view class="notice-bar">
<image <image class="notice-bar__icon" src="https://imgs.agrimedia.cn/bm-bmt/tips.png" mode="widthFix"></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> <text class="notice-bar__text">{{ overview.notice }}</text>
</view> </view>
<image src="https://imgs.agrimedia.cn/bm-bmt/bottom.png" mode="widthFix" style="width: 100%; margin-top: 20rpx"></image> <image src="https://imgs.agrimedia.cn/bm-bmt/bottom.png" mode="widthFix" style="width: 100%; margin-top: 20rpx">
</image>
</view> </view>
</view> </view>
</view> </view>
@ -222,6 +196,7 @@ export default {
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon.png", voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-icon.png",
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon.png", coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-icon.png",
power: "https://imgs.agrimedia.cn/bm-bmt/suanli-icon.png", power: "https://imgs.agrimedia.cn/bm-bmt/suanli-icon.png",
bmtr: "https://imgs.agrimedia.cn/webimg/202607171808386371853.svg",
}; };
return iconMap[key] || ""; return iconMap[key] || "";
@ -233,6 +208,7 @@ export default {
voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-bg.png", voucher: "https://imgs.agrimedia.cn/bm-bmt/quan-bg.png",
coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-bg.png", coupon: "https://imgs.agrimedia.cn/bm-bmt/xiaofei-bg.png",
power: "https://imgs.agrimedia.cn/bm-bmt/suanli-bg.png", power: "https://imgs.agrimedia.cn/bm-bmt/suanli-bg.png",
bmtr: "https://imgs.agrimedia.cn/webimg/202607201122331061368.svg",
}; };
return bgMap[key] || ""; return bgMap[key] || "";
@ -308,6 +284,7 @@ export default {
const urlMap = { const urlMap = {
points: "/pages/assets/ledger?type=points", points: "/pages/assets/ledger?type=points",
power: "/pages/assets/ledger?type=power-flow", power: "/pages/assets/ledger?type=power-flow",
bmtr: "/pages/assets/bmtr",
}; };
const targetUrl = urlMap[item.key]; const targetUrl = urlMap[item.key];
@ -446,6 +423,7 @@ export default {
width: 30rpx; width: 30rpx;
height: 32rpx; height: 32rpx;
margin-right: 8rpx; margin-right: 8rpx;
image { image {
width: 30rpx; width: 30rpx;
height: 32rpx; height: 32rpx;
@ -479,12 +457,10 @@ export default {
bottom: 0; bottom: 0;
left: -140rpx; left: -140rpx;
width: 140rpx; width: 140rpx;
background: linear-gradient( background: linear-gradient(90deg,
90deg,
rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.24) 50%, rgba(255, 255, 255, 0.24) 50%,
rgba(255, 255, 255, 0) 100% rgba(255, 255, 255, 0) 100%);
);
animation: home-skeleton-shimmer 1.35s linear infinite; animation: home-skeleton-shimmer 1.35s linear infinite;
} }
@ -644,6 +620,7 @@ export default {
0% { 0% {
transform: translateX(0); transform: translateX(0);
} }
100% { 100% {
transform: translateX(900rpx); transform: translateX(900rpx);
} }
@ -660,13 +637,10 @@ export default {
height: 380rpx; height: 380rpx;
border-radius: 28rpx; border-radius: 28rpx;
background: background:
linear-gradient( linear-gradient(180deg,
180deg,
rgba(5, 12, 28, 0.06) 0%, rgba(5, 12, 28, 0.06) 0%,
rgba(5, 12, 28, 0.16) 100% rgba(5, 12, 28, 0.16) 100%),
), url("https://imgs.agrimedia.cn/bm-bmt/home2-bg.png") no-repeat;
url("https://imgs.agrimedia.cn/bm-bmt/home2-bg.png")
no-repeat;
border: 1px solid rgba(112, 135, 196, 0.18); border: 1px solid rgba(112, 135, 196, 0.18);
background-size: cover; background-size: cover;
box-shadow: 0 24rpx 48rpx rgba(7, 12, 32, 0.28); box-shadow: 0 24rpx 48rpx rgba(7, 12, 32, 0.28);
@ -710,11 +684,9 @@ export default {
min-height: 126rpx; min-height: 126rpx;
padding: 22rpx 22rpx 20rpx; padding: 22rpx 22rpx 20rpx;
border-radius: 8rpx; border-radius: 8rpx;
background: linear-gradient( background: linear-gradient(180deg,
180deg,
rgba(42, 55, 82, 0.98) 0%, rgba(42, 55, 82, 0.98) 0%,
rgba(39, 51, 76, 0.98) 100% rgba(39, 51, 76, 0.98) 100%);
);
border: 1px solid rgba(121, 139, 190, 0.16); border: 1px solid rgba(121, 139, 190, 0.16);
box-shadow: 0 14rpx 24rpx rgba(5, 11, 29, 0.2); box-shadow: 0 14rpx 24rpx rgba(5, 11, 29, 0.2);
} }
@ -742,19 +714,15 @@ export default {
} }
.stat-card__icon-box--gold { .stat-card__icon-box--gold {
background: linear-gradient( background: linear-gradient(135deg,
135deg,
rgba(20, 182, 255, 0.22) 0%, rgba(20, 182, 255, 0.22) 0%,
rgba(20, 182, 255, 0.08) 100% rgba(20, 182, 255, 0.08) 100%);
);
} }
.stat-card__icon-box--green { .stat-card__icon-box--green {
background: linear-gradient( background: linear-gradient(135deg,
135deg,
rgba(46, 233, 167, 0.22) 0%, rgba(46, 233, 167, 0.22) 0%,
rgba(46, 233, 167, 0.08) 100% rgba(46, 233, 167, 0.08) 100%);
);
} }
.stat-card__body { .stat-card__body {
@ -844,9 +812,9 @@ export default {
background: rgba(35, 45, 79, 0.9); background: rgba(35, 45, 79, 0.9);
} }
.asset-mini-card--full { // .asset-mini-card--full {
grid-column: 1 / -1; // grid-column: 1 / -1;
} // }
.asset-mini-card__bg { .asset-mini-card__bg {
position: absolute; position: absolute;

64
uni.webview.1.5.8.js Normal file
View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<script>
if (window.parent !== window && !window.location.search.includes('be_nested=1')) {
window.stop();
}
</script>
<meta charset="UTF-8">
<link rel="icon" href="https://cdn-static.gitcode.host/static/images/logo-favicon.png">
<link rel="dns-prefetch" href="https://res.hc-cdn.com">
<link rel="dns-prefetch" href="https://assets-cli.s2.udesk.cn">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta content='pc,mobile' name='applicable-device'>
<meta property="og:image" content="https://cdn-img.gitcode.com/images/og-image.png">
<meta property="og:image:secure_url" content="https://cdn-img.gitcode.com/images/og-image.png">
<meta name="twitter:image" content="https://cdn-img.gitcode.com/images/og-image.png">
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="favicon">
<meta property="og:image:type" content="image/png">
<meta name="baidu-site-verification" content="codeva-zQ72KLO8Ne" />
<title>AtomGit - 全球开发者的开源社区,开源代码托管平台</title>
<meta name="description" content="AtomGit是面向全球开发者的开源社区,包括原创博客,开源代码托管,代码协作,项目管理等。与开发者社区互动,提升您的研发效率和质量。"/>
<meta name="keywords" content="开源社区,开源代码,AtomGit"/>
<style type="text/css">
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
<script type="module" crossorigin src="https://cdn-static.gitcode.com/assets/index-7d5af48e.js"></script>
<link rel="modulepreload" crossorigin href="https://cdn-static.gitcode.com/assets/vendor-3b648d5e.js">
<link rel="modulepreload" crossorigin href="https://cdn-static.gitcode.com/assets/vendor-layout-5f380ef2.js">
<link rel="stylesheet" href="https://cdn-static.gitcode.com/assets/vendor-layout-0f2324e7.css">
<link rel="stylesheet" href="https://cdn-static.gitcode.com/assets/index-128d7750.css">
</head>
<body>
<img src="https://cdn-static.gitcode.com/static/images/logo-favicon.png" style="position:absolute;left:-1000px;top:-1000px" alt="favicon">
<div id="app"></div>
</body>
<!-- ai 社区图标 -->
<script type="text/javascript" src="https://cdn-static.gitcode.com/js/font_4976451_914etgmfzh8.js" defer></script>
<script type="text/javascript" src="https://cdn-static.gitcode.com/js/font_4205092_4w2gnan46b9.js" defer></script>
<script type="text/javascript" src="https://cdn-static.gitcode.com/js/lottie.min.js" defer></script>
<script type="text/javascript" src="https://cdn-static.gitcode.com/js/tac/load.min.js" defer></script>
<script type="text/javascript" src="https://cdn-static.gitcode.com/js/yunpian/riddler-sdk-0.2.2.js" defer></script>
<script src="https://cdn-static.gitcode.com/js/yidun/yidun-captcha.js" defer></script>
<script type="text/javascript" src="https://cdn-static.gitcode.com/js/furion.js" defer></script>
<!-- baidu统计X -->
<script>
var _hmt = _hmt || [];
_hmt.push(['_setAutoPageview', true]);
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?62047c952451105d57bab2c4af9ce85b";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</html>