- 在订单列表中增加商品明细的展开/收起功能,默认显示2条商品信息 - 支持点击“展开全部 N 件”切换显示全部或折叠商品明细 - 优化订单状态栏支付状态、发货状态、开票状态及操作按钮中的分隔线统一风格 - 将shop/dashboard运行天数改为基于租户创建时间,而非订阅生效时间,保证准确性 - 调整订单列表表格列宽及显示顺序,改善界面布局和信息展示 - 注释和代码格式微调,删除无用的组件显示及注释代码,提升代码整洁度 - 修复了部分变量定义和模板标签排版,完善代码可维护性
350 lines
10 KiB
Vue
350 lines
10 KiB
Vue
<!-- 搜索表单 -->
|
||
<template>
|
||
<a-space :size="10" style="flex-wrap: wrap">
|
||
<!-- <a-button-->
|
||
<!-- danger-->
|
||
<!-- type="primary"-->
|
||
<!-- class="ele-btn-icon"-->
|
||
<!-- :disabled="selection?.length === 0"-->
|
||
<!-- @click="removeBatch"-->
|
||
<!-- >-->
|
||
<!-- <template #icon>-->
|
||
<!-- <DeleteOutlined/>-->
|
||
<!-- </template>-->
|
||
<!-- <span>批量删除</span>-->
|
||
<!-- </a-button>-->
|
||
<a-input-search
|
||
allow-clear
|
||
v-model:value="where.orderNo"
|
||
placeholder="订单编号"
|
||
style="width: 240px"
|
||
@search="reload"
|
||
@pressEnter="reload"
|
||
/>
|
||
<!-- <a-select-->
|
||
<!-- v-model:value="where.type"-->
|
||
<!-- style="width: 150px"-->
|
||
<!-- placeholder="订单类型"-->
|
||
<!-- @change="search"-->
|
||
<!-- >-->
|
||
<!-- <a-select-option value="">全部</a-select-option>-->
|
||
<!-- <a-select-option :value="1">普通订单</a-select-option>-->
|
||
<!-- <a-select-option :value="2">秒杀订单</a-select-option>-->
|
||
<!-- <a-select-option :value="3">拼团订单</a-select-option>-->
|
||
<!-- </a-select>-->
|
||
<a-select
|
||
v-model:value="where.payStatus"
|
||
style="width: 150px"
|
||
placeholder="付款状态"
|
||
@change="search"
|
||
>
|
||
<a-select-option value="">全部</a-select-option>
|
||
<a-select-option :value="1">已付款</a-select-option>
|
||
<a-select-option :value="0">未付款</a-select-option>
|
||
</a-select>
|
||
<!-- <a-select-->
|
||
<!-- v-model:value="where.orderStatus"-->
|
||
<!-- style="width: 150px"-->
|
||
<!-- placeholder="订单状态"-->
|
||
<!-- @change="search"-->
|
||
<!-- >-->
|
||
<!-- <a-select-option value="">全部</a-select-option>-->
|
||
<!-- <a-select-option :value="1">已完成</a-select-option>-->
|
||
<!-- <a-select-option :value="3">已取消</a-select-option>-->
|
||
<!-- <a-select-option :value="4">退款中</a-select-option>-->
|
||
<!-- <a-select-option :value="5">退款被拒</a-select-option>-->
|
||
<!-- <a-select-option :value="6">退款成功</a-select-option>-->
|
||
<!-- </a-select>-->
|
||
<a-select
|
||
:options="getPayType()"
|
||
v-model:value="where.payType"
|
||
style="width: 150px"
|
||
placeholder="付款方式"
|
||
@change="search"
|
||
/>
|
||
|
||
<a-range-picker
|
||
v-model:value="dateRange"
|
||
@change="search"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
<a-input-search
|
||
allow-clear
|
||
:placeholder="getSearchPlaceholder()"
|
||
style="width: 320px"
|
||
v-model:value="where.keywords"
|
||
@search="reload"
|
||
>
|
||
<template #addonBefore>
|
||
<a-select v-model:value="type" style="width: 88px" @change="onType">
|
||
<a-select-option value="">不限</a-select-option>
|
||
<a-select-option value="userId"> 用户ID</a-select-option>
|
||
<a-select-option value="phone"> 手机号</a-select-option>
|
||
<a-select-option value="nickname"> 昵称</a-select-option>
|
||
</a-select>
|
||
</template>
|
||
</a-input-search>
|
||
<a-button @click="reset">重置</a-button>
|
||
<a-button @click="handleExport">导出</a-button>
|
||
</a-space>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import {ref, watch} from 'vue';
|
||
import {utils, writeFile} from 'xlsx';
|
||
import {message} from 'ant-design-vue';
|
||
import useSearch from '@/utils/use-search';
|
||
import {ShopOrder, ShopOrderParam} from '@/api/shop/shopOrder/model';
|
||
import {listShopOrder} from '@/api/shop/shopOrder';
|
||
import {listShopOrderGoods} from '@/api/shop/shopOrderGoods';
|
||
import {getPayType} from '@/utils/shop';
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
// 选中的订单
|
||
selection?: ShopOrder[];
|
||
// 当前列表标签对应的筛选状态(如待发货=1),导出时一并带入
|
||
statusFilter?: number;
|
||
}>(),
|
||
{}
|
||
);
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'search', where?: ShopOrderParam): void;
|
||
(e: 'add'): void;
|
||
(e: 'remove'): void;
|
||
(e: 'batchMove'): void;
|
||
}>();
|
||
|
||
// 表单数据
|
||
const {where, resetFields} = useSearch<ShopOrderParam>({
|
||
keywords: '',
|
||
orderId: undefined,
|
||
orderNo: undefined,
|
||
createTimeStart: undefined,
|
||
createTimeEnd: undefined,
|
||
userId: undefined,
|
||
payUserId: undefined,
|
||
nickname: undefined,
|
||
phone: undefined,
|
||
payStatus: undefined,
|
||
orderStatus: undefined,
|
||
payType: undefined
|
||
});
|
||
|
||
const reload = () => {
|
||
emit('search', {
|
||
...where,
|
||
keywords: type.value == '' ? where.keywords : undefined
|
||
});
|
||
};
|
||
|
||
// 批量删除
|
||
// const removeBatch = () => {
|
||
// emit('remove');
|
||
// };
|
||
|
||
const onType = () => {
|
||
resetFields();
|
||
};
|
||
|
||
// 获取搜索框placeholder
|
||
const getSearchPlaceholder = () => {
|
||
switch (type.value) {
|
||
case 'userId':
|
||
where.userId = Number(where.keywords);
|
||
return '请输入用户ID';
|
||
case 'phone':
|
||
where.phone = where.keywords;
|
||
return '请输入手机号';
|
||
case 'nickname':
|
||
where.nickname = where.keywords;
|
||
return '请输入用户昵称';
|
||
default:
|
||
return '请输入搜索内容';
|
||
}
|
||
};
|
||
|
||
/* 搜索 */
|
||
const search = () => {
|
||
const [d1, d2] = dateRange.value ?? [];
|
||
xlsFileName.value = `${d1}至${d2}`;
|
||
where.createTimeStart = d1 ? d1 + ' 00:00:00' : undefined;
|
||
where.createTimeEnd = d2 ? d2 + ' 23:59:59' : undefined;
|
||
emit('search', {
|
||
...where
|
||
});
|
||
};
|
||
|
||
/* 重置 */
|
||
const reset = () => {
|
||
resetFields();
|
||
search();
|
||
};
|
||
|
||
const dateRange = ref<[string, string]>(['', '']);
|
||
// 变量
|
||
const loading = ref(false);
|
||
const orders = ref<ShopOrder[]>([]);
|
||
const xlsFileName = ref<string>();
|
||
const type = ref('');
|
||
|
||
// 配送方式中文(0快递配送 1无需发货/自提 2商家送货)
|
||
const getDeliveryTypeLabel = (t?: number) => {
|
||
switch (t) {
|
||
case 0:
|
||
return '快递配送';
|
||
case 1:
|
||
return '无需发货(自提)';
|
||
case 2:
|
||
return '商家送货';
|
||
default:
|
||
return '未设置';
|
||
}
|
||
};
|
||
|
||
// 发货状态中文(10未发货 20已发货 30部分发货)
|
||
const getDeliveryStatusLabel = (s?: number) => {
|
||
switch (s) {
|
||
case 10:
|
||
return '未发货';
|
||
case 20:
|
||
return '已发货';
|
||
case 30:
|
||
return '部分发货';
|
||
default:
|
||
return '未知';
|
||
}
|
||
};
|
||
|
||
// 商品明细汇总(商品名(规格) x 数量)
|
||
const getGoodsSummary = (goods?: any[]) => {
|
||
if (!goods || !goods.length) return '';
|
||
return goods
|
||
.map((g) => {
|
||
const spec = g.spec ? `(${g.spec})` : '';
|
||
return `${g.goodsName || '未知商品'}${spec} x${g.totalNum || 1}`;
|
||
})
|
||
.join(';');
|
||
};
|
||
|
||
// 商品总数量:基于实际取到的商品明细求和(订单级 totalNum 列表接口可能不返回,以明细为准)
|
||
const getGoodsTotalNum = (goods?: any[]) => {
|
||
if (!goods || !goods.length) return 0;
|
||
return goods.reduce((sum, g) => sum + (Number(g.totalNum) || 0), 0);
|
||
};
|
||
|
||
// 自提/发货店铺名称
|
||
const getShopName = (d: ShopOrder) => {
|
||
if (d.deliveryType === 1) return d.selfTakeMerchantName || '';
|
||
return d.expressMerchantName || '';
|
||
};
|
||
|
||
// 导出
|
||
const handleExport = async () => {
|
||
loading.value = true;
|
||
const array: (string | number)[][] = [
|
||
[
|
||
'订单编号',
|
||
'收货人',
|
||
'联系电话',
|
||
'收货地址',
|
||
'配送方式',
|
||
'商品明细',
|
||
'商品总数量',
|
||
'实付金额(元)',
|
||
'配送时间',
|
||
'自提码',
|
||
'发货/自提店铺',
|
||
'买家备注',
|
||
'支付方式',
|
||
'发货状态',
|
||
'下单时间'
|
||
]
|
||
];
|
||
|
||
await listShopOrder({...where, statusFilter: props.statusFilter})
|
||
.then(async (list) => {
|
||
orders.value = list;
|
||
// 列表接口不返回 orderGoods,按订单逐个补齐商品明细(含名称/规格/数量)
|
||
const goodsList = await Promise.all(
|
||
(list ?? []).map((d) =>
|
||
d.orderId
|
||
? listShopOrderGoods({orderId: d.orderId}).catch(() => [])
|
||
: Promise.resolve([])
|
||
)
|
||
);
|
||
list?.forEach((d: ShopOrder, idx: number) => {
|
||
array.push([
|
||
`${d.orderNo || ''}`,
|
||
`${d.realName || ''}`,
|
||
`${d.phone || d.mobile || ''}`,
|
||
`${d.address || ''}`,
|
||
`${getDeliveryTypeLabel(d.deliveryType)}`,
|
||
`${getGoodsSummary(goodsList[idx])}`,
|
||
`${getGoodsTotalNum(goodsList[idx])}`,
|
||
`${d.payPrice || ''}`,
|
||
`${
|
||
d.sendStartTime && d.sendEndTime
|
||
? `${d.sendStartTime} - ${d.sendEndTime}`
|
||
: ''
|
||
}`,
|
||
`${d.selfTakeCode || ''}`,
|
||
`${getShopName(d)}`,
|
||
`${d.buyerRemarks || ''}`,
|
||
`${getPayType(d.payType)}`,
|
||
`${getDeliveryStatusLabel(d.deliveryStatus)}`,
|
||
`${d.createTime || ''}`
|
||
]);
|
||
});
|
||
const sheetName = `订单数据`;
|
||
const workbook = {
|
||
SheetNames: [sheetName],
|
||
Sheets: {}
|
||
};
|
||
const sheet = utils.aoa_to_sheet(array);
|
||
workbook.Sheets[sheetName] = sheet;
|
||
// 设置列宽(与上方表头一一对应)
|
||
sheet['!cols'] = [
|
||
{wch: 22}, // 订单编号
|
||
{wch: 12}, // 收货人
|
||
{wch: 16}, // 联系电话
|
||
{wch: 40}, // 收货地址
|
||
{wch: 14}, // 配送方式
|
||
{wch: 40}, // 商品明细
|
||
{wch: 12}, // 商品总数量
|
||
{wch: 12}, // 实付金额(元)
|
||
{wch: 28}, // 配送时间
|
||
{wch: 12}, // 自提码
|
||
{wch: 20}, // 发货/自提店铺
|
||
{wch: 30}, // 买家备注
|
||
{wch: 12}, // 支付方式
|
||
{wch: 10}, // 发货状态
|
||
{wch: 20} // 下单时间
|
||
];
|
||
message.loading('正在导出...');
|
||
setTimeout(() => {
|
||
writeFile(
|
||
workbook,
|
||
`${
|
||
where.createTimeEnd ? xlsFileName.value + '_' : ''
|
||
}${sheetName}.xlsx`
|
||
);
|
||
loading.value = false;
|
||
}, 1000);
|
||
})
|
||
.catch((msg) => {
|
||
message.error(msg);
|
||
loading.value = false;
|
||
})
|
||
.finally(() => {
|
||
});
|
||
};
|
||
|
||
watch(
|
||
() => props.selection,
|
||
() => {
|
||
}
|
||
);
|
||
</script>
|