| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- <template>
- <div class="page-container">
- <div class="page-header">
- <div>
- <h1 class="page-title">订单管理</h1>
- <p class="page-subtitle">查看、搜索和管理所有发货订单</p>
- </div>
- <div class="page-actions">
- <el-button @click="$emit('exportExcel')">
- <el-icon style="margin-right: 4px"><Download /></el-icon>导出 Excel
- </el-button>
- <el-button type="primary" @click="$emit('openImport')">
- <el-icon style="margin-right: 4px"><Upload /></el-icon>导入物流
- </el-button>
- </div>
- </div>
- <!-- 统计卡片 -->
- <div class="stats-grid">
- <div class="stat-card" v-for="s in statCards" :key="s.label">
- <div class="stat-card-header">
- <span class="stat-card-label">{{ s.label }}</span>
- <div class="stat-card-icon" :class="s.color">
- <component :is="s.icon" />
- </div>
- </div>
- <div class="stat-card-value">{{ s.value }}</div>
- </div>
- </div>
- <!-- 筛选区 -->
- <div class="filter-bar">
- <div class="filter-row">
- <div class="filter-field">
- <label>订单状态</label>
- <el-select v-model="form.status" placeholder="全部" clearable style="width: 130px">
- <el-option label="待处理" :value="0" />
- <el-option label="已完成" :value="1" />
- <el-option label="已取消" :value="2" />
- <el-option label="已发货" :value="3" />
- </el-select>
- </div>
- <div class="filter-field">
- <label>下单日期</label>
- <el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" value-format="YYYY-MM-DD" style="width: 240px" />
- </div>
- <div class="filter-field">
- <label>收货人</label>
- <el-input v-model="form.receiver_name" placeholder="输入收货人" clearable />
- </div>
- <div class="filter-field">
- <label>手机号</label>
- <el-input v-model="form.receiver_phone" placeholder="输入手机号" clearable />
- </div>
- <div class="filter-field">
- <label>订单号</label>
- <el-input v-model="form.order_no" placeholder="输入订单号" clearable />
- </div>
- <div class="filter-actions">
- <el-button @click="handleReset(); $emit('query')">重置</el-button>
- <el-button type="primary" @click="$emit('query')">查询</el-button>
- </div>
- </div>
- </div>
- <!-- 数据表格 -->
- <div class="table-container">
- <div class="table-toolbar">
- <div class="table-toolbar-left">
- <span class="table-title">订单列表</span>
- <span class="count-badge">共 {{ total }} 条</span>
- </div>
- </div>
- <el-table :data="tableData" v-loading="loading" style="width: 100%" :header-cell-style="{ background: 'var(--bg-subtle)', color: 'var(--text-tertiary)', fontSize: '12px', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.04em' }" :row-style="{ height: '48px' }" @row-mouseover="true">
- <el-table-column prop="批次号" label="批次号" width="160">
- <template #default="{ row }"><span class="mono">{{ row['批次号'] }}</span></template>
- </el-table-column>
- <el-table-column prop="订单号" label="订单号" width="150">
- <template #default="{ row }"><span class="mono">{{ row['订单号'] }}</span></template>
- </el-table-column>
- <el-table-column prop="物流公司" label="物流公司" width="120" />
- <el-table-column prop="物流单号" label="物流单号" width="160">
- <template #default="{ row }">
- <span class="mono">{{ row['物流单号'] || '—' }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="订单状态" label="订单状态" width="100" align="center">
- <template #default="{ row }">
- <span class="status-badge" :class="getStatusClass(row['订单状态'])">
- <span class="dot"></span>{{ row['订单状态'] }}
- </span>
- </template>
- </el-table-column>
- <el-table-column prop="下单时间" label="下单时间" width="180">
- <template #default="{ row }"><span style="color: var(--text-secondary); font-size: var(--text-sm);">{{ row['下单时间'] }}</span></template>
- </el-table-column>
- <el-table-column prop="卡号" label="卡号" width="120">
- <template #default="{ row }"><span class="mono">{{ row['卡号'] }}</span></template>
- </el-table-column>
- <el-table-column prop="员工姓名" label="员工姓名" width="100" />
- <el-table-column prop="收货人" label="收货人" width="100" />
- <el-table-column prop="手机号" label="手机号" width="130" />
- <el-table-column prop="收货地址" label="收货地址" width="250" show-overflow-tooltip />
- <el-table-column prop="商品名称" label="商品名称" width="150" />
- <el-table-column prop="数量" label="数量" width="70" align="center" />
- <el-table-column prop="单价" label="单价" width="90" align="right" />
- <el-table-column prop="小计" label="小计" width="90" align="right" />
- <el-table-column prop="备注" label="备注" width="150" show-overflow-tooltip />
- </el-table>
- <div class="table-footer">
- <div class="table-footer-info">显示第 {{ (currentPage - 1) * pageSize + 1 }}-{{ Math.min(currentPage * pageSize, total) }} 条</div>
- <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[20, 50, 100]" :total="total" layout="prev, pager, next, sizes" @size-change="$emit('query')" @current-change="$emit('query')" />
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { reactive, ref, computed } from 'vue'
- import { Download, Upload } from '@element-plus/icons-vue'
- const props = defineProps({
- tableData: Array,
- total: Number,
- loading: Boolean,
- stats: Object
- })
- defineEmits(['query', 'exportExcel', 'openImport'])
- const form = reactive({ receiver_name: '', receiver_phone: '', order_no: '', status: 0 })
- const dateRange = ref(null)
- const currentPage = ref(1)
- const pageSize = ref(20)
- const statCards = computed(() => [
- { label: '总订单数', value: props.stats?.total ?? '—', color: 'primary', icon: 'Box' },
- { label: '待处理', value: props.stats?.pending ?? '—', color: 'warning', icon: 'Clock' },
- { label: '已发货', value: props.stats?.shipped ?? '—', color: 'success', icon: 'CircleCheck' },
- { label: '已取消', value: props.stats?.cancelled ?? '—', color: 'info', icon: 'CircleClose' },
- ])
- function getParams() {
- const params = { ...form }
- if (dateRange.value?.length === 2) {
- params.start_date = dateRange.value[0]
- params.end_date = dateRange.value[1]
- }
- if (params.status === null || params.status === '') delete params.status
- return params
- }
- function handleReset() {
- form.receiver_name = ''; form.receiver_phone = ''; form.order_no = ''; form.status = 0
- dateRange.value = null
- }
- function getStatusClass(status) {
- const map = { '待处理': 'warning', '已完成': 'success', '已取消': 'danger', '已发货': 'info' }
- return map[status] || 'neutral'
- }
- defineExpose({ getParams, handleReset, currentPage, pageSize })
- </script>
- <style scoped>
- .page-container { animation: slideUp 0.4s var(--ease-out); }
- .page-header {
- display: flex; align-items: flex-start; justify-content: space-between;
- gap: var(--space-4); margin-bottom: var(--space-6);
- }
- .page-title { font-size: var(--text-xl); font-weight: 800; letter-spacing: -0.01em; color: var(--text-primary); }
- .page-subtitle { font-size: var(--text-sm); color: var(--text-secondary); margin-top: 2px; }
- .page-actions { display: flex; gap: var(--space-2); flex-shrink: 0; }
- .stats-grid {
- display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4);
- margin-bottom: var(--space-6);
- }
- .stat-card {
- background: var(--bg-surface); border: 1px solid var(--border-subtle);
- border-radius: var(--radius-lg); padding: var(--space-5);
- box-shadow: var(--shadow-xs); transition: box-shadow var(--dur-normal) var(--ease-out);
- }
- .stat-card:hover { box-shadow: var(--shadow-sm); }
- .stat-card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-3); }
- .stat-card-label { font-size: var(--text-sm); color: var(--text-secondary); font-weight: 500; }
- .stat-card-icon {
- width: 36px; height: 36px; border-radius: var(--radius-md);
- display: flex; align-items: center; justify-content: center; font-size: 18px;
- }
- .stat-card-icon.primary { background: var(--primary-50); color: var(--primary-600); }
- .stat-card-icon.success { background: var(--success-50); color: var(--success-600); }
- .stat-card-icon.warning { background: var(--warning-50); color: var(--warning-600); }
- .stat-card-icon.info { background: var(--info-50); color: var(--info-600); }
- .stat-card-value { font-size: var(--text-2xl); font-weight: 800; letter-spacing: -0.02em; color: var(--text-primary); }
- .filter-bar {
- background: var(--bg-surface); border: 1px solid var(--border-subtle);
- border-radius: var(--radius-lg); padding: var(--space-4) var(--space-5);
- margin-bottom: var(--space-4); box-shadow: var(--shadow-xs);
- }
- .filter-row { display: flex; align-items: flex-end; gap: var(--space-3); flex-wrap: wrap; }
- .filter-field { display: flex; flex-direction: column; gap: var(--space-1); }
- .filter-field label { font-size: var(--text-xs); font-weight: 600; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.04em; }
- .filter-field .el-input, .filter-field .el-select { min-width: 130px; }
- .filter-actions { display: flex; gap: var(--space-2); margin-left: auto; }
- .table-container {
- background: var(--bg-surface); border: 1px solid var(--border-subtle);
- border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-xs);
- }
- .table-toolbar {
- display: flex; align-items: center; justify-content: space-between;
- padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border-subtle);
- }
- .table-toolbar-left { display: flex; align-items: center; gap: var(--space-3); }
- .table-title { font-size: var(--text-md); font-weight: 700; color: var(--text-primary); }
- .count-badge {
- font-size: var(--text-xs); font-weight: 600; padding: 2px 8px;
- border-radius: var(--radius-full); background: var(--gray-100); color: var(--gray-600);
- }
- .table-footer {
- display: flex; align-items: center; justify-content: space-between;
- padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border-subtle);
- }
- .table-footer-info { font-size: var(--text-sm); color: var(--text-secondary); }
- @keyframes slideUp { from { opacity: 0; transform: translateY(12px); } }
- </style>
|