feat: 换货新资产归属继承提示

This commit is contained in:
luo
2026-07-20 10:50:23 +08:00
parent 2c8524485b
commit fac05db39a
7 changed files with 485 additions and 17 deletions

View File

@@ -0,0 +1,22 @@
# Change: 换货新资产归属继承提示
## Why
当前换货流程在选择新资产后,页面没有明确告知换货完成后的最终归属店铺,运营人员容易误以为可以手工选择目标店铺,进而对换货结果产生误判。
## What Changes
- 在换货创建入口、发货确认入口、换货详情页补充旧资产所属店铺和新资产继承归属店铺的展示。
- 保持现有换货创建、发货、确认完成接口和入参语义不变,不新增目标店铺选择控件或目标店铺提交字段。
- 扩展换货响应数据,返回 `inherited_shop_id``inherited_shop_name` 供前端统一展示最终归属店铺。
- 在创建、发货、确认完成流程中补充提交中禁用、防重复提交、后端归属校验失败提示,以及成功后的详情刷新。
## Impact
- Affected specs: `exchange-management`
- Related active changes: `update-exchange-flow-direct-type`(本提案建立在现有换货流程类型改造之上,需合并考虑)
- Affected code:
- `src/api/modules/exchange.ts`
- `src/views/asset-management/exchange-management/index.vue`
- `src/views/asset-management/exchange-management/detail.vue`
- 相关旧资产/新资产搜索结果类型或映射逻辑(如需补充店铺名称展示数据)

View File

@@ -0,0 +1,88 @@
## ADDED Requirements
### Requirement: 换货归属继承上下文展示
系统 SHALL 在换货创建和发货确认流程中展示旧资产当前所属店铺,并在用户选定新资产后以只读方式提示换货完成后的继承归属店铺,避免用户误认为可以手工指定目标店铺。
#### Scenario: 创建入口展示旧资产所属店铺
- **GIVEN** 用户进入现有换货创建入口
- **WHEN** 用户选择旧资产
- **THEN** 系统 MUST 显示该旧资产当前所属店铺名称
- **AND** 该所属店铺信息 MUST be read-only
#### Scenario: 选择新资产后展示继承归属提示
- **GIVEN** 用户正在换货创建或发货确认流程中操作
- **WHEN** 用户选择新资产
- **THEN** 系统 MUST 显示只读提示 `换货完成后将归属:{店铺名称}`
- **AND** `{店铺名称}` MUST 表示该新资产换货完成后的最终归属店铺
#### Scenario: 页面不提供目标店铺选择控件
- **WHEN** 用户查看换货创建或发货确认入口
- **THEN** 系统 MUST NOT 渲染目标店铺选择控件
- **AND** 用户 MUST NOT 能够手工覆盖继承归属店铺
### Requirement: 换货归属继承接口契约
系统 SHALL 复用现有换货创建、发货和确认完成接口,并在换货数据模型中返回继承后的店铺标识和店铺名称,供创建结果、发货结果、完成结果和详情页统一展示。
#### Scenario: 创建请求保持既有字段语义
- **WHEN** 前端调用 `POST /api/admin/exchanges`
- **THEN** 请求体 MUST 继续沿用旧资产、新资产、换货类型和资料迁移字段
- **AND** 请求体 MUST NOT 新增目标店铺字段
#### Scenario: 发货和完成请求不新增目标店铺字段
- **WHEN** 前端调用 `POST /api/admin/exchanges/{id}/ship``POST /api/admin/exchanges/{id}/complete`
- **THEN** 请求体 MUST NOT 新增目标店铺字段
- **AND** 后端 MUST 继续按现有换货流程处理归属继承
#### Scenario: 换货响应返回继承店铺字段
- **WHEN** 前端收到换货创建、发货、确认完成结果或读取换货详情
- **THEN** 返回的 `data` MUST 包含 `inherited_shop_id:int64``inherited_shop_name:string`
- **AND** `inherited_shop_name` MUST 表示换货完成后新资产最终归属的店铺名称
### Requirement: 换货详情继承归属展示
系统 SHALL 在换货详情页展示继承后的店铺名称,并在关键操作成功后刷新详情数据,保证页面展示的归属信息与后端一致。
#### Scenario: 详情页展示继承后的店铺名称
- **GIVEN** 用户进入换货详情页
- **WHEN** 详情数据加载成功
- **THEN** 页面 MUST 展示继承后的店铺名称
- **AND** 页面 MUST NOT 隐藏该归属字段
#### Scenario: 发货或确认完成成功后刷新详情
- **GIVEN** 用户完成换货发货或确认完成操作
- **WHEN** 接口返回成功
- **THEN** 系统 MUST 重新拉取该换货单详情
- **AND** 刷新后的详情 MUST 展示最新的继承归属店铺信息
### Requirement: 换货归属继承错误与提交状态处理
系统 SHALL 在新资产归属校验失败时直接展示后端错误,并在创建、发货、确认完成提交期间禁用操作按钮,防止重复提交,同时保证失败后可以重试。
#### Scenario: 新资产属于其他店铺时展示后端错误
- **GIVEN** 用户选择了不允许用于当前换货流程的新资产
- **WHEN** 后端返回“新资产属于其他店铺”或等价归属校验错误
- **THEN** 系统 MUST 直接展示后端错误信息
- **AND** 系统 MUST NOT 提供手工修改目标店铺的替代入口
#### Scenario: 提交期间禁用按钮防止重复提交
- **WHEN** 用户提交创建、发货或确认完成操作
- **THEN** 当前操作按钮 MUST 进入禁用或 loading 状态直到请求结束
- **AND** 在请求完成前的重复点击 MUST NOT 触发第二次提交
#### Scenario: 请求失败后恢复可重试状态
- **WHEN** 创建、发货或确认完成请求失败
- **THEN** 系统 MUST 恢复当前操作按钮可用状态
- **AND** 页面 MUST 保留当前已展示的资产与归属提示,便于用户修正后重试

View File

@@ -0,0 +1,22 @@
## 1. API Contract
- [x] 1.1 更新换货数据模型,补充 `inherited_shop_id``inherited_shop_name` 响应字段,并覆盖创建、发货、确认完成及详情读取场景。
- [x] 1.2 保持 `POST /api/admin/exchanges``POST /api/admin/exchanges/{id}/ship``POST /api/admin/exchanges/{id}/complete` 的既有入参语义,不新增目标店铺字段。
## 2. Exchange Create And Ship UI
- [x] 2.1 在换货创建入口中,选择旧资产后展示旧资产所属店铺。
- [x] 2.2 在换货创建入口和发货确认入口中,选择新资产后展示只读提示 `换货完成后将归属:{店铺名称}`
- [x] 2.3 确保上述页面不新增目标店铺选择控件,也不允许手工覆盖继承归属。
## 3. Exchange Detail And Submission States
- [x] 3.1 在换货详情页展示继承后的店铺名称,并在发货或确认完成成功后重新拉取详情。
- [x] 3.2 在创建、发货、确认完成提交期间禁用操作按钮,防止重复提交。
- [x] 3.3 在新资产归属校验失败时展示后端错误,并在失败后恢复可重试状态。
## 4. Verification
- [x] 4.1 验证创建、发货确认、详情三个入口均能正确展示旧资产所属店铺和继承归属店铺。
- [x] 4.2 验证加载中、失败提示、重复提交防护和成功后详情刷新的交互状态。
- [x] 4.3 运行相关前端校验,并执行 `openspec validate update-exchange-inherited-shop-hint --strict`

View File

@@ -54,9 +54,14 @@ export class AssetService extends BaseService {
*/ */
static resolveAsset( static resolveAsset(
identifier: string, identifier: string,
params?: AssetResolveParams params?: AssetResolveParams,
config?: Record<string, any>
): Promise<BaseResponse<AssetResolveResponse>> { ): Promise<BaseResponse<AssetResolveResponse>> {
return this.getOne<AssetResolveResponse>(`/api/admin/assets/resolve/${identifier}`, params) return this.getOne<AssetResolveResponse>(
`/api/admin/assets/resolve/${identifier}`,
params,
config
)
} }
/** /**

View File

@@ -47,6 +47,8 @@ export interface ExchangeResponse {
recipient_address?: string recipient_address?: string
express_company?: string express_company?: string
express_no?: string express_no?: string
inherited_shop_id?: number | null
inherited_shop_name?: string | null
remark?: string remark?: string
created_at: string created_at: string
updated_at: string updated_at: string
@@ -114,8 +116,8 @@ export class ExchangeService extends BaseService {
* POST /api/admin/exchanges/{id}/complete * POST /api/admin/exchanges/{id}/complete
* @param id 换货单ID * @param id 换货单ID
*/ */
static completeExchange(id: number): Promise<BaseResponse> { static completeExchange(id: number): Promise<BaseResponse<ExchangeResponse>> {
return this.post<BaseResponse>(`/api/admin/exchanges/${id}/complete`, {}) return this.post<BaseResponse<ExchangeResponse>>(`/api/admin/exchanges/${id}/complete`, {})
} }
/** /**

View File

@@ -45,6 +45,12 @@
const exchangeDetail = ref<ExchangeResponse | null>(null) const exchangeDetail = ref<ExchangeResponse | null>(null)
const exchangeId = ref<number>(0) const exchangeId = ref<number>(0)
const formatExchangeShopName = (shopName?: string | null, shopId?: number | null) => {
if (shopName) return shopName
if (shopId === null || shopId === 0) return '平台'
return '--'
}
const getStatusType = (status: number): TagProps['type'] => { const getStatusType = (status: number): TagProps['type'] => {
const types: Record<number, TagProps['type']> = { const types: Record<number, TagProps['type']> = {
1: 'warning', 1: 'warning',
@@ -96,6 +102,11 @@
label: '新资产标识符', label: '新资产标识符',
formatter: (value) => value || '--', formatter: (value) => value || '--',
prop: 'new_asset_identifier' prop: 'new_asset_identifier'
},
{
label: '继承归属店铺',
formatter: (_, data) =>
formatExchangeShopName(data.inherited_shop_name, data.inherited_shop_id)
} }
] ]
}) })

View File

@@ -170,6 +170,9 @@
</ElOption> </ElOption>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem v-if="createOldAssetShopDisplay" label="旧资产所属店铺">
<ElInput :model-value="createOldAssetShopDisplay" readonly disabled />
</ElFormItem>
<ElFormItem <ElFormItem
v-if="createForm.flow_type === 'direct' && createForm.old_asset_type === 'iot_card'" v-if="createForm.flow_type === 'direct' && createForm.old_asset_type === 'iot_card'"
label="新资产标识符" label="新资产标识符"
@@ -232,6 +235,9 @@
</ElOption> </ElOption>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem v-if="createDirectInheritedShopHint" label="归属提示">
<div class="exchange-shop-hint">{{ createDirectInheritedShopHint }}</div>
</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" />
<div style="margin-top: 4px; margin-left: 8px; font-size: 12px; color: #909399"> <div style="margin-top: 4px; margin-left: 8px; font-size: 12px; color: #909399">
@@ -250,7 +256,9 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<ElButton @click="createDialogVisible = false">取消</ElButton> <ElButton @click="createDialogVisible = false" :disabled="createLoading"
>取消</ElButton
>
<ElButton type="primary" @click="handleConfirmCreate" :loading="createLoading"> <ElButton type="primary" @click="handleConfirmCreate" :loading="createLoading">
确认创建 确认创建
</ElButton> </ElButton>
@@ -267,11 +275,70 @@
@closed="handleCloseShipDialog" @closed="handleCloseShipDialog"
> >
<ElForm ref="shipFormRef" :model="shipForm" :rules="shipRules" label-width="120px"> <ElForm ref="shipFormRef" :model="shipForm" :rules="shipRules" label-width="120px">
<ElFormItem label="新资产标识符" prop="new_identifier"> <ElFormItem
<ElInput v-if="shipAssetType === 'iot_card'"
label="新资产标识符"
prop="new_identifier"
>
<ElSelect
v-model="shipForm.new_identifier" v-model="shipForm.new_identifier"
placeholder="请输入新资产标识符(ICCID/虚拟号/IMEI/SN)" filterable
/> remote
reserve-keyword
placeholder="请输入ICCID搜索"
:remote-method="searchShipNewIotCards"
:loading="newCardSearchLoading"
style="width: 100%"
clearable
>
<ElOption
v-for="card in newIotCardOptions"
:key="card.id"
:label="`${card.iccid} (${card.msisdn || '无接入号'})`"
:value="card.iccid"
>
<div style="display: flex; justify-content: space-between">
<span>{{ card.iccid }}</span>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
{{ card.msisdn || '无接入号' }}
</span>
</div>
</ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem
v-else-if="shipAssetType === 'device'"
label="新资产标识符"
prop="new_identifier"
>
<ElSelect
v-model="shipForm.new_identifier"
filterable
remote
reserve-keyword
placeholder="请输入虚拟号或IMEI搜索"
:remote-method="searchShipNewDevices"
:loading="newDeviceSearchLoading"
style="width: 100%"
clearable
>
<ElOption
v-for="device in newDeviceOptions"
:key="device.id"
:label="`${device.virtual_no} (${device.device_name || device.imei})`"
:value="device.virtual_no"
>
<div style="display: flex; justify-content: space-between">
<span>{{ device.virtual_no }}</span>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
{{ device.device_name || device.imei }}
</span>
</div>
</ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem v-if="shipInheritedShopDisplayText" label="归属提示">
<div class="exchange-shop-hint">{{ shipInheritedShopDisplayText }}</div>
</ElFormItem> </ElFormItem>
<ElFormItem label="快递公司" prop="express_company"> <ElFormItem label="快递公司" prop="express_company">
<ElInput v-model="shipForm.express_company" placeholder="请输入快递公司" /> <ElInput v-model="shipForm.express_company" placeholder="请输入快递公司" />
@@ -289,7 +356,7 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<ElButton @click="shipDialogVisible = false">取消</ElButton> <ElButton @click="shipDialogVisible = false" :disabled="shipLoading">取消</ElButton>
<ElButton type="primary" @click="handleConfirmShip" :loading="shipLoading"> <ElButton type="primary" @click="handleConfirmShip" :loading="shipLoading">
确认发货 确认发货
</ElButton> </ElButton>
@@ -411,6 +478,7 @@
const newCardSearchLoading = ref(false) const newCardSearchLoading = ref(false)
const newDeviceOptions = ref<Device[]>([]) const newDeviceOptions = ref<Device[]>([])
const newDeviceSearchLoading = ref(false) const newDeviceSearchLoading = ref(false)
const completeLoadingId = ref<number | null>(null)
// 发货表单 // 发货表单
const shipForm = reactive({ const shipForm = reactive({
@@ -426,6 +494,88 @@
express_no: [{ required: true, message: '请输入快递单号', trigger: 'blur' }] express_no: [{ required: true, message: '请输入快递单号', trigger: 'blur' }]
}) })
type ShopSelectableOption = {
shop_id?: number | null
shop_name?: string | null
}
const getShopDisplayName = (option?: ShopSelectableOption | null) => {
if (!option) return ''
if (option.shop_name) return option.shop_name
if (option.shop_id === null || option.shop_id === 0) return '平台'
return ''
}
const createSelectedOldAsset = computed<ShopSelectableOption | null>(() => {
if (!createForm.old_identifier) return null
if (createForm.old_asset_type === 'iot_card') {
return (
oldIotCardOptions.value.find((card) => card.iccid === createForm.old_identifier) || null
)
}
if (createForm.old_asset_type === 'device') {
return (
oldDeviceOptions.value.find((device) => device.virtual_no === createForm.old_identifier) ||
null
)
}
return null
})
const createSelectedNewAsset = computed<ShopSelectableOption | null>(() => {
if (!createForm.new_identifier) return null
if (createForm.old_asset_type === 'iot_card') {
return (
newIotCardOptions.value.find((card) => card.iccid === createForm.new_identifier) || null
)
}
if (createForm.old_asset_type === 'device') {
return (
newDeviceOptions.value.find((device) => device.virtual_no === createForm.new_identifier) ||
null
)
}
return null
})
const shipAssetType = computed(() => currentExchangeRow.value?.old_asset_type || '')
const shipSelectedNewAsset = computed<ShopSelectableOption | null>(() => {
if (!shipForm.new_identifier) return null
if (shipAssetType.value === 'iot_card') {
return newIotCardOptions.value.find((card) => card.iccid === shipForm.new_identifier) || null
}
if (shipAssetType.value === 'device') {
return (
newDeviceOptions.value.find((device) => device.virtual_no === shipForm.new_identifier) ||
null
)
}
return null
})
const createOldAssetShopDisplay = computed(() => getShopDisplayName(createSelectedOldAsset.value))
const createDirectInheritedShopHint = computed(() => {
const shopName = getShopDisplayName(createSelectedNewAsset.value)
return shopName ? `换货完成后将归属:${shopName}` : ''
})
const shipInheritedShopDisplayText = computed(() => {
const shopName = getShopDisplayName(shipSelectedNewAsset.value)
return shopName ? `换货完成后将归属:${shopName}` : ''
})
// 分页 // 分页
const pagination = reactive({ const pagination = reactive({
page: 1, page: 1,
@@ -732,11 +882,25 @@
} }
} }
const refreshExchangeDetail = async (id: number) => {
try {
const res = await ExchangeService.getExchangeDetail(id)
if (res.code === 0 && res.data) {
return res.data
}
} catch (error) {
console.error('刷新换货详情失败:', error)
}
return null
}
// 加载默认IoT卡列表 // 加载默认IoT卡列表
const loadDefaultIotCards = async () => { const loadDefaultIotCards = async () => {
oldCardSearchLoading.value = true oldCardSearchLoading.value = true
try { try {
const res = await CardService.getStandaloneIotCards({ const res = await CardService.getStandaloneIotCards({
is_standalone: true,
page: 1, page: 1,
page_size: 10 page_size: 10
}) })
@@ -787,6 +951,7 @@
oldCardSearchLoading.value = true oldCardSearchLoading.value = true
try { try {
const res = await CardService.getStandaloneIotCards({ const res = await CardService.getStandaloneIotCards({
is_standalone: true,
keyword: query, keyword: query,
page: 1, page: 1,
page_size: 20 page_size: 20
@@ -845,6 +1010,36 @@
newCardSearchLoading.value = true newCardSearchLoading.value = true
try { try {
const res = await CardService.getStandaloneIotCards({ const res = await CardService.getStandaloneIotCards({
is_standalone: true,
keyword: query,
page: 1,
page_size: 20
})
if (res.code === 0 && res.data) {
newIotCardOptions.value = res.data.items || []
if (newIotCardOptions.value.length === 0) {
ElMessage.info('未找到匹配的IoT卡')
}
}
} catch (error) {
console.error('搜索IoT卡失败:', error)
newIotCardOptions.value = []
} finally {
newCardSearchLoading.value = false
}
}
const searchShipNewIotCards = async (query: string) => {
if (!query) {
await loadDefaultShipNewIotCards()
return
}
newCardSearchLoading.value = true
try {
const res = await CardService.getStandaloneIotCards({
is_standalone: true,
status: 1,
keyword: query, keyword: query,
page: 1, page: 1,
page_size: 20 page_size: 20
@@ -868,6 +1063,27 @@
newCardSearchLoading.value = true newCardSearchLoading.value = true
try { try {
const res = await CardService.getStandaloneIotCards({ const res = await CardService.getStandaloneIotCards({
is_standalone: true,
page: 1,
page_size: 10
})
if (res.code === 0 && res.data) {
newIotCardOptions.value = res.data.items || []
}
} catch (error) {
console.error('加载IoT卡列表失败:', error)
newIotCardOptions.value = []
} finally {
newCardSearchLoading.value = false
}
}
const loadDefaultShipNewIotCards = async () => {
newCardSearchLoading.value = true
try {
const res = await CardService.getStandaloneIotCards({
is_standalone: true,
status: 1,
page: 1, page: 1,
page_size: 10 page_size: 10
}) })
@@ -911,6 +1127,34 @@
} }
} }
const searchShipNewDevices = async (query: string) => {
if (!query) {
await loadDefaultShipNewDevices()
return
}
newDeviceSearchLoading.value = true
try {
const res = await DeviceService.getDevices({
status: 1,
keyword: query,
page: 1,
page_size: 20
})
if (res.code === 0 && res.data) {
newDeviceOptions.value = res.data.items || []
if (newDeviceOptions.value.length === 0) {
ElMessage.info('未找到匹配的设备')
}
}
} catch (error) {
console.error('搜索设备失败:', error)
newDeviceOptions.value = []
} finally {
newDeviceSearchLoading.value = false
}
}
// 加载默认新设备列表 // 加载默认新设备列表
const loadDefaultNewDevices = async () => { const loadDefaultNewDevices = async () => {
newDeviceSearchLoading.value = true newDeviceSearchLoading.value = true
@@ -930,10 +1174,29 @@
} }
} }
const loadDefaultShipNewDevices = async () => {
newDeviceSearchLoading.value = true
try {
const res = await DeviceService.getDevices({
status: 1,
page: 1,
page_size: 10
})
if (res.code === 0 && res.data) {
newDeviceOptions.value = res.data.items || []
}
} catch (error) {
console.error('加载设备列表失败:', error)
newDeviceOptions.value = []
} finally {
newDeviceSearchLoading.value = false
}
}
// 确认创建 // 确认创建
const handleConfirmCreate = () => { const handleConfirmCreate = () => {
createFormRef.value?.validate(async (valid) => { createFormRef.value?.validate(async (valid) => {
if (!valid) return if (!valid || createLoading.value) return
createLoading.value = true createLoading.value = true
try { try {
@@ -961,7 +1224,10 @@
ElMessage.success('换货单创建成功') ElMessage.success('换货单创建成功')
createDialogVisible.value = false createDialogVisible.value = false
await loadExchangeList() await loadExchangeList()
return
} }
ElMessage.error(res.msg || '换货单创建失败')
} catch (error) { } catch (error) {
console.error('创建换货单失败:', error) console.error('创建换货单失败:', error)
} finally { } finally {
@@ -1081,29 +1347,44 @@
} }
// 发货 // 发货
const handleShipExchange = (row: ExchangeResponse) => { const handleShipExchange = async (row: ExchangeResponse) => {
currentExchangeRow.value = row currentExchangeRow.value = row
// 重置表单 // 重置表单
shipForm.new_identifier = '' shipForm.new_identifier = ''
shipForm.express_company = '' shipForm.express_company = ''
shipForm.express_no = '' shipForm.express_no = ''
shipForm.migrate_data = true shipForm.migrate_data = true
newIotCardOptions.value = []
newDeviceOptions.value = []
shipDialogVisible.value = true shipDialogVisible.value = true
if (row.old_asset_type === 'iot_card') {
await loadDefaultShipNewIotCards()
} else if (row.old_asset_type === 'device') {
await loadDefaultShipNewDevices()
}
} }
// 确认发货 // 确认发货
const handleConfirmShip = () => { const handleConfirmShip = () => {
shipFormRef.value?.validate(async (valid) => { shipFormRef.value?.validate(async (valid) => {
if (!valid || !currentExchangeRow.value) return if (!valid || !currentExchangeRow.value || shipLoading.value) return
shipLoading.value = true shipLoading.value = true
try { try {
const res = await ExchangeService.shipExchange(currentExchangeRow.value.id, shipForm) const res = await ExchangeService.shipExchange(currentExchangeRow.value.id, shipForm)
if (res.code === 0) { if (res.code === 0) {
const latestDetail = await refreshExchangeDetail(currentExchangeRow.value.id)
if (latestDetail) {
currentExchangeRow.value = latestDetail
}
ElMessage.success('发货成功') ElMessage.success('发货成功')
shipDialogVisible.value = false shipDialogVisible.value = false
await loadExchangeList() await loadExchangeList()
return
} }
ElMessage.error(res.msg || '发货失败')
} catch (error) { } catch (error) {
console.error('发货失败:', error) console.error('发货失败:', error)
} finally { } finally {
@@ -1115,25 +1396,52 @@
// 关闭发货对话框 // 关闭发货对话框
const handleCloseShipDialog = () => { const handleCloseShipDialog = () => {
shipFormRef.value?.resetFields() shipFormRef.value?.resetFields()
currentExchangeRow.value = null
newIotCardOptions.value = []
newDeviceOptions.value = []
} }
// 确认完成 // 确认完成
const handleCompleteExchange = (row: ExchangeResponse) => { const handleCompleteExchange = (row: ExchangeResponse) => {
if (completeLoadingId.value === row.id) return
completeLoadingId.value = row.id
ElMessageBox.confirm('确定要确认换货完成吗?', '确认操作', { ElMessageBox.confirm('确定要确认换货完成吗?', '确认操作', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning',
}) beforeClose: async (action, instance, done) => {
.then(async () => { if (action !== 'confirm') {
done()
return
}
instance.confirmButtonLoading = true
instance.confirmButtonText = '提交中...'
try { try {
const res = await ExchangeService.completeExchange(row.id) const res = await ExchangeService.completeExchange(row.id)
if (res.code === 0) { if (res.code === 0) {
ElMessage.success('操作成功') await refreshExchangeDetail(row.id)
await loadExchangeList() await loadExchangeList()
ElMessage.success('操作成功')
done()
return
} }
ElMessage.error(res.msg || '确认完成失败')
} catch (error) { } catch (error) {
console.error('确认完成失败:', error) console.error('确认完成失败:', error)
} finally {
instance.confirmButtonLoading = false
instance.confirmButtonText = '确定'
} }
}
})
.then(() => {})
.finally(() => {
completeLoadingId.value = null
}) })
.catch(() => {}) .catch(() => {})
} }
@@ -1168,5 +1476,15 @@
<style lang="scss" scoped> <style lang="scss" scoped>
.exchange-management-page { .exchange-management-page {
height: 100%; height: 100%;
.exchange-shop-hint {
width: 100%;
padding: 10px 12px;
line-height: 1.5;
color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-7);
border-radius: 6px;
}
} }
</style> </style>