|
|
@@ -12,16 +12,8 @@
|
|
|
<template #prepend>订单号</template>
|
|
|
</el-input>
|
|
|
|
|
|
- <el-date-picker
|
|
|
- v-model="searchForm.dateRange"
|
|
|
- class="filter-item date-picker"
|
|
|
- type="daterange"
|
|
|
- unlink-panels
|
|
|
- start-placeholder="下单开始时间"
|
|
|
- end-placeholder="下单结束时间"
|
|
|
- range-separator="~"
|
|
|
- value-format="yyyy-MM-dd"
|
|
|
- />
|
|
|
+ <el-date-picker v-model="searchForm.dateRange" class="filter-item date-picker" style="width: 100px !important;" type="daterange" unlink-panels
|
|
|
+ start-placeholder="下单开始时间" end-placeholder="下单结束时间" range-separator="~" value-format="YYYY-MM-DD" clearable />
|
|
|
|
|
|
<el-button size="small" type="primary" :icon="Search" @click="handleSearch">查询</el-button>
|
|
|
<el-button size="small" @click="handleReset">重置</el-button>
|
|
|
@@ -29,115 +21,119 @@
|
|
|
|
|
|
<div class="history-tabs">
|
|
|
<el-tabs v-model="activeStatus" @tab-click="handleTabClick">
|
|
|
- <el-tab-pane
|
|
|
- v-for="tab in statusTabs"
|
|
|
- :key="tab.value"
|
|
|
- :label="`${tab.label} (${getOrderCount(tab.value)})`"
|
|
|
- :name="tab.value"
|
|
|
- />
|
|
|
+ <el-tab-pane v-for="tab in statusTabs" :key="tab.value" :label="`${tab.label} (${getOrderCount(tab.value)})`"
|
|
|
+ :name="tab.value" />
|
|
|
</el-tabs>
|
|
|
</div>
|
|
|
|
|
|
- <div v-if="filteredOrders.length" class="order-list">
|
|
|
- <div v-for="order in filteredOrders" :key="order.orderId" class="order-card">
|
|
|
- <div class="order-card__header">
|
|
|
- <div class="header-left">
|
|
|
- <span class="order-id">订单号:{{ order.orderId }}</span>
|
|
|
- </div>
|
|
|
- <div class="header-right">
|
|
|
- <span>下单时间:{{ order.orderTime }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="order-card__body">
|
|
|
- <div class="order-item product-info">
|
|
|
- <div class="item-label">商品</div>
|
|
|
- <div class="item-content">
|
|
|
- <div class="product-name">{{ order.productName }}</div>
|
|
|
+ <div v-loading="loading" class="order-list-wrapper">
|
|
|
+ <div v-if="filteredOrders.length" class="order-list">
|
|
|
+ <div v-for="order in filteredOrders" :key="order.order_no" class="order-card">
|
|
|
+ <div class="order-card__header">
|
|
|
+ <div class="header-left">
|
|
|
+ <span class="order-id">订单号:{{ order.order_no }}</span>
|
|
|
+ <el-tag size="small" :type="order.pay_status_text === '已支付' ? 'success' : 'warning'" class="status-tag">
|
|
|
+ {{ order.pay_status_text }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="header-right">
|
|
|
+ <span class="order-time">下单时间:{{ order.order_time_formatted }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div v-if="order.type === 1" class="order-item">
|
|
|
- <div class="item-label">数量</div>
|
|
|
- <div class="item-value">x{{ order.quantity }}</div>
|
|
|
- </div>
|
|
|
- <div v-if="order.type === 2" class="order-item">
|
|
|
- <div class="item-label">席位</div>
|
|
|
- <div class="item-value">{{ order.quantity }}个</div>
|
|
|
- </div>
|
|
|
+ <div class="order-card__body">
|
|
|
+ <div class="order-item product-info">
|
|
|
+ <div class="item-label">商品名称</div>
|
|
|
+ <div class="item-content">
|
|
|
+ <div class="product-name">{{ order.product_name }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="order-item">
|
|
|
- <div class="item-label">金额</div>
|
|
|
- <div class="item-value amount">¥{{ order.amount }}</div>
|
|
|
- </div>
|
|
|
+ <div class="order-item">
|
|
|
+ <div class="item-label">购买数量</div>
|
|
|
+ <div class="item-value">x{{ order.quantity }}</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="order-item">
|
|
|
- <div class="item-label">支付方式</div>
|
|
|
- <div class="item-value">{{ order.payMethod }}</div>
|
|
|
- </div>
|
|
|
+ <div class="order-item">
|
|
|
+ <div class="item-label">订单金额</div>
|
|
|
+ <div class="item-value amount">{{ order.amount }}</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="order-item">
|
|
|
- <div class="item-label">支付状态</div>
|
|
|
- <el-tag size="small" :type="order.payStatus === '已支付' ? 'success' : 'warning'">
|
|
|
- {{ order.payStatus }}
|
|
|
- </el-tag>
|
|
|
- </div>
|
|
|
+ <div class="order-item">
|
|
|
+ <div class="item-label">支付方式</div>
|
|
|
+ <div class="item-value pay-method">{{ order.pay_method_text }}</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="order-item">
|
|
|
- <div class="item-label">发货方式</div>
|
|
|
- <div class="item-value">自动发货</div>
|
|
|
- </div>
|
|
|
+ <div class="order-item">
|
|
|
+ <div class="item-label">发货方式</div>
|
|
|
+ <div class="item-value">自动开通</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="order-item">
|
|
|
- <div class="item-label">套餐类型</div>
|
|
|
- <div class="item-value">{{ order.type === 1 ? '个人套餐' : '企业套餐' }}</div>
|
|
|
- </div>
|
|
|
+ <div class="order-item">
|
|
|
+ <div class="item-label">付款时间</div>
|
|
|
+ <div class="item-value">{{ order.pay_time_formatted || '-' }}</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="order-item full-width">
|
|
|
- <div class="item-label">商家留言</div>
|
|
|
- <div class="item-value note">
|
|
|
- {{ order.merchantNote }}
|
|
|
+ <div class="order-item full-width">
|
|
|
+ <div class="item-label">商家留言</div>
|
|
|
+ <div class="item-value note">
|
|
|
+ 请前往相对应的网站进行使用工具
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div class="order-item full-width time-info">
|
|
|
- <span>下单时间:{{ order.payTime || '-' }}</span>
|
|
|
- <span>付款时间:{{ order.deliverTime || '-' }}</span>
|
|
|
+ <!-- <div class="order-item full-width time-info">
|
|
|
+ <div class="time-item">
|
|
|
+ <span class="time-label">付款时间:</span>
|
|
|
+ <span class="time-value">{{ order.pay_time_formatted || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <el-empty v-else class="history-empty" description="暂无符合条件的订单" />
|
|
|
+ <el-empty v-else-if="!loading" class="history-empty" description="暂无符合条件的订单" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { computed, reactive, ref } from 'vue'
|
|
|
+import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
import { Search } from '@element-plus/icons-vue'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import { purchaseRecordsSimple } from '/@/api/frontend/me'
|
|
|
|
|
|
defineOptions({ name: 'MePurchaseHistory' })
|
|
|
|
|
|
type OrderRecordStatus = 'paid' | 'unpaid'
|
|
|
type OrderTabStatus = 'all' | OrderRecordStatus
|
|
|
+
|
|
|
+// 新的订单数据结构,匹配接口返回
|
|
|
type Order = {
|
|
|
- orderId: string
|
|
|
- productName: string
|
|
|
- sku: string
|
|
|
+ order_no: string
|
|
|
+ product_name: string
|
|
|
quantity: number
|
|
|
- type: number
|
|
|
- amount: number
|
|
|
- payMethod: string
|
|
|
- payStatus: string
|
|
|
- merchantNote: string
|
|
|
- codes: string[]
|
|
|
- orderTime: string
|
|
|
- payTime: string
|
|
|
- deliverTime: string
|
|
|
- status: OrderRecordStatus
|
|
|
+ amount: string
|
|
|
+ pay_method_text: string
|
|
|
+ pay_status_text: string
|
|
|
+ order_time_formatted: string
|
|
|
+ pay_time_formatted: string
|
|
|
+}
|
|
|
+
|
|
|
+// API 返回的数据结构
|
|
|
+type PurchaseRecordsResponse = {
|
|
|
+ list: Order[]
|
|
|
+ total: number
|
|
|
+ page: number
|
|
|
+ page_size: number
|
|
|
+ stats: {
|
|
|
+ total_orders: number
|
|
|
+ paid_orders: number
|
|
|
+ unpaid_orders: number
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const activeStatus = ref<OrderTabStatus>('all')
|
|
|
+const loading = ref(false)
|
|
|
const searchForm = reactive<{ orderId: string; dateRange: string[] }>({
|
|
|
orderId: '',
|
|
|
dateRange: []
|
|
|
@@ -151,76 +147,103 @@ const statusTabs: { label: string; value: OrderTabStatus }[] = [
|
|
|
{ label: '已支付', value: 'paid' },
|
|
|
{ label: '未支付', value: 'unpaid' }
|
|
|
]
|
|
|
-const orders = ref<Order[]>([
|
|
|
- {
|
|
|
- orderId: '6952512020095823599',
|
|
|
- productName: '望舒·月卡',
|
|
|
- sku: '-',
|
|
|
- quantity: 1,
|
|
|
- type: 1,
|
|
|
- amount: 52,
|
|
|
- payMethod: '支付宝',
|
|
|
- payStatus: '已支付',
|
|
|
- merchantNote: '请前往相对应的网站进行使用工具,具体方法在文档里。激活前永久有效,激活后30天内有效。',
|
|
|
- codes: ['XCCSYVIPJ7LM85R6IIOYXP7S6TO'],
|
|
|
- orderTime: '2025-10-01 12:01:02',
|
|
|
- payTime: '2025-10-01 12:01:02',
|
|
|
- deliverTime: '2025-10-01 12:01:32',
|
|
|
- status: 'paid'
|
|
|
- },
|
|
|
- {
|
|
|
- orderId: '4502508151712325808',
|
|
|
- productName: '清和·季卡',
|
|
|
- sku: '-',
|
|
|
- quantity: 1,
|
|
|
- type: 2,
|
|
|
- amount: 168,
|
|
|
- payMethod: '微信',
|
|
|
- payStatus: '待支付',
|
|
|
- merchantNote: '如有疑问请联系在线客服,超过24小时自动关闭。',
|
|
|
- codes: [],
|
|
|
- orderTime: '2025-09-22 09:36:15',
|
|
|
- payTime: '',
|
|
|
- deliverTime: '',
|
|
|
- status: 'unpaid'
|
|
|
- },
|
|
|
- {
|
|
|
- orderId: '55665111144555',
|
|
|
- productName: '聚德神笔日卡',
|
|
|
- sku: 'JD-001-DAY',
|
|
|
- quantity: 1,
|
|
|
- type: 1,
|
|
|
- amount: 6,
|
|
|
- payMethod: '微信',
|
|
|
- payStatus: '已支付',
|
|
|
- merchantNote: '请前往相对应的网站进行使用工具,具体方法在文档里。激活前永久有效,激活后30天内有效。',
|
|
|
- codes: ['JDSB20251001-0001'],
|
|
|
- orderTime: '2025-10-01 12:01:02',
|
|
|
- payTime: '2025-10-01 12:01:02',
|
|
|
- deliverTime: '2025-10-01 12:01:05',
|
|
|
- status: 'paid'
|
|
|
- }
|
|
|
-])
|
|
|
-
|
|
|
-const filteredOrders = computed(() =>
|
|
|
- orders.value.filter(order => {
|
|
|
- const matchesStatus = activeStatus.value === 'all' ? true : order.status === activeStatus.value
|
|
|
- const orderIdFilter = appliedFilters.orderId
|
|
|
- ? order.orderId.includes(appliedFilters.orderId.trim())
|
|
|
- : true
|
|
|
- const dateFilter = isWithinRange(order.orderTime, appliedFilters.dateRange)
|
|
|
- return matchesStatus && orderIdFilter && dateFilter
|
|
|
- })
|
|
|
-)
|
|
|
+const orders = ref<Order[]>([])
|
|
|
+const stats = ref<PurchaseRecordsResponse['stats']>({
|
|
|
+ total_orders: 0,
|
|
|
+ paid_orders: 0,
|
|
|
+ unpaid_orders: 0
|
|
|
+})
|
|
|
+
|
|
|
+// 分页相关
|
|
|
+const pagination = reactive({
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0
|
|
|
+})
|
|
|
+
|
|
|
+// 加载购买记录
|
|
|
+const loadPurchaseRecords = async () => {
|
|
|
+ try {
|
|
|
+ loading.value = true
|
|
|
+ const res = await purchaseRecordsSimple({})
|
|
|
+ if (res.code === 1 && res.data) {
|
|
|
+ const data = res.data as PurchaseRecordsResponse
|
|
|
+ orders.value = data.list || []
|
|
|
+ stats.value = data.stats || { total_orders: 0, paid_orders: 0, unpaid_orders: 0 }
|
|
|
+ pagination.total = data.total || 0
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '获取购买记录失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载购买记录失败:', error)
|
|
|
+ ElMessage.error('加载购买记录失败,请稍后重试')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 先应用订单号和日期筛选,得到基础筛选结果
|
|
|
+const baseFilteredOrders = computed(() => {
|
|
|
+ let filtered = orders.value
|
|
|
+
|
|
|
+ // 订单号过滤
|
|
|
+ if (appliedFilters.orderId) {
|
|
|
+ const orderId = appliedFilters.orderId.trim()
|
|
|
+ filtered = filtered.filter(order => order.order_no.includes(orderId))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 日期范围过滤
|
|
|
+ if (appliedFilters.dateRange && appliedFilters.dateRange.length === 2) {
|
|
|
+ filtered = filtered.filter(order => {
|
|
|
+ return isWithinRange(order.order_time_formatted, appliedFilters.dateRange)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return filtered
|
|
|
+})
|
|
|
+
|
|
|
+// 再应用状态筛选,得到最终筛选结果
|
|
|
+const filteredOrders = computed(() => {
|
|
|
+ let filtered = baseFilteredOrders.value
|
|
|
+
|
|
|
+ // 状态过滤
|
|
|
+ if (activeStatus.value !== 'all') {
|
|
|
+ if (activeStatus.value === 'paid') {
|
|
|
+ filtered = filtered.filter(order => order.pay_status_text === '已支付')
|
|
|
+ } else if (activeStatus.value === 'unpaid') {
|
|
|
+ filtered = filtered.filter(order => order.pay_status_text === '未支付')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return filtered
|
|
|
+})
|
|
|
+
|
|
|
+// 根据基础筛选结果计算各状态的订单数
|
|
|
+const filteredStats = computed(() => {
|
|
|
+ const base = baseFilteredOrders.value
|
|
|
+ return {
|
|
|
+ total_orders: base.length,
|
|
|
+ paid_orders: base.filter(order => order.pay_status_text === '已支付').length,
|
|
|
+ unpaid_orders: base.filter(order => order.pay_status_text === '未支付').length
|
|
|
+ }
|
|
|
+})
|
|
|
|
|
|
const handleSearch = () => {
|
|
|
- appliedFilters.orderId = searchForm.orderId
|
|
|
- appliedFilters.dateRange = [...searchForm.dateRange]
|
|
|
+ // 更新应用的筛选条件
|
|
|
+ appliedFilters.orderId = searchForm.orderId || ''
|
|
|
+ // 确保日期范围格式正确
|
|
|
+ if (searchForm.dateRange && Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) {
|
|
|
+ appliedFilters.dateRange = [...searchForm.dateRange]
|
|
|
+ } else {
|
|
|
+ appliedFilters.dateRange = []
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const handleReset = () => {
|
|
|
+ // 重置搜索表单
|
|
|
searchForm.orderId = ''
|
|
|
searchForm.dateRange = []
|
|
|
+ // 重置应用的筛选条件
|
|
|
appliedFilters.orderId = ''
|
|
|
appliedFilters.dateRange = []
|
|
|
}
|
|
|
@@ -230,18 +253,36 @@ const handleTabClick = (tab: { paneName?: string | number; name?: string | numbe
|
|
|
}
|
|
|
|
|
|
const getOrderCount = (status: OrderTabStatus) => {
|
|
|
- if (status === 'all') return orders.value.length
|
|
|
- return orders.value.filter(order => order.status === status).length
|
|
|
+ // 使用筛选后的统计结果
|
|
|
+ if (status === 'all') return filteredStats.value.total_orders || 0
|
|
|
+ if (status === 'paid') return filteredStats.value.paid_orders || 0
|
|
|
+ if (status === 'unpaid') return filteredStats.value.unpaid_orders || 0
|
|
|
+ return 0
|
|
|
}
|
|
|
|
|
|
const isWithinRange = (orderTime: string, range: string[]) => {
|
|
|
- if (!range || range.length !== 2) return true
|
|
|
+ if (!range || range.length !== 2 || !range[0] || !range[1]) return true
|
|
|
const [start, end] = range
|
|
|
- const startDate = new Date(`${start} 00:00:00`)
|
|
|
- const endDate = new Date(`${end} 23:59:59`)
|
|
|
+
|
|
|
+ // 确保日期格式正确,处理 YYYY-MM-DD 格式
|
|
|
+ const startDate = new Date(start + ' 00:00:00')
|
|
|
+ const endDate = new Date(end + ' 23:59:59')
|
|
|
+
|
|
|
+ // 订单时间格式通常是 "2025-06-11 15:53:35"
|
|
|
const targetDate = new Date(orderTime.replace(/-/g, '/'))
|
|
|
+
|
|
|
+ // 检查日期是否有效
|
|
|
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime()) || isNaN(targetDate.getTime())) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
return targetDate >= startDate && targetDate <= endDate
|
|
|
}
|
|
|
+
|
|
|
+// 初始化加载数据
|
|
|
+onMounted(() => {
|
|
|
+ loadPurchaseRecords()
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
@@ -280,6 +321,7 @@ const isWithinRange = (orderTime: string, range: string[]) => {
|
|
|
.history-filters {
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
gap: 12px;
|
|
|
padding: 16px;
|
|
|
background: linear-gradient(135deg, #ecf5ff 0%, #f5f9ff 100%);
|
|
|
@@ -320,6 +362,10 @@ const isWithinRange = (orderTime: string, range: string[]) => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+.order-list-wrapper {
|
|
|
+ min-height: 200px;
|
|
|
+}
|
|
|
+
|
|
|
.order-list {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
@@ -343,10 +389,29 @@ const isWithinRange = (orderTime: string, range: string[]) => {
|
|
|
background: linear-gradient(135deg, #ecf5ff, #ffffff);
|
|
|
}
|
|
|
|
|
|
+ .header-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
.order-id {
|
|
|
font-weight: 600;
|
|
|
- margin-right: 12px;
|
|
|
color: #1f2d3d;
|
|
|
+ font-size: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-tag {
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-right {
|
|
|
+ color: #606266;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .order-time {
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
.order-card__body {
|
|
|
@@ -391,6 +456,11 @@ const isWithinRange = (orderTime: string, range: string[]) => {
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
+ .pay-method {
|
|
|
+ color: #606266;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
.note {
|
|
|
padding: 10px;
|
|
|
background: #f0f5ff;
|
|
|
@@ -421,11 +491,26 @@ const isWithinRange = (orderTime: string, range: string[]) => {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
flex-wrap: wrap;
|
|
|
- gap: 10px;
|
|
|
- font-size: 13px;
|
|
|
- color: #606266;
|
|
|
- padding-top: 10px;
|
|
|
+ gap: 16px;
|
|
|
+ padding-top: 16px;
|
|
|
border-top: 1px dashed #d9e2f0;
|
|
|
+ margin-top: 4px;
|
|
|
+
|
|
|
+ .time-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-label {
|
|
|
+ color: #909399;
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-value {
|
|
|
+ color: #606266;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.history-empty {
|
|
|
@@ -462,4 +547,3 @@ const isWithinRange = (orderTime: string, range: string[]) => {
|
|
|
}
|
|
|
}
|
|
|
</style>
|
|
|
-
|