2025-10-21 16:50:33 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="admin-orders">
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<!-- 左侧导航栏 -->
|
|
|
|
|
|
<aside class="sidebar">
|
|
|
|
|
|
<div class="logo">
|
2025-11-21 16:10:00 +08:00
|
|
|
|
<img src="/images/backgrounds/logo-admin.svg" alt="Logo" />
|
2025-11-13 17:01:39 +08:00
|
|
|
|
</div>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<nav class="nav-menu">
|
2025-11-21 16:10:00 +08:00
|
|
|
|
<div v-if="isAdminMode" class="nav-item" @click="goToDashboard">
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<el-icon><Grid /></el-icon>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<span>{{ $t('nav.dashboard') }}</span>
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
2025-11-21 16:10:00 +08:00
|
|
|
|
<div v-if="isAdminMode" class="nav-item" @click="goToMembers">
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<el-icon><User /></el-icon>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<span>{{ $t('nav.members') }}</span>
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<div class="nav-item active">
|
|
|
|
|
|
<el-icon><ShoppingCart /></el-icon>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<span>{{ $t('nav.orders') }}</span>
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
2025-11-21 16:10:00 +08:00
|
|
|
|
<div v-if="isAdminMode" class="nav-item" @click="goToAPI">
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<el-icon><Document /></el-icon>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<span>{{ $t('nav.apiManagement') }}</span>
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
2025-11-21 16:10:00 +08:00
|
|
|
|
<div v-if="isAdminMode" class="nav-item" @click="goToTasks">
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<el-icon><Document /></el-icon>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<span>{{ $t('nav.tasks') }}</span>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</div>
|
2025-12-20 15:24:58 +08:00
|
|
|
|
<div v-if="isAdminMode" class="nav-item" @click="goToErrorStats">
|
|
|
|
|
|
<el-icon><Warning /></el-icon>
|
|
|
|
|
|
<span>错误统计</span>
|
|
|
|
|
|
</div>
|
2025-11-21 16:10:00 +08:00
|
|
|
|
<div v-if="isAdminMode" class="nav-item" @click="goToSettings">
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<el-icon><Setting /></el-icon>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<span>{{ $t('nav.systemSettings') }}</span>
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
2025-11-21 16:10:00 +08:00
|
|
|
|
<!-- 普通用户模式的导航 -->
|
|
|
|
|
|
<div v-if="!isAdminMode" class="nav-item" @click="goToProfile">
|
|
|
|
|
|
<el-icon><User /></el-icon>
|
|
|
|
|
|
<span>{{ $t('nav.profile') || '个人主页' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="!isAdminMode" class="nav-item" @click="goToWorks">
|
|
|
|
|
|
<el-icon><Document /></el-icon>
|
|
|
|
|
|
<span>{{ $t('nav.myWorks') || '我的作品' }}</span>
|
|
|
|
|
|
</div>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</nav>
|
|
|
|
|
|
<div class="sidebar-footer">
|
|
|
|
|
|
<div class="online-users">
|
2025-12-08 13:54:02 +08:00
|
|
|
|
{{ $t('nav.todayVisitors') }}: <span class="highlight">{{ onlineUsers }}</span>
|
|
|
|
|
|
</div>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<div class="system-uptime">
|
2025-11-13 17:01:39 +08:00
|
|
|
|
{{ $t('nav.systemUptime') }}: <span class="highlight">{{ systemUptime }}</span>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 主内容区域 -->
|
|
|
|
|
|
<main class="main-content">
|
|
|
|
|
|
<!-- 顶部搜索栏 -->
|
|
|
|
|
|
<header class="top-header">
|
|
|
|
|
|
<div class="search-bar">
|
|
|
|
|
|
<el-icon class="search-icon"><Search /></el-icon>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<input type="text" :placeholder="$t('common.searchPlaceholder')" class="search-input" v-model="searchText" />
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<div class="header-actions">
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<LanguageSwitcher />
|
2025-11-21 16:10:00 +08:00
|
|
|
|
<el-dropdown v-if="isAdminMode" @command="handleUserCommand">
|
|
|
|
|
|
<div class="user-avatar">
|
|
|
|
|
|
<img src="/images/backgrounds/avatar-default.svg" alt="用户头像" />
|
|
|
|
|
|
<el-icon class="arrow-down"><ArrowDown /></el-icon>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template #dropdown>
|
|
|
|
|
|
<el-dropdown-menu>
|
|
|
|
|
|
<el-dropdown-item command="exitAdmin">
|
|
|
|
|
|
{{ $t('admin.exitAdmin') }}
|
|
|
|
|
|
</el-dropdown-item>
|
|
|
|
|
|
</el-dropdown-menu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dropdown>
|
|
|
|
|
|
<div v-else class="user-avatar" @click="goToProfile">
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<img src="/images/backgrounds/avatar-default.svg" alt="用户头像" />
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 订单列表内容 -->
|
|
|
|
|
|
<section class="order-content">
|
|
|
|
|
|
<div class="content-header">
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<h2>{{ $t('orders.title') }}</h2>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<div class="selection-info" v-if="selectedOrders.length > 0">
|
2025-11-13 17:01:39 +08:00
|
|
|
|
{{ $t('orders.selected', { count: selectedOrders.length }) }}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="table-toolbar">
|
|
|
|
|
|
<div class="toolbar-left">
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<el-select v-model="filters.status" :placeholder="$t('orders.allStatus')" size="small" @change="handleFilterChange">
|
|
|
|
|
|
<el-option :label="$t('orders.allStatus')" value="" />
|
|
|
|
|
|
<el-option :label="$t('orders.pending')" value="PENDING" />
|
|
|
|
|
|
<el-option :label="$t('orders.paid')" value="PAID" />
|
|
|
|
|
|
<el-option :label="$t('orders.completed')" value="COMPLETED" />
|
|
|
|
|
|
<el-option :label="$t('orders.cancelled')" value="CANCELLED" />
|
|
|
|
|
|
<el-option :label="$t('orders.refunded')" value="REFUNDED" />
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</el-select>
|
2025-12-05 21:06:16 +08:00
|
|
|
|
<el-select v-model="filters.paymentMethod" :placeholder="$t('orders.allPaymentMethods') || '全部支付方式'" size="small" @change="handleFilterChange">
|
|
|
|
|
|
<el-option :label="$t('orders.allPaymentMethods') || '全部支付方式'" value="" />
|
|
|
|
|
|
<el-option :label="$t('orders.alipay') || '支付宝'" value="ALIPAY" />
|
|
|
|
|
|
<el-option :label="$t('orders.paypal') || 'PayPal'" value="PAYPAL" />
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</el-select>
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<div class="toolbar-right">
|
|
|
|
|
|
<el-button type="danger" size="small" @click="deleteSelected" :disabled="selectedOrders.length === 0">
|
|
|
|
|
|
<el-icon><Delete /></el-icon>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
{{ $t('common.delete') }}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</el-button>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="table-container">
|
|
|
|
|
|
<table class="order-table">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th class="checkbox-col">
|
|
|
|
|
|
<input type="checkbox" @change="toggleAllSelection" :checked="isAllSelected" />
|
|
|
|
|
|
</th>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<th>{{ $t('orders.orderNumber') }}</th>
|
|
|
|
|
|
<th>{{ $t('orders.username') }}</th>
|
|
|
|
|
|
<th>{{ $t('orders.amount') }}</th>
|
|
|
|
|
|
<th>{{ $t('orders.paymentMethod') }}</th>
|
|
|
|
|
|
<th>{{ $t('orders.status') }}</th>
|
|
|
|
|
|
<th>{{ $t('orders.createTime') }}</th>
|
|
|
|
|
|
<th>{{ $t('orders.operation') }}</th>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="order in orders" :key="order.id" class="table-row">
|
|
|
|
|
|
<td class="checkbox-col">
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
2025-11-07 19:09:50 +08:00
|
|
|
|
:checked="selectedOrders.some(o => o.id === order.id)"
|
|
|
|
|
|
@change="toggleOrderSelection(order)" />
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>{{ order.orderNumber || order.id }}</td>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<td>{{ order.user?.username || $t('orders.unpaid') }}</td>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<td>{{ order.currency || '¥' }}{{ order.totalAmount || 0 }}</td>
|
|
|
|
|
|
<td>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<span v-if="order.paymentMethod === 'ALIPAY'">
|
|
|
|
|
|
<el-icon><CreditCard /></el-icon> {{ $t('orders.alipay') }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span v-else-if="order.paymentMethod === 'WECHAT'">
|
|
|
|
|
|
<el-icon><CreditCard /></el-icon> {{ $t('orders.wechat') }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span v-else-if="order.paymentMethod === 'PAYPAL'">
|
|
|
|
|
|
<el-icon><Wallet /></el-icon> {{ $t('orders.paypal') }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span v-else class="text-muted">{{ $t('orders.unpaid') }}</span>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<span class="status-tag" :class="getStatusClass(order.status)">
|
|
|
|
|
|
{{ getStatusText(order.status) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>{{ formatDate(order.createdAt) }}</td>
|
|
|
|
|
|
<td>
|
2025-11-13 17:01:39 +08:00
|
|
|
|
<el-link type="primary" class="action-link" @click="viewOrder(order)">{{ $t('common.view') }}</el-link>
|
|
|
|
|
|
<el-link type="danger" class="action-link" @click="deleteOrder(order)">{{ $t('common.delete') }}</el-link>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
|
<div class="pagination-container">
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<div class="pagination">
|
|
|
|
|
|
<el-icon class="page-arrow" @click="prevPage" :class="{ disabled: currentPage === 1 }"><ArrowLeft /></el-icon>
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="page in visiblePages"
|
|
|
|
|
|
:key="page"
|
|
|
|
|
|
class="page-btn"
|
|
|
|
|
|
:class="{ active: page === currentPage }"
|
|
|
|
|
|
@click="goToPage(page)">
|
|
|
|
|
|
{{ page }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<template v-if="totalPages > 7 && currentPage < totalPages - 2">
|
|
|
|
|
|
<span class="page-ellipsis">...</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="page-btn"
|
|
|
|
|
|
:class="{ active: totalPages === currentPage }"
|
|
|
|
|
|
@click="goToPage(totalPages)">
|
|
|
|
|
|
{{ totalPages }}
|
|
|
|
|
|
</button>
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</template>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<el-icon class="page-arrow" @click="nextPage" :class="{ disabled: currentPage === totalPages }"><ArrowRight /></el-icon>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
2025-12-05 21:06:16 +08:00
|
|
|
|
<!-- 订单详情弹窗 -->
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="orderDetailVisible"
|
|
|
|
|
|
:title="$t('orders.orderDetail') || '订单详情'"
|
|
|
|
|
|
width="600px"
|
|
|
|
|
|
class="order-detail-dialog"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div v-if="currentOrderDetail" class="order-detail-content">
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
|
<h4>{{ $t('orders.basicInfo') || '基本信息' }}</h4>
|
|
|
|
|
|
<div class="detail-grid">
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<span class="label">{{ $t('orders.orderNumber') }}:</span>
|
|
|
|
|
|
<span class="value">{{ currentOrderDetail.orderNumber || currentOrderDetail.id }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<span class="label">{{ $t('orders.username') }}:</span>
|
|
|
|
|
|
<span class="value">{{ currentOrderDetail.user?.username || '-' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<span class="label">{{ $t('orders.orderType') || '订单类型' }}:</span>
|
|
|
|
|
|
<span class="value">{{ getOrderTypeText(currentOrderDetail.orderType) }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<span class="label">{{ $t('orders.status') }}:</span>
|
2025-12-20 15:24:58 +08:00
|
|
|
|
<el-select
|
|
|
|
|
|
v-if="isAdminMode"
|
|
|
|
|
|
v-model="editingStatus"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
style="width: 120px;"
|
|
|
|
|
|
@change="handleStatusChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-option label="待支付" value="PENDING" />
|
|
|
|
|
|
<el-option label="已支付" value="PAID" />
|
|
|
|
|
|
<el-option label="已完成" value="COMPLETED" />
|
|
|
|
|
|
<el-option label="已取消" value="CANCELLED" />
|
|
|
|
|
|
<el-option label="已退款" value="REFUNDED" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
<span v-else class="status-tag" :class="getStatusClass(currentOrderDetail.status)">
|
2025-12-05 21:06:16 +08:00
|
|
|
|
{{ getStatusText(currentOrderDetail.status) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
|
<h4>{{ $t('orders.paymentInfo') || '支付信息' }}</h4>
|
|
|
|
|
|
<div class="detail-grid">
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<span class="label">{{ $t('orders.amount') }}:</span>
|
|
|
|
|
|
<span class="value amount">{{ currentOrderDetail.currency || '¥' }}{{ currentOrderDetail.totalAmount || 0 }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<span class="label">{{ $t('orders.paymentMethod') }}:</span>
|
|
|
|
|
|
<span class="value">
|
|
|
|
|
|
<template v-if="currentOrderDetail.paymentMethod === 'ALIPAY'">{{ $t('orders.alipay') }}</template>
|
|
|
|
|
|
<template v-else-if="currentOrderDetail.paymentMethod === 'WECHAT'">{{ $t('orders.wechat') }}</template>
|
|
|
|
|
|
<template v-else-if="currentOrderDetail.paymentMethod === 'PAYPAL'">{{ $t('orders.paypal') }}</template>
|
|
|
|
|
|
<template v-else>{{ $t('orders.unpaid') }}</template>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<span class="label">{{ $t('orders.createTime') }}:</span>
|
|
|
|
|
|
<span class="value">{{ formatDate(currentOrderDetail.createdAt) }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item" v-if="currentOrderDetail.paidAt">
|
|
|
|
|
|
<span class="label">{{ $t('orders.paidTime') || '支付时间' }}:</span>
|
|
|
|
|
|
<span class="value">{{ formatDate(currentOrderDetail.paidAt) }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="detail-section" v-if="currentOrderDetail.contactEmail || currentOrderDetail.contactPhone">
|
|
|
|
|
|
<h4>{{ $t('orders.contactInfo') || '联系信息' }}</h4>
|
|
|
|
|
|
<div class="detail-grid">
|
|
|
|
|
|
<div class="detail-item" v-if="currentOrderDetail.contactEmail">
|
|
|
|
|
|
<span class="label">{{ $t('orders.email') || '邮箱' }}:</span>
|
|
|
|
|
|
<span class="value">{{ currentOrderDetail.contactEmail }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item" v-if="currentOrderDetail.contactPhone">
|
|
|
|
|
|
<span class="label">{{ $t('orders.phone') || '电话' }}:</span>
|
|
|
|
|
|
<span class="value">{{ currentOrderDetail.contactPhone }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="detail-section" v-if="currentOrderDetail.description">
|
|
|
|
|
|
<h4>{{ $t('orders.description') || '订单描述' }}</h4>
|
|
|
|
|
|
<p class="description-text">{{ currentOrderDetail.description }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="loading-container">
|
|
|
|
|
|
<el-icon class="is-loading"><Loading /></el-icon>
|
|
|
|
|
|
<span>{{ $t('common.loading') || '加载中...' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="orderDetailVisible = false">{{ $t('common.close') || '关闭' }}</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, reactive, onMounted, computed } from 'vue'
|
2025-11-21 16:10:00 +08:00
|
|
|
|
import { useRouter, useRoute } from 'vue-router'
|
2025-10-21 16:50:33 +08:00
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
2025-12-08 13:54:02 +08:00
|
|
|
|
import { useI18n } from 'vue-i18n'
|
2025-10-23 09:59:54 +08:00
|
|
|
|
import {
|
2025-11-07 19:09:50 +08:00
|
|
|
|
Grid,
|
|
|
|
|
|
User,
|
|
|
|
|
|
ShoppingCart,
|
|
|
|
|
|
Document,
|
|
|
|
|
|
Setting,
|
|
|
|
|
|
Search,
|
|
|
|
|
|
ArrowDown,
|
|
|
|
|
|
ArrowLeft,
|
|
|
|
|
|
ArrowRight,
|
|
|
|
|
|
Delete,
|
2025-10-23 09:59:54 +08:00
|
|
|
|
CreditCard,
|
2025-12-05 21:06:16 +08:00
|
|
|
|
Wallet,
|
2025-12-20 15:24:58 +08:00
|
|
|
|
Loading,
|
|
|
|
|
|
Warning
|
2025-10-23 09:59:54 +08:00
|
|
|
|
} from '@element-plus/icons-vue'
|
2025-12-20 15:24:58 +08:00
|
|
|
|
import { getOrders, getAdminOrders, getOrderStats, deleteOrder as deleteOrderAPI, deleteOrders, updateOrderStatus } from '@/api/orders'
|
2025-11-13 17:01:39 +08:00
|
|
|
|
import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
|
2025-11-07 19:09:50 +08:00
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
2025-11-21 16:10:00 +08:00
|
|
|
|
const route = useRoute()
|
2025-12-08 13:54:02 +08:00
|
|
|
|
const { t } = useI18n()
|
2025-11-21 16:10:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 判断是否为管理员模式(基于路由路径)
|
|
|
|
|
|
const isAdminMode = computed(() => route.path.includes('/admin/'))
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const orders = ref([])
|
2025-11-07 19:09:50 +08:00
|
|
|
|
const searchText = ref('')
|
|
|
|
|
|
const selectedOrders = ref([])
|
|
|
|
|
|
const currentPage = ref(1)
|
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
|
const totalOrders = ref(0)
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
2025-11-13 17:01:39 +08:00
|
|
|
|
// 系统状态数据
|
|
|
|
|
|
const onlineUsers = ref('0/500')
|
2025-12-08 13:54:02 +08:00
|
|
|
|
const systemUptime = ref(t('nav.loading'))
|
2025-11-13 17:01:39 +08:00
|
|
|
|
|
2025-10-21 16:50:33 +08:00
|
|
|
|
// 筛选条件
|
|
|
|
|
|
const filters = reactive({
|
|
|
|
|
|
status: '',
|
2025-12-05 21:06:16 +08:00
|
|
|
|
paymentMethod: '',
|
2025-10-21 16:50:33 +08:00
|
|
|
|
search: ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
// 导航功能
|
|
|
|
|
|
const goToDashboard = () => {
|
|
|
|
|
|
router.push('/admin/dashboard')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const goToMembers = () => {
|
|
|
|
|
|
router.push('/member-management')
|
|
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
const goToAPI = () => {
|
|
|
|
|
|
router.push('/api-management')
|
|
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
const goToTasks = () => {
|
|
|
|
|
|
router.push('/generate-task-record')
|
|
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
2025-12-20 15:24:58 +08:00
|
|
|
|
const goToErrorStats = () => {
|
|
|
|
|
|
router.push('/admin/error-statistics')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
const goToSettings = () => {
|
|
|
|
|
|
router.push('/system-settings')
|
|
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
2025-11-21 16:10:00 +08:00
|
|
|
|
// 普通用户模式的导航函数
|
|
|
|
|
|
const goToProfile = () => {
|
|
|
|
|
|
router.push('/profile')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const goToWorks = () => {
|
|
|
|
|
|
router.push('/works')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理用户头像下拉菜单
|
|
|
|
|
|
const handleUserCommand = (command) => {
|
|
|
|
|
|
if (command === 'exitAdmin') {
|
|
|
|
|
|
// 退出后台,返回个人首页
|
|
|
|
|
|
router.push('/profile')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
// 获取状态样式类
|
|
|
|
|
|
const getStatusClass = (status) => {
|
2025-10-21 16:50:33 +08:00
|
|
|
|
const statusMap = {
|
2025-11-07 19:09:50 +08:00
|
|
|
|
'PENDING': 'pending',
|
|
|
|
|
|
'CONFIRMED': 'confirmed',
|
|
|
|
|
|
'PAID': 'paid',
|
|
|
|
|
|
'PROCESSING': 'processing',
|
|
|
|
|
|
'SHIPPED': 'shipped',
|
|
|
|
|
|
'DELIVERED': 'delivered',
|
|
|
|
|
|
'COMPLETED': 'completed',
|
|
|
|
|
|
'CANCELLED': 'cancelled',
|
|
|
|
|
|
'REFUNDED': 'refunded'
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
return statusMap[status] || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取状态文本
|
|
|
|
|
|
const getStatusText = (status) => {
|
|
|
|
|
|
const statusMap = {
|
|
|
|
|
|
'PENDING': '待支付',
|
|
|
|
|
|
'CONFIRMED': '已确认',
|
|
|
|
|
|
'PAID': '已支付',
|
|
|
|
|
|
'PROCESSING': '处理中',
|
|
|
|
|
|
'SHIPPED': '已发货',
|
|
|
|
|
|
'DELIVERED': '已送达',
|
|
|
|
|
|
'COMPLETED': '已完成',
|
|
|
|
|
|
'CANCELLED': '已取消',
|
|
|
|
|
|
'REFUNDED': '已退款'
|
|
|
|
|
|
}
|
|
|
|
|
|
return statusMap[status] || status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取订单类型文本
|
|
|
|
|
|
const getOrderTypeText = (orderType) => {
|
|
|
|
|
|
const typeMap = {
|
|
|
|
|
|
'PRODUCT': '商品订单',
|
|
|
|
|
|
'SERVICE': '服务订单',
|
|
|
|
|
|
'SUBSCRIPTION': '订阅订单',
|
|
|
|
|
|
'DIGITAL': '数字商品',
|
|
|
|
|
|
'PHYSICAL': '实体商品'
|
|
|
|
|
|
}
|
|
|
|
|
|
return typeMap[orderType] || orderType
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化日期
|
|
|
|
|
|
const formatDate = (dateString) => {
|
|
|
|
|
|
const date = new Date(dateString)
|
|
|
|
|
|
return date.toLocaleDateString('zh-CN', {
|
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
|
month: '2-digit',
|
|
|
|
|
|
day: '2-digit',
|
|
|
|
|
|
hour: '2-digit',
|
|
|
|
|
|
minute: '2-digit'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 表格操作
|
|
|
|
|
|
const isAllSelected = computed(() => {
|
|
|
|
|
|
return orders.value.length > 0 && selectedOrders.value.length === orders.value.length
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const totalPages = computed(() => {
|
|
|
|
|
|
return Math.ceil(totalOrders.value / pageSize.value)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const visiblePages = computed(() => {
|
|
|
|
|
|
const pages = []
|
|
|
|
|
|
const total = totalPages.value
|
|
|
|
|
|
const current = currentPage.value
|
|
|
|
|
|
|
|
|
|
|
|
if (total <= 7) {
|
|
|
|
|
|
for (let i = 1; i <= total; i++) {
|
|
|
|
|
|
pages.push(i)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (current <= 3) {
|
|
|
|
|
|
for (let i = 1; i <= 5; i++) {
|
|
|
|
|
|
pages.push(i)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (current >= total - 2) {
|
|
|
|
|
|
for (let i = total - 4; i <= total; i++) {
|
|
|
|
|
|
pages.push(i)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
for (let i = current - 2; i <= current + 2; i++) {
|
|
|
|
|
|
pages.push(i)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return pages
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const toggleAllSelection = () => {
|
|
|
|
|
|
if (isAllSelected.value) {
|
|
|
|
|
|
selectedOrders.value = []
|
|
|
|
|
|
} else {
|
|
|
|
|
|
selectedOrders.value = [...orders.value]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const toggleOrderSelection = (order) => {
|
|
|
|
|
|
const index = selectedOrders.value.findIndex(o => o.id === order.id)
|
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
|
selectedOrders.value.splice(index, 1)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
selectedOrders.value.push(order)
|
|
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
const prevPage = () => {
|
|
|
|
|
|
if (currentPage.value > 1) {
|
|
|
|
|
|
currentPage.value--
|
|
|
|
|
|
fetchOrders()
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
const nextPage = () => {
|
|
|
|
|
|
if (currentPage.value < totalPages.value) {
|
|
|
|
|
|
currentPage.value++
|
|
|
|
|
|
fetchOrders()
|
|
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
const goToPage = (page) => {
|
|
|
|
|
|
currentPage.value = page
|
|
|
|
|
|
fetchOrders()
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 21:06:16 +08:00
|
|
|
|
// 订单详情弹窗相关
|
|
|
|
|
|
const orderDetailVisible = ref(false)
|
|
|
|
|
|
const currentOrderDetail = ref(null)
|
2025-12-20 15:24:58 +08:00
|
|
|
|
const editingStatus = ref('')
|
2025-12-05 21:06:16 +08:00
|
|
|
|
|
|
|
|
|
|
const viewOrder = async (order) => {
|
|
|
|
|
|
currentOrderDetail.value = order
|
2025-12-20 15:24:58 +08:00
|
|
|
|
editingStatus.value = order.status || 'PENDING'
|
2025-12-05 21:06:16 +08:00
|
|
|
|
orderDetailVisible.value = true
|
2025-11-07 19:09:50 +08:00
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
2025-12-20 15:24:58 +08:00
|
|
|
|
// 修改订单状态
|
|
|
|
|
|
const handleStatusChange = async (newStatus) => {
|
|
|
|
|
|
if (!currentOrderDetail.value) return
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await updateOrderStatus(currentOrderDetail.value.id, newStatus)
|
|
|
|
|
|
if (response.data?.success) {
|
|
|
|
|
|
// 更新当前详情
|
|
|
|
|
|
currentOrderDetail.value.status = newStatus
|
|
|
|
|
|
// 更新列表中的订单
|
|
|
|
|
|
const orderIndex = orders.value.findIndex(o => o.id === currentOrderDetail.value.id)
|
|
|
|
|
|
if (orderIndex > -1) {
|
|
|
|
|
|
orders.value[orderIndex].status = newStatus
|
|
|
|
|
|
}
|
|
|
|
|
|
ElMessage.success('订单状态更新成功')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error(response.data?.message || '更新失败')
|
|
|
|
|
|
editingStatus.value = currentOrderDetail.value.status // 恢复原状态
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('更新订单状态失败:', error)
|
|
|
|
|
|
ElMessage.error('更新订单状态失败')
|
|
|
|
|
|
editingStatus.value = currentOrderDetail.value.status // 恢复原状态
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
const deleteOrder = async (order) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
2025-12-08 13:54:02 +08:00
|
|
|
|
t('orders.confirmDeleteOrder', { orderNumber: order.orderNumber || order.id }),
|
|
|
|
|
|
t('orders.confirmDeleteTitle'),
|
2025-11-07 19:09:50 +08:00
|
|
|
|
{
|
2025-12-08 13:54:02 +08:00
|
|
|
|
confirmButtonText: t('common.confirm'),
|
|
|
|
|
|
cancelButtonText: t('common.cancel'),
|
2025-11-07 19:09:50 +08:00
|
|
|
|
type: 'warning',
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-05 21:06:16 +08:00
|
|
|
|
const response = await deleteOrderAPI(order.id)
|
2025-12-08 13:54:02 +08:00
|
|
|
|
console.log('Delete order response:', response)
|
2025-11-07 19:09:50 +08:00
|
|
|
|
|
2025-12-05 21:06:16 +08:00
|
|
|
|
// 检查响应状态
|
|
|
|
|
|
if (response.data?.success) {
|
|
|
|
|
|
const index = orders.value.findIndex(o => o.id === order.id)
|
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
|
orders.value.splice(index, 1)
|
|
|
|
|
|
totalOrders.value--
|
|
|
|
|
|
}
|
2025-12-08 13:54:02 +08:00
|
|
|
|
ElMessage.success(t('orders.deleteSuccess'))
|
2025-12-05 21:06:16 +08:00
|
|
|
|
} else {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
ElMessage.error(response.data?.message || t('orders.deleteFailed'))
|
2025-11-07 19:09:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
console.error('Delete failed:', error)
|
|
|
|
|
|
ElMessage.error(t('orders.deleteFailed') + ': ' + (error.message || t('dashboard.unknownError')))
|
2025-11-07 19:09:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const deleteSelected = async () => {
|
|
|
|
|
|
if (selectedOrders.value.length === 0) {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
ElMessage.warning(t('orders.pleaseSelectOrders'))
|
2025-11-07 19:09:50 +08:00
|
|
|
|
return
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
2025-12-08 13:54:02 +08:00
|
|
|
|
t('orders.confirmBatchDelete', { count: selectedOrders.value.length }),
|
|
|
|
|
|
t('orders.batchDeleteTitle'),
|
2025-11-07 19:09:50 +08:00
|
|
|
|
{
|
2025-12-08 13:54:02 +08:00
|
|
|
|
confirmButtonText: t('common.confirm'),
|
|
|
|
|
|
cancelButtonText: t('common.cancel'),
|
2025-11-07 19:09:50 +08:00
|
|
|
|
type: 'warning',
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const ids = selectedOrders.value.map(o => o.id)
|
2025-12-05 21:06:16 +08:00
|
|
|
|
const response = await deleteOrders(ids)
|
2025-12-08 13:54:02 +08:00
|
|
|
|
console.log('Batch delete orders response:', response)
|
2025-11-07 19:09:50 +08:00
|
|
|
|
|
2025-12-05 21:06:16 +08:00
|
|
|
|
if (response.data?.success) {
|
|
|
|
|
|
orders.value = orders.value.filter(o => !ids.includes(o.id))
|
|
|
|
|
|
totalOrders.value -= response.data?.deletedCount || ids.length
|
|
|
|
|
|
selectedOrders.value = []
|
2025-12-08 13:54:02 +08:00
|
|
|
|
ElMessage.success(response.data?.message || t('orders.batchDeleteSuccess'))
|
2025-12-05 21:06:16 +08:00
|
|
|
|
} else {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
ElMessage.error(response.data?.message || t('orders.batchDeleteFailed'))
|
2025-12-05 21:06:16 +08:00
|
|
|
|
}
|
2025-11-07 19:09:50 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
console.error('Batch delete failed:', error)
|
|
|
|
|
|
ElMessage.error(t('orders.batchDeleteFailed') + ': ' + (error.message || t('dashboard.unknownError')))
|
2025-11-07 19:09:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取订单列表
|
|
|
|
|
|
const fetchOrders = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
loading.value = true
|
2025-11-21 16:10:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 根据模式调用不同的 API
|
|
|
|
|
|
const apiFunction = isAdminMode.value ? getAdminOrders : getOrders
|
|
|
|
|
|
const response = await apiFunction({
|
2025-11-07 19:09:50 +08:00
|
|
|
|
page: currentPage.value - 1,
|
|
|
|
|
|
size: pageSize.value,
|
2025-12-05 21:06:16 +08:00
|
|
|
|
status: filters.status || undefined,
|
|
|
|
|
|
paymentMethod: filters.paymentMethod || undefined,
|
|
|
|
|
|
search: filters.search || searchText.value || undefined
|
2025-10-21 16:50:33 +08:00
|
|
|
|
})
|
2025-11-21 16:10:00 +08:00
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
console.log('获取订单列表响应:', response)
|
2025-11-21 16:10:00 +08:00
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
// 后端返回格式: { success: true, data: { content: [...], totalElements: 100, ... } }
|
|
|
|
|
|
// axios会将响应包装在response.data中
|
|
|
|
|
|
const responseData = response?.data || response || {}
|
|
|
|
|
|
console.log('解析后的响应数据:', responseData)
|
2025-11-21 16:10:00 +08:00
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
if (responseData.success && responseData.data) {
|
|
|
|
|
|
const pageData = responseData.data
|
|
|
|
|
|
if (pageData.content) {
|
|
|
|
|
|
// Spring Data Page格式
|
|
|
|
|
|
orders.value = pageData.content || []
|
|
|
|
|
|
totalOrders.value = pageData.totalElements || 0
|
|
|
|
|
|
console.log('设置后的订单列表:', orders.value)
|
|
|
|
|
|
} else if (Array.isArray(pageData)) {
|
|
|
|
|
|
// 直接数组格式
|
|
|
|
|
|
orders.value = pageData
|
|
|
|
|
|
totalOrders.value = pageData.length
|
|
|
|
|
|
} else {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
console.error('API data format error: data is not Page object or array', pageData)
|
|
|
|
|
|
ElMessage.error(t('orders.apiDataFormatError'))
|
2025-11-07 19:09:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else if (responseData.content) {
|
|
|
|
|
|
// 直接返回Page对象(没有success包装)
|
|
|
|
|
|
orders.value = responseData.content || []
|
|
|
|
|
|
totalOrders.value = responseData.totalElements || 0
|
|
|
|
|
|
} else if (responseData.list) {
|
|
|
|
|
|
// 列表格式
|
|
|
|
|
|
orders.value = responseData.list || []
|
|
|
|
|
|
totalOrders.value = responseData.total || 0
|
|
|
|
|
|
} else {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
console.error('API data format error:', responseData)
|
|
|
|
|
|
ElMessage.error(t('orders.apiDataFormatError'))
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
2025-11-21 16:10:00 +08:00
|
|
|
|
|
2025-10-21 16:50:33 +08:00
|
|
|
|
} catch (error) {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
console.error('Get orders list failed:', error)
|
|
|
|
|
|
ElMessage.error(t('orders.loadOrdersFailed') + ': ' + (error.message || t('dashboard.unknownError')))
|
2025-10-21 16:50:33 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 筛选变化
|
|
|
|
|
|
const handleFilterChange = () => {
|
2025-11-07 19:09:50 +08:00
|
|
|
|
currentPage.value = 1
|
2025-10-21 16:50:33 +08:00
|
|
|
|
fetchOrders()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
2025-10-21 16:50:33 +08:00
|
|
|
|
fetchOrders()
|
2025-11-13 17:01:39 +08:00
|
|
|
|
fetchSystemStats()
|
2025-11-07 19:09:50 +08:00
|
|
|
|
})
|
2025-11-13 17:01:39 +08:00
|
|
|
|
|
2025-12-08 13:54:02 +08:00
|
|
|
|
// 获取系统统计数据(当天访问人数和系统运行时间)
|
2025-11-13 17:01:39 +08:00
|
|
|
|
const fetchSystemStats = async () => {
|
|
|
|
|
|
try {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
const response = await fetch('/api/admin/online-stats', {
|
|
|
|
|
|
headers: {
|
2025-12-11 13:32:24 +08:00
|
|
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
2025-12-08 13:54:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
const data = await response.json()
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
onlineUsers.value = data.todayVisitors || 0
|
|
|
|
|
|
systemUptime.value = data.uptime || t('systemSettings.unknown')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
onlineUsers.value = '0'
|
|
|
|
|
|
systemUptime.value = t('systemSettings.unknown')
|
|
|
|
|
|
}
|
2025-11-13 17:01:39 +08:00
|
|
|
|
} catch (error) {
|
2025-12-08 13:54:02 +08:00
|
|
|
|
console.error('Get online stats failed:', error)
|
|
|
|
|
|
onlineUsers.value = '0'
|
|
|
|
|
|
systemUptime.value = t('systemSettings.unknown')
|
2025-11-13 17:01:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.admin-orders {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
/* 左侧导航栏 */
|
|
|
|
|
|
.sidebar {
|
|
|
|
|
|
width: 240px;
|
2025-11-21 16:10:00 +08:00
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
border-right: 1px solid #e5e7eb;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
padding: 24px 0;
|
|
|
|
|
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.logo {
|
2025-11-13 17:01:39 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2025-11-21 16:10:00 +08:00
|
|
|
|
padding: 0 24px;
|
|
|
|
|
|
margin-bottom: 32px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 16:10:00 +08:00
|
|
|
|
.logo img {
|
2025-11-13 17:01:39 +08:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: auto;
|
2025-11-21 16:10:00 +08:00
|
|
|
|
max-width: 180px;
|
2025-11-13 17:01:39 +08:00
|
|
|
|
object-fit: contain;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.nav-menu {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 0 16px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.nav-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s ease;
|
2025-11-21 16:10:00 +08:00
|
|
|
|
color: #4b5563;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.nav-item:hover {
|
|
|
|
|
|
background: #f3f4f6;
|
2025-11-21 16:10:00 +08:00
|
|
|
|
color: #1f2937;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.nav-item.active {
|
2025-11-21 16:10:00 +08:00
|
|
|
|
background: rgba(64, 158, 255, 0.15);
|
|
|
|
|
|
color: #409EFF;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.nav-item .el-icon {
|
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
|
font-size: 18px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.nav-item span {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.sidebar-footer {
|
|
|
|
|
|
padding: 20px;
|
2025-11-21 16:10:00 +08:00
|
|
|
|
border-top: 1px solid #e5e7eb;
|
|
|
|
|
|
background: #f9fafb;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
margin-top: auto;
|
2025-11-21 16:10:00 +08:00
|
|
|
|
color: #4b5563;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.online-users,
|
|
|
|
|
|
.system-uptime {
|
|
|
|
|
|
font-size: 13px;
|
2025-11-21 16:10:00 +08:00
|
|
|
|
color: #4b5563;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
line-height: 1.5;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 16:10:00 +08:00
|
|
|
|
.sidebar-footer .highlight {
|
|
|
|
|
|
color: #409EFF;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.highlight {
|
2025-11-21 16:10:00 +08:00
|
|
|
|
color: #409EFF;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
font-weight: 600;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
/* 主内容区域 */
|
|
|
|
|
|
.main-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 顶部搜索栏 */
|
|
|
|
|
|
.top-header {
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border-bottom: 1px solid #e9ecef;
|
|
|
|
|
|
padding: 16px 24px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-bar {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-icon {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 12px;
|
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-input {
|
|
|
|
|
|
width: 300px;
|
|
|
|
|
|
padding: 10px 12px 10px 40px;
|
|
|
|
|
|
border: 1px solid #d1d5db;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
transition: border-color 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-input:focus {
|
|
|
|
|
|
border-color: #3b82f6;
|
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.search-input::placeholder {
|
|
|
|
|
|
color: #9ca3af;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.header-actions {
|
2025-10-21 16:50:33 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
gap: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-avatar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
cursor: pointer;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
transition: background 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-avatar:hover {
|
|
|
|
|
|
background: #f3f4f6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-avatar img {
|
|
|
|
|
|
width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
object-fit: cover;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.user-avatar .arrow-down {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #6b7280;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
/* 订单内容区域 */
|
|
|
|
|
|
.order-content {
|
|
|
|
|
|
padding: 24px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
flex: 1;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
background: white;
|
|
|
|
|
|
margin: 24px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.content-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content-header h2 {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1e293b;
|
|
|
|
|
|
margin: 0;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.selection-info {
|
2025-10-21 16:50:33 +08:00
|
|
|
|
font-size: 14px;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
color: #64748b;
|
|
|
|
|
|
background: #f1f5f9;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
border-radius: 6px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.table-toolbar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 16px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.toolbar-left {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 16px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-08 13:54:02 +08:00
|
|
|
|
/* 确保下拉框选中值可见 */
|
|
|
|
|
|
.toolbar-left :deep(.el-select) {
|
|
|
|
|
|
min-width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-left :deep(.el-select__wrapper) {
|
|
|
|
|
|
color: #374151 !important;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
min-height: 32px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-left :deep(.el-select__wrapper *) {
|
|
|
|
|
|
color: #374151 !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-left :deep(.el-select__selection) {
|
|
|
|
|
|
display: flex !important;
|
|
|
|
|
|
align-items: center !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-left :deep(.el-select__selected-item) {
|
|
|
|
|
|
color: #374151 !important;
|
|
|
|
|
|
display: inline-flex !important;
|
|
|
|
|
|
visibility: visible !important;
|
|
|
|
|
|
opacity: 1 !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-left :deep(.el-select__placeholder) {
|
|
|
|
|
|
color: #9ca3af !important;
|
|
|
|
|
|
opacity: 1 !important;
|
|
|
|
|
|
visibility: visible !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.table-container {
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
|
margin-bottom: 24px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.order-table {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.order-table thead {
|
|
|
|
|
|
background: #f9fafb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.order-table th {
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.order-table td {
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
border-bottom: 1px solid #f3f4f6;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.table-row:hover {
|
|
|
|
|
|
background: #f9fafb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-col {
|
|
|
|
|
|
width: 50px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-col input[type="checkbox"] {
|
|
|
|
|
|
width: 16px;
|
|
|
|
|
|
height: 16px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-tag {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
padding: 4px 12px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 12px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
font-weight: 500;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-tag.completed {
|
|
|
|
|
|
background: #10b981;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-tag.cancelled {
|
|
|
|
|
|
background: #ef4444;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-tag.processing {
|
|
|
|
|
|
background: #3b82f6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-tag.pending {
|
|
|
|
|
|
background: #f59e0b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-tag.paid {
|
|
|
|
|
|
background: #6366f1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-20 15:24:58 +08:00
|
|
|
|
.status-tag.refunded {
|
|
|
|
|
|
background: #f97316;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.action-link {
|
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-link:last-child {
|
|
|
|
|
|
margin-right: 0;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.pagination-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
margin-top: 24px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.pagination {
|
2025-10-21 16:50:33 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
gap: 4px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.page-arrow {
|
|
|
|
|
|
width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
border: 1px solid #d1d5db;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
border-radius: 4px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
font-size: 14px;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
transition: all 0.2s ease;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.page-arrow:hover:not(.disabled) {
|
|
|
|
|
|
background: #f3f4f6;
|
|
|
|
|
|
border-color: #9ca3af;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.page-arrow.disabled {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-btn {
|
|
|
|
|
|
min-width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
padding: 0 12px;
|
|
|
|
|
|
border: 1px solid #d1d5db;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
transition: all 0.2s ease;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
display: flex;
|
2025-11-07 19:09:50 +08:00
|
|
|
|
align-items: center;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.page-btn:hover:not(:disabled) {
|
|
|
|
|
|
background: #f3f4f6;
|
|
|
|
|
|
border-color: #9ca3af;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-btn.active {
|
|
|
|
|
|
background: #3b82f6;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-color: #3b82f6;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.page-btn:disabled {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-ellipsis {
|
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 17:01:39 +08:00
|
|
|
|
/* 未支付文本样式 */
|
|
|
|
|
|
.text-muted {
|
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
/* 响应式设计 */
|
|
|
|
|
|
@media (max-width: 1024px) {
|
|
|
|
|
|
.admin-orders {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sidebar {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-menu {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-item {
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
margin-right: 16px;
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sidebar-footer {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-input {
|
|
|
|
|
|
width: 200px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
.order-content {
|
|
|
|
|
|
padding: 16px;
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 21:06:16 +08:00
|
|
|
|
|
|
|
|
|
|
/* 订单详情弹窗样式 */
|
|
|
|
|
|
.order-detail-content {
|
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-section {
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-section:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-section h4 {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1e293b;
|
|
|
|
|
|
margin: 0 0 16px 0;
|
|
|
|
|
|
padding-bottom: 8px;
|
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
gap: 12px 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-item .label {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
min-width: 80px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-item .value {
|
|
|
|
|
|
color: #1e293b;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-item .value.amount {
|
|
|
|
|
|
color: #f59e0b;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.description-text {
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 40px;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-container .el-icon {
|
|
|
|
|
|
font-size: 32px;
|
|
|
|
|
|
color: #3b82f6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.order-detail-dialog .el-dialog__header) {
|
|
|
|
|
|
padding: 16px 20px;
|
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
|
margin-right: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.order-detail-dialog .el-dialog__body) {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.order-detail-dialog .el-dialog__footer) {
|
|
|
|
|
|
padding: 12px 20px;
|
|
|
|
|
|
border-top: 1px solid #e5e7eb;
|
|
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
|