新增消费券 余额页面

This commit is contained in:
whitechiina 2026-08-05 09:33:46 +08:00
parent 939fe3dc11
commit 50ccc2dc33
35 changed files with 20805 additions and 55 deletions

View File

@ -137,6 +137,24 @@ function getHomeTickerCache() {
return runtimeHomeTickerCache || normalizeTicker(null); return runtimeHomeTickerCache || normalizeTicker(null);
} }
function appendTokenToTargetUrl(targetUrl) {
const token = String(getCurrentWebviewToken() || "").trim();
let nextTargetUrl = String(targetUrl || "").trim();
if (token) {
nextTargetUrl +=
(nextTargetUrl.indexOf("?") > -1 ? "&" : "?") +
"token=" +
encodeURIComponent(token);
}
return {
token: token,
targetUrl: nextTargetUrl,
encodedTargetUrl: encodeURIComponent(nextTargetUrl),
};
}
function normalizeBalances(data) { function normalizeBalances(data) {
return { return {
points: toNumber(data && data.point), points: toNumber(data && data.point),
@ -1365,6 +1383,259 @@ function buildDefaultLedger(type, balances) {
}; };
} }
function buildFlowRecordId(item, groupKey, index) {
const directId = String(
pickFirstValue(item, ["id", "flow_id", "log_id", "record_id"]) || "",
).trim();
if (directId) {
return directId;
}
return [
groupKey,
pickFirstValue(item, ["add_time", "time", "created_at"]),
pickFirstValue(item, ["title", "mark", "amount", "number"]),
index,
]
.map((value) => String(value || ""))
.join("-");
}
function isPositiveFlowValue(value) {
const booleanLike = parseBooleanLike(value);
if (booleanLike !== null) {
return booleanLike;
}
return toNumber(value) > 0;
}
function resolveMonthLabel(dateText) {
const normalized = String(dateText || "").trim();
if (/^\d{6}$/.test(normalized)) {
return normalized.slice(4, 6) + "月";
}
return normalized || "本月";
}
function mapBalanceCenterItem(item, groupKey, index) {
const isIncome = isPositiveFlowValue(
pickFirstValue(item, ["pm", "io_type", "direction"]),
);
const amount = formatTransferRecordNumber(
pickFirstValue(item, ["number", "amount", "num"]),
);
const merchantName = pickFirstValue(item, ["merchant_name", "merchantName"]);
const frozenTime = pickFirstValue(item, [
"frozen_time_str",
"frozen_time",
"unlock_time",
]);
const failReason = pickFirstValue(item, ["fail_msg", "reason", "remark"]);
const refunded =
String(pickFirstValue(item, ["status"]) || "").trim() === "-1" &&
isIncome;
const detailRows = [
{
label: "获得时间",
value: pickFirstValue(item, ["add_time", "time", "created_at"]) || "--",
},
];
if (!isIncome && frozenTime && String(frozenTime) !== "0") {
detailRows.push({
label: "解冻时间",
value: String(frozenTime),
});
}
return {
id: buildFlowRecordId(item, groupKey, index),
title:
pickFirstValue(item, ["title", "mark", "type_name"]) ||
(isIncome ? "佣金获得" : "佣金支出"),
amount: (isIncome ? "+" : "-") + amount,
amountTone: isIncome ? "income" : "expense",
directionLabel: isIncome ? "佣金获得" : "佣金支出",
detailRows: detailRows,
merchantName: merchantName ? String(merchantName) : "",
note: failReason
? "原因:" + failReason
: refunded
? "已退单"
: "",
noteTone: failReason ? "danger" : refunded ? "expense" : "muted",
};
}
function mapBalanceCenterSections(data) {
const list = normalizeListData(data);
const groupOrder = [];
const groupedMap = {};
const sourceGroups = Array.isArray(data && data.time) ? data.time : [];
sourceGroups.forEach((groupKey) => {
const normalizedKey = String(groupKey || "").trim();
if (!normalizedKey || groupedMap[normalizedKey]) {
return;
}
groupedMap[normalizedKey] = {
key: normalizedKey,
label: normalizedKey,
items: [],
};
groupOrder.push(normalizedKey);
});
list.forEach((item, index) => {
const groupKey =
String(
pickFirstValue(item, ["time_key", "date_str", "date", "month"]) ||
groupOrder[0] ||
"最新记录",
).trim() || "最新记录";
if (!groupedMap[groupKey]) {
groupedMap[groupKey] = {
key: groupKey,
label: groupKey,
items: [],
};
groupOrder.push(groupKey);
}
groupedMap[groupKey].items.push(mapBalanceCenterItem(item, groupKey, index));
});
return groupOrder
.map((groupKey) => groupedMap[groupKey])
.filter((section) => section && section.items.length);
}
function normalizeVoucherTab(tabKey) {
const normalizedKey = String(tabKey || "").trim();
if (normalizedKey === "normal" || normalizedKey === "3") {
return "normal";
}
if (normalizedKey === "order" || normalizedKey === "6") {
return "order";
}
return "all";
}
function resolveVoucherTabFilters(tabKey) {
const normalizedKey = normalizeVoucherTab(tabKey);
if (normalizedKey === "normal") {
return {
tabKey: normalizedKey,
is_release: 1,
coin_type: 3,
};
}
if (normalizedKey === "order") {
return {
tabKey: normalizedKey,
is_release: 1,
coin_type: 6,
};
}
return {
tabKey: "all",
is_release: 0,
coin_type: "",
};
}
function mapVoucherCenterItem(item, groupKey, index) {
const isIncome = isPositiveFlowValue(
pickFirstValue(item, ["io_type", "pm", "direction"]),
);
const coinType = pickFirstValue(item, ["coin_type", "coinType"]);
const dcNum = pickFirstValue(item, ["dc_num", "dcNum"]);
let title =
pickFirstValue(item, ["title", "mark", "type_name"]) ||
(isIncome ? "抵用券收入" : "抵用券支出");
if (dcNum !== "" && String(coinType) === "6") {
title += "" + String(dcNum) + "代)";
}
return {
id: buildFlowRecordId(item, groupKey, index),
title: title,
amount:
(isIncome ? "+" : "-") +
formatTransferRecordNumber(
pickFirstValue(item, ["amount", "number", "num"]),
),
amountTone: isIncome ? "income" : "expense",
time: pickFirstValue(item, ["add_time", "time", "created_at"]) || "--",
};
}
function mapVoucherCenterSections(data) {
const list = normalizeListData(data);
const statistics = Array.isArray(data && data.statistics)
? data.statistics.slice().reverse()
: [];
const groupOrder = [];
const groupedMap = {};
statistics.forEach((item) => {
const groupKey = String(pickFirstValue(item, ["date_str", "month"]) || "").trim();
if (!groupKey || groupedMap[groupKey]) {
return;
}
groupedMap[groupKey] = {
key: groupKey,
label: resolveMonthLabel(groupKey),
income: formatTransferRecordNumber(
pickFirstValue(item, ["sum_io1", "income", "in_amount"]),
),
expense: formatTransferRecordNumber(
pickFirstValue(item, ["sum_io0", "expense", "out_amount"]),
),
items: [],
};
groupOrder.push(groupKey);
});
list.forEach((item, index) => {
const groupKey =
String(pickFirstValue(item, ["date_str", "month"]) || "").trim() ||
"latest";
if (!groupedMap[groupKey]) {
groupedMap[groupKey] = {
key: groupKey,
label: resolveMonthLabel(groupKey),
income: "0",
expense: "0",
items: [],
};
groupOrder.push(groupKey);
}
groupedMap[groupKey].items.push(mapVoucherCenterItem(item, groupKey, index));
});
return groupOrder
.map((groupKey) => groupedMap[groupKey])
.filter((section) => section && section.items.length);
}
function normalizeTransferTarget(data, fallbackId) { function normalizeTransferTarget(data, fallbackId) {
if (data && typeof data === "object" && !Array.isArray(data)) { if (data && typeof data === "object" && !Array.isArray(data)) {
const id = String(data.uid || data.id || fallbackId || "").trim(); const id = String(data.uid || data.id || fallbackId || "").trim();
@ -1427,6 +1698,18 @@ async function fetchHomeBalanceData(requestOptions) {
); );
} }
async function fetchExtractBankData(requestOptions) {
return fetchPayload(
createRequestOptions(
{
url: serviceConfig.ENDPOINTS.extractBank,
},
requestOptions,
),
"余额信息加载失败",
);
}
async function fetchBmtPowerRateData(requestOptions) { async function fetchBmtPowerRateData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions( createRequestOptions(
@ -1561,6 +1844,19 @@ async function fetchCoinIndexData(requestOptions) {
); );
} }
async function fetchCoinIndexSummaryData(query, requestOptions) {
return fetchPayload(
createRequestOptions(
{
url: serviceConfig.ENDPOINTS.coinIndex,
data: query || {},
},
requestOptions,
),
"抵用券信息加载失败",
);
}
async function fetchUserProfileData(requestOptions) { async function fetchUserProfileData(requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions( createRequestOptions(
@ -1653,6 +1949,41 @@ async function fetchTransferLedgerData(pagination, requestOptions) {
); );
} }
async function fetchSpreadCommissionListData(
recordType,
pagination,
requestOptions,
filters,
) {
const filterSource = filters && typeof filters === "object" ? filters : {};
return fetchPayload(
createRequestOptions(
{
url: serviceConfig.ENDPOINTS.spreadCommission + "/" + recordType,
data: buildPagingRequestData(
{
pm:
pickFirstValue(filterSource, ["pm", "direction", "flow"]) || "",
start_time:
pickFirstValue(filterSource, [
"start_time",
"startTime",
"start",
]) || "",
end_time:
pickFirstValue(filterSource, ["end_time", "endTime", "end"]) ||
"",
},
pagination,
),
},
requestOptions,
),
"余额记录加载失败",
);
}
async function fetchWalletFlowListData(flowType, pagination, requestOptions) { async function fetchWalletFlowListData(flowType, pagination, requestOptions) {
return fetchPayload( return fetchPayload(
createRequestOptions( createRequestOptions(
@ -1671,6 +2002,37 @@ async function fetchWalletFlowListData(flowType, pagination, requestOptions) {
); );
} }
async function fetchCoinReleaseListData(pagination, requestOptions, filters) {
const filterSource = filters && typeof filters === "object" ? filters : {};
return fetchPayload(
createRequestOptions(
{
url: serviceConfig.ENDPOINTS.coinList,
data: buildPagingRequestData(
{
is_release:
pickFirstValue(filterSource, [
"is_release",
"isRelease",
"release",
]) || 0,
coin_type:
pickFirstValue(filterSource, [
"coin_type",
"coinType",
"type",
]) || "",
},
pagination,
),
},
requestOptions,
),
"抵用券记录加载失败",
);
}
async function fetchRedeemRecordListData( async function fetchRedeemRecordListData(
redeemType, redeemType,
pagination, pagination,
@ -1726,9 +2088,20 @@ export async function fetchVoucherBrokerLinkData(requestOptions) {
throw createError("未获取到用户信息", data); throw createError("未获取到用户信息", data);
} }
const targetUrl =
serviceConfig.EXTERNAL_H5_HOST +
"/JXH5/pages/users/user_broker/index?user_id=" +
encodeURIComponent(String(userId)) +
"&total=" +
encodeURIComponent(balance === "" ? "0" : String(balance));
const targetInfo = appendTokenToTargetUrl(targetUrl);
return { return {
userId: String(userId), userId: String(userId),
balance: balance === "" ? "0" : String(balance), balance: balance === "" ? "0" : String(balance),
token: targetInfo.token,
targetUrl: targetInfo.targetUrl,
encodedTargetUrl: targetInfo.encodedTargetUrl,
}; };
} }
@ -1739,27 +2112,27 @@ export async function fetchCouponRedeemLinkData(requestOptions) {
"canRedeem", "canRedeem",
"can_redeem", "can_redeem",
]); ]);
const token = String(getCurrentWebviewToken() || "").trim(); const targetUrl =
let targetUrl = serviceConfig.EXTERNAL_H5_HOST +
serviceConfig.HTTP_REQUEST_URL +
"/MD/pages/redeemVoucher/index?canRedeem=" + "/MD/pages/redeemVoucher/index?canRedeem=" +
encodeURIComponent(canRedeem === "" ? "0" : String(canRedeem)); encodeURIComponent(canRedeem === "" ? "0" : String(canRedeem));
const targetInfo = appendTokenToTargetUrl(targetUrl);
if (token) {
targetUrl +=
(targetUrl.indexOf("?") > -1 ? "&" : "?") +
"token=" +
encodeURIComponent(token);
}
return { return {
canRedeem: canRedeem === "" ? "0" : String(canRedeem), canRedeem: canRedeem === "" ? "0" : String(canRedeem),
token: token, token: targetInfo.token,
targetUrl: targetUrl, targetUrl: targetInfo.targetUrl,
encodedTargetUrl: encodeURIComponent(targetUrl), encodedTargetUrl: targetInfo.encodedTargetUrl,
}; };
} }
export function fetchBalanceSpreadLinkData() {
return appendTokenToTargetUrl(
serviceConfig.EXTERNAL_H5_HOST +
"/JXH5/pages/users/user_spread_money/index?type=2",
);
}
export async function fetchPointsConvertHome( export async function fetchPointsConvertHome(
month, month,
paginationOrRequestOptions, paginationOrRequestOptions,
@ -2370,6 +2743,94 @@ export async function fetchLedgerDetail(
}); });
} }
export async function fetchBalanceCenterPage(
filters,
paginationOrRequestOptions,
requestOptions,
) {
const params = resolvePagingArguments(
paginationOrRequestOptions,
requestOptions,
);
const pagination = params.pagination;
const mergedRequestOptions = params.requestOptions;
const [summaryData, listData] = await Promise.all([
fetchExtractBankData(mergedRequestOptions),
fetchSpreadCommissionListData(
3,
pagination,
mergedRequestOptions,
filters,
),
]);
return {
summary: {
balance: formatTransferRecordNumber(
pickFirstValue(summaryData, ["brokerage_price", "balance"]),
),
totalExtract: formatTransferRecordNumber(
pickFirstValue(summaryData, [
"extractTotalPrice",
"extract_total_price",
"total_extract",
]),
),
frozen: formatTransferRecordNumber(
pickFirstValue(summaryData, [
"broken_commission",
"frozen_amount",
"frozen",
]),
),
},
sections: mapBalanceCenterSections(listData),
pagination: buildPaginationMeta(listData, pagination),
};
}
export async function fetchVoucherCenterPage(
tabKey,
paginationOrRequestOptions,
requestOptions,
) {
const params = resolvePagingArguments(
paginationOrRequestOptions,
requestOptions,
);
const pagination = params.pagination;
const mergedRequestOptions = params.requestOptions;
const tabFilters = resolveVoucherTabFilters(tabKey);
const [summaryData, listData] = await Promise.all([
fetchCoinIndexSummaryData(
{
sign: 1,
integral: 1,
all: 1,
},
mergedRequestOptions,
),
fetchCoinReleaseListData(pagination, mergedRequestOptions, tabFilters),
]);
return {
currentTab: tabFilters.tabKey,
summary: {
balance: formatTransferRecordNumber(
pickFirstValue(summaryData, ["balance", "coin", "total"]),
),
totalIn: formatTransferRecordNumber(
pickFirstValue(summaryData, ["total_in", "totalIn"]),
),
totalOut: formatTransferRecordNumber(
pickFirstValue(summaryData, ["total_out", "totalOut"]),
),
},
sections: mapVoucherCenterSections(listData),
pagination: buildPaginationMeta(listData, pagination),
};
}
export async function fetchWalletDetail(requestOptions) { export async function fetchWalletDetail(requestOptions) {
const data = await fetchWalletAddressData(requestOptions); const data = await fetchWalletAddressData(requestOptions);
return buildWalletPayload(data && data.address); return buildWalletPayload(data && data.address);

View File

@ -1,16 +1,21 @@
// const HOST_URL = "https://tpoint.agrimedia.cn"; const CURRENT_SERVER_URL =
const HOST_URL = window.location.protocol + "//" + window.location.host; window.location.protocol + "//" + window.location.host;
const EXTERNAL_H5_HOST = CURRENT_SERVER_URL;
const serviceConfig = { const serviceConfig = {
BASE_URL: HOST_URL, CURRENT_SERVER_URL: CURRENT_SERVER_URL,
HTTP_REQUEST_URL: HOST_URL, EXTERNAL_H5_HOST: EXTERNAL_H5_HOST,
BASE_URL: CURRENT_SERVER_URL,
HTTP_REQUEST_URL: CURRENT_SERVER_URL,
TIMEOUT: 10000, TIMEOUT: 10000,
WALLET_NAME: "海南农综交易所", WALLET_NAME: "海南农综交易所",
POINTS_CONVERT_INTERVAL: "0,20", POINTS_CONVERT_INTERVAL: "0,20",
ENDPOINTS: { ENDPOINTS: {
price: "/api/hn/getPrice", price: "/api/hn/getPrice",
homeBalance: "/api/hn/getAllBalance", homeBalance: "/api/hn/getAllBalance",
extractBank: "/api/extract/bank",
spreadCommission: "/api/spread/commission",
powerExchangeSubmit: "/api/hn/redeem/power", powerExchangeSubmit: "/api/hn/redeem/power",
bmtFlashExchangeSubmit: "/api/hn/redeem/justRedeem", bmtFlashExchangeSubmit: "/api/hn/redeem/justRedeem",
powerExchangeMuit: "/api/hn/redeem/getMuit", powerExchangeMuit: "/api/hn/redeem/getMuit",
@ -25,6 +30,7 @@ const serviceConfig = {
walletFlowList: "/api/hn/wallet_flow/getList", walletFlowList: "/api/hn/wallet_flow/getList",
redeemRecordList: "/api/hn/redeem/redeemList", redeemRecordList: "/api/hn/redeem/redeemList",
coinIndex: "/api/coin/index", coinIndex: "/api/coin/index",
coinList: "/api/coin/list",
userProfile: "/api/user", userProfile: "/api/user",
walletDetail: "/api/hn/wallet/getWalletAddress", walletDetail: "/api/hn/wallet/getWalletAddress",
walletSave: "/api/hn/wallet/saveAddress", walletSave: "/api/hn/wallet/saveAddress",

View File

@ -72,6 +72,20 @@
"backgroundColor": "#191E32" "backgroundColor": "#191E32"
} }
}, },
{
"path": "pages/assets/balance-center",
"style": {
"navigationStyle": "custom",
"backgroundColor": "#191E32"
}
},
{
"path": "pages/assets/voucher-center",
"style": {
"navigationStyle": "custom",
"backgroundColor": "#191E32"
}
},
{ {
"path": "pages/assets/wallet", "path": "pages/assets/wallet",
"style": { "style": {

View File

@ -0,0 +1,902 @@
<template>
<view class="asset-page balance-page" @click="closePanels">
<asset-page-shell title="余额" />
<view class="asset-scroll balance-scroll">
<view class="balance-header">
<view class="balance-header__card">
<image
class="balance-header__bg"
src="https://imgs.agrimedia.cn/shop/header-yongjin.png"
mode="aspectFill"
></image>
<view class="balance-header__content">
<view class="balance-header__summary">
<view class="balance-header__summary-item">
<text class="balance-header__label">余额</text>
<text class="balance-header__value asset-number-font">{{
summary.balance
}}</text>
</view>
<view class="balance-header__summary-item">
<text class="balance-header__label">冻结佣金</text>
<text class="balance-header__value asset-number-font">{{
summary.frozen
}}</text>
</view>
</view>
<image
class="balance-header__icon"
src="https://imgs.agrimedia.cn/shop/yongjin-icon.png"
mode="aspectFit"
></image>
</view>
</view>
</view>
<view class="balance-toolbar">
<view class="balance-toolbar__row">
<view class="balance-toolbar__item" @click.stop="openDatePopup">
<text class="balance-toolbar__text">{{ currentDateLabel }}</text>
<image
class="balance-toolbar__icon"
src="https://imgs.agrimedia.cn/shop/select-icon.png"
mode="widthFix"
></image>
</view>
<view class="balance-toolbar__item" @click.stop="openStatusPopup">
<text class="balance-toolbar__text">{{
statusOptions[pmIndex].label
}}</text>
<image
class="balance-toolbar__icon"
src="https://imgs.agrimedia.cn/shop/select-icon.png"
mode="widthFix"
></image>
</view>
</view>
</view>
<view class="balance-list">
<view v-if="sections.length" class="balance-records">
<view
v-for="section in sections"
:key="section.key"
class="balance-group"
>
<text class="balance-group__date">{{ section.label }}</text>
<view class="balance-group__items">
<view
v-for="item in section.items"
:key="item.id"
class="balance-item"
>
<view class="balance-item__head">
<view class="balance-item__title-row">
<text class="balance-item__title">{{ item.title }}</text>
<text
class="balance-item__amount asset-number-font"
:class="
item.amountTone === 'income'
? 'balance-item__amount--income'
: 'balance-item__amount--expense'
"
>
{{ item.amount }}
</text>
</view>
<view
v-for="row in item.detailRows"
:key="row.label + row.value"
class="balance-item__line"
>
<text class="balance-item__line-label">{{ row.label }}</text>
<text class="balance-item__line-value asset-number-font">{{
row.value
}}</text>
</view>
<view v-if="item.merchantName" class="balance-item__line">
<text class="balance-item__line-label"></text>
<text class="balance-item__line-value">{{
item.merchantName
}}</text>
</view>
<text
v-if="item.note"
class="balance-item__note"
:class="{
'balance-item__note--refund': item.noteTone === 'expense',
'balance-item__note--danger': item.noteTone === 'danger',
}"
>
{{ item.note }}
</text>
</view>
</view>
</view>
</view>
<view class="balance-loading">
<text class="balance-loading__text asset-number-font">{{
loadMoreText
}}</text>
</view>
</view>
<view v-else class="balance-empty">
<image
class="balance-empty__image"
src="https://imgs.agrimedia.cn/shop/no-datas.png"
mode="aspectFit"
></image>
<text class="balance-empty__text">暂无数据</text>
</view>
</view>
</view>
<view
v-if="datePopupVisible"
class="sheet-mask"
@click="datePopupVisible = false"
></view>
<view
v-if="datePopupVisible"
class="sheet-panel date-sheet"
@click.stop
>
<view class="date-sheet__title">
<text>交易日期</text>
<text class="date-sheet__close" @click="datePopupVisible = false">×</text>
</view>
<view class="date-sheet__block">
<text class="date-sheet__block-title">选择时间</text>
<view class="date-sheet__preset-row">
<view
v-for="(item, index) in timeRanges"
:key="item.key"
class="date-sheet__preset"
:class="{ 'is-active': clickIndex === index }"
@click="clickTime(index)"
>
{{ item.label }}
</view>
</view>
<view class="date-sheet__custom" @click="clickTime(4)">
<view
class="date-sheet__custom-tag"
:class="{ 'is-active': clickIndex === 4 }"
>
自定义时间
</view>
<text class="date-sheet__custom-desc">
最长可查询时间跨度一年的交易
</text>
</view>
</view>
<view v-if="clickIndex === 4" class="date-sheet__range">
<picker
class="date-sheet__picker"
mode="date"
:value="startValue"
@change="handleStartDateChange"
>
<view class="date-sheet__picker-inner">{{ startValue }}</view>
</picker>
<text class="date-sheet__range-separator"></text>
<picker
class="date-sheet__picker"
mode="date"
:value="endValue"
@change="handleEndDateChange"
>
<view class="date-sheet__picker-inner">{{ endValue }}</view>
</picker>
</view>
<view class="date-sheet__confirm" @click="confirmDateFilter">确定</view>
</view>
<view
v-if="statusPopupVisible"
class="sheet-mask"
@click="statusPopupVisible = false"
></view>
<view
v-if="statusPopupVisible"
class="sheet-panel status-sheet"
@click.stop
>
<view
v-for="(item, index) in statusOptions"
:key="item.value + item.label"
class="status-sheet__item"
:class="{ 'is-active': pmIndex === index }"
@click="confirmStatus(index)"
>
{{ item.label }}
</view>
</view>
</view>
</template>
<script>
import AssetPageShell from "../../components/asset-page-shell.vue";
import { fetchBalanceCenterPage } from "../../api/assets";
function padDateUnit(value) {
return value < 10 ? "0" + value : String(value);
}
function formatDate(date) {
return [
date.getFullYear(),
padDateUnit(date.getMonth() + 1),
padDateUnit(date.getDate()),
].join("-");
}
function createTodayDate() {
const date = new Date();
date.setHours(0, 0, 0, 0);
return date;
}
export default {
components: {
AssetPageShell,
},
data() {
const today = formatDate(createTodayDate());
return {
summary: {
balance: "0",
totalExtract: "0",
frozen: "0",
},
form: {
pm: "",
start_time: "",
end_time: "",
},
statusOptions: [
{ label: "全部", value: "" },
{ label: "佣金获得", value: "1" },
{ label: "佣金支出", value: "0" },
],
timeRanges: [
{ key: "today", label: "今天" },
{ key: "yesterday", label: "昨天" },
{ key: "week", label: "近7天" },
{ key: "month", label: "近30天" },
],
pmIndex: 0,
clickIndex: "",
startValue: today,
endValue: today,
datePopupVisible: false,
statusPopupVisible: false,
sections: [],
page: 1,
pageSize: 10,
hasMore: false,
loadingMore: false,
};
},
computed: {
currentDateLabel() {
if (this.clickIndex === "" || this.clickIndex === 4) {
return this.startValue + "~" + this.endValue;
}
return this.timeRanges[this.clickIndex]
? this.timeRanges[this.clickIndex].label
: this.startValue + "~" + this.endValue;
},
loadMoreText() {
if (this.loadingMore) {
return "加载中...";
}
if (this.hasMore) {
return "加载更多";
}
return "暂无更多";
},
},
onLoad() {
this.loadPage(true, 1);
},
onReachBottom() {
this.loadMore();
},
methods: {
closePanels() {
this.datePopupVisible = false;
this.statusPopupVisible = false;
},
openDatePopup() {
this.statusPopupVisible = false;
this.datePopupVisible = true;
},
openStatusPopup() {
this.datePopupVisible = false;
this.statusPopupVisible = true;
},
getRangeDates(index) {
const endDate = createTodayDate();
const startDate = new Date(endDate.getTime());
if (index === 0) {
return {
start: formatDate(startDate),
end: formatDate(endDate),
};
}
if (index === 1) {
startDate.setDate(startDate.getDate() - 1);
return {
start: formatDate(startDate),
end: formatDate(startDate),
};
}
startDate.setDate(startDate.getDate() - (index === 2 ? 6 : 29));
return {
start: formatDate(startDate),
end: formatDate(endDate),
};
},
clickTime(index) {
this.clickIndex = index;
if (index === 4) {
this.form.start_time = this.startValue;
this.form.end_time = this.endValue;
return;
}
const range = this.getRangeDates(index);
this.form.start_time = range.start;
this.form.end_time = range.end;
},
handleStartDateChange(event) {
const value = event && event.detail ? event.detail.value : "";
this.startValue = value || this.startValue;
this.form.start_time = this.startValue;
},
handleEndDateChange(event) {
const value = event && event.detail ? event.detail.value : "";
this.endValue = value || this.endValue;
this.form.end_time = this.endValue;
},
confirmDateFilter() {
if (this.clickIndex === 4) {
this.form.start_time = this.startValue;
this.form.end_time = this.endValue;
}
this.datePopupVisible = false;
this.reloadList();
},
confirmStatus(index) {
const target = this.statusOptions[index];
this.pmIndex = index;
this.form.pm = target ? target.value : "";
this.statusPopupVisible = false;
this.reloadList();
},
reloadList() {
this.sections = [];
this.page = 1;
this.hasMore = false;
this.loadingMore = false;
this.loadPage(true, 1);
},
mergeSections(currentSections, nextSections) {
const sectionMap = {};
const mergedSections = [];
const appendSection = (section) => {
if (!section || !section.key) {
return;
}
if (!sectionMap[section.key]) {
sectionMap[section.key] = {
key: section.key,
label: section.label,
items: [],
};
mergedSections.push(sectionMap[section.key]);
}
const targetSection = sectionMap[section.key];
const itemMap = {};
targetSection.items.forEach((item) => {
itemMap[item.id] = true;
});
(section.items || []).forEach((item) => {
if (!item || itemMap[item.id]) {
return;
}
itemMap[item.id] = true;
targetSection.items.push(item);
});
};
(currentSections || []).forEach(appendSection);
(nextSections || []).forEach(appendSection);
return mergedSections;
},
async loadPage(showLoading, targetPage) {
const page = Number(targetPage || 1);
const isLoadMore = page > 1;
if (isLoadMore && (this.loadingMore || !this.hasMore)) {
return;
}
if (isLoadMore) {
this.loadingMore = true;
}
try {
const requestOptions =
!isLoadMore && showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null;
const result = await fetchBalanceCenterPage(
{
pm: this.form.pm,
start_time: this.form.start_time,
end_time: this.form.end_time,
},
{
page: page,
pageSize: this.pageSize,
},
requestOptions,
);
const nextSections = Array.isArray(result.sections) ? result.sections : [];
this.summary = Object.assign({}, this.summary, result.summary || {});
this.sections = isLoadMore
? this.mergeSections(this.sections, nextSections)
: nextSections;
const pagination = result.pagination || {};
this.page = Number(pagination.page || page || 1);
this.hasMore = Boolean(pagination.hasMore);
} catch (error) {
uni.showToast({
title: error.message || (isLoadMore ? "加载更多失败" : "页面加载失败"),
icon: "none",
});
} finally {
if (isLoadMore) {
this.loadingMore = false;
}
}
},
loadMore() {
if (this.loadingMore || !this.hasMore) {
return;
}
this.loadPage(false, this.page + 1);
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.balance-page {
min-height: 100vh;
background: #f5f5f5;
}
.balance-scroll {
padding: 16rpx 12rpx calc(env(safe-area-inset-bottom) + 36rpx);
}
.balance-header {
margin-top: 4rpx;
}
.balance-header__card {
position: relative;
width: 100%;
height: 187rpx;
overflow: hidden;
border-radius: 24rpx;
}
.balance-header__bg {
width: 100%;
height: 187rpx;
}
.balance-header__content {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 28rpx;
}
.balance-header__summary {
display: flex;
align-items: center;
gap: 56rpx;
}
.balance-header__summary-item {
min-width: 0;
}
.balance-header__label {
display: block;
font-weight: 600;
font-size: 32rpx;
color: #ffffff;
text-align: center;
}
.balance-header__value {
display: block;
margin-top: 12rpx;
font-size: 42rpx;
font-weight: 800;
color: #ffffff;
line-height: 1;
}
.balance-header__icon {
width: 176rpx;
height: 160rpx;
flex-shrink: 0;
}
.balance-toolbar {
padding: 20rpx 10rpx 12rpx;
}
.balance-toolbar__row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.balance-toolbar__item {
display: flex;
align-items: center;
min-width: 0;
}
.balance-toolbar__text {
font-size: 24rpx;
color: #000;
}
.balance-toolbar__icon {
width: 30rpx;
height: 30rpx;
margin-left: 10rpx;
flex-shrink: 0;
}
.balance-list {
width: 100%;
}
.balance-group {
margin-bottom: 20rpx;
padding: 28rpx 24rpx;
border-radius: 22rpx;
background: #ffffff;
}
.balance-group__date {
display: block;
padding: 0;
font-weight: 500;
font-size: 30rpx;
color: #666666;
line-height: 36rpx;
}
.balance-group__items {
margin-top: 14rpx;
}
.balance-item + .balance-item {
margin-top: 18rpx;
padding-top: 18rpx;
border-top: 1px solid rgba(20, 40, 72, 0.08);
}
.balance-item__title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 20rpx;
width: 100%;
margin-bottom: 16rpx;
}
.balance-item__title {
flex: 1;
min-width: 0;
font-weight: 500;
font-size: 30rpx;
color: #000000;
line-height: 1.45;
}
.balance-item__amount {
flex-shrink: 0;
font-weight: 800;
font-size: 36rpx;
line-height: 1;
}
.balance-item__amount--income {
color: #fc2838;
}
.balance-item__amount--expense {
color: #07c261;
}
.balance-item__line {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
margin-bottom: 12rpx;
font-weight: 400;
font-size: 26rpx;
color: #666666;
line-height: 36rpx;
}
.balance-item__line-label {
min-width: 120rpx;
}
.balance-item__line-value {
flex: 1;
min-width: 0;
text-align: right;
word-break: break-all;
}
.balance-item__note {
display: block;
margin-top: 6rpx;
font-size: 26rpx;
line-height: 36rpx;
}
.balance-item__note--refund {
color: #07c261;
}
.balance-item__note--danger {
color: #ff7a45;
}
.balance-loading {
display: flex;
justify-content: center;
padding: 8rpx 0 0;
}
.balance-loading__text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.72);
}
.balance-empty {
display: flex;
flex-direction: column;
align-items: center;
padding: 180rpx 0 60rpx;
}
.balance-empty__image {
width: 328rpx;
height: 338rpx;
}
.balance-empty__text {
margin-top: 36rpx;
font-weight: 400;
font-size: 32rpx;
color: #999999;
}
.sheet-mask {
position: fixed;
inset: 0;
z-index: 80;
background: rgba(0, 0, 0, 0.42);
}
.sheet-panel {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 81;
background: #ffffff;
border-radius: 20rpx 20rpx 0 0;
}
.date-sheet {
padding: 20rpx 20rpx calc(env(safe-area-inset-bottom) + 30rpx);
}
.date-sheet__title {
position: relative;
text-align: center;
font-size: 32rpx;
font-weight: 500;
color: #000000;
}
.date-sheet__close {
position: absolute;
top: 4rpx;
right: 20rpx;
font-size: 42rpx;
line-height: 1;
}
.date-sheet__block {
margin-top: 26rpx;
}
.date-sheet__block-title {
display: block;
margin-bottom: 20rpx;
font-size: 28rpx;
color: #000000;
}
.date-sheet__preset-row {
display: flex;
justify-content: space-between;
gap: 12rpx;
}
.date-sheet__preset {
flex: 1;
height: 50rpx;
line-height: 50rpx;
text-align: center;
border-radius: 10rpx;
border: 2rpx solid #ed151b;
font-size: 22rpx;
color: #ed151b;
}
.date-sheet__preset.is-active {
background: #ed151b;
color: #ffffff;
}
.date-sheet__custom {
display: flex;
align-items: center;
margin-top: 20rpx;
margin-bottom: 30rpx;
color: #ed151b;
}
.date-sheet__custom-tag {
height: 50rpx;
padding: 0 20rpx;
line-height: 50rpx;
text-align: center;
border-radius: 10rpx;
border: 2rpx solid #ed151b;
font-size: 22rpx;
}
.date-sheet__custom-tag.is-active {
background: #ed151b;
color: #ffffff;
}
.date-sheet__custom-desc {
margin-left: 20rpx;
font-size: 22rpx;
color: #666666;
}
.date-sheet__range {
display: flex;
align-items: center;
justify-content: space-between;
}
.date-sheet__picker {
width: 260rpx;
height: 60rpx;
border: 2rpx solid #ed151b;
border-radius: 10rpx;
overflow: hidden;
}
.date-sheet__picker-inner {
width: 100%;
height: 60rpx;
line-height: 60rpx;
text-align: center;
font-size: 24rpx;
color: #000000;
}
.date-sheet__range-separator {
font-size: 24rpx;
color: #000000;
}
.date-sheet__confirm {
width: 100%;
height: 70rpx;
margin-top: 30rpx;
margin-bottom: 30rpx;
border-radius: 10rpx;
background: #ed151b;
line-height: 70rpx;
text-align: center;
font-size: 28rpx;
color: #ffffff;
}
.status-sheet {
padding: 16rpx 0 calc(env(safe-area-inset-bottom) + 20rpx);
}
.status-sheet__item {
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 28rpx;
color: #333333;
}
.status-sheet__item.is-active {
color: #ed151b;
font-weight: 600;
}
</style>

View File

@ -0,0 +1,725 @@
<template>
<view class="asset-page voucher-page" @click="closeTips">
<asset-page-shell title="抵用券" />
<view class="asset-scroll voucher-scroll">
<view class="voucher-header">
<view class="voucher-header__bg">
<image
class="voucher-header__bg-image"
src="https://imgs.agrimedia.cn/shop/d-bg.png"
mode="aspectFill"
></image>
<view class="voucher-header__content">
<view class="voucher-header__current">
<text class="voucher-header__current-label">当前抵用券</text>
<text class="voucher-header__current-value asset-number-font">{{
summary.balance
}}</text>
</view>
<view class="voucher-header__totals">
<view class="voucher-header__total-item">
<text class="voucher-header__total-value asset-number-font">{{
summary.totalIn
}}</text>
<view
class="voucher-header__total-text"
@click.stop="toggleTip('showTip1')"
>
<text>累计收入</text>
<image
class="voucher-header__help-icon"
src="https://imgs.agrimedia.cn/shop/%E6%84%9F%E5%8F%B9%E5%8F%B7.svg"
mode="widthFix"
></image>
</view>
<view
v-if="showTip1"
class="voucher-tooltip voucher-tooltip--left"
@click.stop
>
<view class="voucher-tooltip__row">
<text class="voucher-tooltip__text">
收入不包含消费退款扣回等扣除抵用券的场景
</text>
<view
class="voucher-tooltip__close"
@click.stop="showTip1 = false"
>
<image
src="https://imgs.agrimedia.cn/shop/line-close.png"
mode="aspectFit"
></image>
</view>
</view>
<view class="voucher-tooltip__arrow voucher-tooltip__arrow--left"></view>
</view>
</view>
<view class="voucher-header__total-item">
<text class="voucher-header__total-value asset-number-font">{{
summary.totalOut
}}</text>
<view
class="voucher-header__total-text"
@click.stop="toggleTip('showTip2')"
>
<text>累计支出</text>
<image
class="voucher-header__help-icon"
src="https://imgs.agrimedia.cn/shop/%E6%84%9F%E5%8F%B9%E5%8F%B7.svg"
mode="widthFix"
></image>
</view>
<view
v-if="showTip2"
class="voucher-tooltip voucher-tooltip--right"
@click.stop
>
<view class="voucher-tooltip__row">
<text class="voucher-tooltip__text">
支出包含消费退款扣回等扣除抵用券的场景
</text>
<view
class="voucher-tooltip__close"
@click.stop="showTip2 = false"
>
<image
src="https://imgs.agrimedia.cn/shop/line-close.png"
mode="aspectFit"
></image>
</view>
</view>
<view class="voucher-tooltip__arrow"></view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="voucher-wrapper">
<view class="voucher-nav">
<view class="voucher-tabs">
<view
v-for="item in tabOptions"
:key="item.key"
class="voucher-tabs__item"
:class="{ 'is-active': activeTab === item.key }"
@click.stop="changeTab(item.key)"
>
<text class="voucher-tabs__text">{{ item.label }}</text>
<image
v-if="activeTab === item.key"
class="voucher-tabs__line"
src="https://imgs.agrimedia.cn/shop/active.png"
mode="widthFix"
></image>
</view>
</view>
</view>
<view class="voucher-list" :class="{ 'voucher-list--filled': sections.length }">
<view
v-for="section in sections"
:key="section.key"
class="voucher-card"
>
<view class="voucher-card__head">
<view class="voucher-card__month">
<text class="voucher-card__month-text asset-number-font">{{
section.label
}}</text>
</view>
<view class="voucher-card__totals">
<text class="voucher-card__total-text">
收入
<text class="voucher-card__total-income asset-number-font">{{
section.income
}}</text>
</text>
<text class="voucher-card__total-text">
支出
<text class="voucher-card__total-expense asset-number-font">
-{{ section.expense }}
</text>
</text>
</view>
</view>
<view
v-for="item in section.items"
:key="item.id"
class="voucher-row"
>
<view class="voucher-row__left">
<view class="voucher-row__icon">
<image
src="https://imgs.agrimedia.cn/shop/sifang-icon.png"
mode="aspectFit"
></image>
</view>
<view class="voucher-row__body">
<text class="voucher-row__title">{{ item.title }}</text>
<text class="voucher-row__time asset-number-font">{{
item.time
}}</text>
</view>
</view>
<text
class="voucher-row__amount asset-number-font"
:class="
item.amountTone === 'income'
? 'voucher-row__amount--income'
: 'voucher-row__amount--expense'
"
>
{{ item.amount }}
</text>
</view>
</view>
<view v-if="sections.length" class="voucher-loading">
<text class="voucher-loading__text asset-number-font">{{
loadMoreText
}}</text>
</view>
<view v-else class="voucher-empty">
<text class="voucher-empty__title">暂无记录哦</text>
<text class="voucher-empty__desc">
当前分类下还没有抵用券流水数据
</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import AssetPageShell from "../../components/asset-page-shell.vue";
import { fetchVoucherCenterPage } from "../../api/assets";
export default {
components: {
AssetPageShell,
},
data() {
return {
summary: {
balance: "0",
totalIn: "0",
totalOut: "0",
},
tabOptions: [
{ key: "all", label: "全部" },
{ key: "normal", label: "常规补贴" },
{ key: "order", label: "单单补贴" },
],
activeTab: "all",
sections: [],
page: 1,
pageSize: 10,
hasMore: false,
loadingMore: false,
showTip1: false,
showTip2: false,
};
},
computed: {
loadMoreText() {
if (this.loadingMore) {
return "加载中...";
}
if (this.hasMore) {
return "加载更多";
}
return "暂无更多";
},
},
onLoad() {
this.loadPage(true, 1);
},
onReachBottom() {
this.loadMore();
},
methods: {
closeTips() {
this.showTip1 = false;
this.showTip2 = false;
},
toggleTip(key) {
if (this[key]) {
this[key] = false;
return;
}
this.showTip1 = false;
this.showTip2 = false;
this[key] = true;
},
mergeSections(currentSections, nextSections) {
const sectionMap = {};
const mergedSections = [];
const appendSection = (section) => {
if (!section || !section.key) {
return;
}
if (!sectionMap[section.key]) {
sectionMap[section.key] = {
key: section.key,
label: section.label,
income: section.income,
expense: section.expense,
items: [],
};
mergedSections.push(sectionMap[section.key]);
}
const targetSection = sectionMap[section.key];
targetSection.income = section.income || targetSection.income;
targetSection.expense = section.expense || targetSection.expense;
const itemMap = {};
targetSection.items.forEach((item) => {
itemMap[item.id] = true;
});
(section.items || []).forEach((item) => {
if (!item || itemMap[item.id]) {
return;
}
itemMap[item.id] = true;
targetSection.items.push(item);
});
};
(currentSections || []).forEach(appendSection);
(nextSections || []).forEach(appendSection);
return mergedSections;
},
changeTab(key) {
if (this.activeTab === key) {
return;
}
this.activeTab = key;
this.closeTips();
this.loadPage(true, 1);
},
async loadPage(showLoading, targetPage) {
const page = Number(targetPage || 1);
const isLoadMore = page > 1;
if (isLoadMore && (this.loadingMore || !this.hasMore)) {
return;
}
if (isLoadMore) {
this.loadingMore = true;
}
try {
const requestOptions =
!isLoadMore && showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null;
const result = await fetchVoucherCenterPage(
this.activeTab,
{
page: page,
pageSize: this.pageSize,
},
requestOptions,
);
const nextSections = Array.isArray(result.sections) ? result.sections : [];
this.summary = Object.assign({}, this.summary, result.summary || {});
this.sections = isLoadMore
? this.mergeSections(this.sections, nextSections)
: nextSections;
const pagination = result.pagination || {};
this.page = Number(pagination.page || page || 1);
this.hasMore = Boolean(pagination.hasMore);
} catch (error) {
uni.showToast({
title: error.message || (isLoadMore ? "加载更多失败" : "页面加载失败"),
icon: "none",
});
} finally {
if (isLoadMore) {
this.loadingMore = false;
}
}
},
loadMore() {
if (this.loadingMore || !this.hasMore) {
return;
}
this.loadPage(false, this.page + 1);
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.voucher-page {
min-height: 100vh;
background: #f5f5f5;
}
.voucher-scroll {
padding: 16rpx 12rpx calc(env(safe-area-inset-bottom) + 36rpx);
}
.voucher-header {
margin-top: 6rpx;
}
.voucher-header__bg {
position: relative;
width: 100%;
height: 206rpx;
overflow: visible;
}
.voucher-header__bg-image {
width: 100%;
height: 206rpx;
border-radius: 24rpx;
}
.voucher-header__content {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.voucher-header__current {
position: absolute;
bottom: 130rpx;
display: flex;
align-items: baseline;
justify-content: center;
width: 618rpx;
color: #ffffff;
}
.voucher-header__current-label {
font-size: 36rpx;
font-weight: 500;
}
.voucher-header__current-value {
margin-left: 8rpx;
font-size: 48rpx;
font-weight: 700;
}
.voucher-header__totals {
position: absolute;
top: 100rpx;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
color: #ffffff;
}
.voucher-header__total-item {
position: relative;
text-align: center;
}
.voucher-header__total-value {
display: block;
font-size: 42rpx;
font-weight: 600;
line-height: 1.2;
}
.voucher-header__total-text {
display: flex;
align-items: center;
justify-content: center;
margin-top: 6rpx;
font-size: 28rpx;
}
.voucher-header__help-icon {
width: 30rpx;
margin-left: 10rpx;
}
.voucher-tooltip {
position: absolute;
top: calc(100% + 14rpx);
width: 360rpx;
max-width: calc(100vw - 48rpx);
padding: 16rpx 18rpx;
border-radius: 12rpx;
background: #333333;
color: #ffffff;
z-index: 30;
text-align: left;
box-shadow: 0 12rpx 24rpx rgba(0, 0, 0, 0.18);
}
.voucher-tooltip--left {
left: 0;
right: auto;
}
.voucher-tooltip--right {
right: 0;
left: auto;
}
.voucher-tooltip__row {
display: flex;
align-items: flex-start;
}
.voucher-tooltip__text {
flex: 1;
min-width: 0;
font-size: 24rpx;
line-height: 1.6;
}
.voucher-tooltip__close {
width: 44rpx;
height: 44rpx;
margin-left: 14rpx;
flex-shrink: 0;
}
.voucher-tooltip__close image {
width: 44rpx;
height: 44rpx;
}
.voucher-tooltip__arrow {
position: absolute;
top: -14rpx;
right: 60rpx;
width: 0;
height: 0;
border-left: 12rpx solid transparent;
border-right: 12rpx solid transparent;
border-bottom: 16rpx solid #333333;
}
.voucher-tooltip__arrow--left {
left: 80rpx;
right: auto;
}
.voucher-wrapper {
margin-top: 20rpx;
}
.voucher-nav {
display: flex;
justify-content: center;
}
.voucher-tabs {
display: flex;
width: 100%;
padding: 0 8rpx;
}
.voucher-tabs__item {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 0;
padding: 8rpx 0 18rpx;
}
.voucher-tabs__text {
font-size: 30rpx;
color: #000;
}
.voucher-tabs__item.is-active .voucher-tabs__text {
color: #ec0208;
font-weight: 500;
}
.voucher-tabs__line {
position: absolute;
bottom: 0;
width: 60rpx;
height: 16rpx;
}
.voucher-list {
width: 100%;
margin-top: 20rpx;
border-radius: 20rpx;
}
.voucher-card {
width: 100%;
margin-bottom: 20rpx;
padding: 34rpx 28rpx 20rpx;
border-radius: 22rpx;
background: #ffffff;
overflow: hidden;
}
.voucher-card__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 20rpx;
gap: 20rpx;
}
.voucher-card__month-text {
font-size: 32rpx;
line-height: 44rpx;
color: #000000;
}
.voucher-card__totals {
flex-shrink: 0;
font-size: 28rpx;
line-height: 40rpx;
color: #000000;
}
.voucher-card__total-text + .voucher-card__total-text {
margin-top: 6rpx;
}
.voucher-card__total-income {
margin-left: 8rpx;
color: #fc2838;
}
.voucher-card__total-expense {
margin-left: 8rpx;
color: #40ae36;
}
.voucher-row {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 115rpx;
padding: 10rpx 0;
color: #999999;
}
.voucher-row + .voucher-row {
border-top: 1px solid rgba(0, 0, 0, 0.04);
}
.voucher-row__left {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
margin-right: 20rpx;
}
.voucher-row__icon {
width: 48rpx;
height: 48rpx;
margin-right: 30rpx;
flex-shrink: 0;
}
.voucher-row__icon image {
width: 48rpx;
height: 48rpx;
}
.voucher-row__body {
flex: 1;
min-width: 0;
}
.voucher-row__title {
display: block;
font-size: 26rpx;
color: #000000;
line-height: 1.5;
}
.voucher-row__time {
display: block;
margin-top: 12rpx;
font-size: 24rpx;
color: #999999;
}
.voucher-row__amount {
font-size: 36rpx;
font-weight: 400;
line-height: 1;
}
.voucher-row__amount--income {
color: #e93323;
}
.voucher-row__amount--expense {
color: #16ac57;
}
.voucher-loading {
display: flex;
justify-content: center;
padding: 10rpx 0 0;
}
.voucher-loading__text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.72);
}
.voucher-empty {
padding: 72rpx 28rpx 120rpx;
text-align: center;
}
.voucher-empty__title {
display: block;
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
}
.voucher-empty__desc {
display: block;
margin-top: 16rpx;
font-size: 24rpx;
line-height: 1.7;
color: rgba(255, 255, 255, 0.68);
}
</style>

View File

@ -7,7 +7,7 @@
<view class="home-wallet-btn__icon"> <view class="home-wallet-btn__icon">
<image src="https://imgs.agrimedia.cn/bm-bmt/qianbao.png" mode="widthFix"></image> <image src="https://imgs.agrimedia.cn/bm-bmt/qianbao.png" mode="widthFix"></image>
</view> </view>
<text class="home-wallet-btn__text">钱包1</text> <text class="home-wallet-btn__text">钱包</text>
</view> </view>
</view> </view>
@ -128,9 +128,7 @@
import { import {
fetchAssetHome, fetchAssetHome,
fetchCouponRedeemLinkData, fetchCouponRedeemLinkData,
fetchVoucherBrokerLinkData,
} from "../../api/assets"; } from "../../api/assets";
import serviceConfig from "../../config/service";
export default { export default {
data() { data() {
@ -139,7 +137,6 @@ export default {
hasLoadedOnce: false, hasLoadedOnce: false,
isFetchingHome: false, isFetchingHome: false,
isOpeningCouponRedeem: false, isOpeningCouponRedeem: false,
isOpeningVoucherBroker: false,
overview: { overview: {
title: "数字资产", title: "数字资产",
heroTitle: "BM数字资产管理", heroTitle: "BM数字资产管理",
@ -265,9 +262,7 @@ export default {
}, },
openQuickAsset(item) { openQuickAsset(item) {
if (item && item.key === "balance") { if (item && item.key === "balance") {
this.navigateToExternalUrl( this.openBalanceSpread();
serviceConfig.HTTP_REQUEST_URL + "/JXH5/pages/users/user_spread_money/index?type=2",
);
return; return;
} }
@ -323,32 +318,9 @@ export default {
}); });
}, },
async openVoucherBroker() { async openVoucherBroker() {
if (this.isOpeningVoucherBroker) { uni.navigateTo({
return; url: "/pages/assets/voucher-center",
}
this.isOpeningVoucherBroker = true;
try {
const result = await fetchVoucherBrokerLinkData({
showLoading: true,
loadingText: "加载中",
}); });
const targetUrl =
serviceConfig.HTTP_REQUEST_URL +
"/JXH5/pages/users/user_broker/index?user_id=" +
encodeURIComponent(result.userId) +
"&total=" +
encodeURIComponent(result.balance);
this.navigateToExternalUrl(targetUrl);
} catch (error) {
uni.showToast({
title: error.message || "跳转失败",
icon: "none",
});
} finally {
this.isOpeningVoucherBroker = false;
}
}, },
async openCouponRedeem() { async openCouponRedeem() {
if (this.isOpeningCouponRedeem) { if (this.isOpeningCouponRedeem) {
@ -373,6 +345,11 @@ export default {
this.isOpeningCouponRedeem = false; this.isOpeningCouponRedeem = false;
} }
}, },
openBalanceSpread() {
uni.navigateTo({
url: "/pages/assets/balance-center",
});
},
openWallet() { openWallet() {
uni.navigateTo({ uni.navigateTo({
url: "/pages/assets/wallet", url: "/pages/assets/wallet",
@ -433,7 +410,6 @@ export default {
.home-wallet-btn__text { .home-wallet-btn__text {
font-size: 28rpx; font-size: 28rpx;
color: rgba(229, 235, 255, 0.82); color: rgba(229, 235, 255, 0.82);
padding-top: 10rpx;
} }
.home-scroll { .home-scroll {

View File

@ -1,2 +1,2 @@
<!DOCTYPE html><html lang=zh-CN><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><title>白马交易所</title><script>var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)')) <!DOCTYPE html><html lang=zh-CN><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><title>白马交易所</title><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><link rel=stylesheet href=/bmt/static/index.883130ca.css></head><body><noscript><strong>Please enable JavaScript to continue.</strong></noscript><div id=app></div><script src=/bmt/static/js/chunk-vendors.a58c62f3.js></script><script src=/bmt/static/js/index.59d49159.js></script></body></html> 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><link rel=stylesheet href=/bmt/static/index.883130ca.css></head><body><noscript><strong>Please enable JavaScript to continue.</strong></noscript><div id=app></div><script src=/bmt/static/js/chunk-vendors.a58c62f3.js></script><script src=/bmt/static/js/index.6b0ea112.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var __UniViewStartTime__ = Date.now();
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>View</title>
<link rel="stylesheet" href="view.css" />
</head>
<body>
<div id="app"></div>
<script src="__uniappes6.js"></script>
<script src="view.umd.min.js"></script>
<script src="app-view.js"></script>
</body>
</html>

View File

@ -0,0 +1,8 @@
var isReady=false;var onReadyCallbacks=[];
var isServiceReady=false;var onServiceReadyCallbacks=[];
var __uniConfig = {"pages":["pages/index/index","pages/assets/transfer","pages/assets/power-exchange","pages/assets/bmt-exchange","pages/assets/bmt-flash","pages/assets/withdraw","pages/assets/points-convert","pages/assets/points-convert-list","pages/assets/points-convert-detail","pages/assets/ledger","pages/assets/wallet","pages/assets/wallet-form","pages/assets/bmtr"],"window":{"navigationBarTextStyle":"white","navigationBarTitleText":"数字资产","navigationBarBackgroundColor":"#191E32","backgroundColor":"#191E32"},"darkmode":false,"nvueCompiler":"uni-app","nvueStyleCompiler":"uni-app","renderer":"auto","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":false},"appname":"白马交易所","compilerVersion":"4.76","entryPagePath":"pages/index/index","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000}};
var __uniRoutes = [{"path":"/pages/index/index","meta":{"isQuit":true},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/transfer","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/power-exchange","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/bmt-exchange","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/bmt-flash","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/withdraw","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/points-convert","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32","enablePullDownRefresh":true}},{"path":"/pages/assets/points-convert-list","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/points-convert-detail","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/ledger","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/wallet","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/wallet-form","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}},{"path":"/pages/assets/bmtr","meta":{},"window":{"navigationStyle":"custom","backgroundColor":"#191E32"}}];
__uniConfig.onReady=function(callback){if(__uniConfig.ready){callback()}else{onReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"ready",{get:function(){return isReady},set:function(val){isReady=val;if(!isReady){return}const callbacks=onReadyCallbacks.slice(0);onReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
__uniConfig.onServiceReady=function(callback){if(__uniConfig.serviceReady){callback()}else{onServiceReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"serviceReady",{get:function(){return isServiceReady},set:function(val){isServiceReady=val;if(!isServiceReady){return}const callbacks=onServiceReadyCallbacks.slice(0);onServiceReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
service.register("uni-app-config",{create(a,b,c){if(!__uniConfig.viewport){var d=b.weex.config.env.scale,e=b.weex.config.env.deviceWidth,f=Math.ceil(e/d);Object.assign(__uniConfig,{viewport:f,defaultFontSize:Math.round(f/20)})}return{instance:{__uniConfig:__uniConfig,__uniRoutes:__uniRoutes,global:void 0,window:void 0,document:void 0,frames:void 0,self:void 0,location:void 0,navigator:void 0,localStorage:void 0,history:void 0,Caches:void 0,screen:void 0,alert:void 0,confirm:void 0,prompt:void 0,fetch:void 0,XMLHttpRequest:void 0,WebSocket:void 0,webkit:void 0,print:void 0}}}});

View File

@ -0,0 +1,154 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0];
/******/ var moreModules = data[1];
/******/ var executeModules = data[2];
/******/
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ // add entry modules from loaded chunk to deferred list
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ // run deferred modules when all chunks ready
/******/ return checkDeferredModules();
/******/ };
/******/ function checkDeferredModules() {
/******/ var result;
/******/ for(var i = 0; i < deferredModules.length; i++) {
/******/ var deferredModule = deferredModules[i];
/******/ var fulfilled = true;
/******/ for(var j = 1; j < deferredModule.length; j++) {
/******/ var depId = deferredModule[j];
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
/******/ }
/******/ if(fulfilled) {
/******/ deferredModules.splice(i--, 1);
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ }
/******/ }
/******/
/******/ return result;
/******/ }
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // Promise = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "app-config": 0
/******/ };
/******/
/******/ var deferredModules = [];
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/";
/******/
/******/ var jsonpArray = this["webpackJsonp"] = this["webpackJsonp"] || [];
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ jsonpArray.push = webpackJsonpCallback;
/******/ jsonpArray = jsonpArray.slice();
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ // run deferred modules from other chunks
/******/ checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ([]);

9448
unpackage/dist/dev/app-plus/app-service.js vendored Normal file

File diff suppressed because one or more lines are too long

9006
unpackage/dist/dev/app-plus/app-view.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__3EC3CC8","name":"白马交易所","version":{"name":"1.0.0","code":"100"},"description":"","launch_path":"__uniappview.html","developer":{"name":"","email":"","url":""},"permissions":{"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"target":"id:1","autoclose":true,"waiting":true,"delay":0},"popGesture":"close","launchwebview":{"render":"always","id":"1","kernel":"WKWebview"},"statusbar":{"immersed":"supportedDevice","style":"light","background":"#191E32"},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"distribute":{"google":{"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\"/>"]},"apple":{},"plugins":{"audio":{"mp3":{"description":"Android平台录音支持MP3格式文件"}}}},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"uni-app":{"compilerVersion":"4.76","control":"uni-v3","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal"},"launch_path":"__uniappview.html"}}

Binary file not shown.

1
unpackage/dist/dev/app-plus/view.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long