Commit 9542b035a3a028179672213abd9e5e21326046be
1 parent
4d16c20f
Refactor: 优化订单管理和配送订单列表分页功能,新增状态筛选卡片和交互逻辑,提升用户体验。
Showing
2 changed files
with
220 additions
and
16 deletions
src/views/delivery/DeliveryOrderList.vue
| ... | ... | @@ -6,18 +6,25 @@ |
| 6 | 6 | <a-select v-if="isAdmin" v-model:value="filterCityId" placeholder="租户" allowClear class="list-filter" @change="handleAdminCityChange"> |
| 7 | 7 | <a-select-option v-for="item in cityList" :key="item.id" :value="item.id">{{ item.name }}</a-select-option> |
| 8 | 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 | 11 | <a-select-option :value="2">待接单</a-select-option> |
| 12 | 12 | <a-select-option :value="3">已接单</a-select-option> |
| 13 | 13 | <a-select-option :value="4">配送中</a-select-option> |
| 14 | 14 | <a-select-option :value="6">已完成</a-select-option> |
| 15 | 15 | <a-select-option :value="10">已取消</a-select-option> |
| 16 | 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 | 18 | </div> |
| 19 | 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 | 28 | <template #bodyCell="{ column, record }"> |
| 22 | 29 | <template v-if="column.key === 'cityName'"> |
| 23 | 30 | {{ getCityName(record.cityId) }} |
| ... | ... | @@ -61,12 +68,18 @@ |
| 61 | 68 | <script setup lang="ts"> |
| 62 | 69 | import { computed, ref, onMounted } from 'vue' |
| 63 | 70 | import { message } from 'ant-design-vue' |
| 71 | +import type { TablePaginationConfig } from 'ant-design-vue' | |
| 64 | 72 | import { riderApi } from '@/api' |
| 65 | 73 | import { useRoleCityList } from '@/composables/useRoleCityList' |
| 66 | 74 | |
| 75 | +const PAGE_SIZE = 20 | |
| 76 | + | |
| 67 | 77 | const loading = ref(false) |
| 68 | 78 | const detailLoading = ref(false) |
| 69 | 79 | const list = ref<any[]>([]) |
| 80 | +const total = ref(0) | |
| 81 | +const currentPage = ref(1) | |
| 82 | +const pageSize = ref(PAGE_SIZE) | |
| 70 | 83 | const filterCityId = ref<number | undefined>() |
| 71 | 84 | const filterAppKey = ref('') |
| 72 | 85 | const filterStatus = ref<number | undefined>() |
| ... | ... | @@ -104,6 +117,42 @@ const columns = computed(() => { |
| 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 | 156 | async function loadList() { |
| 108 | 157 | loading.value = true |
| 109 | 158 | try { |
| ... | ... | @@ -112,9 +161,17 @@ async function loadList() { |
| 112 | 161 | appKey: filterAppKey.value || undefined, |
| 113 | 162 | status: filterStatus.value, |
| 114 | 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 | 175 | } finally { loading.value = false } |
| 119 | 176 | } |
| 120 | 177 | |
| ... | ... | @@ -123,6 +180,7 @@ function handleAdminCityChange() { |
| 123 | 180 | queryVisible.value = false |
| 124 | 181 | queryResult.value = null |
| 125 | 182 | } |
| 183 | + resetToFirstPage() | |
| 126 | 184 | loadList() |
| 127 | 185 | } |
| 128 | 186 | |
| ... | ... | @@ -143,7 +201,7 @@ async function cancelOrder(record: any) { |
| 143 | 201 | queryVisible.value = false |
| 144 | 202 | queryResult.value = null |
| 145 | 203 | } |
| 146 | - loadList() | |
| 204 | + await loadList() | |
| 147 | 205 | } |
| 148 | 206 | |
| 149 | 207 | onMounted(async () => { | ... | ... |
src/views/order/OrderList.vue
| 1 | 1 | <template> |
| 2 | 2 | <div> |
| 3 | 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 | 17 | <div class="list-toolbar"> |
| 5 | 18 | <div class="list-toolbar-left"> |
| 6 | 19 | <a-select v-if="isAdmin" v-model:value="filterCityId" placeholder="租户" allowClear class="list-filter" @change="handleAdminCityChange"> |
| 7 | 20 | <a-select-option v-for="item in cityList" :key="item.id" :value="item.id">{{ item.name }}</a-select-option> |
| 8 | 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 | 23 | <a-select-option :value="2">已支付</a-select-option> |
| 11 | 24 | <a-select-option :value="3">已接单</a-select-option> |
| 12 | 25 | <a-select-option :value="4">服务中</a-select-option> |
| ... | ... | @@ -14,15 +27,22 @@ |
| 14 | 27 | <a-select-option :value="7">退款申请</a-select-option> |
| 15 | 28 | <a-select-option :value="10">已取消</a-select-option> |
| 16 | 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 | 31 | <a-select-option :value="2">转单申请中</a-select-option> |
| 19 | 32 | <a-select-option :value="1">已转单</a-select-option> |
| 20 | 33 | <a-select-option :value="3">转单拒绝</a-select-option> |
| 21 | 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 | 36 | </div> |
| 24 | 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 | 46 | <template #bodyCell="{ column, record }"> |
| 27 | 47 | <template v-if="column.key === 'cityName'"> |
| 28 | 48 | {{ cityList.find(item => item.id === record.cityId)?.name || `租户#${record.cityId}` }} |
| ... | ... | @@ -129,12 +149,28 @@ |
| 129 | 149 | <script setup lang="ts"> |
| 130 | 150 | import { computed, ref, onMounted } from 'vue' |
| 131 | 151 | import { message } from 'ant-design-vue' |
| 152 | +import type { TablePaginationConfig } from 'ant-design-vue' | |
| 132 | 153 | import { riderApi, refundApi } from '@/api' |
| 133 | 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 | 167 | const loading = ref(false) |
| 136 | 168 | const saving = ref(false) |
| 137 | 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 | 174 | const filterCityId = ref<number | undefined>() |
| 139 | 175 | const filterStatus = ref<number | undefined>() |
| 140 | 176 | const filterTrans = ref<number | undefined>() |
| ... | ... | @@ -151,6 +187,7 @@ const refundRecord = ref<any>(null) |
| 151 | 187 | const currentRefundRecordId = ref(0) |
| 152 | 188 | const { isAdmin, cityList, loadCities } = useRoleCityList() |
| 153 | 189 | const canOperate = computed(() => !isAdmin.value || !!filterCityId.value) |
| 190 | +const statusCardItems = STATUS_CARD_ITEMS | |
| 154 | 191 | |
| 155 | 192 | const statusMap: Record<number, string> = { |
| 156 | 193 | 1: '待支付', 2: '已支付', 3: '已接单', 4: '服务中', |
| ... | ... | @@ -180,6 +217,52 @@ const columns = computed(() => { |
| 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 | 266 | async function loadList() { |
| 184 | 267 | loading.value = true |
| 185 | 268 | try { |
| ... | ... | @@ -188,9 +271,26 @@ async function loadList() { |
| 188 | 271 | status: filterStatus.value, |
| 189 | 272 | isTrans: filterTrans.value, |
| 190 | 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 | 294 | } finally { loading.value = false } |
| 195 | 295 | } |
| 196 | 296 | |
| ... | ... | @@ -200,6 +300,7 @@ function handleAdminCityChange() { |
| 200 | 300 | refundVisible.value = false |
| 201 | 301 | rejectVisible.value = false |
| 202 | 302 | } |
| 303 | + resetToFirstPage() | |
| 203 | 304 | loadList() |
| 204 | 305 | } |
| 205 | 306 | |
| ... | ... | @@ -228,14 +329,14 @@ async function handleDesignate() { |
| 228 | 329 | await riderApi.designate(designateOrderId.value, designateRiderId.value) |
| 229 | 330 | message.success('指派成功') |
| 230 | 331 | designateVisible.value = false |
| 231 | - loadList() | |
| 332 | + await loadList() | |
| 232 | 333 | } finally { saving.value = false } |
| 233 | 334 | } |
| 234 | 335 | |
| 235 | 336 | async function handleTrans(orderId: number, trans: number) { |
| 236 | 337 | await riderApi.setTrans(orderId, trans) |
| 237 | 338 | message.success('操作成功') |
| 238 | - loadList() | |
| 339 | + await loadList() | |
| 239 | 340 | } |
| 240 | 341 | |
| 241 | 342 | async function openRefund(record: any) { |
| ... | ... | @@ -257,7 +358,7 @@ async function handleRefund(status: number) { |
| 257 | 358 | message.success('操作成功') |
| 258 | 359 | refundVisible.value = false |
| 259 | 360 | rejectVisible.value = false |
| 260 | - loadList() | |
| 361 | + await loadList() | |
| 261 | 362 | } finally { saving.value = false } |
| 262 | 363 | } |
| 263 | 364 | |
| ... | ... | @@ -266,3 +367,48 @@ onMounted(async () => { |
| 266 | 367 | await loadList() |
| 267 | 368 | }) |
| 268 | 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> | ... | ... |