Files
qiming/qiming-mobile/subpackages/pages/my-subscriptions/components/subscribed-content/subscribed-skills/subscribed-skills.uvue

297 lines
7.7 KiB
Plaintext

<template>
<view class="subscribed-skills">
<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="skill-list">
<view v-for="item in list" :key="item.id" class="skill-card">
<view class="skill-header">
<view class="skill-header-left">
<view class="skill-icon" :style="{ backgroundColor: item.icon ? 'transparent' : '#5147ff' }">
<image v-if="item.icon" :src="item.icon" class="skill-icon-img" mode="aspectFill" />
<text v-else class="iconfont icon-Grid"></text>
</view>
<view class="skill-title-box">
<text class="skill-name">{{ item.bizName }}</text>
<text class="skill-desc" v-if="item.planName">{{ item.planName }}</text>
</view>
</view>
<view class="skill-header-right">
<!-- 买断 -->
<view v-if="isForever(item.period)" class="tag tag-bought">
<text class="tag-text">{{
t("Mobile.MySubscriptions.boughtOut")
}}</text>
</view>
<!-- 订阅状态 -->
<view
v-else
class="status-tag"
:class="isActive(item.status) ? 'status-active' : 'status-expired'"
>
<text class="status-text">{{ getStatusText(item.status) }}</text>
</view>
</view>
</view>
<view class="skill-body">
<!-- 买断模式 -->
<view v-if="isForever(item.period)" class="skill-row">
<text class="skill-label">{{
t("Mobile.MySubscriptions.buyoutPriceLabel")
}}</text>
<text class="skill-value">{{
`¥ ${formatNumber(item.plan?.price ?? 0, 2)}`
}}</text>
</view>
<view v-if="isForever(item.period)" class="skill-row">
<text class="skill-label">{{
t("Mobile.MySubscriptions.permanentUse")
}}</text>
</view>
<!-- 订阅模式 -->
<view v-if="!isForever(item.period)" class="skill-row">
<text class="skill-label">{{
t("Mobile.MySubscriptions.subAmount")
}}</text>
<text class="skill-value">
{{
`¥ ${formatNumber(item.plan?.price ?? 0, 2)} ${getPeriodUnit(item.period)}`
}}
</text>
</view>
<view v-if="!isForever(item.period)" class="skill-row">
<text class="skill-label">{{
t("Mobile.MySubscriptions.expireTime")
}}</text>
<text class="skill-value">{{
item.endTime ? formatDate(item.endTime || "", "YYYY-MM-DD") : "-"
}}</text>
</view>
<view v-if="!isForever(item.period)" class="skill-row">
<text class="skill-label">{{ getPayTypeText(item.period) }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import type { MySubscriptionItem } from "@/subpackages/types/interfaces/subscription";
import { SUCCESS_CODE } from "@/constants/codes.constants.uts";
import { apiGetMySubscription } from "@/subpackages/servers/subscription";
import { useI18n } from "@/utils/i18n";
import {
getPeriodUnit,
getStatusText,
isActive,
isForever,
getPayTypeText,
} from "../../../utils.uts";
import EmptyState from "@/components/empty-state/empty-state.uvue";
import { formatDate, formatNumber } from "@/utils/system";
const { t } = useI18n();
const list = ref<MySubscriptionItem[]>([]);
const loading = ref(true);
const fetchData = async () => {
loading.value = true;
try {
const res = await apiGetMySubscription({ bizType: "SKILL" });
if (res.code === SUCCESS_CODE && res.data) {
list.value = res.data.subscriptions || [];
}
} catch (e) {
// ignore
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchData();
});
</script>
<style lang="scss" scoped>
.subscribed-skills {
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;
}
.skill-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.skill-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
.skill-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
.skill-header-left {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
gap: 16rpx;
overflow: hidden;
margin-right: 12rpx;
.skill-icon {
width: 72rpx;
height: 72rpx;
border-radius: 12rpx;
background: #5147ff;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
.skill-icon-img {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
.iconfont {
font-size: 36rpx;
color: #fff;
}
}
.skill-title-box {
flex: 1;
display: flex;
flex-direction: column;
gap: 4rpx;
overflow: hidden;
.skill-name {
font-size: 28rpx;
font-weight: 600;
color: #333;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.skill-desc {
font-size: 22rpx;
color: #999;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
}
.skill-header-right {
flex-shrink: 0;
.tag {
padding: 4rpx 16rpx;
border-radius: 6rpx;
.tag-text {
font-size: 22rpx;
font-weight: 500;
color: #5147ff;
}
&.tag-bought {
background: rgba(81, 71, 255, 0.1);
}
}
.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-expired {
background: rgba(255, 77, 79, 0.1);
.status-text {
color: #ff4d4f;
}
}
}
}
}
.skill-body {
display: flex;
flex-direction: column;
gap: 12rpx;
.skill-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
.skill-label {
font-size: 24rpx;
color: #999;
}
.skill-value {
font-size: 24rpx;
color: #333;
font-weight: 500;
}
}
}
}
}
</style>