Files
AIGC/demo/frontend/src/views/AdminOrders.vue

1289 lines
33 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div class="admin-orders">
<!-- 左侧导航栏 -->
<aside class="sidebar">
<div class="logo">
<img src="/images/backgrounds/logo-admin.svg" alt="Logo" />
</div>
<nav class="nav-menu">
<div v-if="isAdminMode" class="nav-item" @click="goToDashboard">
<el-icon><Grid /></el-icon>
<span>{{ $t('nav.dashboard') }}</span>
</div>
<div v-if="isAdminMode" class="nav-item" @click="goToMembers">
<el-icon><User /></el-icon>
<span>{{ $t('nav.members') }}</span>
</div>
<div class="nav-item active">
<el-icon><ShoppingCart /></el-icon>
<span>{{ $t('nav.orders') }}</span>
</div>
<div v-if="isAdminMode" class="nav-item" @click="goToAPI">
<el-icon><Document /></el-icon>
<span>{{ $t('nav.apiManagement') }}</span>
</div>
<div v-if="isAdminMode" class="nav-item" @click="goToTasks">
<el-icon><Document /></el-icon>
<span>{{ $t('nav.tasks') }}</span>
</div>
<div v-if="isAdminMode" class="nav-item" @click="goToErrorStats">
<el-icon><Warning /></el-icon>
<span>错误统计</span>
</div>
<div v-if="isAdminMode" class="nav-item" @click="goToSettings">
<el-icon><Setting /></el-icon>
<span>{{ $t('nav.systemSettings') }}</span>
</div>
<!-- 普通用户模式的导航 -->
<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>
</nav>
<div class="sidebar-footer">
<div class="online-users">
{{ $t('nav.todayVisitors') }}: <span class="highlight">{{ onlineUsers }}</span>
</div>
<div class="system-uptime">
{{ $t('nav.systemUptime') }}: <span class="highlight">{{ systemUptime }}</span>
</div>
</div>
</aside>
<!-- 主内容区域 -->
<main class="main-content">
<!-- 顶部搜索栏 -->
<header class="top-header">
<div class="search-bar">
<el-icon class="search-icon"><Search /></el-icon>
<input type="text" :placeholder="$t('common.searchPlaceholder')" class="search-input" v-model="searchText" />
</div>
<div class="header-actions">
<LanguageSwitcher />
<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">
<img src="/images/backgrounds/avatar-default.svg" alt="用户头像" />
</div>
</div>
</header>
<!-- 订单列表内容 -->
<section class="order-content">
<div class="content-header">
<h2>{{ $t('orders.title') }}</h2>
<div class="selection-info" v-if="selectedOrders.length > 0">
{{ $t('orders.selected', { count: selectedOrders.length }) }}
</div>
</div>
<div class="table-toolbar">
<div class="toolbar-left">
<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" />
</el-select>
<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" />
</el-select>
</div>
<div class="toolbar-right">
<el-button type="danger" size="small" @click="deleteSelected" :disabled="selectedOrders.length === 0">
<el-icon><Delete /></el-icon>
{{ $t('common.delete') }}
</el-button>
</div>
</div>
<div class="table-container">
<table class="order-table">
<thead>
<tr>
<th class="checkbox-col">
<input type="checkbox" @change="toggleAllSelection" :checked="isAllSelected" />
</th>
<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>
</tr>
</thead>
<tbody>
<tr v-for="order in orders" :key="order.id" class="table-row">
<td class="checkbox-col">
<input
type="checkbox"
:checked="selectedOrders.some(o => o.id === order.id)"
@change="toggleOrderSelection(order)" />
</td>
<td>{{ order.orderNumber || order.id }}</td>
<td>{{ order.user?.username || $t('orders.unpaid') }}</td>
<td>{{ order.currency || '¥' }}{{ order.totalAmount || 0 }}</td>
<td>
<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>
</td>
<td>
<span class="status-tag" :class="getStatusClass(order.status)">
{{ getStatusText(order.status) }}
</span>
</td>
<td>{{ formatDate(order.createdAt) }}</td>
<td>
<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>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination-container">
<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>
</template>
<el-icon class="page-arrow" @click="nextPage" :class="{ disabled: currentPage === totalPages }"><ArrowRight /></el-icon>
</div>
</div>
</section>
</main>
<!-- 订单详情弹窗 -->
<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>
<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)">
{{ 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>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useI18n } from 'vue-i18n'
import {
Grid,
User,
ShoppingCart,
Document,
Setting,
Search,
ArrowDown,
ArrowLeft,
ArrowRight,
Delete,
CreditCard,
Wallet,
Loading,
Warning
} from '@element-plus/icons-vue'
import { getOrders, getAdminOrders, getOrderStats, deleteOrder as deleteOrderAPI, deleteOrders, updateOrderStatus } from '@/api/orders'
import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
const router = useRouter()
const route = useRoute()
const { t } = useI18n()
// 判断是否为管理员模式(基于路由路径)
const isAdminMode = computed(() => route.path.includes('/admin/'))
const loading = ref(false)
const orders = ref([])
const searchText = ref('')
const selectedOrders = ref([])
const currentPage = ref(1)
const pageSize = ref(10)
const totalOrders = ref(0)
// 系统状态数据
const onlineUsers = ref('0/500')
const systemUptime = ref(t('nav.loading'))
// 筛选条件
const filters = reactive({
status: '',
paymentMethod: '',
search: ''
})
// 导航功能
const goToDashboard = () => {
router.push('/admin/dashboard')
}
const goToMembers = () => {
router.push('/member-management')
}
const goToAPI = () => {
router.push('/api-management')
}
const goToTasks = () => {
router.push('/generate-task-record')
}
const goToErrorStats = () => {
router.push('/admin/error-statistics')
}
const goToSettings = () => {
router.push('/system-settings')
}
// 普通用户模式的导航函数
const goToProfile = () => {
router.push('/profile')
}
const goToWorks = () => {
router.push('/works')
}
// 处理用户头像下拉菜单
const handleUserCommand = (command) => {
if (command === 'exitAdmin') {
// 退出后台,返回个人首页
router.push('/profile')
}
}
// 获取状态样式类
const getStatusClass = (status) => {
const statusMap = {
'PENDING': 'pending',
'CONFIRMED': 'confirmed',
'PAID': 'paid',
'PROCESSING': 'processing',
'SHIPPED': 'shipped',
'DELIVERED': 'delivered',
'COMPLETED': 'completed',
'CANCELLED': 'cancelled',
'REFUNDED': 'refunded'
}
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'
})
}
// 表格操作
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)
}
}
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--
fetchOrders()
}
}
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++
fetchOrders()
}
}
const goToPage = (page) => {
currentPage.value = page
fetchOrders()
}
// 订单详情弹窗相关
const orderDetailVisible = ref(false)
const currentOrderDetail = ref(null)
const editingStatus = ref('')
const viewOrder = async (order) => {
currentOrderDetail.value = order
editingStatus.value = order.status || 'PENDING'
orderDetailVisible.value = true
}
// 修改订单状态
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 // 恢复原状态
}
}
const deleteOrder = async (order) => {
try {
await ElMessageBox.confirm(
t('orders.confirmDeleteOrder', { orderNumber: order.orderNumber || order.id }),
t('orders.confirmDeleteTitle'),
{
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
type: 'warning',
}
)
const response = await deleteOrderAPI(order.id)
console.log('Delete order response:', response)
// 检查响应状态
if (response.data?.success) {
const index = orders.value.findIndex(o => o.id === order.id)
if (index > -1) {
orders.value.splice(index, 1)
totalOrders.value--
}
ElMessage.success(t('orders.deleteSuccess'))
} else {
ElMessage.error(response.data?.message || t('orders.deleteFailed'))
}
} catch (error) {
if (error !== 'cancel') {
console.error('Delete failed:', error)
ElMessage.error(t('orders.deleteFailed') + ': ' + (error.message || t('dashboard.unknownError')))
}
}
}
const deleteSelected = async () => {
if (selectedOrders.value.length === 0) {
ElMessage.warning(t('orders.pleaseSelectOrders'))
return
}
try {
await ElMessageBox.confirm(
t('orders.confirmBatchDelete', { count: selectedOrders.value.length }),
t('orders.batchDeleteTitle'),
{
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
type: 'warning',
}
)
const ids = selectedOrders.value.map(o => o.id)
const response = await deleteOrders(ids)
console.log('Batch delete orders response:', response)
if (response.data?.success) {
orders.value = orders.value.filter(o => !ids.includes(o.id))
totalOrders.value -= response.data?.deletedCount || ids.length
selectedOrders.value = []
ElMessage.success(response.data?.message || t('orders.batchDeleteSuccess'))
} else {
ElMessage.error(response.data?.message || t('orders.batchDeleteFailed'))
}
} catch (error) {
if (error !== 'cancel') {
console.error('Batch delete failed:', error)
ElMessage.error(t('orders.batchDeleteFailed') + ': ' + (error.message || t('dashboard.unknownError')))
}
}
}
// 获取订单列表
const fetchOrders = async () => {
try {
loading.value = true
// 根据模式调用不同的 API
const apiFunction = isAdminMode.value ? getAdminOrders : getOrders
const response = await apiFunction({
page: currentPage.value - 1,
size: pageSize.value,
status: filters.status || undefined,
paymentMethod: filters.paymentMethod || undefined,
search: filters.search || searchText.value || undefined
})
console.log('获取订单列表响应:', response)
// 后端返回格式: { success: true, data: { content: [...], totalElements: 100, ... } }
// axios会将响应包装在response.data中
const responseData = response?.data || response || {}
console.log('解析后的响应数据:', responseData)
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 format error: data is not Page object or array', pageData)
ElMessage.error(t('orders.apiDataFormatError'))
}
} 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 data format error:', responseData)
ElMessage.error(t('orders.apiDataFormatError'))
}
} catch (error) {
console.error('Get orders list failed:', error)
ElMessage.error(t('orders.loadOrdersFailed') + ': ' + (error.message || t('dashboard.unknownError')))
} finally {
loading.value = false
}
}
// 筛选变化
const handleFilterChange = () => {
currentPage.value = 1
fetchOrders()
}
onMounted(() => {
fetchOrders()
fetchSystemStats()
})
// 获取系统统计数据(当天访问人数和系统运行时间)
const fetchSystemStats = async () => {
try {
const response = await fetch('/api/admin/online-stats', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
})
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')
}
} catch (error) {
console.error('Get online stats failed:', error)
onlineUsers.value = '0'
systemUptime.value = t('systemSettings.unknown')
}
}
</script>
<style scoped>
.admin-orders {
display: flex;
min-height: 100vh;
background: #f8f9fa;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* 左侧导航栏 */
.sidebar {
width: 240px;
background: #ffffff;
border-right: 1px solid #e5e7eb;
display: flex;
flex-direction: column;
padding: 24px 0;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
}
.logo {
display: flex;
align-items: center;
justify-content: center;
padding: 0 24px;
margin-bottom: 32px;
}
.logo img {
width: 100%;
height: auto;
max-width: 180px;
object-fit: contain;
}
.nav-menu {
flex: 1;
padding: 0 16px;
}
.nav-item {
display: flex;
align-items: center;
padding: 12px 16px;
margin-bottom: 4px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
color: #4b5563;
font-size: 14px;
font-weight: 500;
}
.nav-item:hover {
background: #f3f4f6;
color: #1f2937;
}
.nav-item.active {
background: rgba(64, 158, 255, 0.15);
color: #409EFF;
}
.nav-item .el-icon {
margin-right: 12px;
font-size: 18px;
}
.nav-item span {
font-size: 14px;
font-weight: 500;
}
.sidebar-footer {
padding: 20px;
border-top: 1px solid #e5e7eb;
background: #f9fafb;
margin-top: auto;
color: #4b5563;
}
.online-users,
.system-uptime {
font-size: 13px;
color: #4b5563;
margin-bottom: 8px;
line-height: 1.5;
}
.sidebar-footer .highlight {
color: #409EFF;
font-weight: 600;
}
.highlight {
color: #409EFF;
font-weight: 600;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
background: #f8f9fa;
}
/* 顶部搜索栏 */
.top-header {
background: white;
border-bottom: 1px solid #e9ecef;
padding: 16px 24px;
display: flex;
align-items: center;
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);
}
.search-input::placeholder {
color: #9ca3af;
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
}
.user-avatar {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
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;
}
.user-avatar .arrow-down {
font-size: 12px;
color: #6b7280;
}
/* 订单内容区域 */
.order-content {
padding: 24px;
flex: 1;
background: white;
margin: 24px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.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;
}
.selection-info {
font-size: 14px;
color: #64748b;
background: #f1f5f9;
padding: 8px 16px;
border-radius: 6px;
}
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.toolbar-left {
display: flex;
align-items: center;
gap: 16px;
}
/* 确保下拉框选中值可见 */
.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;
}
.table-container {
background: white;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e5e7eb;
margin-bottom: 24px;
}
.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;
font-weight: 500;
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;
}
.status-tag.refunded {
background: #f97316;
}
.action-link {
margin-right: 12px;
font-size: 14px;
text-decoration: none;
}
.action-link:last-child {
margin-right: 0;
}
.pagination-container {
display: flex;
justify-content: flex-end;
margin-top: 24px;
}
.pagination {
display: flex;
align-items: center;
gap: 4px;
}
.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;
font-size: 14px;
transition: all 0.2s ease;
}
.page-arrow:hover:not(.disabled) {
background: #f3f4f6;
border-color: #9ca3af;
}
.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;
display: flex;
align-items: center;
justify-content: center;
}
.page-btn:hover:not(:disabled) {
background: #f3f4f6;
border-color: #9ca3af;
}
.page-btn.active {
background: #3b82f6;
color: white;
border-color: #3b82f6;
}
.page-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.page-ellipsis {
padding: 0 8px;
color: #6b7280;
font-size: 14px;
}
/* 未支付文本样式 */
.text-muted {
color: #9ca3af;
font-size: 13px;
}
/* 响应式设计 */
@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;
}
.order-content {
padding: 16px;
}
}
/* 订单详情弹窗样式 */
.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;
}
</style>