front/src/views/order/customs9610Order/index.vue
zzh abe9389f66 feat: 顶栏标签折叠管控与业务页体验优化
标签栏最多展示15个并支持更多折叠/一键关闭;同步官网顾问、工作台关怀、下单与操作扫描等页面改动。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 18:39:55 +08:00

1039 lines
34 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 {
declareCustoms9610,
declareCustoms9610Batch,
fetchCustoms9610DeclarationFormBatch,
fetchCustoms9610ManifestBatch,
getCustoms9610Detail,
getCustoms9610List,
getCustoms9610StatusCounts,
updateCustoms9610,
} from '@/api/order/customs9610'
import { $baseMessage } from '@/utils'
import { hasPermission } from '@/utils/rbac-permission'
import { formatDateTime, normalizeListRow, pickField } from '@/utils/warehouseListMapper'
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer'
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(30)
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 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 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(instance: any, td: HTMLElement, row: number) {
const record = tableRows.value[row]
if (!record) {
td.innerHTML = ''
return td
}
const status = String(record.status || '')
const buttons = [
renderWarehouseActionBtn('detail', t('warehouse.common.detail'), 'detail'),
]
if (status === 'pending') {
if (hasPermission('Order.Customs9610.Update')) {
buttons.push(renderWarehouseActionBtn('edit', t('warehouse.common.edit'), 'edit'))
}
if (hasPermission('Order.Customs9610.Declare')) {
buttons.push(renderWarehouseActionBtn('declare', t('order.customs9610.actions.declare'), 'primary'))
}
}
return wrapWarehouseActions(td, buttons)
}
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(payload: { action: string; row: number }) {
const record = tableRows.value[payload.row]
if (!record?.id) return
if (payload.action === 'detail') {
openDetail(record.id, false)
} else if (payload.action === 'edit') {
openDetail(record.id, true)
} else if (payload.action === 'declare') {
handleDeclare(record.id)
}
}
function addItemRow() {
editForm.items.push({
sku: '',
cnName: '',
exHsCode: '',
declareElement: '',
unitPrice: 0,
quantity: 1,
sortNo: editForm.items.length,
})
}
function removeItemRow(index: number) {
editForm.items.splice(index, 1)
}
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) => !x.sku.trim() || !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,
})
$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 handleDeclare(id: number) {
Modal.confirm({
title: t('order.customs9610.confirm.declareTitle'),
content: t('order.customs9610.confirm.declareContent'),
onOk: async () => {
declaring.value = true
try {
const res = await declareCustoms9610(id)
const ok = Boolean(pickField(res, 'success', 'Success'))
$baseMessage(
String(pickField(res, 'message', 'Message') || (ok ? t('order.customs9610.message.declareSuccess') : t('order.customs9610.message.declareFailed'))),
ok ? 'success' : 'error',
)
await loadList()
await loadCounts()
} catch (e: any) {
$baseMessage(e?.message || t('order.customs9610.message.declareFailed'), 'error')
} finally {
declaring.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 handleBatchFetchDeclarationForm() {
return runBatchFetch('declarationForm', fetchCustoms9610DeclarationFormBatch)
}
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">
{{ t('order.myOrders.selectedCount', { count: selectedRowCount }) }}
</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
show-checkbox-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
v-if="editForm.failReason"
type="error"
:title="editForm.failReason"
style="margin-bottom: 12px"
/>
<a-form :model="editForm" layout="horizontal" auto-label-width>
<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>
<a-select v-model="editForm.businessType" :disabled="!isPending">
<a-option value="A">A</a-option>
<a-option value="B">B</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item :label="t('order.customs9610.field.preRecordFlag')">
<a-select v-model="editForm.preRecordFlag" :disabled="!isPending" allow-clear>
<a-option value="0">0</a-option>
<a-option value="1">1</a-option>
</a-select>
</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-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="SZ / other"
/>
</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-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="items-toolbar">
<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>
<a-table :data="editForm.items" :pagination="false" size="small" row-key="sortNo">
<template #columns>
<a-table-column :title="t('order.customs9610.items.sku')" :width="120">
<template #cell="{ record }">
<a-input v-model="record.sku" :disabled="!isPending" size="mini" />
</template>
</a-table-column>
<a-table-column :title="t('order.customs9610.items.cnName')" :width="160">
<template #cell="{ record }">
<a-input v-model="record.cnName" :disabled="!isPending" size="mini" />
</template>
</a-table-column>
<a-table-column :title="t('order.customs9610.items.exHsCode')" :width="100">
<template #cell="{ record }">
<a-input v-model="record.exHsCode" :disabled="!isPending" size="mini" />
</template>
</a-table-column>
<a-table-column :title="t('order.customs9610.items.declareElement')" :width="140">
<template #cell="{ record }">
<a-input v-model="record.declareElement" :disabled="!isPending" size="mini" />
</template>
</a-table-column>
<a-table-column :title="t('order.customs9610.items.unitPrice')" :width="100">
<template #cell="{ record }">
<a-input-number
v-model="record.unitPrice"
:disabled="!isPending"
size="mini"
:min="0"
:precision="5"
/>
</template>
</a-table-column>
<a-table-column :title="t('order.customs9610.items.quantity')" :width="90">
<template #cell="{ record }">
<a-input-number
v-model="record.quantity"
:disabled="!isPending"
size="mini"
:min="0"
:precision="5"
/>
</template>
</a-table-column>
<a-table-column v-if="isPending" :title="t('warehouse.common.delete')" :width="70">
<template #cell="{ rowIndex }">
<a-button size="mini" status="danger" type="text" @click="removeItemRow(rowIndex)">
{{ t('warehouse.common.delete') }}
</a-button>
</template>
</a-table-column>
</template>
</a-table>
</a-spin>
</ModalPro>
</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;
}
</style>