2751 lines
103 KiB
Vue
2751 lines
103 KiB
Vue
<template>
|
||
<div>
|
||
<list-layout>
|
||
<!-- 搜索区域 -->
|
||
<template #search>
|
||
<!-- 移动端:搜索表单部分 -->
|
||
<mobile-search-form
|
||
:model="queryParams"
|
||
@search="handleQuery"
|
||
@reset="resetQuery"
|
||
@quick-search="handleQuickSearch"
|
||
>
|
||
<template #form="{ expanded }">
|
||
<el-form
|
||
:model="queryParams"
|
||
:inline="true"
|
||
label-width="68px"
|
||
label-position="left"
|
||
>
|
||
<el-form-item label="备注">
|
||
<el-input v-model="queryParams.remark" placeholder="单据备注" clearable size="small" @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="订单号">
|
||
<el-input v-model="queryParams.orderSearch" placeholder="订单号/第三方单号/分销标识" clearable size="small" @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="分销标记">
|
||
<el-input v-model="queryParams.distributionMark" placeholder="分销标记" clearable size="small" @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="型号">
|
||
<el-input v-model="queryParams.modelNumber" placeholder="型号" clearable size="small" @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="下单人">
|
||
<el-input v-model="queryParams.buyer" placeholder="下单人" clearable size="small" @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="地址">
|
||
<el-input v-model="queryParams.address" placeholder="收货地址" clearable size="small" @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="状态">
|
||
<el-input v-model="queryParams.status" placeholder="备注/状态" clearable size="small" @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="下单时间">
|
||
<el-date-picker
|
||
v-model="dateRange"
|
||
type="daterange"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
value-format="yyyy-MM-dd"
|
||
size="small"
|
||
range-separator="至"
|
||
@change="handleDateRangeChange"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="完成日期">
|
||
<el-checkbox v-model="queryParams.hasFinishTime" @change="handleQuery">仅显示已完成订单</el-checkbox>
|
||
</el-form-item>
|
||
<el-form-item label="退款状态">
|
||
<el-select v-model="queryParams.isRefunded" placeholder="全部" clearable size="small" style="width: 120px;">
|
||
<el-option label="已退款" :value="1" />
|
||
<el-option label="未退款" :value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="退款到账">
|
||
<el-select v-model="queryParams.isRefundReceived" placeholder="全部" clearable size="small" style="width: 120px;">
|
||
<el-option label="已到账" :value="1" />
|
||
<el-option label="未到账" :value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="后返到账">
|
||
<el-select v-model="queryParams.isRebateReceived" placeholder="全部" clearable size="small" style="width: 120px;">
|
||
<el-option label="已到账" :value="1" />
|
||
<el-option label="未到账" :value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="点过价保">
|
||
<el-select v-model="queryParams.isPriceProtected" placeholder="全部" clearable size="small" style="width: 120px;">
|
||
<el-option label="已点价保" :value="1" />
|
||
<el-option label="未点价保" :value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="开过专票">
|
||
<el-select v-model="queryParams.isInvoiceOpened" placeholder="全部" clearable size="small" style="width: 120px;">
|
||
<el-option label="已开专票" :value="1" />
|
||
<el-option label="未开专票" :value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="晒过评价">
|
||
<el-select v-model="queryParams.isReviewPosted" placeholder="全部" clearable size="small" style="width: 120px;">
|
||
<el-option label="已晒评价" :value="1" />
|
||
<el-option label="未晒评价" :value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<!-- 搜索按钮:桌面端显示,移动端展开时在底部显示 -->
|
||
<el-form-item>
|
||
<el-button type="primary" size="small" icon="el-icon-search" @click="handleQuery">搜索</el-button>
|
||
<el-button size="small" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
||
<el-button type="warning" size="small" icon="el-icon-download" @click="handleExport" v-hasPermi="['system:jdorder:export']">导出</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</template>
|
||
</mobile-search-form>
|
||
|
||
<!-- 操作按钮区域(移动端单独显示) -->
|
||
<div class="action-buttons-section mobile-only">
|
||
<mobile-button-group
|
||
:buttons="actionButtons"
|
||
:primary-count="2"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 桌面端按钮组 -->
|
||
<div class="desktop-action-buttons desktop-only">
|
||
<el-button type="success" size="small" icon="el-icon-setting" @click="showAutoWriteConfig = true" title="配置H-TF订单自动写入腾讯文档">腾峰文档配置</el-button>
|
||
<el-button type="success" size="small" icon="el-icon-monitor" @click="showPushMonitor = true" title="查看推送监控和历史记录">推送监控</el-button>
|
||
<el-button type="warning" size="small" icon="el-icon-user" @click="showTouserConfig = true" title="配置分销标识对应的企业微信接收人">接收人配置</el-button>
|
||
<el-button type="primary" size="small" icon="el-icon-refresh-right" @click="handleBatchSyncLogistics" :loading="batchSyncLoading" title="批量同步物流链接到腾讯文档">一键发货到腾峰</el-button>
|
||
<el-button type="success" size="small" icon="el-icon-check" @click="handleBatchMarkRebateReceived" :loading="batchMarkLoading" title="批量将赔付金额大于0的订单标记为后返到账(仅执行一次)">批量标记后返到账</el-button>
|
||
<el-button type="warning" size="small" icon="el-icon-sort" @click="handleReverseSyncThirdPartyOrderNo" :loading="reverseSyncLoading" title="从腾讯文档第850行开始,通过物流链接反向匹配订单,将腾讯文档的单号列值写入到订单的第三方单号字段">反向同步第三方单号</el-button>
|
||
<el-button v-if="!isMobile" type="primary" size="small" icon="el-icon-document-copy" @click="handleBatchCopyExcelText" :disabled="selectedRows.length === 0" title="批量复制选中订单的录单格式(Excel可粘贴)">批量复制录单格式</el-button>
|
||
<el-button v-if="!isMobile" type="success" size="small" icon="el-icon-document-copy" @click="handleBatchCopyRebateText" :disabled="selectedRows.length === 0" title="批量复制选中订单的后返录表格式(Excel可粘贴)">批量复制后返录表</el-button>
|
||
<el-button v-if="!isMobile" type="info" size="small" icon="el-icon-document-copy" @click="handleBatchCopySichuanCommerceText" :disabled="selectedRows.length === 0" title="批量复制选中订单的四川商贸录表格式(日期 型号 数量 地址 价格 备注 是否安排 物流)">四川商贸录表</el-button>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 表格区域 -->
|
||
<template #table>
|
||
<!-- 移动端卡片列表 -->
|
||
<div v-if="isMobile" class="mobile-order-list" v-loading="loading">
|
||
<div
|
||
v-for="row in list"
|
||
:key="row.id"
|
||
class="mobile-order-card"
|
||
>
|
||
<!-- 卡片头部 -->
|
||
<div class="card-header">
|
||
<div class="header-left">
|
||
<div class="order-id">{{ row.remark || row.orderId }}</div>
|
||
<el-tag
|
||
v-if="row.orderStatus != null"
|
||
:type="getOrderStatusType(row.orderStatus)"
|
||
size="mini"
|
||
style="margin-left: 8px;">
|
||
{{ getOrderStatusText(row.orderStatus) }}
|
||
</el-tag>
|
||
</div>
|
||
<div class="header-right">
|
||
<el-dropdown
|
||
trigger="click"
|
||
placement="left"
|
||
@command="handleActionCommand"
|
||
:popper-class="'mobile-action-dropdown'">
|
||
<el-button
|
||
type="primary"
|
||
size="mini"
|
||
icon="el-icon-more"
|
||
circle
|
||
class="mobile-action-btn">
|
||
</el-button>
|
||
<el-dropdown-menu slot="dropdown">
|
||
<el-dropdown-item
|
||
:command="{action: 'copyOrderId', row: row}"
|
||
icon="el-icon-copy-document">
|
||
复制订单号
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
v-if="row.thirdPartyOrderNo"
|
||
:command="{action: 'copyThirdParty', row: row}"
|
||
icon="el-icon-copy-document">
|
||
复制第三方单号
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'copyAddress', row: row}"
|
||
icon="el-icon-copy-document">
|
||
复制地址
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
v-if="row.logisticsLink"
|
||
:command="{action: 'copyLogistics', row: row}"
|
||
icon="el-icon-copy-document">
|
||
复制物流链接
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'copyReturn', row: row}"
|
||
icon="el-icon-document-copy">
|
||
退货复制
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'copyExcel', row: row}"
|
||
icon="el-icon-document-copy"
|
||
:class="{'is-copied': isExcelTextCopied(row.id)}">
|
||
{{ isExcelTextCopied(row.id) ? '✓ 录单格式(已复制)' : '录单格式' }}
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'copyRebate', row: row}"
|
||
icon="el-icon-document-copy">
|
||
后返录表
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'fetchLogistics', row: row}"
|
||
icon="el-icon-truck"
|
||
style="color: #67C23A;">
|
||
获取物流
|
||
</el-dropdown-item>
|
||
<el-dropdown-item divided>
|
||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 0 10px;">
|
||
<span>统计</span>
|
||
<el-switch
|
||
v-model="row.isCountEnabled"
|
||
:active-value="1"
|
||
:inactive-value="0"
|
||
@change="handleCountEnabledChange(row)"
|
||
size="mini"
|
||
style="margin-left: 10px;">
|
||
</el-switch>
|
||
</div>
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'delete', row: row}"
|
||
icon="el-icon-delete"
|
||
divided
|
||
style="color: #f56c6c;">
|
||
删除
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</el-dropdown>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 卡片内容 -->
|
||
<div class="card-content">
|
||
<div class="field-row">
|
||
<span class="field-label">订单号</span>
|
||
<span class="field-value">{{ row.orderId }}</span>
|
||
</div>
|
||
<div class="field-row" v-if="row.thirdPartyOrderNo">
|
||
<span class="field-label">第三方单号</span>
|
||
<span class="field-value">{{ row.thirdPartyOrderNo }}</span>
|
||
</div>
|
||
<div class="field-row">
|
||
<span class="field-label">标记</span>
|
||
<span class="field-value">{{ row.distributionMark || '-' }}</span>
|
||
</div>
|
||
<div class="field-row">
|
||
<span class="field-label">型号</span>
|
||
<span class="field-value">{{ row.modelNumber || '-' }}</span>
|
||
</div>
|
||
<div class="field-row">
|
||
<span class="field-label">地址</span>
|
||
<span class="field-value address-text">{{ row.address || '-' }}</span>
|
||
</div>
|
||
<div class="field-row">
|
||
<span class="field-label">付款金额</span>
|
||
<span class="field-value amount">{{ toYuan(row.paymentAmount) }}</span>
|
||
</div>
|
||
<div class="field-row">
|
||
<span class="field-label">后返金额</span>
|
||
<span class="field-value amount">{{ toYuan(row.rebateAmount) }}</span>
|
||
</div>
|
||
<div class="field-row">
|
||
<span class="field-label">下单人</span>
|
||
<span class="field-value">{{ row.buyer || '-' }}</span>
|
||
</div>
|
||
|
||
<!-- 退款状态 -->
|
||
<div class="field-row refund-status">
|
||
<span class="field-label">退款状态</span>
|
||
<div class="field-value status-tags">
|
||
<el-tag
|
||
:type="row.isRefunded === 1 ? 'warning' : 'info'"
|
||
size="mini"
|
||
@click.native="toggleRefunded(row)"
|
||
style="cursor: pointer; margin-right: 8px;">
|
||
{{ row.isRefunded === 1 ? '已退款' : '未退款' }}
|
||
</el-tag>
|
||
<el-tag
|
||
:type="row.isRefundReceived === 1 ? 'success' : 'info'"
|
||
size="mini"
|
||
@click.native="toggleRefundReceived(row)"
|
||
style="cursor: pointer; margin-right: 8px;">
|
||
{{ row.isRefundReceived === 1 ? '退款到账' : '未到账' }}
|
||
</el-tag>
|
||
<el-tag
|
||
:type="row.isRebateReceived === 1 ? 'success' : 'info'"
|
||
size="mini"
|
||
@click.native="toggleRebateReceived(row)"
|
||
style="cursor: pointer; margin-right: 8px;">
|
||
{{ row.isRebateReceived === 1 ? '后返到账' : '未到账' }}
|
||
</el-tag>
|
||
<el-tag
|
||
:type="row.isPriceProtected === 1 ? 'warning' : 'info'"
|
||
size="mini"
|
||
@click.native="togglePriceProtected(row)"
|
||
style="cursor: pointer; margin-right: 8px;">
|
||
{{ row.isPriceProtected === 1 ? '点过价保' : '未点价保' }}
|
||
</el-tag>
|
||
<el-tag
|
||
:type="row.isInvoiceOpened === 1 ? 'success' : 'info'"
|
||
size="mini"
|
||
@click.native="toggleInvoiceOpened(row)"
|
||
style="cursor: pointer; margin-right: 8px;">
|
||
{{ row.isInvoiceOpened === 1 ? '开过专票' : '未开专票' }}
|
||
</el-tag>
|
||
<el-tag
|
||
:type="row.isReviewPosted === 1 ? 'success' : 'info'"
|
||
size="mini"
|
||
@click.native="toggleReviewPosted(row)"
|
||
style="cursor: pointer;">
|
||
{{ row.isReviewPosted === 1 ? '晒过评价' : '未晒评价' }}
|
||
</el-tag>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field-row">
|
||
<span class="field-label">创建时间</span>
|
||
<span class="field-value">{{ parseTime(row.createTime) }}</span>
|
||
</div>
|
||
<div class="field-row" v-if="row.finishTime">
|
||
<span class="field-label">完成时间</span>
|
||
<span class="field-value">{{ parseTime(row.finishTime) }}</span>
|
||
</div>
|
||
<div class="field-row" v-if="row.status">
|
||
<span class="field-label">备注/状态</span>
|
||
<span class="field-value">{{ row.status }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<el-empty v-if="!loading && list.length === 0" description="暂无数据" :image-size="100" />
|
||
</div>
|
||
|
||
<!-- 桌面端表格 -->
|
||
<el-table v-else
|
||
:data="list"
|
||
v-loading="loading"
|
||
border
|
||
stripe
|
||
:default-sort="{prop: 'createTime', order: 'descending'}"
|
||
@sort-change="handleSortChange"
|
||
@selection-change="handleSelectionChange"
|
||
style="width: 100%;"
|
||
class="order-table">
|
||
<!-- 多选列(仅桌面端显示) -->
|
||
<el-table-column v-if="!isMobile" type="selection" width="55" fixed="left" align="center"/>
|
||
<!-- 核心信息列 -->
|
||
<el-table-column label="内部单号" prop="remark" width="140" sortable :fixed="isMobile ? false : 'left'"/>
|
||
<el-table-column label="订单号" prop="orderId" width="180"/>
|
||
<el-table-column label="第三方单号" prop="thirdPartyOrderNo" width="150">
|
||
<template slot-scope="scope">
|
||
<span v-if="scope.row.thirdPartyOrderNo">{{ scope.row.thirdPartyOrderNo }}</span>
|
||
<span v-else style="color: #999;">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<!-- 业务信息列 -->
|
||
<el-table-column label="标记" prop="distributionMark" width="100"/>
|
||
<el-table-column label="型号" prop="modelNumber" width="160"/>
|
||
<el-table-column label="地址" prop="address" min-width="280" show-overflow-tooltip class-name="address-cell">
|
||
<template slot-scope="scope">
|
||
<span style="font-weight: bold; display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ scope.row.address }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<!-- 金额信息列 -->
|
||
<el-table-column label="付款金额" prop="paymentAmount" width="110" align="right">
|
||
<template slot-scope="scope">{{ toYuan(scope.row.paymentAmount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="后返金额" prop="rebateAmount" width="110" align="right">
|
||
<template slot-scope="scope">{{ toYuan(scope.row.rebateAmount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="下单人" prop="buyer" width="100"/>
|
||
|
||
<!-- 退款状态标签列(多行显示) -->
|
||
<el-table-column label="退款状态" width="160" align="left">
|
||
<template slot-scope="scope">
|
||
<div style="display: flex; flex-direction: column; gap: 6px;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span style="min-width: 80px; font-size: 12px; color: #606266;">是否退款:</span>
|
||
<el-tag
|
||
:type="scope.row.isRefunded === 1 ? 'warning' : 'info'"
|
||
size="small"
|
||
:title="scope.row.isRefunded === 1 && scope.row.refundDate ? '退款日期:' + parseTime(scope.row.refundDate) : ''"
|
||
@click.native="toggleRefunded(scope.row)"
|
||
style="cursor: pointer; flex: 1;">
|
||
{{ scope.row.isRefunded === 1 ? '已退款' : '未退款' }}
|
||
</el-tag>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span style="min-width: 80px; font-size: 12px; color: #606266;">退款给我:</span>
|
||
<el-tag
|
||
:type="scope.row.isRefundReceived === 1 ? 'success' : 'info'"
|
||
size="small"
|
||
:title="scope.row.isRefundReceived === 1 && scope.row.refundReceivedDate ? '退款到账日期:' + parseTime(scope.row.refundReceivedDate) : ''"
|
||
@click.native="toggleRefundReceived(scope.row)"
|
||
style="cursor: pointer; flex: 1;">
|
||
{{ scope.row.isRefundReceived === 1 ? '退款到账' : '未到账' }}
|
||
</el-tag>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span style="min-width: 80px; font-size: 12px; color: #606266;">后返到账:</span>
|
||
<el-tag
|
||
:type="scope.row.isRebateReceived === 1 ? 'success' : 'info'"
|
||
size="small"
|
||
:title="scope.row.isRebateReceived === 1 && scope.row.rebateReceivedDate ? '后返到账日期:' + parseTime(scope.row.rebateReceivedDate) : ''"
|
||
@click.native="toggleRebateReceived(scope.row)"
|
||
style="cursor: pointer; flex: 1;">
|
||
{{ scope.row.isRebateReceived === 1 ? '后返到账' : '未到账' }}
|
||
</el-tag>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span style="min-width: 80px; font-size: 12px; color: #606266;">点过价保:</span>
|
||
<el-tag
|
||
:type="scope.row.isPriceProtected === 1 ? 'warning' : 'info'"
|
||
size="small"
|
||
:title="scope.row.isPriceProtected === 1 && scope.row.priceProtectedDate ? '价保日期:' + parseTime(scope.row.priceProtectedDate) : ''"
|
||
@click.native="togglePriceProtected(scope.row)"
|
||
style="cursor: pointer; flex: 1;">
|
||
{{ scope.row.isPriceProtected === 1 ? '点过价保' : '未点价保' }}
|
||
</el-tag>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span style="min-width: 80px; font-size: 12px; color: #606266;">开过专票:</span>
|
||
<el-tag
|
||
:type="scope.row.isInvoiceOpened === 1 ? 'success' : 'info'"
|
||
size="small"
|
||
:title="scope.row.isInvoiceOpened === 1 && scope.row.invoiceOpenedDate ? '开票日期:' + parseTime(scope.row.invoiceOpenedDate) : ''"
|
||
@click.native="toggleInvoiceOpened(scope.row)"
|
||
style="cursor: pointer; flex: 1;">
|
||
{{ scope.row.isInvoiceOpened === 1 ? '开过专票' : '未开专票' }}
|
||
</el-tag>
|
||
</div>
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span style="min-width: 80px; font-size: 12px; color: #606266;">晒过评价:</span>
|
||
<el-tag
|
||
:type="scope.row.isReviewPosted === 1 ? 'success' : 'info'"
|
||
size="small"
|
||
:title="scope.row.isReviewPosted === 1 && scope.row.reviewPostedDate ? '评价日期:' + parseTime(scope.row.reviewPostedDate) : ''"
|
||
@click.native="toggleReviewPosted(scope.row)"
|
||
style="cursor: pointer; flex: 1;">
|
||
{{ scope.row.isReviewPosted === 1 ? '晒过评价' : '未晒评价' }}
|
||
</el-tag>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<!-- 时间信息列 -->
|
||
<el-table-column label="创建时间" prop="createTime" width="160" sortable="custom">
|
||
<template slot-scope="scope">{{ parseTime(scope.row.createTime) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="完成时间" prop="finishTime" width="160">
|
||
<template slot-scope="scope">{{ parseTime(scope.row.finishTime) || '-' }}</template>
|
||
</el-table-column>
|
||
|
||
<!-- 其他信息列(可折叠) -->
|
||
<el-table-column label="备注/状态" prop="status" min-width="120" show-overflow-tooltip/>
|
||
<el-table-column label="订单状态" prop="orderStatus" width="100" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag
|
||
v-if="scope.row.orderStatus != null"
|
||
:type="getOrderStatusType(scope.row.orderStatus)"
|
||
size="small">
|
||
{{ getOrderStatusText(scope.row.orderStatus) }}
|
||
</el-tag>
|
||
<span v-else style="color: #999;">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<!-- 操作列(统一放在最右侧) -->
|
||
<el-table-column label="操作" fixed="right" :width="isMobile ? 60 : 280" align="center" class-name="action-column">
|
||
<template slot-scope="scope">
|
||
<!-- 移动端:悬浮操作按钮 -->
|
||
<div v-if="isMobile" class="mobile-action-wrapper">
|
||
<el-dropdown
|
||
trigger="click"
|
||
placement="left"
|
||
@command="handleActionCommand"
|
||
:popper-class="'mobile-action-dropdown'">
|
||
<el-button
|
||
type="primary"
|
||
size="mini"
|
||
icon="el-icon-more"
|
||
circle
|
||
class="mobile-action-btn">
|
||
</el-button>
|
||
<el-dropdown-menu slot="dropdown">
|
||
<el-dropdown-item
|
||
:command="{action: 'copyOrderId', row: scope.row}"
|
||
icon="el-icon-copy-document">
|
||
复制订单号
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
v-if="scope.row.thirdPartyOrderNo"
|
||
:command="{action: 'copyThirdParty', row: scope.row}"
|
||
icon="el-icon-copy-document">
|
||
复制第三方单号
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'copyAddress', row: scope.row}"
|
||
icon="el-icon-copy-document">
|
||
复制地址
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
v-if="scope.row.logisticsLink"
|
||
:command="{action: 'copyLogistics', row: scope.row}"
|
||
icon="el-icon-copy-document">
|
||
复制物流链接
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'copyReturn', row: scope.row}"
|
||
icon="el-icon-document-copy">
|
||
退货复制
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'copyExcel', row: scope.row}"
|
||
icon="el-icon-document-copy"
|
||
:class="{'is-copied': isExcelTextCopied(scope.row.id)}">
|
||
{{ isExcelTextCopied(scope.row.id) ? '✓ 录单格式(已复制)' : '录单格式' }}
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'copyRebate', row: scope.row}"
|
||
icon="el-icon-document-copy">
|
||
后返录表
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'fetchLogistics', row: scope.row}"
|
||
icon="el-icon-truck"
|
||
style="color: #67C23A;">
|
||
获取物流
|
||
</el-dropdown-item>
|
||
<el-dropdown-item divided>
|
||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 0 10px;">
|
||
<span>统计</span>
|
||
<el-switch
|
||
v-model="scope.row.isCountEnabled"
|
||
:active-value="1"
|
||
:inactive-value="0"
|
||
@change="handleCountEnabledChange(scope.row)"
|
||
size="mini"
|
||
style="margin-left: 10px;">
|
||
</el-switch>
|
||
</div>
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
:command="{action: 'delete', row: scope.row}"
|
||
icon="el-icon-delete"
|
||
divided
|
||
style="color: #f56c6c;">
|
||
删除
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</el-dropdown>
|
||
</div>
|
||
|
||
<!-- 桌面端:所有按钮直接显示 -->
|
||
<div v-else class="desktop-action-buttons-wrapper">
|
||
<div class="action-row-all">
|
||
<el-button
|
||
type="text"
|
||
size="mini"
|
||
icon="el-icon-copy-document"
|
||
@click="copyToClipboard(scope.row.orderId)"
|
||
title="复制订单号">
|
||
订单号
|
||
</el-button>
|
||
<el-button
|
||
v-if="scope.row.thirdPartyOrderNo"
|
||
type="text"
|
||
size="mini"
|
||
icon="el-icon-copy-document"
|
||
@click="copyToClipboard(scope.row.thirdPartyOrderNo)"
|
||
title="复制第三方单号">
|
||
第三方
|
||
</el-button>
|
||
<el-button
|
||
type="text"
|
||
size="mini"
|
||
icon="el-icon-copy-document"
|
||
@click="copyToClipboard(scope.row.address)"
|
||
title="复制地址">
|
||
地址
|
||
</el-button>
|
||
<el-button
|
||
v-if="scope.row.logisticsLink"
|
||
type="text"
|
||
size="mini"
|
||
icon="el-icon-copy-document"
|
||
@click="copyToClipboard(scope.row.logisticsLink)"
|
||
title="复制物流链接">
|
||
物流
|
||
</el-button>
|
||
<el-button
|
||
type="text"
|
||
size="mini"
|
||
@click="copyReturnInfo(scope.row)"
|
||
title="复制退货信息">
|
||
退货复制
|
||
</el-button>
|
||
<el-button
|
||
type="text"
|
||
size="mini"
|
||
icon="el-icon-document-copy"
|
||
@click="copySingleOrderExcelText(scope.row)"
|
||
:style="isExcelTextCopied(scope.row.id) ? 'color: #67C23A;' : ''"
|
||
:title="isExcelTextCopied(scope.row.id) ? '已复制录单格式(Excel可粘贴)' : '复制录单格式(Excel可粘贴)'">
|
||
录单格式
|
||
</el-button>
|
||
<el-button
|
||
type="text"
|
||
size="mini"
|
||
icon="el-icon-document-copy"
|
||
@click="copyRebateRecordText(scope.row)"
|
||
title="复制后返录表格式(Excel可粘贴)">
|
||
后返录表
|
||
</el-button>
|
||
<el-button
|
||
type="text"
|
||
size="mini"
|
||
style="color: #67C23A;"
|
||
@click="handleFetchLogistics(scope.row)"
|
||
:title="scope.row.distributionMark">
|
||
获取物流
|
||
</el-button>
|
||
<el-switch
|
||
v-model="scope.row.isCountEnabled"
|
||
:active-value="1"
|
||
:inactive-value="0"
|
||
@change="handleCountEnabledChange(scope.row)"
|
||
active-text="统计"
|
||
inactive-text=""
|
||
size="mini"
|
||
style="margin-left: 4px;">
|
||
</el-switch>
|
||
<el-button
|
||
type="text"
|
||
size="mini"
|
||
style="color: #f56c6c;"
|
||
@click="handleDelete(scope.row)"
|
||
title="删除订单">
|
||
删除
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</template>
|
||
|
||
<!-- 分页区域 -->
|
||
<template #pagination>
|
||
<pagination
|
||
v-show="total > 0"
|
||
:total="total"
|
||
:page.sync="queryParams.pageNum"
|
||
:limit.sync="queryParams.pageSize"
|
||
@pagination="getList"
|
||
class="list-pagination"
|
||
/>
|
||
</template>
|
||
</list-layout>
|
||
|
||
<!-- 获取物流信息对话框 -->
|
||
<el-dialog
|
||
title="获取物流信息(调试)"
|
||
:visible.sync="fetchLogisticsDialogVisible"
|
||
width="800px"
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div v-loading="fetchLogisticsLoading">
|
||
<el-alert
|
||
v-if="fetchLogisticsResult"
|
||
:title="fetchLogisticsResult.success ? '获取成功' : '获取失败'"
|
||
:type="fetchLogisticsResult.success ? 'success' : 'error'"
|
||
:closable="false"
|
||
style="margin-bottom: 20px;"
|
||
/>
|
||
<el-form label-width="120px" v-if="fetchLogisticsResult">
|
||
<el-form-item label="错误信息" v-if="fetchLogisticsResult.error">
|
||
<el-alert
|
||
:title="fetchLogisticsResult.error"
|
||
type="error"
|
||
:closable="false"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="订单ID" v-if="fetchLogisticsResult.orderId">
|
||
<span>{{ fetchLogisticsResult.orderId }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="订单号" v-if="fetchLogisticsResult.orderNo">
|
||
<span>{{ fetchLogisticsResult.orderNo }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="分销标识" v-if="fetchLogisticsResult.distributionMark">
|
||
<span>{{ fetchLogisticsResult.distributionMark }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="物流链接" v-if="fetchLogisticsResult.logisticsLink">
|
||
<el-input
|
||
:value="fetchLogisticsResult.logisticsLink"
|
||
readonly
|
||
type="textarea"
|
||
:rows="2"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="请求URL" v-if="fetchLogisticsResult.requestUrl">
|
||
<el-input
|
||
:value="fetchLogisticsResult.requestUrl"
|
||
readonly
|
||
type="textarea"
|
||
:rows="2"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="返回数据(原始)" v-if="fetchLogisticsResult.responseRaw || fetchLogisticsResult.responseData">
|
||
<el-input
|
||
:value="fetchLogisticsResult.responseRaw || JSON.stringify(fetchLogisticsResult.responseData, null, 2)"
|
||
readonly
|
||
type="textarea"
|
||
:rows="10"
|
||
style="font-family: monospace; font-size: 12px;"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="返回数据(解析后)" v-if="fetchLogisticsResult.responseData">
|
||
<el-input
|
||
:value="JSON.stringify(fetchLogisticsResult.responseData, null, 2)"
|
||
readonly
|
||
type="textarea"
|
||
:rows="10"
|
||
style="font-family: monospace; font-size: 12px;"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div v-else style="text-align: center; padding: 40px;">
|
||
<span style="color: #999;">正在获取物流信息...</span>
|
||
</div>
|
||
</div>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="fetchLogisticsDialogVisible = false">关闭</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="copyFetchLogisticsResult"
|
||
v-if="fetchLogisticsResult"
|
||
>
|
||
复制结果
|
||
</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 同步物流对话框 -->
|
||
<el-dialog
|
||
title="同步物流到腾讯文档"
|
||
:visible.sync="syncLogisticsDialogVisible"
|
||
width="600px"
|
||
:close-on-click-modal="false"
|
||
>
|
||
<el-form :model="syncLogisticsForm" label-width="140px">
|
||
<el-form-item label="文件ID" required>
|
||
<el-input
|
||
v-model="syncLogisticsForm.fileId"
|
||
placeholder="从腾讯文档URL中获取,例如:Dxxxxxxxxxxxxx"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="工作表ID" required>
|
||
<el-input
|
||
v-model="syncLogisticsForm.sheetId"
|
||
placeholder="从腾讯文档URL中获取,例如:BB08J2"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="表头行号">
|
||
<el-input-number
|
||
v-model="syncLogisticsForm.headerRow"
|
||
:min="1"
|
||
style="width: 100%"
|
||
/>
|
||
<div style="font-size: 12px; color: #999; margin-top: 4px;">
|
||
表头所在行号(默认第1行)
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="单号列索引(可选)">
|
||
<el-input-number
|
||
v-model="syncLogisticsForm.orderNoColumn"
|
||
:min="0"
|
||
style="width: 100%"
|
||
placeholder="不填写则自动识别"
|
||
/>
|
||
<div style="font-size: 12px; color: #999; margin-top: 4px;">
|
||
单号列的索引(从0开始),不填写则自动识别
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="物流链接列索引(可选)">
|
||
<el-input-number
|
||
v-model="syncLogisticsForm.logisticsLinkColumn"
|
||
:min="0"
|
||
style="width: 100%"
|
||
placeholder="不填写则自动识别"
|
||
/>
|
||
<div style="font-size: 12px; color: #999; margin-top: 4px;">
|
||
物流链接列的索引(从0开始),不填写则自动识别
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="说明">
|
||
<div style="font-size: 12px; color: #666; line-height: 1.6;">
|
||
<p>1. 点击"同步物流"时,如果没有token会自动打开授权页面</p>
|
||
<p>2. 完成授权后,点击"开始同步"即可自动刷新token并同步数据</p>
|
||
<p>3. 文件ID和工作表ID可以从腾讯文档URL中获取</p>
|
||
<p>4. 系统会自动从上次处理的最大行数-100开始读取,避免重复处理历史数据</p>
|
||
<p>5. 配置会自动保存,下次使用时无需重新填写</p>
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="syncLogisticsDialogVisible = false">取消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:loading="syncLogisticsLoading"
|
||
@click="handleSyncLogisticsSubmit"
|
||
>
|
||
开始同步
|
||
</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- H-TF订单自动写入配置 -->
|
||
<tencent-doc-auto-write-config v-model="showAutoWriteConfig" @config-updated="handleAutoConfigUpdated" />
|
||
|
||
<!-- 腾讯文档推送监控 -->
|
||
<tencent-doc-push-monitor v-model="showPushMonitor" />
|
||
|
||
<!-- 分销标识接收人配置 -->
|
||
<distribution-mark-touser-config v-model="showTouserConfig" @config-updated="handleTouserConfigUpdated" />
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { listJDOrders, updateJDOrder, delJDOrder, fetchLogisticsManually, batchMarkRebateReceived, generateExcelText } from '@/api/system/jdorder'
|
||
import { fillLogisticsByOrderNo, getTokenStatus, getTencentDocAuthUrl, testUserInfo, getAutoWriteConfig, reverseSyncThirdPartyOrderNo } from '@/api/jarvis/tendoc'
|
||
import { mapGetters } from 'vuex'
|
||
import ListLayout from '@/components/ListLayout'
|
||
import MobileSearchForm from '@/components/MobileSearchForm'
|
||
import MobileButtonGroup from '@/components/MobileButtonGroup'
|
||
import TencentDocAutoWriteConfig from './components/TencentDocAutoWriteConfig'
|
||
import TencentDocPushMonitor from './components/TencentDocPushMonitor'
|
||
import DistributionMarkTouserConfig from './components/DistributionMarkTouserConfig'
|
||
|
||
export default {
|
||
name: 'JDOrderList',
|
||
components: {
|
||
ListLayout,
|
||
TencentDocAutoWriteConfig,
|
||
TencentDocPushMonitor,
|
||
DistributionMarkTouserConfig,
|
||
MobileSearchForm,
|
||
MobileButtonGroup
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
list: [],
|
||
total: 0,
|
||
dateRange: [],
|
||
queryParams: {
|
||
pageNum: 1,
|
||
pageSize: 50,
|
||
remark: undefined,
|
||
orderSearch: undefined,
|
||
distributionMark: undefined,
|
||
modelNumber: undefined,
|
||
link: undefined,
|
||
buyer: undefined,
|
||
address: undefined,
|
||
status: undefined,
|
||
beginTime: null,
|
||
endTime: null,
|
||
orderBy: 'create_time',
|
||
isAsc: 'desc',
|
||
hasFinishTime: false,
|
||
isRefunded: undefined,
|
||
isRefundReceived: undefined,
|
||
isRebateReceived: undefined,
|
||
isPriceProtected: undefined,
|
||
isInvoiceOpened: undefined,
|
||
isReviewPosted: undefined
|
||
},
|
||
// 同步物流对话框
|
||
syncLogisticsDialogVisible: false,
|
||
syncLogisticsLoading: false,
|
||
syncLogisticsForm: {
|
||
fileId: '',
|
||
sheetId: '',
|
||
headerRow: 1,
|
||
orderNoColumn: null,
|
||
logisticsLinkColumn: null
|
||
},
|
||
currentOrder: null,
|
||
tokenValid: false,
|
||
tokenStatusChecked: false,
|
||
// H-TF订单自动写入配置
|
||
showAutoWriteConfig: false,
|
||
showPushMonitor: false,
|
||
showTouserConfig: false,
|
||
// 批量同步loading状态
|
||
batchSyncLoading: false,
|
||
// 批量标记后返到账loading状态
|
||
batchMarkLoading: false,
|
||
// 反向同步第三方单号loading状态
|
||
reverseSyncLoading: false,
|
||
// 获取物流信息对话框
|
||
fetchLogisticsDialogVisible: false,
|
||
fetchLogisticsLoading: false,
|
||
fetchLogisticsResult: null,
|
||
// 复制录单格式loading状态
|
||
copyExcelTextLoading: false,
|
||
// 已复制录单格式的订单ID集合(页面级缓存,刷新后消失)
|
||
copiedExcelTextOrderIds: new Set(),
|
||
// 选中的行数据
|
||
selectedRows: []
|
||
}
|
||
},
|
||
computed: {
|
||
...mapGetters(['device']),
|
||
isMobile() {
|
||
// 只在移动端返回true,桌面端严格返回false
|
||
if (this.device === 'mobile') {
|
||
return true
|
||
}
|
||
// 如果device不是mobile,检查窗口宽度(用于响应式)
|
||
if (typeof window !== 'undefined' && window.innerWidth < 768) {
|
||
return true
|
||
}
|
||
return false
|
||
},
|
||
actionButtons() {
|
||
// 移动端只保留推送监控按钮
|
||
if (this.isMobile) {
|
||
return [
|
||
{ key: 'monitor', label: '推送监控', type: 'success', icon: 'el-icon-monitor', handler: () => { this.showPushMonitor = true } }
|
||
]
|
||
}
|
||
// 桌面端显示所有按钮
|
||
return [
|
||
{ key: 'export', label: '导出', type: 'success', icon: 'el-icon-download', handler: () => this.handleExport() },
|
||
{ key: 'config', label: '腾峰文档配置', type: 'warning', icon: 'el-icon-setting', handler: () => { this.showAutoWriteConfig = true } },
|
||
{ key: 'monitor', label: '推送监控', type: 'success', icon: 'el-icon-monitor', handler: () => { this.showPushMonitor = true } },
|
||
{ key: 'touser', label: '接收人配置', type: 'warning', icon: 'el-icon-user', handler: () => { this.showTouserConfig = true } },
|
||
{ key: 'sync', label: '一键发货到腾峰', type: 'warning', icon: 'el-icon-refresh-right', handler: () => this.handleBatchSyncLogistics(), loading: this.batchSyncLoading },
|
||
{ key: 'mark', label: '批量标记后返到账', type: 'warning', icon: 'el-icon-check', handler: () => this.handleBatchMarkRebateReceived(), loading: this.batchMarkLoading },
|
||
{ key: 'reverse', label: '反向同步第三方单号', type: 'warning', icon: 'el-icon-sort', handler: () => this.handleReverseSyncThirdPartyOrderNo(), loading: this.reverseSyncLoading }
|
||
]
|
||
}
|
||
},
|
||
created() {
|
||
// 设置默认日期为今天
|
||
this.setDefaultDateRange()
|
||
this.getListWithFallback()
|
||
|
||
// 监听腾讯文档授权回调消息
|
||
this.handleOAuthMessage = (event) => {
|
||
if (event.data && event.data.type === 'tendoc_oauth_callback') {
|
||
if (event.data.success) {
|
||
this.$message.success(event.data.message || '授权成功!')
|
||
// 刷新token状态
|
||
this.checkTokenStatus()
|
||
} else {
|
||
this.$message.error(event.data.message || '授权失败')
|
||
}
|
||
}
|
||
}
|
||
window.addEventListener('message', this.handleOAuthMessage)
|
||
},
|
||
beforeDestroy() {
|
||
// 移除消息监听器
|
||
if (this.handleOAuthMessage) {
|
||
window.removeEventListener('message', this.handleOAuthMessage)
|
||
}
|
||
},
|
||
methods: {
|
||
/** 处理移动端操作菜单命令 */
|
||
handleActionCommand({ action, row }) {
|
||
switch (action) {
|
||
case 'copyOrderId':
|
||
this.copyToClipboard(row.orderId)
|
||
break
|
||
case 'copyThirdParty':
|
||
this.copyToClipboard(row.thirdPartyOrderNo)
|
||
break
|
||
case 'copyAddress':
|
||
this.copyToClipboard(row.address)
|
||
break
|
||
case 'copyLogistics':
|
||
this.copyToClipboard(row.logisticsLink)
|
||
break
|
||
case 'copyReturn':
|
||
this.copyReturnInfo(row)
|
||
break
|
||
case 'copyExcel':
|
||
this.copySingleOrderExcelText(row)
|
||
break
|
||
case 'copyRebate':
|
||
this.copyRebateRecordText(row)
|
||
break
|
||
case 'fetchLogistics':
|
||
this.handleFetchLogistics(row)
|
||
break
|
||
case 'delete':
|
||
this.handleDelete(row)
|
||
break
|
||
}
|
||
},
|
||
/** 快速搜索 */
|
||
handleQuickSearch(keyword) {
|
||
if (!keyword) {
|
||
this.handleQuery()
|
||
return
|
||
}
|
||
// 快速搜索:在订单号、备注、下单人等字段中搜索
|
||
this.queryParams.orderSearch = keyword
|
||
this.queryParams.remark = keyword
|
||
this.queryParams.buyer = keyword
|
||
this.handleQuery()
|
||
},
|
||
/** 设置默认日期范围为今天 */
|
||
setDefaultDateRange() {
|
||
const today = new Date()
|
||
const todayStr = this.formatDate(today)
|
||
this.dateRange = [todayStr, todayStr]
|
||
this.queryParams.beginTime = todayStr
|
||
this.queryParams.endTime = todayStr
|
||
},
|
||
/** 格式化日期为 yyyy-MM-dd 格式 */
|
||
formatDate(date) {
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
return `${year}-${month}-${day}`
|
||
},
|
||
getList() {
|
||
this.loading = true
|
||
listJDOrders(this.queryParams).then(res => {
|
||
const list = (res.rows || res.data || [])
|
||
// 为退款相关字段设置默认值
|
||
this.list = list.map(item => ({
|
||
...item,
|
||
isRefunded: item.isRefunded != null ? item.isRefunded : 0,
|
||
isRefundReceived: item.isRefundReceived != null ? item.isRefundReceived : 0,
|
||
isRebateReceived: item.isRebateReceived != null ? item.isRebateReceived : 0,
|
||
isPriceProtected: item.isPriceProtected != null ? item.isPriceProtected : 0,
|
||
isInvoiceOpened: item.isInvoiceOpened != null ? item.isInvoiceOpened : 0,
|
||
isReviewPosted: item.isReviewPosted != null ? item.isReviewPosted : 0
|
||
}))
|
||
this.total = res.total || 0
|
||
this.loading = false
|
||
}).catch(() => { this.loading = false })
|
||
},
|
||
/** 智能查询列表,如果今天数据为空则查询昨天 */
|
||
async getListWithFallback() {
|
||
this.loading = true
|
||
|
||
try {
|
||
const res = await listJDOrders(this.queryParams)
|
||
let list = (res.rows || res.data || [])
|
||
// 为退款相关字段设置默认值
|
||
list = list.map(item => ({
|
||
...item,
|
||
isRefunded: item.isRefunded != null ? item.isRefunded : 0,
|
||
isRefundReceived: item.isRefundReceived != null ? item.isRefundReceived : 0,
|
||
isRebateReceived: item.isRebateReceived != null ? item.isRebateReceived : 0,
|
||
isPriceProtected: item.isPriceProtected != null ? item.isPriceProtected : 0,
|
||
isInvoiceOpened: item.isInvoiceOpened != null ? item.isInvoiceOpened : 0,
|
||
isReviewPosted: item.isReviewPosted != null ? item.isReviewPosted : 0
|
||
}))
|
||
this.list = list
|
||
this.total = res.total || 0
|
||
|
||
// 如果今天的数据为空,且是默认查询(没有手动选择日期),则尝试查询昨天
|
||
if (this.list.length === 0 && this.isDefaultQuery()) {
|
||
this.$message.info('今天暂无慢单数据,正在查询昨天的数据...')
|
||
|
||
// 获取昨天的日期
|
||
const yesterday = new Date()
|
||
yesterday.setDate(yesterday.getDate() - 1)
|
||
const yesterdayStr = this.formatDate(yesterday)
|
||
|
||
// 更新查询参数为昨天
|
||
this.queryParams.beginTime = yesterdayStr
|
||
this.queryParams.endTime = yesterdayStr
|
||
this.dateRange = [yesterdayStr, yesterdayStr]
|
||
|
||
// 查询昨天的数据
|
||
const yesterdayRes = await listJDOrders(this.queryParams)
|
||
let yesterdayList = (yesterdayRes.rows || yesterdayRes.data || [])
|
||
// 为退款相关字段设置默认值
|
||
yesterdayList = yesterdayList.map(item => ({
|
||
...item,
|
||
isRefunded: item.isRefunded != null ? item.isRefunded : 0,
|
||
isRefundReceived: item.isRefundReceived != null ? item.isRefundReceived : 0,
|
||
isRebateReceived: item.isRebateReceived != null ? item.isRebateReceived : 0,
|
||
isPriceProtected: item.isPriceProtected != null ? item.isPriceProtected : 0,
|
||
isInvoiceOpened: item.isInvoiceOpened != null ? item.isInvoiceOpened : 0,
|
||
isReviewPosted: item.isReviewPosted != null ? item.isReviewPosted : 0
|
||
}))
|
||
this.list = yesterdayList
|
||
this.total = yesterdayRes.total || 0
|
||
|
||
if (this.list.length > 0) {
|
||
this.$message.success(`已查询到昨天(${yesterdayStr})的慢单数据`)
|
||
} else {
|
||
this.$message.warning('昨天也没有慢单数据')
|
||
}
|
||
}
|
||
|
||
this.loading = false
|
||
} catch (error) {
|
||
this.loading = false
|
||
this.$message.error('查询失败,请稍后重试')
|
||
}
|
||
},
|
||
/** 判断是否为默认查询(今天的数据) */
|
||
isDefaultQuery() {
|
||
const today = new Date()
|
||
const todayStr = this.formatDate(today)
|
||
return this.queryParams.beginTime === todayStr && this.queryParams.endTime === todayStr
|
||
},
|
||
handleQuery() {
|
||
this.prepareQueryParams()
|
||
this.queryParams.pageNum = 1
|
||
this.getList()
|
||
},
|
||
resetQuery() {
|
||
this.queryParams = {
|
||
pageNum: 1,
|
||
pageSize: 50,
|
||
remark: undefined,
|
||
orderSearch: undefined,
|
||
distributionMark: undefined,
|
||
modelNumber: undefined,
|
||
link: undefined,
|
||
buyer: undefined,
|
||
address: undefined,
|
||
status: undefined,
|
||
beginTime: null,
|
||
endTime: null,
|
||
orderBy: 'create_time',
|
||
isAsc: 'desc',
|
||
hasFinishTime: false,
|
||
isRefunded: undefined,
|
||
isRefundReceived: undefined,
|
||
isRebateReceived: undefined,
|
||
isPriceProtected: undefined,
|
||
isInvoiceOpened: undefined,
|
||
isReviewPosted: undefined
|
||
}
|
||
// 重置排序为默认降序
|
||
this.queryParams.orderBy = 'create_time'
|
||
this.queryParams.isAsc = 'desc'
|
||
// 重置后重新设置默认日期为今天
|
||
this.setDefaultDateRange()
|
||
this.getListWithFallback()
|
||
},
|
||
/** 日期范围变化处理 */
|
||
handleDateRangeChange(dates) {
|
||
if (dates && dates.length === 2) {
|
||
this.queryParams.beginTime = dates[0]
|
||
this.queryParams.endTime = dates[1]
|
||
} else {
|
||
this.queryParams.beginTime = null
|
||
this.queryParams.endTime = null
|
||
}
|
||
},
|
||
/** 表格排序变化处理 */
|
||
handleSortChange(sortInfo) {
|
||
if (sortInfo && sortInfo.prop === 'createTime') {
|
||
if (sortInfo.order === 'ascending') {
|
||
this.queryParams.orderBy = 'create_time'
|
||
this.queryParams.isAsc = 'asc'
|
||
} else if (sortInfo.order === 'descending') {
|
||
this.queryParams.orderBy = 'create_time'
|
||
this.queryParams.isAsc = 'desc'
|
||
} else {
|
||
// 取消排序,恢复默认
|
||
this.queryParams.orderBy = 'create_time'
|
||
this.queryParams.isAsc = 'desc'
|
||
}
|
||
// 重新查询数据
|
||
this.queryParams.pageNum = 1
|
||
this.getList()
|
||
}
|
||
},
|
||
toYuan(n) {
|
||
if (n == null || n === '') return ''
|
||
const num = Number(n)
|
||
if (Number.isNaN(num)) return n
|
||
return num.toFixed(2)
|
||
},
|
||
/** 导出按钮操作 */
|
||
handleExport() {
|
||
this.prepareQueryParams()
|
||
this.download('/system/jdorder/export', this.queryParams, `京东订单数据_${new Date().getTime()}.xlsx`)
|
||
},
|
||
/** 复制到剪贴板 */
|
||
copyToClipboard(text) {
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
// 使用现代 Clipboard API
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
this.$message.success('已复制到剪贴板')
|
||
}).catch(() => {
|
||
this.fallbackCopyTextToClipboard(text)
|
||
})
|
||
} else {
|
||
// 降级方案
|
||
this.fallbackCopyTextToClipboard(text)
|
||
}
|
||
},
|
||
/** 降级复制方案 */
|
||
fallbackCopyTextToClipboard(text) {
|
||
const textArea = document.createElement('textarea')
|
||
textArea.value = text
|
||
textArea.style.position = 'fixed'
|
||
textArea.style.left = '-999999px'
|
||
textArea.style.top = '-999999px'
|
||
document.body.appendChild(textArea)
|
||
textArea.focus()
|
||
textArea.select()
|
||
|
||
try {
|
||
const successful = document.execCommand('copy')
|
||
if (successful) {
|
||
this.$message.success('已复制到剪贴板')
|
||
} else {
|
||
this.$message.error('复制失败,请手动复制')
|
||
}
|
||
} catch (err) {
|
||
this.$message.error('复制失败,请手动复制')
|
||
}
|
||
|
||
document.body.removeChild(textArea)
|
||
},
|
||
/** 处理统计开关变化 */
|
||
handleCountEnabledChange(row) {
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的统计状态已更新为:${row.isCountEnabled ? '参与统计' : '不参与统计'}`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isCountEnabled = row.isCountEnabled ? 0 : 1
|
||
})
|
||
},
|
||
/** 切换退款状态(标签点击) */
|
||
toggleRefunded(row) {
|
||
const oldValue = row.isRefunded
|
||
row.isRefunded = row.isRefunded === 1 ? 0 : 1
|
||
// 如果设置为"是",自动设置当前日期
|
||
if (row.isRefunded === 1 && !row.refundDate) {
|
||
row.refundDate = new Date()
|
||
}
|
||
// 如果设置为"否",清空日期
|
||
if (row.isRefunded === 0) {
|
||
row.refundDate = null
|
||
}
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的退款状态已更新`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isRefunded = oldValue
|
||
if (row.isRefunded === 0) {
|
||
row.refundDate = null
|
||
}
|
||
})
|
||
},
|
||
/** 处理是否退款开关变化 */
|
||
handleRefundedChange(row) {
|
||
// 如果设置为"是",自动设置当前日期
|
||
if (row.isRefunded === 1 && !row.refundDate) {
|
||
row.refundDate = new Date()
|
||
}
|
||
// 如果设置为"否",清空日期
|
||
if (row.isRefunded === 0) {
|
||
row.refundDate = null
|
||
}
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的退款状态已更新`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isRefunded = row.isRefunded ? 0 : 1
|
||
if (row.isRefunded === 0) {
|
||
row.refundDate = null
|
||
}
|
||
})
|
||
},
|
||
/** 切换退款到账状态(标签点击) */
|
||
toggleRefundReceived(row) {
|
||
const oldValue = row.isRefundReceived
|
||
row.isRefundReceived = row.isRefundReceived === 1 ? 0 : 1
|
||
// 如果设置为"是",自动设置当前日期
|
||
if (row.isRefundReceived === 1 && !row.refundReceivedDate) {
|
||
row.refundReceivedDate = new Date()
|
||
}
|
||
// 如果设置为"否",清空日期
|
||
if (row.isRefundReceived === 0) {
|
||
row.refundReceivedDate = null
|
||
}
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的退款到账状态已更新`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isRefundReceived = oldValue
|
||
if (row.isRefundReceived === 0) {
|
||
row.refundReceivedDate = null
|
||
}
|
||
})
|
||
},
|
||
/** 处理是否退款到账开关变化 */
|
||
handleRefundReceivedChange(row) {
|
||
// 如果设置为"是",自动设置当前日期
|
||
if (row.isRefundReceived === 1 && !row.refundReceivedDate) {
|
||
row.refundReceivedDate = new Date()
|
||
}
|
||
// 如果设置为"否",清空日期
|
||
if (row.isRefundReceived === 0) {
|
||
row.refundReceivedDate = null
|
||
}
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的退款到账状态已更新`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isRefundReceived = row.isRefundReceived ? 0 : 1
|
||
if (row.isRefundReceived === 0) {
|
||
row.refundReceivedDate = null
|
||
}
|
||
})
|
||
},
|
||
/** 切换后返到账状态(标签点击) */
|
||
toggleRebateReceived(row) {
|
||
const oldValue = row.isRebateReceived
|
||
row.isRebateReceived = row.isRebateReceived === 1 ? 0 : 1
|
||
// 如果设置为"是",自动设置当前日期
|
||
if (row.isRebateReceived === 1 && !row.rebateReceivedDate) {
|
||
row.rebateReceivedDate = new Date()
|
||
}
|
||
// 如果设置为"否",清空日期
|
||
if (row.isRebateReceived === 0) {
|
||
row.rebateReceivedDate = null
|
||
}
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的后返到账状态已更新`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isRebateReceived = oldValue
|
||
if (row.isRebateReceived === 0) {
|
||
row.rebateReceivedDate = null
|
||
}
|
||
})
|
||
},
|
||
/** 处理后返到账开关变化 */
|
||
handleRebateReceivedChange(row) {
|
||
// 如果设置为"是",自动设置当前日期
|
||
if (row.isRebateReceived === 1 && !row.rebateReceivedDate) {
|
||
row.rebateReceivedDate = new Date()
|
||
}
|
||
// 如果设置为"否",清空日期
|
||
if (row.isRebateReceived === 0) {
|
||
row.rebateReceivedDate = null
|
||
}
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的后返到账状态已更新`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isRebateReceived = row.isRebateReceived ? 0 : 1
|
||
if (row.isRebateReceived === 0) {
|
||
row.rebateReceivedDate = null
|
||
}
|
||
})
|
||
},
|
||
/** 切换点过价保状态(标签点击) */
|
||
togglePriceProtected(row) {
|
||
const oldValue = row.isPriceProtected
|
||
row.isPriceProtected = row.isPriceProtected === 1 ? 0 : 1
|
||
// 如果设置为"是",自动设置当前日期
|
||
if (row.isPriceProtected === 1 && !row.priceProtectedDate) {
|
||
row.priceProtectedDate = new Date()
|
||
}
|
||
// 如果设置为"否",清空日期
|
||
if (row.isPriceProtected === 0) {
|
||
row.priceProtectedDate = null
|
||
}
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的点过价保状态已更新`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isPriceProtected = oldValue
|
||
if (row.isPriceProtected === 0) {
|
||
row.priceProtectedDate = null
|
||
}
|
||
})
|
||
},
|
||
/** 切换开过专票状态(标签点击) */
|
||
toggleInvoiceOpened(row) {
|
||
const oldValue = row.isInvoiceOpened
|
||
row.isInvoiceOpened = row.isInvoiceOpened === 1 ? 0 : 1
|
||
// 如果设置为"是",自动设置当前日期
|
||
if (row.isInvoiceOpened === 1 && !row.invoiceOpenedDate) {
|
||
row.invoiceOpenedDate = new Date()
|
||
}
|
||
// 如果设置为"否",清空日期
|
||
if (row.isInvoiceOpened === 0) {
|
||
row.invoiceOpenedDate = null
|
||
}
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的开过专票状态已更新`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isInvoiceOpened = oldValue
|
||
if (row.isInvoiceOpened === 0) {
|
||
row.invoiceOpenedDate = null
|
||
}
|
||
})
|
||
},
|
||
/** 切换晒过评价状态(标签点击) */
|
||
toggleReviewPosted(row) {
|
||
const oldValue = row.isReviewPosted
|
||
row.isReviewPosted = row.isReviewPosted === 1 ? 0 : 1
|
||
// 如果设置为"是",自动设置当前日期
|
||
if (row.isReviewPosted === 1 && !row.reviewPostedDate) {
|
||
row.reviewPostedDate = new Date()
|
||
}
|
||
// 如果设置为"否",清空日期
|
||
if (row.isReviewPosted === 0) {
|
||
row.reviewPostedDate = null
|
||
}
|
||
// 调用后端API更新数据库
|
||
updateJDOrder(row).then(() => {
|
||
this.$message.success(`订单 ${row.remark} 的晒过评价状态已更新`)
|
||
}).catch(() => {
|
||
this.$message.error('更新失败,请稍后重试')
|
||
// 恢复原状态
|
||
row.isReviewPosted = oldValue
|
||
if (row.isReviewPosted === 0) {
|
||
row.reviewPostedDate = null
|
||
}
|
||
})
|
||
},
|
||
/** 删除单条记录(需输入随机确认码) */
|
||
async handleDelete(row) {
|
||
const code = String(Math.floor(100000 + Math.random() * 900000))
|
||
try {
|
||
await this.$prompt(`请输入确认码以删除该订单:${code}`, '删除确认', {
|
||
confirmButtonText: '删除',
|
||
cancelButtonText: '取消',
|
||
inputPlaceholder: '请输入上方确认码',
|
||
inputValidator: (value) => {
|
||
if (value === code) return true
|
||
return '确认码不匹配,请重新输入'
|
||
},
|
||
inputErrorMessage: '确认码不匹配',
|
||
type: 'warning',
|
||
dangerouslyUseHTMLString: false
|
||
})
|
||
} catch (e) {
|
||
return
|
||
}
|
||
|
||
this.loading = true
|
||
try {
|
||
await delJDOrder(row.id)
|
||
this.$message.success('删除成功')
|
||
this.getList()
|
||
} catch (e) {
|
||
this.$message.error('删除失败,请稍后重试')
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
|
||
/** 批量同步物流到腾讯文档 */
|
||
async handleBatchSyncLogistics() {
|
||
try {
|
||
// 先检查配置是否完整
|
||
const configRes = await getAutoWriteConfig()
|
||
if (configRes.code !== 200 || !configRes.data || !configRes.data.isConfigured) {
|
||
this.$confirm('检测到尚未完成H-TF自动写入配置,是否现在配置?', '提示', {
|
||
type: 'warning'
|
||
}).then(() => {
|
||
this.showAutoWriteConfig = true
|
||
})
|
||
return
|
||
}
|
||
|
||
const config = configRes.data
|
||
|
||
// 构建确认消息
|
||
let confirmMsg = '批量同步将从腾讯文档中搜索订单并填充物流链接\n\n'
|
||
confirmMsg += `文档: ${config.fileId}\n`
|
||
confirmMsg += `工作表: ${config.sheetId}\n`
|
||
confirmMsg += `表头行: ${config.headerRow}\n`
|
||
confirmMsg += `数据起始行: ${config.startRow}\n`
|
||
|
||
if (config.currentProgress) {
|
||
confirmMsg += `\n表格行数: 第 ${config.currentProgress} 行(接口获取)\n`
|
||
confirmMsg += (config.progressHint || '') + '\n'
|
||
}
|
||
|
||
confirmMsg += `\n防重推送: ${config.skipPushedOrders ? '已启用(跳过已推送订单)' : '已禁用'}\n`
|
||
confirmMsg += '\n⚠️ 注意:\n'
|
||
confirmMsg += '- 系统会自动跳过已有物流链接的行\n'
|
||
confirmMsg += '- 系统会自动跳过已标记为"已推送"的订单\n'
|
||
confirmMsg += '- 所有操作都会记录到操作日志\n'
|
||
|
||
// 确认同步
|
||
await this.$confirm(confirmMsg, '批量同步物流确认', {
|
||
type: 'info',
|
||
confirmButtonText: '开始同步',
|
||
cancelButtonText: '取消'
|
||
})
|
||
|
||
this.batchSyncLoading = true
|
||
|
||
// 调用批量同步API
|
||
const res = await fillLogisticsByOrderNo({})
|
||
|
||
if (res.code === 200) {
|
||
const data = res.data || {}
|
||
this.$notify({
|
||
title: '批量同步完成',
|
||
message: `✓ 成功填充: ${data.filledCount || 0} 条\n⊙ 跳过: ${data.skippedCount || 0} 条\n✗ 错误: ${data.errorCount || 0} 条\n\n${data.message || ''}`,
|
||
type: 'success',
|
||
duration: 10000,
|
||
dangerouslyUseHTMLString: false
|
||
})
|
||
} else {
|
||
this.$message.error(res.msg || '同步失败')
|
||
}
|
||
} catch (e) {
|
||
if (e !== 'cancel') {
|
||
this.$message.error('操作失败:' + (e.message || '未知错误'))
|
||
console.error('批量同步失败', e)
|
||
}
|
||
} finally {
|
||
this.batchSyncLoading = false
|
||
}
|
||
},
|
||
|
||
/** 打开授权页面并等待授权完成 */
|
||
async openAuthAndWait() {
|
||
try {
|
||
// 获取授权URL
|
||
const authUrlRes = await getTencentDocAuthUrl()
|
||
if (authUrlRes.code !== 200 || !authUrlRes.data) {
|
||
this.$message.error('获取授权URL失败')
|
||
return
|
||
}
|
||
|
||
const authUrl = authUrlRes.data
|
||
|
||
// 打开授权页面
|
||
this.$message.info('正在打开授权页面,请完成授权后继续...')
|
||
const width = 600
|
||
const height = 700
|
||
const left = (window.screen.width - width) / 2
|
||
const top = (window.screen.height - height) / 2
|
||
|
||
const authWindow = window.open(
|
||
authUrl,
|
||
'腾讯文档授权',
|
||
`width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`
|
||
)
|
||
|
||
// 监听窗口关闭事件(备用方案,如果postMessage失效)
|
||
const checkWindowClosed = setInterval(() => {
|
||
if (authWindow.closed) {
|
||
clearInterval(checkWindowClosed)
|
||
// 等待1秒后检查token状态(作为备用验证)
|
||
setTimeout(async () => {
|
||
try {
|
||
const tokenStatusRes = await getTokenStatus()
|
||
if (tokenStatusRes.data && tokenStatusRes.data.hasToken) {
|
||
// 如果已经通过postMessage收到成功消息,这里不再重复提示
|
||
} else {
|
||
// 如果没有收到postMessage,可能是授权失败
|
||
this.$message.warning('授权未完成,请重新尝试')
|
||
}
|
||
} catch (e) {
|
||
// 静默处理,避免重复提示
|
||
}
|
||
}, 1000)
|
||
}
|
||
}, 1000)
|
||
|
||
} catch (e) {
|
||
this.$message.error('打开授权页面失败: ' + (e.message || '未知错误'))
|
||
console.error('打开授权页面失败', e)
|
||
}
|
||
},
|
||
|
||
/** 检查后端token状态 */
|
||
async checkTokenStatus() {
|
||
try {
|
||
const res = await getTokenStatus()
|
||
if (res.code === 200 && res.data) {
|
||
this.tokenValid = res.data.hasToken === true
|
||
} else {
|
||
this.tokenValid = false
|
||
}
|
||
} catch (e) {
|
||
console.error('检查token状态失败', e)
|
||
this.tokenValid = false
|
||
} finally {
|
||
this.tokenStatusChecked = true
|
||
}
|
||
},
|
||
|
||
/** 测试获取用户信息接口 */
|
||
async handleTestUserInfo() {
|
||
try {
|
||
this.$message.info('正在测试用户信息接口...')
|
||
const res = await testUserInfo()
|
||
if (res.code === 200) {
|
||
this.$message.success('测试成功!用户信息:' + JSON.stringify(res.data, null, 2))
|
||
// 显示详细信息
|
||
this.$alert(
|
||
'<pre style="text-align: left; max-height: 400px; overflow: auto;">' +
|
||
JSON.stringify(res.data, null, 2) +
|
||
'</pre>',
|
||
'用户信息测试结果',
|
||
{
|
||
dangerouslyUseHTMLString: true,
|
||
confirmButtonText: '确定',
|
||
type: 'success'
|
||
}
|
||
)
|
||
} else {
|
||
this.$message.error('测试失败:' + (res.msg || '未知错误'))
|
||
}
|
||
} catch (e) {
|
||
this.$message.error('测试失败:' + (e.message || '未知错误'))
|
||
console.error('测试用户信息失败', e)
|
||
}
|
||
},
|
||
|
||
/** 同步物流链接 */
|
||
async handleSyncLogisticsSubmit() {
|
||
try {
|
||
// 后端会自动从配置中读取 fileId、sheetId、startRow 等参数
|
||
// 前端只需传递空对象即可
|
||
const res = await fillLogisticsByOrderNo({})
|
||
|
||
if (res.code === 200) {
|
||
const data = res.data || {}
|
||
this.$message.success(
|
||
`同步成功!成功填充 ${data.filledCount || 0} 条,跳过 ${data.skippedCount || 0} 条,错误 ${data.errorCount || 0} 条`
|
||
)
|
||
} else {
|
||
this.$message.error(res.msg || '同步失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error('同步失败:' + (e.message || '未知错误'))
|
||
console.error('同步物流失败', e)
|
||
}
|
||
},
|
||
|
||
/** 手动获取物流信息 */
|
||
async handleFetchLogistics(row) {
|
||
|
||
|
||
// 检查物流链接
|
||
if (!row.logisticsLink || !row.logisticsLink.trim()) {
|
||
this.$message.warning('该订单暂无物流链接')
|
||
return
|
||
}
|
||
|
||
this.fetchLogisticsDialogVisible = true
|
||
this.fetchLogisticsLoading = true
|
||
this.fetchLogisticsResult = null
|
||
|
||
try {
|
||
const res = await fetchLogisticsManually({ orderId: row.id })
|
||
|
||
if (res.code === 200) {
|
||
this.fetchLogisticsResult = {
|
||
success: true,
|
||
...res.data
|
||
}
|
||
this.$message.success('获取物流信息成功,数据已记录到日志文件')
|
||
} else {
|
||
this.fetchLogisticsResult = {
|
||
success: false,
|
||
error: res.msg || '获取失败'
|
||
}
|
||
this.$message.error(res.msg || '获取物流信息失败')
|
||
}
|
||
} catch (e) {
|
||
this.fetchLogisticsResult = {
|
||
success: false,
|
||
error: e.message || '请求异常'
|
||
}
|
||
this.$message.error('获取物流信息失败: ' + (e.message || '未知错误'))
|
||
console.error('获取物流信息失败', e)
|
||
} finally {
|
||
this.fetchLogisticsLoading = false
|
||
}
|
||
},
|
||
|
||
/** 复制获取物流信息的结果 */
|
||
copyFetchLogisticsResult() {
|
||
if (!this.fetchLogisticsResult) return
|
||
|
||
const resultText = JSON.stringify(this.fetchLogisticsResult, null, 2)
|
||
this.copyToClipboard(resultText)
|
||
},
|
||
|
||
/** H-TF订单自动写入配置更新后的回调 */
|
||
handleAutoConfigUpdated() {
|
||
this.$message.success('H-TF订单自动写入配置已更新')
|
||
},
|
||
|
||
/** 分销标识接收人配置更新后的回调 */
|
||
handleTouserConfigUpdated() {
|
||
this.$message.success('分销标识接收人配置已更新')
|
||
},
|
||
|
||
/** 批量标记后返到账(赔付金额>0的订单) */
|
||
async handleBatchMarkRebateReceived() {
|
||
try {
|
||
// 确认操作
|
||
await this.$confirm(
|
||
'此操作将批量将赔付金额大于0的订单标记为后返到账。\n\n' +
|
||
'⚠️ 注意:此操作只应执行一次,用于处理历史数据。\n\n' +
|
||
'是否继续?',
|
||
'批量标记后返到账',
|
||
{
|
||
type: 'warning',
|
||
confirmButtonText: '确定执行',
|
||
cancelButtonText: '取消'
|
||
}
|
||
)
|
||
|
||
this.batchMarkLoading = true
|
||
const res = await batchMarkRebateReceived()
|
||
|
||
if (res && (res.code === 200 || res.msg === '操作成功' || res.msg === '查询成功')) {
|
||
this.$message.success(res.msg || '批量标记后返到账完成,请查看控制台日志')
|
||
// 刷新列表
|
||
this.getList()
|
||
} else {
|
||
this.$message.error(res && res.msg ? res.msg : '批量标记失败')
|
||
}
|
||
} catch (e) {
|
||
if (e !== 'cancel') {
|
||
this.$message.error('批量标记失败: ' + (e.message || '未知错误'))
|
||
console.error('批量标记后返到账失败', e)
|
||
}
|
||
} finally {
|
||
this.batchMarkLoading = false
|
||
}
|
||
},
|
||
|
||
/** 获取订单状态文本 */
|
||
getOrderStatusText(status) {
|
||
const statusMap = {
|
||
'-100': '无变化',
|
||
'-1': '未知',
|
||
2: '无效-拆单',
|
||
3: '无效-取消',
|
||
4: '无效-京东帮帮主订单',
|
||
5: '无效-账号异常',
|
||
6: '无效-赠品类目不返佣',
|
||
7: '无效-校园订单',
|
||
8: '无效-企业订单',
|
||
9: '无效-团购订单',
|
||
11: '无效-乡村推广员下单',
|
||
13: '违规订单-其他',
|
||
14: '无效-来源与备案网址不符',
|
||
15: '待付款',
|
||
16: '已付款',
|
||
17: '已完成',
|
||
19: '无效-佣金比例为0',
|
||
20: '无效-此复购订单对应的首购订单无效',
|
||
21: '无效-云店订单',
|
||
22: '无效-PLUS会员佣金比例为0',
|
||
23: '无效-支付有礼',
|
||
24: '已付定金',
|
||
25: '违规订单-流量劫持',
|
||
26: '违规订单-流量异常',
|
||
27: '违规订单-违反京东平台规则',
|
||
28: '违规订单-多笔交易异常',
|
||
29: '无效-跨屏跨店',
|
||
30: '无效-累计件数超出类目上限',
|
||
31: '无效-黑名单sku',
|
||
33: '超市卡充值订单',
|
||
34: '无效-推卡订单无效'
|
||
}
|
||
return statusMap[status] || `状态${status}`
|
||
},
|
||
|
||
/** 获取订单状态标签类型 */
|
||
getOrderStatusType(status) {
|
||
// 取消状态(优先级最高)
|
||
if (status === 3) return 'danger' // 无效-取消(红色,优先级高于违规)
|
||
// 正常状态
|
||
if (status === 16) return 'success' // 已付款
|
||
if (status === 17) return 'success' // 已完成
|
||
if (status === 15) return 'warning' // 待付款
|
||
if (status === 24) return 'warning' // 已付定金
|
||
// 违规状态
|
||
if ([13, 25, 26, 27, 28].includes(status)) return 'warning' // 违规订单(黄色,优先级低于取消)
|
||
// 无效状态
|
||
if ([2, 4, 5, 6, 7, 8, 9, 11, 14, 19, 20, 21, 22, 23, 29, 30, 31, 34].includes(status)) return 'info' // 无效订单
|
||
if (status === 33) return 'success' // 超市卡充值订单
|
||
return 'info' // 其他状态
|
||
},
|
||
|
||
/** 准备查询参数:仅去除空格,保留日期等所有搜索条件 */
|
||
prepareQueryParams() {
|
||
const textKeys = ['remark', 'orderSearch', 'distributionMark', 'modelNumber', 'link', 'buyer', 'address', 'status']
|
||
|
||
textKeys.forEach(key => {
|
||
const value = this.queryParams[key]
|
||
if (typeof value === 'string') {
|
||
const trimmed = value.trim()
|
||
this.queryParams[key] = trimmed || undefined
|
||
}
|
||
})
|
||
},
|
||
|
||
/** 退货复制 */
|
||
copyReturnInfo(row) {
|
||
const toLine = (v) => (v == null ? '' : String(v).trim())
|
||
const parts = []
|
||
// 既有顺序保持不变
|
||
parts.push(toLine(row.remark)) // 内部单号
|
||
parts.push(toLine(row.orderId)) // 订单号
|
||
parts.push(toLine(row.modelNumber)) // 型号
|
||
parts.push(toLine(row.thirdPartyOrderNo)) // 第三方单号
|
||
parts.push(toLine(row.distributionMark)) // 分销标记
|
||
parts.push(toLine(row.address)) // 地址
|
||
// 新增:金额与物流
|
||
parts.push(row.paymentAmount != null && row.paymentAmount !== '' ? this.toYuan(row.paymentAmount) : '') // 付款金额
|
||
parts.push(row.rebateAmount != null && row.rebateAmount !== '' ? this.toYuan(row.rebateAmount) : '') // 后返金额
|
||
parts.push(toLine(row.logisticsLink)) // 物流链接
|
||
// 末尾:下单人
|
||
parts.push(toLine(row.buyer)) // 下单人
|
||
|
||
const text = parts.join('\n')
|
||
this.copyToClipboard(text)
|
||
},
|
||
|
||
/** 复制单条订单的录单格式文本到剪贴板 */
|
||
copySingleOrderExcelText(row) {
|
||
try {
|
||
// 日期(格式:yyyy/MM/dd)
|
||
let dateStr = ''
|
||
if (row.orderTime) {
|
||
const date = new Date(row.orderTime)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
dateStr = `${year}/${month}/${day}`
|
||
}
|
||
|
||
// 多多单号(第三方单号,如果没有则使用内部单号)
|
||
const duoduoOrderNo = (row.thirdPartyOrderNo && row.thirdPartyOrderNo.trim())
|
||
? row.thirdPartyOrderNo : (row.remark || '')
|
||
|
||
// 型号
|
||
const modelNumber = row.modelNumber || ''
|
||
|
||
// 数量(固定为1)
|
||
const quantity = '1'
|
||
|
||
// 地址
|
||
const address = row.address || ''
|
||
|
||
// 姓名(从地址中提取,地址格式通常是"姓名 电话 详细地址")
|
||
let buyer = ''
|
||
if (address) {
|
||
// 提取地址中的第一个词作为姓名
|
||
const addressParts = address.trim().split(/\s+/)
|
||
if (addressParts.length > 0) {
|
||
buyer = addressParts[0]
|
||
}
|
||
}
|
||
|
||
// 售价(固定为0)
|
||
const sellingPriceStr = '0'
|
||
|
||
// 成本(售价 - 利润,售价是0)
|
||
const rebate = row.rebateAmount || 0
|
||
const costStr = '' // 售价是0,成本也设为空
|
||
|
||
// 利润(后返金额)
|
||
const profitStr = ''
|
||
|
||
// 京东单号
|
||
const orderId = row.orderId || ''
|
||
|
||
// 物流链接
|
||
const logisticsLink = row.logisticsLink || ''
|
||
|
||
// 下单付款
|
||
const paymentAmountStr = row.paymentAmount
|
||
? row.paymentAmount.toFixed(2) : ''
|
||
|
||
// 后返
|
||
const rebateAmountStr = row.rebateAmount
|
||
? row.rebateAmount.toFixed(2) : ''
|
||
const shopName = ''
|
||
|
||
|
||
// 按顺序拼接:日期、多多单号、型号、数量、姓名、地址、售价、成本、利润、京东单号、物流、下单付款、后返
|
||
const text = [
|
||
dateStr,
|
||
duoduoOrderNo,
|
||
shopName,
|
||
modelNumber,
|
||
quantity,
|
||
buyer,
|
||
address,
|
||
sellingPriceStr,
|
||
costStr,
|
||
profitStr,
|
||
orderId,
|
||
logisticsLink,
|
||
paymentAmountStr,
|
||
rebateAmountStr
|
||
].join('\t')
|
||
|
||
this.copyToClipboard(text)
|
||
// 记录已复制的订单ID
|
||
if (row.id) {
|
||
this.copiedExcelTextOrderIds.add(row.id)
|
||
}
|
||
this.$message.success('已复制到剪贴板,可以直接粘贴到Excel')
|
||
} catch (e) {
|
||
this.$message.error('复制失败:' + (e.message || '未知错误'))
|
||
console.error('复制单条订单录单格式失败', e)
|
||
}
|
||
},
|
||
|
||
/** 检查订单是否已复制录单格式 */
|
||
isExcelTextCopied(orderId) {
|
||
return orderId && this.copiedExcelTextOrderIds.has(orderId)
|
||
},
|
||
|
||
/** 复制后返录表格式文本到剪贴板 */
|
||
copyRebateRecordText(row) {
|
||
try {
|
||
// 前5列:空(发过运营、需要重发运营、已经重发、需要二次重发运营、二次重发)
|
||
const emptyCols = []
|
||
|
||
// 单号:orderId
|
||
const orderId = row.orderId || ''
|
||
|
||
// 型号:modelNumber
|
||
const modelNumber = row.modelNumber || ''
|
||
|
||
// 返现金额(团长):空
|
||
const leaderRebateAmount = ''
|
||
|
||
// 晒单金额(主图没标):空
|
||
const reviewRebateAmount = ''
|
||
|
||
// 总共返现:rebateAmount(整数格式)
|
||
const totalRebateAmount = row.rebateAmount
|
||
? Math.round(row.rebateAmount).toString() : ''
|
||
|
||
// 确认收货日期:finishTime,格式yyyy/MM/dd
|
||
let finishDateStr = ''
|
||
if (row.finishTime) {
|
||
const finishDate = new Date(row.finishTime)
|
||
const year = finishDate.getFullYear()
|
||
const month = String(finishDate.getMonth() + 1).padStart(2, '0')
|
||
const day = String(finishDate.getDate()).padStart(2, '0')
|
||
finishDateStr = `${year}/${month}/${day}`
|
||
}
|
||
|
||
// 认领人:buyer
|
||
const buyer = row.buyer || ''
|
||
|
||
// 下单日期:orderTime,格式yyyyMMdd
|
||
let orderDateStr = ''
|
||
if (row.orderTime) {
|
||
const orderDate = new Date(row.orderTime)
|
||
const year = orderDate.getFullYear()
|
||
const month = String(orderDate.getMonth() + 1).padStart(2, '0')
|
||
const day = String(orderDate.getDate()).padStart(2, '0')
|
||
orderDateStr = `${year}${month}${day}`
|
||
}
|
||
|
||
// 按顺序拼接:发过运营、需要重发运营、已经重发、需要二次重发运营、二次重发、单号、型号、返现金额(团长)、晒单金额(主图没标)、总共返现、确认收货日期、认领人、下单日期
|
||
const text = [
|
||
...emptyCols,
|
||
orderId,
|
||
modelNumber,
|
||
leaderRebateAmount,
|
||
reviewRebateAmount,
|
||
totalRebateAmount,
|
||
finishDateStr,
|
||
buyer,
|
||
orderDateStr
|
||
].join('\t')
|
||
|
||
this.copyToClipboard(text)
|
||
this.$message.success('已复制后返录表格式到剪贴板,可以直接粘贴到Excel')
|
||
} catch (e) {
|
||
this.$message.error('复制失败:' + (e.message || '未知错误'))
|
||
console.error('复制后返录表格式失败', e)
|
||
}
|
||
},
|
||
|
||
/** 复制录单格式文本到剪贴板(批量) */
|
||
async handleCopyExcelText() {
|
||
try {
|
||
this.prepareQueryParams()
|
||
this.copyExcelTextLoading = true
|
||
|
||
// 调用后端接口生成Excel格式文本
|
||
const res = await generateExcelText(this.queryParams)
|
||
|
||
if (res.code === 200 && res.data) {
|
||
// 复制到剪贴板
|
||
const text = res.data
|
||
if (text === '暂无订单数据') {
|
||
this.$message.warning('暂无订单数据,请调整查询条件')
|
||
return
|
||
}
|
||
|
||
this.copyToClipboard(text)
|
||
this.$message.success('已复制到剪贴板,可以直接粘贴到Excel')
|
||
} else {
|
||
this.$message.error(res.msg || '生成失败')
|
||
}
|
||
} catch (e) {
|
||
this.$message.error('复制失败:' + (e.message || '未知错误'))
|
||
console.error('复制录单格式失败', e)
|
||
} finally {
|
||
this.copyExcelTextLoading = false
|
||
}
|
||
},
|
||
|
||
/** 反向同步第三方单号 */
|
||
async handleReverseSyncThirdPartyOrderNo() {
|
||
try {
|
||
// 先检查配置是否完整
|
||
const configRes = await getAutoWriteConfig()
|
||
if (configRes.code !== 200 || !configRes.data || !configRes.data.isConfigured) {
|
||
this.$confirm('检测到尚未完成H-TF自动写入配置,是否现在配置?', '提示', {
|
||
type: 'warning'
|
||
}).then(() => {
|
||
this.showAutoWriteConfig = true
|
||
})
|
||
return
|
||
}
|
||
|
||
const config = configRes.data
|
||
|
||
// 构建确认消息
|
||
let confirmMsg = '反向同步将从腾讯文档第850行开始读取物流单号列\n'
|
||
confirmMsg += '通过物流链接匹配本地订单,将腾讯文档的单号列值写入到订单的第三方单号字段\n\n'
|
||
confirmMsg += `文档: ${config.fileId}\n`
|
||
confirmMsg += `工作表: ${config.sheetId}\n`
|
||
confirmMsg += `起始行: 850\n`
|
||
confirmMsg += `结束行: 2500\n`
|
||
confirmMsg += `处理行数: 1651行\n`
|
||
confirmMsg += '\n⚠️ 注意:\n'
|
||
confirmMsg += '- 只更新订单的第三方单号字段,不会清除任何数据\n'
|
||
confirmMsg += '- 跳过物流链接为空的行\n'
|
||
confirmMsg += '- 跳过单号为空的行\n'
|
||
confirmMsg += '- 如果订单已有第三方单号且与文档中的不同,将跳过\n'
|
||
confirmMsg += '- 所有操作都会记录到操作日志\n'
|
||
|
||
// 确认同步
|
||
await this.$confirm(confirmMsg, '反向同步第三方单号确认', {
|
||
type: 'info',
|
||
confirmButtonText: '开始同步',
|
||
cancelButtonText: '取消'
|
||
})
|
||
|
||
this.reverseSyncLoading = true
|
||
|
||
// 调用反向同步API
|
||
const res = await reverseSyncThirdPartyOrderNo({
|
||
startRow: 850,
|
||
endRow: 2500 // 从850行到2500行
|
||
})
|
||
|
||
if (res.code === 200) {
|
||
const data = res.data || {}
|
||
this.$notify({
|
||
title: '反向同步完成',
|
||
message: `✓ 成功: ${data.successCount || 0} 条\n⊙ 跳过: ${data.skippedCount || 0} 条\n✗ 错误: ${data.errorCount || 0} 条\n\n${data.message || ''}`,
|
||
type: 'success',
|
||
duration: 10000,
|
||
dangerouslyUseHTMLString: false
|
||
})
|
||
// 刷新列表
|
||
this.getList()
|
||
} else {
|
||
this.$message.error(res.msg || '同步失败')
|
||
}
|
||
} catch (e) {
|
||
if (e !== 'cancel') {
|
||
this.$message.error('操作失败:' + (e.message || '未知错误'))
|
||
console.error('反向同步失败', e)
|
||
}
|
||
} finally {
|
||
this.reverseSyncLoading = false
|
||
}
|
||
},
|
||
|
||
/** 处理表格选择变化 */
|
||
handleSelectionChange(selection) {
|
||
this.selectedRows = selection
|
||
},
|
||
|
||
/** 批量复制录单格式 */
|
||
handleBatchCopyExcelText() {
|
||
if (!this.selectedRows || this.selectedRows.length === 0) {
|
||
this.$message.warning('请先选择要复制的订单')
|
||
return
|
||
}
|
||
|
||
try {
|
||
const lines = []
|
||
|
||
// 遍历选中的每一行,生成录单格式文本
|
||
this.selectedRows.forEach(row => {
|
||
// 日期(格式:yyyy/MM/dd)
|
||
let dateStr = ''
|
||
if (row.orderTime) {
|
||
const date = new Date(row.orderTime)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
dateStr = `${year}/${month}/${day}`
|
||
}
|
||
|
||
// 多多单号(第三方单号,如果没有则使用内部单号)
|
||
const duoduoOrderNo = (row.thirdPartyOrderNo && row.thirdPartyOrderNo.trim())
|
||
? row.thirdPartyOrderNo : (row.remark || '')
|
||
|
||
// 型号
|
||
const modelNumber = row.modelNumber || ''
|
||
|
||
// 数量(固定为1)
|
||
const quantity = '1'
|
||
|
||
// 地址
|
||
const address = row.address || ''
|
||
|
||
// 姓名(从地址中提取,地址格式通常是"姓名 电话 详细地址")
|
||
let buyer = ''
|
||
if (address) {
|
||
// 提取地址中的第一个词作为姓名
|
||
const addressParts = address.trim().split(/\s+/)
|
||
if (addressParts.length > 0) {
|
||
buyer = addressParts[0]
|
||
}
|
||
}
|
||
|
||
// 售价(固定为0)
|
||
const sellingPriceStr = '0'
|
||
|
||
// 成本(售价 - 利润,售价是0)
|
||
const costStr = '' // 售价是0,成本也设为空
|
||
|
||
// 利润(后返金额)
|
||
const profitStr = ''
|
||
|
||
// 京东单号
|
||
const orderId = row.orderId || ''
|
||
|
||
// 物流链接
|
||
const logisticsLink = row.logisticsLink || ''
|
||
|
||
// 下单付款
|
||
const paymentAmountStr = row.paymentAmount
|
||
? row.paymentAmount.toFixed(2) : ''
|
||
|
||
// 后返
|
||
const rebateAmountStr = row.rebateAmount
|
||
? row.rebateAmount.toFixed(2) : ''
|
||
const shopName = ''
|
||
|
||
// 按顺序拼接:日期、多多单号、型号、数量、姓名、地址、售价、成本、利润、京东单号、物流、下单付款、后返
|
||
const text = [
|
||
dateStr,
|
||
duoduoOrderNo,
|
||
shopName,
|
||
modelNumber,
|
||
quantity,
|
||
buyer,
|
||
address,
|
||
sellingPriceStr,
|
||
costStr,
|
||
profitStr,
|
||
orderId,
|
||
logisticsLink,
|
||
paymentAmountStr,
|
||
rebateAmountStr
|
||
].join('\t')
|
||
|
||
lines.push(text)
|
||
})
|
||
|
||
// 将所有行用换行符连接
|
||
const finalText = lines.join('\n')
|
||
|
||
this.copyToClipboard(finalText)
|
||
|
||
// 记录已复制的订单ID
|
||
this.selectedRows.forEach(row => {
|
||
if (row.id) {
|
||
this.copiedExcelTextOrderIds.add(row.id)
|
||
}
|
||
})
|
||
|
||
this.$message.success(`已复制 ${this.selectedRows.length} 条订单的录单格式到剪贴板,可以直接粘贴到Excel`)
|
||
} catch (e) {
|
||
this.$message.error('批量复制失败:' + (e.message || '未知错误'))
|
||
console.error('批量复制录单格式失败', e)
|
||
}
|
||
},
|
||
|
||
/** 批量复制后返录表 */
|
||
handleBatchCopyRebateText() {
|
||
if (!this.selectedRows || this.selectedRows.length === 0) {
|
||
this.$message.warning('请先选择要复制的订单')
|
||
return
|
||
}
|
||
|
||
try {
|
||
const lines = []
|
||
|
||
// 遍历选中的每一行,生成后返录表格式文本
|
||
this.selectedRows.forEach(row => {
|
||
// 前5列:空(发过运营、需要重发运营、已经重发、需要二次重发运营、二次重发)
|
||
const emptyCols = ['']
|
||
|
||
// 单号:orderId
|
||
const orderId = row.orderId || ''
|
||
|
||
// 型号:modelNumber
|
||
const modelNumber = row.modelNumber || ''
|
||
|
||
// 返现金额(团长):空
|
||
const leaderRebateAmount = ''
|
||
|
||
// 晒单金额(主图没标):空
|
||
const reviewRebateAmount = ''
|
||
|
||
// 总共返现:rebateAmount(整数格式)
|
||
const totalRebateAmount = row.rebateAmount
|
||
? Math.round(row.rebateAmount).toString() : ''
|
||
|
||
// 确认收货日期:finishTime,格式yyyy/MM/dd
|
||
let finishDateStr = ''
|
||
if (row.finishTime) {
|
||
const finishDate = new Date(row.finishTime)
|
||
const year = finishDate.getFullYear()
|
||
const month = String(finishDate.getMonth() + 1).padStart(2, '0')
|
||
const day = String(finishDate.getDate()).padStart(2, '0')
|
||
finishDateStr = `${year}/${month}/${day}`
|
||
}
|
||
|
||
// 认领人:buyer
|
||
const buyer = row.buyer || ''
|
||
|
||
// 下单日期:orderTime,格式yyyyMMdd
|
||
let orderDateStr = ''
|
||
if (row.orderTime) {
|
||
const orderDate = new Date(row.orderTime)
|
||
const year = orderDate.getFullYear()
|
||
const month = String(orderDate.getMonth() + 1).padStart(2, '0')
|
||
const day = String(orderDate.getDate()).padStart(2, '0')
|
||
orderDateStr = `${year}${month}${day}`
|
||
}
|
||
|
||
// 按顺序拼接:发过运营、需要重发运营、已经重发、需要二次重发运营、二次重发、单号、型号、返现金额(团长)、晒单金额(主图没标)、总共返现、确认收货日期、认领人、下单日期
|
||
const text = [
|
||
orderId,
|
||
modelNumber,
|
||
leaderRebateAmount,
|
||
reviewRebateAmount,
|
||
totalRebateAmount,
|
||
finishDateStr,
|
||
buyer,
|
||
orderDateStr
|
||
].join('\t')
|
||
|
||
lines.push(text)
|
||
})
|
||
|
||
// 将所有行用换行符连接
|
||
const finalText = lines.join('\n')
|
||
|
||
this.copyToClipboard(finalText)
|
||
this.$message.success(`已复制 ${this.selectedRows.length} 条订单的后返录表格式到剪贴板,可以直接粘贴到Excel`)
|
||
} catch (e) {
|
||
this.$message.error('批量复制失败:' + (e.message || '未知错误'))
|
||
console.error('批量复制后返录表格式失败', e)
|
||
}
|
||
},
|
||
|
||
/** 批量复制四川商贸录表(格式:日期 型号 数量 地址 价格 备注 是否安排 物流) */
|
||
handleBatchCopySichuanCommerceText() {
|
||
if (!this.selectedRows || this.selectedRows.length === 0) {
|
||
this.$message.warning('请先选择要复制的订单')
|
||
return
|
||
}
|
||
|
||
try {
|
||
const header = ['日期', '型号', '数量', '地址', '价格', '备注', '是否安排', '物流'].join('\t')
|
||
const lines = [header]
|
||
|
||
this.selectedRows.forEach(row => {
|
||
// 日期(格式:yyyy/MM/dd)
|
||
let dateStr = ''
|
||
if (row.orderTime) {
|
||
const date = new Date(row.orderTime)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
dateStr = `${year}/${month}/${day}`
|
||
}
|
||
|
||
const modelNumber = row.modelNumber || ''
|
||
const quantity = (row.productCount != null && row.productCount !== '') ? String(row.productCount) : '1'
|
||
const address = row.address || ''
|
||
const priceStr = row.paymentAmount != null ? row.paymentAmount.toFixed(2) : ''
|
||
const remark = row.remark || ''
|
||
const arranged = '' // 是否安排,留空由用户填写
|
||
const logistics = row.logisticsLink || ''
|
||
|
||
const text = [dateStr, modelNumber, quantity, address, priceStr, remark, arranged, logistics].join('\t')
|
||
lines.push(text)
|
||
})
|
||
|
||
const finalText = lines.join('\n')
|
||
this.copyToClipboard(finalText)
|
||
this.$message.success(`已复制 ${this.selectedRows.length} 条订单的四川商贸录表格式到剪贴板(含表头),可直接粘贴到Excel`)
|
||
} catch (e) {
|
||
this.$message.error('批量复制失败:' + (e.message || '未知错误'))
|
||
console.error('批量复制四川商贸录表失败', e)
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 优化表格滚动条 */
|
||
.order-table ::v-deep .el-table__body-wrapper {
|
||
scrollbar-width: thick; /* Firefox */
|
||
scrollbar-color: #c1c1c1 #f1f1f1; /* Firefox */
|
||
}
|
||
|
||
.order-table ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
|
||
width: 12px;
|
||
height: 12px;
|
||
}
|
||
|
||
.order-table ::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
|
||
background: #f1f1f1;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.order-table ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
|
||
background: #c1c1c1;
|
||
border-radius: 6px;
|
||
border: 2px solid #f1f1f1;
|
||
}
|
||
|
||
.order-table ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
|
||
background: #a8a8a8;
|
||
}
|
||
|
||
/* 优化标签样式 */
|
||
.order-table ::v-deep .el-tag {
|
||
transition: all 0.3s;
|
||
user-select: none;
|
||
}
|
||
|
||
.order-table ::v-deep .el-tag:hover {
|
||
opacity: 0.8;
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
/* 优化操作按钮布局 */
|
||
.order-table ::v-deep .el-table__fixed-right {
|
||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* 优化表格单元格内容 */
|
||
.order-table ::v-deep .el-table td {
|
||
padding: 8px 0;
|
||
}
|
||
|
||
/* 优化地址列显示,防止过长换行 */
|
||
.order-table ::v-deep .el-table .address-cell {
|
||
max-width: 280px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.order-table ::v-deep .el-table th {
|
||
padding: 10px 0;
|
||
background-color: #fafafa;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 优化固定列 */
|
||
.order-table ::v-deep .el-table__fixed-left {
|
||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* 移动端卡片列表样式 */
|
||
.mobile-order-list {
|
||
padding: 12px;
|
||
background: #f5f5f5;
|
||
min-height: 300px;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
.mobile-order-card {
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
margin-bottom: 12px;
|
||
padding: 16px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.mobile-order-card:active {
|
||
transform: scale(0.98);
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
|
||
}
|
||
|
||
.mobile-order-card:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.mobile-order-card .card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.mobile-order-card .header-left {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.mobile-order-card .order-id {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.mobile-order-card .header-right {
|
||
flex-shrink: 0;
|
||
margin-left: 12px;
|
||
}
|
||
|
||
.mobile-order-card .card-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.mobile-order-card .field-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
min-height: 24px;
|
||
}
|
||
|
||
.mobile-order-card .field-label {
|
||
font-size: 13px;
|
||
color: #909399;
|
||
min-width: 80px;
|
||
flex-shrink: 0;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.mobile-order-card .field-value {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
color: #303133;
|
||
text-align: right;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.mobile-order-card .field-value.address-text {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.mobile-order-card .field-value.amount {
|
||
font-weight: 600;
|
||
color: #e6a23c;
|
||
}
|
||
|
||
.mobile-order-card .refund-status .field-value {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-end;
|
||
gap: 6px;
|
||
align-items: center;
|
||
}
|
||
|
||
.mobile-order-card .status-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
/* 移动端操作按钮样式 - 只在移动端生效 */
|
||
@media (max-width: 768px) {
|
||
.mobile-action-wrapper {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.mobile-action-btn {
|
||
width: 36px;
|
||
height: 36px;
|
||
padding: 0;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
/* 移动端下拉菜单样式 */
|
||
::v-deep .mobile-action-dropdown {
|
||
min-width: 180px;
|
||
max-width: 240px;
|
||
}
|
||
|
||
::v-deep .mobile-action-dropdown .el-dropdown-menu__item {
|
||
padding: 12px 20px;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
::v-deep .mobile-action-dropdown .el-dropdown-menu__item.is-copied {
|
||
color: #67C23A;
|
||
}
|
||
|
||
::v-deep .mobile-action-dropdown .el-dropdown-menu__item--divided {
|
||
border-top: 1px solid #e4e7ed;
|
||
margin-top: 4px;
|
||
padding-top: 8px;
|
||
}
|
||
|
||
/* 移动端隐藏操作列标题 */
|
||
.order-table ::v-deep .action-column .el-table__column-filter-trigger,
|
||
.order-table ::v-deep .action-column .cell {
|
||
padding: 8px 4px;
|
||
}
|
||
|
||
.order-table ::v-deep .el-table__fixed-right {
|
||
width: 60px !important;
|
||
}
|
||
|
||
.order-table ::v-deep .el-table__fixed-right-patch {
|
||
width: 60px !important;
|
||
}
|
||
|
||
/* 隐藏桌面端表格 */
|
||
.order-table {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
/* 桌面端操作按钮优化布局 - 所有按钮一行显示 */
|
||
.desktop-action-buttons-wrapper {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
width: 100%;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
flex-wrap: wrap;
|
||
gap: 2px;
|
||
}
|
||
|
||
.desktop-action-buttons-wrapper .action-row-all {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 2px;
|
||
justify-content: flex-start;
|
||
align-items: center;
|
||
width: 100%;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.desktop-action-buttons-wrapper .el-button {
|
||
padding: 3px 6px !important;
|
||
font-size: 12px !important;
|
||
white-space: nowrap !important;
|
||
margin: 0 !important;
|
||
height: 24px !important;
|
||
line-height: 1.2 !important;
|
||
min-height: 24px !important;
|
||
border: none !important;
|
||
flex-shrink: 0;
|
||
display: inline-flex !important;
|
||
align-items: center !important;
|
||
}
|
||
|
||
.desktop-action-buttons-wrapper .el-switch {
|
||
margin-left: 4px !important;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* 桌面端下拉菜单样式 */
|
||
::v-deep .desktop-action-dropdown {
|
||
min-width: 160px;
|
||
}
|
||
|
||
::v-deep .desktop-action-dropdown .el-dropdown-menu__item {
|
||
padding: 10px 20px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
::v-deep .desktop-action-dropdown .el-dropdown-menu__item.is-copied {
|
||
color: #67C23A;
|
||
}
|
||
|
||
/* 桌面端确保操作列正常显示 */
|
||
@media (min-width: 769px) {
|
||
.order-table ::v-deep .action-column {
|
||
width: 380px !important;
|
||
}
|
||
|
||
.order-table ::v-deep .el-table__fixed-right {
|
||
width: 380px !important;
|
||
}
|
||
|
||
.order-table ::v-deep .el-table__fixed-right-patch {
|
||
width: 380px !important;
|
||
}
|
||
|
||
/* 隐藏移动端按钮 */
|
||
.mobile-action-wrapper {
|
||
display: none !important;
|
||
}
|
||
|
||
/* 优化表格单元格高度和布局 */
|
||
.order-table ::v-deep .action-column .cell {
|
||
padding: 6px 4px !important;
|
||
line-height: 1.3 !important;
|
||
overflow: visible !important;
|
||
}
|
||
|
||
/* 确保操作按钮容器正确显示 */
|
||
.order-table ::v-deep .action-column .desktop-action-buttons-wrapper {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
}
|
||
|
||
/* 操作按钮区域 */
|
||
.action-buttons-section {
|
||
margin-top: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
/* 移动端和桌面端按钮组显示控制 */
|
||
@media (max-width: 768px) {
|
||
.desktop-only {
|
||
display: none !important;
|
||
}
|
||
|
||
.action-buttons-section.mobile-only {
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
@media (min-width: 769px) {
|
||
.mobile-only {
|
||
display: none !important;
|
||
}
|
||
|
||
.desktop-action-buttons.desktop-only {
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
.desktop-action-buttons {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
/* 移动端优化 */
|
||
@media (max-width: 768px) {
|
||
.action-buttons-section.mobile-only {
|
||
margin-top: 8px;
|
||
margin-bottom: 8px;
|
||
display: block;
|
||
}
|
||
|
||
.desktop-action-buttons.desktop-only {
|
||
display: none !important;
|
||
}
|
||
|
||
/* 移动端隐藏分页或简化显示 */
|
||
.list-pagination {
|
||
display: none; /* 移动端隐藏分页,因为很少用 */
|
||
}
|
||
}
|
||
|
||
/* 桌面端显示桌面按钮,隐藏移动端按钮 */
|
||
@media (min-width: 769px) {
|
||
.action-buttons-section.mobile-only {
|
||
display: none !important;
|
||
}
|
||
|
||
.desktop-action-buttons.desktop-only {
|
||
display: flex !important;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 12px;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
|