Commit 47090e96533d7d7f8395b0d4ad1e91c92d27d8e9

Authored by 杨刚
1 parent 32f52c2c

refactor: centralize city management logic into composables for cleaner code and reusability

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&lt;T&gt;(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&lt;number, string&gt; = {
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&lt;number | null&gt;(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&lt;number | undefined&gt;()
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&lt;number, string&gt; = {
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') : []
... ...