Commit 47090e96533d7d7f8395b0d4ad1e91c92d27d8e9
1 parent
32f52c2c
refactor: centralize city management logic into composables for cleaner code and reusability
Showing
14 changed files
with
262 additions
and
168 deletions
src/api/index.ts
| ... | ... | @@ -117,6 +117,10 @@ export const riderApi = { |
| 117 | 117 | orderList: (params: any) => request.get('/api/admin/rider/order/list', { params }), |
| 118 | 118 | // 配送订单列表(外部系统推入的) |
| 119 | 119 | deliveryOrderList: (params: any) => request.get('/api/admin/rider/order/delivery/list', { params }), |
| 120 | + deliveryOrderDetail: (orderId: number) => | |
| 121 | + request.get('/api/admin/rider/order/delivery/detail', { params: { orderId } }), | |
| 122 | + deliveryOrderCancel: (orderId: number) => | |
| 123 | + request.post('/api/admin/rider/order/delivery/cancel', null, { params: { orderId } }), | |
| 120 | 124 | } |
| 121 | 125 | |
| 122 | 126 | export const riderLevelApi = { | ... | ... |
src/composables/useRoleCityList.ts
0 → 100644
| 1 | +import { computed, ref } from 'vue' | |
| 2 | +import { storeToRefs } from 'pinia' | |
| 3 | +import { cityApi } from '@/api' | |
| 4 | +import { useAuthStore } from '@/stores/auth' | |
| 5 | + | |
| 6 | +export type CityOption = { | |
| 7 | + id: number | |
| 8 | + name: string | |
| 9 | + [key: string]: any | |
| 10 | +} | |
| 11 | + | |
| 12 | +export function useRoleCityList() { | |
| 13 | + const auth = useAuthStore() | |
| 14 | + const { isAdmin, user } = storeToRefs(auth) | |
| 15 | + const cityList = ref<CityOption[]>([]) | |
| 16 | + const managedCityId = computed<number | undefined>(() => user.value?.cityId) | |
| 17 | + const managedCityName = computed(() => user.value?.cityName || '') | |
| 18 | + | |
| 19 | + function buildManagedCityList() { | |
| 20 | + return managedCityId.value | |
| 21 | + ? [{ id: managedCityId.value, name: managedCityName.value || `租户#${managedCityId.value}` }] | |
| 22 | + : [] | |
| 23 | + } | |
| 24 | + | |
| 25 | + async function loadCities() { | |
| 26 | + if (!isAdmin.value) { | |
| 27 | + cityList.value = buildManagedCityList() | |
| 28 | + return | |
| 29 | + } | |
| 30 | + const res: any = await cityApi.openList() | |
| 31 | + cityList.value = Array.isArray(res?.data) ? res.data : [] | |
| 32 | + } | |
| 33 | + | |
| 34 | + function getCityName(cityId?: number) { | |
| 35 | + const city = cityList.value.find(item => item.id === cityId) | |
| 36 | + return city?.name || (cityId ? `租户#${cityId}` : '-') | |
| 37 | + } | |
| 38 | + | |
| 39 | + return { | |
| 40 | + isAdmin, | |
| 41 | + managedCityId, | |
| 42 | + managedCityName, | |
| 43 | + cityList, | |
| 44 | + loadCities, | |
| 45 | + getCityName, | |
| 46 | + } | |
| 47 | +} | ... | ... |
src/composables/useRoleSelectedCity.ts
0 → 100644
| 1 | +import { computed, ref } from 'vue' | |
| 2 | +import { useRoleCityList } from '@/composables/useRoleCityList' | |
| 3 | + | |
| 4 | +export function useRoleSelectedCity() { | |
| 5 | + const { | |
| 6 | + isAdmin, | |
| 7 | + managedCityId, | |
| 8 | + managedCityName, | |
| 9 | + cityList, | |
| 10 | + loadCities: loadRoleCities, | |
| 11 | + getCityName, | |
| 12 | + } = useRoleCityList() | |
| 13 | + const selectedCityId = ref<number | undefined>() | |
| 14 | + | |
| 15 | + function resolveSelectedCity(initialCityId?: number) { | |
| 16 | + if (!isAdmin.value) { | |
| 17 | + selectedCityId.value = managedCityId.value | |
| 18 | + return | |
| 19 | + } | |
| 20 | + if (initialCityId && cityList.value.some(item => item.id === initialCityId)) { | |
| 21 | + selectedCityId.value = initialCityId | |
| 22 | + return | |
| 23 | + } | |
| 24 | + if (selectedCityId.value && cityList.value.some(item => item.id === selectedCityId.value)) { | |
| 25 | + return | |
| 26 | + } | |
| 27 | + selectedCityId.value = cityList.value[0]?.id | |
| 28 | + } | |
| 29 | + | |
| 30 | + async function loadCities(initialCityId?: number) { | |
| 31 | + await loadRoleCities() | |
| 32 | + resolveSelectedCity(initialCityId) | |
| 33 | + } | |
| 34 | + | |
| 35 | + const currentCityName = computed(() => | |
| 36 | + selectedCityId.value ? getCityName(selectedCityId.value) : managedCityName.value || '' | |
| 37 | + ) | |
| 38 | + | |
| 39 | + return { | |
| 40 | + isAdmin, | |
| 41 | + cityList, | |
| 42 | + selectedCityId, | |
| 43 | + currentCityName, | |
| 44 | + loadCities, | |
| 45 | + } | |
| 46 | +} | ... | ... |
src/layouts/MainLayout.vue
| ... | ... | @@ -88,7 +88,7 @@ |
| 88 | 88 | <span class="profile-avatar">{{ avatarText }}</span> |
| 89 | 89 | <span class="profile-copy"> |
| 90 | 90 | <strong>{{ auth.user?.userNickname || '管理员' }}</strong> |
| 91 | - <small>{{ auth.user?.role === 'admin' ? '超级管理员' : '分站管理员' }}</small> | |
| 91 | + <small>{{ auth.isAdmin ? '超级管理员' : '分站管理员' }}</small> | |
| 92 | 92 | </span> |
| 93 | 93 | <down-outlined /> |
| 94 | 94 | </a-button> | ... | ... |
src/views/config/FeePlanList.vue
| ... | ... | @@ -424,11 +424,11 @@ |
| 424 | 424 | </template> |
| 425 | 425 | |
| 426 | 426 | <script setup lang="ts"> |
| 427 | -import { computed, onMounted, reactive, ref } from 'vue' | |
| 427 | +import { onMounted, reactive, ref } from 'vue' | |
| 428 | 428 | import { useRoute } from 'vue-router' |
| 429 | 429 | import { message } from 'ant-design-vue' |
| 430 | -import { adminFeePlanApi, cityApi } from '@/api' | |
| 431 | -import { useAuthStore } from '@/stores/auth' | |
| 430 | +import { adminFeePlanApi } from '@/api' | |
| 431 | +import { useRoleSelectedCity } from '@/composables/useRoleSelectedCity' | |
| 432 | 432 | |
| 433 | 433 | type TimePeriodForm = { |
| 434 | 434 | startText: string |
| ... | ... | @@ -450,12 +450,13 @@ type PieceRuleForm = { |
| 450 | 450 | } |
| 451 | 451 | |
| 452 | 452 | const route = useRoute() |
| 453 | -const auth = useAuthStore() | |
| 454 | -const isAdmin = computed(() => auth.user?.role === 'admin') | |
| 455 | -const managedCityId = computed<number | undefined>(() => auth.user?.cityId) | |
| 456 | -const cityList = ref<any[]>([]) | |
| 457 | -const selectedCityId = ref<number | undefined>() | |
| 458 | -const currentCityName = computed(() => cityList.value.find(item => item.id === selectedCityId.value)?.name || auth.user?.cityName || '') | |
| 453 | +const { | |
| 454 | + isAdmin, | |
| 455 | + cityList, | |
| 456 | + selectedCityId, | |
| 457 | + currentCityName, | |
| 458 | + loadCities, | |
| 459 | +} = useRoleSelectedCity() | |
| 459 | 460 | |
| 460 | 461 | const config = ref<any>(null) |
| 461 | 462 | const planList = ref<any[]>([]) |
| ... | ... | @@ -478,22 +479,9 @@ const previewForm = reactive({ |
| 478 | 479 | serviceTime: '', |
| 479 | 480 | }) |
| 480 | 481 | |
| 481 | -async function loadCities() { | |
| 482 | - if (isAdmin.value) { | |
| 483 | - const res: any = await cityApi.openList() | |
| 484 | - cityList.value = Array.isArray(res?.data) ? res.data : [] | |
| 485 | - const queryCityId = Number(route.query.cityId || 0) || undefined | |
| 486 | - if (queryCityId && cityList.value.some(item => item.id === queryCityId)) { | |
| 487 | - selectedCityId.value = queryCityId | |
| 488 | - } | |
| 489 | - if (!selectedCityId.value && cityList.value.length) { | |
| 490 | - selectedCityId.value = cityList.value[0].id | |
| 491 | - } | |
| 492 | - } else { | |
| 493 | - selectedCityId.value = managedCityId.value | |
| 494 | - cityList.value = selectedCityId.value ? [{ id: selectedCityId.value, name: auth.user?.cityName || `租户#${selectedCityId.value}` }] : [] | |
| 495 | - } | |
| 496 | - | |
| 482 | +async function loadFeePlanCities() { | |
| 483 | + const queryCityId = Number(route.query.cityId || 0) || undefined | |
| 484 | + await loadCities(queryCityId) | |
| 497 | 485 | if (selectedCityId.value) { |
| 498 | 486 | await loadPlanList(selectedCityId.value) |
| 499 | 487 | } |
| ... | ... | @@ -833,7 +821,7 @@ function deepClone<T>(value: T): T { |
| 833 | 821 | return JSON.parse(JSON.stringify(value)) |
| 834 | 822 | } |
| 835 | 823 | |
| 836 | -onMounted(loadCities) | |
| 824 | +onMounted(loadFeePlanCities) | |
| 837 | 825 | </script> |
| 838 | 826 | |
| 839 | 827 | <style scoped> | ... | ... |
src/views/delivery/DeliveryOrderList.vue
| ... | ... | @@ -3,6 +3,10 @@ |
| 3 | 3 | <a-card title="配送订单" :bordered="false" class="list-table-card"> |
| 4 | 4 | <div class="list-toolbar"> |
| 5 | 5 | <div class="list-toolbar-left"> |
| 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> | |
| 8 | + </a-select> | |
| 9 | + <a-input v-if="isAdmin" v-model:value="filterAppKey" placeholder="接入方 AppKey" class="list-search appkey-search" @pressEnter="loadList" /> | |
| 6 | 10 | <a-select v-model:value="filterStatus" placeholder="状态" allowClear class="list-filter" @change="loadList"> |
| 7 | 11 | <a-select-option :value="2">待接单</a-select-option> |
| 8 | 12 | <a-select-option :value="3">已接单</a-select-option> |
| ... | ... | @@ -12,33 +16,37 @@ |
| 12 | 16 | </a-select> |
| 13 | 17 | <a-input-search v-model:value="keyword" placeholder="外部订单号" @search="loadList" class="list-search" /> |
| 14 | 18 | </div> |
| 15 | - <div class="list-toolbar-right"> | |
| 16 | - <a-button type="primary" @click="queryByNo" :loading="querying">查询</a-button> | |
| 17 | - </div> | |
| 18 | 19 | </div> |
| 19 | 20 | <a-table :dataSource="list" :columns="columns" :loading="loading" rowKey="id" :pagination="false"> |
| 20 | 21 | <template #bodyCell="{ column, record }"> |
| 22 | + <template v-if="column.key === 'cityName'"> | |
| 23 | + {{ getCityName(record.cityId) }} | |
| 24 | + </template> | |
| 21 | 25 | <template v-if="column.key === 'status'"> |
| 22 | 26 | <a-tag :color="statusColor[record.status]">{{ statusMap[record.status] }}</a-tag> |
| 23 | 27 | </template> |
| 24 | 28 | <template v-if="column.key === 'action'"> |
| 25 | - <a-popconfirm v-if="record.status === 2" title="确认取消该配送订单?" @confirm="cancelOrder(record)"> | |
| 26 | - <a style="color:red">取消</a> | |
| 27 | - </a-popconfirm> | |
| 28 | - <span v-else>-</span> | |
| 29 | + <a-space> | |
| 30 | + <a @click="openDetail(record)">详情</a> | |
| 31 | + <a-popconfirm v-if="record.status === 2 && canOperate" title="确认取消该配送订单?" @confirm="cancelOrder(record)"> | |
| 32 | + <a style="color:red">取消</a> | |
| 33 | + </a-popconfirm> | |
| 34 | + <span v-else-if="isAdmin && record.status === 2 && !canOperate">选择租户后可取消</span> | |
| 35 | + </a-space> | |
| 29 | 36 | </template> |
| 30 | 37 | </template> |
| 31 | 38 | </a-table> |
| 32 | 39 | </a-card> |
| 33 | 40 | |
| 34 | - <a-modal v-model:open="queryVisible" title="配送订单详情" :footer="null"> | |
| 41 | + <a-modal v-model:open="queryVisible" title="配送订单详情" :footer="null" :confirmLoading="detailLoading"> | |
| 35 | 42 | <div v-if="queryResult" class="soft-page-stack"> |
| 36 | 43 | <div class="soft-note-card"> |
| 37 | - <strong>订单查询结果</strong> | |
| 38 | - <p>这里展示开放平台配送单的核心状态和计费结果,适合按外部订单号快速核对。</p> | |
| 44 | + <strong>配送订单详情</strong> | |
| 45 | + <p>这里展示后台可查看的配送单核心状态和计费结果,便于运营快速核对。</p> | |
| 39 | 46 | </div> |
| 40 | 47 | <a-descriptions :column="1" bordered size="small"> |
| 41 | 48 | <a-descriptions-item label="配送订单ID">{{ queryResult.deliveryOrderId }}</a-descriptions-item> |
| 49 | + <a-descriptions-item label="配送订单号">{{ queryResult.orderNo }}</a-descriptions-item> | |
| 42 | 50 | <a-descriptions-item label="外部订单号">{{ queryResult.outOrderNo }}</a-descriptions-item> |
| 43 | 51 | <a-descriptions-item label="状态">{{ statusMap[queryResult.status] }}</a-descriptions-item> |
| 44 | 52 | <a-descriptions-item label="配送费">¥{{ queryResult.totalFee }}</a-descriptions-item> |
| ... | ... | @@ -51,17 +59,22 @@ |
| 51 | 59 | </template> |
| 52 | 60 | |
| 53 | 61 | <script setup lang="ts"> |
| 54 | -import { ref, onMounted } from 'vue' | |
| 62 | +import { computed, ref, onMounted } from 'vue' | |
| 55 | 63 | import { message } from 'ant-design-vue' |
| 56 | -import { deliveryOrderApi, riderApi } from '@/api' | |
| 64 | +import { riderApi } from '@/api' | |
| 65 | +import { useRoleCityList } from '@/composables/useRoleCityList' | |
| 57 | 66 | |
| 58 | 67 | const loading = ref(false) |
| 59 | -const querying = ref(false) | |
| 68 | +const detailLoading = ref(false) | |
| 60 | 69 | const list = ref<any[]>([]) |
| 70 | +const filterCityId = ref<number | undefined>() | |
| 71 | +const filterAppKey = ref('') | |
| 61 | 72 | const filterStatus = ref<number | undefined>() |
| 62 | 73 | const keyword = ref('') |
| 63 | 74 | const queryVisible = ref(false) |
| 64 | 75 | const queryResult = ref<any>(null) |
| 76 | +const { isAdmin, cityList, loadCities, getCityName } = useRoleCityList() | |
| 77 | +const canOperate = computed(() => !isAdmin.value || !!filterCityId.value) | |
| 65 | 78 | |
| 66 | 79 | const statusMap: Record<number, string> = { |
| 67 | 80 | 2: '待接单', 3: '已接单', 4: '配送中', 6: '已完成', 7: '退款申请', |
| ... | ... | @@ -72,20 +85,31 @@ const statusColor: Record<number, string> = { |
| 72 | 85 | 7: 'orange', 8: 'green', 9: 'red', 10: 'red' |
| 73 | 86 | } |
| 74 | 87 | |
| 75 | -const columns = [ | |
| 76 | - { title: 'ID', dataIndex: 'id', width: 80 }, | |
| 77 | - { title: '外部订单号', dataIndex: 'outOrderNo' }, | |
| 78 | - { title: '接入方', dataIndex: 'appKey', ellipsis: true }, | |
| 79 | - { title: '收件人', dataIndex: 'recipName' }, | |
| 80 | - { title: '配送费', dataIndex: 'moneyDelivery' }, | |
| 81 | - { title: '状态', key: 'status' }, | |
| 82 | - { title: '操作', key: 'action' }, | |
| 83 | -] | |
| 88 | +const columns = computed(() => { | |
| 89 | + const base = [ | |
| 90 | + { title: 'ID', dataIndex: 'id', width: 80 }, | |
| 91 | + { title: '外部订单号', dataIndex: 'outOrderNo' }, | |
| 92 | + { title: '接入方', dataIndex: 'appKey', ellipsis: true }, | |
| 93 | + { title: '收件人', dataIndex: 'recipName' }, | |
| 94 | + { title: '配送费', dataIndex: 'moneyDelivery' }, | |
| 95 | + { title: '状态', key: 'status' }, | |
| 96 | + { title: '操作', key: 'action' }, | |
| 97 | + ] | |
| 98 | + if (!isAdmin.value) { | |
| 99 | + return base | |
| 100 | + } | |
| 101 | + return [ | |
| 102 | + { title: '租户', key: 'cityName' }, | |
| 103 | + ...base, | |
| 104 | + ] | |
| 105 | +}) | |
| 84 | 106 | |
| 85 | 107 | async function loadList() { |
| 86 | 108 | loading.value = true |
| 87 | 109 | try { |
| 88 | 110 | const res: any = await riderApi.deliveryOrderList({ |
| 111 | + cityId: filterCityId.value, | |
| 112 | + appKey: filterAppKey.value || undefined, | |
| 89 | 113 | status: filterStatus.value, |
| 90 | 114 | outOrderNo: keyword.value || undefined, |
| 91 | 115 | page: 1 |
| ... | ... | @@ -94,21 +118,36 @@ async function loadList() { |
| 94 | 118 | } finally { loading.value = false } |
| 95 | 119 | } |
| 96 | 120 | |
| 97 | -async function queryByNo() { | |
| 98 | - if (!keyword.value) { message.warning('请输入外部订单号'); return } | |
| 99 | - querying.value = true | |
| 121 | +function handleAdminCityChange() { | |
| 122 | + if (!filterCityId.value) { | |
| 123 | + queryVisible.value = false | |
| 124 | + queryResult.value = null | |
| 125 | + } | |
| 126 | + loadList() | |
| 127 | +} | |
| 128 | + | |
| 129 | +async function openDetail(record: any) { | |
| 130 | + detailLoading.value = true | |
| 131 | + queryResult.value = null | |
| 132 | + queryVisible.value = true | |
| 100 | 133 | try { |
| 101 | - const res: any = await deliveryOrderApi.query(keyword.value) | |
| 134 | + const res: any = await riderApi.deliveryOrderDetail(record.id) | |
| 102 | 135 | queryResult.value = res.data |
| 103 | - queryVisible.value = true | |
| 104 | - } finally { querying.value = false } | |
| 136 | + } finally { detailLoading.value = false } | |
| 105 | 137 | } |
| 106 | 138 | |
| 107 | 139 | async function cancelOrder(record: any) { |
| 108 | - await deliveryOrderApi.cancel(record.outOrderNo) | |
| 140 | + await riderApi.deliveryOrderCancel(record.id) | |
| 109 | 141 | message.success('取消成功') |
| 142 | + if (queryVisible.value && queryResult.value?.deliveryOrderId === record.id) { | |
| 143 | + queryVisible.value = false | |
| 144 | + queryResult.value = null | |
| 145 | + } | |
| 110 | 146 | loadList() |
| 111 | 147 | } |
| 112 | 148 | |
| 113 | -onMounted(loadList) | |
| 149 | +onMounted(async () => { | |
| 150 | + await loadCities() | |
| 151 | + await loadList() | |
| 152 | +}) | |
| 114 | 153 | </script> | ... | ... |
src/views/dispatch/DispatchRuleList.vue
| ... | ... | @@ -237,15 +237,16 @@ |
| 237 | 237 | <script setup lang="ts"> |
| 238 | 238 | import { computed, onMounted, reactive, ref } from 'vue' |
| 239 | 239 | import { message } from 'ant-design-vue' |
| 240 | -import { cityApi, dispatchRuleApi } from '@/api' | |
| 241 | -import { useAuthStore } from '@/stores/auth' | |
| 242 | - | |
| 243 | -const auth = useAuthStore() | |
| 244 | -const isAdmin = computed(() => auth.user?.role === 'admin') | |
| 245 | -const managedCityId = computed<number | undefined>(() => auth.user?.cityId) | |
| 246 | -const cityList = ref<any[]>([]) | |
| 247 | -const selectedCityId = ref<number | undefined>() | |
| 248 | -const currentCityName = computed(() => cityList.value.find(item => item.id === selectedCityId.value)?.name || auth.user?.cityName || '') | |
| 240 | +import { dispatchRuleApi } from '@/api' | |
| 241 | +import { useRoleSelectedCity } from '@/composables/useRoleSelectedCity' | |
| 242 | + | |
| 243 | +const { | |
| 244 | + isAdmin, | |
| 245 | + cityList, | |
| 246 | + selectedCityId, | |
| 247 | + currentCityName, | |
| 248 | + loadCities, | |
| 249 | +} = useRoleSelectedCity() | |
| 249 | 250 | const templateList = ref<any[]>([]) |
| 250 | 251 | const selectedTemplateId = ref<number | null>(null) |
| 251 | 252 | const loadingTemplates = ref(false) |
| ... | ... | @@ -291,18 +292,8 @@ const autoDispatchChecked = computed({ |
| 291 | 292 | |
| 292 | 293 | const editorVisible = computed(() => !!form.id || creatingNew.value) |
| 293 | 294 | |
| 294 | -async function loadCities() { | |
| 295 | - if (isAdmin.value) { | |
| 296 | - const res: any = await cityApi.openList() | |
| 297 | - cityList.value = res.data || [] | |
| 298 | - if (!selectedCityId.value && cityList.value.length) { | |
| 299 | - selectedCityId.value = cityList.value[0].id | |
| 300 | - } | |
| 301 | - } else { | |
| 302 | - selectedCityId.value = managedCityId.value | |
| 303 | - cityList.value = selectedCityId.value ? [{ id: selectedCityId.value, name: auth.user?.cityName || `租户#${selectedCityId.value}` }] : [] | |
| 304 | - } | |
| 305 | - | |
| 295 | +async function loadDispatchCities() { | |
| 296 | + await loadCities() | |
| 306 | 297 | if (selectedCityId.value) { |
| 307 | 298 | await loadTemplates() |
| 308 | 299 | } |
| ... | ... | @@ -497,7 +488,7 @@ function resetForm() { |
| 497 | 488 | selectedTemplateId.value = null |
| 498 | 489 | } |
| 499 | 490 | |
| 500 | -onMounted(loadCities) | |
| 491 | +onMounted(loadDispatchCities) | |
| 501 | 492 | </script> |
| 502 | 493 | |
| 503 | 494 | <style scoped> | ... | ... |
src/views/merchant/StoreList.vue
| ... | ... | @@ -107,12 +107,12 @@ |
| 107 | 107 | <script setup lang="ts"> |
| 108 | 108 | import { ref, reactive, onMounted } from 'vue' |
| 109 | 109 | import { message } from 'ant-design-vue' |
| 110 | -import { merchantApi, cityApi } from '@/api' | |
| 110 | +import { merchantApi } from '@/api' | |
| 111 | +import { useRoleCityList } from '@/composables/useRoleCityList' | |
| 111 | 112 | |
| 112 | 113 | const loading = ref(false) |
| 113 | 114 | const saving = ref(false) |
| 114 | 115 | const list = ref<any[]>([]) |
| 115 | -const cityList = ref<any[]>([]) | |
| 116 | 116 | const keyword = ref('') |
| 117 | 117 | const filterCityId = ref<number | undefined>() |
| 118 | 118 | const modalVisible = ref(false) |
| ... | ... | @@ -121,6 +121,7 @@ const editingId = ref<number | null>(null) |
| 121 | 121 | const currentStoreId = ref(0) |
| 122 | 122 | const form = reactive<any>({ name: '', cityId: undefined, address: '', lng: '', lat: '', shippingType: 1, automaticOrder: 0, accountMobile: '', about: '', outStoreId: '' }) |
| 123 | 123 | const feeForm = reactive({ freeShipping: 0, upToSend: 0 }) |
| 124 | +const { cityList, loadCities, getCityName } = useRoleCityList() | |
| 124 | 125 | |
| 125 | 126 | const columns = [ |
| 126 | 127 | { title: 'ID', dataIndex: 'id', width: 80 }, |
| ... | ... | @@ -142,16 +143,6 @@ async function loadList() { |
| 142 | 143 | } finally { loading.value = false } |
| 143 | 144 | } |
| 144 | 145 | |
| 145 | -async function loadCities() { | |
| 146 | - const res: any = await cityApi.openList() | |
| 147 | - cityList.value = res.data | |
| 148 | -} | |
| 149 | - | |
| 150 | -function getCityName(cityId?: number) { | |
| 151 | - const city = cityList.value.find(item => item.id === cityId) | |
| 152 | - return city?.name || (cityId ? `租户#${cityId}` : '-') | |
| 153 | -} | |
| 154 | - | |
| 155 | 146 | function openAdd() { |
| 156 | 147 | editingId.value = null |
| 157 | 148 | Object.assign(form, { name: '', cityId: undefined, address: '', lng: '', lat: '', shippingType: 1, automaticOrder: 0, accountMobile: '', about: '', outStoreId: '' }) | ... | ... |
src/views/open/OpenAppList.vue
| ... | ... | @@ -103,19 +103,20 @@ |
| 103 | 103 | <script setup lang="ts"> |
| 104 | 104 | import { ref, reactive, onMounted } from 'vue' |
| 105 | 105 | import { message, Modal } from 'ant-design-vue' |
| 106 | -import { openApi, cityApi } from '@/api' | |
| 106 | +import { openApi } from '@/api' | |
| 107 | +import { useRoleCityList } from '@/composables/useRoleCityList' | |
| 107 | 108 | |
| 108 | 109 | const loading = ref(false) |
| 109 | 110 | const saving = ref(false) |
| 110 | 111 | const list = ref<any[]>([]) |
| 111 | 112 | const logs = ref<any[]>([]) |
| 112 | -const cityList = ref<any[]>([]) | |
| 113 | 113 | const addVisible = ref(false) |
| 114 | 114 | const webhookVisible = ref(false) |
| 115 | 115 | const logsVisible = ref(false) |
| 116 | 116 | const currentAppId = ref(0) |
| 117 | 117 | const addForm = reactive({ appName: '', cityId: undefined as number | undefined, remark: '' }) |
| 118 | 118 | const webhookForm = reactive({ webhookUrl: '', webhookEvents: '' }) |
| 119 | +const { cityList, loadCities, getCityName } = useRoleCityList() | |
| 119 | 120 | |
| 120 | 121 | const columns = [ |
| 121 | 122 | { title: 'ID', dataIndex: 'id', width: 80 }, |
| ... | ... | @@ -145,16 +146,6 @@ async function loadList() { |
| 145 | 146 | } finally { loading.value = false } |
| 146 | 147 | } |
| 147 | 148 | |
| 148 | -async function loadCities() { | |
| 149 | - const res: any = await cityApi.openList() | |
| 150 | - cityList.value = res.data | |
| 151 | -} | |
| 152 | - | |
| 153 | -function getCityName(cityId?: number) { | |
| 154 | - const city = cityList.value.find(item => item.id === cityId) | |
| 155 | - return city?.name || (cityId ? `租户#${cityId}` : '-') | |
| 156 | -} | |
| 157 | - | |
| 158 | 149 | function openAdd() { |
| 159 | 150 | Object.assign(addForm, { appName: '', cityId: undefined, remark: '' }) |
| 160 | 151 | addVisible.value = true | ... | ... |
src/views/open/OpenMockDelivery.vue
| ... | ... | @@ -270,7 +270,8 @@ |
| 270 | 270 | <script setup lang="ts"> |
| 271 | 271 | import { computed, onMounted, reactive, ref, watch } from 'vue' |
| 272 | 272 | import { message } from 'ant-design-vue' |
| 273 | -import { cityApi, deliveryApi, openApi } from '@/api' | |
| 273 | +import { deliveryApi, openApi } from '@/api' | |
| 274 | +import { useRoleCityList } from '@/composables/useRoleCityList' | |
| 274 | 275 | |
| 275 | 276 | type OpenAppItem = { |
| 276 | 277 | id: number |
| ... | ... | @@ -288,11 +289,11 @@ type DeliveryItem = { |
| 288 | 289 | } |
| 289 | 290 | |
| 290 | 291 | const appList = ref<OpenAppItem[]>([]) |
| 291 | -const cityList = ref<any[]>([]) | |
| 292 | 292 | const saving = ref(false) |
| 293 | 293 | const calcLoading = ref(false) |
| 294 | 294 | const feeResult = ref<any>(null) |
| 295 | 295 | const result = ref<any>(null) |
| 296 | +const { loadCities, getCityName } = useRoleCityList() | |
| 296 | 297 | |
| 297 | 298 | const statusMap: Record<number, string> = { |
| 298 | 299 | 2: '待接单', |
| ... | ... | @@ -332,10 +333,7 @@ function createDefaultForm() { |
| 332 | 333 | const form = reactive(createDefaultForm()) |
| 333 | 334 | |
| 334 | 335 | const selectedApp = computed(() => appList.value.find(item => item.id === form.appId)) |
| 335 | -const selectedTenantName = computed(() => { | |
| 336 | - const city = cityList.value.find(item => item.id === selectedApp.value?.cityId) | |
| 337 | - return city?.name || '' | |
| 338 | -}) | |
| 336 | +const selectedTenantName = computed(() => getCityName(selectedApp.value?.cityId)) | |
| 339 | 337 | |
| 340 | 338 | function generateOutOrderNo() { |
| 341 | 339 | return `MOCK${Date.now()}` |
| ... | ... | @@ -451,11 +449,6 @@ async function loadApps() { |
| 451 | 449 | appList.value = Array.isArray(res?.data) ? res.data.filter((item: OpenAppItem) => item.status === 1) : [] |
| 452 | 450 | } |
| 453 | 451 | |
| 454 | -async function loadCities() { | |
| 455 | - const res: any = await cityApi.openList() | |
| 456 | - cityList.value = Array.isArray(res?.data) ? res.data : [] | |
| 457 | -} | |
| 458 | - | |
| 459 | 452 | async function handleSubmit() { |
| 460 | 453 | if (!form.appId) { |
| 461 | 454 | message.error('请选择开放应用') | ... | ... |
src/views/order/OrderList.vue
| ... | ... | @@ -3,6 +3,9 @@ |
| 3 | 3 | <a-card title="订单管理" :bordered="false" class="list-table-card"> |
| 4 | 4 | <div class="list-toolbar"> |
| 5 | 5 | <div class="list-toolbar-left"> |
| 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> | |
| 8 | + </a-select> | |
| 6 | 9 | <a-select v-model:value="filterStatus" placeholder="订单状态" allowClear class="list-filter" @change="loadList"> |
| 7 | 10 | <a-select-option :value="2">已支付</a-select-option> |
| 8 | 11 | <a-select-option :value="3">已接单</a-select-option> |
| ... | ... | @@ -21,6 +24,9 @@ |
| 21 | 24 | </div> |
| 22 | 25 | <a-table :dataSource="list" :columns="columns" :loading="loading" rowKey="id" :pagination="false"> |
| 23 | 26 | <template #bodyCell="{ column, record }"> |
| 27 | + <template v-if="column.key === 'cityName'"> | |
| 28 | + {{ cityList.find(item => item.id === record.cityId)?.name || `租户#${record.cityId}` }} | |
| 29 | + </template> | |
| 24 | 30 | <template v-if="column.key === 'status'"> |
| 25 | 31 | <a-tag :color="statusColor[record.status]">{{ statusMap[record.status] }}</a-tag> |
| 26 | 32 | </template> |
| ... | ... | @@ -32,8 +38,8 @@ |
| 32 | 38 | </template> |
| 33 | 39 | <template v-if="column.key === 'action'"> |
| 34 | 40 | <a-space> |
| 35 | - <a @click="openDesignate(record)" v-if="record.status === 2">指派骑手</a> | |
| 36 | - <template v-if="record.isTrans === 2 && record.status === 4"> | |
| 41 | + <a @click="openDesignate(record)" v-if="record.status === 2 && canOperate">指派骑手</a> | |
| 42 | + <template v-if="record.isTrans === 2 && record.status === 4 && canOperate"> | |
| 37 | 43 | <a-popconfirm title="通过转单申请?" @confirm="handleTrans(record.id, 1)"> |
| 38 | 44 | <a style="color:green">通过转单</a> |
| 39 | 45 | </a-popconfirm> |
| ... | ... | @@ -41,7 +47,8 @@ |
| 41 | 47 | <a style="color:red">拒绝转单</a> |
| 42 | 48 | </a-popconfirm> |
| 43 | 49 | </template> |
| 44 | - <a v-if="record.status === 7" @click="openRefund(record)">查看退款</a> | |
| 50 | + <a v-if="record.status === 7 && canOperate" @click="openRefund(record)">查看退款</a> | |
| 51 | + <span v-if="isAdmin && !canOperate">选择租户后可操作</span> | |
| 45 | 52 | </a-space> |
| 46 | 53 | </template> |
| 47 | 54 | </template> |
| ... | ... | @@ -54,6 +61,7 @@ |
| 54 | 61 | <strong>指派说明</strong> |
| 55 | 62 | <p>这里只展示当前订单可指派的骑手候选,列表中会带出手机号和在线/休息状态,避免只能靠 ID 操作。</p> |
| 56 | 63 | </div> |
| 64 | + <a-alert v-if="isAdmin" type="info" show-icon message="平台管理员当前正在所选租户上下文中操作。" /> | |
| 57 | 65 | <a-form layout="vertical"> |
| 58 | 66 | <a-form-item label="选择骑手"> |
| 59 | 67 | <a-select |
| ... | ... | @@ -119,13 +127,15 @@ |
| 119 | 127 | </template> |
| 120 | 128 | |
| 121 | 129 | <script setup lang="ts"> |
| 122 | -import { ref, onMounted } from 'vue' | |
| 130 | +import { computed, ref, onMounted } from 'vue' | |
| 123 | 131 | import { message } from 'ant-design-vue' |
| 124 | 132 | import { riderApi, refundApi } from '@/api' |
| 133 | +import { useRoleCityList } from '@/composables/useRoleCityList' | |
| 125 | 134 | |
| 126 | 135 | const loading = ref(false) |
| 127 | 136 | const saving = ref(false) |
| 128 | 137 | const list = ref<any[]>([]) |
| 138 | +const filterCityId = ref<number | undefined>() | |
| 129 | 139 | const filterStatus = ref<number | undefined>() |
| 130 | 140 | const filterTrans = ref<number | undefined>() |
| 131 | 141 | const keyword = ref('') |
| ... | ... | @@ -139,6 +149,8 @@ const designateRiderId = ref<number | undefined>() |
| 139 | 149 | const designateCandidates = ref<any[]>([]) |
| 140 | 150 | const refundRecord = ref<any>(null) |
| 141 | 151 | const currentRefundRecordId = ref(0) |
| 152 | +const { isAdmin, cityList, loadCities } = useRoleCityList() | |
| 153 | +const canOperate = computed(() => !isAdmin.value || !!filterCityId.value) | |
| 142 | 154 | |
| 143 | 155 | const statusMap: Record<number, string> = { |
| 144 | 156 | 1: '待支付', 2: '已支付', 3: '已接单', 4: '服务中', |
| ... | ... | @@ -149,20 +161,30 @@ const statusColor: Record<number, string> = { |
| 149 | 161 | 6: 'green', 7: 'orange', 8: 'green', 9: 'red', 10: 'red' |
| 150 | 162 | } |
| 151 | 163 | |
| 152 | -const columns = [ | |
| 153 | - { title: 'ID', dataIndex: 'id', width: 80 }, | |
| 154 | - { title: '订单号', dataIndex: 'orderNo', ellipsis: true }, | |
| 155 | - { title: '状态', key: 'status' }, | |
| 156 | - { title: '骑手ID', dataIndex: 'riderId' }, | |
| 157 | - { title: '转单', key: 'isTrans' }, | |
| 158 | - { title: '配送费', dataIndex: 'moneyDelivery' }, | |
| 159 | - { title: '操作', key: 'action' }, | |
| 160 | -] | |
| 164 | +const columns = computed(() => { | |
| 165 | + const base = [ | |
| 166 | + { title: 'ID', dataIndex: 'id', width: 80 }, | |
| 167 | + { title: '订单号', dataIndex: 'orderNo', ellipsis: true }, | |
| 168 | + { title: '状态', key: 'status' }, | |
| 169 | + { title: '骑手ID', dataIndex: 'riderId' }, | |
| 170 | + { title: '转单', key: 'isTrans' }, | |
| 171 | + { title: '配送费', dataIndex: 'moneyDelivery' }, | |
| 172 | + { title: '操作', key: 'action' }, | |
| 173 | + ] | |
| 174 | + if (!isAdmin.value) { | |
| 175 | + return base | |
| 176 | + } | |
| 177 | + return [ | |
| 178 | + { title: '租户', dataIndex: 'cityName', key: 'cityName' }, | |
| 179 | + ...base, | |
| 180 | + ] | |
| 181 | +}) | |
| 161 | 182 | |
| 162 | 183 | async function loadList() { |
| 163 | 184 | loading.value = true |
| 164 | 185 | try { |
| 165 | 186 | const res: any = await riderApi.orderList({ |
| 187 | + cityId: filterCityId.value, | |
| 166 | 188 | status: filterStatus.value, |
| 167 | 189 | isTrans: filterTrans.value, |
| 168 | 190 | keyword: keyword.value, |
| ... | ... | @@ -172,6 +194,15 @@ async function loadList() { |
| 172 | 194 | } finally { loading.value = false } |
| 173 | 195 | } |
| 174 | 196 | |
| 197 | +function handleAdminCityChange() { | |
| 198 | + if (!filterCityId.value) { | |
| 199 | + designateVisible.value = false | |
| 200 | + refundVisible.value = false | |
| 201 | + rejectVisible.value = false | |
| 202 | + } | |
| 203 | + loadList() | |
| 204 | +} | |
| 205 | + | |
| 175 | 206 | async function openDesignate(record: any) { |
| 176 | 207 | designateOrderId.value = record.id |
| 177 | 208 | designateRiderId.value = undefined |
| ... | ... | @@ -230,5 +261,8 @@ async function handleRefund(status: number) { |
| 230 | 261 | } finally { saving.value = false } |
| 231 | 262 | } |
| 232 | 263 | |
| 233 | -onMounted(loadList) | |
| 264 | +onMounted(async () => { | |
| 265 | + await loadCities() | |
| 266 | + await loadList() | |
| 267 | +}) | |
| 234 | 268 | </script> | ... | ... |
src/views/rider/RiderEvaluateList.vue
| ... | ... | @@ -31,13 +31,14 @@ |
| 31 | 31 | |
| 32 | 32 | <script setup lang="ts"> |
| 33 | 33 | import { ref, onMounted } from 'vue' |
| 34 | -import { cityApi, riderApi } from '@/api' | |
| 34 | +import { riderApi } from '@/api' | |
| 35 | +import { useRoleCityList } from '@/composables/useRoleCityList' | |
| 35 | 36 | |
| 36 | 37 | const loading = ref(false) |
| 37 | 38 | const list = ref<any[]>([]) |
| 38 | -const cityList = ref<any[]>([]) | |
| 39 | 39 | const filterRiderId = ref<number | undefined>() |
| 40 | 40 | const filterType = ref(0) |
| 41 | +const { loadCities, getCityName } = useRoleCityList() | |
| 41 | 42 | |
| 42 | 43 | const columns = [ |
| 43 | 44 | { title: 'ID', dataIndex: 'id', width: 80 }, |
| ... | ... | @@ -47,16 +48,6 @@ const columns = [ |
| 47 | 48 | { title: '租户', key: 'cityId' }, |
| 48 | 49 | ] |
| 49 | 50 | |
| 50 | -async function loadCities() { | |
| 51 | - const res: any = await cityApi.openList() | |
| 52 | - cityList.value = Array.isArray(res?.data) ? res.data : [] | |
| 53 | -} | |
| 54 | - | |
| 55 | -function getCityName(cityId?: number) { | |
| 56 | - const city = cityList.value.find(item => item.id === cityId) | |
| 57 | - return city?.name || (cityId ? `租户#${cityId}` : '-') | |
| 58 | -} | |
| 59 | - | |
| 60 | 51 | async function loadList() { |
| 61 | 52 | if (!filterRiderId.value) return |
| 62 | 53 | loading.value = true | ... | ... |
src/views/rider/RiderList.vue
| ... | ... | @@ -121,16 +121,14 @@ |
| 121 | 121 | </template> |
| 122 | 122 | |
| 123 | 123 | <script setup lang="ts"> |
| 124 | -import { computed, reactive, ref, onMounted } from 'vue' | |
| 124 | +import { reactive, ref, onMounted } from 'vue' | |
| 125 | 125 | import { message } from 'ant-design-vue' |
| 126 | -import { cityApi, riderApi, riderLevelApi } from '@/api' | |
| 127 | -import { useAuthStore } from '@/stores/auth' | |
| 126 | +import { riderApi, riderLevelApi } from '@/api' | |
| 127 | +import { useRoleCityList } from '@/composables/useRoleCityList' | |
| 128 | 128 | |
| 129 | -const auth = useAuthStore() | |
| 130 | 129 | const loading = ref(false) |
| 131 | 130 | const saving = ref(false) |
| 132 | 131 | const list = ref<any[]>([]) |
| 133 | -const cityList = ref<any[]>([]) | |
| 134 | 132 | const levelOptions = ref<any[]>([]) |
| 135 | 133 | const filterStatus = ref<number | undefined>() |
| 136 | 134 | const keyword = ref('') |
| ... | ... | @@ -146,7 +144,7 @@ const levelSaving = ref(false) |
| 146 | 144 | const levelTargetId = ref<number>(0) |
| 147 | 145 | const levelTargetName = ref('') |
| 148 | 146 | const selectedLevelId = ref<number>(0) |
| 149 | -const isAdmin = computed(() => auth.user?.role === 'admin') | |
| 147 | +const { isAdmin, cityList, loadCities, getCityName } = useRoleCityList() | |
| 150 | 148 | |
| 151 | 149 | const statusMap: Record<number, string> = { 0: '已拒绝', 1: '已通过', 2: '待审核' } |
| 152 | 150 | |
| ... | ... | @@ -172,11 +170,6 @@ function getWorkStatus(record: any) { |
| 172 | 170 | return record.isRest === 1 ? 1 : 0 |
| 173 | 171 | } |
| 174 | 172 | |
| 175 | -function getCityName(cityId?: number) { | |
| 176 | - const city = cityList.value.find(item => item.id === cityId) | |
| 177 | - return city?.name || (cityId ? `租户#${cityId}` : '-') | |
| 178 | -} | |
| 179 | - | |
| 180 | 173 | async function loadList() { |
| 181 | 174 | loading.value = true |
| 182 | 175 | try { |
| ... | ... | @@ -188,11 +181,6 @@ async function loadList() { |
| 188 | 181 | } finally { loading.value = false } |
| 189 | 182 | } |
| 190 | 183 | |
| 191 | -async function loadCities() { | |
| 192 | - const res: any = await cityApi.openList() | |
| 193 | - cityList.value = Array.isArray(res?.data) ? res.data : [] | |
| 194 | -} | |
| 195 | - | |
| 196 | 184 | function openAdd() { |
| 197 | 185 | Object.assign(form, { |
| 198 | 186 | cityId: undefined, | ... | ... |
src/views/substation/SubstationList.vue
| ... | ... | @@ -97,17 +97,18 @@ |
| 97 | 97 | <script setup lang="ts"> |
| 98 | 98 | import { reactive, ref, onMounted } from 'vue' |
| 99 | 99 | import { message } from 'ant-design-vue' |
| 100 | -import { cityApi, substationApi, systemRoleApi } from '@/api' | |
| 100 | +import { substationApi, systemRoleApi } from '@/api' | |
| 101 | +import { useRoleCityList } from '@/composables/useRoleCityList' | |
| 101 | 102 | |
| 102 | 103 | const loading = ref(false) |
| 103 | 104 | const saving = ref(false) |
| 104 | 105 | const list = ref<any[]>([]) |
| 105 | -const cityList = ref<any[]>([]) | |
| 106 | 106 | const roleOptions = ref<any[]>([]) |
| 107 | 107 | const keyword = ref('') |
| 108 | 108 | const modalVisible = ref(false) |
| 109 | 109 | const editingId = ref<number | null>(null) |
| 110 | 110 | const form = reactive({ cityId: undefined, roleId: undefined, userLogin: '', userNickname: '', mobile: '', userPass: '' }) |
| 111 | +const { cityList, loadCities, getCityName } = useRoleCityList() | |
| 111 | 112 | |
| 112 | 113 | const columns = [ |
| 113 | 114 | { title: 'ID', dataIndex: 'id', width: 80 }, |
| ... | ... | @@ -125,11 +126,6 @@ function getRoleName(roleId?: number) { |
| 125 | 126 | return role?.name || (roleId ? `角色#${roleId}` : '-') |
| 126 | 127 | } |
| 127 | 128 | |
| 128 | -function getCityName(cityId?: number) { | |
| 129 | - const city = cityList.value.find(item => item.id === cityId) | |
| 130 | - return city?.name || (cityId ? `租户#${cityId}` : '-') | |
| 131 | -} | |
| 132 | - | |
| 133 | 129 | async function loadList() { |
| 134 | 130 | loading.value = true |
| 135 | 131 | try { |
| ... | ... | @@ -138,11 +134,6 @@ async function loadList() { |
| 138 | 134 | } finally { loading.value = false } |
| 139 | 135 | } |
| 140 | 136 | |
| 141 | -async function loadCities() { | |
| 142 | - const res: any = await cityApi.openList() | |
| 143 | - cityList.value = res.data | |
| 144 | -} | |
| 145 | - | |
| 146 | 137 | async function loadRoles() { |
| 147 | 138 | const res: any = await systemRoleApi.list() |
| 148 | 139 | roleOptions.value = Array.isArray(res?.data) ? res.data.filter((item: any) => item.roleScope === 'SUBSTATION') : [] | ... | ... |