2 Commits

Author SHA1 Message Date
luo
8229ad9248 fix: 退款管理申请退款显示逻辑
Some checks failed
构建并部署前端到测试环境 / build-and-deploy (push) Failing after 52m24s
2026-09-12 11:45:42 +08:00
luo
fc01fa2ac9 fix: 超时时间改为90s, 分配店铺查询
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m11s
2026-09-09 16:40:40 +08:00
7 changed files with 163 additions and 76 deletions

View File

@@ -10,6 +10,19 @@ export enum RefundStatus {
RETURNED = 4 // 已退回 RETURNED = 4 // 已退回
} }
// 审批状态
export enum RefundApprovalStatus {
SUBMITTING = 0, // 提交中
APPROVING = 1, // 审批中
APPROVED = 2, // 已通过
REJECTED = 3, // 已拒绝
REVOKED = 4, // 已撤销
REVOKED_AFTER_APPROVED = 5, // 通过后撤销
DELETED = 6, // 已删除
SUBMIT_FAILED = 7, // 提交失败
RESULT_UNKNOWN = 8 // 提交结果未知
}
export interface RefundAttachment { export interface RefundAttachment {
file_key: string file_key: string
file_name?: string file_name?: string
@@ -70,7 +83,7 @@ export interface Refund {
approval_provider?: 'wecom' | null approval_provider?: 'wecom' | null
approval_instance_id?: number | null approval_instance_id?: number | null
approval_source?: 'none' | 'legacy' | 'wecom' | null // 审批来源 approval_source?: 'none' | 'legacy' | 'wecom' | null // 审批来源
approval_status?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | null // 审批状态 approval_status?: RefundApprovalStatus | null // 审批状态
approval_status_name?: string | null // 审批状态名称 approval_status_name?: string | null // 审批状态名称
current_approver_summary?: string | null // 当前审批人摘要 current_approver_summary?: string | null // 当前审批人摘要
processing_status?: string | null // 业务处理状态 processing_status?: string | null // 业务处理状态

View File

@@ -7,7 +7,7 @@ import { normalizeApiError } from '@/utils/business/apiError'
import { syncCompatibleIdFields } from '@/utils/business/id' import { syncCompatibleIdFields } from '@/utils/business/id'
const axiosInstance = axios.create({ const axiosInstance = axios.create({
timeout: 15000, // 请求超时时间(毫秒) timeout: 90000, // 请求超时时间(毫秒)
baseURL: import.meta.env.DEV ? '' : import.meta.env.VITE_API_URL, // 开发服务器使用代理其他模式使用完整URL baseURL: import.meta.env.DEV ? '' : import.meta.env.VITE_API_URL, // 开发服务器使用代理其他模式使用完整URL
withCredentials: false, // 异步请求携带cookie withCredentials: false, // 异步请求携带cookie
transformRequest: [ transformRequest: [

View File

@@ -108,6 +108,7 @@
v-model="allocateForm.target_shop_id" v-model="allocateForm.target_shop_id"
:options="shopCascadeOptions" :options="shopCascadeOptions"
:props="shopCascadeProps" :props="shopCascadeProps"
:before-filter="handleAllocateShopBeforeFilter"
placeholder="请选择目标店铺" placeholder="请选择目标店铺"
clearable clearable
filterable filterable
@@ -1049,6 +1050,62 @@
const batchRealnamePolicyError = ref('') const batchRealnamePolicyError = ref('')
const exportDialogVisible = ref(false) const exportDialogVisible = ref(false)
const shopCascadeOptions = ref<any[]>([]) const shopCascadeOptions = ref<any[]>([])
let allocateShopSearchRequestId = 0
const mapShopCascadeNodes = (items: any[]) => {
const seen = new Set<number>()
return items.reduce<any[]>((nodes, item) => {
if (seen.has(item.id)) return nodes
seen.add(item.id)
nodes.push({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
})
return nodes
}, [])
}
const handleAllocateShopBeforeFilter = async (query: string) => {
const keyword = query.trim()
if (!keyword) return true
const requestId = ++allocateShopSearchRequestId
try {
const shopResponse = await ShopService.getShops({
page: 1,
page_size: 20,
shop_name: keyword
})
if (requestId !== allocateShopSearchRequestId) return false
if (shopResponse.code !== 0) {
shopCascadeOptions.value = []
return false
}
const parentIds = Array.from(
new Set((shopResponse.data.items || []).map((shop) => shop.parent_id ?? null))
)
const cascadeResponses = await Promise.all(
parentIds.map((parentId) =>
ShopService.getShopsCascade({
shop_name: keyword,
parent_id: parentId ?? undefined
})
)
)
if (requestId !== allocateShopSearchRequestId) return false
shopCascadeOptions.value = mapShopCascadeNodes(
cascadeResponses.flatMap((response) => (response.code === 0 ? response.data || [] : []))
)
return true
} catch (error) {
if (requestId === allocateShopSearchRequestId) shopCascadeOptions.value = []
console.error('搜索批量分配目标店铺失败:', error)
return false
}
}
const shopCascadeProps = { const shopCascadeProps = {
lazy: true, lazy: true,
checkStrictly: true, checkStrictly: true,
@@ -1059,12 +1116,7 @@
try { try {
const res = await ShopService.getShopsCascade({ parent_id: parentId }) const res = await ShopService.getShopsCascade({ parent_id: parentId })
if (res.code === 0) { if (res.code === 0) {
const nodes = (res.data || []).map((item: any) => ({ resolve(mapShopCascadeNodes(res.data || []))
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
resolve(nodes)
} else { } else {
resolve([]) resolve([])
} }
@@ -1081,11 +1133,7 @@
try { try {
const res = await ShopService.getShopsCascade({ parent_id: undefined }) const res = await ShopService.getShopsCascade({ parent_id: undefined })
if (res.code === 0) { if (res.code === 0) {
shopCascadeOptions.value = (res.data || []).map((item: any) => ({ shopCascadeOptions.value = mapShopCascadeNodes(res.data || [])
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
} }
} catch (error) { } catch (error) {
console.error('加载顶级店铺失败:', error) console.error('加载顶级店铺失败:', error)
@@ -2527,10 +2575,7 @@
const showEnterpriseDeviceAuthorizeDialog = async () => { const showEnterpriseDeviceAuthorizeDialog = async () => {
resetEnterpriseDeviceAuthorizeForm() resetEnterpriseDeviceAuthorizeForm()
await Promise.all([ await Promise.all([handleSearchEnterprise(), loadTopLevelShopCascadeOptions()])
handleSearchEnterprise(),
loadTopLevelShopCascadeOptions()
])
enterpriseDeviceAuthorizeDialogVisible.value = true enterpriseDeviceAuthorizeDialogVisible.value = true
enterpriseDeviceAuthorizeFormRef.value?.clearValidate() enterpriseDeviceAuthorizeFormRef.value?.clearValidate()
} }

View File

@@ -268,8 +268,8 @@
{{ createNewDeviceEmptyMessage }} {{ createNewDeviceEmptyMessage }}
</div> </div>
</ElFormItem> </ElFormItem>
<ElFormItem v-if="createDirectInheritedShopHint" label="归属提示"> <ElFormItem label="归属提示">
<div class="exchange-shop-hint">{{ createDirectInheritedShopHint }}</div> <div class="exchange-shop-hint">新资产会继承旧资产店铺</div>
</ElFormItem> </ElFormItem>
<ElFormItem v-if="createForm.flow_type === 'direct'" label="是否迁移数据"> <ElFormItem v-if="createForm.flow_type === 'direct'" label="是否迁移数据">
<ElSwitch v-model="createForm.migrate_data" /> <ElSwitch v-model="createForm.migrate_data" />
@@ -771,11 +771,6 @@
return undefined return undefined
} }
const createDirectInheritedShopHint = computed(() => {
const shopName = getShopDisplayName(createSelectedNewAsset.value)
return shopName ? `换货完成后将归属:${shopName}` : ''
})
const shipInheritedShopDisplayText = computed(() => { const shipInheritedShopDisplayText = computed(() => {
const shopName = getShopDisplayName(shipSelectedNewAsset.value) const shopName = getShopDisplayName(shipSelectedNewAsset.value)

View File

@@ -112,6 +112,7 @@
v-model="allocateForm.to_shop_id" v-model="allocateForm.to_shop_id"
:options="shopCascadeOptions" :options="shopCascadeOptions"
:props="shopCascadeProps" :props="shopCascadeProps"
:before-filter="handleAllocateShopBeforeFilter"
placeholder="请选择目标店铺" placeholder="请选择目标店铺"
clearable clearable
filterable filterable
@@ -1523,6 +1524,62 @@
// 店铺相关 // 店铺相关
const shopCascadeOptions = ref<any[]>([]) const shopCascadeOptions = ref<any[]>([])
let allocateShopSearchRequestId = 0
const mapShopCascadeNodes = (items: any[]) => {
const seen = new Set<number>()
return items.reduce<any[]>((nodes, item) => {
if (seen.has(item.id)) return nodes
seen.add(item.id)
nodes.push({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
})
return nodes
}, [])
}
const handleAllocateShopBeforeFilter = async (query: string) => {
const keyword = query.trim()
if (!keyword) return true
const requestId = ++allocateShopSearchRequestId
try {
const shopResponse = await ShopService.getShops({
page: 1,
page_size: 20,
shop_name: keyword
})
if (requestId !== allocateShopSearchRequestId) return false
if (shopResponse.code !== 0) {
shopCascadeOptions.value = []
return false
}
const parentIds = Array.from(
new Set((shopResponse.data.items || []).map((shop) => shop.parent_id ?? null))
)
const cascadeResponses = await Promise.all(
parentIds.map((parentId) =>
ShopService.getShopsCascade({
shop_name: keyword,
parent_id: parentId ?? undefined
})
)
)
if (requestId !== allocateShopSearchRequestId) return false
shopCascadeOptions.value = mapShopCascadeNodes(
cascadeResponses.flatMap((response) => (response.code === 0 ? response.data || [] : []))
)
return true
} catch (error) {
if (requestId === allocateShopSearchRequestId) shopCascadeOptions.value = []
console.error('搜索批量分配目标店铺失败:', error)
return false
}
}
const shopCascadeProps = { const shopCascadeProps = {
lazy: true, lazy: true,
checkStrictly: true, checkStrictly: true,
@@ -1533,12 +1590,7 @@
try { try {
const res = await ShopService.getShopsCascade({ parent_id: parentId }) const res = await ShopService.getShopsCascade({ parent_id: parentId })
if (res.code === 0) { if (res.code === 0) {
const nodes = (res.data || []).map((item: any) => ({ resolve(mapShopCascadeNodes(res.data || []))
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
resolve(nodes)
} else { } else {
resolve([]) resolve([])
} }
@@ -1555,11 +1607,7 @@
try { try {
const res = await ShopService.getShopsCascade({ parent_id: undefined }) const res = await ShopService.getShopsCascade({ parent_id: undefined })
if (res.code === 0) { if (res.code === 0) {
shopCascadeOptions.value = (res.data || []).map((item: any) => ({ shopCascadeOptions.value = mapShopCascadeNodes(res.data || [])
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
} }
} catch (error) { } catch (error) {
console.error('加载顶级店铺失败:', error) console.error('加载顶级店铺失败:', error)
@@ -2486,6 +2534,7 @@
return return
} }
allocateDialogVisible.value = true allocateDialogVisible.value = true
shopCascadeOptions.value = []
Object.assign(allocateForm, { Object.assign(allocateForm, {
selection_type: CardSelectionType.LIST, selection_type: CardSelectionType.LIST,
to_shop_id: undefined, to_shop_id: undefined,
@@ -2700,10 +2749,7 @@
const showEnterpriseCardRecallDialog = async () => { const showEnterpriseCardRecallDialog = async () => {
resetEnterpriseCardRecallForm() resetEnterpriseCardRecallForm()
await Promise.all([ await Promise.all([handleSearchEnterprise(), loadAllocateCarrierOptions()])
handleSearchEnterprise(),
loadAllocateCarrierOptions()
])
enterpriseCardRecallDialogVisible.value = true enterpriseCardRecallDialogVisible.value = true
enterpriseCardRecallFormRef.value?.clearValidate() enterpriseCardRecallFormRef.value?.clearValidate()
} }
@@ -2823,10 +2869,7 @@
selection_type: selection_type:
selectedCards.value.length > 0 ? CardSelectionType.LIST : CardSelectionType.FILTER selectedCards.value.length > 0 ? CardSelectionType.LIST : CardSelectionType.FILTER
}) })
await Promise.all([ await Promise.all([loadPackageSeriesList(), loadAllocateCarrierOptions()])
loadPackageSeriesList(),
loadAllocateCarrierOptions()
])
seriesBindingDialogVisible.value = true seriesBindingDialogVisible.value = true
seriesBindingFormRef.value?.clearValidate() seriesBindingFormRef.value?.clearValidate()
} }

View File

@@ -113,7 +113,13 @@
import DetailPage from '@/components/common/DetailPage.vue' import DetailPage from '@/components/common/DetailPage.vue'
import type { DetailSection } from '@/components/common/DetailPage.vue' import type { DetailSection } from '@/components/common/DetailPage.vue'
import { RefundService } from '@/api/modules' import { RefundService } from '@/api/modules'
import type { Refund, ResubmitRefundRequest, RefundAttachment } from '@/types/api' import {
RefundApprovalStatus,
RefundStatus,
type Refund,
type ResubmitRefundRequest,
type RefundAttachment
} from '@/types/api'
import { fenToYuan, formatDateTime, yuanToFen } from '@/utils/business/format' import { fenToYuan, formatDateTime, yuanToFen } from '@/utils/business/format'
import { toVoucherKeyList, getErrorMessage } from '@/utils/business' import { toVoucherKeyList, getErrorMessage } from '@/utils/business'
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue' import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
@@ -456,22 +462,14 @@
resubmitUploadRef.value?.clearFiles(false) resubmitUploadRef.value?.clearFiles(false)
} }
const canResubmit = (item: Refund) => { const canResubmit = (item: Refund) =>
const statusText = [item.approval_status, item.approval_status_name, item.status_name] item.status === RefundStatus.REJECTED ||
.filter(Boolean) item.status === RefundStatus.RETURNED ||
.join(' ') item.approval_status === RefundApprovalStatus.REJECTED ||
.toLowerCase() item.approval_status === RefundApprovalStatus.REVOKED ||
item.approval_status === RefundApprovalStatus.REVOKED_AFTER_APPROVED ||
return ( item.approval_status === RefundApprovalStatus.SUBMIT_FAILED ||
item.status === 3 || item.approval_status === RefundApprovalStatus.RESULT_UNKNOWN
statusText.includes('reject') ||
statusText.includes('revoke') ||
statusText.includes('delete') ||
statusText.includes('驳回') ||
statusText.includes('撤销') ||
statusText.includes('删除')
)
}
const handleShowResubmit = () => { const handleShowResubmit = () => {
if (!refund.value || !canResubmit(refund.value)) return if (!refund.value || !canResubmit(refund.value)) return

View File

@@ -150,6 +150,7 @@
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { import {
RefundStatus, RefundStatus,
RefundApprovalStatus,
type Refund, type Refund,
type RefundQueryParams, type RefundQueryParams,
type ResubmitRefundRequest, type ResubmitRefundRequest,
@@ -734,22 +735,14 @@
return keys.length ? keys : toVoucherKeyList(row.refund_voucher_key) return keys.length ? keys : toVoucherKeyList(row.refund_voucher_key)
} }
const canResubmit = (row: Refund) => { const canResubmit = (row: Refund) =>
const statusText = [row.approval_status, row.approval_status_name, row.status_name] row.status === RefundStatus.REJECTED ||
.filter(Boolean) row.status === RefundStatus.RETURNED ||
.join(' ') row.approval_status === RefundApprovalStatus.REJECTED ||
.toLowerCase() row.approval_status === RefundApprovalStatus.REVOKED ||
row.approval_status === RefundApprovalStatus.REVOKED_AFTER_APPROVED ||
return ( row.approval_status === RefundApprovalStatus.SUBMIT_FAILED ||
row.status === 3 || row.approval_status === RefundApprovalStatus.RESULT_UNKNOWN
statusText.includes('reject') ||
statusText.includes('revoke') ||
statusText.includes('delete') ||
statusText.includes('驳回') ||
statusText.includes('撤销') ||
statusText.includes('删除')
)
}
const canTriggerApproval = (row: Refund) => { const canTriggerApproval = (row: Refund) => {
const approvalStatusEmpty = row.approval_status === undefined || row.approval_status === null const approvalStatusEmpty = row.approval_status === undefined || row.approval_status === null