1517 lines
34 KiB
Plaintext
1517 lines
34 KiB
Plaintext
<template>
|
||
<AdminLayout current-page="order">
|
||
<view class="order-management">
|
||
<!-- 页面标题 -->
|
||
<view class="page-header">
|
||
<text class="page-title">订单管理</text>
|
||
<text class="page-subtitle">管理系统订单和物流信息</text>
|
||
</view>
|
||
|
||
<!-- Tab 切换栏 -->
|
||
<view class="tab-bar">
|
||
<view
|
||
v-for="tab in tabs"
|
||
:key="tab.key"
|
||
class="tab-item"
|
||
:class="{ 'active': activeTab === tab.key }"
|
||
@click="switchTab(tab.key)"
|
||
>
|
||
<text class="iconfont tab-icon">{{ tab.icon }}</text>
|
||
<text class="tab-title">{{ tab.title }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单列表Tab -->
|
||
<view v-if="activeTab === 'order-list'">
|
||
<!-- 统计卡片 -->
|
||
<view class="stats-cards">
|
||
<view class="stat-card">
|
||
<view class="stat-icon">📦</view>
|
||
<view class="stat-content">
|
||
<text class="stat-value">{{ totalOrders }}</text>
|
||
<text class="stat-label">总订单数</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">⏳</view>
|
||
<view class="stat-content">
|
||
<text class="stat-value">{{ pendingOrders }}</text>
|
||
<text class="stat-label">待处理</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">🚚</view>
|
||
<view class="stat-content">
|
||
<text class="stat-value">{{ shippingOrders }}</text>
|
||
<text class="stat-label">配送中</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">✅</view>
|
||
<view class="stat-content">
|
||
<text class="stat-value">{{ completedOrders }}</text>
|
||
<text class="stat-label">已完成</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 搜索和筛选 -->
|
||
<view class="search-section">
|
||
<view class="search-bar">
|
||
<view class="search-input-wrapper">
|
||
<input
|
||
v-model="searchKeyword"
|
||
class="search-input"
|
||
placeholder="搜索订单号、用户名、商品名称"
|
||
@confirm="handleSearch"
|
||
/>
|
||
<view class="search-btn" @click="handleSearch">
|
||
<text class="iconfont icon-search"></text>
|
||
</view>
|
||
</view>
|
||
<view class="advanced-toggle" @click="showAdvancedSearch = !showAdvancedSearch">
|
||
<text>{{ showAdvancedSearch ? '收起' : '展开' }}筛选</text>
|
||
<text class="iconfont">{{ showAdvancedSearch ? 'icon-up' : 'icon-down' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 高级搜索 -->
|
||
<view v-if="showAdvancedSearch" class="advanced-search">
|
||
<view class="filter-row">
|
||
<view class="filter-item">
|
||
<text class="filter-label">订单状态:</text>
|
||
<picker
|
||
mode="selector"
|
||
:range="statusOptions"
|
||
:value="selectedStatus"
|
||
@change="handleStatusChange"
|
||
>
|
||
<view class="picker-display">
|
||
<text>{{ statusOptions[selectedStatus] }}</text>
|
||
<text class="iconfont icon-down"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">支付方式:</text>
|
||
<picker
|
||
mode="selector"
|
||
:range="paymentOptions"
|
||
:value="selectedPayment"
|
||
@change="handlePaymentChange"
|
||
>
|
||
<view class="picker-display">
|
||
<text>{{ paymentOptions[selectedPayment] }}</text>
|
||
<text class="iconfont icon-down"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="filter-row">
|
||
<view class="filter-item">
|
||
<text class="filter-label">订单时间:</text>
|
||
<view class="date-range">
|
||
<picker
|
||
mode="date"
|
||
:value="startDate"
|
||
:start="minDate"
|
||
:end="maxDate"
|
||
@change="handleStartDateChange"
|
||
>
|
||
<view class="date-input">
|
||
<text>{{ startDate || '开始日期' }}</text>
|
||
</view>
|
||
</picker>
|
||
<text class="date-separator">-</text>
|
||
<picker
|
||
mode="date"
|
||
:value="endDate"
|
||
:start="minDate"
|
||
:end="maxDate"
|
||
@change="handleEndDateChange"
|
||
>
|
||
<view class="date-input">
|
||
<text>{{ endDate || '结束日期' }}</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="filter-actions">
|
||
<button class="btn-secondary" @click="resetFilters">重置</button>
|
||
<button class="btn-primary" @click="handleAdvancedSearch">搜索</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 操作按钮 -->
|
||
<view class="action-bar">
|
||
<view class="left-actions">
|
||
<button class="btn-primary" @click="handleExport">
|
||
<text class="iconfont icon-export"></text>
|
||
导出订单
|
||
</button>
|
||
</view>
|
||
<view class="right-actions">
|
||
<view class="select-all">
|
||
<checkbox :checked="selectAll" @change="handleSelectAll" />
|
||
<text>全选</text>
|
||
</view>
|
||
<button class="btn-danger" :disabled="selectedOrders.length === 0" @click="handleBatchDelete">
|
||
<text class="iconfont icon-delete"></text>
|
||
批量删除
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单列表 -->
|
||
<view class="order-table">
|
||
<view class="table-header">
|
||
<view class="table-row">
|
||
<view class="col-select">选择</view>
|
||
<view class="col-order">订单信息</view>
|
||
<view class="col-user">用户信息</view>
|
||
<view class="col-amount">金额</view>
|
||
<view class="col-status">状态</view>
|
||
<view class="col-time">下单时间</view>
|
||
<view class="col-actions">操作</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="table-body">
|
||
<view
|
||
v-for="order in orderList"
|
||
:key="order.id"
|
||
class="table-row"
|
||
:class="{ 'selected': selectedOrders.includes(order.id) }"
|
||
>
|
||
<view class="col-select">
|
||
<checkbox
|
||
:checked="selectedOrders.includes(order.id)"
|
||
@change="handleOrderSelect(order.id, $event)"
|
||
/>
|
||
</view>
|
||
<view class="col-order">
|
||
<view class="order-number">{{ order.orderNumber }}</view>
|
||
<view class="order-items">
|
||
<text v-for="item in order.items" :key="item.id" class="order-item">
|
||
{{ item.name }} x{{ item.quantity }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view class="col-user">
|
||
<view class="user-name">{{ order.userName }}</view>
|
||
<view class="user-phone">{{ order.userPhone }}</view>
|
||
</view>
|
||
<view class="col-amount">
|
||
<view class="order-amount">¥{{ order.totalAmount }}</view>
|
||
<view class="payment-method">{{ order.paymentMethod }}</view>
|
||
</view>
|
||
<view class="col-status">
|
||
<view class="status-badge" :class="order.status">
|
||
{{ order.status === 'pending' ? '待处理' :
|
||
order.status === 'paid' ? '已支付' :
|
||
order.status === 'shipping' ? '配送中' :
|
||
order.status === 'completed' ? '已完成' : '已取消' }}
|
||
</view>
|
||
</view>
|
||
<view class="col-time">
|
||
<text class="order-time">{{ formatDate(order.createdAt) }}</text>
|
||
</view>
|
||
<view class="col-actions">
|
||
<button class="action-btn view" @click="handleViewOrder(order)">
|
||
<text class="iconfont icon-view"></text>
|
||
</button>
|
||
<button class="action-btn ship" @click="handleShipOrder(order)" v-if="order.status === 'paid'">
|
||
<text class="iconfont icon-ship"></text>
|
||
</button>
|
||
<button class="action-btn complete" @click="handleCompleteOrder(order)" v-if="order.status === 'shipping'">
|
||
<text class="iconfont icon-complete"></text>
|
||
</button>
|
||
<button class="action-btn delete" @click="handleDeleteOrder(order)">
|
||
<text class="iconfont icon-delete"></text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分页 -->
|
||
<view class="pagination">
|
||
<view class="page-info">
|
||
<text>共 {{ totalOrders }} 条记录,显示 {{ (currentPage - 1) * pageSize + 1 }}-{{ Math.min(currentPage * pageSize, totalOrders) }} 条</text>
|
||
</view>
|
||
<view class="page-controls">
|
||
<button
|
||
class="page-btn"
|
||
:disabled="currentPage === 1"
|
||
@click="goToPage(currentPage - 1)"
|
||
>
|
||
上一页
|
||
</button>
|
||
|
||
<view class="page-numbers">
|
||
<button
|
||
v-for="page in visiblePages"
|
||
:key="page"
|
||
class="page-number"
|
||
:class="{ 'active': page === currentPage }"
|
||
@click="goToPage(page)"
|
||
>
|
||
{{ page }}
|
||
</button>
|
||
</view>
|
||
|
||
<button
|
||
class="page-btn"
|
||
:disabled="currentPage === totalPages"
|
||
@click="goToPage(currentPage + 1)"
|
||
>
|
||
下一页
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单详情模态框 -->
|
||
<view v-if="showOrderDetail" class="modal-overlay" @click="closeModal">
|
||
<view class="modal-content order-detail-modal" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">订单详情 - {{ currentOrder?.orderNumber }}</text>
|
||
<view class="modal-close" @click="closeModal">
|
||
<text class="iconfont icon-close"></text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-body">
|
||
<view class="order-info-section">
|
||
<view class="info-row">
|
||
<text class="info-label">订单号:</text>
|
||
<text class="info-value">{{ currentOrder?.orderNumber }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">下单时间:</text>
|
||
<text class="info-value">{{ formatDate(currentOrder?.createdAt) }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">订单状态:</text>
|
||
<view class="status-badge small" :class="currentOrder?.status">
|
||
{{ currentOrder?.status === 'pending' ? '待处理' :
|
||
currentOrder?.status === 'paid' ? '已支付' :
|
||
currentOrder?.status === 'shipping' ? '配送中' :
|
||
currentOrder?.status === 'completed' ? '已完成' : '已取消' }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="user-info-section">
|
||
<text class="section-title">用户信息</text>
|
||
<view class="info-row">
|
||
<text class="info-label">用户名:</text>
|
||
<text class="info-value">{{ currentOrder?.userName }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">联系电话:</text>
|
||
<text class="info-value">{{ currentOrder?.userPhone }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">收货地址:</text>
|
||
<text class="info-value">{{ currentOrder?.address }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="items-section">
|
||
<text class="section-title">商品信息</text>
|
||
<view class="order-items-list">
|
||
<view v-for="item in currentOrder?.items" :key="item.id" class="item-row">
|
||
<view class="item-info">
|
||
<text class="item-name">{{ item.name }}</text>
|
||
<text class="item-spec">{{ item.spec }}</text>
|
||
</view>
|
||
<view class="item-price">¥{{ item.price }}</view>
|
||
<view class="item-quantity">x{{ item.quantity }}</view>
|
||
<view class="item-total">¥{{ item.price * item.quantity }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="amount-section">
|
||
<view class="amount-row">
|
||
<text class="amount-label">商品总价:</text>
|
||
<text class="amount-value">¥{{ currentOrder?.subtotal }}</text>
|
||
</view>
|
||
<view class="amount-row">
|
||
<text class="amount-label">运费:</text>
|
||
<text class="amount-value">¥{{ currentOrder?.shippingFee }}</text>
|
||
</view>
|
||
<view class="amount-row total">
|
||
<text class="amount-label">订单总价:</text>
|
||
<text class="amount-value">¥{{ currentOrder?.totalAmount }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<button class="btn-secondary" @click="closeModal">关闭</button>
|
||
<view class="action-buttons">
|
||
<button
|
||
class="btn-primary"
|
||
@click="handleShipOrder(currentOrder)"
|
||
v-if="currentOrder?.status === 'paid'"
|
||
>
|
||
发货
|
||
</button>
|
||
<button
|
||
class="btn-success"
|
||
@click="handleCompleteOrder(currentOrder)"
|
||
v-if="currentOrder?.status === 'shipping'"
|
||
>
|
||
确认收货
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单详情Tab -->
|
||
<view v-if="activeTab === 'order-detail'" class="order-detail-section">
|
||
<view class="order-detail-placeholder">
|
||
<text class="placeholder-title">订单详情页面</text>
|
||
<text class="placeholder-desc">选择订单列表中的订单查看详情</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</AdminLayout>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, computed} from 'vue'
|
||
import AdminLayout from '@/layouts/admin/index.uvue'
|
||
|
||
// Tab 相关
|
||
const activeTab = ref('order-list')
|
||
const tabs = ref([
|
||
{ key: 'order-list', title: '订单列表', icon: 'icon-list' },
|
||
{ key: 'order-detail', title: '订单详情', icon: 'icon-detail' }
|
||
])
|
||
|
||
// 响应式数据
|
||
const searchKeyword = ref('')
|
||
const showAdvancedSearch = ref(false)
|
||
const totalOrders = ref(342)
|
||
const pendingOrders = ref(25)
|
||
const shippingOrders = ref(15)
|
||
const completedOrders = ref(302)
|
||
|
||
// 筛选条件
|
||
const selectedStatus = ref(0)
|
||
const selectedPayment = ref(0)
|
||
const startDate = ref('')
|
||
const endDate = ref('')
|
||
const minDate = '2020-01-01'
|
||
const maxDate = new Date().toISOString().split('T')[0]
|
||
|
||
// 选项数据
|
||
const statusOptions = ['全部状态', '待处理', '已支付', '配送中', '已完成', '已取消']
|
||
const paymentOptions = ['全部方式', '支付宝', '微信支付', '银行卡', '货到付款']
|
||
|
||
// 订单列表
|
||
const orderList = ref([
|
||
{
|
||
id: 1,
|
||
orderNumber: 'DD202401150001',
|
||
userName: '张三',
|
||
userPhone: '13800138001',
|
||
address: '北京市朝阳区某某街道某某小区某某号',
|
||
status: 'pending',
|
||
paymentMethod: '支付宝',
|
||
subtotal: 2599,
|
||
shippingFee: 0,
|
||
totalAmount: 2599,
|
||
createdAt: '2024-01-15T10:30:00Z',
|
||
items: [
|
||
{ id: 1, name: 'iPhone 15 Pro', spec: '256GB 黑色', price: 8999, quantity: 1 },
|
||
{ id: 2, name: '手机壳', spec: '硅胶保护壳', price: 59, quantity: 1 }
|
||
]
|
||
},
|
||
{
|
||
id: 2,
|
||
orderNumber: 'DD202401150002',
|
||
userName: '李四',
|
||
userPhone: '13800138002',
|
||
address: '上海市浦东新区某某街道某某小区某某号',
|
||
status: 'paid',
|
||
paymentMethod: '微信支付',
|
||
subtotal: 699,
|
||
shippingFee: 10,
|
||
totalAmount: 709,
|
||
createdAt: '2024-01-15T09:15:00Z',
|
||
items: [
|
||
{ id: 3, name: 'Nike 运动鞋', spec: '42码 白色', price: 599, quantity: 1 },
|
||
{ id: 4, name: '运动袜', spec: '中筒 黑色', price: 29, quantity: 2 }
|
||
]
|
||
},
|
||
{
|
||
id: 3,
|
||
orderNumber: 'DD202401150003',
|
||
userName: '王五',
|
||
userPhone: '13800138003',
|
||
address: '广州市天河区某某街道某某小区某某号',
|
||
status: 'shipping',
|
||
paymentMethod: '支付宝',
|
||
subtotal: 1299,
|
||
shippingFee: 0,
|
||
totalAmount: 1299,
|
||
createdAt: '2024-01-14T16:45:00Z',
|
||
items: [
|
||
{ id: 5, name: '咖啡机', spec: '全自动磨豆', price: 1299, quantity: 1 }
|
||
]
|
||
},
|
||
{
|
||
id: 4,
|
||
orderNumber: 'DD202401150004',
|
||
userName: '赵六',
|
||
userPhone: '13800138004',
|
||
address: '深圳市南山区某某街道某某小区某某号',
|
||
status: 'completed',
|
||
paymentMethod: '银行卡',
|
||
subtotal: 4599,
|
||
shippingFee: 0,
|
||
totalAmount: 4599,
|
||
createdAt: '2024-01-13T14:20:00Z',
|
||
items: [
|
||
{ id: 6, name: '联想笔记本', spec: 'i5 16GB 512GB SSD', price: 4599, quantity: 1 }
|
||
]
|
||
}
|
||
])
|
||
|
||
// 分页相关
|
||
const currentPage = ref(1)
|
||
const pageSize = ref(10)
|
||
const totalPages = computed(() => Math.ceil(totalOrders.value / pageSize.value))
|
||
const visiblePages = computed(() => {
|
||
const pages = []
|
||
const start = Math.max(1, currentPage.value - 2)
|
||
const end = Math.min(totalPages.value, currentPage.value + 2)
|
||
|
||
for (let i = start; i <= end; i++) {
|
||
pages.push(i)
|
||
}
|
||
|
||
return pages
|
||
})
|
||
|
||
// 选择相关
|
||
const selectAll = ref(false)
|
||
const selectedOrders = ref<number[]>([])
|
||
|
||
// 模态框相关
|
||
const showOrderDetail = ref(false)
|
||
const currentOrder = ref(null)
|
||
|
||
// 方法
|
||
const handleSearch = () => {
|
||
console.log('搜索:', searchKeyword.value)
|
||
// TODO: 实现搜索逻辑
|
||
}
|
||
|
||
const handleStatusChange = (e: any) => {
|
||
selectedStatus.value = e.detail.value
|
||
}
|
||
|
||
const handlePaymentChange = (e: any) => {
|
||
selectedPayment.value = e.detail.value
|
||
}
|
||
|
||
const handleStartDateChange = (e: any) => {
|
||
startDate.value = e.detail.value
|
||
}
|
||
|
||
const handleEndDateChange = (e: any) => {
|
||
endDate.value = e.detail.value
|
||
}
|
||
|
||
const resetFilters = () => {
|
||
selectedStatus.value = 0
|
||
selectedPayment.value = 0
|
||
startDate.value = ''
|
||
endDate.value = ''
|
||
searchKeyword.value = ''
|
||
}
|
||
|
||
const handleAdvancedSearch = () => {
|
||
console.log('高级搜索:', {
|
||
status: statusOptions[selectedStatus.value],
|
||
payment: paymentOptions[selectedPayment.value],
|
||
startDate: startDate.value,
|
||
endDate: endDate.value,
|
||
keyword: searchKeyword.value
|
||
})
|
||
// TODO: 实现高级搜索逻辑
|
||
}
|
||
|
||
const handleExport = () => {
|
||
uni.showToast({
|
||
title: '导出功能开发中',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
const handleSelectAll = (e: any) => {
|
||
selectAll.value = e.detail.value
|
||
if (selectAll.value) {
|
||
selectedOrders.value = orderList.value.map(order => order.id)
|
||
} else {
|
||
selectedOrders.value = []
|
||
}
|
||
}
|
||
|
||
const handleOrderSelect = (orderId: number, e: any) => {
|
||
if (e.detail.value) {
|
||
selectedOrders.value.push(orderId)
|
||
} else {
|
||
selectedOrders.value = selectedOrders.value.filter(id => id !== orderId)
|
||
}
|
||
selectAll.value = selectedOrders.value.length === orderList.value.length
|
||
}
|
||
|
||
const handleBatchDelete = () => {
|
||
if (selectedOrders.value.length === 0) return
|
||
|
||
uni.showModal({
|
||
title: '确认删除',
|
||
content: `确定要删除 ${selectedOrders.value.length} 个订单吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// TODO: 实现批量删除逻辑
|
||
selectedOrders.value.forEach(id => {
|
||
const index = orderList.value.findIndex(o => o.id === id)
|
||
if (index > -1) {
|
||
orderList.value.splice(index, 1)
|
||
totalOrders.value--
|
||
}
|
||
})
|
||
|
||
uni.showToast({
|
||
title: '批量删除成功',
|
||
icon: 'success'
|
||
})
|
||
selectedOrders.value = []
|
||
selectAll.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleViewOrder = (order: any) => {
|
||
currentOrder.value = order
|
||
showOrderDetail.value = true
|
||
}
|
||
|
||
const handleShipOrder = (order: any) => {
|
||
uni.showModal({
|
||
title: '确认发货',
|
||
content: `确定要为订单 "${order.orderNumber}" 发货吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// TODO: 实现发货逻辑
|
||
order.status = 'shipping'
|
||
uni.showToast({
|
||
title: '发货成功',
|
||
icon: 'success'
|
||
})
|
||
closeModal()
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleCompleteOrder = (order: any) => {
|
||
uni.showModal({
|
||
title: '确认收货',
|
||
content: `确定订单 "${order.orderNumber}" 已收货吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// TODO: 实现确认收货逻辑
|
||
order.status = 'completed'
|
||
uni.showToast({
|
||
title: '确认收货成功',
|
||
icon: 'success'
|
||
})
|
||
closeModal()
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const handleDeleteOrder = (order: any) => {
|
||
uni.showModal({
|
||
title: '确认删除',
|
||
content: `确定要删除订单 "${order.orderNumber}" 吗?此操作不可恢复。`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// TODO: 实现删除逻辑
|
||
const index = orderList.value.findIndex(o => o.id === order.id)
|
||
if (index > -1) {
|
||
orderList.value.splice(index, 1)
|
||
totalOrders.value--
|
||
}
|
||
|
||
uni.showToast({
|
||
title: '删除成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const closeModal = () => {
|
||
showOrderDetail.value = false
|
||
currentOrder.value = null
|
||
}
|
||
|
||
const goToPage = (page: number) => {
|
||
currentPage.value = page
|
||
// TODO: 实现分页数据加载
|
||
}
|
||
|
||
const formatDate = (dateString: string) => {
|
||
const date = new Date(dateString)
|
||
return date.toLocaleDateString('zh-CN') + ' ' + date.toLocaleTimeString('zh-CN', { hour12: false })
|
||
}
|
||
|
||
// Tab 切换方法
|
||
const switchTab = (tabKey: string) => {
|
||
activeTab.value = tabKey
|
||
}
|
||
|
||
// 页面生命周期
|
||
onLoad((options: any) => {
|
||
// 处理页面参数,切换到对应的tab
|
||
if (options && options.action) {
|
||
if (options.action === 'detail') {
|
||
activeTab.value = 'order-detail'
|
||
} else {
|
||
activeTab.value = 'order-list'
|
||
}
|
||
} else {
|
||
activeTab.value = 'order-list'
|
||
}
|
||
|
||
console.log('订单管理页面加载,参数:', options)
|
||
})
|
||
|
||
onMounted(() => {
|
||
// 初始化数据
|
||
console.log('订单管理页面初始化')
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
/* Tab 栏样式 */
|
||
.tab-bar {
|
||
display: flex;
|
||
background-color: #ffffff;
|
||
border-radius: 8rpx;
|
||
padding: 8rpx;
|
||
margin-bottom: 24rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
border-radius: 6rpx;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
background-color: #f5f5f5;
|
||
color: #666666;
|
||
}
|
||
|
||
.tab-item:hover {
|
||
background-color: #e8e8e8;
|
||
}
|
||
|
||
.tab-item.active {
|
||
background-color: #1890ff;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.tab-icon {
|
||
font-size: 16rpx;
|
||
}
|
||
|
||
.tab-title {
|
||
font-size: 14rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 订单详情占位符 */
|
||
.order-detail-section {
|
||
background-color: #ffffff;
|
||
border-radius: 8rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||
min-height: 400rpx;
|
||
}
|
||
|
||
.order-detail-placeholder {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 80rpx 40rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.placeholder-title {
|
||
font-size: 20rpx;
|
||
font-weight: 600;
|
||
color: #262626;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.placeholder-desc {
|
||
font-size: 14rpx;
|
||
color: #666666;
|
||
}
|
||
|
||
.order-management {
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.page-header {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.page-title {
|
||
display: block;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.page-subtitle {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.stats-cards {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 40rpx;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.stat-card {
|
||
flex: 1;
|
||
min-width: 200rpx;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 16rpx;
|
||
padding: 40rpx 30rpx;
|
||
text-align: center;
|
||
color: #fff;
|
||
box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.3);
|
||
}
|
||
|
||
.stat-icon {
|
||
font-size: 48rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.stat-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.stat-value {
|
||
display: block;
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.stat-label {
|
||
display: block;
|
||
font-size: 24rpx;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.search-section {
|
||
background-color: #fff;
|
||
border-radius: 12rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.search-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.search-input-wrapper {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
padding: 16rpx 20rpx;
|
||
border: none;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.search-btn {
|
||
padding: 16rpx 20rpx;
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
border: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.advanced-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
color: #1890ff;
|
||
cursor: pointer;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.advanced-search {
|
||
border-top: 1rpx solid #eee;
|
||
padding-top: 30rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.filter-row {
|
||
display: flex;
|
||
gap: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.filter-item {
|
||
flex: 1;
|
||
min-width: 200rpx;
|
||
}
|
||
|
||
.filter-label {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.picker-display {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16rpx 20rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
background-color: #fff;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.date-range {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.date-input {
|
||
flex: 1;
|
||
padding: 16rpx 20rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
background-color: #fff;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.date-separator {
|
||
color: #666;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.filter-actions {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
justify-content: flex-end;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.action-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30rpx;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.left-actions,
|
||
.right-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.select-all {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
font-size: 26rpx;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background-color: #fff;
|
||
color: #666;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
font-size: 26rpx;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.btn-success {
|
||
background-color: #52c41a;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
font-size: 26rpx;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.btn-danger {
|
||
background-color: #ff4d4f;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx 24rpx;
|
||
font-size: 26rpx;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.btn-danger:disabled {
|
||
background-color: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.order-table {
|
||
background-color: #fff;
|
||
border-radius: 12rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.table-header {
|
||
background-color: #f5f5f5;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.table-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.table-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.table-row.selected {
|
||
background-color: #f0f8ff;
|
||
}
|
||
|
||
.col-select {
|
||
width: 80rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.col-order {
|
||
flex: 2;
|
||
}
|
||
|
||
.col-user {
|
||
flex: 1.5;
|
||
}
|
||
|
||
.col-amount,
|
||
.col-status,
|
||
.col-time {
|
||
flex: 1;
|
||
}
|
||
|
||
.col-actions {
|
||
width: 200rpx;
|
||
display: flex;
|
||
gap: 10rpx;
|
||
justify-content: center;
|
||
}
|
||
|
||
.order-number {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #1890ff;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.order-items {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.order-item {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
display: block;
|
||
}
|
||
|
||
.user-name {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.user-phone {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.order-amount {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #ff4d4f;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.payment-method {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.status-badge {
|
||
display: inline-block;
|
||
padding: 6rpx 12rpx;
|
||
border-radius: 12rpx;
|
||
font-size: 22rpx;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
}
|
||
|
||
.status-badge.small {
|
||
padding: 4rpx 8rpx;
|
||
font-size: 20rpx;
|
||
}
|
||
|
||
.status-badge.pending {
|
||
background-color: #faad14;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-badge.paid {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-badge.shipping {
|
||
background-color: #52c41a;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-badge.completed {
|
||
background-color: #722ed1;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-badge.cancelled {
|
||
background-color: #ff4d4f;
|
||
color: #fff;
|
||
}
|
||
|
||
.order-time {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.action-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
border: none;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.action-btn.view {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn.ship {
|
||
background-color: #52c41a;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn.complete {
|
||
background-color: #722ed1;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn.delete {
|
||
background-color: #ff4d4f;
|
||
color: #fff;
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 30rpx;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.page-info {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.page-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.page-btn {
|
||
padding: 12rpx 20rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 6rpx;
|
||
background-color: #fff;
|
||
color: #333;
|
||
cursor: pointer;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.page-btn:disabled {
|
||
background-color: #f5f5f5;
|
||
color: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.page-numbers {
|
||
display: flex;
|
||
gap: 8rpx;
|
||
margin: 0 20rpx;
|
||
}
|
||
|
||
.page-number {
|
||
width: 50rpx;
|
||
height: 50rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 6rpx;
|
||
background-color: #fff;
|
||
color: #333;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.page-number.active {
|
||
background-color: #1890ff;
|
||
color: #fff;
|
||
border-color: #1890ff;
|
||
}
|
||
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.modal-content {
|
||
background-color: #fff;
|
||
border-radius: 12rpx;
|
||
width: 90%;
|
||
max-width: 800rpx;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.order-detail-modal {
|
||
max-width: 700rpx;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 30rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.modal-close {
|
||
cursor: pointer;
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
padding: 10rpx;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.order-info-section,
|
||
.user-info-section,
|
||
.items-section,
|
||
.amount-section {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 20rpx;
|
||
display: block;
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.info-row:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
flex: 1;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
flex: 2;
|
||
text-align: right;
|
||
}
|
||
|
||
.order-items-list {
|
||
border: 1rpx solid #eee;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.item-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.item-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.item-info {
|
||
flex: 2;
|
||
}
|
||
|
||
.item-name {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
display: block;
|
||
}
|
||
|
||
.item-spec {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
display: block;
|
||
}
|
||
|
||
.item-price,
|
||
.item-quantity,
|
||
.item-total {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.amount-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.amount-row.total {
|
||
border-top: 1rpx solid #eee;
|
||
padding-top: 15rpx;
|
||
margin-top: 15rpx;
|
||
}
|
||
|
||
.amount-label {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.amount-value {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.amount-row.total .amount-value {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #ff4d4f;
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 30rpx;
|
||
border-top: 1rpx solid #eee;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.iconfont {
|
||
font-family: 'iconfont';
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media screen and (max-width: 750rpx) {
|
||
.stats-cards {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.filter-row {
|
||
flex-direction: column;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.action-bar {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.left-actions,
|
||
.right-actions {
|
||
justify-content: center;
|
||
}
|
||
|
||
.table-row {
|
||
flex-wrap: wrap;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.col-order,
|
||
.col-user {
|
||
flex: 1 1 100%;
|
||
}
|
||
|
||
.pagination {
|
||
flex-direction: column;
|
||
text-align: center;
|
||
}
|
||
|
||
.info-row {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 5rpx;
|
||
}
|
||
|
||
.info-value {
|
||
text-align: left;
|
||
}
|
||
|
||
.item-row {
|
||
flex-wrap: wrap;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.modal-footer {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.action-buttons {
|
||
width: 100%;
|
||
justify-content: center;
|
||
}
|
||
}
|
||
</style> |