Browse Source

feat(game): 添加排行榜自动刷新和倒计时功能

- 在赛事排行榜页面添加右上角倒计时显示组件
- 实现每300秒自动刷新排行榜数据功能
- 添加手动刷新时重置倒计时逻辑
- 在加分数据页面增加倒计时标签显示
- 实现成绩编辑页面的自动刷新倒计时按钮
- 添加响应式设计适配移动端显示
- 注释掉部分未使用的居住地址查询字段
- 调整表单标签宽度以优化界面布局
zhou 2 weeks ago
parent
commit
1c9e9e7f87

+ 2 - 2
src/views/system/gameAthlete/index.vue

@@ -16,9 +16,9 @@
             <el-form-item label="手机号" prop="phone">
               <el-input v-model="queryParams.phone" placeholder="请输入手机号" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="居住地址" prop="location">
+            <!-- <el-form-item label="居住地址" prop="location">
               <el-input v-model="queryParams.location" placeholder="请输入居住地址" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
+            </el-form-item> -->
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button icon="Refresh" @click="resetQuery">重置</el-button>

+ 134 - 14
src/views/system/gameEvent/RankingBoardPage.vue

@@ -1,5 +1,22 @@
 <template>
   <div class="ranking-board-page">
+    <!-- 右上角倒计时显示 -->
+    <div class="countdown-display">
+      <el-card shadow="never" class="countdown-card">
+        <div class="countdown-content">
+          <el-icon class="countdown-icon"><Timer /></el-icon>
+          <span class="countdown-text">{{ countdownSeconds }}s</span>
+          <!-- <el-button 
+            type="text" 
+            size="small" 
+            @click="stopAutoRefresh"
+            class="stop-btn"
+          >
+            <el-icon><Close /></el-icon>
+          </el-button> -->
+        </div>
+      </el-card>
+    </div>
     <!-- 赛事名称标题 -->
     <div class="event-title">
       <h2>{{defaultEventInfo ? defaultEventInfo.eventName : '赛事排行榜'}}</h2>
@@ -173,9 +190,9 @@
     </el-row>
   </div>
 </template>
-
+8
 <script setup lang="ts">
-import { ref, computed, onMounted } from 'vue';
+import { ref, computed, onMounted, onUnmounted, getCurrentInstance, toRefs } from 'vue';
 import { listScoreRanking, listPersonalRanking, listTeamRanking } from '@/api/system/gameEvent/eventRank';
 import { listGameScore } from '@/api/system/gameScore';
 import { listGameTeam } from '@/api/system/gameTeam';
@@ -188,6 +205,7 @@ import { storeToRefs } from 'pinia';
 import { listRankGroup } from '@/api/system/rankGroup'; 
 import { RankGroupVO } from '@/api/system/rankGroup/types'; 
 import { Refresh } from '@element-plus/icons-vue';
+import { Timer, Close } from '@element-plus/icons-vue';
 
 // 定义队伍积分排行榜的数据结构
 interface TeamScore {
@@ -233,6 +251,35 @@ const teamDisplayCount = ref(10); // 团队排行榜显示数量,默认10
 const personalRefreshing = ref(false); // 个人排行榜刷新状态
 const teamRefreshing = ref(false); // 团队排行榜刷新状态
 
+// 自动刷新相关
+const autoRefreshInterval = ref<NodeJS.Timeout | null>(null);
+const autoRefreshSeconds = 300; // 默认300秒自动刷新
+// 添加倒计时相关状态
+const countdownSeconds = ref(300); // 倒计时秒数
+const countdownInterval = ref<NodeJS.Timeout | null>(null); // 倒计时定时器
+
+// 启动倒计时
+const startCountdown = () => {
+  countdownSeconds.value = autoRefreshSeconds;
+  
+  countdownInterval.value = setInterval(() => {
+    countdownSeconds.value--;
+    
+    if (countdownSeconds.value <= 0) {
+      countdownSeconds.value = autoRefreshSeconds;
+    }
+  }, 1000);
+};
+
+// 停止倒计时
+const stopCountdown = () => {
+  if (countdownInterval.value) {
+    clearInterval(countdownInterval.value);
+    countdownInterval.value = null;
+  }
+  countdownSeconds.value = 0;
+};
+
 // 根据显示数量过滤数据
 const displayedAthleteList = computed(() => {
   return athleteScoreList.value.slice(0, personalDisplayCount.value);
@@ -515,7 +562,8 @@ const refreshPersonalRanking = async () => {
     const res = await listScoreRanking(defaultEventInfo.value.eventId.toString());
     // 按照totalScore字段降序排序(分数高的在前面)
     athleteScoreList.value = res.data.sort((a, b) => b.totalScore - a.totalScore);
-    
+    // 手动刷新时重置倒计时
+    startCountdown();
     // 显示成功消息
     ElMessage.success('个人排行榜数据已刷新');
   } catch (error) {
@@ -532,6 +580,8 @@ const refreshTeamRanking = async () => {
   try {
     // 重新加载队伍积分排行榜
     await loadTeamScores();
+    // 手动刷新时重置倒计时
+    startCountdown();
     
     // 显示成功消息
     ElMessage.success('团队排行榜数据已刷新');
@@ -566,11 +616,6 @@ const refreshAllData = async () => {
   }
 };
 
-// 自动刷新相关
-const autoRefreshInterval = ref<NodeJS.Timeout | null>(null);
-const autoRefreshEnabled = ref(false);
-const autoRefreshSeconds = ref(60); // 默认60秒自动刷新
-
 // 开启自动刷新
 const startAutoRefresh = () => {
   if (autoRefreshInterval.value) {
@@ -579,10 +624,10 @@ const startAutoRefresh = () => {
   
   autoRefreshInterval.value = setInterval(() => {
     refreshAllData();
-  }, autoRefreshSeconds.value * 1000);
-  
-  autoRefreshEnabled.value = true;
-  ElMessage.success(`已开启自动刷新,每${autoRefreshSeconds.value}秒刷新一次`);
+  }, autoRefreshSeconds * 1000);
+  // 开启倒计时
+  startCountdown();
+  ElMessage.success(`已开启自动刷新,每${autoRefreshSeconds}秒刷新一次`);
 };
 
 // 停止自动刷新
@@ -591,8 +636,8 @@ const stopAutoRefresh = () => {
     clearInterval(autoRefreshInterval.value);
     autoRefreshInterval.value = null;
   }
-  
-  autoRefreshEnabled.value = false;
+  // 停止倒计时
+  stopCountdown();
   ElMessage.info('已停止自动刷新');
 };
 
@@ -601,6 +646,8 @@ onUnmounted(() => {
   if (autoRefreshInterval.value) {
     clearInterval(autoRefreshInterval.value);
   }
+  // 停止倒计时
+  stopCountdown();
 });
 
 onMounted(async () => {
@@ -626,6 +673,7 @@ onMounted(async () => {
 .ranking-board-page {
   padding: 20px;
   height: calc(100vh - 120px);
+  position: relative;
 }
 /* 赛事名称标题样式 */
 .event-title {
@@ -980,4 +1028,76 @@ onMounted(async () => {
   text-align: center;
   padding: 0 5px;
 }
+
+/* 右上角倒计时样式 */
+.countdown-display {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  z-index: 1000;
+}
+
+.countdown-card {
+  background: rgba(255, 255, 255, 0.95);
+  backdrop-filter: blur(10px);
+  border: 1px solid #e4e7ed;
+}
+
+.countdown-content {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 4px 8px;
+}
+
+.countdown-icon {
+  color: #409eff;
+  font-size: 16px;
+}
+
+.countdown-text {
+  font-weight: bold;
+  color: #409eff;
+  font-size: 14px;
+  min-width: 30px;
+  text-align: center;
+}
+
+/* .stop-btn {
+  padding: 2px;
+  color: #f56c6c;
+}
+
+.stop-btn:hover {
+  background-color: #fef0f0;
+} */
+
+@keyframes pulse {
+  0% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(1.05);
+  }
+  100% {
+    transform: scale(1);
+  }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .countdown-display {
+    top: 10px;
+    right: 10px;
+  }
+  
+  .countdown-content {
+    padding: 2px 6px;
+  }
+  
+  .countdown-text {
+    font-size: 12px;
+    min-width: 25px;
+  }
+}
 </style>

+ 1 - 1
src/views/system/gameEventProject/index.vue

@@ -134,7 +134,7 @@
     </el-card>
     <!-- 添加或修改赛事项目对话框 -->
     <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
-      <el-form ref="gameEventProjectFormRef" :model="form" :rules="rules" label-width="80px">
+      <el-form ref="gameEventProjectFormRef" :model="form" :rules="rules" label-width="90px">
         <el-form-item label="项目名称" prop="projectName">
           <el-input v-model="form.projectName" placeholder="请输入项目名称" />
         </el-form-item>

+ 182 - 0
src/views/system/gameScore/gameScoreBonus.vue

@@ -18,6 +18,16 @@
               <el-icon><Refresh /></el-icon> 刷新数据
             </el-button>
           </el-col>
+          <!-- 倒计时显示区域 -->
+          <el-col :span="6">
+            <div class="countdown-info">
+              <el-tag type="info" size="small" class="countdown-tag">
+                <el-icon class="countdown-icon"><Timer /></el-icon>
+                {{ countdownSeconds }}s
+              </el-tag>
+              <!-- <span class="countdown-description">数据将自动刷新</span> -->
+            </div>
+          </el-col>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="loadBonusData" :columns="columns"></right-toolbar>
         </el-row>
       </template>
@@ -80,8 +90,11 @@
 </template>
 
 <script setup name="GameScoreBonus" lang="ts">
+import { ref, onMounted, onUnmounted, getCurrentInstance, toRefs } from 'vue';
+import { Timer } from '@element-plus/icons-vue';
 import { getBonusData, updateBonusData, exportBonusExcel } from '@/api/system/gameScore';
 import { useGameEventStore } from '@/store/modules/gameEvent';
+import type { ComponentInternalInstance } from 'vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -107,6 +120,52 @@ const columns = ref<FieldOption[]>([
   { key: 5, label: '额外加分', visible: true },
 ]);
 
+// 自动刷新相关状态
+const autoRefreshInterval = ref<NodeJS.Timeout | null>(null);
+const autoRefreshSeconds = ref(300); // 默认300秒自动刷新
+// 倒计时相关状态
+const countdownSeconds = ref(0);
+const countdownInterval = ref<NodeJS.Timeout | null>(null);
+
+// 启动倒计时
+const startCountdown = () => {
+  // 先停止之前的倒计时
+  stopCountdown();
+  
+  countdownSeconds.value = autoRefreshSeconds.value;
+  
+  countdownInterval.value = setInterval(() => {
+    countdownSeconds.value--;
+    
+    if (countdownSeconds.value <= 0) {
+      countdownSeconds.value = autoRefreshSeconds.value;
+    }
+  }, 1000);
+};
+
+// 停止倒计时
+const stopCountdown = () => {
+  if (countdownInterval.value) {
+    clearInterval(countdownInterval.value);
+    countdownInterval.value = null;
+  }
+  countdownSeconds.value = 0;
+};
+
+// 开启自动刷新(页面加载时自动调用)
+const startAutoRefresh = () => {
+  if (autoRefreshInterval.value) {
+    clearInterval(autoRefreshInterval.value);
+  }
+  
+  autoRefreshInterval.value = setInterval(() => {
+    loadBonusData(); // 自动刷新数据
+  }, autoRefreshSeconds.value * 1000);
+  
+  startCountdown();
+  console.log(`自动刷新已开启,每${autoRefreshSeconds.value}秒刷新一次`);
+};
+
 // 加载加分数据
 const loadBonusData = async () => {
   bonusLoading.value = true;
@@ -249,13 +308,136 @@ const exportBonusData = async () => {
 // 刷新加分数据
 const refreshBonusData = async () => {
   await loadBonusData();
+  // 手动刷新时重置倒计时
+  startCountdown();
 };
 
+onUnmounted(() => {
+  if (autoRefreshInterval.value) {
+    clearInterval(autoRefreshInterval.value);
+  }
+  stopCountdown();
+});
+
 onMounted(async () => {
   // 先加载默认赛事信息
   await gameEventStore.fetchDefaultEvent();
   // 然后加载加分数据
   await loadBonusData();
+  // 开启自动刷新
+  startAutoRefresh();
 });
 </script>
 
+<style scoped>
+
+/* 倒计时信息区域样式 */
+.countdown-info {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  gap: 4px;
+}
+
+.countdown-tag {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  background-color: #f0f9ff;
+  border-color: #409eff;
+  color: #409eff;
+}
+
+/* .countdown-description {
+  font-size: 12px;
+  color: #666;
+  margin-left: 4px;
+} */
+
+/* 右上角倒计时样式 */
+/* .countdown-display {
+  position: fixed;
+  top: 20px;
+  right: 20px;
+  z-index: 1000;
+} */
+
+.countdown-card {
+  background: rgba(255, 255, 255, 0.95);
+  backdrop-filter: blur(10px);
+  border: 1px solid #e4e7ed;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* .countdown-content {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 4px 8px;
+} */
+
+.countdown-icon {
+  color: #409eff;
+  font-size: 16px;
+}
+
+/* .countdown-text {
+  font-weight: bold;
+  color: #409eff;
+  font-size: 14px;
+  min-width: 30px;
+  text-align: center;
+} */
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .countdown-info {
+    align-items: center;
+    text-align: center;
+  }
+  
+  /* .countdown-description {
+    margin-left: 0;
+  } */
+}
+
+@media (max-width: 768px) {
+  /* .countdown-display {
+    top: 10px;
+    right: 10px;
+  } */
+  
+  /* .countdown-content {
+    padding: 2px 6px;
+  } */
+  
+  /* .countdown-text {
+    font-size: 12px;
+    min-width: 25px;
+  } */
+  
+  .countdown-info {
+    margin-top: 10px;
+  }
+  
+  .countdown-tag {
+    font-size: 12px;
+  }
+  
+  /* .countdown-description {
+    font-size: 11px;
+  } */
+}
+
+@media (max-width: 480px) {
+  .countdown-info {
+    width: 100%;
+    text-align: center;
+  }
+  
+  .countdown-tag {
+    width: 100%;
+    justify-content: center;
+  }
+}
+</style>

+ 437 - 2
src/views/system/gameScore/gameScoreEdit.vue

@@ -1,11 +1,26 @@
 <template>
   <div class="p-2">
+    <!-- 右上角倒计时显示 -->
+    <!-- <div class="countdown-display" v-if="countdownSeconds > 0">
+      <el-card shadow="never" class="countdown-card">
+        <div class="countdown-content">
+          <el-icon class="countdown-icon"><Timer /></el-icon>
+          <span class="countdown-text">{{ countdownSeconds }}s</span>
+        </div>
+      </el-card>
+    </div> -->
     <!-- 新增:顶部搜索框 -->
     <div class="flex items-center mb-4">
       <el-button type="primary" @click="refreshData"><el-icon><Refresh /></el-icon> 刷新</el-button>
       <el-button type="success" @click="calculateRankings" :loading="rankingLoading">
         <el-icon><Trophy /></el-icon> 计算排名
       </el-button>
+      <div class="countdown-button" v-if="countdownSeconds > 0">
+        <el-button size="default" disabled class="countdown-btn">
+          <el-icon class="countdown-icon"><Timer /></el-icon>
+          {{ countdownSeconds }}s
+        </el-button>
+      </div>
       <el-input 
         v-model="searchValue" 
         :placeholder="projectClassification === '0' ? '输入运动员姓名搜索' : '输入队伍名称搜索'" 
@@ -103,11 +118,13 @@
 </template>
 
 <script setup name="GameScoreEdit" lang="ts">
-import { onMounted, ref } from 'vue';
+import { onMounted, onUnmounted, ref, reactive, getCurrentInstance, toRefs } from 'vue';
 import { useRoute } from 'vue-router';
 import { getProjectScoreData, updateScoreAndRecalculate } from '@/api/system/gameScore/index';
 import { GameScoreForm } from '@/api/system/gameScore/types';
 import Pagination from '@/components/Pagination/index.vue';
+import { Timer } from '@element-plus/icons-vue';
+import type { ComponentInternalInstance } from 'vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { game_project_type } = toRefs<any>(proxy?.useDict('game_project_type'));
@@ -138,10 +155,58 @@ const queryParams = reactive({
   classification: projectClassification,
 });
 
+// 自动刷新相关状态
+const autoRefreshInterval = ref<NodeJS.Timeout | null>(null);
+const autoRefreshSeconds = ref(300); // 默认300秒自动刷新
+// 倒计时相关状态
+const countdownSeconds = ref(300);
+const countdownInterval = ref<NodeJS.Timeout | null>(null);
+
 //刷新数据方法
 const refreshData = () => {
   searchValue.value = '';
   loadData(false); // 刷新时不自动计算排名,需要手动点击计算排名按钮
+  // 手动刷新时重置倒计时
+  startCountdown();
+};
+
+// 启动倒计时
+const startCountdown = () => {
+  // 先停止之前的倒计时
+  stopCountdown();
+  
+  countdownSeconds.value = autoRefreshSeconds.value;
+  
+  countdownInterval.value = setInterval(() => {
+    countdownSeconds.value--;
+    
+    if (countdownSeconds.value <= 0) {
+      countdownSeconds.value = autoRefreshSeconds.value;
+    }
+  }, 1000);
+};
+
+// 停止倒计时
+const stopCountdown = () => {
+  if (countdownInterval.value) {
+    clearInterval(countdownInterval.value);
+    countdownInterval.value = null;
+  }
+  countdownSeconds.value = 0;
+};
+
+// 开启自动刷新(页面加载时自动调用)
+const startAutoRefresh = () => {
+  if (autoRefreshInterval.value) {
+    clearInterval(autoRefreshInterval.value);
+  }
+  
+  autoRefreshInterval.value = setInterval(() => {
+    loadData(false); // 自动刷新时不自动计算排名
+  }, autoRefreshSeconds.value * 1000);
+  
+  startCountdown();
+  console.log(`自动刷新已开启,每${autoRefreshSeconds.value}秒刷新一次`);
 };
 
 // 计算排名方法
@@ -409,7 +474,377 @@ const handlePagination = (paginationData: { page: number, limit: number }) => {
   loadData(false); // 分页加载不自动计算排名
 };
 
+// 组件卸载时清理定时器
+onUnmounted(() => {
+  if (autoRefreshInterval.value) {
+    clearInterval(autoRefreshInterval.value);
+  }
+  stopCountdown();
+});
+
 onMounted(() => {
   loadData(false); // 页面初始化时不自动计算排名,需要手动点击计算排名按钮
+  // 开启自动刷新
+  startAutoRefresh();
 });
-</script>
+</script>
+
+<style scoped>
+/* 页面基础样式 */
+.p-2 {
+  padding: 0.5rem;
+  position: relative;
+}
+
+/* Flex 布局样式 */
+.flex {
+  display: flex;
+}
+
+.items-center {
+  align-items: center;
+}
+
+.mb-4 {
+  margin-bottom: 1rem;
+}
+
+.ml-4 {
+  margin-left: 1rem;
+}
+
+.mr-2 {
+  margin-right: 0.5rem;
+}
+
+/* 文本样式 */
+.text-gray-600 {
+  color: #6b7280;
+}
+
+/* 表格样式 */
+.el-table {
+  width: 100%;
+}
+
+.el-table .el-table__header {
+  background-color: #f5f7fa;
+}
+
+.el-table .el-table__row:hover {
+  background-color: #f5f7fa;
+}
+
+/* 表格列样式 */
+.small-padding {
+  padding: 8px;
+}
+
+.fixed-width {
+  width: 120px;
+}
+
+/* 按钮样式 */
+.el-button {
+  margin-right: 8px;
+}
+
+.el-button:last-child {
+  margin-right: 0;
+}
+
+/* 输入框样式 */
+.el-input {
+  width: 100%;
+}
+
+/* 标签样式 */
+.el-tag {
+  margin-right: 8px;
+}
+
+/* 对话框样式 */
+.dialog-footer {
+  text-align: right;
+}
+
+.dialog-footer .el-button {
+  margin-left: 8px;
+}
+
+/* 分页样式 */
+.pagination-container {
+  margin-top: 20px;
+  text-align: center;
+}
+
+/* 加载状态样式 */
+.el-loading-mask {
+  background-color: rgba(255, 255, 255, 0.8);
+}
+
+/* 表单样式 */
+.el-form-item {
+  margin-bottom: 18px;
+}
+
+.el-form-item__label {
+  font-weight: 500;
+}
+
+/* 数字输入框样式 */
+.el-input-number {
+  width: 100%;
+}
+
+.el-input-number .el-input__inner {
+  text-align: left;
+}
+
+/* 搜索框样式 */
+.el-input__prefix {
+  color: #c0c4cc;
+}
+
+.el-input__suffix {
+  color: #c0c4cc;
+}
+
+/* 图标样式 */
+.el-icon {
+  font-size: 16px;
+}
+
+/* 工具提示样式 */
+.el-tooltip {
+  display: inline-block;
+}
+
+/* 卡片样式 */
+.el-card {
+  border-radius: 4px;
+  border: 1px solid #ebeef5;
+  background-color: #fff;
+  overflow: hidden;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.el-card__header {
+  padding: 18px 20px;
+  border-bottom: 1px solid #ebeef5;
+  box-sizing: border-box;
+}
+
+.el-card__body {
+  padding: 20px;
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .flex {
+    flex-wrap: wrap;
+  }
+  
+  .ml-4 {
+    margin-left: 0;
+    margin-top: 10px;
+  }
+}
+
+@media (max-width: 768px) {
+  .p-2 {
+    padding: 0.25rem;
+  }
+  
+  .mb-4 {
+    margin-bottom: 0.5rem;
+  }
+  
+  .el-button {
+    margin-bottom: 8px;
+  }
+  
+  .el-input {
+    width: 100%;
+    margin-left: 0 !important;
+    margin-top: 10px;
+  }
+  
+  .text-gray-600 {
+    margin-top: 10px;
+    text-align: center;
+  }
+}
+
+@media (max-width: 480px) {
+  .el-table {
+    font-size: 12px;
+  }
+  
+  .el-button {
+    padding: 8px 12px;
+    font-size: 12px;
+  }
+  
+  .el-input {
+    font-size: 12px;
+  }
+}
+
+/* 动画效果 */
+.el-button {
+  transition: all 0.3s;
+}
+
+.el-input {
+  transition: all 0.3s;
+}
+
+.el-table__row {
+  transition: background-color 0.3s;
+}
+
+/* 焦点状态 */
+.el-input:focus-within {
+  border-color: #409eff;
+}
+
+.el-button:focus {
+  outline: none;
+}
+
+/* 禁用状态 */
+.el-button.is-disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+/* 加载状态 */
+.el-button.is-loading {
+  pointer-events: none;
+}
+
+/* 表格选择列样式 */
+.el-table__selection {
+  text-align: center;
+}
+
+/* 表格操作列样式 */
+.el-table__fixed-right {
+  background-color: #fff;
+}
+
+/* 对话框动画 */
+.el-dialog {
+  border-radius: 4px;
+}
+
+.el-dialog__header {
+  padding: 20px 20px 10px;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.el-dialog__body {
+  padding: 20px;
+}
+
+.el-dialog__footer {
+  padding: 10px 20px 20px;
+  text-align: right;
+}
+
+/* 表单验证样式 */
+.el-form-item.is-error .el-input__inner {
+  border-color: #f56c6c;
+}
+
+.el-form-item.is-success .el-input__inner {
+  border-color: #67c23a;
+}
+
+/* 消息提示样式 */
+.el-message {
+  border-radius: 4px;
+}
+
+/* 工具提示样式 */
+.el-tooltip__popper {
+  border-radius: 4px;
+}
+
+/* 标签样式 */
+.el-tag {
+  border-radius: 4px;
+  border: 1px solid;
+}
+
+.el-tag--success {
+  background-color: #f0f9ff;
+  border-color: #67c23a;
+  color: #67c23a;
+}
+
+.el-tag--warning {
+  background-color: #fdf6ec;
+  border-color: #e6a23c;
+  color: #e6a23c;
+}
+
+/* 分页组件样式 */
+.pagination-container .el-pagination {
+  text-align: center;
+  margin-top: 20px;
+}
+
+/* 右上角倒计时样式 */
+.countdown-display {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  z-index: 1000;
+}
+
+.countdown-card {
+  background: rgba(255, 255, 255, 0.95);
+  backdrop-filter: blur(10px);
+  border: 1px solid #e4e7ed;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.countdown-content {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 4px 8px;
+}
+
+.countdown-icon {
+  color: #409eff;
+  font-size: 16px;
+}
+
+.countdown-text {
+  font-weight: bold;
+  color: #409eff;
+  font-size: 14px;
+  min-width: 30px;
+  text-align: center;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .countdown-display {
+    top: 10px;
+    right: 10px;
+  }
+  
+  .countdown-content {
+    padding: 2px 6px;
+  }
+  
+  .countdown-text {
+    font-size: 12px;
+    min-width: 25px;
+  }
+}
+</style>