All checks were successful
Build and Deploy Front / deploy (push) Successful in 5m44s
Co-authored-by: Cursor <cursoragent@cursor.com>
1180 lines
38 KiB
Vue
1180 lines
38 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { Modal } from '@arco-design/web-vue'
|
||
import HandsontablePro from '@/components/HandsontablePro/index.vue'
|
||
import PaginationPro from '@/components/PaginationPro/index.vue'
|
||
import ModalPro from '@/components/ModalPro/index.vue'
|
||
import useModal from '@/components/ModalPro/useModal'
|
||
import { useTableContainerHeight } from '@/composables/useTableContainerHeight'
|
||
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer'
|
||
import {
|
||
declareCustoms9610Batch,
|
||
fetchCustoms9610DeclarationFormBatch,
|
||
fetchCustoms9610ManifestBatch,
|
||
getCustoms9610Detail,
|
||
getCustoms9610List,
|
||
getCustoms9610StatusCounts,
|
||
updateCustoms9610,
|
||
} from '@/api/order/customs9610'
|
||
import { $baseMessage } from '@/utils'
|
||
import { hasPermission } from '@/utils/rbac-permission'
|
||
import { prodApiOrigin } from '@/utils/rewriteBrowserApiPath'
|
||
import { formatDateTime, normalizeListRow, pickField } from '@/utils/warehouseListMapper'
|
||
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer'
|
||
import SlidingTabs from '@/components/SlidingTabs/index.vue'
|
||
|
||
defineOptions({ name: 'OrderCustoms9610' })
|
||
|
||
const { t } = useI18n()
|
||
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
||
const {
|
||
isAdmin,
|
||
customerId: adminCustomerId,
|
||
customerOptions,
|
||
withCustomerId,
|
||
} = useWarehouseAdminCustomer()
|
||
|
||
const STATUS_ALL = 'all'
|
||
const statusTab = ref(STATUS_ALL)
|
||
const keyword = ref('')
|
||
const listLoading = ref(false)
|
||
const tableRows = ref<any[]>([])
|
||
const currentPage = ref(1)
|
||
const pageSize = ref(50)
|
||
const totalCount = ref(0)
|
||
const selectedIds = ref<number[]>([])
|
||
const statusCounts = ref({ total: 0, pending: 0, declared: 0 })
|
||
const declaring = ref(false)
|
||
const fetchingManifest = ref(false)
|
||
const fetchingDeclarationForm = ref(false)
|
||
const downstreamForwardModalVisible = ref(false)
|
||
const downstreamForwardResultText = ref('')
|
||
const saving = ref(false)
|
||
const detailLoading = ref(false)
|
||
|
||
const [registerEditModal, { openModal: openEditModal, closeModal: closeEditModal }] = useModal() as any
|
||
|
||
const editForm = reactive({
|
||
id: 0,
|
||
status: '',
|
||
platformCode: '',
|
||
ecomCorpCode: '',
|
||
ecomCorpName: '',
|
||
producerCode: '',
|
||
producerName: '',
|
||
clientNo: '',
|
||
serviceNo: '',
|
||
businessType: 'B',
|
||
region: '',
|
||
agentDeclare: '',
|
||
actualSellerCode: '',
|
||
preRecordFlag: '1',
|
||
isSummarySimple: false,
|
||
grossWeight: 0 as number,
|
||
currency: 'CNY',
|
||
destCountry: '',
|
||
freight: undefined as number | undefined,
|
||
shipmentNo: '',
|
||
customsOrderNo: '',
|
||
failReason: '',
|
||
items: [] as Array<{
|
||
id?: number
|
||
orderDeclarationId?: number
|
||
sku: string
|
||
cnName: string
|
||
exHsCode?: string
|
||
declareElement?: string
|
||
unitPrice: number
|
||
quantity: number
|
||
freight?: number
|
||
sortNo: number
|
||
}>,
|
||
})
|
||
|
||
const isPending = computed(() => editForm.status === 'pending')
|
||
|
||
const statusTabs = computed(() => [
|
||
{ key: STATUS_ALL, label: t('common.all'), count: statusCounts.value.total, showCount: true },
|
||
{
|
||
key: 'pending',
|
||
label: t('order.customs9610.status.pending'),
|
||
count: statusCounts.value.pending,
|
||
showCount: true,
|
||
},
|
||
{
|
||
key: 'declared',
|
||
label: t('order.customs9610.status.declared'),
|
||
count: statusCounts.value.declared,
|
||
showCount: true,
|
||
},
|
||
])
|
||
|
||
const slidingTabItems = computed(() =>
|
||
statusTabs.value.map((tab) => ({
|
||
key: tab.key,
|
||
label: tab.label,
|
||
badge: tab.showCount && tab.count != null ? tab.count : undefined,
|
||
})),
|
||
)
|
||
|
||
const businessTypeTabs = computed(() => [
|
||
{ key: 'A', label: 'A', disabled: !isPending.value },
|
||
{ key: 'B', label: 'B', disabled: !isPending.value },
|
||
])
|
||
|
||
const preRecordFlagTabs = computed(() => [
|
||
{ key: '0', label: '0', disabled: !isPending.value },
|
||
{ key: '1', label: '1', disabled: !isPending.value },
|
||
])
|
||
|
||
const modalTip = computed(() =>
|
||
isPending.value ? t('order.customs9610.modal.tipEdit') : t('order.customs9610.modal.tipDetail'),
|
||
)
|
||
|
||
const itemHotColumns = computed(() => [
|
||
{ key: 'sku', label: t('order.customs9610.items.sku'), width: 120 },
|
||
{ key: 'cnName', label: t('order.customs9610.items.cnName'), width: 160 },
|
||
{ key: 'exHsCode', label: t('order.customs9610.items.exHsCode'), width: 110 },
|
||
{ key: 'declareElement', label: t('order.customs9610.items.declareElement'), width: 140 },
|
||
{ key: 'unitPrice', label: t('order.customs9610.items.unitPrice'), width: 100, type: 'numeric' },
|
||
{ key: 'quantity', label: t('order.customs9610.items.quantity'), width: 90, type: 'numeric' },
|
||
])
|
||
|
||
const selectedRowCount = computed(() => selectedIds.value.length)
|
||
|
||
function handleTabChange(key: string) {
|
||
if (statusTab.value === key) return
|
||
statusTab.value = key
|
||
currentPage.value = 1
|
||
loadList()
|
||
}
|
||
|
||
function handleResetSearch() {
|
||
keyword.value = ''
|
||
if (isAdmin.value) adminCustomerId.value = undefined
|
||
currentPage.value = 1
|
||
loadList()
|
||
loadCounts()
|
||
}
|
||
|
||
const hotColumns = computed(() => [
|
||
{ key: 'serviceNo', label: t('order.customs9610.table.serviceNo'), width: 150 },
|
||
{ key: 'clientNo', label: t('order.customs9610.table.clientNo'), width: 140 },
|
||
{ key: 'customerName', label: t('warehouse.common.customerName'), width: 120 },
|
||
{ key: 'destCountry', label: t('order.customs9610.table.destCountry'), width: 80 },
|
||
{ key: 'grossWeight', label: t('order.customs9610.table.grossWeight'), width: 90 },
|
||
{ key: 'currency', label: t('order.customs9610.table.currency'), width: 70 },
|
||
{ key: 'businessType', label: t('order.customs9610.table.businessType'), width: 70 },
|
||
{ key: 'statusLabel', label: t('order.customs9610.table.status'), width: 90 },
|
||
{ key: 'customsOrderNo', label: t('order.customs9610.table.customsOrderNo'), width: 140 },
|
||
{ key: 'declareTime', label: t('order.customs9610.table.declareTime'), width: 160 },
|
||
{ key: 'createTime', label: t('order.customs9610.table.createTime'), width: 160 },
|
||
])
|
||
|
||
function statusLabel(code: string) {
|
||
if (code === 'declared') return t('order.customs9610.status.declared')
|
||
if (code === 'pending') return t('order.customs9610.status.pending')
|
||
return code || '-'
|
||
}
|
||
|
||
function actionRenderer(rowData: any, rowIndex?: number) {
|
||
const record = rowData || tableRows.value[rowIndex ?? -1]
|
||
if (!record) return ''
|
||
const status = String(record.status || '')
|
||
const buttons = [
|
||
renderWarehouseActionBtn('detail', t('warehouse.common.detail'), 'view'),
|
||
]
|
||
if (status === 'pending' && hasPermission('Order.Customs9610.Update')) {
|
||
buttons.push(renderWarehouseActionBtn('edit', t('warehouse.common.edit'), 'edit'))
|
||
}
|
||
return wrapWarehouseActions(buttons, rowIndex)
|
||
}
|
||
|
||
function handleSelectionChange(rows: any[]) {
|
||
selectedIds.value = (rows || [])
|
||
.map((x) => Number(x?.id ?? x?.Id ?? 0))
|
||
.filter((x) => Number.isFinite(x) && x > 0)
|
||
}
|
||
|
||
async function loadCounts() {
|
||
try {
|
||
const data = await getCustoms9610StatusCounts(withCustomerId({}))
|
||
statusCounts.value = {
|
||
total: Number(pickField(data, 'total', 'Total') ?? 0),
|
||
pending: Number(pickField(data, 'pending', 'Pending') ?? 0),
|
||
declared: Number(pickField(data, 'declared', 'Declared') ?? 0),
|
||
}
|
||
} catch {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
async function loadList() {
|
||
listLoading.value = true
|
||
try {
|
||
const body = withCustomerId({
|
||
page: currentPage.value,
|
||
pageSize: pageSize.value,
|
||
keyword: keyword.value.trim() || undefined,
|
||
status: statusTab.value === STATUS_ALL ? undefined : statusTab.value,
|
||
})
|
||
const { items, total } = await getCustoms9610List(body)
|
||
tableRows.value = (items || []).map((raw: any) => {
|
||
const row = normalizeListRow(raw)
|
||
const status = String(pickField(raw, 'status', 'Status') ?? '')
|
||
return {
|
||
...row,
|
||
id: Number(pickField(raw, 'id', 'Id') ?? 0),
|
||
status,
|
||
statusLabel: statusLabel(status),
|
||
serviceNo: pickField(raw, 'serviceNo', 'ServiceNo') || '-',
|
||
clientNo: pickField(raw, 'clientNo', 'ClientNo') || '-',
|
||
customerName: pickField(raw, 'customerName', 'CustomerName') || '-',
|
||
destCountry: pickField(raw, 'destCountry', 'Country') || '-',
|
||
grossWeight: pickField(raw, 'grossWeight', 'GrossWeight') ?? '-',
|
||
currency: pickField(raw, 'currency', 'Currency') || '-',
|
||
businessType: pickField(raw, 'businessType', 'FormType') || '-',
|
||
customsOrderNo: pickField(raw, 'customsOrderNo', 'OrderNo') || '-',
|
||
declareTime: formatDateTime(pickField(raw, 'declareTime', 'DeclareTime')),
|
||
createTime: formatDateTime(pickField(raw, 'createTime', 'CreateTime')),
|
||
_raw: raw,
|
||
}
|
||
})
|
||
totalCount.value = total || 0
|
||
selectedIds.value = []
|
||
} catch (e: any) {
|
||
$baseMessage(e?.message || t('order.customs9610.message.loadFailed'), 'error')
|
||
} finally {
|
||
listLoading.value = false
|
||
}
|
||
}
|
||
|
||
function handleSearch() {
|
||
currentPage.value = 1
|
||
loadList()
|
||
loadCounts()
|
||
}
|
||
|
||
function handlePageChange(page: number) {
|
||
currentPage.value = page
|
||
loadList()
|
||
}
|
||
|
||
function handlePageSizeChange(size: number) {
|
||
pageSize.value = size
|
||
currentPage.value = 1
|
||
loadList()
|
||
}
|
||
|
||
function fillEditForm(data: any) {
|
||
const items = (pickField(data, 'items', 'Items') as any[]) || []
|
||
Object.assign(editForm, {
|
||
id: Number(pickField(data, 'id', 'Id') ?? 0),
|
||
status: String(pickField(data, 'status', 'Status') ?? ''),
|
||
platformCode: String(pickField(data, 'platformCode', 'Ebp') ?? ''),
|
||
ecomCorpCode: String(pickField(data, 'ecomCorpCode', 'EbcCode') ?? ''),
|
||
ecomCorpName: String(pickField(data, 'ecomCorpName', 'EbcName') ?? ''),
|
||
producerCode: String(pickField(data, 'producerCode', 'OwnerCode') ?? ''),
|
||
producerName: String(pickField(data, 'producerName', 'OwnerName') ?? ''),
|
||
clientNo: String(pickField(data, 'clientNo', 'ClientNo') ?? ''),
|
||
serviceNo: String(pickField(data, 'serviceNo', 'ServiceNo') ?? ''),
|
||
businessType: String(pickField(data, 'businessType', 'FormType') ?? 'B'),
|
||
region: String(pickField(data, 'region', 'Region') ?? ''),
|
||
agentDeclare: String(pickField(data, 'agentDeclare', 'IsAgent') ?? ''),
|
||
actualSellerCode: String(pickField(data, 'actualSellerCode', 'ActBuyer') ?? ''),
|
||
preRecordFlag: String(pickField(data, 'preRecordFlag', 'SoLabelFlag') ?? '1'),
|
||
isSummarySimple: Boolean(pickField(data, 'isSummarySimple', 'IsSimple')),
|
||
grossWeight: Number(pickField(data, 'grossWeight', 'GrossWeight') ?? 0),
|
||
currency: String(pickField(data, 'currency', 'Currency') ?? 'CNY'),
|
||
destCountry: String(pickField(data, 'destCountry', 'Country') ?? ''),
|
||
freight: pickField(data, 'freight', 'Freight') as number | undefined,
|
||
shipmentNo: String(pickField(data, 'shipmentNo', 'BlNo') ?? ''),
|
||
customsOrderNo: String(pickField(data, 'customsOrderNo', 'OrderNo') ?? ''),
|
||
failReason: String(pickField(data, 'failReason', 'FailReason') ?? ''),
|
||
items: items.map((it: any, idx: number) => ({
|
||
id: Number(pickField(it, 'id', 'Id') ?? 0) || undefined,
|
||
orderDeclarationId: pickField(it, 'orderDeclarationId', 'OrderDeclarationId') as number | undefined,
|
||
sku: String(pickField(it, 'sku', 'Sku', 'itemNo', 'ItemNo') ?? ''),
|
||
cnName: String(pickField(it, 'cnName', 'CnName', 'itemName', 'ItemName') ?? ''),
|
||
exHsCode: String(pickField(it, 'exHsCode', 'ExHsCode', 'gcode', 'Gcode') ?? ''),
|
||
declareElement: String(pickField(it, 'declareElement', 'Gmodel') ?? ''),
|
||
unitPrice: Number(pickField(it, 'unitPrice', 'UnitPrice', 'price', 'Price') ?? 0),
|
||
quantity: Number(pickField(it, 'quantity', 'Quantity', 'qty', 'Qty') ?? 0),
|
||
freight: pickField(it, 'freight', 'Freight') as number | undefined,
|
||
sortNo: Number(pickField(it, 'sortNo', 'SortNo') ?? idx),
|
||
})),
|
||
})
|
||
}
|
||
|
||
async function openDetail(id: number, editable: boolean) {
|
||
detailLoading.value = true
|
||
openEditModal(true)
|
||
try {
|
||
const data = await getCustoms9610Detail(id)
|
||
fillEditForm(data)
|
||
if (!editable) {
|
||
editForm.status = String(pickField(data, 'status', 'Status') ?? editForm.status)
|
||
}
|
||
} catch (e: any) {
|
||
$baseMessage(e?.message || t('order.customs9610.message.loadFailed'), 'error')
|
||
closeEditModal()
|
||
} finally {
|
||
detailLoading.value = false
|
||
}
|
||
}
|
||
|
||
function handleRowAction(row: any, rowIndex: number, action?: string) {
|
||
const record = row || tableRows.value[rowIndex]
|
||
if (!record?.id) return
|
||
if (action === 'detail') {
|
||
openDetail(record.id, false)
|
||
} else if (action === 'edit') {
|
||
openDetail(record.id, true)
|
||
}
|
||
}
|
||
|
||
function addItemRow() {
|
||
editForm.items.push({
|
||
sku: '',
|
||
cnName: '',
|
||
exHsCode: '',
|
||
declareElement: '',
|
||
unitPrice: 0,
|
||
quantity: 1,
|
||
sortNo: editForm.items.length,
|
||
})
|
||
}
|
||
|
||
function removeItemRow(index: number) {
|
||
if (index < 0 || index >= editForm.items.length) return
|
||
editForm.items.splice(index, 1)
|
||
editForm.items.forEach((item, i) => {
|
||
item.sortNo = i
|
||
})
|
||
}
|
||
|
||
function itemActionRenderer(_rowData: any, rowIndex?: number) {
|
||
return wrapWarehouseActions(
|
||
[renderWarehouseActionBtn('delete', t('warehouse.common.delete'), 'delete')],
|
||
rowIndex,
|
||
)
|
||
}
|
||
|
||
function handleItemActionClick(_row: any, rowIndex: number, action?: string) {
|
||
if (action === 'delete') {
|
||
removeItemRow(rowIndex)
|
||
}
|
||
}
|
||
|
||
function validateEdit() {
|
||
if (!editForm.platformCode.trim()) {
|
||
$baseMessage(t('order.customs9610.message.platformCodeRequired'), 'warning')
|
||
return false
|
||
}
|
||
if (!editForm.clientNo.trim() || !editForm.serviceNo.trim()) {
|
||
$baseMessage(t('order.customs9610.message.clientServiceRequired'), 'warning')
|
||
return false
|
||
}
|
||
if (!(editForm.grossWeight > 0)) {
|
||
$baseMessage(t('order.customs9610.message.weightRequired'), 'warning')
|
||
return false
|
||
}
|
||
if (!editForm.currency.trim() || !editForm.destCountry.trim()) {
|
||
$baseMessage(t('order.customs9610.message.currencyDestCountryRequired'), 'warning')
|
||
return false
|
||
}
|
||
if (editForm.businessType === 'B' && !editForm.preRecordFlag) {
|
||
$baseMessage(t('order.customs9610.message.preRecordRequired'), 'warning')
|
||
return false
|
||
}
|
||
if (editForm.businessType === 'A' && (!editForm.region || !editForm.agentDeclare)) {
|
||
$baseMessage(t('order.customs9610.message.aFieldsRequired'), 'warning')
|
||
return false
|
||
}
|
||
if (!editForm.items.length || editForm.items.some((x) => !String(x.sku || '').trim() || !String(x.cnName || '').trim())) {
|
||
$baseMessage(t('order.customs9610.message.itemsRequired'), 'warning')
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
async function handleSave() {
|
||
if (!isPending.value) {
|
||
closeEditModal()
|
||
return
|
||
}
|
||
if (!validateEdit()) return
|
||
saving.value = true
|
||
try {
|
||
await updateCustoms9610({
|
||
id: editForm.id,
|
||
platformCode: editForm.platformCode,
|
||
ecomCorpCode: editForm.ecomCorpCode || undefined,
|
||
ecomCorpName: editForm.ecomCorpName || undefined,
|
||
producerCode: editForm.producerCode || undefined,
|
||
producerName: editForm.producerName || undefined,
|
||
clientNo: editForm.clientNo,
|
||
serviceNo: editForm.serviceNo,
|
||
businessType: editForm.businessType,
|
||
region: editForm.region || undefined,
|
||
agentDeclare: editForm.agentDeclare || undefined,
|
||
actualSellerCode: editForm.actualSellerCode || undefined,
|
||
preRecordFlag: editForm.preRecordFlag || undefined,
|
||
isSummarySimple: editForm.isSummarySimple,
|
||
grossWeight: editForm.grossWeight,
|
||
currency: editForm.currency,
|
||
destCountry: editForm.destCountry,
|
||
freight: editForm.freight,
|
||
shipmentNo: editForm.shipmentNo || undefined,
|
||
items: editForm.items.map((item, index) => ({
|
||
...item,
|
||
sortNo: index,
|
||
sku: String(item.sku || '').trim(),
|
||
cnName: String(item.cnName || '').trim(),
|
||
exHsCode: item.exHsCode ? String(item.exHsCode).trim() : undefined,
|
||
declareElement: item.declareElement ? String(item.declareElement).trim() : undefined,
|
||
unitPrice: Number(item.unitPrice) || 0,
|
||
quantity: Number(item.quantity) || 0,
|
||
})),
|
||
})
|
||
$baseMessage(t('order.customs9610.message.saveSuccess'), 'success')
|
||
closeEditModal()
|
||
await loadList()
|
||
await loadCounts()
|
||
} catch (e: any) {
|
||
$baseMessage(e?.message || t('order.customs9610.message.saveFailed'), 'error')
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
async function handleBatchDeclare() {
|
||
const ids = selectedIds.value
|
||
if (!ids.length) {
|
||
$baseMessage(t('order.customs9610.message.selectFirst'), 'warning')
|
||
return
|
||
}
|
||
Modal.confirm({
|
||
title: t('order.customs9610.confirm.batchDeclareTitle'),
|
||
content: t('order.customs9610.confirm.batchDeclareContent', { count: ids.length }),
|
||
onOk: async () => {
|
||
declaring.value = true
|
||
try {
|
||
const res = await declareCustoms9610Batch(ids)
|
||
const successCount = Number(pickField(res, 'successCount', 'SuccessCount') ?? 0)
|
||
const failCount = Number(pickField(res, 'failCount', 'FailCount') ?? 0)
|
||
$baseMessage(
|
||
t('order.customs9610.message.batchResult', { success: successCount, fail: failCount }),
|
||
failCount > 0 ? 'warning' : 'success',
|
||
)
|
||
await loadList()
|
||
await loadCounts()
|
||
} catch (e: any) {
|
||
$baseMessage(e?.message || t('order.customs9610.message.declareFailed'), 'error')
|
||
} finally {
|
||
declaring.value = false
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
async function runBatchFetch(
|
||
kind: 'manifest' | 'declarationForm',
|
||
api: (ids: Array<number | string>) => Promise<any>,
|
||
) {
|
||
const ids = selectedIds.value
|
||
if (!ids.length) {
|
||
$baseMessage(t('order.customs9610.message.selectFirst'), 'warning')
|
||
return
|
||
}
|
||
const loadingRef = kind === 'manifest' ? fetchingManifest : fetchingDeclarationForm
|
||
const title =
|
||
kind === 'manifest'
|
||
? t('order.customs9610.confirm.batchManifestTitle')
|
||
: t('order.customs9610.confirm.batchDeclarationFormTitle')
|
||
const content =
|
||
kind === 'manifest'
|
||
? t('order.customs9610.confirm.batchManifestContent', { count: ids.length })
|
||
: t('order.customs9610.confirm.batchDeclarationFormContent', { count: ids.length })
|
||
const failFallback =
|
||
kind === 'manifest'
|
||
? t('order.customs9610.message.fetchManifestFailed')
|
||
: t('order.customs9610.message.fetchDeclarationFormFailed')
|
||
|
||
Modal.confirm({
|
||
title,
|
||
content,
|
||
onOk: async () => {
|
||
loadingRef.value = true
|
||
try {
|
||
const res = await api(ids)
|
||
const successCount = Number(pickField(res, 'successCount', 'SuccessCount') ?? 0)
|
||
const failCount = Number(pickField(res, 'failCount', 'FailCount') ?? 0)
|
||
$baseMessage(
|
||
t('order.customs9610.message.batchResult', { success: successCount, fail: failCount }),
|
||
failCount > 0 ? 'warning' : 'success',
|
||
)
|
||
} catch (e: any) {
|
||
$baseMessage(e?.message || failFallback, 'error')
|
||
} finally {
|
||
loadingRef.value = false
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
function handleBatchFetchManifest() {
|
||
return runBatchFetch('manifest', fetchCustoms9610ManifestBatch)
|
||
}
|
||
|
||
function formatDownstreamForwardResult(item: Record<string, unknown>): string {
|
||
const id = pickField(item, 'id', 'Id')
|
||
const serviceNo = pickField(item, 'serviceNo', 'ServiceNo') || '-'
|
||
const clientNo = pickField(item, 'clientNo', 'ClientNo') || '-'
|
||
const success = Boolean(pickField(item, 'success', 'Success'))
|
||
const lines = [
|
||
`【${serviceNo} / ${clientNo} / #${id}】`,
|
||
success ? '成功' : '失败',
|
||
`standardCode:create9610Order`,
|
||
`supplierCode:BH`,
|
||
`消息:${pickField(item, 'message', 'Message') || '-'}`,
|
||
`请求:${pickField(item, 'url', 'Url') || `${prodApiOrigin()}/itfx/InterfaceForward/ForwardWithSubscribe?standardCode=create9610Order&supplierCode=BH`}`,
|
||
]
|
||
const requestJson = pickField(item, 'requestJson', 'RequestJson')
|
||
if (requestJson) {
|
||
lines.push(`请求体:\n${requestJson}`)
|
||
}
|
||
lines.push(`HTTP:${pickField(item, 'httpStatusCode', 'HttpStatusCode') ?? '-'}`)
|
||
const responseBody = pickField(item, 'responseBody', 'ResponseBody')
|
||
if (responseBody) {
|
||
lines.push(`响应:\n${responseBody}`)
|
||
}
|
||
return lines.join('\n')
|
||
}
|
||
|
||
function handleBatchFetchDeclarationForm() {
|
||
const ids = selectedIds.value
|
||
if (!ids.length) {
|
||
$baseMessage(t('order.customs9610.message.selectFirst'), 'warning')
|
||
return
|
||
}
|
||
Modal.confirm({
|
||
title: t('order.customs9610.confirm.batchDeclarationFormTitle'),
|
||
content: t('order.customs9610.confirm.batchDeclarationFormContent', { count: ids.length }),
|
||
onOk: async () => {
|
||
fetchingDeclarationForm.value = true
|
||
try {
|
||
const res = await fetchCustoms9610DeclarationFormBatch(ids)
|
||
const items = (pickField(res, 'items', 'Items') as any[]) || []
|
||
const successCount = Number(pickField(res, 'successCount', 'SuccessCount') ?? 0)
|
||
const failCount = Number(pickField(res, 'failCount', 'FailCount') ?? 0)
|
||
downstreamForwardResultText.value = items.length
|
||
? items.map((item) => formatDownstreamForwardResult(item as Record<string, unknown>)).join('\n\n')
|
||
: t('order.customs9610.message.batchResult', {
|
||
success: successCount,
|
||
fail: failCount,
|
||
})
|
||
downstreamForwardModalVisible.value = true
|
||
await loadList()
|
||
await loadCounts()
|
||
} catch (e: any) {
|
||
$baseMessage(e?.message || t('order.customs9610.message.fetchDeclarationFormFailed'), 'error')
|
||
} finally {
|
||
fetchingDeclarationForm.value = false
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
watch(adminCustomerId, () => {
|
||
handleSearch()
|
||
})
|
||
|
||
onMounted(() => {
|
||
loadList()
|
||
loadCounts()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="customs9610-container">
|
||
<div class="table-section">
|
||
<div class="tabs-actions-section">
|
||
<div class="tabs-slot">
|
||
<SlidingTabs
|
||
:model-value="statusTab"
|
||
:items="slidingTabItems"
|
||
@change="handleTabChange"
|
||
/>
|
||
</div>
|
||
|
||
<div class="toolbar-left">
|
||
<div class="list-filters">
|
||
<a-select
|
||
v-if="isAdmin"
|
||
v-model="adminCustomerId"
|
||
allow-clear
|
||
allow-search
|
||
:placeholder="t('warehouse.common.selectCustomer')"
|
||
:options="customerOptions"
|
||
style="width: 200px"
|
||
@change="handleSearch"
|
||
/>
|
||
<a-input
|
||
v-model="keyword"
|
||
allow-clear
|
||
:placeholder="t('order.customs9610.search.keyword')"
|
||
style="width: 240px"
|
||
@press-enter="handleSearch"
|
||
/>
|
||
<a-button class="app-btn-search" @click="handleSearch">
|
||
{{ t('order.myOrders.search.actions.search') }}
|
||
</a-button>
|
||
<a-button class="app-btn-reset" @click="handleResetSearch">
|
||
{{ t('order.myOrders.search.actions.reset') }}
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-toolbar">
|
||
<div class="actions-right">
|
||
<a-button
|
||
v-if="hasPermission('Order.Customs9610.DeclareBatch')"
|
||
class="app-btn-toolbar"
|
||
:loading="declaring"
|
||
:disabled="!selectedIds.length || declaring"
|
||
@click="handleBatchDeclare"
|
||
>
|
||
{{ t('order.customs9610.actions.batchDeclare') }}
|
||
</a-button>
|
||
<a-button
|
||
v-if="hasPermission('Order.Customs9610.FetchManifestBatch')"
|
||
class="app-btn-toolbar"
|
||
:loading="fetchingManifest"
|
||
:disabled="!selectedIds.length || fetchingManifest"
|
||
@click="handleBatchFetchManifest"
|
||
>
|
||
{{ t('order.customs9610.actions.batchFetchManifest') }}
|
||
</a-button>
|
||
<a-button
|
||
v-if="hasPermission('Order.Customs9610.FetchDeclarationFormBatch')"
|
||
class="app-btn-toolbar"
|
||
:loading="fetchingDeclarationForm"
|
||
:disabled="!selectedIds.length || fetchingDeclarationForm"
|
||
@click="handleBatchFetchDeclarationForm"
|
||
>
|
||
{{ t('order.customs9610.actions.batchFetchDeclarationForm') }}
|
||
</a-button>
|
||
</div>
|
||
<div class="toolbar-right">
|
||
<span class="selected-count">
|
||
<i18n-t keypath="order.myOrders.selectedCount" tag="span" scope="global">
|
||
<template #count>
|
||
<span class="count-number">{{ selectedRowCount }}</span>
|
||
</template>
|
||
</i18n-t>
|
||
</span>
|
||
<div class="pagination-controls">
|
||
<PaginationPro
|
||
:total-count="totalCount"
|
||
v-model:current-page="currentPage"
|
||
v-model:page-size="pageSize"
|
||
:page-sizes="[10, 30, 50, 100]"
|
||
@page-change="handlePageChange"
|
||
@page-size-change="handlePageSizeChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div ref="tableContainerRef" class="list-wrap">
|
||
<div v-if="!listLoading && totalCount === 0" class="list-empty-hint">
|
||
<span>{{ t('order.customs9610.empty.hint') }}</span>
|
||
</div>
|
||
<a-spin :loading="listLoading" class="table-spin">
|
||
<HandsontablePro
|
||
:data="tableRows"
|
||
:columns="hotColumns"
|
||
:height="tableHeight"
|
||
stretch-columns
|
||
show-action-column
|
||
:action-renderer="actionRenderer"
|
||
@action-click="handleRowAction"
|
||
@selection-change="handleSelectionChange"
|
||
/>
|
||
</a-spin>
|
||
</div>
|
||
</div>
|
||
|
||
<ModalPro
|
||
@register="registerEditModal"
|
||
:title="isPending ? t('order.customs9610.modal.edit') : t('order.customs9610.modal.detail')"
|
||
width="920px"
|
||
:mask-closable="false"
|
||
:confirm-loading="saving"
|
||
:ok-text="isPending ? t('warehouse.common.save') : t('warehouse.common.confirm')"
|
||
@ok="handleSave"
|
||
@cancel="closeEditModal"
|
||
>
|
||
<a-spin :loading="detailLoading" style="width: 100%">
|
||
<a-alert type="info" :show-icon="false" class="modal-form-tip">
|
||
{{ modalTip }}
|
||
</a-alert>
|
||
<a-alert
|
||
v-if="editForm.failReason"
|
||
type="error"
|
||
:show-icon="false"
|
||
class="modal-form-tip modal-form-tip--error"
|
||
>
|
||
{{ editForm.failReason }}
|
||
</a-alert>
|
||
|
||
<a-form :model="editForm" layout="vertical" class="customs-edit-form">
|
||
<div class="form-section-title">{{ t('order.customs9610.section.basic') }}</div>
|
||
<a-row :gutter="16">
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.platformCode')" required>
|
||
<a-input v-model="editForm.platformCode" :disabled="!isPending" :max-length="16" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.businessType')" required>
|
||
<SlidingTabs
|
||
v-model="editForm.businessType"
|
||
size="mini"
|
||
class="field-tabs"
|
||
:items="businessTypeTabs"
|
||
:aria-label="t('order.customs9610.field.businessType')"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.preRecordFlag')">
|
||
<SlidingTabs
|
||
v-model="editForm.preRecordFlag"
|
||
size="mini"
|
||
class="field-tabs"
|
||
:items="preRecordFlagTabs"
|
||
:aria-label="t('order.customs9610.field.preRecordFlag')"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.clientNo')" required>
|
||
<a-input v-model="editForm.clientNo" :disabled="!isPending" :max-length="48" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.serviceNo')" required>
|
||
<a-input v-model="editForm.serviceNo" :disabled="!isPending" :max-length="48" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.destCountry')" required>
|
||
<a-input v-model="editForm.destCountry" :disabled="!isPending" :max-length="4" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.currency')" required>
|
||
<a-input v-model="editForm.currency" :disabled="!isPending" :max-length="4" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.grossWeight')" required>
|
||
<a-input-number
|
||
v-model="editForm.grossWeight"
|
||
:disabled="!isPending"
|
||
:min="0"
|
||
:precision="5"
|
||
style="width: 100%"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.freight')">
|
||
<a-input-number
|
||
v-model="editForm.freight"
|
||
:disabled="!isPending"
|
||
:min="0"
|
||
:precision="2"
|
||
style="width: 100%"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<div class="form-section-title">{{ t('order.customs9610.section.enterprise') }}</div>
|
||
<a-row :gutter="16">
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.ecomCorpCode')">
|
||
<a-input v-model="editForm.ecomCorpCode" :disabled="!isPending" :max-length="18" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.ecomCorpName')">
|
||
<a-input v-model="editForm.ecomCorpName" :disabled="!isPending" :max-length="100" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.producerCode')">
|
||
<a-input v-model="editForm.producerCode" :disabled="!isPending" :max-length="18" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.producerName')">
|
||
<a-input v-model="editForm.producerName" :disabled="!isPending" :max-length="100" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.region')">
|
||
<a-input
|
||
v-model="editForm.region"
|
||
:disabled="!isPending"
|
||
:max-length="10"
|
||
:placeholder="t('order.customs9610.placeholder.region')"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.agentDeclare')">
|
||
<a-input v-model="editForm.agentDeclare" :disabled="!isPending" :max-length="2" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.actualSellerCode')">
|
||
<a-input v-model="editForm.actualSellerCode" :disabled="!isPending" :max-length="18" />
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<div class="form-section-title">{{ t('order.customs9610.section.more') }}</div>
|
||
<a-row :gutter="16">
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.shipmentNo')">
|
||
<a-input v-model="editForm.shipmentNo" :disabled="!isPending" :max-length="32" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.customsOrderNo')">
|
||
<a-input :model-value="editForm.customsOrderNo" disabled />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item :label="t('order.customs9610.field.isSummarySimple')">
|
||
<a-switch v-model="editForm.isSummarySimple" :disabled="!isPending" />
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
</a-form>
|
||
|
||
<div class="form-section-title items-section-title">
|
||
<span>{{ t('order.customs9610.items.title') }}</span>
|
||
<a-button v-if="isPending" size="mini" type="outline" @click="addItemRow">
|
||
{{ t('order.customs9610.items.add') }}
|
||
</a-button>
|
||
</div>
|
||
<div class="items-table-wrap">
|
||
<HandsontablePro
|
||
:key="`customs-items-${editForm.id}-${isPending ? 'edit' : 'view'}`"
|
||
:columns="itemHotColumns"
|
||
:data="editForm.items"
|
||
:height="260"
|
||
:read-only="!isPending"
|
||
:stretch-columns="true"
|
||
:enable-column-filter="false"
|
||
:show-action-column="isPending"
|
||
:action-column-header="t('warehouse.common.delete')"
|
||
:action-renderer="itemActionRenderer"
|
||
@action-click="handleItemActionClick"
|
||
/>
|
||
</div>
|
||
</a-spin>
|
||
</ModalPro>
|
||
|
||
<a-modal
|
||
v-model:visible="downstreamForwardModalVisible"
|
||
:title="t('order.customs9610.modal.downstreamForwardTitle')"
|
||
:width="800"
|
||
:footer="false"
|
||
>
|
||
<pre class="downstream-forward-result">{{ downstreamForwardResultText }}</pre>
|
||
</a-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped lang="less">
|
||
.customs9610-container {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
padding: 16px 16px 24px;
|
||
background-color: var(--app-page-bg);
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-x: hidden;
|
||
overflow-y: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.tabs-actions-section {
|
||
background: transparent;
|
||
border-radius: 0;
|
||
padding: 10px 16px;
|
||
margin-bottom: 0;
|
||
border-bottom: 1px solid var(--color-border-2);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: nowrap;
|
||
gap: 12px;
|
||
flex-shrink: 0;
|
||
min-height: 52px;
|
||
box-sizing: border-box;
|
||
|
||
.tabs-slot {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
overflow: hidden;
|
||
}
|
||
|
||
:deep(.t-tabs) {
|
||
flex: 0 1 auto;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
width: max-content;
|
||
}
|
||
}
|
||
|
||
.toolbar-left {
|
||
flex: 0 0 auto;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
padding: 0;
|
||
max-width: 100%;
|
||
height: 32px;
|
||
|
||
.list-filters {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
height: 32px;
|
||
|
||
:deep(.arco-select-view),
|
||
:deep(.arco-input-wrapper),
|
||
:deep(.arco-btn) {
|
||
height: 32px;
|
||
}
|
||
|
||
:deep(.arco-select-view-single),
|
||
:deep(.arco-input-wrapper) {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
|
||
:deep(.arco-input-wrapper) {
|
||
padding-top: 0;
|
||
padding-bottom: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.table-section {
|
||
background: var(--app-card-bg);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--app-card-border);
|
||
padding: 0;
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.table-toolbar {
|
||
padding: 12px 16px 0;
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
|
||
.toolbar-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-end;
|
||
|
||
.selected-count {
|
||
font-size: 13px;
|
||
color: var(--color-text-3);
|
||
white-space: nowrap;
|
||
line-height: 32px;
|
||
}
|
||
|
||
.pagination-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
.actions-right {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: var(--app-button-gap, 8px);
|
||
align-items: center;
|
||
flex: 1;
|
||
min-width: 0;
|
||
|
||
:deep(.arco-btn) {
|
||
height: 32px;
|
||
padding: 0 12px;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
font-weight: 400;
|
||
line-height: 30px;
|
||
}
|
||
}
|
||
|
||
.list-empty-hint {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 2;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px;
|
||
pointer-events: none;
|
||
font-size: 14px;
|
||
color: var(--color-text-3);
|
||
text-align: center;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.table-spin {
|
||
width: 100%;
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
:deep(.arco-spin-children) {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
|
||
.list-wrap {
|
||
position: relative;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
flex: 1;
|
||
min-height: 0;
|
||
padding: 0 16px 16px;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overscroll-behavior: contain;
|
||
touch-action: pan-x pan-y;
|
||
|
||
:deep(.handsontable-pro-container) {
|
||
width: 100%;
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
|
||
.items-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin: 8px 0;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.modal-form-tip {
|
||
margin-bottom: 16px;
|
||
border-radius: 6px;
|
||
background: color-mix(in srgb, var(--app-primary-color) 8%, #fff);
|
||
border: 1px solid color-mix(in srgb, var(--app-primary-color) 18%, transparent);
|
||
color: var(--color-text-2);
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
|
||
&--error {
|
||
background: color-mix(in srgb, rgb(var(--danger-6)) 8%, #fff);
|
||
border-color: color-mix(in srgb, rgb(var(--danger-6)) 22%, transparent);
|
||
color: rgb(var(--danger-6));
|
||
}
|
||
}
|
||
|
||
.form-section-title {
|
||
margin: 0 0 12px;
|
||
padding: 0 0 8px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--color-text-2);
|
||
border-bottom: 1px solid var(--color-border-2);
|
||
|
||
~ .form-section-title {
|
||
margin-top: 8px;
|
||
}
|
||
}
|
||
|
||
.items-section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.customs-edit-form {
|
||
:deep(.arco-form-item) {
|
||
margin-bottom: 16px;
|
||
}
|
||
}
|
||
|
||
.field-tabs {
|
||
width: fit-content;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.items-table-wrap {
|
||
height: 260px;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
border: 1px solid var(--color-border-2);
|
||
border-radius: 6px;
|
||
|
||
:deep(.handsontable-pro-container) {
|
||
height: 100%;
|
||
min-height: 0;
|
||
}
|
||
}
|
||
|
||
.downstream-forward-result {
|
||
margin: 0;
|
||
max-height: 60vh;
|
||
overflow: auto;
|
||
padding: 12px;
|
||
background: var(--color-fill-2);
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
</style>
|