| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063 |
- @page "/home2"
- @rendermode InteractiveServer
- @attribute [ReuseTabsPage(Title = "主页 2", Pin = true, Closable = false, Order = 1)]
- @using EasyTemplate.Tool.Util
- @using EasyTemplate.Service
- @using System.Timers
- <div class="main-container">
- <!-- 1. Header Section -->
- @* <header class="header-section">
- <!-- Global System Status Summary -->
- <div class="global-status">
- <div class="status-item status-normal">
- <span class="status-dot animate-ping"></span>
- <span class="status-text">正常:<span class="status-count">@normalCount</span></span>
- </div>
- <div class="status-divider"></div>
- <div class="status-item status-warning">
- <span class="status-dot"></span>
- <span class="status-text">预警:<span class="status-count">@warningCount</span></span>
- </div>
- <div class="status-divider"></div>
- <div class="status-item status-alarm">
- <span class="status-dot animate-pulse"></span>
- <span class="status-text">报警:<span class="status-count">@alarmCount</span></span>
- </div>
- </div>
- </header> *@
- <!-- 2. Main Nozzle Grid Section -->
- <main class="main-content">
- <div class="nozzle-grid">
- @if (items.Any())
- {
- int i = 0;
- foreach (var item in items)
- {
- i++;
- int num_page = ShowNozzleItem_Row * ShowNozzle_Col;
- if (i >= (currentPage-1) * num_page+1 && i <= currentPage * num_page)
- {
- <NozzleCard NozNo="@item.Value.noz.ToString()"
- OilName="@item.Value.oil"
- VLR="@item.Value.VLR"
- warnstate="@item.Value.warnstate"
- nozzlestate="@item.Value.nozzlestate" />
- }
- }
- @* @foreach (var item in items)
- {
- <NozzleCard
- NozNo="@item.Value.noz.ToString()"
- OilName="@item.Value.oil"
- VLR="@item.Value.VLR"
- warnstate="@item.Value.warnstate"
- nozzlestate="@item.Value.nozzlestate" />
- } *@
- }
- else
- {
- @for (int i = 1; i <= 32; i++)
- {
- var num = i.ToString("D2");
- var fuelTypes = new[] { "92#", "95#", "98#", "0#" };
- var fuel = fuelTypes[i % fuelTypes.Length];
- var state = i switch
- {
- 5 => 1, // Warning
- 6 => 2, // Alarm
- 4 or 15 or 22 => 3, // Offline
- _ => i % 3 == 0 ? 0 : (i % 2 == 0 ? -1 : 0) // Normal or Idle
- };
-
- <NozzleCard
- NozNo="@(num)"
- OilName="@(fuel)"
- VLR="@(state == 0 || state == 1 ? (1.0 + (i % 10) * 0.01).ToString("F2") : "--")"
- warnstate="@(state)"
- nozzlestate="@(state == 0 || state == 1 ? 1 : 0)" />
- }
- }
- </div>
- </main>
- <!-- 3. Top Status Bar (Key Metrics & Remote Platforms) -->
- <section class="top-status-bar">
- <!-- Key Metrics (Left) -->
- <div class="key-metrics">
- <!-- Tank Pressure -->
- <div class="metric-card metric-normal">
- <div class="metric-corner"></div>
- <div class="metric-icon">
- <span>📊</span>
- </div>
- <div class="metric-content">
- <div class="metric-label">油罐压力</div>
- <div class="metric-value">
- <span>@tankPressure.Value</span>
- <span class="metric-unit">kPa</span>
- </div>
- </div>
- <div class="metric-status">
- <span class="status-icon">✓</span>
- <span class="status-label">正常</span>
- </div>
- </div>
- <!-- Vapor Concentration -->
- <div class="metric-card metric-warning">
- <div class="metric-corner"></div>
- <div class="metric-icon">
- <span>💧</span>
- </div>
- <div class="metric-content">
- <div class="metric-label">油气浓度</div>
- <div class="metric-value">
- <span>@vaporConcentration.Value</span>
- <span class="metric-unit">g/m³</span>
- </div>
- </div>
- <div class="metric-status">
- <span class="status-icon animate-pulse">⚠</span>
- <span class="status-label">偏高</span>
- </div>
- </div>
- <!-- Pipeline Pressure -->
- <div class="metric-card metric-normal">
- <div class="metric-corner"></div>
- <div class="metric-icon">
- <span>🔧</span>
- </div>
- <div class="metric-content">
- <div class="metric-label">管线压力</div>
- <div class="metric-value">
- <span>@pipelinePressure.Value</span>
- <span class="metric-unit">kPa</span>
- </div>
- </div>
- <div class="metric-status">
- <span class="status-icon">✓</span>
- <span class="status-label">正常</span>
- </div>
- </div>
- <!-- Tank Temperature -->
- <div class="metric-card metric-alarm">
- <div class="metric-corner"></div>
- <div class="metric-icon">
- <span>🌡</span>
- </div>
- <div class="metric-content">
- <div class="metric-label">油罐温度</div>
- <div class="metric-value">
- <span>@tankTemperature.Value</span>
- <span class="metric-unit">°C</span>
- </div>
- </div>
- <div class="metric-status">
- <span class="status-icon animate-bounce">✕</span>
- <span class="status-label">超限</span>
- </div>
- </div>
- </div>
- <!-- Remote Platform Status (Right) -->
- <div class="remote-platforms">
- <div class="platform-title">
- <span>☁️</span>
- <span>数据远传状态</span>
- </div>
- <div class="platform-grid">
- <div class="platform-item">
- <span class="platform-name">市环保局</span>
- <div class="platform-status status-uploading">
- <span class="sync-icon animate-spin">🔄</span>
- <span>上传中</span>
- </div>
- </div>
- <div class="platform-item">
- <span class="platform-name">省质监局</span>
- <div class="platform-status status-synced">
- <span class="status-dot-small"></span>
- <span>已同步</span>
- </div>
- </div>
- <div class="platform-item">
- <span class="platform-name">集团总部</span>
- <div class="platform-status status-uploading">
- <span class="sync-icon animate-spin">🔄</span>
- <span>上传中</span>
- </div>
- </div>
- <div class="platform-item platform-delayed">
- <span class="platform-name">应急管理</span>
- <div class="platform-status status-delayed">
- <span class="status-dot-small animate-pulse"></span>
- <span>网络延迟</span>
- </div>
- </div>
- </div>
- </div>
- </section>
- <!-- 4. Pagination Control (Fixed Bottom) -->
- <footer class="pagination-footer">
- <!-- Legend -->
- <div class="legend">
- <span class="legend-title">状态图例:</span>
- <div class="legend-item">
- <span class="legend-dot legend-normal"></span>
- <span class="legend-text">正常 (加油/空闲)</span>
- </div>
- <div class="legend-item">
- <span class="legend-dot legend-offline"></span>
- <span class="legend-text">离线</span>
- </div>
- <div class="legend-item">
- <span class="legend-dot legend-warning"></span>
- <span class="legend-text">预警</span>
- </div>
- <div class="legend-item">
- <span class="legend-dot legend-alarm"></span>
- <span class="legend-text">报警</span>
- </div>
- </div>
- <!-- Pagination Center -->
- <div class="pagination-center">
- <span class="pagination-info">
- 第 <span class="page-number">@currentPage</span> 页 / 共 @totalPages 页 (@items.Count 条)
- </span>
- <div class="pagination-buttons">
- <button class="page-btn" disabled="@(currentPage <= 1)" @onclick="PreviousPage">
- ← 上一页
- </button>
-
- @for (int i = 1; i <= totalPages; i++)
- {
- var hasWarning = i == 2;
- var hasAlarm = i == 1;
- var isAllNormal = i == 3;
-
- <button class="page-btn @(i == currentPage ? "active" : "") @(hasAlarm ? "btn-alarm" : "") @(hasWarning && !hasAlarm ? "btn-warning" : "") @(isAllNormal && !hasAlarm && !hasWarning ? "btn-normal" : "")"
- @onclick="() => GoToPage(i)">
- @i
- @if (hasWarning && !hasAlarm)
- {
- <span class="page-indicator"></span>
- }
- </button>
- }
-
- <button class="page-btn" disabled="@(currentPage >= totalPages)" @onclick="NextPage">
- 下一页 →
- </button>
- </div>
- </div>
- <!-- Quick Jump -->
- <div class="quick-jump">
- <span class="jump-label">跳转至</span>
- <select class="jump-select" @onchange="OnQuickJump">
- @for (int i = 1; i <= totalPages; i++)
- {
- <option value="@i">@i</option>
- }
- </select>
- <span class="jump-label">页</span>
- </div>
- </footer>
- </div>
- @code {
- private Dictionary<int, NozzleState> items = new Dictionary<int, NozzleState>();
-
- private const int ShowNozzleItem_Row = 8; // 每行显示的枪数
- private const int ShowNozzle_Col = 4; // 每页显示的行数
- private const int ItemsPerPage = ShowNozzleItem_Row * ShowNozzle_Col;
- private int currentPage = 1;
- private int totalPages = 1;
- // Status counts
- private int normalCount = 0;
- private int warningCount = 0;
- private int alarmCount = 0;
- // Current time
- private DateTime currentTime = DateTime.Now;
- // Sensor data
- private SensorData tankPressure = new() { Value = "1.25", Status = 0 };
- private SensorData vaporConcentration = new() { Value = "18.5", Status = 1 };
- private SensorData pipelinePressure = new() { Value = "3.80", Status = 0 };
- private SensorData tankTemperature = new() { Value = "42.5", Status = 2 };
- private Timer? timer;
- protected override async Task OnInitializedAsync()
- {
- await RefreshData();
- StartTimer();
- }
- private void StartTimer()
- {
- timer = new Timer(1000); // 1 秒间隔
- timer.Elapsed += async (sender, e) => await RefreshData();
- timer.Start();
- }
- private async Task RefreshData()
- {
- var nozzlestates = GlobalTool.g_mNozzleState;
-
- try
- {
- await InvokeAsync(() =>
- {
- items = nozzlestates;
- currentTime = DateTime.Now;
-
- // Calculate status counts
- normalCount = items.Count(x => x.Value.warnstate == 0 || x.Value.warnstate == -1);
- warningCount = items.Count(x => x.Value.warnstate == 1);
- alarmCount = items.Count(x => x.Value.warnstate == 2);
-
- // Update sensor data from real data if available
- UpdateSensorData();
-
- // Calculate total pages
- totalPages = Math.Max(1, (int)Math.Ceiling(items.Count / (double)ItemsPerPage));
-
- StateHasChanged();
- });
- }
- catch (Exception ex)
- {
- Console.WriteLine($"数据刷新失败:{ex.Message}");
- }
- }
- private void UpdateSensorData()
- {
- // TODO: Replace with actual sensor data from database or service
- // For now, using mock data
- }
- private void PreviousPage()
- {
- if (currentPage > 1)
- {
- currentPage--;
- StateHasChanged();
- }
- }
- private void NextPage()
- {
- if (currentPage < totalPages)
- {
- currentPage++;
- StateHasChanged();
- }
- }
- private void GoToPage(int page)
- {
- if (page >= 1 && page <= totalPages)
- {
- currentPage = page;
- StateHasChanged();
- }
- }
- private void OnQuickJump(ChangeEventArgs e)
- {
- if (int.TryParse(e.Value?.ToString(), out int page))
- {
- GoToPage(page);
- }
- }
- public void Dispose()
- {
- timer?.Stop();
- timer?.Dispose();
- }
- public class SensorData
- {
- public string Value { get; set; } = "";
- public int Status { get; set; }
- }
- }
- <style>
- /* Main Container */
- .main-container {
- display: flex;
- flex-direction: column;
- height: 100vh;
- background-color: #f8fafc;
- color: #0f172a;
- font-family: 'Satoshi', Arial, sans-serif;
- overflow: hidden;
- }
- /* Header Section */
- .header-section {
- height: 64px;
- background-color: rgba(255, 255, 255, 0.9);
- backdrop-filter: blur(10px);
- border-bottom: 1px solid #e2e8f0;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 24px;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
- z-index: 20;
- flex-shrink: 0;
- }
- .header-left {
- display: flex;
- align-items: center;
- gap: 16px;
- }
- .logo-icon {
- width: 40px;
- height: 40px;
- border-radius: 8px;
- background: linear-gradient(135deg, #10b981, #059669);
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 4px 6px rgba(16, 185, 129, 0.2);
- }
- .icon-leaf {
- font-size: 24px;
- color: white;
- }
- .app-title {
- font-size: 20px;
- font-weight: bold;
- letter-spacing: 0.5px;
- color: #1e293b;
- }
- /* Global Status */
- .global-status {
- display: flex;
- align-items: center;
- gap: 24px;
- background-color: #f8fafc;
- padding: 8px 20px;
- border-radius: 9999px;
- border: 1px solid #e2e8f0;
- }
- .status-item {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .status-dot {
- width: 12px;
- height: 12px;
- border-radius: 50%;
- position: relative;
- }
- .status-normal .status-dot {
- background-color: #10b981;
- }
- .status-warning .status-dot {
- background-color: #f59e0b;
- }
- .status-alarm .status-dot {
- background-color: #ef4444;
- }
- .animate-ping {
- animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
- }
- @@keyframes ping {
- 75%, 100% {
- transform: scale(2);
- opacity: 0;
- }
- }
- .animate-pulse {
- animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
- }
- @@keyframes pulse {
- 0%, 100% {
- opacity: 1;
- }
- 50% {
- opacity: 0.5;
- }
- }
- .status-text {
- font-size: 14px;
- color: #475569;
- }
- .status-count {
- font-family: monospace;
- font-weight: bold;
- }
- .status-normal .status-count {
- color: #10b981;
- }
- .status-warning .status-count {
- color: #f59e0b;
- }
- .status-alarm .status-count {
- color: #ef4444;
- }
- .status-divider {
- width: 1px;
- height: 32px;
- background-color: #cbd5e1;
- }
- /* Header Right */
- .header-right {
- display: flex;
- align-items: center;
- gap: 24px;
- }
- .clock-display {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- }
- .clock-time {
- font-family: monospace;
- font-size: 20px;
- font-weight: bold;
- color: #1e293b;
- }
- .clock-date {
- font-size: 12px;
- color: #64748b;
- font-family: monospace;
- }
- .user-button {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- background-color: #f8fafc;
- border: 1px solid #e2e8f0;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: background-color 0.2s;
- }
- .user-button:hover {
- background-color: #f1f5f9;
- }
- /* Top Status Bar */
- .top-status-bar {
- display: grid;
- grid-template-columns: 2fr 1fr;
- gap: 16px;
- padding: 16px 24px;
- flex-shrink: 0;
- }
- /* Key Metrics */
- .key-metrics {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 16px;
- }
- .metric-card {
- background-color: white;
- border-radius: 12px;
- padding: 16px;
- display: flex;
- align-items: center;
- gap: 16px;
- position: relative;
- overflow: hidden;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
- }
- .metric-normal {
- border: 1px solid #e2e8f0;
- }
- .metric-warning {
- border: 1px solid rgba(245, 158, 11, 0.3);
- }
- .metric-alarm {
- border: 2px solid rgba(239, 68, 68, 0.4);
- box-shadow: 0 0 16px rgba(239, 68, 68, 0.2);
- }
- .metric-corner {
- position: absolute;
- top: 0;
- right: 0;
- width: 64px;
- height: 64px;
- background-color: rgba(16, 185, 129, 0.1);
- border-radius: 0 0 0 64px;
- }
- .metric-warning .metric-corner {
- background-color: rgba(245, 158, 11, 0.1);
- }
- .metric-alarm .metric-corner {
- background-color: rgba(239, 68, 68, 0.1);
- }
- .metric-icon {
- width: 48px;
- height: 48px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- font-size: 24px;
- }
- .metric-normal .metric-icon {
- background-color: rgba(16, 185, 129, 0.1);
- border: 1px solid rgba(16, 185, 129, 0.2);
- }
- .metric-warning .metric-icon {
- background-color: rgba(245, 158, 11, 0.1);
- border: 1px solid rgba(245, 158, 11, 0.3);
- }
- .metric-alarm .metric-icon {
- background-color: rgba(239, 68, 68, 0.1);
- border: 1px solid rgba(239, 68, 68, 0.3);
- }
- .metric-content {
- flex: 1;
- }
- .metric-label {
- font-size: 12px;
- color: #64748b;
- margin-bottom: 4px;
- }
- .metric-value {
- display: flex;
- align-items: baseline;
- gap: 4px;
- }
- .metric-value span:first-child {
- font-size: 24px;
- font-family: monospace;
- font-weight: bold;
- color: #1e293b;
- }
- .metric-warning .metric-value span:first-child {
- color: #f59e0b;
- }
- .metric-alarm .metric-value span:first-child {
- color: #ef4444;
- }
- .metric-unit {
- font-size: 12px;
- color: #64748b;
- font-family: monospace;
- }
- .metric-status {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- }
- .status-icon {
- font-size: 16px;
- }
- .metric-normal .status-icon {
- color: #10b981;
- }
- .metric-warning .status-icon {
- color: #f59e0b;
- }
- .metric-alarm .status-icon {
- color: #ef4444;
- }
- .status-label {
- font-size: 10px;
- margin-top: 4px;
- }
- .metric-normal .status-label {
- color: #10b981;
- }
- .metric-warning .status-label {
- color: #f59e0b;
- }
- .metric-alarm .status-label {
- color: #ef4444;
- font-weight: bold;
- }
- .animate-bounce {
- animation: bounce 1s infinite;
- }
- @@keyframes bounce {
- 0%, 100% {
- transform: translateY(-25%);
- animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
- }
- 50% {
- transform: translateY(0);
- animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
- }
- }
- .animate-spin {
- animation: spin 2s linear infinite;
- }
- @@keyframes spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
- }
- /* Remote Platforms */
- .remote-platforms {
- background-color: white;
- border-radius: 12px;
- border: 1px solid #e2e8f0;
- padding: 16px;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
- }
- .platform-title {
- font-size: 14px;
- font-weight: medium;
- color: #334155;
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 8px;
- }
- .platform-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 8px;
- }
- .platform-item {
- background-color: #f8fafc;
- border: 1px solid #f1f5f9;
- border-radius: 6px;
- padding: 8px 12px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .platform-delayed {
- background-color: rgba(251, 146, 60, 0.05);
- border-color: rgba(245, 158, 11, 0.3);
- }
- .platform-name {
- font-size: 12px;
- color: #475569;
- }
- .platform-status {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 10px;
- }
- .status-dot-small {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background-color: #10b981;
- }
- .status-uploading {
- color: #10b981;
- }
- .status-synced {
- color: #64748b;
- }
- .status-delayed {
- color: #f59e0b;
- }
- .sync-icon {
- font-size: 12px;
- }
- /* Main Content */
- .main-content {
- flex: 1;
- overflow-y: hidden;
- padding: 0 24px 192px;
- }
- .nozzle-grid {
- display: grid;
- grid-template-columns: repeat(8, 1fr);
- gap: 4px;
- }
- /* Pagination Footer */
- .pagination-footer {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- height: 56px;
- background-color: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(10px);
- border-top: 1px solid #e2e8f0;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 24px;
- z-index: 40;
- box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.02);
- }
- .legend {
- display: flex;
- align-items: center;
- gap: 16px;
- font-size: 12px;
- }
- .legend-title {
- color: #64748b;
- }
- .legend-item {
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .legend-dot {
- width: 10px;
- height: 10px;
- border-radius: 50%;
- }
- .legend-normal {
- background-color: #10b981;
- box-shadow: 0 0 8px rgba(16, 185, 129, 0.3);
- }
- .legend-offline {
- background-color: #cbd5e1;
- border: 1px solid #94a3b8;
- }
- .legend-warning {
- background-color: #f59e0b;
- box-shadow: 0 0 8px rgba(245, 158, 11, 0.4);
- }
- .legend-alarm {
- background-color: #ef4444;
- box-shadow: 0 0 10px rgba(239, 68, 68, 0.4);
- }
- .legend-text {
- color: #475569;
- }
- .pagination-center {
- display: flex;
- align-items: center;
- gap: 24px;
- }
- .pagination-info {
- font-size: 14px;
- color: #64748b;
- }
- .page-number {
- color: #1e293b;
- font-weight: bold;
- }
- .pagination-buttons {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .page-btn {
- min-width: 28px;
- height: 28px;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 13px;
- font-weight: bold;
- cursor: pointer;
- transition: all 0.2s;
- border: 1px solid transparent;
- position: relative;
- }
- .page-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .page-btn:not(:disabled):hover {
- transform: translateY(-1px);
- }
- .page-btn.active {
- background-color: #ef4444;
- color: white;
- box-shadow: 0 2px 4px rgba(239, 68, 68, 0.3);
- }
- .btn-warning {
- background-color: #fef3c7;
- color: #f59e0b;
- border-color: rgba(245, 158, 11, 0.3);
- }
- .btn-warning:hover:not(:disabled) {
- background-color: #fde68a;
- }
- .btn-normal {
- background-color: #d1fae5;
- color: #059669;
- border-color: rgba(16, 185, 129, 0.2);
- }
- .btn-normal:hover:not(:disabled) {
- background-color: #a7f3d0;
- }
- .page-indicator {
- position: absolute;
- top: -4px;
- right: -4px;
- width: 8px;
- height: 8px;
- background-color: #f59e0b;
- border-radius: 50%;
- }
- .quick-jump {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 14px;
- }
- .jump-label {
- color: #64748b;
- }
- .jump-select {
- background-color: white;
- border: 1px solid #cbd5e1;
- border-radius: 6px;
- padding: 4px 8px;
- font-size: 13px;
- outline: none;
- cursor: pointer;
- }
- .jump-select:focus {
- border-color: #10b981;
- box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.1);
- }
- /* Scrollbar */
- ::-webkit-scrollbar {
- width: 6px;
- height: 6px;
- }
- ::-webkit-scrollbar-track {
- background: #f1f5f9;
- }
- ::-webkit-scrollbar-thumb {
- background: #cbd5e1;
- border-radius: 3px;
- }
- ::-webkit-scrollbar-thumb:hover {
- background: #94a3b8;
- }
- </style>
|