2026-02-12 19:10:18 +08:00

899 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="dashboard-page" :style="safeAreaStyle">
<!-- 左侧面板用户信息 -->
<view class="left-panel">
<view class="brand">
<view class="brand-title" data-content="学小乐AI">学小乐AI</view>
<view class="brand-sub">教师端</view>
</view>
<view class="user-card" v-if="isLoggedIn">
<image class="user-avatar" :src="teacherInfo.avatarUrl || defaultAvatar" mode="aspectFill" />
<text class="user-name">{{ teacherInfo.nickName || teacherInfo.account || '老师' }}</text>
<text class="user-school">{{ teacherInfo.schoolName || '' }}</text>
</view>
<view class="user-card login-card" v-else @click="goLogin">
<image class="user-avatar" :src="defaultAvatar" mode="aspectFill" />
<text class="user-name">点击登录</text>
</view>
<!-- 班级选择 -->
<picker :range="classNames" @change="onClassPickerChange" :value="classPickerIndex" class="class-picker-wrap">
<view class="class-picker-btn" :class="{ active: !!selectedClassId }">
<text class="class-picker-text">{{ selectedClassName || '选择班级' }}</text>
<text class="class-picker-arrow">▼</text>
</view>
</picker>
<view class="left-actions">
<view class="action-item" @click="handleNavigate('/pages/teacher/homework/filter')">
<text class="action-icon">📊</text>
<text class="action-label">学情报告</text>
</view>
<view class="action-item logout" v-if="isLoggedIn" @click="handleLogout">
<text class="action-label">退出登录</text>
</view>
</view>
</view>
<!-- 右侧面板:数据看板 -->
<view class="right-panel">
<!-- 顶部筛选栏 -->
<view class="filter-bar" :style="topRightPaddingStyle">
<!-- 学科标签横向滚动 -->
<scroll-view v-if="subjectList.length > 0" class="subject-scroll" scroll-x enhanced :show-scrollbar="false">
<view class="subject-tags">
<view
v-for="sub in subjectList"
:key="sub.value"
class="subject-tag"
:class="{ active: selectedSubjectIds.includes(sub.value) }"
@click="toggleSubject(sub.value)"
>
<text>{{ sub.label }}</text>
</view>
</view>
</scroll-view>
<!-- 日期选择 -->
<view class="date-group">
<picker mode="date" :value="startTime" @change="onStartChange">
<view class="date-chip" :class="{ filled: !!startTime }">
<text>{{ startTime || '开始' }}</text>
</view>
</picker>
<text class="date-sep">~</text>
<picker mode="date" :value="endTime" @change="onEndChange">
<view class="date-chip" :class="{ filled: !!endTime }">
<text>{{ endTime || '结束' }}</text>
</view>
</picker>
</view>
<!-- 查询按钮 -->
<view class="query-btn" @click="fetchOverview">
<text>查询</text>
</view>
</view>
<!-- 看板内容区 -->
<scroll-view class="dashboard-body" scroll-y enhanced :show-scrollbar="false">
<!-- 加载 / 空状态 -->
<view v-if="loading" class="status-box">
<text class="status-text">加载中...</text>
</view>
<view v-else-if="!selectedClassId" class="status-box">
<text class="status-text">请选择班级查看数据</text>
</view>
<view v-else-if="errorMsg" class="status-box error">
<text class="status-text">{{ errorMsg }}</text>
</view>
<view v-else-if="overviewData" class="dashboard-content">
<!-- 核心指标卡片 -->
<view class="metrics-row">
<view class="metric-card paper">
<view class="metric-icon">📄</view>
<view class="metric-info">
<text class="metric-value">{{ overviewData.aiPaperCount || 0 }}<text class="metric-unit"></text></text>
<text class="metric-label">AI小助手为您批改试卷</text>
</view>
</view>
<view class="metric-card title">
<view class="metric-icon">📝</view>
<view class="metric-info">
<text class="metric-value">{{ overviewData.aiTitleCount || 0 }}<text class="metric-unit"></text></text>
<text class="metric-label">AI小助手为您批改题目</text>
</view>
</view>
</view>
<!-- 节约时间提示横幅 -->
<view class="save-time-banner">
<text class="save-time-icon">🎉</text>
<text class="save-time-text">{{ saveTimeText }}</text>
</view>
<!-- 成绩分布全宽 -->
<view class="grade-section">
<view class="section-head">
<text class="section-title">成绩分布</text>
</view>
<view class="grade-cards">
<view class="grade-card excellent">
<view class="grade-card-top">
<text class="grade-card-count">{{ overviewData.excellentCount || 0 }}</text>
<text class="grade-card-rate">{{ formatRate(overviewData.excellentRate) }}</text>
</view>
<view class="grade-card-bar">
<view class="grade-card-fill" :style="{ width: (overviewData.excellentRate || 0) + '%' }" />
</view>
<text class="grade-card-label">优秀</text>
</view>
<view class="grade-card good">
<view class="grade-card-top">
<text class="grade-card-count">{{ overviewData.goodCount || 0 }}</text>
<text class="grade-card-rate">{{ formatRate(overviewData.goodRate) }}</text>
</view>
<view class="grade-card-bar">
<view class="grade-card-fill" :style="{ width: (overviewData.goodRate || 0) + '%' }" />
</view>
<text class="grade-card-label">良好</text>
</view>
<view class="grade-card pass">
<view class="grade-card-top">
<text class="grade-card-count">{{ overviewData.passCount || 0 }}</text>
<text class="grade-card-rate">{{ formatRate(overviewData.passRate) }}</text>
</view>
<view class="grade-card-bar">
<view class="grade-card-fill" :style="{ width: (overviewData.passRate || 0) + '%' }" />
</view>
<text class="grade-card-label">合格</text>
</view>
<view class="grade-card unsub">
<view class="grade-card-top">
<text class="grade-card-count">{{ overviewData.unsubmittedCount || 0 }}</text>
<text class="grade-card-rate">{{ formatRate(overviewData.unsubmittedRate) }}</text>
</view>
<view class="grade-card-bar">
<view class="grade-card-fill" :style="{ width: (overviewData.unsubmittedRate || 0) + '%' }" />
</view>
<text class="grade-card-label">未提交</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import { teacher } from '@/store/teacher';
import { user } from '@/store';
import { storeToRefs } from 'pinia';
import {
getClassListByTeacher,
getSubjectListByClassId,
getAiCorrectionOverview,
} from '@/api/teacher';
import type { AiCorrectionOverview } from '@/api/teacher';
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
const defaultAvatar = `${OSS_URL}/urm/logo.png`;
const teacherStore = teacher();
const userStore = user();
const { teacherInfo, schoolId } = storeToRefs(teacherStore);
const { token } = storeToRefs(userStore);
const isLoggedIn = computed(() => !!token.value && !!teacherInfo.value.id);
// ==================== 安全区域 / 胶囊 ====================
const topPaddingRightRpx = ref(0);
const safeAreaLeftRpx = ref(0);
const topRightPaddingStyle = computed(() =>
topPaddingRightRpx.value > 0 ? { paddingRight: topPaddingRightRpx.value + 'rpx' } : {},
);
const safeAreaStyle = computed(() => {
const style: Record<string, string> = {};
if (safeAreaLeftRpx.value > 0) style.paddingLeft = safeAreaLeftRpx.value + 'rpx';
return style;
});
function getCapsulePadding() {
try {
// #ifdef MP-WEIXIN
const menuButton = uni.getMenuButtonBoundingClientRect();
const sys = uni.getSystemInfoSync();
if (menuButton && sys?.windowWidth) {
const r = 750 / sys.windowWidth;
topPaddingRightRpx.value = Math.ceil((sys.windowWidth - menuButton.left) * r) + 24;
} else {
topPaddingRightRpx.value = 140;
}
if (sys?.safeArea && sys.safeArea.left > 0) {
safeAreaLeftRpx.value = Math.ceil(sys.safeArea.left * (750 / (sys.windowWidth || sys.screenWidth))) + 4;
}
// #endif
} catch {
topPaddingRightRpx.value = 140;
}
}
// ==================== 导航 / 操作 ====================
const handleNavigate = (url: string) => {
if (!token.value) {
uni.navigateTo({ url: `/pages/login/index?redirect=${encodeURIComponent(url)}` });
return;
}
uni.navigateTo({ url });
};
const goLogin = () => uni.navigateTo({ url: '/pages/login/index' });
const handleLogout = () => {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
teacherStore.clear();
userStore.logout();
}
},
});
};
// ==================== 筛选状态 ====================
// 班级
const classList = ref<{ id: string; name: string }[]>([]);
const classPickerIndex = ref(0);
const selectedClassId = ref('');
const selectedClassName = computed(() => {
const cls = classList.value.find((c) => c.id === selectedClassId.value);
return cls?.name || '';
});
const classNames = computed(() => classList.value.map((c) => c.name));
function onClassPickerChange(e: any) {
const idx = Number(e.detail.value);
classPickerIndex.value = idx;
if (classList.value[idx]) {
selectedClassId.value = classList.value[idx].id;
}
}
// 学科
const subjectList = ref<{ value: number; label: string }[]>([]);
const selectedSubjectIds = ref<number[]>([]);
function toggleSubject(value: number) {
const idx = selectedSubjectIds.value.indexOf(value);
if (idx >= 0) {
selectedSubjectIds.value = selectedSubjectIds.value.filter((v) => v !== value);
} else {
selectedSubjectIds.value = [...selectedSubjectIds.value, value];
}
}
// 监听班级变化 → 加载学科
watch(() => selectedClassId.value, async (newId) => {
selectedSubjectIds.value = [];
subjectList.value = [];
if (!newId) return;
try {
const res = await getSubjectListByClassId(newId);
const data = res?.data ?? res;
if (Array.isArray(data)) {
subjectList.value = data.map((item: any) => ({
value: Number(item.subjectId ?? item.id ?? item.value),
label: item.subjectName ?? item.name ?? item.label ?? '',
}));
}
// 自动选中老师对应学科
const tid = Number(teacherInfo.value?.subjectId);
if (!isNaN(tid) && tid && subjectList.value.some((s) => s.value === tid)) {
selectedSubjectIds.value = [tid];
}
} catch (e) {
console.error('获取学科列表失败', e);
}
// 选择班级后自动查询
fetchOverview();
});
// 日期(默认当天)
function getTodayStr() {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
const startTime = ref(getTodayStr());
const endTime = ref(getTodayStr());
function onStartChange(e: any) { startTime.value = e.detail?.value || ''; }
function onEndChange(e: any) { endTime.value = e.detail?.value || ''; }
// ==================== 看板数据 ====================
const overviewData = ref<AiCorrectionOverview | null>(null);
const loading = ref(false);
const errorMsg = ref('');
// 节约时间友好文案
const saveTimeText = computed(() => {
const mins = overviewData.value?.teacherSaveTimeMinutes || 0;
const suffix = ',请您注意休息!';
if (mins <= 0) return 'AI小助手已就绪随时为您节省批阅时间' + suffix;
if (mins >= 1440) {
const days = (mins / 1440).toFixed(1).replace(/\.0$/, '');
return 'AI小助手已为您节省了 ' + days + ' 天的批阅时间' + suffix;
}
if (mins >= 60) {
const hours = (mins / 60).toFixed(1).replace(/\.0$/, '');
return 'AI小助手已为您节省了 ' + hours + ' 小时的批阅时间' + suffix;
}
return 'AI小助手已为您节省了 ' + mins + ' 分钟的批阅时间' + suffix;
});
function formatRate(rate: number | undefined | null): string {
if (rate == null) return '0%';
return (typeof rate === 'number' ? (rate > 1 ? rate.toFixed(1) : (rate * 100).toFixed(1)) : rate) + '%';
}
async function fetchOverview() {
if (!selectedClassId.value) return;
loading.value = true;
errorMsg.value = '';
try {
const params: any = { classId: selectedClassId.value };
if (schoolId.value) params.schoolId = schoolId.value;
if (selectedSubjectIds.value.length > 0) params.subjectIds = selectedSubjectIds.value;
if (startTime.value?.trim()) params.startTime = startTime.value.trim();
if (endTime.value?.trim()) params.endTime = endTime.value.trim();
const res = await getAiCorrectionOverview(params);
overviewData.value = (res?.data ?? res) as AiCorrectionOverview;
} catch (e) {
console.error('获取看板数据失败', e);
errorMsg.value = '加载失败,请重试';
overviewData.value = null;
} finally {
loading.value = false;
}
}
// ==================== 初始化 ====================
const fetchClassList = async () => {
try {
const res = await getClassListByTeacher();
const data = res?.data ?? res;
classList.value = Array.isArray(data) ? data : data?.list ?? data?.rows ?? [];
// 如果只有一个班级,自动选中
if (classList.value.length === 1) {
selectedClassId.value = classList.value[0].id;
classPickerIndex.value = 0;
}
} catch (e) {
console.error('获取班级列表失败', e);
}
};
const fetchTeacherInfo = async () => {
if (!token.value) return;
try {
await teacherStore.getLoginUser();
} catch (e) {
console.error('获取老师信息失败', e);
}
};
onShow(async () => {
getCapsulePadding();
if (token.value) {
await fetchTeacherInfo();
if (classList.value.length === 0) {
await fetchClassList();
}
}
});
</script>
<style lang="scss" scoped>
$teacher-bg: 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/main_bg.svg';
.dashboard-page {
height: 100vh;
width: 100vw;
background-image: url($teacher-bg);
background-size: cover;
background-position: center;
box-sizing: border-box;
display: flex;
flex-direction: row;
overflow: hidden;
}
// ========== 左侧面板 ==========
.left-panel {
width: 140rpx;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 16rpx 8rpx;
box-sizing: border-box;
flex-shrink: 0;
}
.brand {
text-align: center;
margin-bottom: 12rpx;
.brand-title {
font-family: $font-special;
font-size: 20rpx;
color: #fff;
@include font-stroke($font-color, 2rpx);
}
.brand-sub {
font-size: 12rpx;
color: rgba(255, 255, 255, 0.9);
margin-top: 2rpx;
}
}
.user-card {
background: rgba(255, 255, 255, 0.92);
border-radius: 12rpx;
padding: 10rpx 6rpx;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
border: 1rpx solid rgba(196, 181, 255, 0.25);
.user-avatar {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
border: 2rpx solid rgba(196, 181, 255, 0.4);
margin-bottom: 6rpx;
}
.user-name {
font-family: $font-special;
font-size: 14rpx;
color: $font-color;
@include single-ellipsis;
max-width: 100%;
text-align: center;
}
.user-school {
font-size: 10rpx;
color: #888;
margin-top: 2rpx;
@include single-ellipsis;
max-width: 100%;
text-align: center;
}
&.login-card {
cursor: pointer;
}
}
// 班级选择器
.class-picker-wrap {
width: 100%;
margin-top: 8rpx;
}
.class-picker-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 4rpx;
padding: 8rpx 6rpx;
background: rgba(255, 255, 255, 0.85);
border-radius: 8rpx;
border: 1rpx solid rgba(196, 181, 255, 0.3);
.class-picker-text {
font-family: $font-special;
font-size: 13rpx;
color: #666;
@include single-ellipsis;
max-width: 100rpx;
}
.class-picker-arrow {
font-size: 10rpx;
color: #aaa;
}
&.active {
background: linear-gradient(135deg, #8f9df7 0%, #cab5ff 100%);
border-color: #8f9df7;
.class-picker-text { color: #fff; font-weight: 600; }
.class-picker-arrow { color: rgba(255, 255, 255, 0.8); }
}
&:active { opacity: 0.85; }
}
.left-actions {
margin-top: auto;
width: 100%;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 8rpx 4rpx;
background: rgba(255, 255, 255, 0.85);
border-radius: 8rpx;
border: 1rpx solid rgba(196, 181, 255, 0.2);
.action-icon {
font-size: 18rpx;
margin-bottom: 2rpx;
}
.action-label {
font-family: $font-special;
font-size: 11rpx;
color: #666;
}
&.logout {
background: rgba(255, 255, 255, 0.6);
.action-label { color: #999; }
}
&:active { opacity: 0.8; }
}
// ========== 右侧面板 ==========
.right-panel {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 0 12rpx 0 6rpx;
}
// ========== 筛选栏 ==========
.filter-bar {
display: flex;
align-items: center;
gap: 6rpx;
padding: 6rpx 0 4rpx;
flex-shrink: 0;
flex-wrap: wrap;
}
.filter-chip {
display: flex;
align-items: center;
gap: 3rpx;
padding: 4rpx 10rpx;
background: rgba(255, 255, 255, 0.9);
border: 1rpx solid rgba(196, 181, 255, 0.3);
border-radius: 6rpx;
flex-shrink: 0;
.filter-chip-text {
font-family: $font-special;
font-size: 13rpx;
color: #666;
max-width: 120rpx;
@include single-ellipsis;
}
.filter-chip-arrow {
font-size: 10rpx;
color: #aaa;
}
&.active {
background: linear-gradient(135deg, #8f9df7 0%, #cab5ff 100%);
border-color: #8f9df7;
.filter-chip-text { color: #fff; font-weight: 600; }
.filter-chip-arrow { color: rgba(255, 255, 255, 0.8); }
}
}
.subject-scroll {
flex: 1;
min-width: 0;
white-space: nowrap;
}
.subject-tags {
display: inline-flex;
gap: 4rpx;
padding-right: 4rpx;
}
.subject-tag {
padding: 3rpx 8rpx;
border-radius: 6rpx;
background: rgba(255, 255, 255, 0.8);
border: 1rpx solid rgba(196, 181, 255, 0.25);
flex-shrink: 0;
text {
font-family: $font-special;
font-size: 12rpx;
color: #888;
}
&.active {
background: linear-gradient(135deg, #5b6ef7 0%, #8f6df7 100%);
border-color: #5b6ef7;
text { color: #fff; font-weight: 600; }
}
}
.date-group {
display: flex;
align-items: center;
gap: 3rpx;
flex-shrink: 0;
}
.date-chip {
padding: 3rpx 8rpx;
border-radius: 6rpx;
background: rgba(255, 255, 255, 0.8);
border: 1rpx solid rgba(196, 181, 255, 0.25);
text {
font-size: 12rpx;
color: #999;
}
&.filled {
border-color: #8f9df7;
text { color: #8f9df7; }
}
}
.date-sep {
font-size: 11rpx;
color: #999;
}
.query-btn {
padding: 4rpx 14rpx;
border-radius: 6rpx;
background: linear-gradient(135deg, #8f9df7 0%, #cab5ff 100%);
flex-shrink: 0;
text {
font-family: $font-special;
font-size: 12rpx;
color: #fff;
font-weight: 600;
}
&:active { opacity: 0.8; }
}
// ========== 看板主体 ==========
.dashboard-body {
flex: 1;
height: 0;
}
.status-box {
display: flex;
align-items: center;
justify-content: center;
padding: 60rpx 20rpx;
.status-text {
font-family: $font-special;
font-size: 16rpx;
color: #fff;
text-shadow: 0 1rpx 4rpx rgba(143, 157, 247, 0.5);
}
&.error .status-text { color: #ffb3b3; text-shadow: 0 1rpx 4rpx rgba(224, 80, 64, 0.4); }
}
.dashboard-content {
padding: 2rpx 0 12rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
}
// ========== 核心指标卡片 ==========
.metrics-row {
display: flex;
gap: 10rpx;
}
.metric-card {
flex: 1;
display: flex;
align-items: center;
gap: 10rpx;
padding: 16rpx 14rpx;
border-radius: 12rpx;
border: 1rpx solid rgba(196, 181, 255, 0.18);
background: rgba(255, 255, 255, 0.95);
.metric-icon {
font-size: 32rpx;
flex-shrink: 0;
}
.metric-info {
display: flex;
flex-direction: column;
}
.metric-value {
font-family: $font-special;
font-size: 32rpx;
font-weight: 700;
color: $font-color;
line-height: 1.1;
}
.metric-unit {
font-size: 14rpx;
font-weight: 400;
color: #999;
margin-left: 2rpx;
}
.metric-label {
font-size: 13rpx;
color: #999;
margin-top: 3rpx;
}
&.paper {
background: linear-gradient(135deg, #eee8ff 0%, #fff 100%);
border-color: rgba(143, 157, 247, 0.3);
.metric-value { color: #6c5ce7; }
}
&.title {
background: linear-gradient(135deg, #e3ecff 0%, #fff 100%);
border-color: rgba(91, 141, 239, 0.3);
.metric-value { color: #3867d6; }
}
}
// ========== 节约时间横幅 ==========
.save-time-banner {
display: flex;
align-items: center;
gap: 8rpx;
padding: 10rpx 16rpx;
background: linear-gradient(135deg, rgba(47, 173, 99, 0.1) 0%, rgba(80, 216, 128, 0.08) 100%);
border: 1rpx solid rgba(47, 173, 99, 0.2);
border-radius: 10rpx;
.save-time-icon {
font-size: 20rpx;
flex-shrink: 0;
}
.save-time-text {
font-family: $font-special;
font-size: 15rpx;
color: #fff;
font-weight: 500;
}
}
// ========== 成绩分布标题 ==========
.section-head {
margin-bottom: 8rpx;
.section-title {
font-family: $font-special;
font-size: 18rpx;
font-weight: 600;
color: $font-color;
}
}
// ========== 成绩分布 ==========
.grade-section {
background: rgba(255, 255, 255, 0.95);
border-radius: 12rpx;
padding: 14rpx 16rpx;
border: 1rpx solid rgba(196, 181, 255, 0.18);
flex: 1;
}
.grade-cards {
display: flex;
gap: 10rpx;
}
.grade-card {
flex: 1;
padding: 12rpx 14rpx;
border-radius: 10rpx;
display: flex;
flex-direction: column;
.grade-card-top {
display: flex;
align-items: baseline;
gap: 6rpx;
margin-bottom: 6rpx;
}
.grade-card-count {
font-family: $font-special;
font-size: 28rpx;
font-weight: 700;
line-height: 1;
}
.grade-card-rate {
font-size: 13rpx;
opacity: 0.7;
}
.grade-card-bar {
height: 6rpx;
background: rgba(255, 255, 255, 0.4);
border-radius: 6rpx;
overflow: hidden;
margin-bottom: 6rpx;
}
.grade-card-fill {
height: 100%;
border-radius: 6rpx;
background: rgba(255, 255, 255, 0.7);
transition: width 0.5s ease;
}
.grade-card-label {
font-family: $font-special;
font-size: 13rpx;
font-weight: 500;
}
&.excellent {
background: linear-gradient(135deg, #e0fae9 0%, #c8f5d8 100%);
border: 1rpx solid rgba(47, 173, 99, 0.15);
.grade-card-count { color: #20a04b; }
.grade-card-rate { color: #20a04b; }
.grade-card-label { color: #2fad63; }
.grade-card-fill { background: #2fad63; }
}
&.good {
background: linear-gradient(135deg, #e3ecff 0%, #d0dfff 100%);
border: 1rpx solid rgba(91, 141, 239, 0.15);
.grade-card-count { color: #3867d6; }
.grade-card-rate { color: #3867d6; }
.grade-card-label { color: #5b8def; }
.grade-card-fill { background: #5b8def; }
}
&.pass {
background: linear-gradient(135deg, #fff5e0 0%, #ffeccc 100%);
border: 1rpx solid rgba(247, 166, 53, 0.15);
.grade-card-count { color: #d68f00; }
.grade-card-rate { color: #d68f00; }
.grade-card-label { color: #f7a635; }
.grade-card-fill { background: #f7a635; }
}
&.unsub {
background: linear-gradient(135deg, #ffe8e5 0%, #ffd6d2 100%);
border: 1rpx solid rgba(224, 80, 64, 0.15);
.grade-card-count { color: #c0392b; }
.grade-card-rate { color: #c0392b; }
.grade-card-label { color: #e05040; }
.grade-card-fill { background: #e05040; }
}
}
</style>