chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,283 @@
<template>
<view class="subscribed-credits">
<view v-if="loading" class="loading-state">
<text class="loading-text">...</text>
</view>
<view v-else-if="list.length === 0" class="empty-wrapper">
<empty-state text="Mobile.MySubscriptions.empty" />
</view>
<view v-else class="credit-list">
<view v-for="item in list" :key="item.id" class="credit-card">
<!-- 头部 -->
<view class="credit-header">
<text class="credit-name">{{ item.remark || item.batchNo }}</text>
<text class="credit-date">{{
formatDate(item.created || "", "YYYY-MM-DD")
}}</text>
</view>
<!-- 数据四宫格 -->
<view class="credit-grid">
<view class="grid-item">
<text class="grid-label">{{
t("Mobile.MySubscriptions.totalCredits")
}}</text>
<text class="grid-value"
>+{{ formatNumber(item.totalAmount, 0) }}</text
>
</view>
<view class="grid-item">
<text class="grid-label">{{
t("Mobile.MySubscriptions.usedAmount")
}}</text>
<text class="grid-value">{{
formatNumber(item.usedAmount, 0)
}}</text>
</view>
<view class="grid-item">
<text class="grid-label">{{
t("Mobile.MySubscriptions.expireTime")
}}</text>
<text class="grid-value">{{
item.expireTime
? formatDate(item.expireTime || "", "YYYY-MM-DD")
: "-"
}}</text>
</view>
<view class="grid-item">
<text class="grid-label">{{
t("Mobile.MySubscriptions.purchaseAmount")
}}</text>
<text
class="grid-value highlight-price"
>{{ item.extra?.price != null ? `¥ ${formatNumber(item.extra!.price, 2)}` : '-' }}</text
>
</view>
</view>
<!-- 底部 -->
<view class="credit-footer">
<text class="footer-remain">
{{
`${t("Mobile.MySubscriptions.remaining")}${formatNumber(item.remainAmount, 0)} ${t("Mobile.MySubscriptions.creditUnit")}`
}}
</text>
<view
v-if="getCreditStatus(item)"
class="status-tag"
:class="getCreditStatus(item)?.cls"
>
<text class="status-text">{{ getCreditStatus(item)?.text }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import type { CreditBatchItem } from "@/subpackages/types/interfaces/subscription";
import { SUCCESS_CODE } from "@/constants/codes.constants.uts";
import { apiGetCreditBatches } from "@/subpackages/servers/subscription";
import { useI18n } from "@/utils/i18n";
import EmptyState from "@/components/empty-state/empty-state.uvue";
import { formatDate, formatNumber } from "@/utils/system";
const { t } = useI18n();
const list = ref<CreditBatchItem[]>([]);
const loading = ref(true);
interface CreditStatus {
text: string;
cls: string;
}
const getCreditStatus = (item: CreditBatchItem): CreditStatus | null => {
if (item.expired) {
return {
text: t("Mobile.MySubscriptions.expired"),
cls: "status-expired",
};
}
if (item.remainAmount <= 0) {
return {
text: t("Mobile.MySubscriptions.fullyUsed"),
cls: "status-expired",
};
}
const ratio = item.totalAmount > 0 ? item.usedAmount / item.totalAmount : 0;
if (ratio >= 0.8) {
return {
text: t("Mobile.MySubscriptions.aboutToUseUp"),
cls: "status-warning",
};
}
return {
text: t("Mobile.MySubscriptions.normalUsage"),
cls: "status-active",
};
};
const fetchData = async () => {
loading.value = true;
try {
const res = await apiGetCreditBatches({ creditType: "PURCHASE" });
if (res.code === SUCCESS_CODE && res.data) {
list.value = Array.isArray(res.data) ? res.data : [];
}
} catch (e) {
// ignore
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchData();
});
</script>
<style lang="scss" scoped>
.subscribed-credits {
min-height: 200rpx;
.loading-state {
display: flex;
align-items: center;
justify-content: center;
padding: 80rpx 0;
.loading-text {
font-size: 28rpx;
color: #999;
}
}
.empty-wrapper {
padding: 40rpx 0;
}
.credit-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.credit-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
overflow: hidden;
border: 2rpx solid #e5e6eb;
.credit-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
.credit-name {
font-size: 28rpx;
font-weight: 600;
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 16rpx;
}
.credit-date {
font-size: 22rpx;
color: #999;
flex-shrink: 0;
}
}
.credit-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 16rpx;
padding: 20rpx 0;
border-top: 2rpx solid #e5e6eb;
.grid-item {
width: calc(50% - 8rpx);
display: flex;
flex-direction: column;
gap: 8rpx;
.grid-label {
font-size: 22rpx;
color: #999;
}
.grid-value {
font-size: 28rpx;
font-weight: 600;
color: #333;
&.highlight-price {
color: #f59e0b;
}
}
}
}
.credit-footer {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin: 20rpx -24rpx -24rpx -24rpx;
padding: 24rpx 24rpx;
background: #f8fafc;
border-top: 2rpx solid #e5e6eb;
.footer-remain {
font-size: 24rpx;
color: #666;
}
.status-tag {
padding: 4rpx 16rpx;
border-radius: 6rpx;
.status-text {
font-size: 22rpx;
font-weight: 500;
}
&.status-active {
background: rgba(82, 196, 26, 0.1);
.status-text {
color: #52c41a;
}
}
&.status-warning {
background: rgba(245, 158, 11, 0.1);
.status-text {
color: #f59e0b;
}
}
&.status-expired {
background: rgba(255, 77, 79, 0.1);
.status-text {
color: #ff4d4f;
}
}
}
}
}
}
</style>