|
@@ -2,14 +2,23 @@
|
|
|
<div class="my-orders">
|
|
<div class="my-orders">
|
|
|
<h1 class="page-title">我的订单</h1>
|
|
<h1 class="page-title">我的订单</h1>
|
|
|
|
|
|
|
|
- <el-tabs v-model="activeTab" @tab-change="loadOrders">
|
|
|
|
|
|
|
+ <el-tabs v-model="activeTab" @tab-change="onTabChange">
|
|
|
<el-tab-pane label="我买到的" name="bought" />
|
|
<el-tab-pane label="我买到的" name="bought" />
|
|
|
<el-tab-pane label="我卖出的" name="sold" />
|
|
<el-tab-pane label="我卖出的" name="sold" />
|
|
|
</el-tabs>
|
|
</el-tabs>
|
|
|
|
|
|
|
|
|
|
+ <el-radio-group v-model="statusFilter" size="small" class="status-filter" @change="onFilterChange">
|
|
|
|
|
+ <el-radio-button label="">全部</el-radio-button>
|
|
|
|
|
+ <el-radio-button label="PENDING">待接单</el-radio-button>
|
|
|
|
|
+ <el-radio-button label="CONFIRMED">待面交</el-radio-button>
|
|
|
|
|
+ <el-radio-button label="DELIVERED">待收货</el-radio-button>
|
|
|
|
|
+ <el-radio-button label="COMPLETED">已完成</el-radio-button>
|
|
|
|
|
+ <el-radio-button label="CANCELLED">已取消</el-radio-button>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+
|
|
|
<div v-loading="loading">
|
|
<div v-loading="loading">
|
|
|
- <div v-for="order in orderList" :key="order.id" class="order-row">
|
|
|
|
|
- <el-card shadow="hover" class="order-card" @click="router.push(`/order/${order.id}`)">
|
|
|
|
|
|
|
+ <div v-for="order in paginatedOrders" :key="order.id" class="order-row">
|
|
|
|
|
+ <el-card shadow="hover" class="order-card" @click="goToDetail(order)">
|
|
|
<div class="order-content">
|
|
<div class="order-content">
|
|
|
<el-image
|
|
<el-image
|
|
|
:src="order.itemImage"
|
|
:src="order.itemImage"
|
|
@@ -28,18 +37,42 @@
|
|
|
<span class="order-trader">
|
|
<span class="order-trader">
|
|
|
{{ activeTab === 'bought' ? '卖家:' + order.sellerNickname : '买家:' + order.buyerNickname }}
|
|
{{ activeTab === 'bought' ? '卖家:' + order.sellerNickname : '买家:' + order.buyerNickname }}
|
|
|
</span>
|
|
</span>
|
|
|
- </div>
|
|
|
|
|
- <div class="order-status">
|
|
|
|
|
- <el-tag :type="statusTagType(order.status)" size="large">
|
|
|
|
|
|
|
+ <el-tag :type="statusTagType(order.status)" size="small" class="order-tag">
|
|
|
{{ statusLabel(order.status) }}
|
|
{{ statusLabel(order.status) }}
|
|
|
</el-tag>
|
|
</el-tag>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="order-actions" @click.stop>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="activeTab === 'sold' && order.status === 'PENDING'"
|
|
|
|
|
+ size="small" type="primary" @click="handleAccept(order)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 接单
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="activeTab === 'sold' && order.status === 'PENDING'"
|
|
|
|
|
+ size="small" type="danger" @click="handleReject(order)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 拒单
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="activeTab === 'sold' && order.status === 'CONFIRMED'"
|
|
|
|
|
+ size="small" type="primary" @click="handleDeliver(order)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 确认交货
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="activeTab === 'bought' && order.status === 'DELIVERED'"
|
|
|
|
|
+ size="small" type="success" @click="handleConfirm(order)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 确认收货
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</el-card>
|
|
</el-card>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div v-if="!loading && orderList.length === 0" class="empty-state">
|
|
|
|
|
|
|
+ <div v-if="!loading && filteredOrders.length === 0" class="empty-state">
|
|
|
<el-empty description="暂无订单" />
|
|
<el-empty description="暂无订单" />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -50,25 +83,37 @@
|
|
|
:page-size="pageSize"
|
|
:page-size="pageSize"
|
|
|
layout="prev, pager, next"
|
|
layout="prev, pager, next"
|
|
|
class="pagination"
|
|
class="pagination"
|
|
|
- @current-change="loadOrders"
|
|
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref } from 'vue'
|
|
|
|
|
|
|
+import { ref, computed, onMounted } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import { Picture } from '@element-plus/icons-vue'
|
|
import { Picture } from '@element-plus/icons-vue'
|
|
|
import { getOrders } from '../api/endpoints/orders'
|
|
import { getOrders } from '../api/endpoints/orders'
|
|
|
import type { OrderVO } from '../api/models'
|
|
import type { OrderVO } from '../api/models'
|
|
|
|
|
|
|
|
const router = useRouter()
|
|
const router = useRouter()
|
|
|
const activeTab = ref('bought')
|
|
const activeTab = ref('bought')
|
|
|
-const orderList = ref<OrderVO[]>([])
|
|
|
|
|
-const total = ref(0)
|
|
|
|
|
|
|
+const allOrders = ref<OrderVO[]>([])
|
|
|
|
|
+const loading = ref(false)
|
|
|
const pageNum = ref(1)
|
|
const pageNum = ref(1)
|
|
|
const pageSize = 10
|
|
const pageSize = 10
|
|
|
-const loading = ref(false)
|
|
|
|
|
|
|
+const statusFilter = ref('')
|
|
|
|
|
+
|
|
|
|
|
+const filteredOrders = computed(() => {
|
|
|
|
|
+ if (!statusFilter.value) return allOrders.value
|
|
|
|
|
+ return allOrders.value.filter((o) => o.status === statusFilter.value)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const total = computed(() => filteredOrders.value.length)
|
|
|
|
|
+
|
|
|
|
|
+const paginatedOrders = computed(() => {
|
|
|
|
|
+ const start = (pageNum.value - 1) * pageSize
|
|
|
|
|
+ return filteredOrders.value.slice(start, start + pageSize)
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
const statusLabel = (status?: string) => {
|
|
const statusLabel = (status?: string) => {
|
|
|
const map: Record<string, string> = {
|
|
const map: Record<string, string> = {
|
|
@@ -92,22 +137,94 @@ const statusTagType = (status?: string) => {
|
|
|
return map[status ?? ''] || 'info'
|
|
return map[status ?? ''] || 'info'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const onTabChange = () => {
|
|
|
|
|
+ pageNum.value = 1
|
|
|
|
|
+ statusFilter.value = ''
|
|
|
|
|
+ loadOrders()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const onFilterChange = () => {
|
|
|
|
|
+ pageNum.value = 1
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const loadOrders = async () => {
|
|
const loadOrders = async () => {
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
try {
|
|
try {
|
|
|
const api = getOrders()
|
|
const api = getOrders()
|
|
|
- const params = { pageNum: pageNum.value, pageSize }
|
|
|
|
|
|
|
+ const params = { pageNum: 1, pageSize: 100 }
|
|
|
const res = activeTab.value === 'bought'
|
|
const res = activeTab.value === 'bought'
|
|
|
- ? await api.bought(params)
|
|
|
|
|
- : await api.sold(params)
|
|
|
|
|
|
|
+ ? await api.listBoughtOrders(params)
|
|
|
|
|
+ : await api.listSoldOrders(params)
|
|
|
if (res.code === 200) {
|
|
if (res.code === 200) {
|
|
|
- orderList.value = res.data?.list ?? []
|
|
|
|
|
- total.value = res.data?.total ?? 0
|
|
|
|
|
|
|
+ allOrders.value = res.data?.list ?? []
|
|
|
}
|
|
}
|
|
|
} finally {
|
|
} finally {
|
|
|
loading.value = false
|
|
loading.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+const goToDetail = (order: OrderVO) => {
|
|
|
|
|
+ router.push({
|
|
|
|
|
+ path: `/order/${order.id}`,
|
|
|
|
|
+ state: { orderData: JSON.parse(JSON.stringify(order)) },
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleAccept = async (order: OrderVO) => {
|
|
|
|
|
+ if (!order.id) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getOrders().acceptOrder(order.id)
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ ElMessage.success('已接单')
|
|
|
|
|
+ loadOrders()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err: unknown) {
|
|
|
|
|
+ if (err && typeof err === 'object' && 'message' in err) {
|
|
|
|
|
+ ElMessage.error((err as { message: string }).message)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleReject = async (order: OrderVO) => {
|
|
|
|
|
+ if (!order.id) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.confirm('确定拒绝该订单?', '确认')
|
|
|
|
|
+ const res = await getOrders().rejectOrder(order.id)
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ ElMessage.success('已拒单')
|
|
|
|
|
+ loadOrders()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch { /* cancelled */ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleDeliver = async (order: OrderVO) => {
|
|
|
|
|
+ if (!order.id) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getOrders().deliverOrder(order.id)
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ ElMessage.success('已确认交货,等待买家收货')
|
|
|
|
|
+ loadOrders()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err: unknown) {
|
|
|
|
|
+ if (err && typeof err === 'object' && 'message' in err) {
|
|
|
|
|
+ ElMessage.error((err as { message: string }).message)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleConfirm = async (order: OrderVO) => {
|
|
|
|
|
+ if (!order.id) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.confirm('确定已收到物品?', '确认收货')
|
|
|
|
|
+ const res = await getOrders().confirmOrder(order.id)
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ ElMessage.success('交易完成')
|
|
|
|
|
+ loadOrders()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch { /* cancelled */ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(loadOrders)
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
@@ -120,8 +237,16 @@ const loadOrders = async () => {
|
|
|
font-size: 24px;
|
|
font-size: 24px;
|
|
|
margin-bottom: 20px;
|
|
margin-bottom: 20px;
|
|
|
}
|
|
}
|
|
|
|
|
+.status-filter {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
.order-row {
|
|
.order-row {
|
|
|
margin-bottom: 12px;
|
|
margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.order-card {
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
}
|
|
}
|
|
|
.order-content {
|
|
.order-content {
|
|
@@ -147,6 +272,7 @@ const loadOrders = async () => {
|
|
|
}
|
|
}
|
|
|
.order-info {
|
|
.order-info {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
}
|
|
}
|
|
|
.order-title {
|
|
.order-title {
|
|
|
margin: 0 0 4px;
|
|
margin: 0 0 4px;
|
|
@@ -164,8 +290,14 @@ const loadOrders = async () => {
|
|
|
display: block;
|
|
display: block;
|
|
|
margin-top: 4px;
|
|
margin-top: 4px;
|
|
|
}
|
|
}
|
|
|
-.order-status {
|
|
|
|
|
|
|
+.order-tag {
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+.order-actions {
|
|
|
flex-shrink: 0;
|
|
flex-shrink: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 6px;
|
|
|
}
|
|
}
|
|
|
.empty-state {
|
|
.empty-state {
|
|
|
padding: 40px 0;
|
|
padding: 40px 0;
|