|
|
@@ -1,261 +1,186 @@
|
|
|
<template>
|
|
|
<div class="app-page">
|
|
|
- <section class="glass-card section-card filter-section">
|
|
|
- <div class="filter-main">
|
|
|
- <el-form :model="filters" inline class="filter-form">
|
|
|
- <el-form-item label="订单号/渠道单号" class="filter-item">
|
|
|
- <el-input v-model="filters.orderNo" placeholder="搜索订单号" clearable style="width:200px" @keyup.enter="loadData" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="买家" class="filter-item">
|
|
|
- <el-input v-model="filters.buyer" placeholder="姓名/手机/邮箱" clearable style="width:160px" @keyup.enter="loadData" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="渠道" class="filter-item">
|
|
|
- <el-select v-model="filters.channel" placeholder="全部渠道" clearable style="width:140px">
|
|
|
- <el-option label="Shopify US" value="Shopify US" />
|
|
|
- <el-option label="Shopify JP" value="Shopify JP" />
|
|
|
- <el-option label="TikTok UK" value="TikTok UK" />
|
|
|
- <el-option label="Amazon US" value="Amazon US" />
|
|
|
- <el-option label="eBay" value="eBay" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="仓库" class="filter-item">
|
|
|
- <el-select v-model="filters.warehouse" placeholder="全部仓库" clearable style="width:140px">
|
|
|
- <el-option label="深圳南山仓" value="深圳南山仓" />
|
|
|
- <el-option label="义乌商贸仓" value="义乌商贸仓" />
|
|
|
- <el-option label="洛杉矶海外仓" value="洛杉矶海外仓" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="订单状态" class="filter-item">
|
|
|
- <el-select v-model="filters.orderStatus" placeholder="全部状态" clearable style="width:130px">
|
|
|
- <el-option label="待支付" value="created" />
|
|
|
- <el-option label="已支付" value="paid" />
|
|
|
- <el-option label="已分配" value="allocated" />
|
|
|
- <el-option label="已发货" value="shipped" />
|
|
|
- <el-option label="已完成" value="completed" />
|
|
|
- <el-option label="已取消" value="cancelled" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item class="filter-item">
|
|
|
- <el-button type="primary" @click="loadData">查询</el-button>
|
|
|
- <el-button @click="resetFilters">重置</el-button>
|
|
|
- <el-button type="text" @click="showAdvanced = !showAdvanced">
|
|
|
- 高级筛选 <el-icon><ArrowDown v-if="!showAdvanced" /><ArrowUp v-else /></el-icon>
|
|
|
- </el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
+ <section class="page-hero">
|
|
|
+ <div class="page-hero__meta">
|
|
|
+ <h1>订单列表</h1>
|
|
|
+ <p>管理所有订单,支持筛选、批量操作和快速处理</p>
|
|
|
</div>
|
|
|
-
|
|
|
- <div v-if="showAdvanced" class="filter-advanced">
|
|
|
- <el-form :model="filters" inline class="filter-form">
|
|
|
- <el-form-item label="日期范围" class="filter-item">
|
|
|
- <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:240px" value-format="YYYY-MM-DD" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="支付状态" class="filter-item">
|
|
|
- <el-select v-model="filters.paymentStatus" placeholder="全部" clearable style="width:120px">
|
|
|
- <el-option label="待支付" value="pending" />
|
|
|
- <el-option label="已支付" value="paid" />
|
|
|
- <el-option label="已退款" value="refunded" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="发货状态" class="filter-item">
|
|
|
- <el-select v-model="filters.shippingStatus" placeholder="全部" clearable style="width:120px">
|
|
|
- <el-option label="未发货" value="unshipped" />
|
|
|
- <el-option label="处理中" value="processing" />
|
|
|
- <el-option label="已发货" value="shipped" />
|
|
|
- <el-option label="已签收" value="delivered" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="买家国家" class="filter-item">
|
|
|
- <el-select v-model="filters.buyerCountry" placeholder="全部国家" clearable style="width:130px">
|
|
|
- <el-option label="美国 US" value="US" />
|
|
|
- <el-option label="英国 UK" value="UK" />
|
|
|
- <el-option label="日本 JP" value="JP" />
|
|
|
- <el-option label="德国 DE" value="DE" />
|
|
|
- <el-option label="法国 FR" value="FR" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="异常标签" class="filter-item">
|
|
|
- <el-select v-model="filters.exceptionTag" placeholder="全部" clearable style="width:130px">
|
|
|
- <el-option label="正常" value="正常" />
|
|
|
- <el-option label="地址需复核" value="地址需复核" />
|
|
|
- <el-option label="高频下单" value="高频下单" />
|
|
|
- <el-option label="疑似刷单" value="疑似刷单" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
+ <div class="chip-list">
|
|
|
+ <el-statistic title="今日订单" :value="todayCount" />
|
|
|
+ <el-statistic title="待处理" :value="pendingCount" />
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
- <section class="glass-card section-card toolbar-section">
|
|
|
- <div class="table-toolbar">
|
|
|
- <div class="toolbar-left">
|
|
|
- <el-button type="primary" @click="openCreateOrder">创建订单</el-button>
|
|
|
- <el-button type="success" @click="refreshSync">刷新同步</el-button>
|
|
|
- <el-button @click="doExport">导出订单</el-button>
|
|
|
- <el-dropdown v-if="selected.length" @command="handleBatchCommand">
|
|
|
- <el-button type="warning">批量操作 ({{ selected.length }}) <el-icon><ArrowDown /></el-icon></el-button>
|
|
|
- <template #dropdown>
|
|
|
- <el-dropdown-menu>
|
|
|
- <el-dropdown-item command="mark">批量标记</el-dropdown-item>
|
|
|
- <el-dropdown-item command="assign">批量分配</el-dropdown-item>
|
|
|
- <el-dropdown-item command="cancel" divided>批量取消</el-dropdown-item>
|
|
|
- </el-dropdown-menu>
|
|
|
+ <section class="section-card">
|
|
|
+ <div class="filter-bar">
|
|
|
+ <div class="filter-bar__main">
|
|
|
+ <el-input
|
|
|
+ v-model="searchKeyword"
|
|
|
+ placeholder="订单号 / 渠道单号 / 买家"
|
|
|
+ clearable
|
|
|
+ style="width: 280px"
|
|
|
+ @keyup.enter="loadData"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
</template>
|
|
|
- </el-dropdown>
|
|
|
- </div>
|
|
|
- <div class="toolbar-right">
|
|
|
- <el-select v-model="savedView" placeholder="快捷视图" clearable style="width:140px">
|
|
|
- <el-option label="待处理订单" value="pending" />
|
|
|
- <el-option label="异常订单" value="exception" />
|
|
|
- <el-option label="今日订单" value="today" />
|
|
|
- <el-option label="待发货" value="unshipped" />
|
|
|
+ </el-input>
|
|
|
+ <el-select v-model="filters.orderStatus" placeholder="订单状态" clearable style="width: 120px">
|
|
|
+ <el-option label="待支付" value="created" />
|
|
|
+ <el-option label="已支付" value="paid" />
|
|
|
+ <el-option label="已分配" value="allocated" />
|
|
|
+ <el-option label="已发货" value="shipped" />
|
|
|
+ <el-option label="已完成" value="completed" />
|
|
|
+ <el-option label="已取消" value="cancelled" />
|
|
|
</el-select>
|
|
|
- <el-button-group>
|
|
|
- <el-button :type="viewMode === 'table' ? 'primary' : ''" @click="viewMode = 'table'">
|
|
|
- <el-icon><Grid /></el-icon>
|
|
|
- </el-button>
|
|
|
- <el-button :type="viewMode === 'card' ? 'primary' : ''" @click="viewMode = 'card'">
|
|
|
- <el-icon><List /></el-icon>
|
|
|
- </el-button>
|
|
|
- </el-button-group>
|
|
|
+ <el-select v-model="filters.channel" placeholder="渠道" clearable style="width: 140px">
|
|
|
+ <el-option label="Shopify US" value="Shopify US" />
|
|
|
+ <el-option label="Shopify JP" value="Shopify JP" />
|
|
|
+ <el-option label="TikTok UK" value="TikTok UK" />
|
|
|
+ <el-option label="Amazon US" value="Amazon US" />
|
|
|
+ </el-select>
|
|
|
+ <el-select v-model="filters.shippingStatus" placeholder="发货状态" clearable style="width: 120px">
|
|
|
+ <el-option label="未发货" value="unshipped" />
|
|
|
+ <el-option label="处理中" value="processing" />
|
|
|
+ <el-option label="已发货" value="shipped" />
|
|
|
+ <el-option label="已签收" value="delivered" />
|
|
|
+ </el-select>
|
|
|
+ <el-button type="primary" @click="loadData">查询</el-button>
|
|
|
+ <el-button @click="resetFilters">重置</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="filter-bar__actions">
|
|
|
+ <el-button text @click="showFilterDrawer = true">
|
|
|
+ <el-icon><Filter /></el-icon>
|
|
|
+ 高级筛选
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
- <section class="glass-card section-card table-section">
|
|
|
- <template v-if="viewMode === 'table'">
|
|
|
- <el-table :data="filteredItems" stripe v-loading="loading" @selection-change="onSelection" :row-class-name="rowClass" class="order-table">
|
|
|
- <el-table-column type="selection" width="50" />
|
|
|
- <el-table-column prop="orderNo" label="订单信息" min-width="200">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="order-info">
|
|
|
- <div class="order-no">{{ row.orderNo }}</div>
|
|
|
- <div class="order-meta">
|
|
|
- <el-tag v-if="row.channelOrderNo" size="small" type="info">{{ row.channelOrderNo }}</el-tag>
|
|
|
- <el-tag v-if="row.priority === 'urgent'" type="danger" size="small">加急</el-tag>
|
|
|
- <el-tag v-for="tag in (row.orderTags || []).slice(0, 1)" :key="tag" type="warning" size="small">{{ tag }}</el-tag>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="channel" label="渠道" width="110">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-tag size="small">{{ row.channel }}</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="buyer" label="买家" min-width="160">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="buyer-cell">
|
|
|
- <div class="buyer-name">
|
|
|
- {{ row.buyer }}
|
|
|
- <el-tag v-if="row.buyerLevel" size="small" :type="buyerLevelTag(row.buyerLevel)">{{ row.buyerLevel }}</el-tag>
|
|
|
- </div>
|
|
|
- <div class="buyer-contact">{{ row.buyerPhone || row.buyerEmail }}</div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="receiverCity" label="收货地" width="120">
|
|
|
- <template #default="{ row }">
|
|
|
- <span class="location">{{ getCountryFlag(row.buyerCountry) }} {{ row.receiverCity }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="itemCount" label="件数" width="70" align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-popover placement="bottom" :width="280" trigger="hover">
|
|
|
- <template #reference>
|
|
|
- <span class="item-count">{{ row.itemCount }}件 <el-icon><QuestionFilled /></el-icon></span>
|
|
|
- </template>
|
|
|
- <div class="items-popover">
|
|
|
- <div v-for="item in row.items.slice(0, 4)" :key="item.sku" class="item-row">
|
|
|
- <span class="item-name">{{ item.productTitle }}</span>
|
|
|
- <span class="item-qty">x{{ item.qty }}</span>
|
|
|
- </div>
|
|
|
- <div v-if="row.items.length > 4" class="item-more">还有 {{ row.items.length - 4 }} 件</div>
|
|
|
- </div>
|
|
|
- </el-popover>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="amount" label="金额" width="120" align="right">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="amount-cell">
|
|
|
- <span class="amount">{{ row.currency }} {{ row.actualPaid || row.amount }}</span>
|
|
|
- <span v-if="row.discountAmount && parseFloat(row.discountAmount) > 0" class="discount">-{{ row.discountAmount }}</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="orderStatus" label="订单状态" width="100" align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-tag :type="statusType(row.orderStatus)" size="small">{{ statusLabel(row.orderStatus) }}</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="shippingStatus" label="发货状态" width="90" align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-tag :type="shippingStatusType(row.shippingStatus)" size="small">{{ shippingStatusLabel(row.shippingStatus) }}</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="warehouse" label="仓库" width="100">
|
|
|
- <template #default="{ row }">
|
|
|
- <span v-if="row.warehouse" class="warehouse">{{ row.warehouse }}</span>
|
|
|
- <el-tag v-else type="info" size="small">待分配</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="createdAt" label="下单时间" width="150" />
|
|
|
- <el-table-column label="操作" width="160" fixed="right">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-button link type="primary" @click="$router.push(`/order/detail?id=${row.id}`)">详情</el-button>
|
|
|
- <el-dropdown trigger="click" @command="(cmd: string) => handleRowCommand(cmd, row)">
|
|
|
- <el-button link type="primary">更多</el-button>
|
|
|
- <template #dropdown>
|
|
|
- <el-dropdown-menu>
|
|
|
- <el-dropdown-item command="track">物流追踪</el-dropdown-item>
|
|
|
- <el-dropdown-item command="assign">分配处理人</el-dropdown-item>
|
|
|
- <el-dropdown-item command="remark">添加备注</el-dropdown-item>
|
|
|
- <el-dropdown-item command="print">打印面单</el-dropdown-item>
|
|
|
- <el-dropdown-item command="cancel" divided>取消订单</el-dropdown-item>
|
|
|
- </el-dropdown-menu>
|
|
|
- </template>
|
|
|
- </el-dropdown>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- </template>
|
|
|
+ <section class="section-card">
|
|
|
+ <div class="batch-bar" v-if="selected.length">
|
|
|
+ <span class="batch-bar__info">已选择 {{ selected.length }} 项</span>
|
|
|
+ <el-button size="small" @click="batchAssign">批量分配</el-button>
|
|
|
+ <el-button size="small" @click="batchTag">批量标记</el-button>
|
|
|
+ <el-button size="small" type="danger" @click="batchCancel">批量取消</el-button>
|
|
|
+ <el-button size="small" text @click="selected = []">清除选择</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="toolbar">
|
|
|
+ <div class="toolbar__left">
|
|
|
+ <el-button type="primary" @click="openCreateOrder">
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ 创建订单
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="refreshSync">
|
|
|
+ <el-icon><Refresh /></el-icon>
|
|
|
+ 刷新同步
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="doExport">
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
+ 导出
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <div class="toolbar__right">
|
|
|
+ <el-radio-group v-model="viewMode" size="small">
|
|
|
+ <el-radio-button value="table">
|
|
|
+ <el-icon><Grid /></el-icon>
|
|
|
+ </el-radio-button>
|
|
|
+ <el-radio-button value="card">
|
|
|
+ <el-icon><List /></el-icon>
|
|
|
+ </el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <template v-else>
|
|
|
- <div class="order-cards-grid">
|
|
|
- <div v-for="row in filteredItems" :key="row.id" class="order-card" :class="{ 'exception': row.exceptionTag !== '正常' }">
|
|
|
- <div class="card-header">
|
|
|
- <div class="card-title">
|
|
|
- <span class="card-order-no">{{ row.orderNo }}</span>
|
|
|
- <el-tag size="small">{{ row.channel }}</el-tag>
|
|
|
+ <el-table
|
|
|
+ :data="filteredItems"
|
|
|
+ stripe
|
|
|
+ v-loading="loading"
|
|
|
+ @selection-change="onSelection"
|
|
|
+ :row-class-name="rowClass"
|
|
|
+ class="order-table"
|
|
|
+ @row-click="handleRowClick"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="45" />
|
|
|
+ <el-table-column prop="orderNo" label="订单信息" min-width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="order-info">
|
|
|
+ <div class="order-no">{{ row.orderNo }}</div>
|
|
|
+ <div class="order-meta">
|
|
|
+ <el-tag v-if="row.channelOrderNo" size="small" type="info">{{ row.channelOrderNo }}</el-tag>
|
|
|
+ <el-tag v-if="row.priority === 'urgent'" type="danger" size="small">加急</el-tag>
|
|
|
</div>
|
|
|
- <el-tag :type="statusType(row.orderStatus)" size="small">{{ statusLabel(row.orderStatus) }}</el-tag>
|
|
|
</div>
|
|
|
- <div class="card-buyer">
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="channel" label="渠道" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag size="small">{{ row.channel }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="buyer" label="买家信息" min-width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="buyer-info">
|
|
|
<span class="buyer-name">{{ row.buyer }}</span>
|
|
|
- <span class="buyer-country">{{ getCountryFlag(row.buyerCountry) }} {{ row.buyerCountry }}</span>
|
|
|
- </div>
|
|
|
- <div class="card-location">
|
|
|
- <el-icon><Location /></el-icon>
|
|
|
- {{ row.receiverCity }}, {{ row.receiverCountry }}
|
|
|
+ <span class="buyer-contact">{{ row.buyerCountry }} {{ row.receiverCity }}</span>
|
|
|
</div>
|
|
|
- <div class="card-items">
|
|
|
- <span>商品 x{{ row.itemCount }}</span>
|
|
|
- <span class="card-amount">{{ row.currency }} {{ row.actualPaid || row.amount }}</span>
|
|
|
- </div>
|
|
|
- <div class="card-footer">
|
|
|
- <span class="card-time">{{ row.createdAt }}</span>
|
|
|
- <el-button type="primary" size="small" @click="$router.push(`/order/detail?id=${row.id}`)">查看详情</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="itemCount" label="件数" width="70" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-popover placement="bottom" :width="260" trigger="hover">
|
|
|
+ <template #reference>
|
|
|
+ <span class="item-count">{{ row.itemCount }}</span>
|
|
|
+ </template>
|
|
|
+ <div class="items-popover">
|
|
|
+ <div v-for="item in row.items.slice(0, 5)" :key="item.sku" class="item-row">
|
|
|
+ <span class="item-name">{{ item.productTitle }}</span>
|
|
|
+ <span class="item-qty">x{{ item.qty }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-popover>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="actualPaid" label="金额" width="100" align="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="amount">{{ row.currency }} {{ row.actualPaid || row.amount }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="orderStatus" label="状态" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="statusType(row.orderStatus)" size="small" effect="light">
|
|
|
+ {{ statusLabel(row.orderStatus) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="warehouse" label="仓库" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span v-if="row.warehouse" class="warehouse-text">{{ row.warehouse }}</span>
|
|
|
+ <el-tag v-else type="info" size="small">待分配</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="createdAt" label="时间" width="140" />
|
|
|
+ <el-table-column label="操作" width="240" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="action-buttons">
|
|
|
+ <el-button size="small" type="primary" link @click.stop="viewDetail(row)">详情</el-button>
|
|
|
+ <el-button size="small" link @click.stop="handleRowCommand('assign', row)">分配</el-button>
|
|
|
+ <el-button size="small" link @click.stop="handleRowCommand('remark', row)">备注</el-button>
|
|
|
+ <el-button size="small" link @click.stop="handleRowCommand('print', row)">面单</el-button>
|
|
|
+ <el-button size="small" link @click.stop="handleRowCommand('track', row)">追踪</el-button>
|
|
|
+ <el-button size="small" link type="danger" @click.stop="handleRowCommand('cancel', row)">取消</el-button>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
|
|
|
- <div class="pagination-wrapper" v-if="total > 0">
|
|
|
+ <div class="pagination-wrapper">
|
|
|
<el-pagination
|
|
|
v-model:current-page="page"
|
|
|
v-model:page-size="pageSize"
|
|
|
:total="total"
|
|
|
- :page-sizes="[10, 20, 50]"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
layout="total, sizes, prev, pager, next"
|
|
|
@size-change="loadData"
|
|
|
@current-change="loadData"
|
|
|
@@ -263,10 +188,76 @@
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
+ <el-drawer v-model="showFilterDrawer" title="高级筛选" size="400px">
|
|
|
+ <el-form :model="filters" label-position="top">
|
|
|
+ <el-form-item label="日期范围">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="filters.dateRange"
|
|
|
+ type="daterange"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始"
|
|
|
+ end-placeholder="结束"
|
|
|
+ style="width: 100%"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="仓库">
|
|
|
+ <el-select v-model="filters.warehouse" placeholder="全部仓库" clearable style="width: 100%">
|
|
|
+ <el-option label="深圳南山仓" value="深圳南山仓" />
|
|
|
+ <el-option label="义乌商贸仓" value="义乌商贸仓" />
|
|
|
+ <el-option label="洛杉矶海外仓" value="洛杉矶海外仓" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="支付状态">
|
|
|
+ <el-select v-model="filters.paymentStatus" placeholder="全部" clearable style="width: 100%">
|
|
|
+ <el-option label="待支付" value="pending" />
|
|
|
+ <el-option label="已支付" value="paid" />
|
|
|
+ <el-option label="已退款" value="refunded" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="买家国家">
|
|
|
+ <el-select v-model="filters.buyerCountry" placeholder="全部国家" clearable style="width: 100%">
|
|
|
+ <el-option label="美国 US" value="US" />
|
|
|
+ <el-option label="英国 UK" value="UK" />
|
|
|
+ <el-option label="日本 JP" value="JP" />
|
|
|
+ <el-option label="德国 DE" value="DE" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="异常标签">
|
|
|
+ <el-select v-model="filters.exceptionTag" placeholder="全部" clearable style="width: 100%">
|
|
|
+ <el-option label="正常" value="正常" />
|
|
|
+ <el-option label="地址需复核" value="地址需复核" />
|
|
|
+ <el-option label="高频下单" value="高频下单" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="resetFilters">重置</el-button>
|
|
|
+ <el-button type="primary" @click="loadData">应用筛选</el-button>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+
|
|
|
+ <el-drawer v-model="trackDrawer" title="物流追踪" size="420px">
|
|
|
+ <template v-if="currentOrder">
|
|
|
+ <div class="track-header">
|
|
|
+ <el-tag type="success">已发货</el-tag>
|
|
|
+ <span class="track-carrier">{{ currentOrder.carrier }}</span>
|
|
|
+ <span class="track-no">{{ currentOrder.trackingNo }}</span>
|
|
|
+ </div>
|
|
|
+ <el-timeline class="track-timeline">
|
|
|
+ <el-timeline-item timestamp="2024-01-18 14:30" type="success">商品已签收</el-timeline-item>
|
|
|
+ <el-timeline-item timestamp="2024-01-17 09:20">到达广州转运中心</el-timeline-item>
|
|
|
+ <el-timeline-item timestamp="2024-01-16 18:45">已从深圳南山仓发出</el-timeline-item>
|
|
|
+ <el-timeline-item timestamp="2024-01-16 16:00" type="primary">快递员已取件</el-timeline-item>
|
|
|
+ <el-timeline-item timestamp="2024-01-15 10:30">卖家正在打包</el-timeline-item>
|
|
|
+ </el-timeline>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+
|
|
|
<el-dialog v-model="assignDialog" title="分配处理人" width="420px">
|
|
|
<el-form label-position="top">
|
|
|
<el-form-item label="选择处理人">
|
|
|
- <el-select v-model="assignPerson" placeholder="请选择处理人" style="width:100%">
|
|
|
+ <el-select v-model="assignPerson" placeholder="请选择处理人" style="width: 100%">
|
|
|
<el-option label="运营组 A / 陈欣" value="运营组 A / 陈欣" />
|
|
|
<el-option label="运营组 B / 王磊" value="运营组 B / 王磊" />
|
|
|
<el-option label="客服组 / 张丽" value="客服组 / 张丽" />
|
|
|
@@ -283,28 +274,10 @@
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
- <el-dialog v-model="tagDialog" title="添加标签" width="420px">
|
|
|
- <el-form label-position="top">
|
|
|
- <el-form-item label="选择标签">
|
|
|
- <el-select v-model="selectedTags" multiple placeholder="选择标签" style="width:100%">
|
|
|
- <el-option label="加急" value="加急" />
|
|
|
- <el-option label="VIP客户" value="VIP客户" />
|
|
|
- <el-option label="预售订单" value="预售订单" />
|
|
|
- <el-option label="定制商品" value="定制商品" />
|
|
|
- <el-option label="偏远地区" value="偏远地区" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- <template #footer>
|
|
|
- <el-button @click="tagDialog = false">取消</el-button>
|
|
|
- <el-button type="primary" @click="confirmTag">确认</el-button>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
<el-dialog v-model="createDialog" title="创建订单" width="500px">
|
|
|
<el-form :model="createForm" label-width="90px">
|
|
|
<el-form-item label="渠道" required>
|
|
|
- <el-select v-model="createForm.channel" placeholder="选择渠道" style="width:100%">
|
|
|
+ <el-select v-model="createForm.channel" placeholder="选择渠道" style="width: 100%">
|
|
|
<el-option label="Shopify US" value="Shopify US" />
|
|
|
<el-option label="Shopify JP" value="Shopify JP" />
|
|
|
<el-option label="TikTok UK" value="TikTok UK" />
|
|
|
@@ -328,73 +301,70 @@
|
|
|
<el-button type="primary" @click="confirmCreateOrder">确认创建</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
-
|
|
|
- <el-drawer v-model="trackDrawer" title="物流追踪" size="400px">
|
|
|
- <template v-if="currentOrder">
|
|
|
- <div class="track-header">
|
|
|
- <el-tag type="success">已发货</el-tag>
|
|
|
- <span>{{ currentOrder.carrier }}</span>
|
|
|
- <span class="track-no">{{ currentOrder.trackingNo }}</span>
|
|
|
- </div>
|
|
|
- <el-timeline class="track-timeline">
|
|
|
- <el-timeline-item timestamp="2024-01-18 14:30" type="success">商品已签收</el-timeline-item>
|
|
|
- <el-timeline-item timestamp="2024-01-17 09:20">到达广州转运中心</el-timeline-item>
|
|
|
- <el-timeline-item timestamp="2024-01-16 18:45">已从深圳南山仓发出</el-timeline-item>
|
|
|
- <el-timeline-item timestamp="2024-01-16 16:00" type="primary">快递员已取件</el-timeline-item>
|
|
|
- <el-timeline-item timestamp="2024-01-15 10:30">卖家正在打包</el-timeline-item>
|
|
|
- </el-timeline>
|
|
|
- </template>
|
|
|
- </el-drawer>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import { computed, onMounted, ref } from 'vue';
|
|
|
+import { useRouter } from 'vue-router';
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
+import {
|
|
|
+ Search, Filter, Plus, Refresh, Download, Grid, List
|
|
|
+} from '@element-plus/icons-vue';
|
|
|
import { api } from '@/api/services';
|
|
|
import type { OrderItem, OrderProductItem } from '@/types/page';
|
|
|
|
|
|
+const router = useRouter();
|
|
|
+
|
|
|
const items = ref<OrderItem[]>([]);
|
|
|
const selected = ref<OrderItem[]>([]);
|
|
|
-const savedView = ref('');
|
|
|
const viewMode = ref<'table' | 'card'>('table');
|
|
|
const loading = ref(false);
|
|
|
const page = ref(1);
|
|
|
const pageSize = ref(20);
|
|
|
-const total = computed(() => filteredItems.value.length);
|
|
|
-const showAdvanced = ref(false);
|
|
|
+const searchKeyword = ref('');
|
|
|
+const showFilterDrawer = ref(false);
|
|
|
|
|
|
const filters = ref({
|
|
|
- orderNo: '', buyer: '', channel: '', warehouse: '',
|
|
|
- orderStatus: '', paymentStatus: '', shippingStatus: '',
|
|
|
- exceptionTag: '', buyerCountry: '', dateRange: [] as string[]
|
|
|
+ orderNo: '',
|
|
|
+ buyer: '',
|
|
|
+ channel: '',
|
|
|
+ warehouse: '',
|
|
|
+ orderStatus: '',
|
|
|
+ paymentStatus: '',
|
|
|
+ shippingStatus: '',
|
|
|
+ exceptionTag: '',
|
|
|
+ buyerCountry: '',
|
|
|
+ dateRange: [] as string[]
|
|
|
});
|
|
|
|
|
|
-const countryFlags: Record<string, string> = {
|
|
|
- 'US': '🇺🇸', 'UK': '🇬🇧', 'JP': '🇯🇵', 'DE': '🇩🇪', 'FR': '🇫🇷',
|
|
|
- 'CA': '🇨🇦', 'AU': '🇦🇺', 'IT': '🇮🇹', 'ES': '🇪🇸', 'KR': '🇰🇷', 'CN': '🇨🇳'
|
|
|
-};
|
|
|
-const getCountryFlag = (c: string) => countryFlags[c] || '🌍';
|
|
|
-
|
|
|
-const filteredItems = computed(() => items.value.filter(item => {
|
|
|
- if (filters.value.orderNo && !item.orderNo.includes(filters.value.orderNo) && !(item.channelOrderNo || '').includes(filters.value.orderNo)) return false;
|
|
|
- if (filters.value.buyer && !item.buyer.toLowerCase().includes(filters.value.buyer.toLowerCase()) && !(item.buyerPhone || '').includes(filters.value.buyer)) return false;
|
|
|
- if (filters.value.channel && item.channel !== filters.value.channel) return false;
|
|
|
- if (filters.value.warehouse && item.warehouse !== filters.value.warehouse) return false;
|
|
|
- if (filters.value.orderStatus && item.orderStatus !== filters.value.orderStatus) return false;
|
|
|
- if (filters.value.paymentStatus && item.paymentStatus !== filters.value.paymentStatus) return false;
|
|
|
- if (filters.value.shippingStatus && item.shippingStatus !== filters.value.shippingStatus) return false;
|
|
|
- if (filters.value.exceptionTag && item.exceptionTag !== filters.value.exceptionTag) return false;
|
|
|
- if (filters.value.buyerCountry && item.buyerCountry !== filters.value.buyerCountry) return false;
|
|
|
- return true;
|
|
|
-}));
|
|
|
+const todayCount = computed(() => items.value.filter(i => i.createdAt?.includes('2026-04-21')).length);
|
|
|
+const pendingCount = computed(() => items.value.filter(i => ['created', 'paid'].includes(i.orderStatus || '')).length);
|
|
|
+const total = computed(() => filteredItems.value.length);
|
|
|
+
|
|
|
+const filteredItems = computed(() => {
|
|
|
+ return items.value.filter(item => {
|
|
|
+ if (searchKeyword.value) {
|
|
|
+ const kw = searchKeyword.value.toLowerCase();
|
|
|
+ if (!item.orderNo?.toLowerCase().includes(kw) &&
|
|
|
+ !(item.channelOrderNo || '').toLowerCase().includes(kw) &&
|
|
|
+ !(item.buyer || '').toLowerCase().includes(kw)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (filters.value.channel && item.channel !== filters.value.channel) return false;
|
|
|
+ if (filters.value.warehouse && item.warehouse !== filters.value.warehouse) return false;
|
|
|
+ if (filters.value.orderStatus && item.orderStatus !== filters.value.orderStatus) return false;
|
|
|
+ if (filters.value.paymentStatus && item.paymentStatus !== filters.value.paymentStatus) return false;
|
|
|
+ if (filters.value.shippingStatus && item.shippingStatus !== filters.value.shippingStatus) return false;
|
|
|
+ if (filters.value.exceptionTag && item.exceptionTag !== filters.value.exceptionTag) return false;
|
|
|
+ if (filters.value.buyerCountry && item.buyerCountry !== filters.value.buyerCountry) return false;
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+});
|
|
|
|
|
|
const statusType = (s: string) => ({ created: 'info', paid: '', allocated: 'warning', shipped: 'success', delivered: 'success', completed: 'success', cancelled: 'danger', refunded: 'danger' }[s] || '');
|
|
|
const statusLabel = (s: string) => ({ created: '待支付', paid: '已支付', allocated: '已分配', shipped: '已发货', delivered: '已签收', completed: '已完成', cancelled: '已取消', refunded: '已退款' }[s] || s);
|
|
|
-const paymentStatusType = (s: string) => ({ '待支付': 'warning', '已支付': 'success', '已退款': 'danger', pending: 'warning', paid: 'success', refunded: 'danger' }[s] || '');
|
|
|
-const shippingStatusType = (s: string) => ({ unshipped: 'info', processing: 'warning', shipped: '', intransit: 'primary', delivered: 'success' }[s] || '');
|
|
|
-const shippingStatusLabel = (s: string) => ({ unshipped: '未发货', processing: '处理中', shipped: '已发货', intransit: '运输中', delivered: '已签收' }[s] || s);
|
|
|
-const buyerLevelTag = (l: string) => ({ 'VIP': 'danger', '黄金': 'warning', '普通': 'info' }[l] || 'info');
|
|
|
|
|
|
const rowClass = ({ row }: { row: OrderItem }) => row.exceptionTag !== '正常' ? 'exception-row' : '';
|
|
|
|
|
|
@@ -404,72 +374,38 @@ const loadData = async () => {
|
|
|
const res = await api.getOrders();
|
|
|
items.value = res.items || [];
|
|
|
} catch (e) {
|
|
|
- console.error('Failed to load orders:', e);
|
|
|
items.value = [];
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-const generateMockOrders = (): OrderItem[] => {
|
|
|
- const channels = ['Shopify US', 'Shopify JP', 'TikTok UK', 'Amazon US'];
|
|
|
- const countries = ['US', 'UK', 'JP', 'DE', 'FR'];
|
|
|
- const levels = ['VIP', '黄金', '普通'];
|
|
|
- const statuses = ['created', 'paid', 'allocated', 'shipped', 'delivered'];
|
|
|
- const warehouses = ['深圳南山仓', '义乌商贸仓', '洛杉矶海外仓'];
|
|
|
- const handlers = ['陈欣', '王磊', '张丽', '李明'];
|
|
|
-
|
|
|
- return Array.from({ length: 12 }, (_, i) => {
|
|
|
- const orderNo = `OMS-202604${String(19 - Math.floor(i / 3)).padStart(2, '0')}-${String(1000 + i).padStart(4, '0')}`;
|
|
|
- const country = countries[i % countries.length];
|
|
|
- const prods: OrderProductItem[] = Array.from({ length: (i % 3) + 1 }, (_, j) => ({
|
|
|
- id: `item-${i}-${j}`, productId: `p-${i}-${j}`, skuId: `s-${i}-${j}`,
|
|
|
- sku: `SKU-${String.fromCharCode(65 + j)}${100 + i}-${country}`,
|
|
|
- productTitle: ['TravelFlex Carry-On 20寸', 'Classic Leather Tote', 'Urban Backpack', 'Yoga Mat Pro'][j % 4],
|
|
|
- productImage: '', categoryId: `cat-${j}`, categoryName: ['行李箱', '皮革包', '双肩包', '运动'][j % 4],
|
|
|
- specs: [], barcode: `BC${Date.now()}${j}`, qty: (i % 3) + 1,
|
|
|
- price: (Math.random() * 80 + 20).toFixed(2), costPrice: (Math.random() * 40 + 10).toFixed(2),
|
|
|
- profit: (Math.random() * 20 + 5).toFixed(2), profitRate: Math.floor(Math.random() * 30 + 15),
|
|
|
- subtotal: (Math.random() * 150 + 30).toFixed(2), weight: Math.random() * 2 + 0.5,
|
|
|
- available: 50, locked: 5, inTransit: 20, reservedQty: 0, shippedQty: 0, returnedQty: 0, giftFlag: false
|
|
|
- }));
|
|
|
- const amount = prods.reduce((s, p) => s + parseFloat(p.subtotal), 0);
|
|
|
- return {
|
|
|
- id: `order-${i}`, orderNo,
|
|
|
- channelOrderNo: channels[i % 4] === 'Shopify US' ? `CH${Date.now()}${i}` : '',
|
|
|
- channel: channels[i % channels.length],
|
|
|
- orderStatus: statuses[i % statuses.length],
|
|
|
- shippingStatus: ['unshipped', 'processing', 'shipped'][i % 3],
|
|
|
- paymentStatus: ['pending', 'paid', 'paid', 'paid'][i % 4],
|
|
|
- exceptionTag: Math.random() > 0.85 ? ['地址需复核', '高频下单'][i % 2] : '正常',
|
|
|
- priority: Math.random() > 0.9 ? 'urgent' : 'normal',
|
|
|
- createdAt: `2026-04-${String(19 - Math.floor(i / 4)).padStart(2, '0')} ${String(10 + (i % 10)).padStart(2, '0')}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}:00`,
|
|
|
- updatedAt: '', paidAt: '', shippedAt: '', deliveredAt: '',
|
|
|
- buyerId: `buyer-${1000 + i}`, buyer: ['Olivia Zhang', 'Noah Smith', 'Liam Chen', 'Emma Wilson', 'Sophie Brown', 'James Wang', 'Lisa Johnson', 'David Lee'][i % 8],
|
|
|
- buyerEmail: `buyer${i}@example.com`, buyerPhone: `+1 213-555-${String(1000 + i).padStart(4, '0')}`,
|
|
|
- buyerCountry: country, buyerLevel: levels[i % levels.length], buyerTags: Math.random() > 0.7 ? ['高价值'] : [],
|
|
|
- buyerRegisterTime: '', buyerOrderCount: 0, buyerTotalSpent: '',
|
|
|
- receiverName: '', receiverPhone: '', receiverCountry: country, receiverState: '', receiverCity: ['Los Angeles', 'New York', 'Houston', 'Miami', 'Seattle'][i % 5],
|
|
|
- receiverDistrict: '', receiverPostalCode: `${10000 + i * 100}`, receiverAddress: `${100 + i * 10} Main St`, receiverAddressLang: '', latitude: 0, longitude: 0,
|
|
|
- transactionId: `TXN${Date.now()}${i}`, paymentMethod: ['Visa', 'MasterCard', 'PayPal'][i % 3], paymentTime: '',
|
|
|
- currency: country === 'JP' ? 'JPY' : country === 'UK' ? 'GBP' : 'USD', exchangeRate: 1,
|
|
|
- orderAmount: amount.toFixed(2), orderAmountCNY: (amount * 7.2).toFixed(2), taxAmount: (amount * 0.08).toFixed(2),
|
|
|
- shippingFee: (Math.random() * 15 + 5).toFixed(2), discountAmount: Math.random() > 0.7 ? (amount * 0.1).toFixed(2) : '0.00',
|
|
|
- couponCode: '', couponDiscount: '', actualPaid: (amount - (Math.random() > 0.7 ? amount * 0.1 : 0)).toFixed(2),
|
|
|
- refundAmount: '0.00', refundStatus: '',
|
|
|
- shippingMethod: '', carrier: ['顺丰', 'DHL', 'FedEx'][i % 3], trackingNo: '', trackingUrl: '',
|
|
|
- warehouse: warehouses[i % warehouses.length], warehouseLocation: `A-${10 + i}-${String.fromCharCode(65 + (i % 5))}`,
|
|
|
- estimatedDelivery: '', weight: 0, length: 0, width: 0, height: 0,
|
|
|
- utmSource: '', utmMedium: '', utmCampaign: '', utmContent: '', utmTerm: '', referrerUrl: '', landingPage: '', ip: '', ipCountry: '', device: '', browser: '', os: '',
|
|
|
- parentOrderId: '', childOrderIds: [], mergeOrderId: '', relatedOrderId: '', originalOrderId: '',
|
|
|
- handler: handlers[i % handlers.length], handlerGroup: '', orderTags: [], internalRemark: '', buyerRemark: '',
|
|
|
- itemCount: prods.reduce((s, p) => s + p.qty, 0), amount: amount.toFixed(2), items: prods, timeline: [], logs: []
|
|
|
- };
|
|
|
- });
|
|
|
+const resetFilters = () => {
|
|
|
+ searchKeyword.value = '';
|
|
|
+ filters.value = { orderNo: '', buyer: '', channel: '', warehouse: '', orderStatus: '', paymentStatus: '', shippingStatus: '', exceptionTag: '', buyerCountry: '', dateRange: [] };
|
|
|
+ showFilterDrawer.value = false;
|
|
|
};
|
|
|
|
|
|
-const resetFilters = () => { filters.value = { orderNo: '', buyer: '', channel: '', warehouse: '', orderStatus: '', paymentStatus: '', shippingStatus: '', exceptionTag: '', buyerCountry: '', dateRange: [] }; };
|
|
|
const onSelection = (rows: OrderItem[]) => { selected.value = rows; };
|
|
|
+
|
|
|
+const handleRowClick = (row: OrderItem) => {
|
|
|
+ viewDetail(row);
|
|
|
+};
|
|
|
+
|
|
|
+const viewDetail = (row: OrderItem) => {
|
|
|
+ router.push(`/order/detail?id=${row.id}`);
|
|
|
+};
|
|
|
+
|
|
|
+const quickAssign = (row: OrderItem) => {
|
|
|
+ currentOrder.value = row;
|
|
|
+ assignDialog.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const trackLogistics = (row: OrderItem) => {
|
|
|
+ currentOrder.value = row;
|
|
|
+ trackDrawer.value = true;
|
|
|
+};
|
|
|
+
|
|
|
const refreshSync = async () => {
|
|
|
try {
|
|
|
const count = Math.floor(Math.random() * 3) + 1;
|
|
|
@@ -478,108 +414,269 @@ const refreshSync = async () => {
|
|
|
}
|
|
|
ElMessage.success(`同步完成:新增 ${count} 单`);
|
|
|
loadData();
|
|
|
- } catch (e) {
|
|
|
+ } catch {
|
|
|
ElMessage.error('同步失败');
|
|
|
}
|
|
|
};
|
|
|
-const doExport = () => { ElMessage.success('导出已开始'); };
|
|
|
|
|
|
-const handleBatchCommand = (cmd: string) => {
|
|
|
- if (!selected.value.length) { ElMessage.warning('请先选择订单'); return; }
|
|
|
- if (cmd === 'mark') { tagDialog.value = true; }
|
|
|
- else if (cmd === 'assign') { assignDialog.value = true; }
|
|
|
- else if (cmd === 'cancel') { ElMessageBox.confirm(`确定取消 ${selected.value.length} 个订单?`, '提示', { type: 'warning' }).then(() => ElMessage.success('已取消')).catch(() => {}); }
|
|
|
-};
|
|
|
+const doExport = () => { ElMessage.success('导出已开始'); };
|
|
|
|
|
|
const handleRowCommand = (cmd: string, row: OrderItem) => {
|
|
|
if (cmd === 'track') { currentOrder.value = row; trackDrawer.value = true; }
|
|
|
- else if (cmd === 'assign') { assignDialog.value = true; }
|
|
|
+ else if (cmd === 'assign') { quickAssign(row); }
|
|
|
else if (cmd === 'remark') { ElMessage.info('备注功能'); }
|
|
|
else if (cmd === 'print') { ElMessage.success(`正在生成 ${row.orderNo} 面单`); }
|
|
|
- else if (cmd === 'cancel') { ElMessageBox.confirm(`确定取消订单 ${row.orderNo}?`, '提示', { type: 'warning' }).then(() => ElMessage.success('已取消')).catch(() => {}); }
|
|
|
+ else if (cmd === 'cancel') {
|
|
|
+ ElMessageBox.confirm(`确定取消订单 ${row.orderNo}?`, '提示', { type: 'warning' })
|
|
|
+ .then(() => ElMessage.success('已取消')).catch(() => {});
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const batchAssign = () => { assignDialog.value = true; };
|
|
|
+const batchTag = () => { ElMessage.info('批量标记功能'); };
|
|
|
+const batchCancel = () => {
|
|
|
+ ElMessageBox.confirm(`确定取消 ${selected.value.length} 个订单?`, '提示', { type: 'warning' })
|
|
|
+ .then(() => { selected.value = []; ElMessage.success('已取消'); }).catch(() => {});
|
|
|
};
|
|
|
|
|
|
const assignDialog = ref(false);
|
|
|
const assignPerson = ref('');
|
|
|
const assignRemark = ref('');
|
|
|
-const tagDialog = ref(false);
|
|
|
-const selectedTags = ref<string[]>([]);
|
|
|
const createDialog = ref(false);
|
|
|
const createForm = ref({ channel: '', buyerName: '', phone: '', address: '', remark: '' });
|
|
|
const trackDrawer = ref(false);
|
|
|
const currentOrder = ref<OrderItem | null>(null);
|
|
|
|
|
|
-const confirmAssign = () => { ElMessage.success(`已分配给 ${assignPerson.value}`); assignDialog.value = false; };
|
|
|
-const confirmTag = () => { ElMessage.success(`已添加标签`); tagDialog.value = false; };
|
|
|
-const openCreateOrder = () => { createForm.value = { channel: '', buyerName: '', phone: '', address: '', remark: '' }; createDialog.value = true; };
|
|
|
-const confirmCreateOrder = () => { if (!createForm.value.channel || !createForm.value.buyerName) { ElMessage.warning('请填写必填项'); return; } ElMessage.success('订单创建成功'); createDialog.value = false; loadData(); };
|
|
|
+const confirmAssign = () => {
|
|
|
+ ElMessage.success(`已分配给 ${assignPerson.value}`);
|
|
|
+ assignDialog.value = false;
|
|
|
+ selected.value = [];
|
|
|
+};
|
|
|
+
|
|
|
+const openCreateOrder = () => {
|
|
|
+ createForm.value = { channel: '', buyerName: '', phone: '', address: '', remark: '' };
|
|
|
+ createDialog.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const confirmCreateOrder = () => {
|
|
|
+ if (!createForm.value.channel || !createForm.value.buyerName) {
|
|
|
+ ElMessage.warning('请填写必填项');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ElMessage.success('订单创建成功');
|
|
|
+ createDialog.value = false;
|
|
|
+ loadData();
|
|
|
+};
|
|
|
|
|
|
onMounted(loadData);
|
|
|
</script>
|
|
|
|
|
|
-<style scoped>
|
|
|
-.filter-section { padding: 16px 20px; }
|
|
|
-.filter-main { }
|
|
|
-.filter-advanced { margin-top: 12px; padding-top: 12px; border-top: 1px dashed #eee; }
|
|
|
-.filter-form { display: flex; flex-wrap: wrap; gap: 12px; }
|
|
|
-.filter-item { margin-bottom: 0; }
|
|
|
-
|
|
|
-.toolbar-section { padding: 12px 20px; margin-top: 12px; }
|
|
|
-.table-toolbar { display: flex; justify-content: space-between; align-items: center; }
|
|
|
-.toolbar-left { display: flex; gap: 8px; }
|
|
|
-.toolbar-right { display: flex; gap: 10px; align-items: center; }
|
|
|
-
|
|
|
-.table-section { margin-top: 12px; padding: 0; }
|
|
|
-.table-section :deep(.el-table) { border-radius: 0; }
|
|
|
-.table-section :deep(.el-table__header th) { background: #fafafa; }
|
|
|
-
|
|
|
-.order-table :deep(.el-table th) { padding: 12px 8px; }
|
|
|
-.order-table :deep(.el-table td) { padding: 12px 8px; }
|
|
|
-
|
|
|
-.order-info { display: flex; flex-direction: column; gap: 4px; }
|
|
|
-.order-no { font-weight: 600; color: var(--cb-primary); font-size: 14px; }
|
|
|
-.order-meta { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
|
-
|
|
|
-.buyer-cell { display: flex; flex-direction: column; gap: 2px; }
|
|
|
-.buyer-name { font-weight: 500; display: flex; align-items: center; gap: 6px; }
|
|
|
-.buyer-contact { font-size: 12px; color: #666; }
|
|
|
-
|
|
|
-.location { font-size: 13px; }
|
|
|
-.item-count { font-size: 13px; color: var(--cb-primary); cursor: pointer; display: flex; align-items: center; gap: 4px; }
|
|
|
-
|
|
|
-.amount-cell { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
|
|
|
-.amount { font-weight: 600; color: var(--cb-accent); font-size: 14px; }
|
|
|
-.discount { font-size: 12px; color: #67c23a; }
|
|
|
-
|
|
|
-.warehouse { font-size: 13px; }
|
|
|
-
|
|
|
-.items-popover { display: flex; flex-direction: column; gap: 6px; }
|
|
|
-.item-row { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid #f0f0f0; }
|
|
|
-.item-name { font-size: 13px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
-.item-qty { font-size: 12px; color: #999; }
|
|
|
-.item-more { font-size: 12px; color: var(--cb-primary); text-align: center; padding-top: 4px; }
|
|
|
-
|
|
|
-:deep(.exception-row) { background-color: rgba(230, 162, 60, 0.08) !important; }
|
|
|
-
|
|
|
-.order-cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; padding: 16px; }
|
|
|
-.order-card { background: #fafafa; border-radius: 12px; padding: 16px; border: 1px solid #f0f0f0; transition: all 0.2s; }
|
|
|
-.order-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
|
|
-.order-card.exception { border-left: 3px solid #e6a23c; }
|
|
|
-.card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
|
|
|
-.card-title { display: flex; flex-direction: column; gap: 4px; }
|
|
|
-.card-order-no { font-weight: 600; color: var(--cb-primary); font-size: 14px; }
|
|
|
-.card-buyer { display: flex; justify-content: space-between; margin-bottom: 8px; }
|
|
|
-.buyer-name { font-weight: 500; }
|
|
|
-.buyer-country { font-size: 13px; color: #666; }
|
|
|
-.card-location { display: flex; align-items: center; gap: 6px; color: #666; font-size: 13px; margin-bottom: 12px; }
|
|
|
-.card-items { display: flex; justify-content: space-between; padding: 10px 0; border-top: 1px solid #eee; border-bottom: 1px solid #eee; margin-bottom: 12px; }
|
|
|
-.card-amount { font-weight: 600; color: var(--cb-accent); font-size: 15px; }
|
|
|
-.card-footer { display: flex; justify-content: space-between; align-items: center; }
|
|
|
-.card-time { font-size: 12px; color: #999; }
|
|
|
-
|
|
|
-.pagination-wrapper { display: flex; justify-content: flex-end; padding: 16px; }
|
|
|
-
|
|
|
-.track-header { display: flex; align-items: center; gap: 10px; padding: 12px; background: #f5f5f5; border-radius: 8px; margin-bottom: 20px; }
|
|
|
-.track-no { font-family: monospace; font-weight: 600; }
|
|
|
-.track-timeline { padding: 0 8px; }
|
|
|
-</style>
|
|
|
+<style scoped lang="scss">
|
|
|
+.page-hero {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px 24px;
|
|
|
+ background: var(--cb-panel);
|
|
|
+ border: 1px solid var(--cb-border);
|
|
|
+ border-radius: var(--el-border-radius-base);
|
|
|
+}
|
|
|
+
|
|
|
+.page-hero__meta h1 {
|
|
|
+ margin: 0 0 4px;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.page-hero__meta p {
|
|
|
+ margin: 0;
|
|
|
+ color: var(--cb-text-soft);
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-bar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-bar__main {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-bar__actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.batch-bar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ background: var(--cb-primary-soft);
|
|
|
+ border-radius: var(--el-border-radius-base);
|
|
|
+ border: 1px solid rgba(14, 165, 233, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.batch-bar__info {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--cb-primary);
|
|
|
+ margin-right: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar__left {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar__right {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.order-table {
|
|
|
+ margin: 0 -20px;
|
|
|
+}
|
|
|
+
|
|
|
+.order-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.order-no {
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--cb-primary);
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.order-meta {
|
|
|
+ display: flex;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.buyer-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.buyer-name {
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.buyer-contact {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--cb-text-soft);
|
|
|
+}
|
|
|
+
|
|
|
+.item-count {
|
|
|
+ color: var(--cb-primary);
|
|
|
+ cursor: pointer;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.amount {
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--cb-text);
|
|
|
+}
|
|
|
+
|
|
|
+.warehouse-text {
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.items-popover {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.item-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 4px 0;
|
|
|
+ border-bottom: 1px solid var(--cb-border);
|
|
|
+}
|
|
|
+
|
|
|
+.item-row:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+
|
|
|
+.item-name {
|
|
|
+ font-size: 13px;
|
|
|
+ max-width: 180px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.item-qty {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--cb-text-soft);
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.action-buttons {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.action-buttons .el-button {
|
|
|
+ padding-left: 8px;
|
|
|
+ padding-right: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.pagination-wrapper {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding: 16px 0 0;
|
|
|
+}
|
|
|
+
|
|
|
+.track-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 16px;
|
|
|
+ background: var(--cb-bg-soft);
|
|
|
+ border-radius: var(--el-border-radius-base);
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.track-carrier {
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.track-no {
|
|
|
+ font-family: monospace;
|
|
|
+ color: var(--cb-text-soft);
|
|
|
+}
|
|
|
+
|
|
|
+.track-timeline {
|
|
|
+ padding: 0 8px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.exception-row) {
|
|
|
+ background-color: var(--cb-warning-soft) !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-table__row) {
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+</style>
|