Files
guilixu-admin/src/views/shop/shopOrder/components/search.vue
赵忠林 e87e60fd6a feat(shopOrder): 添加订单商品明细展开功能并优化订单状态显示
- 在订单列表中增加商品明细的展开/收起功能,默认显示2条商品信息
- 支持点击“展开全部 N 件”切换显示全部或折叠商品明细
- 优化订单状态栏支付状态、发货状态、开票状态及操作按钮中的分隔线统一风格
- 将shop/dashboard运行天数改为基于租户创建时间,而非订阅生效时间,保证准确性
- 调整订单列表表格列宽及显示顺序,改善界面布局和信息展示
- 注释和代码格式微调,删除无用的组件显示及注释代码,提升代码整洁度
- 修复了部分变量定义和模板标签排版,完善代码可维护性
2026-07-29 00:44:54 +08:00

350 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 搜索表单 -->
<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>