Commit c03775b8 by renyizhao

算力资源改造完成

parent f3883e76
...@@ -6,10 +6,11 @@ export interface ResourceOrder { ...@@ -6,10 +6,11 @@ export interface ResourceOrder {
id: number; // 订单ID id: number; // 订单ID
userId?: number; // 下单用户ID userId?: number; // 下单用户ID
mobile?: string; // 下单用户手机号 mobile?: string; // 下单用户手机号
nickname?: string; // 下单用户昵称
skuId?: number; // 算力资源SKU ID skuId?: number; // 算力资源SKU ID
spuName?: string; // 算力资源名称(下单时快照) spuName?: string; // 算力资源名称(下单时快照)
orderNo?: string; // 订单编号 orderNo?: string; // 订单编号
status: number; // 订单状态:0=待支付,1=已支付,2=已取消 status: number; // 订单状态:0=待支付,5=待发货,6=已发货,2=已结束,3=已取消,7=待退款,4=已退款
marketPrice: number; // 市场价格(分) marketPrice: number; // 市场价格(分)
paymentPrice?: number; // 实付金额(分) paymentPrice?: number; // 实付金额(分)
payOrderId: number; // 支付订单编号 payOrderId: number; // 支付订单编号
...@@ -22,10 +23,40 @@ export interface ResourceOrder { ...@@ -22,10 +23,40 @@ export interface ResourceOrder {
resourceStatus: number; // 算力资源状态:[0]未启用,[1]使用中,[2]已释放 resourceStatus: number; // 算力资源状态:[0]未启用,[1]使用中,[2]已释放
refundStatus: number; // 退款状态 refundStatus: number; // 退款状态
refundPrice: string; // 退款金额 refundPrice: string; // 退款金额
refundTime?: string | Dayjs; // 退款时间(阶段 D 新增,客服标已退款时填写)
deliveryIp?: string; // 发货 IP(阶段 C 新增)
deliveryUsername?: string; // 发货初始用户名(阶段 C 新增)
deliveryPassword?: string; // 发货初始密码(阶段 C 新增)
deliveredTime?: string | Dayjs; // 发货时间(阶段 C 新增)
deliverOperatorId?: number; // 发货操作人 ID(阶段 C 新增)
deliverOperatorName?: string; // 发货操作人姓名(阶段 C 新增)
invoiceStatus: number; // 开票状态:[0]未开 [1]开票中 [2]已开票 invoiceStatus: number; // 开票状态:[0]未开 [1]开票中 [2]已开票
invoiceUrl: string; // 发票链接 invoiceUrl: string; // 发票链接
createTime: string | Dayjs; // 创建时间
} }
/** 商家发货 Request VO */
export interface ResourceOrderDeliverReqVO {
orderId: number;
deliveryIp: string;
deliveryUsername: string;
deliveryPassword: string;
deliverRemark?: string;
}
/** 客服取消订单 Request VO */
export interface ResourceOrderAdminCancelReqVO {
orderId: number;
reason: string;
}
/** 客服标记已退款 Request VO */
export interface ResourceOrderMarkRefundedReqVO {
orderId: number;
refundPrice?: number; // 可选;null / 0 视为全额
refundRemark?: string;
}
// 算力资源订单 API // 算力资源订单 API
export const ResourceOrderApi = { export const ResourceOrderApi = {
// 查询算力资源订单分页 // 查询算力资源订单分页
...@@ -66,5 +97,20 @@ export const ResourceOrderApi = { ...@@ -66,5 +97,20 @@ export const ResourceOrderApi = {
// 开具发票 // 开具发票
issueInvoice: async (data: any) => { issueInvoice: async (data: any) => {
return await request.put({ url: `/compute/resource-order/issue-invoice`, data }) return await request.put({ url: `/compute/resource-order/issue-invoice`, data })
},
// 商家发货(阶段 C)
deliverResourceOrder: async (data: ResourceOrderDeliverReqVO) => {
return await request.post({ url: `/compute/resource-order/deliver`, data })
},
// 客服取消待发货订单(阶段 D)
adminCancelResourceOrder: async (data: ResourceOrderAdminCancelReqVO) => {
return await request.post({ url: `/compute/resource-order/admin-cancel`, data })
},
// 客服标记已退款(阶段 D)
markRefundedResourceOrder: async (data: ResourceOrderMarkRefundedReqVO) => {
return await request.post({ url: `/compute/resource-order/mark-refunded`, data })
} }
} }
\ No newline at end of file
<template> <template>
<Dialog title="订单详情" v-model="dialogVisible" width="720px"> <Dialog title="订单详情" v-model="dialogVisible" width="820px">
<el-descriptions :column="2" border v-loading="loading" label-width="130px"> <el-descriptions :column="2" border v-loading="loading" label-width="130px">
<el-descriptions-item label="订单编号" :span="2"> <el-descriptions-item label="订单编号" :span="2">
{{ detail.orderNo }} {{ detail.orderNo }}
...@@ -17,32 +17,60 @@ ...@@ -17,32 +17,60 @@
<dict-tag :type="DICT_TYPE.COMPUTE_RESOURCE_ORDER_STATUS" :value="detail.status" /> <dict-tag :type="DICT_TYPE.COMPUTE_RESOURCE_ORDER_STATUS" :value="detail.status" />
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="市场价格"> <el-descriptions-item label="市场价格">
¥{{ fenToYuan(detail.marketPrice) }} ¥{{ fenToYuan(detail.marketPrice ?? 0) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="实付金额"> <el-descriptions-item label="实付金额">
¥{{ fenToYuan(detail.paymentPrice) }} ¥{{ fenToYuan(detail.paymentPrice ?? 0) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="支付渠道"> <el-descriptions-item label="支付渠道">
{{ detail.payChannelCode }} {{ detail.payChannelCode }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="支付时间"> <el-descriptions-item label="支付时间">
{{ detail.payTime ? formatDate(detail.payTime) : '-' }} {{ formatDateTime(detail.payTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="租赁开始时间"> <el-descriptions-item label="租赁开始时间">
{{ detail.rentStartTime ? formatDate(detail.rentStartTime) : '-' }} {{ formatDateTime(detail.rentStartTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="租赁结束时间"> <el-descriptions-item label="租赁结束时间">
{{ detail.rentEndTime ? formatDate(detail.rentEndTime) : '-' }} {{ formatDateTime(detail.rentEndTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="取消时间"> <el-descriptions-item label="取消时间">
{{ detail.cancelTime ? formatDate(detail.cancelTime) : '-' }} {{ formatDateTime(detail.cancelTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="退款状态"> <!-- 阶段 C:发货信息区(仅 DELIVERED 状态显示) -->
<dict-tag :type="DICT_TYPE.COMPUTE_RESOURCE_REFUND_STATUS" :value="detail.refundStatus" /> <template v-if="detail.status === 6">
</el-descriptions-item> <el-descriptions-item label="服务器 IP" :span="2">
<el-descriptions-item label="退款金额"> {{ detail.deliveryIp || '-' }}
{{ detail.refundPrice ? '¥' + fenToYuan(Number(detail.refundPrice)) : '-' }} </el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="初始用户名" :span="2">
{{ detail.deliveryUsername || '-' }}
</el-descriptions-item>
<el-descriptions-item label="初始密码" :span="2">
<span v-if="showPassword">{{ detail.deliveryPassword || '-' }}</span>
<el-button v-else link type="primary" @click="showPassword = true">显示密码</el-button>
</el-descriptions-item>
<el-descriptions-item label="发货时间">
{{ formatDateTime(detail.deliveredTime) }}
</el-descriptions-item>
<el-descriptions-item label="发货人">
{{ detail.deliverOperatorName || '-' }}
</el-descriptions-item>
</template>
<!-- 阶段 D:退款信息(仅 PENDING_REFUND 或 REFUNDED 显示) -->
<template v-if="detail.status === 7 || detail.status === 4">
<el-descriptions-item label="退款状态">
<dict-tag
:type="DICT_TYPE.COMPUTE_RESOURCE_REFUND_STATUS"
:value="detail.refundStatus"
/>
</el-descriptions-item>
<el-descriptions-item label="退款金额">
{{ detail.refundPrice ? '¥' + fenToYuan(Number(detail.refundPrice)) : '-' }}
</el-descriptions-item>
<el-descriptions-item label="退款时间" :span="2">
{{ formatDateTime(detail.refundTime) }}
</el-descriptions-item>
</template>
<el-descriptions-item label="开票状态"> <el-descriptions-item label="开票状态">
<dict-tag :type="DICT_TYPE.TRADE_INVOICE_STATUS" :value="detail.invoiceStatus" /> <dict-tag :type="DICT_TYPE.TRADE_INVOICE_STATUS" :value="detail.invoiceStatus" />
</el-descriptions-item> </el-descriptions-item>
...@@ -56,13 +84,49 @@ ...@@ -56,13 +84,49 @@
{{ detail.remark || '-' }} {{ detail.remark || '-' }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="创建时间"> <el-descriptions-item label="创建时间">
{{ detail.createTime ? formatDate(detail.createTime) : '-' }} {{ formatDateTime(detail.createTime) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<template #footer> <template #footer>
<!-- 阶段 C:商家发货(仅 PENDING_DELIVERY) -->
<el-button
v-if="detail.status === 5"
v-hasPermi="['compute:resource-order:deliver']"
type="primary"
@click="handleDeliver"
>
去发货
</el-button>
<!-- 阶段 D:客服取消(仅 PENDING_DELIVERY) -->
<el-button
v-if="detail.status === 5"
v-hasPermi="['compute:resource-order:admin-cancel']"
type="warning"
@click="handleAdminCancel"
>
客服取消
</el-button>
<!-- 阶段 D:标记已退款(仅 PENDING_REFUND) -->
<el-button
v-if="detail.status === 7"
v-hasPermi="['compute:resource-order:mark-refunded']"
type="success"
@click="handleMarkRefunded"
>
标记已退款
</el-button>
<el-button @click="dialogVisible = false">关 闭</el-button> <el-button @click="dialogVisible = false">关 闭</el-button>
</template> </template>
</Dialog> </Dialog>
<!-- 阶段 C:商家发货弹窗 -->
<ResourceOrderDeliverForm ref="deliverFormRef" @success="reloadDetail" />
<!-- 阶段 D:客服取消弹窗 -->
<ResourceOrderAdminCancelForm ref="adminCancelFormRef" @success="reloadDetail" />
<!-- 阶段 D:标记已退款弹窗 -->
<ResourceOrderMarkRefundedForm ref="markRefundedFormRef" @success="reloadDetail" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
...@@ -70,18 +134,43 @@ import { ResourceOrderApi, ResourceOrder } from '@/api/compute/resourceorder' ...@@ -70,18 +134,43 @@ import { ResourceOrderApi, ResourceOrder } from '@/api/compute/resourceorder'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { fenToYuan } from '@/utils' import { fenToYuan } from '@/utils'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import ResourceOrderDeliverForm from './form/ResourceOrderDeliverForm.vue'
import ResourceOrderAdminCancelForm from './form/ResourceOrderAdminCancelForm.vue'
import ResourceOrderMarkRefundedForm from './form/ResourceOrderMarkRefundedForm.vue'
/** 算力资源订单详情 */ /** 算力资源订单详情 */
defineOptions({ name: 'ResourceOrderDetail' }) defineOptions({ name: 'ResourceOrderDetail' })
const dialogVisible = ref(false) const dialogVisible = ref(false)
const loading = ref(false) const loading = ref(false)
const detail = ref<Partial<ResourceOrder>>({}) const detail = ref<any>({}) // 后端返回完整订单对象,用 any 避免 Partial 严格推断
const showPassword = ref(false) // 密码默认隐藏
const deliverFormRef = ref() // 发货弹窗
const adminCancelFormRef = ref() // 客服取消弹窗
const markRefundedFormRef = ref() // 标记已退款弹窗
let currentOrderId: number | undefined = undefined
/**
* 格式化日期(兼容 string / Dayjs / Date / undefined / null)
*/
const formatDateTime = (val: any): string => {
if (!val) return '-'
if (val instanceof Date) return formatDate(val)
if (typeof val === 'string') return formatDate(new Date(val))
if (typeof val === 'number') return formatDate(new Date(val))
// Dayjs 对象
if (typeof val.format === 'function') return formatDate(val.toDate())
return '-'
}
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (id: number) => { const open = async (id: number) => {
dialogVisible.value = true dialogVisible.value = true
loading.value = true loading.value = true
showPassword.value = false
currentOrderId = id
try { try {
detail.value = await ResourceOrderApi.getResourceOrder(id) detail.value = await ResourceOrderApi.getResourceOrder(id)
} finally { } finally {
...@@ -89,5 +178,31 @@ const open = async (id: number) => { ...@@ -89,5 +178,31 @@ const open = async (id: number) => {
} }
} }
/** 操作后重新拉详情 */
const reloadDetail = async () => {
if (!currentOrderId) return
loading.value = true
try {
detail.value = await ResourceOrderApi.getResourceOrder(currentOrderId)
} finally {
loading.value = false
}
}
/** 阶段 C:打开发货弹窗 */
const handleDeliver = () => {
deliverFormRef.value?.open(detail.value as ResourceOrder)
}
/** 阶段 D:打开客服取消弹窗 */
const handleAdminCancel = () => {
adminCancelFormRef.value?.open(detail.value as ResourceOrder)
}
/** 阶段 D:打开标记已退款弹窗 */
const handleMarkRefunded = () => {
markRefundedFormRef.value?.open(detail.value as ResourceOrder)
}
defineExpose({ open }) defineExpose({ open })
</script> </script>
<template>
<Dialog title="客服取消订单" v-model="dialogVisible" width="500px">
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="订单编号">
<span>{{ formData.orderNo }}</span>
</el-form-item>
<el-form-item label="商品名称">
<span>{{ formData.spuName }}</span>
</el-form-item>
<el-alert
type="warning"
:closable="false"
show-icon
title="取消后客户将进入待退款状态,需客服人工退款"
/>
<el-form-item label="取消原因" prop="reason" style="margin-top: 12px">
<el-input
v-model="formData.reason"
type="textarea"
:rows="3"
placeholder="请输入取消原因(5-200 字)"
maxlength="200"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="warning" @click="submitForm">确认取消</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ResourceOrderApi, ResourceOrder } from '@/api/compute/resourceorder'
defineOptions({ name: 'ResourceOrderAdminCancelForm' })
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗是否展示
const formRef = ref() // 表单 Ref
const formLoading = ref(false) // 表单的加载中
interface AdminCancelFormData {
orderId?: number
orderNo: string
spuName: string
reason: string
}
const formData = reactive<AdminCancelFormData>({
orderId: undefined,
orderNo: '',
spuName: '',
reason: ''
})
const rules = {
reason: [
{ required: true, message: '取消原因不能为空', trigger: 'blur' },
{ min: 5, max: 200, message: '原因长度 5-200 字', trigger: 'blur' }
]
}
const emit = defineEmits(['success'])
/** 重置表单 */
const resetForm = () => {
formData.orderId = undefined
formData.orderNo = ''
formData.spuName = ''
formData.reason = ''
formRef.value?.resetFields()
}
/** 打开弹窗 */
const open = (row: ResourceOrder) => {
resetForm()
formData.orderId = row.id
formData.orderNo = row.orderNo ?? ''
formData.spuName = row.spuName ?? ''
dialogVisible.value = true
}
/** 提交表单 */
const submitForm = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
} catch {
return
}
if (!formData.orderId) return
// 二次确认
try {
await message.confirm('确认取消该订单?取消后客户将进入待退款状态')
} catch {
return
}
formLoading.value = true
try {
await ResourceOrderApi.adminCancelResourceOrder({
orderId: formData.orderId,
reason: formData.reason
})
message.success('已取消,等待退款')
dialogVisible.value = false
emit('success', true)
} catch (error) {
console.error(error)
} finally {
formLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<Dialog title="商家发货" v-model="dialogVisible" width="500px">
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="订单编号">
<span>{{ formData.orderNo }}</span>
</el-form-item>
<el-form-item label="商品名称">
<span>{{ formData.spuName }}</span>
</el-form-item>
<el-form-item label="服务器 IP" prop="deliveryIp">
<el-input v-model="formData.deliveryIp" placeholder="请输入服务器 IP" />
</el-form-item>
<el-form-item label="初始用户名" prop="deliveryUsername">
<el-input v-model="formData.deliveryUsername" placeholder="请输入初始用户名" />
</el-form-item>
<el-form-item label="初始密码" prop="deliveryPassword">
<el-input
v-model="formData.deliveryPassword"
placeholder="请输入初始密码"
show-password
/>
</el-form-item>
<el-form-item label="发货备注">
<el-input
v-model="formData.deliverRemark"
type="textarea"
:rows="2"
placeholder="选填,如:已预装 CentOS 7.9"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">确认发货</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ResourceOrderApi, ResourceOrder } from '@/api/compute/resourceorder'
defineOptions({ name: 'ResourceOrderDeliverForm' })
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗是否展示
const formRef = ref() // 表单 Ref
const formLoading = ref(false) // 表单的加载中
interface DeliverFormData {
orderId?: number
orderNo: string
spuName: string
deliveryIp: string
deliveryUsername: string
deliveryPassword: string
deliverRemark: string
}
const formData = reactive<DeliverFormData>({
orderId: undefined,
orderNo: '',
spuName: '',
deliveryIp: '',
deliveryUsername: '',
deliveryPassword: '',
deliverRemark: ''
})
const rules = {
deliveryIp: [{ required: true, message: '服务器 IP 不能为空', trigger: 'blur' }],
deliveryUsername: [{ required: true, message: '初始用户名不能为空', trigger: 'blur' }],
deliveryPassword: [{ required: true, message: '初始密码不能为空', trigger: 'blur' }]
}
const emit = defineEmits(['success'])
/** 重置表单 */
const resetForm = () => {
formData.orderId = undefined
formData.orderNo = ''
formData.spuName = ''
formData.deliveryIp = ''
formData.deliveryUsername = ''
formData.deliveryPassword = ''
formData.deliverRemark = ''
formRef.value?.resetFields()
}
/** 打开弹窗 */
const open = (row: ResourceOrder) => {
resetForm()
formData.orderId = row.id
formData.orderNo = row.orderNo ?? ''
formData.spuName = row.spuName ?? ''
dialogVisible.value = true
}
/** 提交表单 */
const submitForm = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
} catch {
return
}
if (!formData.orderId) return
formLoading.value = true
try {
await ResourceOrderApi.deliverResourceOrder({
orderId: formData.orderId,
deliveryIp: formData.deliveryIp,
deliveryUsername: formData.deliveryUsername,
deliveryPassword: formData.deliveryPassword,
deliverRemark: formData.deliverRemark || undefined
})
message.success('发货成功')
dialogVisible.value = false
emit('success', true)
} catch (error) {
console.error(error)
} finally {
formLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<Dialog title="标记已退款" v-model="dialogVisible" width="500px">
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="订单编号">
<span>{{ formData.orderNo }}</span>
</el-form-item>
<el-form-item label="商品名称">
<span>{{ formData.spuName }}</span>
</el-form-item>
<el-form-item label="支付金额">
<span>¥{{ paymentPriceYuanText }}</span>
</el-form-item>
<el-form-item label="退款金额" prop="refundPrice">
<el-input-number
v-model="formData.refundPrice"
:precision="2"
:min="0"
:max="paymentPriceYuanNumber"
:step="0.01"
placeholder="0 表示全额"
class="!w-200px"
/>
<el-button link type="primary" @click="fillFullRefund" class="ml-8px">全额退款</el-button>
</el-form-item>
<el-form-item label="退款备注">
<el-input
v-model="formData.refundRemark"
type="textarea"
:rows="2"
placeholder="选填,如:已通过 WPGJ 对账退款"
/>
</el-form-item>
<el-alert
type="warning"
:closable="false"
show-icon
title="此操作不可撤销,请确认已通过线下渠道(银行 / WPGJ)完成退款后再操作"
/>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="success" @click="submitForm">确认已退款</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ResourceOrderApi, ResourceOrder } from '@/api/compute/resourceorder'
import { fenToYuan } from '@/utils'
defineOptions({ name: 'ResourceOrderMarkRefundedForm' })
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗是否展示
const formRef = ref() // 表单 Ref
const formLoading = ref(false) // 表单的加载中
interface MarkRefundedFormData {
orderId?: number
orderNo: string
spuName: string
refundPrice: number
refundRemark: string
}
const formData = reactive<MarkRefundedFormData>({
orderId: undefined,
orderNo: '',
spuName: '',
refundPrice: 0,
refundRemark: ''
})
/** 后端 paymentPrice 是分,UI 用元,提交时再换算回分 */
const paymentPriceFen = ref(0)
/** 展示用文本(保留 2 位小数) */
const paymentPriceYuanText = computed(() => fenToYuan(paymentPriceFen.value))
/** 用于 el-input-number max / 规则校验(number) */
const paymentPriceYuanNumber = computed(() => Number(fenToYuan(paymentPriceFen.value)))
const rules = {
refundPrice: [
{
validator: (_rule: unknown, value: number, callback: (err?: Error) => void) => {
if (value < 0) {
callback(new Error('退款金额不能为负数'))
} else if (value > paymentPriceYuanNumber.value) {
callback(new Error('退款金额不能超过支付金额'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
const emit = defineEmits(['success'])
/** 重置表单 */
const resetForm = () => {
formData.orderId = undefined
formData.orderNo = ''
formData.spuName = ''
formData.refundPrice = 0
formData.refundRemark = ''
paymentPriceFen.value = 0
formRef.value?.resetFields()
}
/** 打开弹窗 */
const open = (row: ResourceOrder) => {
resetForm()
formData.orderId = row.id
formData.orderNo = row.orderNo ?? ''
formData.spuName = row.spuName ?? ''
paymentPriceFen.value = row.paymentPrice ?? 0
formData.refundPrice = paymentPriceYuanNumber.value
dialogVisible.value = true
}
/** 全额退款快捷按钮 */
const fillFullRefund = () => {
formData.refundPrice = paymentPriceYuanNumber.value
}
/** 提交表单 */
const submitForm = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
} catch {
return
}
if (!formData.orderId) return
// 二次确认
try {
await message.confirm('确认该订单已退款?此操作不可撤销')
} catch {
return
}
formLoading.value = true
try {
// 元 → 分(保留整数,向下取整)
const refundPriceFen =
formData.refundPrice && formData.refundPrice > 0
? Math.floor(formData.refundPrice * 100)
: undefined
await ResourceOrderApi.markRefundedResourceOrder({
orderId: formData.orderId,
refundPrice: refundPriceFen,
refundRemark: formData.refundRemark || undefined
})
message.success('已标记为已退款')
dialogVisible.value = false
emit('success', true)
} catch (error) {
console.error(error)
} finally {
formLoading.value = false
}
}
defineExpose({ open })
</script>
...@@ -308,6 +308,14 @@ ...@@ -308,6 +308,14 @@
width="180px" width="180px"
/> />
<el-table-column <el-table-column
label="发货时间"
align="center"
prop="deliveredTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="发货人" align="center" prop="deliverOperatorName" min-width="100" />
<el-table-column
label="取消时间" label="取消时间"
align="center" align="center"
prop="cancelTime" prop="cancelTime"
...@@ -331,6 +339,13 @@ ...@@ -331,6 +339,13 @@
<el-table-column label="退款金额" align="center" prop="refundPrice"> <el-table-column label="退款金额" align="center" prop="refundPrice">
<template #default="{ row }"> ¥{{ fenToYuan(row.refundPrice) }} </template> <template #default="{ row }"> ¥{{ fenToYuan(row.refundPrice) }} </template>
</el-table-column> </el-table-column>
<el-table-column
label="退款时间"
align="center"
prop="refundTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="开票状态" align="center" prop="invoiceStatus" width="90"> <el-table-column label="开票状态" align="center" prop="invoiceStatus" width="90">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.TRADE_INVOICE_STATUS" :value="scope.row.invoiceStatus" /> <dict-tag :type="DICT_TYPE.TRADE_INVOICE_STATUS" :value="scope.row.invoiceStatus" />
...@@ -344,7 +359,7 @@ ...@@ -344,7 +359,7 @@
:formatter="dateFormatter" :formatter="dateFormatter"
width="180px" width="180px"
/> />
<el-table-column label="操作" align="center" min-width="190px" fixed="right"> <el-table-column label="操作" align="center" min-width="300px" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button <el-button
link link
...@@ -354,14 +369,36 @@ ...@@ -354,14 +369,36 @@
> >
详情 详情
</el-button> </el-button>
<!-- <el-button <!-- 阶段 C:商家发货(仅 PENDING_DELIVERY 显示) -->
<el-button
v-if="scope.row.status === 5"
link link
type="primary" type="primary"
@click="openForm('update', scope.row.id)" @click="handleDeliver(scope.row)"
v-hasPermi="['compute:resource-order:update']" v-hasPermi="['compute:resource-order:deliver']"
> >
编辑 发货
</el-button> --> </el-button>
<!-- 阶段 D:客服取消(仅 PENDING_DELIVERY 显示) -->
<el-button
v-if="scope.row.status === 5"
link
type="warning"
@click="handleAdminCancel(scope.row)"
v-hasPermi="['compute:resource-order:admin-cancel']"
>
客服取消
</el-button>
<!-- 阶段 D:标记已退款(仅 PENDING_REFUND 显示) -->
<el-button
v-if="scope.row.status === 7"
link
type="success"
@click="handleMarkRefunded(scope.row)"
v-hasPermi="['compute:resource-order:mark-refunded']"
>
标记已退款
</el-button>
<!-- 如果订单开票状态为【未开票】【开票中】,则展示【开票】按钮--> <!-- 如果订单开票状态为【未开票】【开票中】,则展示【开票】按钮-->
<el-button <el-button
v-if=" v-if="
...@@ -421,6 +458,15 @@ ...@@ -421,6 +458,15 @@
<!-- 开票表单弹窗 --> <!-- 开票表单弹窗 -->
<ResourceOrderIssueInvoiceForm ref="issueInvoiceFormRef" @success="getList" /> <ResourceOrderIssueInvoiceForm ref="issueInvoiceFormRef" @success="getList" />
<!-- 阶段 C:商家发货弹窗 -->
<ResourceOrderDeliverForm ref="deliverFormRef" @success="getList" />
<!-- 阶段 D:客服取消弹窗 -->
<ResourceOrderAdminCancelForm ref="adminCancelFormRef" @success="getList" />
<!-- 阶段 D:标记已退款弹窗 -->
<ResourceOrderMarkRefundedForm ref="markRefundedFormRef" @success="getList" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
...@@ -434,6 +480,9 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' ...@@ -434,6 +480,9 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { fenToYuan } from '@/utils' import { fenToYuan } from '@/utils'
import { InvoiceRequestEnum } from '@/utils/constants' import { InvoiceRequestEnum } from '@/utils/constants'
import ResourceOrderIssueInvoiceForm from './form/ResourceOrderIssueInvoiceForm.vue' import ResourceOrderIssueInvoiceForm from './form/ResourceOrderIssueInvoiceForm.vue'
import ResourceOrderDeliverForm from './form/ResourceOrderDeliverForm.vue'
import ResourceOrderAdminCancelForm from './form/ResourceOrderAdminCancelForm.vue'
import ResourceOrderMarkRefundedForm from './form/ResourceOrderMarkRefundedForm.vue'
/** 算力资源订单 列表 */ /** 算力资源订单 列表 */
defineOptions({ name: 'ResourceOrder' }) defineOptions({ name: 'ResourceOrder' })
...@@ -471,6 +520,9 @@ const queryParams = reactive({ ...@@ -471,6 +520,9 @@ const queryParams = reactive({
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中 const exportLoading = ref(false) // 导出的加载中
const issueInvoiceFormRef = ref() // 开票表单ref const issueInvoiceFormRef = ref() // 开票表单ref
const deliverFormRef = ref() // 发货表单ref(阶段 C)
const adminCancelFormRef = ref() // 客服取消表单ref(阶段 D)
const markRefundedFormRef = ref() // 标记已退款表单ref(阶段 D)
const detailRef = ref() // 详情弹窗ref const detailRef = ref() // 详情弹窗ref
const pdfDialogVisible = ref(false) // 发票预览弹窗 const pdfDialogVisible = ref(false) // 发票预览弹窗
const invoiceUrl = ref('') // 发票地址 const invoiceUrl = ref('') // 发票地址
...@@ -554,6 +606,21 @@ const handleIssueInvoice = (row: ResourceOrder) => { ...@@ -554,6 +606,21 @@ const handleIssueInvoice = (row: ResourceOrder) => {
issueInvoiceFormRef.value?.open(row) issueInvoiceFormRef.value?.open(row)
} }
/** 阶段 C:商家发货 */
const handleDeliver = (row: ResourceOrder) => {
deliverFormRef.value?.open(row)
}
/** 阶段 D:客服取消 */
const handleAdminCancel = (row: ResourceOrder) => {
adminCancelFormRef.value?.open(row)
}
/** 阶段 D:标记已退款 */
const handleMarkRefunded = (row: ResourceOrder) => {
markRefundedFormRef.value?.open(row)
}
/** 查看详情 */ /** 查看详情 */
const handleDetail = (id: number) => { const handleDetail = (id: number) => {
detailRef.value?.open(id) detailRef.value?.open(id)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment