All checks were successful
Build and Deploy Front / deploy (push) Successful in 5m56s
- 支付弹窗(PC/H5)支持选券与批量支付体验优化,运单管理复用我的订单支付弹窗;立即支付仅允许待支付订单 - 订单详情费用明细展示优惠券抵扣/会员折扣负数行与「含优惠」;价格配置与手工加费过滤抵扣费用项 - 申报明细新增产品标识编码/非标产品标识编码/标准产品标识编码(PC/H5 下单、改单、详情、开放接口文档、公式目录、Excel 模板) - 9610 页「批量申报」改为「创建9610订单」:选运单并补充出口资质备案、电商平台 - 店铺订单批量按钮按动作独立 loading,获取面单限并发并显示进度 - 另含:国家管理、登录门户切换、监控与工作台、公告弹窗、nginx/vite 配置等改动
520 lines
16 KiB
Vue
520 lines
16 KiB
Vue
<script setup lang="ts">
|
|
import { computed, reactive, ref, watch } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useRouter } from 'vue-router'
|
|
import type { TableColumnData, TableRowSelection } from '@arco-design/web-vue'
|
|
import ModalPro from '@/components/ModalPro/index.vue'
|
|
import useModal from '@/components/ModalPro/useModal'
|
|
import { postOrderList } from '@/api/order'
|
|
import { createCustoms9610FromOrders } from '@/api/order/customs9610'
|
|
import { getExportQualificationList } from '@/api/customer/exportQualification'
|
|
import { getDictListAll } from '@/api/system/dict'
|
|
import { getOrderStatusMeta, resolveOrderStatusCode } from '@/constants/orderStatus'
|
|
import { $baseMessage } from '@/utils'
|
|
import { formatDateTime, normalizeListRow, pickField } from '@/utils/warehouseListMapper'
|
|
|
|
interface OrderPickRow {
|
|
id: number
|
|
serviceNo: string
|
|
clientNo: string
|
|
customerName: string
|
|
country: string
|
|
statusName: string
|
|
createTime: string
|
|
/** 已是 9610 汇总申报的运单不可再选 */
|
|
is9610: boolean
|
|
/** a-table 行级禁用勾选 */
|
|
disabled: boolean
|
|
}
|
|
|
|
interface FailRow {
|
|
orderId: number
|
|
serviceNo: string
|
|
message: string
|
|
}
|
|
|
|
const ORDER_PAGE_SIZE = 30
|
|
const SUMMARY_MODE = '9610summary'
|
|
|
|
const props = defineProps<{
|
|
isAdmin: boolean
|
|
customerOptions: Array<{ label: string; value: number }>
|
|
/** 列表页已选客户,作为管理端默认客户 */
|
|
defaultCustomerId?: number
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
success: []
|
|
}>()
|
|
|
|
const { t } = useI18n()
|
|
const router = useRouter()
|
|
const [registerModal, { openModal, closeModal }] = useModal() as any
|
|
|
|
const submitting = ref(false)
|
|
const form = reactive({
|
|
customerId: undefined as number | undefined,
|
|
exportQualificationId: undefined as number | undefined,
|
|
platformCode: '',
|
|
})
|
|
const qualificationOptions = ref<Array<{ label: string; value: number }>>([])
|
|
const qualificationLoading = ref(false)
|
|
const platformOptions = ref<Array<{ label: string; value: string }>>([])
|
|
|
|
const keyword = ref('')
|
|
const orderLoading = ref(false)
|
|
const orderRows = ref<OrderPickRow[]>([])
|
|
const orderPage = ref(1)
|
|
const orderTotal = ref(0)
|
|
const selectedKeys = ref<number[]>([])
|
|
/** 跨页保留已选运单信息,用于结果展示 */
|
|
const selectedMap = ref<Map<number, OrderPickRow>>(new Map())
|
|
const failRows = ref<FailRow[]>([])
|
|
|
|
const needCustomer = computed(() => props.isAdmin && !form.customerId)
|
|
|
|
const orderColumns = computed<TableColumnData[]>(() => [
|
|
{ title: t('order.customs9610.create.col.serviceNo'), dataIndex: 'serviceNo', width: 180, ellipsis: true, tooltip: true },
|
|
{ title: t('order.customs9610.create.col.clientNo'), dataIndex: 'clientNo', width: 150, ellipsis: true, tooltip: true },
|
|
...(props.isAdmin
|
|
? [{ title: t('order.customs9610.create.col.customer'), dataIndex: 'customerName', width: 140, ellipsis: true, tooltip: true }]
|
|
: []),
|
|
{ title: t('order.customs9610.create.col.country'), dataIndex: 'country', width: 90 },
|
|
{ title: t('order.customs9610.create.col.status'), dataIndex: 'statusName', width: 100 },
|
|
{ title: t('order.customs9610.create.col.createTime'), dataIndex: 'createTime', width: 160 },
|
|
{ title: t('order.customs9610.create.col.remark'), slotName: 'remark', width: 120 },
|
|
])
|
|
|
|
const rowSelection = computed<TableRowSelection>(() => ({
|
|
type: 'checkbox',
|
|
showCheckedAll: true,
|
|
onlyCurrent: false,
|
|
}))
|
|
|
|
const orderPagination = computed(() => ({
|
|
current: orderPage.value,
|
|
pageSize: ORDER_PAGE_SIZE,
|
|
total: orderTotal.value,
|
|
size: 'small' as const,
|
|
showTotal: true,
|
|
}))
|
|
|
|
const canSubmit = computed(
|
|
() =>
|
|
!submitting.value
|
|
&& !needCustomer.value
|
|
&& selectedKeys.value.length > 0
|
|
&& !!form.platformCode,
|
|
)
|
|
|
|
function statusName(code: string) {
|
|
const meta = getOrderStatusMeta(code)
|
|
return meta ? t(meta.labelKey) : code
|
|
}
|
|
|
|
function mapOrderRow(raw: any): OrderPickRow | null {
|
|
const row = normalizeListRow(raw)
|
|
const id = Number(pickField(row, 'id', 'Id') || 0)
|
|
if (!id) return null
|
|
const status = resolveOrderStatusCode(row) || String(pickField(row, 'status', 'Status') || '')
|
|
if (status === 'deleted') return null
|
|
const mode = String(pickField(row, 'exportCustomsMode', 'ExportCustomsMode') || '').toLowerCase()
|
|
const is9610 = mode === SUMMARY_MODE
|
|
return {
|
|
id,
|
|
serviceNo: String(pickField(row, 'serviceNo', 'ServiceNo') || '-'),
|
|
clientNo: String(pickField(row, 'clientNo', 'ClientNo') || '-'),
|
|
customerName: String(pickField(row, 'customerName', 'CustomerName', 'customerCode', 'CustomerCode') || '-'),
|
|
country: String(pickField(row, 'consigneeCountry', 'ConsigneeCountry') || '-'),
|
|
statusName: statusName(status),
|
|
createTime: formatDateTime(pickField(row, 'createTime', 'CreateTime')) || '-',
|
|
is9610,
|
|
disabled: is9610,
|
|
}
|
|
}
|
|
|
|
async function loadOrders(page = 1) {
|
|
if (needCustomer.value) {
|
|
orderRows.value = []
|
|
orderTotal.value = 0
|
|
return
|
|
}
|
|
orderLoading.value = true
|
|
orderPage.value = page
|
|
try {
|
|
const body: Record<string, unknown> = {
|
|
page,
|
|
pageSize: ORDER_PAGE_SIZE,
|
|
status: 'all',
|
|
keyword: keyword.value.trim(),
|
|
}
|
|
if (props.isAdmin && form.customerId) body.customerId = form.customerId
|
|
const res: any = await postOrderList(body as any)
|
|
orderRows.value = (res.items || [])
|
|
.map((raw: any) => mapOrderRow(raw))
|
|
.filter((x: OrderPickRow | null): x is OrderPickRow => !!x)
|
|
orderTotal.value = Number(res.total ?? res.totalCount ?? orderRows.value.length) || 0
|
|
} catch (e: any) {
|
|
orderRows.value = []
|
|
orderTotal.value = 0
|
|
$baseMessage(e?.message || t('order.customs9610.create.message.loadOrdersFailed'), 'error')
|
|
} finally {
|
|
orderLoading.value = false
|
|
}
|
|
}
|
|
|
|
function handleSelectionChange(keys: Array<string | number>) {
|
|
const ids = (keys || []).map(Number).filter((id) => id > 0)
|
|
const next = new Map<number, OrderPickRow>()
|
|
for (const id of ids) {
|
|
const row = orderRows.value.find((r) => r.id === id) ?? selectedMap.value.get(id)
|
|
if (row && !row.is9610) next.set(id, row)
|
|
}
|
|
selectedMap.value = next
|
|
selectedKeys.value = [...next.keys()]
|
|
}
|
|
|
|
function clearSelection() {
|
|
selectedMap.value = new Map()
|
|
selectedKeys.value = []
|
|
}
|
|
|
|
async function loadQualifications() {
|
|
qualificationOptions.value = []
|
|
form.exportQualificationId = undefined
|
|
if (needCustomer.value) return
|
|
qualificationLoading.value = true
|
|
try {
|
|
const body: Record<string, unknown> = { page: 1, pageSize: 200 }
|
|
if (props.isAdmin && form.customerId) body.customerId = form.customerId
|
|
const res: any = await getExportQualificationList(body)
|
|
const items = Array.isArray(res?.items) ? res.items : []
|
|
let defaultId: number | undefined
|
|
qualificationOptions.value = items
|
|
.map((raw: any) => {
|
|
const id = Number(pickField(raw, 'id', 'Id') || 0)
|
|
if (!id) return null
|
|
const name = String(pickField(raw, 'companyName', 'CompanyName') || pickField(raw, 'socialCreditCode', 'SocialCreditCode') || `#${id}`)
|
|
const isDefault = Boolean(pickField(raw, 'isDefault', 'IsDefault'))
|
|
if (isDefault) defaultId = id
|
|
return {
|
|
label: isDefault ? `${name}${t('order.customs9610.create.defaultSuffix')}` : name,
|
|
value: id,
|
|
}
|
|
})
|
|
.filter((x: any): x is { label: string; value: number } => !!x)
|
|
form.exportQualificationId = defaultId ?? qualificationOptions.value[0]?.value
|
|
} catch {
|
|
qualificationOptions.value = []
|
|
} finally {
|
|
qualificationLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function ensurePlatformOptions() {
|
|
if (platformOptions.value.length) return
|
|
try {
|
|
const rows = await getDictListAll({ dictType: 'e-commerce_platform' })
|
|
platformOptions.value = rows
|
|
.map((r: any) => ({
|
|
label: String(r.dictName ?? r.dictCode ?? '').trim(),
|
|
value: String(r.dictCode ?? '').trim(),
|
|
}))
|
|
.filter((x) => x.value)
|
|
} catch {
|
|
platformOptions.value = []
|
|
}
|
|
}
|
|
|
|
function goQualificationManage() {
|
|
router.push({ path: '/user/exportQualification' })
|
|
}
|
|
|
|
async function open() {
|
|
form.customerId = props.isAdmin ? props.defaultCustomerId : undefined
|
|
form.platformCode = ''
|
|
keyword.value = ''
|
|
failRows.value = []
|
|
clearSelection()
|
|
openModal()
|
|
await Promise.all([ensurePlatformOptions(), loadQualifications(), loadOrders(1)])
|
|
}
|
|
|
|
function close() {
|
|
if (submitting.value) return
|
|
closeModal()
|
|
}
|
|
|
|
async function handleSubmit() {
|
|
if (!canSubmit.value) return
|
|
submitting.value = true
|
|
try {
|
|
const res: any = await createCustoms9610FromOrders({
|
|
orderIds: [...selectedKeys.value],
|
|
exportQualificationId: form.exportQualificationId,
|
|
platformCode: form.platformCode,
|
|
})
|
|
const items: any[] = Array.isArray(res?.items ?? res?.Items) ? (res.items ?? res.Items) : []
|
|
const successCount = Number(pickField(res, 'successCount', 'SuccessCount') ?? 0)
|
|
const failed = items.filter((x) => !pickField(x, 'success', 'Success'))
|
|
if (successCount > 0) emit('success')
|
|
|
|
if (!failed.length) {
|
|
$baseMessage(t('order.customs9610.create.message.allSuccess', { count: successCount }), 'success')
|
|
closeModal()
|
|
return
|
|
}
|
|
|
|
failRows.value = failed.map((x) => {
|
|
const orderId = Number(pickField(x, 'orderId', 'OrderId') || 0)
|
|
return {
|
|
orderId,
|
|
serviceNo: String(pickField(x, 'serviceNo', 'ServiceNo') || selectedMap.value.get(orderId)?.serviceNo || orderId),
|
|
message: String(pickField(x, 'message', 'Message') || t('order.customs9610.create.message.failed')),
|
|
}
|
|
})
|
|
// 成功的运单从已选中移除,保留失败的便于调整后重试
|
|
const failedIds = new Set(failRows.value.map((x) => x.orderId))
|
|
const next = new Map([...selectedMap.value].filter(([id]) => failedIds.has(id)))
|
|
selectedMap.value = next
|
|
selectedKeys.value = [...next.keys()]
|
|
$baseMessage(
|
|
t('order.customs9610.create.message.partial', { success: successCount, fail: failed.length }),
|
|
'warning',
|
|
)
|
|
if (successCount > 0) void loadOrders(orderPage.value)
|
|
} catch (e: any) {
|
|
$baseMessage(e?.message || t('order.customs9610.create.message.failed'), 'error')
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => form.customerId,
|
|
(val, old) => {
|
|
if (val === old) return
|
|
clearSelection()
|
|
failRows.value = []
|
|
void loadQualifications()
|
|
void loadOrders(1)
|
|
},
|
|
)
|
|
|
|
defineExpose({ open })
|
|
</script>
|
|
|
|
<template>
|
|
<ModalPro
|
|
@register="registerModal"
|
|
:title="t('order.customs9610.create.title')"
|
|
width="1000px"
|
|
:mask-closable="false"
|
|
:closable="!submitting"
|
|
@cancel="close"
|
|
>
|
|
<a-alert type="info" :show-icon="false" class="c9610-create__tip">
|
|
{{ t('order.customs9610.create.tip') }}
|
|
</a-alert>
|
|
|
|
<div class="c9610-create__section-title">{{ t('order.customs9610.create.sectionSupplement') }}</div>
|
|
<a-form :model="form" layout="vertical" class="c9610-create__form">
|
|
<a-row :gutter="20">
|
|
<a-col v-if="isAdmin" :span="8">
|
|
<a-form-item :label="t('order.customs9610.create.customer')" required>
|
|
<a-select
|
|
v-model="form.customerId"
|
|
allow-search
|
|
:options="customerOptions"
|
|
:placeholder="t('order.customs9610.create.customerPh')"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col :span="isAdmin ? 8 : 12">
|
|
<a-form-item :label="t('order.customs9610.create.exportQualification')">
|
|
<a-select
|
|
v-model="form.exportQualificationId"
|
|
allow-clear
|
|
:loading="qualificationLoading"
|
|
:disabled="needCustomer"
|
|
:options="qualificationOptions"
|
|
:placeholder="t('order.customs9610.create.exportQualificationPh')"
|
|
/>
|
|
<template #extra>
|
|
<a-link :hoverable="false" @click="goQualificationManage">
|
|
{{ t('order.customs9610.create.manageQualification') }}
|
|
</a-link>
|
|
</template>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col :span="isAdmin ? 8 : 12">
|
|
<a-form-item :label="t('order.customs9610.create.platform')" required>
|
|
<a-select
|
|
v-model="form.platformCode"
|
|
allow-search
|
|
:options="platformOptions"
|
|
:placeholder="t('order.customs9610.create.platformPh')"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
</a-row>
|
|
</a-form>
|
|
|
|
<div class="c9610-create__section-title">
|
|
{{ t('order.customs9610.create.sectionOrders') }}
|
|
</div>
|
|
<div class="c9610-create__toolbar">
|
|
<a-input
|
|
v-model="keyword"
|
|
allow-clear
|
|
:disabled="needCustomer"
|
|
:placeholder="t('order.customs9610.create.keywordPh')"
|
|
style="width: 260px"
|
|
@press-enter="loadOrders(1)"
|
|
/>
|
|
<a-button class="app-btn-search" :loading="orderLoading" :disabled="needCustomer" @click="loadOrders(1)">
|
|
{{ t('order.myOrders.search.actions.search') }}
|
|
</a-button>
|
|
<span class="c9610-create__selected">
|
|
{{ t('order.customs9610.create.selected', { count: selectedKeys.length }) }}
|
|
<a-link v-if="selectedKeys.length" :hoverable="false" @click="clearSelection">
|
|
{{ t('order.customs9610.create.clearSelected') }}
|
|
</a-link>
|
|
</span>
|
|
</div>
|
|
|
|
<a-table
|
|
row-key="id"
|
|
size="small"
|
|
:loading="orderLoading"
|
|
:data="orderRows"
|
|
:columns="orderColumns"
|
|
:row-selection="rowSelection"
|
|
:selected-keys="selectedKeys"
|
|
:pagination="orderPagination"
|
|
:scroll="{ y: 300 }"
|
|
:row-class="(record: OrderPickRow) => (record.is9610 ? 'c9610-create__row--disabled' : '')"
|
|
@selection-change="handleSelectionChange"
|
|
@page-change="loadOrders"
|
|
>
|
|
<template #remark="{ record }">
|
|
<a-tag v-if="record.is9610" size="small" color="gray">
|
|
{{ t('order.customs9610.create.already9610') }}
|
|
</a-tag>
|
|
</template>
|
|
<template #empty>
|
|
<div class="c9610-create__empty">
|
|
{{ needCustomer ? t('order.customs9610.create.selectCustomerFirst') : t('order.customs9610.create.emptyOrders') }}
|
|
</div>
|
|
</template>
|
|
</a-table>
|
|
|
|
<div v-if="failRows.length" class="c9610-create__fail">
|
|
<div class="c9610-create__fail-title">
|
|
{{ t('order.customs9610.create.failTitle', { count: failRows.length }) }}
|
|
</div>
|
|
<ul>
|
|
<li v-for="row in failRows" :key="row.orderId">
|
|
<b>{{ row.serviceNo }}</b>
|
|
<span>{{ row.message }}</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<template #footer>
|
|
<div class="c9610-create__footer">
|
|
<a-button :disabled="submitting" @click="close">{{ t('common.cancel') }}</a-button>
|
|
<a-button type="primary" :loading="submitting" :disabled="!canSubmit" @click="handleSubmit">
|
|
{{ t('order.customs9610.create.submit', { count: selectedKeys.length }) }}
|
|
</a-button>
|
|
</div>
|
|
</template>
|
|
</ModalPro>
|
|
</template>
|
|
|
|
<style scoped lang="less">
|
|
.c9610-create__tip {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.c9610-create__section-title {
|
|
margin-bottom: 12px;
|
|
padding-left: 8px;
|
|
border-left: 3px solid var(--app-primary-color);
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--color-text-1);
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.c9610-create__form {
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.c9610-create__toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.c9610-create__selected {
|
|
margin-left: auto;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 13px;
|
|
color: var(--color-text-2);
|
|
}
|
|
|
|
.c9610-create__empty {
|
|
padding: 24px 12px;
|
|
text-align: center;
|
|
font-size: 13px;
|
|
color: var(--color-text-3);
|
|
}
|
|
|
|
:deep(.c9610-create__row--disabled) {
|
|
color: var(--color-text-4);
|
|
}
|
|
|
|
.c9610-create__fail {
|
|
margin-top: 12px;
|
|
padding: 10px 12px;
|
|
border-radius: 4px;
|
|
background: rgb(var(--danger-1));
|
|
|
|
ul {
|
|
margin: 6px 0 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
max-height: 120px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
li {
|
|
display: flex;
|
|
gap: 12px;
|
|
padding: 2px 0;
|
|
font-size: 12px;
|
|
color: rgb(var(--danger-6));
|
|
}
|
|
}
|
|
|
|
.c9610-create__fail-title {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: rgb(var(--danger-6));
|
|
}
|
|
|
|
.c9610-create__footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
}
|
|
|
|
:deep(.arco-table-th) {
|
|
background: var(--color-fill-1);
|
|
}
|
|
</style>
|