feat(integral-external): 新增寄卖外部免认证三件套页面
This commit is contained in:
@@ -39,3 +39,59 @@ export function getExternalIntegralLog(data) {
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 寄卖订单 列表(免认证)
|
||||
*/
|
||||
export function getExternalWaOrderList(params) {
|
||||
return requestNoAuth({
|
||||
url: 'external/integral/wa-order/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 寄卖订单 详情(免认证)
|
||||
*/
|
||||
export function getExternalWaOrderInfo(id) {
|
||||
return requestNoAuth({
|
||||
url: 'external/integral/wa-order/info',
|
||||
method: 'get',
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 团队每日对账报表(免认证)
|
||||
*/
|
||||
export function getExternalTeamDailyReport(params) {
|
||||
return requestNoAuth({
|
||||
url: 'external/integral/team-report/daily',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 团队每日对账报表 - Excel 导出(免认证)
|
||||
*/
|
||||
export function exportExternalTeamDailyReport(params) {
|
||||
return requestNoAuth({
|
||||
url: 'external/integral/team-report/daily/export',
|
||||
method: 'get',
|
||||
params,
|
||||
responseType: 'blob',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 今日抢单用户列表(免认证)
|
||||
*/
|
||||
export function getExternalGrabUserList(params) {
|
||||
return requestNoAuth({
|
||||
url: 'external/integral/grab-user/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,24 @@ const integralExternalRouter = {
|
||||
name: 'IntegralExternalUserDetail',
|
||||
meta: { title: '用户积分明细' },
|
||||
},
|
||||
{
|
||||
path: 'wa-order',
|
||||
component: () => import('@/views/integral-external/wa-order/index'),
|
||||
name: 'IntegralExternalWaOrder',
|
||||
meta: { title: '寄卖订单' },
|
||||
},
|
||||
{
|
||||
path: 'team-report',
|
||||
component: () => import('@/views/integral-external/team-report/index'),
|
||||
name: 'IntegralExternalTeamReport',
|
||||
meta: { title: '团队日报' },
|
||||
},
|
||||
{
|
||||
path: 'grab-user',
|
||||
component: () => import('@/views/integral-external/grab-user/index'),
|
||||
name: 'IntegralExternalGrabUser',
|
||||
meta: { title: '今日抢单用户' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -28,6 +28,11 @@ service.interceptors.request.use(
|
||||
// 响应拦截器 — 不拦截 401 跳转
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
// Blob / arraybuffer 等二进制响应直接透传,不做业务码拆包
|
||||
const responseType = response.config && response.config.responseType;
|
||||
if (responseType === 'blob' || responseType === 'arraybuffer') {
|
||||
return response.data;
|
||||
}
|
||||
const res = response.data;
|
||||
if (res.code !== 0 && res.code !== 200) {
|
||||
Message({
|
||||
|
||||
@@ -121,6 +121,27 @@ export default {
|
||||
url: '/integral-external/user/integral-detail',
|
||||
alwaysShow: true,
|
||||
},
|
||||
{
|
||||
bgColor: '#F56C6C',
|
||||
icon: 'icondingdanguanli',
|
||||
title: '寄卖订单',
|
||||
url: '/integral-external/wa-order',
|
||||
alwaysShow: true,
|
||||
},
|
||||
{
|
||||
bgColor: '#67C23A',
|
||||
icon: 'iconfenxiaoguanli',
|
||||
title: '团队日报',
|
||||
url: '/integral-external/team-report',
|
||||
alwaysShow: true,
|
||||
},
|
||||
{
|
||||
bgColor: '#E6A23C',
|
||||
icon: 'iconhuiyuanguanli',
|
||||
title: '今日抢单用户',
|
||||
url: '/integral-external/grab-user',
|
||||
alwaysShow: true,
|
||||
},
|
||||
],
|
||||
statisticData: [
|
||||
{ title: '待发货订单', num: 0, path: '/order/index', perms: ['admin:order:list'] },
|
||||
|
||||
297
backend-adminend/src/views/integral-external/grab-user/index.vue
Normal file
297
backend-adminend/src/views/integral-external/grab-user/index.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<div class="divBox relative">
|
||||
<!-- 顶部搜索区 -->
|
||||
<el-card class="box-card">
|
||||
<div class="container">
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
:model="tableFrom"
|
||||
inline
|
||||
size="small"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="用户ID:">
|
||||
<el-input
|
||||
v-model="tableFrom.uid"
|
||||
placeholder="请输入用户ID"
|
||||
class="selWidth"
|
||||
clearable
|
||||
@keyup.enter.native="seachList"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系方式:">
|
||||
<el-input
|
||||
v-model="tableFrom.mobile"
|
||||
placeholder="请输入手机号 / 账号"
|
||||
class="selWidth"
|
||||
clearable
|
||||
@keyup.enter.native="seachList"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="上级ID:">
|
||||
<el-input
|
||||
v-model="tableFrom.pid"
|
||||
placeholder="请输入上级ID"
|
||||
class="selWidth"
|
||||
clearable
|
||||
@keyup.enter.native="seachList"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="seachList">查询</el-button>
|
||||
<el-button icon="el-icon-refresh-left" @click="resetHandler">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 主表 -->
|
||||
<el-card class="box-card mt10">
|
||||
<div class="header-actions">
|
||||
<el-button size="mini" icon="el-icon-refresh" circle @click="getList" />
|
||||
<el-button size="mini" icon="el-icon-upload2" circle @click="exportCsv" />
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="listLoading"
|
||||
:data="tableData.data"
|
||||
size="mini"
|
||||
class="table"
|
||||
highlight-current-row
|
||||
:header-cell-style="{ fontWeight: 'bold' }"
|
||||
>
|
||||
<el-table-column prop="id" label="用户ID" min-width="80" />
|
||||
<el-table-column prop="username" label="账号" min-width="130">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.username || scope.row.mobile || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="nickname" label="昵称" min-width="100" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.nickname || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="昨日卖/今日买" min-width="120" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.prevSellCnt || 0 }}/{{ scope.row.todayBuyCnt || 0 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合同" min-width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-link
|
||||
v-if="scope.row.contract"
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="openContract(scope.row.contract)"
|
||||
>查看</el-link>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mobile" label="联系方式" min-width="130" />
|
||||
<el-table-column label="上级ID" min-width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.pid || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最高可抢单数" min-width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.maxOrder ? scope.row.maxOrder : '未设置' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="todayBuyAmount" label="今日购买总金额" min-width="130" align="right" />
|
||||
<el-table-column prop="todaySellAmount" label="今日卖出总金额" min-width="130" align="right" />
|
||||
<el-table-column label="用户等级" min-width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: levelColor(scope.row.level) }">
|
||||
{{ scope.row.levelName || '普通用户' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="money" label="余额" min-width="100" align="right" />
|
||||
<el-table-column prop="coupon" label="优惠券" min-width="100" align="right" />
|
||||
<el-table-column prop="selfBonus" label="个人奖金" min-width="100" align="right" />
|
||||
<el-table-column prop="shareBonus" label="推广奖金" min-width="100" align="right" />
|
||||
<el-table-column label="状态" min-width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'" size="mini">
|
||||
{{ scope.row.statusStr || (scope.row.status === 1 ? '正常' : '禁用') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updatedAt" label="更新时间" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="操作" min-width="160" fixed="right" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="onEditPid(scope.row)">修改上级</el-button>
|
||||
<el-button type="text" size="small" @click="onContractRenew(scope.row)">合同重签</el-button>
|
||||
<el-button type="text" size="small" @click="onEdit(scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="block mt20">
|
||||
<el-pagination
|
||||
:page-sizes="[15, 30, 45, 60]"
|
||||
:page-size="tableFrom.limit"
|
||||
:current-page="tableFrom.page"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="tableData.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 写操作提示对话框(外部页面默认不直接修改资金类字段) -->
|
||||
<el-dialog title="提示" :visible.sync="writeTipVisible" width="380px" append-to-body>
|
||||
<span>{{ writeTipText }}</span>
|
||||
<span slot="footer">
|
||||
<el-button type="primary" size="small" @click="writeTipVisible = false">知道了</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getExternalGrabUserList } from '@/api/integralExternal';
|
||||
|
||||
export default {
|
||||
name: 'IntegralExternalGrabUser',
|
||||
data() {
|
||||
return {
|
||||
listLoading: false,
|
||||
tableData: { data: [], total: 0 },
|
||||
tableFrom: {
|
||||
uid: '',
|
||||
mobile: '',
|
||||
pid: '',
|
||||
page: 1,
|
||||
limit: 15,
|
||||
},
|
||||
writeTipVisible: false,
|
||||
writeTipText: '',
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true;
|
||||
const params = { ...this.tableFrom };
|
||||
// 空字段不发送
|
||||
Object.keys(params).forEach((k) => {
|
||||
if (params[k] === '' || params[k] === null) delete params[k];
|
||||
});
|
||||
getExternalGrabUserList(params)
|
||||
.then((res) => {
|
||||
this.tableData.data = res.list || [];
|
||||
this.tableData.total = res.total || 0;
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.listLoading = false;
|
||||
});
|
||||
},
|
||||
seachList() {
|
||||
this.tableFrom.page = 1;
|
||||
this.getList();
|
||||
},
|
||||
resetHandler() {
|
||||
this.tableFrom = { uid: '', mobile: '', pid: '', page: 1, limit: 15 };
|
||||
this.getList();
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.tableFrom.limit = val;
|
||||
this.getList();
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.tableFrom.page = val;
|
||||
this.getList();
|
||||
},
|
||||
openContract(url) {
|
||||
if (!url) return;
|
||||
window.open(url, '_blank');
|
||||
},
|
||||
levelColor(level) {
|
||||
switch (level) {
|
||||
case 2:
|
||||
return '#E6A23C';
|
||||
case 3:
|
||||
return '#67C23A';
|
||||
default:
|
||||
return '#409EFF';
|
||||
}
|
||||
},
|
||||
onEditPid() {
|
||||
this.writeTipText = '请在管理后台「用户管理」中修改上级;本页面不支持写操作。';
|
||||
this.writeTipVisible = true;
|
||||
},
|
||||
onContractRenew() {
|
||||
this.writeTipText = '请在管理后台「用户管理」中进行合同重签;本页面不支持写操作。';
|
||||
this.writeTipVisible = true;
|
||||
},
|
||||
onEdit() {
|
||||
this.writeTipText = '请在管理后台「用户管理」中编辑;本页面不支持写操作。';
|
||||
this.writeTipVisible = true;
|
||||
},
|
||||
/**
|
||||
* 简易 CSV 导出(仅当前页)
|
||||
* 生产环境如需全量导出建议改为后端 /grab-user/list/export
|
||||
*/
|
||||
exportCsv() {
|
||||
const rows = this.tableData.data || [];
|
||||
if (rows.length === 0) {
|
||||
this.$message.info('当前页没有数据可导出');
|
||||
return;
|
||||
}
|
||||
const headers = [
|
||||
'用户ID', '账号', '昵称', '昨日卖/今日买', '联系方式', '上级ID',
|
||||
'最高可抢单数', '今日购买总金额', '今日卖出总金额', '用户等级',
|
||||
'余额', '优惠券', '个人奖金', '推广奖金', '状态', '更新时间',
|
||||
];
|
||||
const lines = [headers.join(',')];
|
||||
rows.forEach((r) => {
|
||||
const fields = [
|
||||
r.id,
|
||||
r.username || r.mobile || '',
|
||||
r.nickname || '',
|
||||
`${r.prevSellCnt || 0}/${r.todayBuyCnt || 0}`,
|
||||
r.mobile || '',
|
||||
r.pid || '',
|
||||
r.maxOrder || '',
|
||||
r.todayBuyAmount || '0',
|
||||
r.todaySellAmount || '0',
|
||||
r.levelName || '普通用户',
|
||||
r.money || '0',
|
||||
r.coupon || '0',
|
||||
r.selfBonus || '0',
|
||||
r.shareBonus || '0',
|
||||
r.statusStr || (r.status === 1 ? '正常' : '禁用'),
|
||||
r.updatedAt || '',
|
||||
].map((v) => `"${String(v).replace(/"/g, '""')}"`);
|
||||
lines.push(fields.join(','));
|
||||
});
|
||||
const blob = new Blob(['' + lines.join('\n')], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `今日抢单用户_${new Date().toISOString().slice(0, 10)}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mt10 { margin-top: 10px; }
|
||||
.mt20 { margin-top: 20px; }
|
||||
.selWidth { width: 220px; }
|
||||
.header-actions {
|
||||
text-align: right;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.block { text-align: right; }
|
||||
</style>
|
||||
@@ -0,0 +1,421 @@
|
||||
<template>
|
||||
<div class="divBox relative">
|
||||
<!-- 条件区 -->
|
||||
<el-card class="box-card">
|
||||
<el-form
|
||||
ref="searchForm"
|
||||
:model="tableFrom"
|
||||
inline
|
||||
size="small"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="团队长 ID:">
|
||||
<el-input
|
||||
v-model.number="tableFrom.leaderId"
|
||||
placeholder="留空=全部"
|
||||
class="leaderIdWidth"
|
||||
clearable
|
||||
@keyup.enter.native="seachList"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期 D:">
|
||||
<el-date-picker
|
||||
v-model="tableFrom.date"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="缺省=昨天"
|
||||
class="dateWidth"
|
||||
:picker-options="pickerOptions"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="含禁用成员:">
|
||||
<el-switch v-model="tableFrom.includeDisabled" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="seachList">查询</el-button>
|
||||
<el-button icon="el-icon-refresh-left" @click="resetHandler">重置</el-button>
|
||||
<el-button
|
||||
icon="el-icon-download"
|
||||
:disabled="!tableFrom.leaderId"
|
||||
@click="exportExcel"
|
||||
>导出 Excel</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-if="!tableFrom.leaderId" class="hint">
|
||||
Tips: 团队长 ID 留空时返回所有团队的日报,导出 Excel 仅支持单团队。
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 跨团队总计(仅在多团队模式下显示) -->
|
||||
<el-card v-if="report && report.teams && report.teams.length > 1" class="box-card mt10">
|
||||
<div class="report-header">
|
||||
<span class="title">
|
||||
全部团队总计 · {{ report.teamCount }} 个团队 · 共 {{ report.totalMemberCount }} 人 · {{ report.date }}
|
||||
</span>
|
||||
<span class="rate-info">
|
||||
服务费率 {{ report.serviceRate }} · E 积分率 {{ report.eScoreRate }}
|
||||
</span>
|
||||
</div>
|
||||
<el-row :gutter="16" class="summary-row">
|
||||
<el-col :span="6">
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">{{ report.previousDate }} 买单合计</div>
|
||||
<div class="summary-value">{{ formatNum(report.grandSummary.prevBuy) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">{{ report.date }} 卖单合计</div>
|
||||
<div class="summary-value">{{ formatNum(report.grandSummary.todaySell) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">{{ report.date }} 买单合计</div>
|
||||
<div class="summary-value">{{ formatNum(report.grandSummary.todayBuy) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">实际收付合计</div>
|
||||
<div
|
||||
class="summary-value"
|
||||
:class="actualClass(report.grandSummary.actual)"
|
||||
>{{ formatNum(report.grandSummary.actual) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 各团队子报表 -->
|
||||
<template v-if="report && report.teams && report.teams.length">
|
||||
<el-card
|
||||
v-for="team in report.teams"
|
||||
:key="team.leaderId"
|
||||
class="box-card mt10 team-card"
|
||||
>
|
||||
<div class="report-header">
|
||||
<span class="title">
|
||||
团队长 {{ team.leaderNickname || team.teamCode || '-' }}
|
||||
· {{ team.memberCount }} 人 · {{ team.date }}
|
||||
</span>
|
||||
<el-button
|
||||
v-if="!tableFrom.leaderId"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-download"
|
||||
@click="exportTeamExcel(team)"
|
||||
>导出该团队</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 团队级 4 卡片摘要(仅在单团队模式 / 想看每个团队也可以看;保留显示) -->
|
||||
<el-row v-if="report.teams.length === 1" :gutter="16" class="summary-row">
|
||||
<el-col :span="6">
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">{{ team.previousDate }} 买单合计</div>
|
||||
<div class="summary-value">{{ formatNum(team.summary.prevBuy) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">{{ team.date }} 卖单合计</div>
|
||||
<div class="summary-value">{{ formatNum(team.summary.todaySell) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">{{ team.date }} 买单合计</div>
|
||||
<div class="summary-value">{{ formatNum(team.summary.todayBuy) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">实际收付合计</div>
|
||||
<div
|
||||
class="summary-value"
|
||||
:class="actualClass(team.summary.actual)"
|
||||
>{{ formatNum(team.summary.actual) }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
:data="team.rows"
|
||||
size="mini"
|
||||
class="table"
|
||||
border
|
||||
highlight-current-row
|
||||
:header-cell-style="headerCellStyle"
|
||||
:show-summary="true"
|
||||
:summary-method="(meta) => getTeamSummaries(meta, team)"
|
||||
>
|
||||
<el-table-column label="昵称" prop="nickname" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.nickname || '-' }}</span>
|
||||
<el-tag v-if="scope.row.status === 0" size="mini" type="info" style="margin-left: 4px">已禁用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="prevBuyLabel(team)" prop="prevBuy" min-width="110" align="right">
|
||||
<template slot-scope="scope">{{ formatNum(scope.row.prevBuy) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="todaySellLabel(team)" prop="todaySell" min-width="110" align="right">
|
||||
<template slot-scope="scope">{{ formatNum(scope.row.todaySell) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="todayBuyLabel(team)" prop="todayBuy" min-width="110" align="right">
|
||||
<template slot-scope="scope">{{ formatNum(scope.row.todayBuy) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="serviceFeeLabel(team)" prop="serviceFee" min-width="120" align="right">
|
||||
<template slot-scope="scope">{{ formatNum(scope.row.serviceFee) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="E积分" prop="eScore" min-width="90" align="right">
|
||||
<template slot-scope="scope">{{ formatNum(scope.row.eScore) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际收付" prop="actual" min-width="110" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :class="actualClass(scope.row.actual)">{{ formatNum(scope.row.actual) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="团队" min-width="120" align="center">
|
||||
<template>
|
||||
<span>{{ team.leaderNickname || team.teamCode || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="180">
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="scope.row.remark"
|
||||
size="mini"
|
||||
placeholder="可填写(仅当前会话有效)"
|
||||
clearable
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-if="!team.rows || team.rows.length === 0" class="empty-tip">该团队当日无成员数据</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<el-card v-else-if="report" class="box-card mt10">
|
||||
<div class="empty-tip">未查询到任何团队数据。</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from 'file-saver';
|
||||
import {
|
||||
getExternalTeamDailyReport,
|
||||
exportExternalTeamDailyReport,
|
||||
} from '@/api/integralExternal';
|
||||
|
||||
export default {
|
||||
name: 'IntegralExternalTeamReport',
|
||||
data() {
|
||||
return {
|
||||
listLoading: false,
|
||||
report: null,
|
||||
tableFrom: {
|
||||
leaderId: '',
|
||||
date: this.yesterdayStr(),
|
||||
includeDisabled: false,
|
||||
},
|
||||
pickerOptions: {
|
||||
disabledDate(t) {
|
||||
return t.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// 进入页面默认拉取所有团队(不传 leaderId)
|
||||
this.seachList();
|
||||
},
|
||||
methods: {
|
||||
yesterdayStr() {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - 1);
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${m}-${day}`;
|
||||
},
|
||||
headerCellStyle() {
|
||||
return {
|
||||
background: '#C6EFCE',
|
||||
color: '#000',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
};
|
||||
},
|
||||
seachList() {
|
||||
this.listLoading = true;
|
||||
const params = {
|
||||
date: this.tableFrom.date,
|
||||
includeDisabled: this.tableFrom.includeDisabled,
|
||||
};
|
||||
if (this.tableFrom.leaderId) {
|
||||
params.leaderId = this.tableFrom.leaderId;
|
||||
}
|
||||
getExternalTeamDailyReport(params)
|
||||
.then((res) => {
|
||||
this.report = res || null;
|
||||
})
|
||||
.catch(() => {
|
||||
this.report = null;
|
||||
})
|
||||
.finally(() => {
|
||||
this.listLoading = false;
|
||||
});
|
||||
},
|
||||
resetHandler() {
|
||||
this.tableFrom = {
|
||||
leaderId: '',
|
||||
date: this.yesterdayStr(),
|
||||
includeDisabled: false,
|
||||
};
|
||||
this.seachList();
|
||||
},
|
||||
formatNum(v) {
|
||||
if (v === null || v === undefined || v === '') return '0';
|
||||
const n = Number(v);
|
||||
if (Number.isNaN(n)) return v;
|
||||
const sign = n < 0 ? '-' : '';
|
||||
const abs = Math.abs(n);
|
||||
const fixed = abs.toFixed(2);
|
||||
const [intPart, decPart] = fixed.split('.');
|
||||
const withSep = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
return `${sign}${withSep}.${decPart}`;
|
||||
},
|
||||
actualClass(v) {
|
||||
const n = Number(v);
|
||||
if (Number.isNaN(n)) return '';
|
||||
if (n < 0) return 'text-danger';
|
||||
if (n === 0) return 'text-muted';
|
||||
return '';
|
||||
},
|
||||
prevBuyLabel(team) {
|
||||
return team && team.previousDate ? `${team.previousDate.slice(5)} 买单` : 'D-1 买单';
|
||||
},
|
||||
todaySellLabel(team) {
|
||||
return team && team.date ? `${team.date.slice(5)} 卖单` : 'D 卖单';
|
||||
},
|
||||
todayBuyLabel(team) {
|
||||
return team && team.date ? `${team.date.slice(5)} 买单` : 'D 买单';
|
||||
},
|
||||
serviceFeeLabel(team) {
|
||||
return team && team.serviceRate
|
||||
? `服务费*${team.serviceRate}`
|
||||
: `服务费*${this.report ? this.report.serviceRate : '0.02'}`;
|
||||
},
|
||||
/** 团队子表的小计行(直接取 team.summary,不在前端逐行累加) */
|
||||
getTeamSummaries({ columns }, team) {
|
||||
const result = new Array(columns.length).fill('');
|
||||
if (!team || !team.summary) return result;
|
||||
const s = team.summary;
|
||||
const map = {
|
||||
nickname: '小计',
|
||||
prevBuy: s.prevBuy,
|
||||
todaySell: s.todaySell,
|
||||
todayBuy: s.todayBuy,
|
||||
serviceFee:s.serviceFee,
|
||||
eScore: s.eScore,
|
||||
actual: s.actual,
|
||||
};
|
||||
columns.forEach((col, idx) => {
|
||||
const v = map[col.property];
|
||||
if (v !== undefined && v !== null) {
|
||||
result[idx] = col.property === 'nickname' ? v : this.formatNum(v);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
/** 工具栏里的"导出 Excel"按钮:使用当前 tableFrom 的 leaderId */
|
||||
exportExcel() {
|
||||
if (!this.tableFrom.leaderId) {
|
||||
this.$message.warning('Excel 导出需要指定团队长 ID');
|
||||
return;
|
||||
}
|
||||
this.doExport(this.tableFrom.leaderId, '');
|
||||
},
|
||||
/** 多团队卡片右上角的"导出该团队"按钮 */
|
||||
exportTeamExcel(team) {
|
||||
if (!team || !team.leaderId) return;
|
||||
this.doExport(team.leaderId, team.leaderNickname || team.teamCode || '');
|
||||
},
|
||||
doExport(leaderId, nickname) {
|
||||
this.listLoading = true;
|
||||
exportExternalTeamDailyReport({
|
||||
leaderId,
|
||||
date: this.tableFrom.date,
|
||||
includeDisabled: this.tableFrom.includeDisabled,
|
||||
})
|
||||
.then((blob) => {
|
||||
const file = blob instanceof Blob ? blob : new Blob([blob], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
});
|
||||
const date = this.tableFrom.date || this.yesterdayStr();
|
||||
const name = nickname || `leader${leaderId}`;
|
||||
saveAs(file, `团队${name}_${date}.xlsx`);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.listLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mt10 { margin-top: 10px; }
|
||||
.selWidth { width: 240px; }
|
||||
.leaderIdWidth { width: 100px; }
|
||||
.dateWidth { width: 160px; }
|
||||
.hint {
|
||||
margin-top: 4px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
.team-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.report-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: 12px;
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.rate-info {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.summary-row {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.summary-card {
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
.summary-label {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.summary-value {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
.text-danger { color: #C00000; font-weight: bold; }
|
||||
.text-muted { color: #909399; }
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
padding: 30px 0;
|
||||
}
|
||||
</style>
|
||||
747
backend-adminend/src/views/integral-external/wa-order/index.vue
Normal file
747
backend-adminend/src/views/integral-external/wa-order/index.vue
Normal file
@@ -0,0 +1,747 @@
|
||||
<template>
|
||||
<div class="divBox relative">
|
||||
<!-- 顶部搜索区 -->
|
||||
<el-card class="box-card">
|
||||
<el-form size="small" inline label-width="100px">
|
||||
<el-form-item label="订单状态:">
|
||||
<el-radio-group v-model="uiStatus" type="button" size="mini" @change="onStatusChange">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="unPaid">待付款</el-radio-button>
|
||||
<el-radio-button label="paid">已支付</el-radio-button>
|
||||
<el-radio-button label="complete">交易完成</el-radio-button>
|
||||
<el-radio-button label="cancel">已取消</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="时间字段:">
|
||||
<el-select
|
||||
v-model="timeField"
|
||||
placeholder="选择时间维度"
|
||||
style="width: 140px"
|
||||
size="mini"
|
||||
@change="seachList"
|
||||
>
|
||||
<el-option label="抢购时间" value="buyTime" />
|
||||
<el-option label="支付时间" value="payTime" />
|
||||
<el-option label="完成时间" value="confirmTime" />
|
||||
<el-option label="创建时间" value="createdAt" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期范围:">
|
||||
<el-date-picker
|
||||
v-model="timeVal"
|
||||
value-format="yyyy-MM-dd"
|
||||
format="yyyy-MM-dd"
|
||||
size="mini"
|
||||
type="daterange"
|
||||
placeholder="自定义时间"
|
||||
style="width: 220px"
|
||||
@change="onTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="订单号:">
|
||||
<el-input
|
||||
v-model="tableFrom.orderSn"
|
||||
placeholder="请输入订单号"
|
||||
clearable
|
||||
class="selWidth"
|
||||
size="mini"
|
||||
@keyup.enter.native="seachList"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="买家ID:">
|
||||
<el-input
|
||||
v-model.number="tableFrom.buyerId"
|
||||
placeholder="买家用户ID"
|
||||
clearable
|
||||
class="selWidth"
|
||||
size="mini"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="卖家ID:">
|
||||
<el-input
|
||||
v-model.number="tableFrom.sellerId"
|
||||
placeholder="卖家ID(0=平台)"
|
||||
clearable
|
||||
class="selWidth"
|
||||
size="mini"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否转拍:">
|
||||
<el-select
|
||||
v-model="tableFrom.isResell"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100px"
|
||||
size="mini"
|
||||
@change="seachList"
|
||||
>
|
||||
<el-option :value="1" label="是" />
|
||||
<el-option :value="0" label="否" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="seachList">查询</el-button>
|
||||
<el-button icon="el-icon-refresh-left" size="mini" @click="resetHandler">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<el-card class="box-card mt10">
|
||||
<div class="header-actions">
|
||||
<el-popover
|
||||
placement="bottom-end"
|
||||
width="300"
|
||||
trigger="click"
|
||||
v-model="columnPopoverVisible"
|
||||
>
|
||||
<div class="column-setting">
|
||||
<div class="column-setting-actions">
|
||||
<el-button size="mini" type="text" @click="selectAllColumns">全选</el-button>
|
||||
<el-button size="mini" type="text" @click="invertColumns">反选</el-button>
|
||||
<el-button size="mini" type="text" @click="resetColumns">恢复默认</el-button>
|
||||
</div>
|
||||
<el-checkbox-group v-model="visibleColumns" class="column-checkbox-group">
|
||||
<el-checkbox
|
||||
v-for="col in columnsConfig"
|
||||
:key="col.prop"
|
||||
:label="col.prop"
|
||||
:disabled="col.fixed"
|
||||
>{{ col.label }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<el-button slot="reference" size="mini" icon="el-icon-setting">列设置</el-button>
|
||||
</el-popover>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="getList" />
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="listLoading"
|
||||
:data="tableData.data"
|
||||
size="mini"
|
||||
class="table"
|
||||
highlight-current-row
|
||||
:header-cell-style="{ fontWeight: 'bold' }"
|
||||
>
|
||||
<template v-for="col in columnsConfig">
|
||||
<el-table-column
|
||||
v-if="col.prop !== 'actions' && visibleColumns.includes(col.prop)"
|
||||
:key="col.prop"
|
||||
:label="col.label"
|
||||
:min-width="col.width"
|
||||
:show-overflow-tooltip="col.tooltip"
|
||||
:align="col.align || 'left'"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<!-- 商品图片单元格使用 el-image,其他文本走 formatCell -->
|
||||
<el-image
|
||||
v-if="col.prop === 'merchandiseImage' && scope.row.merchandiseImage"
|
||||
:src="scope.row.merchandiseImage"
|
||||
:preview-src-list="[scope.row.merchandiseImage]"
|
||||
style="width:48px;height:48px;border-radius:4px"
|
||||
fit="cover"
|
||||
/>
|
||||
<span v-else-if="col.prop === 'merchandiseImage'">-</span>
|
||||
<span v-else v-html="formatCell(col.prop, scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" min-width="120" fixed="right" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="openDetail(scope.row.id)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="block mt20">
|
||||
<el-pagination
|
||||
:page-sizes="[15, 30, 45, 60]"
|
||||
:page-size="tableFrom.limit"
|
||||
:current-page="tableFrom.page"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="tableData.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 详情抽屉 -->
|
||||
<el-drawer
|
||||
:visible.sync="drawerVisible"
|
||||
direction="rtl"
|
||||
size="680px"
|
||||
:destroy-on-close="true"
|
||||
:with-header="false"
|
||||
append-to-body
|
||||
custom-class="wa-order-drawer"
|
||||
@close="onDrawerClose"
|
||||
>
|
||||
<div v-loading="detailLoading" class="drawer-body">
|
||||
<!-- 顶部摘要:订单号 + 状态徽标 + 关闭 -->
|
||||
<div class="drawer-header">
|
||||
<div class="header-left">
|
||||
<div class="order-sn-row">
|
||||
<span class="order-sn-label">订单号</span>
|
||||
<span class="order-sn">{{ detail ? detail.orderSn : '-' }}</span>
|
||||
<el-button
|
||||
v-if="detail"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-document-copy"
|
||||
@click="copyOrderSn"
|
||||
>复制</el-button>
|
||||
</div>
|
||||
<div class="tag-row">
|
||||
<el-tag v-if="detail" :type="statusTagType(detail)" size="small">{{ statusLabel(detail) }}</el-tag>
|
||||
<el-tag v-if="detail && detail.isResell === 1" type="warning" size="small" effect="plain">转拍</el-tag>
|
||||
<el-tag v-if="detail && detail.isCancel === 1" type="danger" size="small" effect="plain">已取消</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
class="close-btn"
|
||||
icon="el-icon-close"
|
||||
type="text"
|
||||
@click="drawerVisible = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="detail" class="drawer-content">
|
||||
<!-- 时间链路 -->
|
||||
<el-card shadow="never" class="block-card">
|
||||
<div slot="header" class="block-title">订单时间链路</div>
|
||||
<el-steps :active="stepActive(detail)" finish-status="success" align-center>
|
||||
<el-step title="抢购下单" :description="detail.buyTime || '-'" />
|
||||
<el-step
|
||||
v-if="detail.isResell === 1"
|
||||
title="转拍生成"
|
||||
:description="detail.createdAt || '-'"
|
||||
/>
|
||||
<el-step title="支付" :description="detail.payTime || '-'" />
|
||||
<el-step title="完成" :description="detail.confirmTime || '-'" />
|
||||
</el-steps>
|
||||
</el-card>
|
||||
|
||||
<!-- 订单基础 -->
|
||||
<el-card shadow="never" class="block-card">
|
||||
<div slot="header" class="block-title">订单基础</div>
|
||||
<el-descriptions :column="2" :colon="false" size="small" class="info-desc">
|
||||
<el-descriptions-item label="订单ID">{{ detail.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单金额">
|
||||
<span class="price">¥{{ detail.totalMoney }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="商品ID">{{ detail.merchandiseId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商品名称">{{ detail.merchandiseTitle || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否显示">{{ detail.isShow === 1 ? '显示' : '隐藏' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原订单ID">{{ detail.oldId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商品图片" :span="2">
|
||||
<el-image
|
||||
v-if="detail.merchandiseImage"
|
||||
:src="detail.merchandiseImage"
|
||||
:preview-src-list="[detail.merchandiseImage]"
|
||||
style="width:80px;height:80px;border-radius:4px"
|
||||
fit="cover"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 买家 -->
|
||||
<el-card shadow="never" class="block-card">
|
||||
<div slot="header" class="block-title">买家信息</div>
|
||||
<el-descriptions :column="2" :colon="false" size="small" class="info-desc">
|
||||
<el-descriptions-item label="买家ID">{{ detail.buyerId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="买家昵称">{{ detail.buyerName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收货人">{{ detail.consignee || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收货电话">{{ detail.phone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收货地区" :span="2">{{ joinArea(detail) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="详细地址" :span="2">{{ detail.address || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 卖家 -->
|
||||
<el-card shadow="never" class="block-card">
|
||||
<div slot="header" class="block-title">卖家信息</div>
|
||||
<el-descriptions :column="2" :colon="false" size="small" class="info-desc">
|
||||
<el-descriptions-item label="卖家ID">
|
||||
{{ detail.sellerId === 0 ? '0(平台)' : detail.sellerId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="卖家昵称">
|
||||
{{ detail.sellerId === 0 ? '平台' : (detail.sellerName || '-') }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 支付与日志 -->
|
||||
<el-card shadow="never" class="block-card">
|
||||
<div slot="header" class="block-title">支付与日志</div>
|
||||
<el-descriptions :column="2" :colon="false" size="small" class="info-desc">
|
||||
<el-descriptions-item label="抢购时间">{{ detail.buyTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="支付时间">{{ detail.payTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="完成时间">{{ detail.confirmTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detail.createdAt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="下单IP">{{ detail.buyIp || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detail.isCancel === 1" label="取消IP">
|
||||
{{ detail.cancelIp || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="支付凭证" :span="2">
|
||||
<el-image
|
||||
v-if="detail.payImg"
|
||||
:src="detail.payImg"
|
||||
:preview-src-list="[detail.payImg]"
|
||||
style="width:80px;height:80px;border-radius:4px"
|
||||
fit="cover"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div class="drawer-footer">
|
||||
<el-button @click="drawerVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getExternalWaOrderList,
|
||||
getExternalWaOrderInfo,
|
||||
} from '@/api/integralExternal';
|
||||
|
||||
const STORAGE_KEY = 'waOrderColumns:external';
|
||||
|
||||
const COLUMN_DEFS = [
|
||||
{ prop: 'id', label: '订单ID', width: 80, defaultVisible: true, fixed: true, tooltip: false, align: 'left' },
|
||||
{ prop: 'orderSn', label: '订单号', width: 210, defaultVisible: true, fixed: true, tooltip: true },
|
||||
{ prop: 'merchandiseId', label: '商品ID', width: 90, defaultVisible: true, align: 'left' },
|
||||
{ prop: 'merchandiseTitle', label: '商品名称', width: 200, defaultVisible: true, tooltip: true },
|
||||
{ prop: 'merchandiseImage', label: '商品图片', width: 90, defaultVisible: false, align: 'center' },
|
||||
{ prop: 'totalMoney', label: '订单金额', width: 110, defaultVisible: true, align: 'right' },
|
||||
{ prop: 'statusStr', label: '订单状态', width: 110, defaultVisible: true, align: 'center' },
|
||||
{ prop: 'isResell', label: '是否转拍', width: 90, defaultVisible: false, align: 'center' },
|
||||
{ prop: 'buyerName', label: '买家昵称', width: 120, defaultVisible: true, tooltip: true },
|
||||
{ prop: 'buyerId', label: '买家ID', width: 90, defaultVisible: false },
|
||||
{ prop: 'sellerName', label: '卖家昵称', width: 120, defaultVisible: true, tooltip: true },
|
||||
{ prop: 'sellerId', label: '卖家ID', width: 90, defaultVisible: false },
|
||||
{ prop: 'consignee', label: '收货人', width: 100, defaultVisible: false },
|
||||
{ prop: 'phone', label: '收货电话', width: 130, defaultVisible: false },
|
||||
{ prop: 'areaText', label: '收货地区', width: 180, defaultVisible: false, tooltip: true },
|
||||
{ prop: 'address', label: '详细地址', width: 220, defaultVisible: false, tooltip: true },
|
||||
{ prop: 'buyTime', label: '抢购时间', width: 160, defaultVisible: true },
|
||||
{ prop: 'createdAt', label: '转拍/创建时间', width: 160, defaultVisible: false },
|
||||
{ prop: 'payTime', label: '支付时间', width: 160, defaultVisible: true },
|
||||
{ prop: 'confirmTime', label: '完成时间', width: 160, defaultVisible: true },
|
||||
{ prop: 'updatedAt', label: '更新时间', width: 160, defaultVisible: false },
|
||||
{ prop: 'buyIp', label: '下单IP', width: 130, defaultVisible: false },
|
||||
{ prop: 'cancelIp', label: '取消IP', width: 130, defaultVisible: false },
|
||||
{ prop: 'isShow', label: '显示', width: 80, defaultVisible: false, align: 'center' },
|
||||
];
|
||||
|
||||
export default {
|
||||
name: 'IntegralExternalWaOrder',
|
||||
data() {
|
||||
return {
|
||||
listLoading: false,
|
||||
tableData: { data: [], total: 0 },
|
||||
tableFrom: {
|
||||
orderSn: '',
|
||||
buyerId: '',
|
||||
sellerId: '',
|
||||
status: null,
|
||||
isCancel: null,
|
||||
isResell: null,
|
||||
buyTimeStart: '',
|
||||
buyTimeEnd: '',
|
||||
confirmTimeStart: '',
|
||||
confirmTimeEnd: '',
|
||||
page: 1,
|
||||
limit: 15,
|
||||
},
|
||||
uiStatus: 'all',
|
||||
timeField: 'buyTime',
|
||||
timeVal: [],
|
||||
columnsConfig: COLUMN_DEFS.concat([{ prop: 'actions', label: '操作', fixed: true }]),
|
||||
visibleColumns: this.loadColumns(),
|
||||
columnPopoverVisible: false,
|
||||
drawerVisible: false,
|
||||
detail: null,
|
||||
detailLoading: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// 支持 ?detailId=xxx 直接打开抽屉
|
||||
if (this.$route && this.$route.query && this.$route.query.detailId) {
|
||||
this.openDetail(this.$route.query.detailId);
|
||||
}
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
loadColumns() {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (raw) {
|
||||
const arr = JSON.parse(raw);
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
const validProps = COLUMN_DEFS.map((c) => c.prop);
|
||||
// 1) 与最新字段定义做交集;2) 锁定列必须存在;3) 新增的 defaultVisible 列自动并入
|
||||
const next = arr.filter((p) => validProps.includes(p));
|
||||
COLUMN_DEFS.forEach((c) => {
|
||||
if ((c.fixed || c.defaultVisible) && !next.includes(c.prop)) {
|
||||
next.push(c.prop);
|
||||
}
|
||||
});
|
||||
return next;
|
||||
}
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
return COLUMN_DEFS.filter((c) => c.defaultVisible || c.fixed).map((c) => c.prop);
|
||||
},
|
||||
persistColumns() {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.visibleColumns));
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
selectAllColumns() {
|
||||
this.visibleColumns = COLUMN_DEFS.map((c) => c.prop);
|
||||
this.persistColumns();
|
||||
},
|
||||
invertColumns() {
|
||||
const all = COLUMN_DEFS.map((c) => c.prop);
|
||||
const next = all.filter((p) => !this.visibleColumns.includes(p));
|
||||
// 锁定列必须保留
|
||||
COLUMN_DEFS.filter((c) => c.fixed).forEach((c) => {
|
||||
if (!next.includes(c.prop)) next.push(c.prop);
|
||||
});
|
||||
this.visibleColumns = next;
|
||||
this.persistColumns();
|
||||
},
|
||||
resetColumns() {
|
||||
this.visibleColumns = COLUMN_DEFS.filter((c) => c.defaultVisible || c.fixed).map((c) => c.prop);
|
||||
this.persistColumns();
|
||||
},
|
||||
onStatusChange() {
|
||||
this.applyUiStatus();
|
||||
this.seachList();
|
||||
},
|
||||
applyUiStatus() {
|
||||
// UI 标签 → status / isCancel 组合
|
||||
switch (this.uiStatus) {
|
||||
case 'unPaid':
|
||||
this.tableFrom.status = 0;
|
||||
this.tableFrom.isCancel = 0;
|
||||
break;
|
||||
case 'paid':
|
||||
this.tableFrom.status = 1;
|
||||
this.tableFrom.isCancel = 0;
|
||||
break;
|
||||
case 'complete':
|
||||
this.tableFrom.status = 2;
|
||||
this.tableFrom.isCancel = 0;
|
||||
break;
|
||||
case 'cancel':
|
||||
this.tableFrom.status = null;
|
||||
this.tableFrom.isCancel = 1;
|
||||
break;
|
||||
default:
|
||||
this.tableFrom.status = null;
|
||||
this.tableFrom.isCancel = null;
|
||||
}
|
||||
},
|
||||
onTimeChange(val) {
|
||||
// 清掉所有时间字段
|
||||
this.tableFrom.buyTimeStart = '';
|
||||
this.tableFrom.buyTimeEnd = '';
|
||||
this.tableFrom.confirmTimeStart = '';
|
||||
this.tableFrom.confirmTimeEnd = '';
|
||||
if (val && val.length === 2) {
|
||||
const startKey = `${this.timeField}Start`;
|
||||
const endKey = `${this.timeField}End`;
|
||||
// WaOrderSearchRequest 仅支持 buyTime / confirmTime;其余维度回退到 buyTime
|
||||
if (startKey in this.tableFrom) {
|
||||
this.tableFrom[startKey] = `${val[0]} 00:00:00`;
|
||||
this.tableFrom[endKey] = `${val[1]} 23:59:59`;
|
||||
} else {
|
||||
this.tableFrom.buyTimeStart = `${val[0]} 00:00:00`;
|
||||
this.tableFrom.buyTimeEnd = `${val[1]} 23:59:59`;
|
||||
}
|
||||
}
|
||||
this.seachList();
|
||||
},
|
||||
seachList() {
|
||||
this.tableFrom.page = 1;
|
||||
this.getList();
|
||||
},
|
||||
resetHandler() {
|
||||
this.tableFrom = {
|
||||
orderSn: '', buyerId: '', sellerId: '',
|
||||
status: null, isCancel: null, isResell: null,
|
||||
buyTimeStart: '', buyTimeEnd: '', confirmTimeStart: '', confirmTimeEnd: '',
|
||||
page: 1, limit: 15,
|
||||
};
|
||||
this.uiStatus = 'all';
|
||||
this.timeField = 'buyTime';
|
||||
this.timeVal = [];
|
||||
this.getList();
|
||||
},
|
||||
getList() {
|
||||
this.listLoading = true;
|
||||
const params = { ...this.tableFrom };
|
||||
// null/空字段不发
|
||||
Object.keys(params).forEach((k) => {
|
||||
if (params[k] === '' || params[k] === null) delete params[k];
|
||||
});
|
||||
getExternalWaOrderList(params)
|
||||
.then((res) => {
|
||||
this.tableData.data = res.list || [];
|
||||
this.tableData.total = res.total || 0;
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
this.listLoading = false;
|
||||
});
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.tableFrom.limit = val;
|
||||
this.getList();
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.tableFrom.page = val;
|
||||
this.getList();
|
||||
},
|
||||
formatCell(prop, row) {
|
||||
if (!row) return '-';
|
||||
switch (prop) {
|
||||
case 'totalMoney':
|
||||
return row.totalMoney != null ? `¥${row.totalMoney}` : '-';
|
||||
case 'statusStr': {
|
||||
const status = this.statusLabel(row);
|
||||
const type = this.statusTagType(row);
|
||||
return `<span class="el-tag el-tag--${type} el-tag--mini">${status}</span>`;
|
||||
}
|
||||
case 'isResell':
|
||||
return row.isResell === 1
|
||||
? '<span style="color:#E6A23C">是</span>'
|
||||
: '否';
|
||||
case 'sellerId':
|
||||
return row.sellerId === 0 ? '0(平台)' : row.sellerId;
|
||||
case 'sellerName':
|
||||
return row.sellerId === 0 ? '平台' : (row.sellerName || '-');
|
||||
case 'areaText':
|
||||
return this.joinArea(row);
|
||||
case 'isShow':
|
||||
return row.isShow === 1 ? '是' : '否';
|
||||
case 'merchandiseTitle':
|
||||
return row.merchandiseTitle || row.productName || '-';
|
||||
default: {
|
||||
const v = row[prop];
|
||||
return v == null || v === '' ? '-' : v;
|
||||
}
|
||||
}
|
||||
},
|
||||
statusLabel(row) {
|
||||
if (!row) return '-';
|
||||
if (row.isCancel === 1) return '已取消';
|
||||
if (row.isResell === 1 && row.status === 0) return '转拍中';
|
||||
switch (row.status) {
|
||||
case 0: return '待付款';
|
||||
case 1: return '已支付';
|
||||
case 2: return '交易完成';
|
||||
default: return '-';
|
||||
}
|
||||
},
|
||||
statusTagType(row) {
|
||||
if (!row) return 'info';
|
||||
if (row.isCancel === 1) return 'info';
|
||||
switch (row.status) {
|
||||
case 0: return 'warning';
|
||||
case 1: return 'primary';
|
||||
case 2: return 'success';
|
||||
default: return 'info';
|
||||
}
|
||||
},
|
||||
stepActive(row) {
|
||||
if (!row) return 0;
|
||||
if (row.confirmTime) return row.isResell === 1 ? 4 : 3;
|
||||
if (row.payTime) return row.isResell === 1 ? 3 : 2;
|
||||
if (row.isResell === 1 && row.createdAt) return 2;
|
||||
if (row.buyTime) return 1;
|
||||
return 0;
|
||||
},
|
||||
joinArea(row) {
|
||||
if (!row) return '-';
|
||||
const parts = [row.province, row.city, row.area].filter(Boolean);
|
||||
return parts.length ? parts.join(' / ') : '-';
|
||||
},
|
||||
openDetail(id) {
|
||||
if (!id) return;
|
||||
this.drawerVisible = true;
|
||||
this.detailLoading = true;
|
||||
this.detail = null;
|
||||
getExternalWaOrderInfo(id)
|
||||
.then((res) => {
|
||||
this.detail = res || null;
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('订单详情加载失败');
|
||||
this.drawerVisible = false;
|
||||
})
|
||||
.finally(() => {
|
||||
this.detailLoading = false;
|
||||
});
|
||||
},
|
||||
onDrawerClose() {
|
||||
this.detail = null;
|
||||
// 清掉 URL 上的 detailId
|
||||
if (this.$route && this.$route.query && this.$route.query.detailId) {
|
||||
const q = { ...this.$route.query };
|
||||
delete q.detailId;
|
||||
this.$router.replace({ path: this.$route.path, query: q });
|
||||
}
|
||||
},
|
||||
copyOrderSn() {
|
||||
if (!this.detail || !this.detail.orderSn) return;
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = this.detail.orderSn;
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
try { document.execCommand('copy'); this.$message.success('已复制订单号'); }
|
||||
catch (e) { this.$message.error('复制失败'); }
|
||||
document.body.removeChild(ta);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visibleColumns: {
|
||||
deep: true,
|
||||
handler() { this.persistColumns(); },
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mt10 { margin-top: 10px; }
|
||||
.mt20 { margin-top: 20px; }
|
||||
.selWidth { width: 180px; }
|
||||
.header-actions {
|
||||
text-align: right;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.column-setting {
|
||||
.column-setting-actions {
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px dashed #ebeef5;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
}
|
||||
.column-checkbox-group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 6px 0;
|
||||
}
|
||||
.block { text-align: right; }
|
||||
|
||||
/* ===== 抽屉详情样式 ===== */
|
||||
.drawer-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.drawer-header {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 16px 20px;
|
||||
background: #f5f7fa;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
.header-left { flex: 1; min-width: 0; }
|
||||
.order-sn-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.order-sn-label {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
.order-sn {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.tag-row {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.close-btn {
|
||||
font-size: 18px;
|
||||
color: #909399;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
.drawer-content {
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.drawer-footer {
|
||||
flex: 0 0 auto;
|
||||
text-align: right;
|
||||
padding: 12px 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
}
|
||||
.block-card {
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #ebeef5;
|
||||
::v-deep .el-card__header {
|
||||
padding: 10px 14px;
|
||||
background: #fafbfc;
|
||||
}
|
||||
::v-deep .el-card__body {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
}
|
||||
.block-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
position: relative;
|
||||
padding-left: 8px;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0; top: 3px;
|
||||
width: 3px; height: 14px;
|
||||
background: #409EFF;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.info-desc {
|
||||
::v-deep .el-descriptions__label {
|
||||
color: #909399;
|
||||
font-weight: normal;
|
||||
width: 90px;
|
||||
}
|
||||
::v-deep .el-descriptions__content {
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
.price {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
::v-deep .wa-order-drawer .el-drawer__body {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user