feat: 添加发票退款功能并优化发票上传组件样式和功能

This commit is contained in:
Wei_佳 2025-11-27 11:06:47 +08:00
parent d98330d5ce
commit 823230cb2d
4 changed files with 88 additions and 46 deletions

View File

@ -52,6 +52,8 @@ export default {
getInvoiceById: (params = {}) => request.get(`/transactions/receipts/${params.id}`, { params }),
// 后端接口要求请求体包裹在 data 字段下
sendInvoice: (data = {}) => request.post('/transactions/send-email', { data }),
// 退款
refundInvoice: (data = {}) => request.post('/invoice/update-status', data),
// invoice headers
getInvoiceHeaders: (params = {}) => request.get('/invoice/list', { params }),
// valuation (估值评估)

View File

@ -172,18 +172,16 @@ const modalTitle = props.mode === 'invoice' ? '开票' : '查看发票'
<div style="width: 100%">
<NUpload
v-model:file-list="fileList"
multiple
:max="2"
:max="3"
list-type="image-card"
:action="uploadUrl"
:headers="uploadHeaders"
:before-upload="beforeUpload"
@finish="handleUploadFinish"
:disabled="mode === 'view'"
show-preview-button
show-download-button
>
<div class="upload-trigger">
<div class="upload-icon">+</div>
</div>
</NUpload>
</div>
</NFormItem>
@ -223,13 +221,4 @@ const modalTitle = props.mode === 'invoice' ? '开票' : '查看发票'
color: #d9d9d9;
}
:deep(.n-upload-file-list) {
display: flex;
gap: 8px;
}
:deep(.n-upload-trigger) {
width: 100px;
height: 100px;
}
</style>

View File

@ -1,7 +1,7 @@
<script setup>
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
import dayjs from 'dayjs'
import { NButton, NInput, NTag, NSelect, NDatePicker } from 'naive-ui'
import { NButton, NInput, NTag, NSelect, NDatePicker, useMessage, useDialog } from 'naive-ui'
import CommonPage from '@/components/page/CommonPage.vue'
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
@ -16,6 +16,8 @@ defineOptions({ name: '开票记录' })
const $table = ref(null)
const queryItems = ref({})
const vPermission = resolveDirective('permission')
const $message = useMessage()
const dialog = useDialog()
// /
const invoiceModalVisible = ref(false)
@ -190,23 +192,60 @@ const columns = [
{
title: '操作',
key: 'actions',
width: 80,
width: 150,
align: 'center',
fixed: 'right',
render(row) {
const editable = row.status === 'pending'
return withDirectives(
h(
NButton,
{
size: 'small',
type: editable ? 'primary' : 'default',
onClick: () => handleInvoice(row),
},
{ default: () => '开票' }
),
[[vPermission, 'post/api/v1/transactions/send-email']]
)
const buttons = []
if (row.status === 'pending') {
// """退"
buttons.push(
withDirectives(
h(
NButton,
{
size: 'small',
type: 'primary',
onClick: () => handleInvoice(row),
},
{ default: () => '开票' }
),
[[vPermission, 'post/api/v1/transactions/send-email']]
)
)
buttons.push(
withDirectives(
h(
NButton,
{
size: 'small',
type: 'warning',
onClick: () => handleRefund(row),
style: { marginLeft: '8px' },
},
{ default: () => '退款' }
),
[[vPermission, 'post/api/v1/invoice/update-status']]
)
)
} else if (row.status === 'invoiced') {
// ""
buttons.push(
h(
NButton,
{
size: 'small',
type: 'default',
onClick: () => handleInvoice(row),
},
{ default: () => '查看' }
)
)
}
// 退
return h('div', { style: 'display: flex; gap: 8px; justify-content: center;' }, buttons)
},
},
]
@ -219,7 +258,35 @@ function handleInvoice(row) {
invoiceModalVisible.value = true
}
//
// 退
function handleRefund(row) {
dialog.warning({
title: '确认退款',
content: `确定要将此订单标记为已退款吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
const invoiceId = row.invoice_id
if (!invoiceId) {
$message.error('无法获取发票ID')
return
}
await api.refundInvoice({
id: invoiceId,
status: 'refunded',
})
$message.success('退款成功')
$table.value?.handleSearch()
} catch (error) {
console.error(error)
$message.error(error?.message || '退款失败')
}
},
})
}
//
async function handleInvoiceConfirm(formData) {
try {

View File

@ -409,22 +409,6 @@ const handlePreview = (file) => {
gap: 12px;
}
/* 缩略图上传组件样式 */
:deep(.n-upload) {
width: 100%;
}
:deep(.n-upload-file-list) {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 8px;
}
:deep(.n-upload-file-card) {
aspect-ratio: 1;
border-radius: 6px;
overflow: hidden;
}
/* 响应式调整 */
@media (max-width: 768px) {