Commit 9542b035a3a028179672213abd9e5e21326046be

Authored by shaofan
1 parent 4d16c20f

Refactor: 优化订单管理和配送订单列表分页功能,新增状态筛选卡片和交互逻辑,提升用户体验。

src/views/delivery/DeliveryOrderList.vue
@@ -6,18 +6,25 @@ @@ -6,18 +6,25 @@
6 <a-select v-if="isAdmin" v-model:value="filterCityId" placeholder="租户" allowClear class="list-filter" @change="handleAdminCityChange"> 6 <a-select v-if="isAdmin" v-model:value="filterCityId" placeholder="租户" allowClear class="list-filter" @change="handleAdminCityChange">
7 <a-select-option v-for="item in cityList" :key="item.id" :value="item.id">{{ item.name }}</a-select-option> 7 <a-select-option v-for="item in cityList" :key="item.id" :value="item.id">{{ item.name }}</a-select-option>
8 </a-select> 8 </a-select>
9 - <a-input v-if="isAdmin" v-model:value="filterAppKey" placeholder="接入方 AppKey" class="list-search appkey-search" @pressEnter="loadList" />  
10 - <a-select v-model:value="filterStatus" placeholder="状态" allowClear class="list-filter" @change="loadList"> 9 + <a-input v-if="isAdmin" v-model:value="filterAppKey" placeholder="接入方 AppKey" class="list-search appkey-search" @pressEnter="handleAppKeySearch" />
  10 + <a-select v-model:value="filterStatus" placeholder="状态" allowClear class="list-filter" @change="handleStatusChange">
11 <a-select-option :value="2">待接单</a-select-option> 11 <a-select-option :value="2">待接单</a-select-option>
12 <a-select-option :value="3">已接单</a-select-option> 12 <a-select-option :value="3">已接单</a-select-option>
13 <a-select-option :value="4">配送中</a-select-option> 13 <a-select-option :value="4">配送中</a-select-option>
14 <a-select-option :value="6">已完成</a-select-option> 14 <a-select-option :value="6">已完成</a-select-option>
15 <a-select-option :value="10">已取消</a-select-option> 15 <a-select-option :value="10">已取消</a-select-option>
16 </a-select> 16 </a-select>
17 - <a-input-search v-model:value="keyword" placeholder="外部订单号" @search="loadList" class="list-search" /> 17 + <a-input-search v-model:value="keyword" placeholder="外部订单号" @search="handleKeywordSearch" class="list-search" />
18 </div> 18 </div>
19 </div> 19 </div>
20 - <a-table :dataSource="list" :columns="columns" :loading="loading" rowKey="id" :pagination="false"> 20 + <a-table
  21 + :dataSource="list"
  22 + :columns="columns"
  23 + :loading="loading"
  24 + rowKey="id"
  25 + :pagination="pagination"
  26 + @change="handleTableChange"
  27 + >
21 <template #bodyCell="{ column, record }"> 28 <template #bodyCell="{ column, record }">
22 <template v-if="column.key === 'cityName'"> 29 <template v-if="column.key === 'cityName'">
23 {{ getCityName(record.cityId) }} 30 {{ getCityName(record.cityId) }}
@@ -61,12 +68,18 @@ @@ -61,12 +68,18 @@
61 <script setup lang="ts"> 68 <script setup lang="ts">
62 import { computed, ref, onMounted } from 'vue' 69 import { computed, ref, onMounted } from 'vue'
63 import { message } from 'ant-design-vue' 70 import { message } from 'ant-design-vue'
  71 +import type { TablePaginationConfig } from 'ant-design-vue'
64 import { riderApi } from '@/api' 72 import { riderApi } from '@/api'
65 import { useRoleCityList } from '@/composables/useRoleCityList' 73 import { useRoleCityList } from '@/composables/useRoleCityList'
66 74
  75 +const PAGE_SIZE = 20
  76 +
67 const loading = ref(false) 77 const loading = ref(false)
68 const detailLoading = ref(false) 78 const detailLoading = ref(false)
69 const list = ref<any[]>([]) 79 const list = ref<any[]>([])
  80 +const total = ref(0)
  81 +const currentPage = ref(1)
  82 +const pageSize = ref(PAGE_SIZE)
70 const filterCityId = ref<number | undefined>() 83 const filterCityId = ref<number | undefined>()
71 const filterAppKey = ref('') 84 const filterAppKey = ref('')
72 const filterStatus = ref<number | undefined>() 85 const filterStatus = ref<number | undefined>()
@@ -104,6 +117,42 @@ const columns = computed(() =&gt; { @@ -104,6 +117,42 @@ const columns = computed(() =&gt; {
104 ] 117 ]
105 }) 118 })
106 119
  120 +const pagination = computed<TablePaginationConfig>(() => ({
  121 + current: currentPage.value,
  122 + pageSize: pageSize.value,
  123 + total: total.value,
  124 + showSizeChanger: false,
  125 + showTotal: (count) => `共 ${count} 条`,
  126 +}))
  127 +
  128 +function resetToFirstPage() {
  129 + currentPage.value = 1
  130 +}
  131 +
  132 +function handleStatusChange() {
  133 + resetToFirstPage()
  134 + loadList()
  135 +}
  136 +
  137 +function handleKeywordSearch() {
  138 + resetToFirstPage()
  139 + loadList()
  140 +}
  141 +
  142 +function handleAppKeySearch() {
  143 + resetToFirstPage()
  144 + loadList()
  145 +}
  146 +
  147 +function handleTableChange(page: TablePaginationConfig) {
  148 + const nextPage = page.current ?? 1
  149 + if (nextPage === currentPage.value) {
  150 + return
  151 + }
  152 + currentPage.value = nextPage
  153 + loadList()
  154 +}
  155 +
107 async function loadList() { 156 async function loadList() {
108 loading.value = true 157 loading.value = true
109 try { 158 try {
@@ -112,9 +161,17 @@ async function loadList() { @@ -112,9 +161,17 @@ async function loadList() {
112 appKey: filterAppKey.value || undefined, 161 appKey: filterAppKey.value || undefined,
113 status: filterStatus.value, 162 status: filterStatus.value,
114 outOrderNo: keyword.value || undefined, 163 outOrderNo: keyword.value || undefined,
115 - page: 1 164 + page: currentPage.value
116 }) 165 })
117 - list.value = Array.isArray(res?.data) ? res.data : [] 166 + const data = res?.data ?? {}
  167 + list.value = Array.isArray(data.list) ? data.list : []
  168 + total.value = Number(data.total ?? 0)
  169 + currentPage.value = Number(data.page ?? currentPage.value)
  170 + pageSize.value = Number(data.pageSize ?? PAGE_SIZE)
  171 + if (!list.value.length && total.value > 0 && currentPage.value > 1) {
  172 + currentPage.value -= 1
  173 + await loadList()
  174 + }
118 } finally { loading.value = false } 175 } finally { loading.value = false }
119 } 176 }
120 177
@@ -123,6 +180,7 @@ function handleAdminCityChange() { @@ -123,6 +180,7 @@ function handleAdminCityChange() {
123 queryVisible.value = false 180 queryVisible.value = false
124 queryResult.value = null 181 queryResult.value = null
125 } 182 }
  183 + resetToFirstPage()
126 loadList() 184 loadList()
127 } 185 }
128 186
@@ -143,7 +201,7 @@ async function cancelOrder(record: any) { @@ -143,7 +201,7 @@ async function cancelOrder(record: any) {
143 queryVisible.value = false 201 queryVisible.value = false
144 queryResult.value = null 202 queryResult.value = null
145 } 203 }
146 - loadList() 204 + await loadList()
147 } 205 }
148 206
149 onMounted(async () => { 207 onMounted(async () => {
src/views/order/OrderList.vue
1 <template> 1 <template>
2 <div> 2 <div>
3 <a-card title="订单管理" :bordered="false" class="list-table-card"> 3 <a-card title="订单管理" :bordered="false" class="list-table-card">
  4 + <div class="order-status-cards">
  5 + <button
  6 + v-for="item in statusCardItems"
  7 + :key="item.key"
  8 + type="button"
  9 + class="order-status-card"
  10 + :class="{ 'is-active': isStatusCardActive(item.value) }"
  11 + @click="handleStatusCardClick(item.value)"
  12 + >
  13 + <span class="order-status-card__label">{{ item.label }}</span>
  14 + <strong class="order-status-card__count">{{ statusSummary[item.key] ?? 0 }}</strong>
  15 + </button>
  16 + </div>
4 <div class="list-toolbar"> 17 <div class="list-toolbar">
5 <div class="list-toolbar-left"> 18 <div class="list-toolbar-left">
6 <a-select v-if="isAdmin" v-model:value="filterCityId" placeholder="租户" allowClear class="list-filter" @change="handleAdminCityChange"> 19 <a-select v-if="isAdmin" v-model:value="filterCityId" placeholder="租户" allowClear class="list-filter" @change="handleAdminCityChange">
7 <a-select-option v-for="item in cityList" :key="item.id" :value="item.id">{{ item.name }}</a-select-option> 20 <a-select-option v-for="item in cityList" :key="item.id" :value="item.id">{{ item.name }}</a-select-option>
8 </a-select> 21 </a-select>
9 - <a-select v-model:value="filterStatus" placeholder="订单状态" allowClear class="list-filter" @change="loadList"> 22 + <a-select v-model:value="filterStatus" placeholder="订单状态" allowClear class="list-filter" @change="handleStatusChange">
10 <a-select-option :value="2">已支付</a-select-option> 23 <a-select-option :value="2">已支付</a-select-option>
11 <a-select-option :value="3">已接单</a-select-option> 24 <a-select-option :value="3">已接单</a-select-option>
12 <a-select-option :value="4">服务中</a-select-option> 25 <a-select-option :value="4">服务中</a-select-option>
@@ -14,15 +27,22 @@ @@ -14,15 +27,22 @@
14 <a-select-option :value="7">退款申请</a-select-option> 27 <a-select-option :value="7">退款申请</a-select-option>
15 <a-select-option :value="10">已取消</a-select-option> 28 <a-select-option :value="10">已取消</a-select-option>
16 </a-select> 29 </a-select>
17 - <a-select v-model:value="filterTrans" placeholder="转单状态" allowClear class="list-filter" @change="loadList"> 30 + <a-select v-model:value="filterTrans" placeholder="转单状态" allowClear class="list-filter" @change="handleTransChange">
18 <a-select-option :value="2">转单申请中</a-select-option> 31 <a-select-option :value="2">转单申请中</a-select-option>
19 <a-select-option :value="1">已转单</a-select-option> 32 <a-select-option :value="1">已转单</a-select-option>
20 <a-select-option :value="3">转单拒绝</a-select-option> 33 <a-select-option :value="3">转单拒绝</a-select-option>
21 </a-select> 34 </a-select>
22 - <a-input-search v-model:value="keyword" placeholder="订单号" @search="loadList" class="list-search" /> 35 + <a-input-search v-model:value="keyword" placeholder="订单号" @search="handleKeywordSearch" class="list-search" />
23 </div> 36 </div>
24 </div> 37 </div>
25 - <a-table :dataSource="list" :columns="columns" :loading="loading" rowKey="id" :pagination="false"> 38 + <a-table
  39 + :dataSource="list"
  40 + :columns="columns"
  41 + :loading="loading"
  42 + rowKey="id"
  43 + :pagination="pagination"
  44 + @change="handleTableChange"
  45 + >
26 <template #bodyCell="{ column, record }"> 46 <template #bodyCell="{ column, record }">
27 <template v-if="column.key === 'cityName'"> 47 <template v-if="column.key === 'cityName'">
28 {{ cityList.find(item => item.id === record.cityId)?.name || `租户#${record.cityId}` }} 48 {{ cityList.find(item => item.id === record.cityId)?.name || `租户#${record.cityId}` }}
@@ -129,12 +149,28 @@ @@ -129,12 +149,28 @@
129 <script setup lang="ts"> 149 <script setup lang="ts">
130 import { computed, ref, onMounted } from 'vue' 150 import { computed, ref, onMounted } from 'vue'
131 import { message } from 'ant-design-vue' 151 import { message } from 'ant-design-vue'
  152 +import type { TablePaginationConfig } from 'ant-design-vue'
132 import { riderApi, refundApi } from '@/api' 153 import { riderApi, refundApi } from '@/api'
133 import { useRoleCityList } from '@/composables/useRoleCityList' 154 import { useRoleCityList } from '@/composables/useRoleCityList'
134 155
  156 +const PAGE_SIZE = 20
  157 +const STATUS_CARD_ITEMS = [
  158 + { key: 'all', label: '全部', value: undefined },
  159 + { key: '2', label: '已支付', value: 2 },
  160 + { key: '3', label: '已接单', value: 3 },
  161 + { key: '4', label: '服务中', value: 4 },
  162 + { key: '6', label: '已完成', value: 6 },
  163 + { key: '7', label: '退款申请', value: 7 },
  164 + { key: '10', label: '已取消', value: 10 },
  165 +] as const
  166 +
135 const loading = ref(false) 167 const loading = ref(false)
136 const saving = ref(false) 168 const saving = ref(false)
137 const list = ref<any[]>([]) 169 const list = ref<any[]>([])
  170 +const total = ref(0)
  171 +const currentPage = ref(1)
  172 +const pageSize = ref(PAGE_SIZE)
  173 +const statusSummary = ref<Record<string, number>>({ all: 0, '2': 0, '3': 0, '4': 0, '6': 0, '7': 0, '10': 0 })
138 const filterCityId = ref<number | undefined>() 174 const filterCityId = ref<number | undefined>()
139 const filterStatus = ref<number | undefined>() 175 const filterStatus = ref<number | undefined>()
140 const filterTrans = ref<number | undefined>() 176 const filterTrans = ref<number | undefined>()
@@ -151,6 +187,7 @@ const refundRecord = ref&lt;any&gt;(null) @@ -151,6 +187,7 @@ const refundRecord = ref&lt;any&gt;(null)
151 const currentRefundRecordId = ref(0) 187 const currentRefundRecordId = ref(0)
152 const { isAdmin, cityList, loadCities } = useRoleCityList() 188 const { isAdmin, cityList, loadCities } = useRoleCityList()
153 const canOperate = computed(() => !isAdmin.value || !!filterCityId.value) 189 const canOperate = computed(() => !isAdmin.value || !!filterCityId.value)
  190 +const statusCardItems = STATUS_CARD_ITEMS
154 191
155 const statusMap: Record<number, string> = { 192 const statusMap: Record<number, string> = {
156 1: '待支付', 2: '已支付', 3: '已接单', 4: '服务中', 193 1: '待支付', 2: '已支付', 3: '已接单', 4: '服务中',
@@ -180,6 +217,52 @@ const columns = computed(() =&gt; { @@ -180,6 +217,52 @@ const columns = computed(() =&gt; {
180 ] 217 ]
181 }) 218 })
182 219
  220 +const pagination = computed<TablePaginationConfig>(() => ({
  221 + current: currentPage.value,
  222 + pageSize: pageSize.value,
  223 + total: total.value,
  224 + showSizeChanger: false,
  225 + showTotal: (count) => `共 ${count} 条`,
  226 +}))
  227 +
  228 +function resetToFirstPage() {
  229 + currentPage.value = 1
  230 +}
  231 +
  232 +function isStatusCardActive(value: number | undefined) {
  233 + return value === undefined ? filterStatus.value === undefined : filterStatus.value === value
  234 +}
  235 +
  236 +function handleStatusCardClick(value: number | undefined) {
  237 + filterStatus.value = value
  238 + resetToFirstPage()
  239 + loadList()
  240 +}
  241 +
  242 +function handleStatusChange() {
  243 + resetToFirstPage()
  244 + loadList()
  245 +}
  246 +
  247 +function handleTransChange() {
  248 + resetToFirstPage()
  249 + loadList()
  250 +}
  251 +
  252 +function handleKeywordSearch() {
  253 + resetToFirstPage()
  254 + loadList()
  255 +}
  256 +
  257 +function handleTableChange(page: TablePaginationConfig) {
  258 + const nextPage = page.current ?? 1
  259 + if (nextPage === currentPage.value) {
  260 + return
  261 + }
  262 + currentPage.value = nextPage
  263 + loadList()
  264 +}
  265 +
183 async function loadList() { 266 async function loadList() {
184 loading.value = true 267 loading.value = true
185 try { 268 try {
@@ -188,9 +271,26 @@ async function loadList() { @@ -188,9 +271,26 @@ async function loadList() {
188 status: filterStatus.value, 271 status: filterStatus.value,
189 isTrans: filterTrans.value, 272 isTrans: filterTrans.value,
190 keyword: keyword.value, 273 keyword: keyword.value,
191 - page: 1 274 + page: currentPage.value
192 }) 275 })
193 - list.value = Array.isArray(res?.data) ? res.data : [] 276 + const data = res?.data ?? {}
  277 + list.value = Array.isArray(data.list) ? data.list : []
  278 + total.value = Number(data.total ?? 0)
  279 + currentPage.value = Number(data.page ?? currentPage.value)
  280 + pageSize.value = Number(data.pageSize ?? PAGE_SIZE)
  281 + statusSummary.value = {
  282 + all: Number(data.statusSummary?.all ?? 0),
  283 + '2': Number(data.statusSummary?.['2'] ?? 0),
  284 + '3': Number(data.statusSummary?.['3'] ?? 0),
  285 + '4': Number(data.statusSummary?.['4'] ?? 0),
  286 + '6': Number(data.statusSummary?.['6'] ?? 0),
  287 + '7': Number(data.statusSummary?.['7'] ?? 0),
  288 + '10': Number(data.statusSummary?.['10'] ?? 0),
  289 + }
  290 + if (!list.value.length && total.value > 0 && currentPage.value > 1) {
  291 + currentPage.value -= 1
  292 + await loadList()
  293 + }
194 } finally { loading.value = false } 294 } finally { loading.value = false }
195 } 295 }
196 296
@@ -200,6 +300,7 @@ function handleAdminCityChange() { @@ -200,6 +300,7 @@ function handleAdminCityChange() {
200 refundVisible.value = false 300 refundVisible.value = false
201 rejectVisible.value = false 301 rejectVisible.value = false
202 } 302 }
  303 + resetToFirstPage()
203 loadList() 304 loadList()
204 } 305 }
205 306
@@ -228,14 +329,14 @@ async function handleDesignate() { @@ -228,14 +329,14 @@ async function handleDesignate() {
228 await riderApi.designate(designateOrderId.value, designateRiderId.value) 329 await riderApi.designate(designateOrderId.value, designateRiderId.value)
229 message.success('指派成功') 330 message.success('指派成功')
230 designateVisible.value = false 331 designateVisible.value = false
231 - loadList() 332 + await loadList()
232 } finally { saving.value = false } 333 } finally { saving.value = false }
233 } 334 }
234 335
235 async function handleTrans(orderId: number, trans: number) { 336 async function handleTrans(orderId: number, trans: number) {
236 await riderApi.setTrans(orderId, trans) 337 await riderApi.setTrans(orderId, trans)
237 message.success('操作成功') 338 message.success('操作成功')
238 - loadList() 339 + await loadList()
239 } 340 }
240 341
241 async function openRefund(record: any) { 342 async function openRefund(record: any) {
@@ -257,7 +358,7 @@ async function handleRefund(status: number) { @@ -257,7 +358,7 @@ async function handleRefund(status: number) {
257 message.success('操作成功') 358 message.success('操作成功')
258 refundVisible.value = false 359 refundVisible.value = false
259 rejectVisible.value = false 360 rejectVisible.value = false
260 - loadList() 361 + await loadList()
261 } finally { saving.value = false } 362 } finally { saving.value = false }
262 } 363 }
263 364
@@ -266,3 +367,48 @@ onMounted(async () =&gt; { @@ -266,3 +367,48 @@ onMounted(async () =&gt; {
266 await loadList() 367 await loadList()
267 }) 368 })
268 </script> 369 </script>
  370 +
  371 +<style scoped>
  372 +.order-status-cards {
  373 + display: grid;
  374 + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  375 + gap: 12px;
  376 + margin-bottom: 16px;
  377 +}
  378 +
  379 +.order-status-card {
  380 + display: flex;
  381 + flex-direction: column;
  382 + align-items: flex-start;
  383 + gap: 8px;
  384 + min-height: 88px;
  385 + padding: 16px;
  386 + border: 1px solid #e5e7eb;
  387 + border-radius: 12px;
  388 + background: #fff;
  389 + cursor: pointer;
  390 + transition: all 0.2s ease;
  391 +}
  392 +
  393 +.order-status-card:hover {
  394 + border-color: #1677ff;
  395 + box-shadow: 0 8px 24px rgba(22, 119, 255, 0.08);
  396 +}
  397 +
  398 +.order-status-card.is-active {
  399 + border-color: #1677ff;
  400 + background: #f0f7ff;
  401 + box-shadow: 0 8px 24px rgba(22, 119, 255, 0.12);
  402 +}
  403 +
  404 +.order-status-card__label {
  405 + color: #64748b;
  406 + font-size: 14px;
  407 +}
  408 +
  409 +.order-status-card__count {
  410 + color: #0f172a;
  411 + font-size: 28px;
  412 + line-height: 1;
  413 +}
  414 +</style>