chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<view class="credit-records-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<custom-nav-bar :title="t('Mobile.CreditRecords.pageTitle')">
|
||||
<template v-slot:left>
|
||||
<view class="header-icon-box" @tap="jumpNavigateBack">
|
||||
<text class="iconfont icon-a-Chevronleft"></text>
|
||||
</view>
|
||||
</template>
|
||||
</custom-nav-bar>
|
||||
|
||||
<!-- 分类筛选横向滑动 Tab 栏 -->
|
||||
<scroll-view
|
||||
direction="horizontal"
|
||||
class="filter-tabs-bar"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view
|
||||
v-for="tab in filterTabs"
|
||||
:key="tab.value"
|
||||
class="filter-tab-item"
|
||||
:class="{ active: currentType === tab.value }"
|
||||
@tap="handleSelectType(tab.value)"
|
||||
>
|
||||
<text class="tab-label" :class="{ active: currentType === tab.value }">
|
||||
{{ t(tab.label) }}
|
||||
</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 列表展示区域,使用 scroll-view 支持下拉刷新和上拉触底加载 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="list-scroll-view"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="handleRefresh"
|
||||
@scrolltolower="handleLoadMore"
|
||||
>
|
||||
<view v-if="list.length > 0" class="list-content">
|
||||
<view v-for="item in list" :key="item.id" class="record-card">
|
||||
<!-- 左侧流水描述、时间以及增加/扣减标签 -->
|
||||
<view class="card-left">
|
||||
<text class="record-name">
|
||||
{{
|
||||
item.remark != null && item.remark !== ""
|
||||
? item.remark
|
||||
: item.creditTypeName
|
||||
}}
|
||||
</text>
|
||||
<text class="record-time">
|
||||
{{ formatDate(item.created, "YYYY-MM-DD HH:mm:ss") }}
|
||||
</text>
|
||||
<view class="badge-container">
|
||||
<view
|
||||
class="record-badge"
|
||||
:class="
|
||||
item.operationType === 1 ? 'badge-increase' : 'badge-decrease'
|
||||
"
|
||||
>
|
||||
<text
|
||||
class="badge-text"
|
||||
:class="
|
||||
item.operationType === 1 ? 'text-increase' : 'text-decrease'
|
||||
"
|
||||
>
|
||||
{{
|
||||
item.operationType === 1
|
||||
? t("Mobile.CreditRecords.badgeIncrease")
|
||||
: t("Mobile.CreditRecords.badgeDecrease")
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧变化额度与变动后积分余额 -->
|
||||
<view class="card-right">
|
||||
<text
|
||||
class="record-amount"
|
||||
:class="{
|
||||
'amount-increase': item.operationType === 1,
|
||||
'amount-decrease': item.operationType === 2,
|
||||
}"
|
||||
>
|
||||
{{ item.operationType === 1 ? "+" : "-"
|
||||
}}{{ formatNumber(item.amount, 2) }}
|
||||
</text>
|
||||
<text class="record-balance">
|
||||
{{
|
||||
t("Mobile.CreditRecords.remaining") +
|
||||
" " +
|
||||
formatNumber(item.afterAmount, 2)
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 触底加载更多 Loading 提示 -->
|
||||
<view v-if="loading && !refreshing" class="loading-state">
|
||||
<text class="loading-text">{{ t("Mobile.Page.loadingMore") }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 已加载完毕提示 -->
|
||||
<view v-if="!hasMore && list.length > 0" class="no-more-state">
|
||||
<text class="no-more-text">{{ t("Mobile.Common.noMoreData") }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空数据状态 -->
|
||||
<view v-else-if="!loading" class="empty-wrapper">
|
||||
<empty-state text="Mobile.CreditRecords.empty" />
|
||||
</view>
|
||||
|
||||
<!-- 首次加载的 loading -->
|
||||
<view v-else class="loading-state">
|
||||
<text class="loading-text">{{ t("Mobile.Page.loadingMore") }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import type { CreditRecordInfo } from "@/subpackages/types/interfaces/subscription";
|
||||
import { SUCCESS_CODE } from "@/constants/codes.constants.uts";
|
||||
import { apiGetCreditFlows } from "@/subpackages/servers/subscription";
|
||||
import { jumpNavigateBack } from "@/utils/common";
|
||||
import { formatDate, formatNumber } from "@/utils/system";
|
||||
import { useI18n } from "@/utils/i18n";
|
||||
import EmptyState from "@/components/empty-state/empty-state.uvue";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 筛选 Tab 配置词条
|
||||
interface TabOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const filterTabs: TabOption[] = [
|
||||
{ label: "Mobile.Common.all", value: "" },
|
||||
{ label: "Mobile.CreditRecords.typeSubscription", value: "SUBSCRIPTION" },
|
||||
{ label: "Mobile.CreditRecords.typePurchase", value: "PURCHASE" },
|
||||
{ label: "Mobile.CreditRecords.typeActivity", value: "ACTIVITY" },
|
||||
{ label: "Mobile.CreditRecords.typeManual", value: "MANUAL" },
|
||||
{ label: "Mobile.CreditRecords.typeModelCall", value: "MODEL_CALL" },
|
||||
{ label: "Mobile.CreditRecords.typeAgentCall", value: "AGENT_CALL" },
|
||||
{ label: "Mobile.CreditRecords.typeToolCall", value: "TOOL_CALL" },
|
||||
{ label: "Mobile.CreditRecords.typeManualDeduct", value: "MANUAL_DEDUCT" },
|
||||
];
|
||||
|
||||
// 状态属性定义
|
||||
const list = ref<CreditRecordInfo[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const refreshing = ref<boolean>(false);
|
||||
const currentType = ref<string>("");
|
||||
const lastId = ref<number | null>(null);
|
||||
const hasMore = ref<boolean>(true);
|
||||
|
||||
// 获取积分明细数据
|
||||
const fetchPageData = async (isFirst: boolean) => {
|
||||
if (loading.value) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const params: Record<string, any> = {
|
||||
pageSize: 30,
|
||||
};
|
||||
|
||||
if (currentType.value !== "") {
|
||||
params["creditType"] = currentType.value;
|
||||
}
|
||||
|
||||
if (!isFirst && lastId.value != null) {
|
||||
params["lastId"] = lastId.value;
|
||||
}
|
||||
|
||||
const res = await apiGetCreditFlows(params);
|
||||
if (res.code === SUCCESS_CODE && res.data != null) {
|
||||
const newData = res.data!;
|
||||
if (isFirst) {
|
||||
list.value = newData;
|
||||
} else {
|
||||
list.value = [...list.value, ...newData];
|
||||
}
|
||||
|
||||
if (newData.length > 0) {
|
||||
lastId.value = newData[newData.length - 1].id;
|
||||
}
|
||||
|
||||
hasMore.value = newData.length === 30;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("加载积分明细异常:", e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
refreshing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 下拉刷新事件
|
||||
const handleRefresh = () => {
|
||||
refreshing.value = true;
|
||||
lastId.value = null;
|
||||
hasMore.value = true;
|
||||
fetchPageData(true);
|
||||
};
|
||||
|
||||
// 上拉触底加载
|
||||
const handleLoadMore = () => {
|
||||
if (!hasMore.value || loading.value) return;
|
||||
fetchPageData(false);
|
||||
};
|
||||
|
||||
// 切换筛选类型
|
||||
const handleSelectType = (value: string) => {
|
||||
if (currentType.value === value) return;
|
||||
currentType.value = value;
|
||||
lastId.value = null;
|
||||
hasMore.value = true;
|
||||
list.value = [];
|
||||
fetchPageData(true);
|
||||
};
|
||||
|
||||
// 页面生命周期载入
|
||||
onLoad(() => {
|
||||
fetchPageData(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
196
qiming-mobile/subpackages/pages/credit-records/styles/index.scss
Normal file
196
qiming-mobile/subpackages/pages/credit-records/styles/index.scss
Normal file
@@ -0,0 +1,196 @@
|
||||
.credit-records-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f8f9fa;
|
||||
overflow: hidden;
|
||||
|
||||
.header-icon-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
|
||||
.iconfont {
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-tabs-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: #ffffff;
|
||||
padding: 20rpx 0rpx;
|
||||
border-bottom: 1rpx solid #e2e8f0;
|
||||
width: 100%;
|
||||
|
||||
.filter-tab-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12rpx 28rpx;
|
||||
background: #f1f5f9;
|
||||
border-radius: 100rpx;
|
||||
border: 1rpx solid transparent;
|
||||
margin-left: 16rpx;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgba(81, 71, 255, 0.08);
|
||||
border-color: rgba(81, 71, 255, 0.2);
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
font-size: 24rpx;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
|
||||
&.active {
|
||||
color: #5147ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-scroll-view {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
margin: 24rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border: 1rpx solid #e2e8f0;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.015);
|
||||
}
|
||||
|
||||
.record-card {
|
||||
background: #ffffff;
|
||||
padding: 32rpx 32rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1rpx solid #f1f5f9;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 20rpx;
|
||||
|
||||
.record-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
margin-bottom: 8rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.badge-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.record-badge {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
&.badge-increase {
|
||||
background-color: rgba(16, 185, 129, 0.08);
|
||||
}
|
||||
|
||||
&.badge-decrease {
|
||||
background-color: rgba(239, 68, 68, 0.08);
|
||||
}
|
||||
|
||||
.badge-text {
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&.text-increase {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
&.text-decrease {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
|
||||
.record-amount {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8rpx;
|
||||
line-height: 1.2;
|
||||
|
||||
&.amount-increase {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
&.amount-decrease {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
.record-balance {
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-state, .no-more-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32rpx 0;
|
||||
|
||||
.loading-text, .no-more-text {
|
||||
font-size: 24rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-wrapper {
|
||||
padding-top: 160rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user