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-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-11-13 17:01:39 +08:00
|
|
|
|
{{ $t('nav.onlineUsers') }}: <span class="highlight">{{ onlineUsers }}</span>
|
2025-11-07 19:09:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<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.confirmed')" value="CONFIRMED" />
|
|
|
|
|
|
<el-option :label="$t('orders.paid')" value="PAID" />
|
|
|
|
|
|
<el-option :label="$t('orders.processing')" value="PROCESSING" />
|
|
|
|
|
|
<el-option :label="$t('orders.shipped')" value="SHIPPED" />
|
|
|
|
|
|
<el-option :label="$t('orders.delivered')" value="DELIVERED" />
|
|
|
|
|
|
<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-11-13 17:01:39 +08:00
|
|
|
|
<el-select v-model="filters.type" :placeholder="$t('orders.allTypes')" size="small" @change="handleFilterChange">
|
|
|
|
|
|
<el-option :label="$t('orders.allTypes')" value="" />
|
2025-11-07 19:09:50 +08:00
|
|
|
|
<el-option label="商品订单" value="PRODUCT" />
|
|
|
|
|
|
<el-option label="服务订单" value="SERVICE" />
|
|
|
|
|
|
<el-option label="订阅订单" value="SUBSCRIPTION" />
|
|
|
|
|
|
<el-option label="数字商品" value="DIGITAL" />
|
|
|
|
|
|
<el-option label="实体商品" value="PHYSICAL" />
|
|
|
|
|
|
</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-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-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,
|
|
|
|
|
|
Wallet
|
|
|
|
|
|
} from '@element-plus/icons-vue'
|
2025-11-21 16:10:00 +08:00
|
|
|
|
import { getOrders, getAdminOrders, getOrderStats, deleteOrder as deleteOrderAPI, deleteOrders } 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()
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否为管理员模式(基于路由路径)
|
|
|
|
|
|
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')
|
|
|
|
|
|
const systemUptime = ref('加载中...')
|
|
|
|
|
|
|
2025-10-21 16:50:33 +08:00
|
|
|
|
// 筛选条件
|
|
|
|
|
|
const filters = reactive({
|
|
|
|
|
|
status: '',
|
2025-11-07 19:09:50 +08:00
|
|
|
|
type: '',
|
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-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-11-07 19:09:50 +08:00
|
|
|
|
const viewOrder = (order) => {
|
|
|
|
|
|
router.push(`/orders/${order.id}`)
|
|
|
|
|
|
}
|
2025-10-21 16:50:33 +08:00
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
const deleteOrder = async (order) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
|
`确定要删除订单 ${order.orderNumber || order.id} 吗?`,
|
|
|
|
|
|
'确认删除',
|
|
|
|
|
|
{
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning',
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
await deleteOrderAPI(order.id)
|
|
|
|
|
|
|
|
|
|
|
|
const index = orders.value.findIndex(o => o.id === order.id)
|
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
|
orders.value.splice(index, 1)
|
|
|
|
|
|
totalOrders.value--
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
console.error('删除失败:', error)
|
|
|
|
|
|
ElMessage.error('删除失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const deleteSelected = async () => {
|
|
|
|
|
|
if (selectedOrders.value.length === 0) {
|
|
|
|
|
|
ElMessage.warning('请先选择要删除的订单')
|
|
|
|
|
|
return
|
2025-10-21 16:50:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
|
`确定要删除选中的 ${selectedOrders.value.length} 个订单吗?`,
|
|
|
|
|
|
'批量删除',
|
|
|
|
|
|
{
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning',
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const ids = selectedOrders.value.map(o => o.id)
|
|
|
|
|
|
await deleteOrders(ids)
|
|
|
|
|
|
|
|
|
|
|
|
orders.value = orders.value.filter(o => !ids.includes(o.id))
|
|
|
|
|
|
totalOrders.value -= ids.length
|
|
|
|
|
|
selectedOrders.value = []
|
|
|
|
|
|
|
|
|
|
|
|
ElMessage.success('批量删除成功')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
console.error('批量删除失败:', error)
|
|
|
|
|
|
ElMessage.error('批量删除失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
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-10-21 16:50:33 +08:00
|
|
|
|
status: filters.status,
|
2025-11-07 19:09:50 +08:00
|
|
|
|
search: filters.search || searchText.value
|
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 {
|
|
|
|
|
|
console.error('API返回数据格式错误: data不是Page对象也不是数组', pageData)
|
|
|
|
|
|
ElMessage.error('API返回数据格式错误')
|
|
|
|
|
|
}
|
|
|
|
|
|
} 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 {
|
|
|
|
|
|
console.error('API返回数据格式错误:', responseData)
|
|
|
|
|
|
ElMessage.error('API返回数据格式错误')
|
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-11-07 19:09:50 +08:00
|
|
|
|
console.error('获取订单列表失败:', error)
|
|
|
|
|
|
ElMessage.error('获取订单列表失败: ' + (error.message || '未知错误'))
|
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
|
|
|
|
|
|
|
|
|
|
// 获取系统统计数据
|
|
|
|
|
|
const fetchSystemStats = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 临时使用计算值,后续可以从API获取
|
|
|
|
|
|
// 计算在线用户数(这里简化处理)
|
|
|
|
|
|
const randomOnline = Math.floor(Math.random() * 50) + 10
|
|
|
|
|
|
onlineUsers.value = `${randomOnline}/500`
|
|
|
|
|
|
|
|
|
|
|
|
// 计算系统运行时间(基于当前时间简单模拟)
|
|
|
|
|
|
const hours = new Date().getHours()
|
|
|
|
|
|
const minutes = new Date().getMinutes()
|
|
|
|
|
|
systemUptime.value = `${hours}小时${minutes}分`
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取系统统计失败:', error)
|
|
|
|
|
|
onlineUsers.value = '0/500'
|
|
|
|
|
|
systemUptime.value = '未知'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
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-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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-11-07 19:09:50 +08:00
|
|
|
|
|