jcloud/dashboard/src2/pages/BillingOrders.vue
2025-04-12 17:39:38 +08:00

348 lines
9.9 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>
<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>