348 lines
9.9 KiB
Vue
348 lines
9.9 KiB
Vue
<template>
|
||
<div class="p-4">
|
||
<div class="mb-4 flex justify-between">
|
||
<div>
|
||
<FInput
|
||
v-model="filters.search"
|
||
placeholder="搜索订单..."
|
||
type="text"
|
||
class="w-60"
|
||
@input="debouncedSearch"
|
||
>
|
||
<template #prefix>
|
||
<i-lucide-search class="h-4 w-4 text-gray-500" />
|
||
</template>
|
||
</FInput>
|
||
</div>
|
||
<div class="flex items-center space-x-2">
|
||
<Button
|
||
icon="download"
|
||
appearance="primary"
|
||
@click="exportToCsv"
|
||
>
|
||
导出
|
||
</Button>
|
||
<Button
|
||
icon="refresh-cw"
|
||
appearance="minimal"
|
||
@click="resetAndFetch"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<Card class="overflow-hidden !rounded-none !border-0">
|
||
<template v-if="orders.length === 0">
|
||
<div class="flex h-60 flex-col items-center justify-center space-y-2 p-4 text-center">
|
||
<i-lucide-file-text class="h-8 w-8 text-gray-400" />
|
||
<p class="text-base font-medium">无订单记录</p>
|
||
<p class="text-sm text-gray-600">暂无订单记录可以显示</p>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-else>
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full">
|
||
<thead>
|
||
<tr class="border-b text-left text-sm font-medium text-gray-600">
|
||
<th
|
||
v-for="column in columns"
|
||
:key="column.key"
|
||
class="whitespace-nowrap px-4 py-3"
|
||
:class="column.class"
|
||
>
|
||
{{ column.label }}
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr
|
||
v-for="order in orders"
|
||
:key="order.name || order.order_id"
|
||
class="border-b text-sm hover:bg-gray-50"
|
||
>
|
||
<td class="px-4 py-3">{{ formatDate(order.creation) }}</td>
|
||
<td class="px-4 py-3">{{ order.title || '-' }}</td>
|
||
<td class="px-4 py-3">{{ order.order_id || '-' }}</td>
|
||
<td class="px-4 py-3">{{ order.trade_no || '-' }}</td>
|
||
<td class="px-4 py-3">{{ order.order_type || '-' }}</td>
|
||
<td class="px-4 py-3">{{ order.payment_method || '-' }}</td>
|
||
<td class="px-4 py-3">{{ order.description || '-' }}</td>
|
||
<td class="px-4 py-3 text-right">{{ formatCurrency(order.total) }}</td>
|
||
<td class="px-4 py-3">
|
||
<StatusIndicator :status="getStatusProps(order.status)" />
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="flex items-center justify-between p-4">
|
||
<div class="text-sm text-gray-600">
|
||
显示 {{ orders.length }} 条订单,共 {{ totalCount }} 条
|
||
</div>
|
||
<Button
|
||
v-if="hasMoreToLoad"
|
||
@click="loadMore"
|
||
:loading="loadingMore"
|
||
appearance="primary"
|
||
>
|
||
加载更多
|
||
</Button>
|
||
</div>
|
||
</template>
|
||
</Card>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { ref, reactive, computed, onMounted } from 'vue';
|
||
import { Button, Card, Input as FInput, createResource } from 'jingrow-ui';
|
||
import StatusIndicator from '../components/StatusIndicator.vue';
|
||
import { unparse } from 'papaparse';
|
||
|
||
export default {
|
||
name: 'BillingOrders',
|
||
components: {
|
||
Button,
|
||
Card,
|
||
FInput,
|
||
StatusIndicator,
|
||
},
|
||
setup() {
|
||
const pageSize = 20;
|
||
const orders = ref([]);
|
||
const totalCount = ref(0);
|
||
const currentPage = ref(1);
|
||
const initialLoading = ref(true);
|
||
const loadingMore = ref(false);
|
||
|
||
const filters = reactive({
|
||
search: ''
|
||
});
|
||
|
||
// 定义表格列
|
||
const columns = [
|
||
{ key: 'creation', label: '时间', class: '' },
|
||
{ key: 'title', label: '标题', class: '' },
|
||
{ key: 'order_id', label: '订单号', class: '' },
|
||
{ key: 'trade_no', label: '交易号', class: '' },
|
||
{ key: 'order_type', label: '订单类型', class: '' },
|
||
{ key: 'payment_method', label: '支付方式', class: '' },
|
||
{ key: 'description', label: '描述', class: '' },
|
||
{ key: 'total', label: '金额', class: 'text-right' },
|
||
{ key: 'status', label: '状态', class: '' }
|
||
];
|
||
|
||
// 计算是否还有更多数据可加载
|
||
const hasMoreToLoad = computed(() => {
|
||
return orders.value.length < totalCount.value;
|
||
});
|
||
|
||
// 创建获取订单的资源
|
||
const ordersResource = createResource({
|
||
url: 'jcloud.api.billing.get_orders',
|
||
transform(response) {
|
||
console.log('API响应:', response); // 调试日志
|
||
return {
|
||
orders: response.orders || [],
|
||
total: response.total || 0
|
||
};
|
||
},
|
||
onSuccess(data) {
|
||
// 更新总数
|
||
totalCount.value = data.total;
|
||
|
||
if (currentPage.value === 1) {
|
||
// 第一页数据,替换现有数据
|
||
orders.value = data.orders;
|
||
} else {
|
||
// 非第一页,追加数据
|
||
orders.value = [...orders.value, ...data.orders];
|
||
}
|
||
|
||
// 更新加载状态
|
||
initialLoading.value = false;
|
||
loadingMore.value = false;
|
||
|
||
console.log('获取到订单数据:', {
|
||
orders: orders.value,
|
||
total: totalCount.value,
|
||
hasMore: hasMoreToLoad.value
|
||
});
|
||
},
|
||
onError(error) {
|
||
console.error('获取订单数据失败:', error);
|
||
initialLoading.value = false;
|
||
loadingMore.value = false;
|
||
}
|
||
});
|
||
|
||
// 导出资源
|
||
const exportResource = createResource({
|
||
url: 'jcloud.api.billing.get_orders',
|
||
onSuccess(response) {
|
||
const orders = response.orders || [];
|
||
|
||
// 定义CSV字段
|
||
const fields = [
|
||
'标题',
|
||
'订单ID',
|
||
'交易号',
|
||
'订单类型',
|
||
'支付方式',
|
||
'描述',
|
||
'金额',
|
||
'状态',
|
||
'创建时间'
|
||
];
|
||
|
||
// 准备数据
|
||
const csvData = orders.map(order => [
|
||
order.title || '',
|
||
order.order_id || '',
|
||
order.trade_no || '',
|
||
order.order_type || '',
|
||
order.payment_method || '',
|
||
order.description || '',
|
||
order.total || 0,
|
||
order.status || '',
|
||
order.creation || ''
|
||
]);
|
||
|
||
// 添加表头
|
||
csvData.unshift(fields);
|
||
|
||
// 生成CSV
|
||
let csv = unparse(csvData);
|
||
|
||
// 添加BOM以支持中文
|
||
csv = '\uFEFF' + csv;
|
||
|
||
// 触发下载
|
||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
|
||
const today = new Date().toISOString().split('T')[0];
|
||
const filename = `订单记录-${today}.csv`;
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = filename;
|
||
link.click();
|
||
URL.revokeObjectURL(link.href);
|
||
},
|
||
onError(error) {
|
||
console.error('导出数据失败:', error);
|
||
}
|
||
});
|
||
|
||
// 方法:加载初始数据
|
||
function fetchOrders() {
|
||
if (currentPage.value === 1) {
|
||
initialLoading.value = true;
|
||
} else {
|
||
loadingMore.value = true;
|
||
}
|
||
|
||
ordersResource.submit({
|
||
page: currentPage.value,
|
||
page_size: pageSize,
|
||
search: filters.search,
|
||
});
|
||
}
|
||
|
||
// 方法:重置并获取第一页数据
|
||
function resetAndFetch() {
|
||
currentPage.value = 1;
|
||
fetchOrders();
|
||
}
|
||
|
||
// 方法:加载更多数据
|
||
function loadMore() {
|
||
if (loadingMore.value) return;
|
||
|
||
currentPage.value += 1;
|
||
loadingMore.value = true;
|
||
fetchOrders();
|
||
}
|
||
|
||
// 搜索防抖
|
||
let searchTimeout;
|
||
function debouncedSearch() {
|
||
clearTimeout(searchTimeout);
|
||
searchTimeout = setTimeout(() => {
|
||
resetAndFetch();
|
||
}, 300);
|
||
}
|
||
|
||
// 导出为CSV
|
||
function exportToCsv() {
|
||
exportResource.submit({
|
||
page: 1,
|
||
page_size: 1000, // 设置较大的值获取更多数据用于导出
|
||
search: filters.search,
|
||
});
|
||
}
|
||
|
||
// 格式化金额
|
||
function formatCurrency(amount) {
|
||
if (amount === undefined || amount === null) return '-';
|
||
return new Intl.NumberFormat('zh-CN', {
|
||
style: 'currency',
|
||
currency: 'CNY',
|
||
}).format(amount);
|
||
}
|
||
|
||
// 格式化日期
|
||
function formatDate(dateString) {
|
||
if (!dateString) return '-';
|
||
|
||
const date = new Date(dateString);
|
||
if (isNaN(date.getTime())) return '-';
|
||
|
||
// 格式化为 YYYY-MM-DD HH:MM 格式
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const hours = String(date.getHours()).padStart(2, '0');
|
||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||
|
||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||
}
|
||
|
||
// 获取状态样式
|
||
function getStatusProps(status) {
|
||
const statusMap = {
|
||
'待支付': { label: '待支付', color: 'orange' },
|
||
'已支付': { label: '已支付', color: 'green' },
|
||
'交易成功': { label: '交易成功', color: 'green' },
|
||
'已取消': { label: '已取消', color: 'red' },
|
||
'已退款': { label: '已退款', color: 'red' },
|
||
};
|
||
|
||
return statusMap[status] || { label: status, color: 'blue' };
|
||
}
|
||
|
||
// 生命周期钩子
|
||
onMounted(() => {
|
||
fetchOrders();
|
||
});
|
||
|
||
return {
|
||
columns,
|
||
orders,
|
||
totalCount,
|
||
currentPage,
|
||
initialLoading,
|
||
loadingMore,
|
||
hasMoreToLoad,
|
||
filters,
|
||
fetchOrders,
|
||
resetAndFetch,
|
||
loadMore,
|
||
debouncedSearch,
|
||
exportToCsv,
|
||
formatCurrency,
|
||
formatDate,
|
||
getStatusProps
|
||
};
|
||
}
|
||
};
|
||
</script> |