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