feat: 新增 9610 申报页与批量清单/报关单操作
同步 SPA 回退、权限目录、拣货绑定组件及相关系统页调整。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b5edbb8447
commit
cf4d2c4a3f
BIN
IDE官网/assets/img/favicon.png
Normal file
BIN
IDE官网/assets/img/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@ -10,7 +10,7 @@
|
||||
const PHONE = '18123959245';
|
||||
const EMAIL = 'bd01@1st56.com';
|
||||
const WECHAT = 'ydgjzzh';
|
||||
const ICP = '粤ICP备19018720号';
|
||||
const ICP = '粤ICP备2025463970号-2';
|
||||
const ADDRESS_ZH = '深圳宝安区福永怀德翠岗5区36栋';
|
||||
const ADDRESS_EN = 'Bldg 36, Cuigang 5, Fuyong, Bao’an, Shenzhen';
|
||||
|
||||
|
||||
@ -115,8 +115,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const languageStorageKey = 'ide-language';
|
||||
const pageTitleMap = {
|
||||
'IDE|Faster & Simpler': 'IDE|更快 · 更简单',
|
||||
'IDE|Immediate Delivery Express — Cross-Border Logistics': 'IDE|Immediate Delivery Express — 跨境物流',
|
||||
'IDE | Global Digital One-Stop Cross-Border Logistics': 'IDE | 全球数字化一站式跨境物流服务商',
|
||||
'IDE|Immediate Delivery Express — Cross-Border Logistics': 'IDE | 全球数字化一站式跨境物流服务商',
|
||||
'Faster & Simpler': '更快 · 更简单',
|
||||
'|Faster & Simpler': '更快 · 更简单',
|
||||
'IDE - About': 'IDE - 关于我们',
|
||||
|
||||
@ -5,12 +5,12 @@
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
|
||||
<title>IDE|Immediate Delivery Express — 跨境物流</title>
|
||||
<title>IDE | 全球数字化一站式跨境物流服务商</title>
|
||||
<meta content="IDE 面向中小卖家的跨境物流。全球数字化一站式跨境物流服务商。注册登记后即可询价,一口价不砍,专属顾问跟到签收。" name="description">
|
||||
<meta content="IDE, 跨境物流, 中小卖家, 9610, 疑难单, 出口退税, 欧美专线, 东南亚专线" name="keywords">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link href="assets/img/favicon.png" rel="icon">
|
||||
<!-- Favicons(logo1 已入库;favicon.png 由 postbuild 同步) -->
|
||||
<link href="assets/img/logo1.png" rel="icon" type="image/png">
|
||||
<link href="assets/img/apple-touch-icon.png" rel="apple-touch-icon">
|
||||
|
||||
<!-- Vendor CSS Files -->
|
||||
|
||||
@ -370,6 +370,17 @@ const menuData = [
|
||||
hideInMenu: false,
|
||||
parentRoute: '/order',
|
||||
},
|
||||
{
|
||||
level: 2,
|
||||
name: '9610订单',
|
||||
permissionCode: 'Order.Customs9610',
|
||||
route: '/order/customs9610Order',
|
||||
component: 'views/order/customs9610Order/index',
|
||||
icon: null,
|
||||
order: 5,
|
||||
hideInMenu: false,
|
||||
parentRoute: '/order',
|
||||
},
|
||||
|
||||
// 一级菜单 - 渠道
|
||||
{
|
||||
|
||||
@ -73,6 +73,20 @@ server {
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /favicon.ico {
|
||||
try_files /favicon.png =404;
|
||||
expires 7d;
|
||||
add_header Cache-Control "public";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /favicon.png {
|
||||
try_files /favicon.png =404;
|
||||
expires 7d;
|
||||
add_header Cache-Control "public";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# ============ 客户 / 管理端登录入口 ============
|
||||
location = /login {
|
||||
try_files /index.html =200;
|
||||
@ -166,6 +180,9 @@ server {
|
||||
location ^~ /order/pickupOrder {
|
||||
try_files /index.html =200;
|
||||
}
|
||||
location ^~ /order/customs9610Order {
|
||||
try_files /index.html =200;
|
||||
}
|
||||
location ^~ /order/batchJobs {
|
||||
try_files /index.html =200;
|
||||
}
|
||||
|
||||
@ -39,6 +39,20 @@ server {
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /favicon.ico {
|
||||
try_files /favicon.png =404;
|
||||
expires 7d;
|
||||
add_header Cache-Control "public";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /favicon.png {
|
||||
try_files /favicon.png =404;
|
||||
expires 7d;
|
||||
add_header Cache-Control "public";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /login {
|
||||
try_files /index.html =200;
|
||||
}
|
||||
@ -85,6 +99,7 @@ server {
|
||||
location ^~ /order/fulfillOrders { try_files /index.html =200; }
|
||||
location ^~ /order/waybillManage { try_files /index.html =200; }
|
||||
location ^~ /order/pickupOrder { try_files /index.html =200; }
|
||||
location ^~ /order/customs9610Order { try_files /index.html =200; }
|
||||
location ^~ /order/batchJobs { try_files /index.html =200; }
|
||||
location ^~ /order/templateMapping { try_files /index.html =200; }
|
||||
location ^~ /order/platformStore { try_files /index.html =200; }
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "^6.39.17",
|
||||
"@fontsource/zcool-qingke-huangyou": "^5.3.0",
|
||||
"@handsontable/vue3": "^16.2.0",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@vueuse/core": "^9.3.0",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -32,6 +32,9 @@ importers:
|
||||
'@codemirror/view':
|
||||
specifier: ^6.39.17
|
||||
version: 6.39.17
|
||||
'@fontsource/zcool-qingke-huangyou':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@handsontable/vue3':
|
||||
specifier: ^16.2.0
|
||||
version: 16.2.0(handsontable@16.2.0)(vue@3.5.18(typescript@4.9.5))
|
||||
@ -922,6 +925,9 @@ packages:
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@fontsource/zcool-qingke-huangyou@5.3.0':
|
||||
resolution: {integrity: sha512-6/ld8x/knq1NjDQxLSyg9pDnBPOmtWDb30ZTFk+n8OxfkBoUvPAxh3aV/6+UFGmA5CekJfcgpUwd2/x/weLvSQ==}
|
||||
|
||||
'@handsontable/pikaday@1.0.0':
|
||||
resolution: {integrity: sha512-1VN6N38t5/DcjJ7y7XUYrDx1LuzvvzlrFdBdMG90Qo1xc8+LXHqbWbsTEm5Ec5gXTEbDEO53vUT35R+2COmOyg==}
|
||||
|
||||
@ -7893,6 +7899,8 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@fontsource/zcool-qingke-huangyou@5.3.0': {}
|
||||
|
||||
'@handsontable/pikaday@1.0.0': {}
|
||||
|
||||
'@handsontable/vue3@16.2.0(handsontable@16.2.0)(vue@3.5.18(typescript@4.9.5))':
|
||||
|
||||
@ -74,6 +74,12 @@ function copyOfficialSite() {
|
||||
fs.copyFileSync(src9610, path.join(dist, '9610.html'));
|
||||
}
|
||||
|
||||
const faviconSrc = path.join(siteDir, 'assets', 'img', 'logo1.png');
|
||||
if (fs.existsSync(faviconSrc)) {
|
||||
fs.copyFileSync(faviconSrc, path.join(siteDir, 'assets', 'img', 'favicon.png'));
|
||||
fs.copyFileSync(faviconSrc, path.join(dist, 'favicon.png'));
|
||||
}
|
||||
|
||||
console.log('[postbuild] copied official site to dist/site (assets under /site/assets)');
|
||||
}
|
||||
|
||||
|
||||
37
src/api/order/customs9610.ts
Normal file
37
src/api/order/customs9610.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { axiosInstance } from '@/api/rbac'
|
||||
import { assertBusinessOk, omsUrl, parseListPayload, unwrapData } from '@/api/oms/common'
|
||||
|
||||
function postOk(path: string, body: Record<string, unknown>) {
|
||||
return axiosInstance.post(omsUrl(path), body).then((res) => {
|
||||
assertBusinessOk(res.data)
|
||||
return unwrapData(res.data)
|
||||
})
|
||||
}
|
||||
|
||||
function getData(path: string, params?: Record<string, unknown>) {
|
||||
return axiosInstance.get(omsUrl(path), { params }).then((res) => unwrapData(res.data))
|
||||
}
|
||||
|
||||
export const getCustoms9610List = (body: Record<string, unknown>) =>
|
||||
axiosInstance.post(omsUrl('/order/customs-9610/list'), body).then((res) => parseListPayload(res.data))
|
||||
|
||||
export const getCustoms9610StatusCounts = (params?: { customerId?: number }) =>
|
||||
getData('/order/customs-9610/status-counts', params as Record<string, unknown> | undefined)
|
||||
|
||||
export const getCustoms9610Detail = (id: number | string) =>
|
||||
getData('/order/customs-9610/detail', { id })
|
||||
|
||||
export const updateCustoms9610 = (body: Record<string, unknown>) =>
|
||||
postOk('/order/customs-9610/update', body)
|
||||
|
||||
export const declareCustoms9610 = (id: number | string) =>
|
||||
postOk('/order/customs-9610/declare', { id })
|
||||
|
||||
export const declareCustoms9610Batch = (ids: Array<number | string>) =>
|
||||
postOk('/order/customs-9610/declare-batch', { ids })
|
||||
|
||||
export const fetchCustoms9610ManifestBatch = (ids: Array<number | string>) =>
|
||||
postOk('/order/customs-9610/fetch-manifest-batch', { ids })
|
||||
|
||||
export const fetchCustoms9610DeclarationFormBatch = (ids: Array<number | string>) =>
|
||||
postOk('/order/customs-9610/fetch-declaration-form-batch', { ids })
|
||||
@ -30,6 +30,9 @@ export const createPickupOrderV2 = (body: PickupOrderCreateBody) =>
|
||||
export const getPickupOrderList = (body: Record<string, unknown>) =>
|
||||
axiosInstance.post(omsUrl('/order/pickup-order/list'), body).then((res) => parseListPayload(res.data))
|
||||
|
||||
export const getPickupOrderStatusCounts = (params?: { customerId?: number }) =>
|
||||
getData('/order/pickup-order/status-counts', params as Record<string, unknown> | undefined)
|
||||
|
||||
export const getPickupOrderDetail = (id: number | string) =>
|
||||
getData('/order/pickup-order/detail', { id })
|
||||
|
||||
|
||||
@ -12,33 +12,38 @@
|
||||
|
||||
<template v-else>
|
||||
<div class="shell-header">
|
||||
<div class="header-left">
|
||||
<div v-if="$slots.icon" class="header-icon">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<div class="header-title-row">
|
||||
<h2 class="header-title">{{ title }}</h2>
|
||||
<a-tag
|
||||
v-for="tag in tags"
|
||||
:key="tag.label"
|
||||
:color="tag.color"
|
||||
size="small"
|
||||
>
|
||||
{{ tag.label }}
|
||||
</a-tag>
|
||||
<div class="header-main">
|
||||
<div class="header-left">
|
||||
<div v-if="$slots.icon" class="header-icon">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<p v-if="subtitle" class="header-subtitle">{{ subtitle }}</p>
|
||||
<div class="header-text">
|
||||
<div class="header-title-row">
|
||||
<h2 class="header-title">{{ title }}</h2>
|
||||
<a-tag
|
||||
v-for="tag in tags"
|
||||
:key="tag.label"
|
||||
:color="tag.color"
|
||||
size="small"
|
||||
>
|
||||
{{ tag.label }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<p v-if="subtitle" class="header-subtitle">{{ subtitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="stats.length" class="header-stats">
|
||||
<div v-for="stat in stats" :key="stat.label" class="stat-item">
|
||||
<span class="stat-value">{{ stat.value }}</span>
|
||||
<span class="stat-label">{{ stat.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.actions" class="header-actions">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="stats.length" class="header-stats">
|
||||
<div v-for="stat in stats" :key="stat.label" class="stat-item">
|
||||
<span class="stat-value">{{ stat.value }}</span>
|
||||
<span class="stat-label">{{ stat.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.actions" class="header-actions">
|
||||
<slot name="actions" />
|
||||
<div v-if="$slots['header-extra']" class="header-extra">
|
||||
<slot name="header-extra" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -51,12 +56,10 @@
|
||||
@click="onTabClick(tab.key)"
|
||||
>
|
||||
<span>{{ tab.label }}</span>
|
||||
<a-badge
|
||||
<span
|
||||
v-if="tab.badge != null && tab.badge !== ''"
|
||||
:count="tab.badge"
|
||||
:max-count="999"
|
||||
class="tab-badge"
|
||||
/>
|
||||
class="tab-count"
|
||||
>{{ tab.badge }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots['tabs-extra']" class="tabs-extra">
|
||||
@ -181,17 +184,23 @@ function onTabClick(key: string) {
|
||||
|
||||
.shell-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 20px 24px 16px;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--color-border-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
@ -217,11 +226,15 @@ function onTabClick(key: string) {
|
||||
}
|
||||
|
||||
.header-subtitle {
|
||||
margin: 6px 0 0;
|
||||
margin: 4px 0 0;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.header-extra {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-stats {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
@ -296,26 +309,38 @@ function onTabClick(key: string) {
|
||||
}
|
||||
}
|
||||
|
||||
.tab-badge :deep(.arco-badge-number) {
|
||||
.tab-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 22px;
|
||||
height: 20px;
|
||||
padding: 0 6px;
|
||||
border-radius: 10px;
|
||||
background: var(--color-fill-3);
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
color: var(--color-text-2);
|
||||
box-shadow: none;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-badge :deep(.arco-badge-number) {
|
||||
.tab-item.active .tab-count {
|
||||
background: rgba(var(--primary-6), 0.12);
|
||||
color: var(--app-primary-color, #2d8c8c);
|
||||
}
|
||||
|
||||
.tabs-extra {
|
||||
padding: 8px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.shell-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 20px 24px 24px;
|
||||
padding: 16px 24px 20px;
|
||||
}
|
||||
|
||||
.shell-body--loading {
|
||||
|
||||
@ -273,12 +273,16 @@ export const PAGE_BUTTON_PARENT_ALIASES: Record<string, string> = {
|
||||
'System.DataPerm.Audit': 'Rbac.DataPerm.Audit',
|
||||
'System.DataPerm.Templates': 'Rbac.DataPerm.Template',
|
||||
'System.DataPerm.Users': 'Rbac.DataPerm',
|
||||
'Rbac.email': 'System.BatchEmail',
|
||||
'System.BatchEmail.Use': 'System.BatchEmail',
|
||||
'System.BatchEmail': 'System.BatchEmail',
|
||||
'Api.ApiTest': 'Api.ApiTest',
|
||||
'Api.Test.Query': 'Api.ApiTest',
|
||||
'Channel.LabelDesign': 'Channel.LabelDesign',
|
||||
'Order.BatchJobs.Query': 'Order.BatchJobs',
|
||||
'Order.WaybillManage.Query': 'Order.WaybillManage',
|
||||
'Order.Pickup.Query': 'Order.Pickup',
|
||||
'Order.Customs9610.Query': 'Order.Customs9610',
|
||||
'Order.SenderAddress.Query': 'Order.SenderAddress',
|
||||
'Order.PlatformStore.Query': 'Order.PlatformStore',
|
||||
'Order.PlatformStoreOrder.Query': 'Order.PlatformStoreOrder',
|
||||
@ -591,6 +595,15 @@ const CORE_GROUPS: PageButtonGroup[] = [
|
||||
{ code: 'Search', name: '搜索' },
|
||||
{ code: 'Create', name: '创建揽收订单' },
|
||||
]),
|
||||
B('Order.Customs9610', [
|
||||
{ code: 'Query', name: '查询' },
|
||||
{ code: 'Search', name: '搜索' },
|
||||
{ code: 'Update', name: '编辑' },
|
||||
{ code: 'Declare', name: '申报' },
|
||||
{ code: 'DeclareBatch', name: '批量申报' },
|
||||
{ code: 'FetchManifestBatch', name: '批量获取清单数据' },
|
||||
{ code: 'FetchDeclarationFormBatch', name: '批量获取报关单' },
|
||||
]),
|
||||
B('Order.SenderAddress', [
|
||||
{ code: 'Query', name: '查询' },
|
||||
{ code: 'Search', name: '搜索' },
|
||||
|
||||
@ -119,6 +119,7 @@ export default {
|
||||
'menu.order.fulfillOrders': 'Fulfillment Orders',
|
||||
'menu.order.waybillManage': 'Waybill Management',
|
||||
'menu.order.pickupOrder': 'Pickup sheets',
|
||||
'menu.order.customs9610Order': '9610 Orders',
|
||||
'menu.order.batchJobs': 'Batch Jobs',
|
||||
'menu.order.platformStore': 'Platform Stores',
|
||||
'menu.order.platformStoreOrder': 'Store Orders',
|
||||
@ -464,7 +465,7 @@ export default {
|
||||
'user.senderAddress.table.addressName': 'Alias',
|
||||
'user.senderAddress.table.senderName': 'Contact',
|
||||
'user.senderAddress.table.phone': 'Phone',
|
||||
'user.senderAddress.table.country': 'Country',
|
||||
'user.senderAddress.table.destCountry': 'Country',
|
||||
'user.senderAddress.table.province': 'Province',
|
||||
'user.senderAddress.table.city': 'City',
|
||||
'user.senderAddress.table.address': 'Address',
|
||||
@ -475,7 +476,7 @@ export default {
|
||||
'user.senderAddress.form.senderName': 'Contact',
|
||||
'user.senderAddress.form.phone': 'Phone',
|
||||
'user.senderAddress.form.company': 'Company',
|
||||
'user.senderAddress.form.countryCode': 'Country Code',
|
||||
'user.senderAddress.form.destCountryCode': 'Country Code',
|
||||
'user.senderAddress.form.province': 'Province',
|
||||
'user.senderAddress.form.city': 'City',
|
||||
'user.senderAddress.form.zipCode': 'Zip Code',
|
||||
@ -491,6 +492,79 @@ export default {
|
||||
'user.senderAddress.message.typeRequired': 'Address type is required',
|
||||
'user.senderAddress.message.senderNameRequired': 'Contact is required',
|
||||
'user.senderAddress.message.addressRequired': 'Street address is required',
|
||||
'order.customs9610.status.pending': 'Pending declare',
|
||||
'order.customs9610.status.declared': 'Declared',
|
||||
'order.customs9610.search.keyword': 'Service No. / Client No. / Customs order No.',
|
||||
'order.customs9610.empty.hint': 'No 9610 declarations yet. Orders created with ExportCustomsMode=9610Summary will appear here.',
|
||||
'order.customs9610.table.serviceNo': 'Service No.',
|
||||
'order.customs9610.table.clientNo': 'Client No.',
|
||||
'order.customs9610.table.destCountry': 'Dest country',
|
||||
'order.customs9610.table.grossWeight': 'Gross weight',
|
||||
'order.customs9610.table.currency': 'Currency',
|
||||
'order.customs9610.table.businessType': 'Business type',
|
||||
'order.customs9610.table.status': 'Status',
|
||||
'order.customs9610.table.customsOrderNo': 'Customs order No.',
|
||||
'order.customs9610.table.declareTime': 'Declare time',
|
||||
'order.customs9610.table.createTime': 'Created',
|
||||
'order.customs9610.actions.declare': 'Declare',
|
||||
'order.customs9610.actions.batchDeclare': 'Batch declare',
|
||||
'order.customs9610.actions.batchFetchManifest': 'Batch fetch manifest',
|
||||
'order.customs9610.actions.batchFetchDeclarationForm': 'Batch fetch declaration form',
|
||||
'order.customs9610.modal.edit': 'Edit 9610 declaration',
|
||||
'order.customs9610.modal.detail': '9610 declaration detail',
|
||||
'order.customs9610.field.platformCode': 'Platform',
|
||||
'order.customs9610.field.businessType': 'Business type',
|
||||
'order.customs9610.field.preRecordFlag': 'Pre-record flag',
|
||||
'order.customs9610.field.clientNo': 'Client No.',
|
||||
'order.customs9610.field.serviceNo': 'Service No.',
|
||||
'order.customs9610.field.destCountry': 'Dest country',
|
||||
'order.customs9610.field.currency': 'Currency',
|
||||
'order.customs9610.field.grossWeight': 'Gross weight (KG)',
|
||||
'order.customs9610.field.freight': 'Freight',
|
||||
'order.customs9610.field.ecomCorpCode': 'E-commerce corp code',
|
||||
'order.customs9610.field.ecomCorpName': 'E-commerce corp name',
|
||||
'order.customs9610.field.producerCode': 'Producer code',
|
||||
'order.customs9610.field.producerName': 'Producer name',
|
||||
'order.customs9610.field.region': 'Region',
|
||||
'order.customs9610.field.agentDeclare': 'Agent declare',
|
||||
'order.customs9610.field.actualSellerCode': 'Actual seller code',
|
||||
'order.customs9610.field.shipmentNo': 'Shipment No.',
|
||||
'order.customs9610.field.customsOrderNo': 'Customs order No.',
|
||||
'order.customs9610.field.isSummarySimple': 'Summary simple',
|
||||
'order.customs9610.items.title': 'Order items',
|
||||
'order.customs9610.items.add': 'Add row',
|
||||
'order.customs9610.items.sku': 'SKU',
|
||||
'order.customs9610.items.cnName': 'CN name',
|
||||
'order.customs9610.items.exHsCode': 'Export HS code',
|
||||
'order.customs9610.items.declareElement': 'Declare element',
|
||||
'order.customs9610.items.unitPrice': 'Unit price',
|
||||
'order.customs9610.items.quantity': 'Quantity',
|
||||
'order.customs9610.confirm.declareTitle': 'Confirm declare',
|
||||
'order.customs9610.confirm.declareContent': 'This will call the API-center upstream interface. Continue?',
|
||||
'order.customs9610.confirm.batchDeclareTitle': 'Batch declare',
|
||||
'order.customs9610.confirm.batchDeclareContent': 'Declare {count} selected pending records?',
|
||||
'order.customs9610.confirm.batchManifestTitle': 'Batch fetch manifest',
|
||||
'order.customs9610.confirm.batchManifestContent':
|
||||
'Fetch manifest data for {count} selected declared records?',
|
||||
'order.customs9610.confirm.batchDeclarationFormTitle': 'Batch fetch declaration form',
|
||||
'order.customs9610.confirm.batchDeclarationFormContent':
|
||||
'Fetch declaration forms for {count} selected declared records?',
|
||||
'order.customs9610.message.loadFailed': 'Load failed',
|
||||
'order.customs9610.message.saveSuccess': 'Saved',
|
||||
'order.customs9610.message.saveFailed': 'Save failed',
|
||||
'order.customs9610.message.declareSuccess': 'Declared',
|
||||
'order.customs9610.message.declareFailed': 'Declare failed',
|
||||
'order.customs9610.message.batchResult': 'Success {success}, failed {fail}',
|
||||
'order.customs9610.message.fetchManifestFailed': 'Failed to fetch manifest',
|
||||
'order.customs9610.message.fetchDeclarationFormFailed': 'Failed to fetch declaration form',
|
||||
'order.customs9610.message.selectFirst': 'Select records first',
|
||||
'order.customs9610.message.platformCodeRequired': 'Platform is required',
|
||||
'order.customs9610.message.clientServiceRequired': 'Client No. and Service No. are required',
|
||||
'order.customs9610.message.weightRequired': 'Gross weight must be > 0',
|
||||
'order.customs9610.message.currencyDestCountryRequired': 'Currency and dest country are required',
|
||||
'order.customs9610.message.preRecordRequired': 'Pre-record flag is required for form type B',
|
||||
'order.customs9610.message.aFieldsRequired': 'Region and agentDeclare are required for form type A',
|
||||
'order.customs9610.message.itemsRequired': 'SKU and CN name are required',
|
||||
'order.pickupOrder.search.keyword': 'System No. / Carrier Waybill / Contact',
|
||||
'order.pickupOrder.search.customer': 'Customer',
|
||||
'order.pickupOrder.search.status': 'Status',
|
||||
@ -510,7 +584,23 @@ export default {
|
||||
'order.pickupOrder.actions.create': 'Create Pickup Order',
|
||||
'order.pickupOrder.actions.submit': 'Submit',
|
||||
'order.pickupOrder.modal.create': 'Create Pickup Order',
|
||||
'order.pickupOrder.hint.createFromMyOrders': 'To merge multiple parcels, select them in My Orders and click Create Pickup',
|
||||
'order.pickupOrder.hint.createFromMyOrders': 'Bind pre-declared waybills or create a new one; multi-select must share a warehouse',
|
||||
'order.pickupOrder.create.lead': 'Fill pickup and cargo details. A waybill and pickup sheet will be created.',
|
||||
'order.pickupOrder.create.leadBind': 'Select unbound pre-declared waybills (same warehouse), then fill pickup details.',
|
||||
'order.pickupOrder.create.modeBind': 'Bind existing waybills',
|
||||
'order.pickupOrder.create.modeNew': 'Create new waybill pickup',
|
||||
'order.pickupOrder.create.sectionPickup': 'Pickup details',
|
||||
'order.pickupOrder.create.sectionCargo': 'Cargo details',
|
||||
'order.pickupOrder.create.sectionOrders': 'Select waybills',
|
||||
'order.pickupOrder.create.orderKeywordPh': 'Service No. / Client No.',
|
||||
'order.pickupOrder.create.selectedOrders': '{count} selected · Warehouse: {warehouse}',
|
||||
'order.pickupOrder.create.warehouseFromOrders': 'Select waybills first (warehouse auto-filled)',
|
||||
'order.pickupOrder.create.needSelectOrders': 'Select at least one waybill',
|
||||
'order.pickupOrder.form.cargoNamePlaceholder': 'Enter cargo name (Chinese)',
|
||||
'order.pickupOrder.form.clientNoPlaceholder': 'Optional; auto-generated if empty',
|
||||
'order.pickupOrder.message.noPickupProduct': 'No domestic pickup sales product available. Configure it under Channel first.',
|
||||
'order.pickupOrder.message.contactRequired': 'Please complete phone and email before creating a pickup',
|
||||
'order.pickupOrder.message.loadOrdersFailed': 'Failed to load bindable waybills',
|
||||
'order.pickupOrder.table.pickupNo': 'System No.',
|
||||
'order.pickupOrder.table.carrierWaybillNo': 'Carrier Waybill No.',
|
||||
'order.pickupOrder.table.customer': 'Customer',
|
||||
@ -532,6 +622,8 @@ export default {
|
||||
'order.pickupOrder.table.createTime': 'Created',
|
||||
'order.pickupOrder.detail.title': 'Pickup Detail',
|
||||
'order.pickupOrder.detail.orders': 'Linked Waybills',
|
||||
'order.pickupOrder.detail.sectionInfo': 'Pickup info',
|
||||
'order.pickupOrder.detail.close': 'Close',
|
||||
'order.pickupOrder.form.freight': 'Pickup Freight',
|
||||
'order.pickupOrder.form.otherFee': 'Other Fee',
|
||||
'order.pickupOrder.form.carrierWaybillNoPh': 'Domestic carrier waybill number',
|
||||
@ -549,6 +641,7 @@ export default {
|
||||
'order.pickupOrder.rebind.remove': 'Remove linked waybills',
|
||||
'order.pickupOrder.rebind.add': 'Add waybills',
|
||||
'order.pickupOrder.rebind.addPh': 'Enter service / client numbers, separated by comma or newline',
|
||||
'order.pickupOrder.rebind.addHint': 'Select unbound pre-declared waybills from the same warehouse',
|
||||
'order.pickupOrder.rebind.emptyBound': 'No linked waybills',
|
||||
'order.pickupOrder.status.draft': 'Draft',
|
||||
'order.pickupOrder.status.scheduled': 'Scheduled',
|
||||
@ -586,11 +679,49 @@ export default {
|
||||
'order.pickupOrder.message.cancelOk': 'Cancelled',
|
||||
'order.pickupOrder.message.cancelConfirm': 'Cancel this pickup sheet? Linked waybills will be unbound.',
|
||||
'order.pickupOrder.message.rebindOk': 'Rebind succeeded',
|
||||
'order.pickupOrder.message.rebindEmpty': 'Select waybills to remove or enter numbers to add',
|
||||
'order.pickupOrder.message.rebindEmpty': 'Select waybills to remove or pick waybills to add',
|
||||
'order.pickupOrder.message.rebindNotFound': 'Waybill not found: {nos}',
|
||||
'order.pickupOrder.message.pickedOk': 'Marked as picked',
|
||||
'order.pickupOrder.message.inboundOk': 'Inbound confirmed',
|
||||
'order.pickupOrder.message.actionFailed': 'Action failed',
|
||||
'order.pickupOrder.search.warehouse': 'Warehouse',
|
||||
'order.pickupOrder.search.expectStart': 'Expect pickup from',
|
||||
'order.pickupOrder.search.expectEnd': 'Expect pickup to',
|
||||
'order.pickupOrder.table.settlement': 'Settlement',
|
||||
'order.pickupOrder.settlementStatus.none': 'None',
|
||||
'order.pickupOrder.settlementStatus.pending': 'Pending',
|
||||
'order.pickupOrder.settlementStatus.settled': 'Settled',
|
||||
'order.pickupOrder.actions.reschedule': 'Reschedule',
|
||||
'order.pickupOrder.actions.batchConfirmFreight': 'Batch Confirm Freight',
|
||||
'order.pickupOrder.actions.batchSchedule': 'Batch Draft Forecast',
|
||||
'order.pickupOrder.actions.copyServiceNos': 'Copy all service Nos.',
|
||||
'order.pickupOrder.create.emptyOrders': 'No bindable pre-declared waybills',
|
||||
'order.pickupOrder.create.goPredeclared': 'Go to pre-declared',
|
||||
'order.pickupOrder.createNext.title': 'Created',
|
||||
'order.pickupOrder.createNext.desc': 'Pickup sheet {no} created. Next steps:',
|
||||
'order.pickupOrder.createNext.goSchedule': 'Draft forecast',
|
||||
'order.pickupOrder.createNext.goPay': 'Pay now',
|
||||
'order.pickupOrder.createNext.viewDetail': 'View detail',
|
||||
'order.pickupOrder.payCheck.amount': 'Amount due: ¥{amount}',
|
||||
'order.pickupOrder.payCheck.balance': 'Balance: ¥{balance}',
|
||||
'order.pickupOrder.payCheck.balanceUnknown': 'Balance: unavailable',
|
||||
'order.pickupOrder.payCheck.shortfall': 'Shortfall: ¥{shortfall}',
|
||||
'order.pickupOrder.payCheck.payAnyway': 'Pay anyway',
|
||||
'order.pickupOrder.detail.timeline': 'Activity',
|
||||
'order.pickupOrder.timeline.createTime': 'Created',
|
||||
'order.pickupOrder.timeline.payTime': 'Paid',
|
||||
'order.pickupOrder.timeline.actualPickupTime': 'Actual pickup',
|
||||
'order.pickupOrder.timeline.updateTime': 'Updated',
|
||||
'order.pickupOrder.message.rescheduleOk': 'Rescheduled',
|
||||
'order.pickupOrder.message.noServiceNos': 'No service numbers to copy',
|
||||
'order.pickupOrder.message.copyServiceNosOk': 'Service numbers copied',
|
||||
'order.pickupOrder.message.copyFailed': 'Copy failed',
|
||||
'order.pickupOrder.message.batchFreightNoCandidates': 'No selected sheets eligible for freight confirm',
|
||||
'order.pickupOrder.message.batchFreightHint': 'Confirm freight for {count} unpaid pickup sheet(s)',
|
||||
'order.pickupOrder.message.batchFreightResult': 'Batch freight: {success} succeeded, {fail} failed',
|
||||
'order.pickupOrder.message.batchScheduleNoCandidates': 'No draft/scheduled sheets selected for forecast',
|
||||
'order.pickupOrder.message.batchScheduleHint': 'Draft-forecast {count} pickup sheet(s)',
|
||||
'order.pickupOrder.message.batchScheduleResult': 'Batch schedule: {success} succeeded, {fail} failed',
|
||||
'order.platformStoreOrder.status.all': 'All',
|
||||
'order.platformStoreOrder.status.pending': 'Pending',
|
||||
'order.platformStoreOrder.status.toShip': 'To Ship',
|
||||
@ -599,19 +730,19 @@ export default {
|
||||
'order.platformStoreOrder.status.canceled': 'Canceled',
|
||||
'order.platformStoreOrder.status.completed': 'Completed',
|
||||
'order.platformStoreOrder.table.platform': 'Platform',
|
||||
'order.platformStoreOrder.table.country': 'Country',
|
||||
'order.platformStoreOrder.table.destCountry': 'Country',
|
||||
'order.platformStoreOrder.table.itemCount': 'Items',
|
||||
'order.platformStoreOrder.table.shippingLabel': 'Label No',
|
||||
'order.platformStoreOrder.table.warehouse': 'Warehouse',
|
||||
'order.platformStoreOrder.table.lastSync': 'Last Sync',
|
||||
'order.platformStoreOrder.table.orderNo': 'Order No',
|
||||
'order.platformStoreOrder.table.customsOrderNo': 'Order No',
|
||||
'order.platformStoreOrder.table.store': 'Store',
|
||||
'order.platformStoreOrder.table.status': 'Status',
|
||||
'order.platformStoreOrder.table.amount': 'Amount',
|
||||
'order.platformStoreOrder.table.orderTime': 'Order Time',
|
||||
'order.platformStoreOrder.table.linkedServiceNo': 'Linked Service No',
|
||||
'order.platformStoreOrder.form.product': 'Sales Product',
|
||||
'order.platformStoreOrder.search.orderNo': 'Order No',
|
||||
'order.platformStoreOrder.search.customsOrderNo': 'Order No',
|
||||
'order.platformStoreOrder.summary.pending': 'Pending',
|
||||
'order.platformStoreOrder.summary.toShip': 'To Ship',
|
||||
'order.platformStoreOrder.drawer.detail': 'Order Detail',
|
||||
@ -628,7 +759,7 @@ export default {
|
||||
'order.platformStoreOrder.recipient.phone': 'Phone',
|
||||
'order.platformStoreOrder.recipient.telePhone': 'Tel',
|
||||
'order.platformStoreOrder.recipient.email': 'Email',
|
||||
'order.platformStoreOrder.recipient.country': 'Country',
|
||||
'order.platformStoreOrder.recipient.destCountry': 'Country',
|
||||
'order.platformStoreOrder.recipient.province': 'State/Province',
|
||||
'order.platformStoreOrder.recipient.city': 'City',
|
||||
'order.platformStoreOrder.recipient.zipCode': 'Zip',
|
||||
@ -849,7 +980,7 @@ export default {
|
||||
'system.mcpTools.selectHint': 'Select a tool on the left',
|
||||
'system.mcpTools.testTitle': 'Test tool',
|
||||
'system.mcpTools.testHint': 'Call the OMS wrapper with JSON args (no LLM).',
|
||||
'system.mcpTools.testArgsPlaceholder': '{ "destinationCountry": "US", "cargoWeight": 0.5 }',
|
||||
'system.mcpTools.testArgsPlaceholder': `{'{ "destinationCountry": "US", "cargoWeight": 0.5 }'}`,
|
||||
'system.mcpTools.runTest': 'Run',
|
||||
'system.mcpTools.testFail': 'Test failed',
|
||||
'system.mcpTools.argsInvalid': 'Arguments must be valid JSON',
|
||||
@ -1099,9 +1230,9 @@ export default {
|
||||
'nanaAssistant.mcp.optional': 'Optional',
|
||||
'nanaAssistant.mcp.missingAsk': 'Please also provide: {fields}. Then I can look it up.',
|
||||
'nanaAssistant.mcp.priceTitle': 'Price check',
|
||||
'nanaAssistant.mcp.priceDesc': 'Estimate freight by country & weight',
|
||||
'nanaAssistant.mcp.priceDesc': 'Estimate freight by destCountry & weight',
|
||||
'nanaAssistant.mcp.priceCoach':
|
||||
'To quote I need:\n1) Destination country (e.g. US)\n2) Actual weight in kg\n\nOptional: postal code, L×W×H (cm), cargo type.\nExample: US 0.5kg zip 10001',
|
||||
'To quote I need:\n1) Destination destCountry (e.g. US)\n2) Actual weight in kg\n\nOptional: postal code, L×W×H (cm), cargo type.\nExample: US 0.5kg zip 10001',
|
||||
'nanaAssistant.mcp.priceExample': 'e.g. US 0.5kg zip 10001',
|
||||
'nanaAssistant.mcp.priceDraft':
|
||||
'Country:\nWeight (kg):\nPostal code (optional):\nDims LWH cm (optional):',
|
||||
@ -1117,14 +1248,14 @@ export default {
|
||||
'Please provide a waybill / tracking / customer order number.\nIf a waybill is already loaded, tap “Track”.',
|
||||
'nanaAssistant.mcp.trackExample': 'e.g. Track waybill NFXXXX',
|
||||
'nanaAssistant.mcp.trackDraft': 'Waybill No.:',
|
||||
'nanaAssistant.mcp.field.country': 'Destination country',
|
||||
'nanaAssistant.mcp.field.destCountry': 'Destination destCountry',
|
||||
'nanaAssistant.mcp.field.weight': 'Weight (kg)',
|
||||
'nanaAssistant.mcp.field.postal': 'Postal code',
|
||||
'nanaAssistant.mcp.field.dims': 'Dimensions (cm)',
|
||||
'nanaAssistant.mcp.field.cargoType': 'Cargo type',
|
||||
'nanaAssistant.mcp.field.product': 'Sales product code/name',
|
||||
'nanaAssistant.mcp.field.cargoName': 'Cargo name',
|
||||
'nanaAssistant.mcp.field.orderNo': 'Waybill No.',
|
||||
'nanaAssistant.mcp.field.customsOrderNo': 'Waybill No.',
|
||||
'nanaAssistant.human': 'Talk to a human advisor →',
|
||||
'nanaAssistant.you': 'You',
|
||||
'nanaAssistant.backToChat': 'Back to chat',
|
||||
@ -1136,13 +1267,13 @@ export default {
|
||||
'nanaAssistant.quickAfterSales': 'Something went wrong?',
|
||||
'nanaAssistant.quickHuman': 'Talk to human advisor',
|
||||
'nanaAssistant.replySmall': 'Yes. IDE focuses on SMB sellers and shipments others decline — special routes, complex cargo, and low-volume orders are in scope. Tell me origin, destination, and cargo type to get started.',
|
||||
'nanaAssistant.replyPrice': 'For a quote, destination country + actual weight (kg) is enough — e.g. US 0.5kg. Postal code/dims help; origin city is not required.',
|
||||
'nanaAssistant.replyQuoteSpeed': 'With country + weight we can return an indicative quote right away; final billing follows inbound weight and product rules.',
|
||||
'nanaAssistant.replyPrice': 'For a quote, destination destCountry + actual weight (kg) is enough — e.g. US 0.5kg. Postal code/dims help; origin city is not required.',
|
||||
'nanaAssistant.replyQuoteSpeed': 'With destCountry + weight we can return an indicative quote right away; final billing follows inbound weight and product rules.',
|
||||
'nanaAssistant.trackHint': 'Just send the waybill number to track, e.g. IDE2607281746002926',
|
||||
'nanaAssistant.replyVisibility': 'You get milestone updates from pickup to delivery — pickup, export, line-haul, customs, and final delivery. We proactively flag delays before they escalate.',
|
||||
'nanaAssistant.replyAfterSales': "Your dedicated advisor handles exceptions, claims, and after-sales follow-up — you won't be left chasing tickets alone. Describe the issue and tracking number if you have one.",
|
||||
'nanaAssistant.replyHuman': 'Reach us via the contact page — phone 18123959245 or email bd01@1st56.com. Mention your route and cargo so we assign the right advisor.',
|
||||
'nanaAssistant.fallback': 'Tell me destination country + weight to quote, or paste a waybill number to track — or talk to a human advisor.',
|
||||
'nanaAssistant.fallback': 'Tell me destination destCountry + weight to quote, or paste a waybill number to track — or talk to a human advisor.',
|
||||
'nanaAssistant.emptyReply': 'No useful reply came back. Please try again with destination + weight, or a waybill number.',
|
||||
'nanaAssistant.aiUnavailable': 'AI advisor is not configured yet. Please contact a human advisor or try later.',
|
||||
'nanaAssistant.providerLoading': 'Connecting to AI advisor… please send again in a moment.',
|
||||
@ -1356,7 +1487,7 @@ export default {
|
||||
'order.waybillManage.table.senderPhone': 'Sender mobile',
|
||||
'order.waybillManage.table.senderTelePhone': 'Sender phone',
|
||||
'order.waybillManage.table.senderEmail': 'Sender email',
|
||||
'order.waybillManage.table.senderCountryCode': 'Sender country code',
|
||||
'order.waybillManage.table.senderCountryCode': 'Sender destCountry code',
|
||||
'order.waybillManage.table.senderProvince': 'Sender state',
|
||||
'order.waybillManage.table.senderCity': 'Sender city',
|
||||
'order.waybillManage.table.senderZipCode': 'Sender zip',
|
||||
@ -1369,7 +1500,7 @@ export default {
|
||||
'order.waybillManage.table.receiverPhone': 'Consignee mobile (raw)',
|
||||
'order.waybillManage.table.receiverTelePhone': 'Consignee phone',
|
||||
'order.waybillManage.table.receiverEmail': 'Consignee email',
|
||||
'order.waybillManage.table.receiverCountryCode': 'Consignee country code',
|
||||
'order.waybillManage.table.receiverCountryCode': 'Consignee destCountry code',
|
||||
'order.waybillManage.table.receiverProvince': 'Consignee state (raw)',
|
||||
'order.waybillManage.table.receiverCity': 'Consignee city (raw)',
|
||||
'order.waybillManage.table.receiverStreetNumber': 'Consignee street no.',
|
||||
@ -1688,7 +1819,7 @@ export default {
|
||||
'order.myOrders.drawer.declaration': 'Declaration',
|
||||
'order.myOrders.drawer.senderName': 'Sender Name',
|
||||
'order.myOrders.drawer.company': 'Company',
|
||||
'order.myOrders.drawer.country': 'Country',
|
||||
'order.myOrders.drawer.destCountry': 'Country',
|
||||
'order.myOrders.drawer.province': 'Province',
|
||||
'order.myOrders.drawer.city': 'City',
|
||||
'order.myOrders.drawer.zipCode': 'Zip Code',
|
||||
@ -1823,9 +1954,9 @@ export default {
|
||||
'order.myOrders.drawer.fee.priceEffectiveTimePlaceholder': 'Select effective time',
|
||||
|
||||
// Order - Price Check
|
||||
'order.priceCheck.form.destinationCountry': 'Destination country',
|
||||
'order.priceCheck.form.destinationCountry': 'Destination destCountry',
|
||||
'order.priceCheck.form.title': 'Quick quote',
|
||||
'order.priceCheck.form.subtitle': 'Pick country and weight to compare; add postal code or dimensions for more accurate quotes',
|
||||
'order.priceCheck.form.subtitle': 'Pick destCountry and weight to compare; add postal code or dimensions for more accurate quotes',
|
||||
'order.priceCheck.form.destinationCountry.placeholder': 'Please select',
|
||||
'order.priceCheck.form.shippingMethod': 'Shipping method',
|
||||
'order.priceCheck.form.shippingMethod.placeholder': 'Please select',
|
||||
@ -1851,7 +1982,7 @@ export default {
|
||||
'order.priceCheck.form.moreFilters': 'More filters',
|
||||
'order.priceCheck.actions.query': 'Get quotes',
|
||||
'order.priceCheck.actions.reset': 'Reset',
|
||||
'order.priceCheck.validate.requiredBasic': 'Please enter destination country and cargo weight',
|
||||
'order.priceCheck.validate.requiredBasic': 'Please enter destination destCountry and cargo weight',
|
||||
'order.priceCheck.validate.partialDimensions': 'Enter length, width and height together, or leave all blank',
|
||||
'order.priceCheck.message.querySuccess': 'Loaded {count} quotes',
|
||||
'order.priceCheck.message.queryFailed': 'Quote search failed',
|
||||
@ -1859,7 +1990,7 @@ export default {
|
||||
'order.priceCheck.sort.price': 'Price',
|
||||
'order.priceCheck.sort.time': 'Transit',
|
||||
'order.priceCheck.resultCount': '{count} results',
|
||||
'order.priceCheck.shortcuts.countryAria': 'Common destination countries',
|
||||
'order.priceCheck.shortcuts.destCountryAria': 'Common destination countries',
|
||||
|
||||
'order.priceCheck.shortcut.US': 'United States',
|
||||
'order.priceCheck.shortcut.CA': 'Canada',
|
||||
@ -1875,10 +2006,10 @@ export default {
|
||||
'order.priceCheck.shortcut.ES': 'Spain',
|
||||
'order.priceCheck.shortcut.NL': 'Netherlands',
|
||||
|
||||
'order.priceCheck.country.US': 'United States',
|
||||
'order.priceCheck.country.UK': 'United Kingdom',
|
||||
'order.priceCheck.country.DE': 'Germany',
|
||||
'order.priceCheck.country.FR': 'France',
|
||||
'order.priceCheck.destCountry.US': 'United States',
|
||||
'order.priceCheck.destCountry.UK': 'United Kingdom',
|
||||
'order.priceCheck.destCountry.DE': 'Germany',
|
||||
'order.priceCheck.destCountry.FR': 'France',
|
||||
|
||||
'order.priceCheck.shippingMethod.air': 'Air',
|
||||
'order.priceCheck.shippingMethod.sea': 'Sea',
|
||||
@ -1918,7 +2049,7 @@ export default {
|
||||
'order.priceCheck.table.viewDetail': 'Details',
|
||||
'order.priceCheck.table.viewGuide': 'Channel guide',
|
||||
'order.priceCheck.table.viewGuideHint': 'Click to view route guide',
|
||||
'order.priceCheck.table.orderNow': 'Place order',
|
||||
'order.priceCheck.table.customsOrderNow': 'Place order',
|
||||
'order.priceCheck.table.askAi': 'Ask AI',
|
||||
'order.priceCheck.table.askAiCanShip': 'Can this ship?',
|
||||
'order.priceCheck.table.askAiCheaper': 'Anything cheaper?',
|
||||
@ -1936,7 +2067,7 @@ export default {
|
||||
'order.priceCheck.hint.bannerLead': 'Some details were not provided. Prices below are for reference only and may change after order or inbound weighing.',
|
||||
'order.priceCheck.hint.missing.dimensions': 'No package dimensions: chargeable weight uses actual weight only; price may change after inbound measurement.',
|
||||
'order.priceCheck.hint.missing.postalCode': 'No postal code: blacklist, remote and zone matching are not exact; quote is for reference only.',
|
||||
'order.priceCheck.hint.missing.province': 'No state/province: zone matched at country level; exact address may change the price.',
|
||||
'order.priceCheck.hint.missing.province': 'No state/province: zone matched at destCountry level; exact address may change the price.',
|
||||
'order.priceCheck.hint.missing.cargoType': 'No cargo attribute selected: quotes shown for general, electronics, sensitive, etc.; final price depends on your selection at order time.',
|
||||
'order.priceCheck.hint.missing.warehouse': 'No warehouse selected: quotes from all warehouses; confirm warehouse when placing order.',
|
||||
|
||||
@ -1948,7 +2079,7 @@ export default {
|
||||
'order.priceCheck.guide.close': 'Close guide',
|
||||
'order.priceCheck.guide.idle': 'Select a quote card to view customer guide and limits',
|
||||
'order.priceCheck.empty.title': 'Start a quote',
|
||||
'order.priceCheck.empty.desc': 'Pick a country and weight to compare. Postal code improves accuracy.',
|
||||
'order.priceCheck.empty.desc': 'Pick a destCountry and weight to compare. Postal code improves accuracy.',
|
||||
'order.priceCheck.empty.step1': 'Choose destination',
|
||||
'order.priceCheck.empty.step2': 'Enter weight (postal recommended)',
|
||||
'order.priceCheck.empty.step3': 'Search, then AI-pick by cargo',
|
||||
@ -2035,7 +2166,7 @@ export default {
|
||||
'order.placeOrder.fromPriceCheck.item.warehouse': 'Warehouse',
|
||||
'order.placeOrder.fromPriceCheck.item.weight': 'Weight',
|
||||
'order.placeOrder.fromPriceCheck.item.dimensions': 'Dimensions',
|
||||
'order.placeOrder.fromPriceCheck.item.country': 'Destination country',
|
||||
'order.placeOrder.fromPriceCheck.item.destCountry': 'Destination destCountry',
|
||||
'order.placeOrder.fromPriceCheck.item.zip': 'Postal code',
|
||||
'order.placeOrder.fromPriceCheck.item.clientNo': 'Customer order No.',
|
||||
'order.placeOrder.fromPriceCheck.item.receiverName': 'Recipient name',
|
||||
@ -2726,11 +2857,14 @@ export default {
|
||||
|
||||
// System - Role Management (Permission Assignment)
|
||||
'system.role.permission.expandAll': 'Expand All',
|
||||
'system.role.permission.adminAllHint':
|
||||
'Admin role has all menu and button permissions by default; no assignment needed',
|
||||
'system.role.permission.collapseAll': 'Collapse All',
|
||||
'system.role.permission.checkAll': 'Check All',
|
||||
'system.role.permission.uncheckAll': 'Uncheck All',
|
||||
'system.role.permission.loading': 'Loading permission data...',
|
||||
'system.role.permission.assignSuccess': 'Permission assigned successfully',
|
||||
'system.role.permission.refreshHint': 'Permissions saved. Please sign in again for them to take effect.',
|
||||
'system.role.permission.search': 'Search permission name or code',
|
||||
'system.role.permission.selected': 'Selected',
|
||||
'system.role.permission.total': 'Total',
|
||||
@ -3085,14 +3219,14 @@ export default {
|
||||
'operation.common.keepScanFocus': 'Keep focus on scan field',
|
||||
'operation.common.voiceBroadcast': 'Voice broadcast',
|
||||
'operation.common.feedback.idle': 'Scan a number — result will appear here',
|
||||
'operation.common.message.orderNotFound': 'Order not found',
|
||||
'operation.common.message.customsOrderNotFound': 'Order not found',
|
||||
'operation.common.message.lookupSuccess': 'Order matched',
|
||||
|
||||
// Operation Module - Inbound Scan
|
||||
'operation.inbound.pageTitle': 'Waybill Information',
|
||||
'operation.inbound.broadcastSettings': 'Broadcast Settings',
|
||||
'operation.inbound.scan.title': 'Scan inbound',
|
||||
'operation.inbound.tip': 'Scan to verify product and country, then enter received weight and dimensions before inbound.',
|
||||
'operation.inbound.tip': 'Scan to verify product and destCountry, then enter received weight and dimensions before inbound.',
|
||||
'operation.inbound.preview.title': 'Verify',
|
||||
'operation.inbound.preview.empty': 'Scan a number to show order info and enter weight / dimensions',
|
||||
'operation.inbound.form.scanNumber': 'Scan Number:',
|
||||
@ -3120,7 +3254,7 @@ export default {
|
||||
'operation.inbound.table.weight': 'Weight',
|
||||
'operation.inbound.table.dimensions': 'Dimensions',
|
||||
'operation.inbound.table.product': 'Product',
|
||||
'operation.inbound.table.country': 'Destination Country',
|
||||
'operation.inbound.table.destCountry': 'Destination Country',
|
||||
'operation.inbound.table.warehouse': 'Warehouse',
|
||||
'operation.inbound.table.operateTime': 'Operate Time',
|
||||
'operation.inbound.table.operator': 'Operator',
|
||||
@ -3142,14 +3276,14 @@ export default {
|
||||
'operation.outbound.pageTitle': 'Waybill Information',
|
||||
'operation.outbound.broadcastSettings': 'Broadcast Settings',
|
||||
'operation.outbound.scan.title': 'Scan outbound',
|
||||
'operation.outbound.tip': 'Scan waybill to verify product, country and channel. Weight and dimensions are optional.',
|
||||
'operation.outbound.tip': 'Scan waybill to verify product, destCountry and channel. Weight and dimensions are optional.',
|
||||
'operation.outbound.preview.title': 'Verify',
|
||||
'operation.outbound.preview.empty': 'Scan a number to show order info; weight / dimensions are optional',
|
||||
'operation.outbound.form.scanNumber': 'Scan Number:',
|
||||
'operation.outbound.form.shipWeight': 'Shipped weight',
|
||||
'operation.outbound.form.dimensions': 'L × W × H',
|
||||
'operation.outbound.form.salesProduct': 'Sales product',
|
||||
'operation.outbound.form.destinationCountry': 'Destination country',
|
||||
'operation.outbound.form.destinationCountry': 'Destination destCountry',
|
||||
'operation.outbound.form.serviceChannel': 'Service channel',
|
||||
'operation.outbound.form.placeholder.scanOrInput': 'Scan or enter number',
|
||||
'operation.outbound.form.placeholder.input': 'Enter',
|
||||
@ -3170,7 +3304,7 @@ export default {
|
||||
'operation.outbound.table.weight': 'Weight',
|
||||
'operation.outbound.table.dimensions': 'Dimensions',
|
||||
'operation.outbound.table.product': 'Product',
|
||||
'operation.outbound.table.country': 'Destination',
|
||||
'operation.outbound.table.destCountry': 'Destination',
|
||||
'operation.outbound.table.serviceChannel': 'Service channel',
|
||||
'operation.outbound.table.warehouse': 'Warehouse',
|
||||
'operation.outbound.table.operateTime': 'Operate Time',
|
||||
@ -3223,7 +3357,7 @@ export default {
|
||||
'operation.fulfillPack.message.batchPrintPartial': 'Print done: {success} ok, {fail} failed',
|
||||
'operation.fulfillPack.message.skuMultiMatch': 'SKU matched {count} orders; queue filtered and selected',
|
||||
'operation.fulfillPack.message.notInQueue': 'Matched order(s) not in the current pick queue (not arrived or not bound)',
|
||||
'operation.fulfillPack.message.orderNotInQueue': 'Order not in pick queue; check bind and arrival status',
|
||||
'operation.fulfillPack.message.customsOrderNotInQueue': 'Order not in pick queue; check bind and arrival status',
|
||||
'operation.fulfillPack.queue.emptyFiltered': 'No pending orders match the current filter',
|
||||
'order.myOrders.table.sku': 'SKU',
|
||||
'operation.printLabel.form.scanNumber': 'Scan number',
|
||||
@ -3323,7 +3457,7 @@ export default {
|
||||
'system.batchEmail.preview': 'Template edit & preview',
|
||||
'system.batchEmail.saveTpl': 'Save template',
|
||||
'system.batchEmail.deleteTpl': 'Delete template',
|
||||
'system.batchEmail.placeholderHint': 'Placeholders: {客户单号}, {最新轨迹}, {换单号}',
|
||||
'system.batchEmail.placeholderHint': `{'Placeholders: {客户单号}, {最新轨迹}, {换单号}'}`,
|
||||
'system.batchEmail.emailConfig': 'Mailbox',
|
||||
'system.batchEmail.account': 'Account',
|
||||
'system.batchEmail.password': 'App password',
|
||||
@ -3340,7 +3474,7 @@ export default {
|
||||
'system.batchEmail.saveConfig': 'Save',
|
||||
'system.batchEmail.testConnection': 'Test connection',
|
||||
'system.batchEmail.subject': 'Subject',
|
||||
'system.batchEmail.subjectPlaceholder': 'Placeholders supported, e.g. Order {客户单号}',
|
||||
'system.batchEmail.subjectPlaceholder': `{'Placeholders supported, e.g. Order {客户单号}'}`,
|
||||
'system.batchEmail.contentPlaceholder': 'Body',
|
||||
'system.batchEmail.saveTplModalTitle': 'Save template',
|
||||
'system.batchEmail.saveTplNamePlaceholder': 'Template name',
|
||||
@ -3741,7 +3875,7 @@ export default {
|
||||
'finance.receivablePayable.table.isPaid': 'Payment status',
|
||||
'finance.receivablePayable.table.productCode': 'Product code',
|
||||
'finance.receivablePayable.table.channelCode': 'Channel code',
|
||||
'finance.receivablePayable.table.consigneeCountryName': 'Destination country',
|
||||
'finance.receivablePayable.table.consigneeCountryName': 'Destination destCountry',
|
||||
'finance.receivablePayable.table.downstreamProvider': 'Downstream provider',
|
||||
'finance.receivablePayable.table.priceNo': 'Price no.',
|
||||
'finance.receivablePayable.table.priceEffectiveTime': 'Price effective time',
|
||||
@ -4321,7 +4455,7 @@ export default {
|
||||
'channel.salesProducts.form.additionalServices': 'Additional services',
|
||||
'channel.salesProducts.billingSimulate.open': 'Billing simulate',
|
||||
'channel.salesProducts.billingSimulate.title': 'Billing simulate',
|
||||
'channel.salesProducts.billingSimulate.country': 'Country',
|
||||
'channel.salesProducts.billingSimulate.destCountry': 'Country',
|
||||
'channel.salesProducts.billingSimulate.province': 'Province/State',
|
||||
'channel.salesProducts.billingSimulate.city': 'City',
|
||||
'channel.salesProducts.billingSimulate.zipCode': 'Zip code',
|
||||
@ -4346,7 +4480,7 @@ export default {
|
||||
'channel.salesProducts.billingSimulate.warnings': 'Warnings',
|
||||
'channel.salesProducts.billingSimulate.emptyResult': 'No simulation result yet',
|
||||
'channel.salesProducts.billingSimulate.needProduct': 'Select a sales product first',
|
||||
'channel.salesProducts.billingSimulate.needCountry': 'Please select a country',
|
||||
'channel.salesProducts.billingSimulate.needCountry': 'Please select a destCountry',
|
||||
'channel.salesProducts.billingSimulate.costItem': 'Cost item',
|
||||
'channel.salesProducts.billingSimulate.amount': 'Amount',
|
||||
'channel.salesProducts.billingSimulate.currency': 'Currency',
|
||||
@ -4550,7 +4684,7 @@ export default {
|
||||
'channel.area.pagination.page': 'page',
|
||||
'channel.area.form.keyword': 'Keyword',
|
||||
'channel.area.form.areaName': 'Area name',
|
||||
'channel.area.form.country': 'Country/Region',
|
||||
'channel.area.form.destCountry': 'Country/Region',
|
||||
'channel.area.form.province': 'State/Province',
|
||||
'channel.area.form.city': 'City',
|
||||
'channel.area.form.leftPostCode': 'Start postcode',
|
||||
@ -4559,7 +4693,7 @@ export default {
|
||||
'channel.area.form.placeholder.areaName': 'Enter area name',
|
||||
'channel.area.form.placeholder.input': 'Please enter',
|
||||
'channel.area.form.rules.areaNameRequired': 'Please enter area name',
|
||||
'channel.area.form.rules.countryRequired': 'Please select country/region',
|
||||
'channel.area.form.rules.destCountryRequired': 'Please select destCountry/region',
|
||||
'channel.area.form.rules.maxLength128': 'Cannot exceed 128 characters',
|
||||
'channel.area.form.rules.maxLength30': 'Cannot exceed 30 characters',
|
||||
'channel.area.form.rules.noChinese': 'Cannot contain Chinese characters',
|
||||
@ -4593,8 +4727,8 @@ export default {
|
||||
'channel.config.detailCount': '{count} detail record(s)',
|
||||
'channel.area.tip.addArea': 'Areas group detail records. Use a clear name such as "US West Coast" or "EU Remote".',
|
||||
'channel.area.tip.editArea': 'Renaming an area does not change existing details. Avoid duplicate names.',
|
||||
'channel.area.tip.addDetail': 'Country/region is required; state/province and city are optional. When using postcodes, start must not exceed end. Country only matches all postcodes in that country.',
|
||||
'channel.area.tip.editDetail': 'Changes apply immediately to channel matching. Verify country and postcode range.',
|
||||
'channel.area.tip.addDetail': 'Country/region is required; state/province and city are optional. When using postcodes, start must not exceed end. Country only matches all postcodes in that destCountry.',
|
||||
'channel.area.tip.editDetail': 'Changes apply immediately to channel matching. Verify destCountry and postcode range.',
|
||||
'channel.area.form.placeholder.provinceOptional': 'Optional; leave blank for any state/province',
|
||||
'channel.area.form.placeholder.cityOptional': 'Optional; leave blank for any city',
|
||||
'channel.area.form.placeholder.leftPostCode': 'Optional; pair with end postcode',
|
||||
@ -4602,8 +4736,8 @@ export default {
|
||||
'channel.area.form.rules.maxLength50': 'Cannot exceed 50 characters',
|
||||
'channel.area.form.rules.postcodeRangeInvalid': 'Start postcode cannot be greater than end postcode',
|
||||
'channel.area.modal.deleteConfirmWithName': 'Delete area "{name}"? All details under it will be removed.',
|
||||
'channel.area.message.countryLoadFailed': 'Failed to load countries. Please refresh and try again.',
|
||||
'channel.area.message.countryQuickTagMiss': 'Country not matched. Please select from the dropdown.',
|
||||
'channel.area.message.destCountryLoadFailed': 'Failed to load countries. Please refresh and try again.',
|
||||
'channel.area.message.destCountryQuickTagMiss': 'Country not matched. Please select from the dropdown.',
|
||||
'channel.area.message.editMissingRow': 'Record not found. Please refresh and try again.',
|
||||
'channel.area.message.deleteFailed': 'Delete failed. Please try again later.',
|
||||
|
||||
@ -4846,7 +4980,7 @@ export default {
|
||||
'channel.issueTemplate.message.saveFailed': 'Failed to save',
|
||||
|
||||
// Track Center - Track Order
|
||||
'track.order.search.placeholder.orderNo': 'Enter order number',
|
||||
'track.order.search.placeholder.customsOrderNo': 'Enter order number',
|
||||
'track.order.search.placeholder.isExternal': 'Please select if external',
|
||||
'track.order.search.label.showTrajectory': 'Show track',
|
||||
'track.order.search.label.hideTrajectory': 'Hide track',
|
||||
@ -4864,7 +4998,7 @@ export default {
|
||||
'track.order.drawer.tabs.small': 'Small package',
|
||||
'track.order.drawer.tabs.main': 'Main order',
|
||||
'track.order.table.orderType': 'Order type',
|
||||
'track.order.table.orderNo': 'Customer order number',
|
||||
'track.order.table.customsOrderNo': 'Customer order number',
|
||||
'track.order.table.customerShortName': 'Customer short name',
|
||||
'track.order.table.thirdPartyOrderNo': 'Last mile order number',
|
||||
'track.order.table.estimatedDeliveryTime': 'Estimated delivery time',
|
||||
@ -4883,7 +5017,7 @@ export default {
|
||||
'track.order.table.product': 'Sales product',
|
||||
'track.order.table.channel': 'Service channel',
|
||||
'track.order.table.apiCode': 'API service provider',
|
||||
'track.order.table.countryCode': 'Country',
|
||||
'track.order.table.destCountryCode': 'Country',
|
||||
'track.order.table.province': 'Province/State',
|
||||
'track.order.table.city': 'City',
|
||||
'track.order.table.postcode': 'Postcode',
|
||||
@ -4901,7 +5035,7 @@ export default {
|
||||
'track.order.detail.title.serviceProvider': 'Service provider',
|
||||
'track.order.detail.title.trackPlaceCity': 'Track location (city)',
|
||||
'track.order.detail.title.trackPlaceProvince': 'Track location (province/state)',
|
||||
'track.order.detail.title.trackPlaceNation': 'Track location (country/region)',
|
||||
'track.order.detail.title.trackPlaceNation': 'Track location (destCountry/region)',
|
||||
'track.order.detail.title.trackPlacePostcode': 'Track location postcode',
|
||||
'track.order.detail.title.trackPlace': 'Track location',
|
||||
'track.order.detail.title.isVisibility': 'Is visible',
|
||||
@ -4934,7 +5068,7 @@ export default {
|
||||
'track.order.modal.viewDetail': 'View detail',
|
||||
'track.order.modal.deleteSingleTrajectory': 'Are you sure to delete track {content}?',
|
||||
'track.order.modal.deleteMultipleTrajectory': 'Are you sure to delete the following {count} tracks?',
|
||||
'track.order.modal.deleteOrder': 'Are you sure to delete order {orderNo}?',
|
||||
'track.order.modal.deleteOrder': 'Are you sure to delete order {customsOrderNo}?',
|
||||
'track.order.modal.deleteMultipleOrders': 'Are you sure to delete the following {count} orders?',
|
||||
'track.order.message.selectSameOrderType': 'Please select the same order type and subsystem',
|
||||
'track.order.message.selectSameOrderTypeOnly': 'Please select the same order type',
|
||||
@ -4965,7 +5099,7 @@ export default {
|
||||
'track.order.common.no': 'No',
|
||||
'track.order.pullConfig.appKey': 'App Key',
|
||||
'track.order.pullConfig.orderType': 'Order Type',
|
||||
'track.order.pullConfig.orderNos': 'Order Numbers',
|
||||
'track.order.pullConfig.customsOrderNos': 'Order Numbers',
|
||||
'track.order.pullConfig.items': 'Config Items',
|
||||
'track.order.pullConfig.noData': 'No data',
|
||||
'track.order.pullConfig.table.index': 'No.',
|
||||
@ -5026,7 +5160,7 @@ export default {
|
||||
'track.warningOrder.pagination.goTo': 'Go to',
|
||||
'track.warningOrder.pagination.page': 'page',
|
||||
'track.warningOrder.table.serialNo': 'No.',
|
||||
'track.warningOrder.table.orderNo': 'Order number',
|
||||
'track.warningOrder.table.customsOrderNo': 'Order number',
|
||||
'track.warningOrder.table.status': 'Status',
|
||||
'track.warningOrder.table.createTime': 'Create time',
|
||||
'track.warningOrder.message.loadFailed': 'Failed to load data',
|
||||
|
||||
@ -120,6 +120,7 @@ export default {
|
||||
'menu.order.fulfillOrders': '代发订单',
|
||||
'menu.order.waybillManage': '运单管理',
|
||||
'menu.order.pickupOrder': '揽收单',
|
||||
'menu.order.customs9610Order': '9610订单',
|
||||
'menu.order.batchJobs': '批量任务',
|
||||
'menu.order.platformStore': '店铺授权',
|
||||
'menu.order.platformStoreOrder': '店铺订单',
|
||||
@ -465,7 +466,7 @@ export default {
|
||||
'user.senderAddress.table.addressName': '别名',
|
||||
'user.senderAddress.table.senderName': '联系人',
|
||||
'user.senderAddress.table.phone': '手机',
|
||||
'user.senderAddress.table.country': '国家',
|
||||
'user.senderAddress.table.destCountry': '国家',
|
||||
'user.senderAddress.table.province': '省/州',
|
||||
'user.senderAddress.table.city': '城市',
|
||||
'user.senderAddress.table.address': '地址',
|
||||
@ -476,7 +477,7 @@ export default {
|
||||
'user.senderAddress.form.senderName': '联系人',
|
||||
'user.senderAddress.form.phone': '手机',
|
||||
'user.senderAddress.form.company': '公司',
|
||||
'user.senderAddress.form.countryCode': '国家二字码',
|
||||
'user.senderAddress.form.destCountryCode': '国家二字码',
|
||||
'user.senderAddress.form.province': '省/州',
|
||||
'user.senderAddress.form.city': '城市',
|
||||
'user.senderAddress.form.zipCode': '邮编',
|
||||
@ -492,6 +493,77 @@ export default {
|
||||
'user.senderAddress.message.typeRequired': '请选择地址类型',
|
||||
'user.senderAddress.message.senderNameRequired': '请填写联系人',
|
||||
'user.senderAddress.message.addressRequired': '请填写详细地址',
|
||||
'order.customs9610.status.pending': '待申报',
|
||||
'order.customs9610.status.declared': '已申报',
|
||||
'order.customs9610.search.keyword': '运单号/客户单号/海关订单号',
|
||||
'order.customs9610.empty.hint': '暂无 9610 申报单,出口报关方式为「9610汇总」的运单创建后会自动出现在此',
|
||||
'order.customs9610.table.serviceNo': '运单号',
|
||||
'order.customs9610.table.clientNo': '客户单号',
|
||||
'order.customs9610.table.destCountry': '目的国',
|
||||
'order.customs9610.table.grossWeight': '毛重',
|
||||
'order.customs9610.table.currency': '币制',
|
||||
'order.customs9610.table.businessType': '业务类型',
|
||||
'order.customs9610.table.status': '状态',
|
||||
'order.customs9610.table.customsOrderNo': '海关订单号',
|
||||
'order.customs9610.table.declareTime': '申报时间',
|
||||
'order.customs9610.table.createTime': '创建时间',
|
||||
'order.customs9610.actions.declare': '申报',
|
||||
'order.customs9610.actions.batchDeclare': '批量申报',
|
||||
'order.customs9610.actions.batchFetchManifest': '批量获取清单数据',
|
||||
'order.customs9610.actions.batchFetchDeclarationForm': '批量获取报关单',
|
||||
'order.customs9610.modal.edit': '编辑 9610 申报',
|
||||
'order.customs9610.modal.detail': '9610 申报详情',
|
||||
'order.customs9610.field.platformCode': '电商平台',
|
||||
'order.customs9610.field.businessType': '业务类型',
|
||||
'order.customs9610.field.preRecordFlag': '汇总预录',
|
||||
'order.customs9610.field.clientNo': '客户单号',
|
||||
'order.customs9610.field.serviceNo': '运单号',
|
||||
'order.customs9610.field.destCountry': '目的国',
|
||||
'order.customs9610.field.currency': '币制',
|
||||
'order.customs9610.field.grossWeight': '毛重(KG)',
|
||||
'order.customs9610.field.freight': '运费',
|
||||
'order.customs9610.field.ecomCorpCode': '电商企业代码',
|
||||
'order.customs9610.field.ecomCorpName': '电商企业名称',
|
||||
'order.customs9610.field.producerCode': '生产销售单位代码',
|
||||
'order.customs9610.field.producerName': '生产销售单位',
|
||||
'order.customs9610.field.region': '区域',
|
||||
'order.customs9610.field.agentDeclare': '是否代理申报',
|
||||
'order.customs9610.field.actualSellerCode': '实际卖家信用代码',
|
||||
'order.customs9610.field.shipmentNo': '出货单号',
|
||||
'order.customs9610.field.customsOrderNo': '海关订单号',
|
||||
'order.customs9610.field.isSummarySimple': '汇总简化',
|
||||
'order.customs9610.items.title': '商品明细',
|
||||
'order.customs9610.items.add': '添加行',
|
||||
'order.customs9610.items.sku': 'SKU',
|
||||
'order.customs9610.items.cnName': '中文品名',
|
||||
'order.customs9610.items.exHsCode': '出口海关编码',
|
||||
'order.customs9610.items.declareElement': '申报要素',
|
||||
'order.customs9610.items.unitPrice': '单价',
|
||||
'order.customs9610.items.quantity': '数量',
|
||||
'order.customs9610.confirm.declareTitle': '确认申报',
|
||||
'order.customs9610.confirm.declareContent': '将调用 API 中心上游接口申报,是否继续?',
|
||||
'order.customs9610.confirm.batchDeclareTitle': '批量申报',
|
||||
'order.customs9610.confirm.batchDeclareContent': '将对选中的 {count} 条待申报单据发起申报,是否继续?',
|
||||
'order.customs9610.confirm.batchManifestTitle': '批量获取清单数据',
|
||||
'order.customs9610.confirm.batchManifestContent': '将对选中的 {count} 条已申报单据获取清单数据,是否继续?',
|
||||
'order.customs9610.confirm.batchDeclarationFormTitle': '批量获取报关单',
|
||||
'order.customs9610.confirm.batchDeclarationFormContent': '将对选中的 {count} 条已申报单据获取报关单,是否继续?',
|
||||
'order.customs9610.message.loadFailed': '加载失败',
|
||||
'order.customs9610.message.saveSuccess': '保存成功',
|
||||
'order.customs9610.message.saveFailed': '保存失败',
|
||||
'order.customs9610.message.declareSuccess': '申报成功',
|
||||
'order.customs9610.message.declareFailed': '申报失败',
|
||||
'order.customs9610.message.batchResult': '成功 {success},失败 {fail}',
|
||||
'order.customs9610.message.fetchManifestFailed': '获取清单数据失败',
|
||||
'order.customs9610.message.fetchDeclarationFormFailed': '获取报关单失败',
|
||||
'order.customs9610.message.selectFirst': '请先勾选单据',
|
||||
'order.customs9610.message.platformCodeRequired': '请填写电商平台',
|
||||
'order.customs9610.message.clientServiceRequired': '请填写客户单号与运单号',
|
||||
'order.customs9610.message.weightRequired': '毛重必须大于 0',
|
||||
'order.customs9610.message.currencyDestCountryRequired': '请填写币制与目的国',
|
||||
'order.customs9610.message.preRecordRequired': 'B 汇总申报必须填写预录标记',
|
||||
'order.customs9610.message.aFieldsRequired': 'A 简化申报必须填写区域与是否代理',
|
||||
'order.customs9610.message.itemsRequired': '请完善 SKU 与中文品名',
|
||||
'order.pickupOrder.search.keyword': '系统单号/揽收单号/联系人',
|
||||
'order.pickupOrder.search.customer': '客户',
|
||||
'order.pickupOrder.search.status': '状态',
|
||||
@ -508,7 +580,23 @@ export default {
|
||||
'order.pickupOrder.actions.markPicked': '标记已取件',
|
||||
'order.pickupOrder.actions.markInbound': '确认到仓',
|
||||
'order.pickupOrder.actions.cancel': '取消订单',
|
||||
'order.pickupOrder.hint.createFromMyOrders': '多票合并请到「我的订单」勾选后点「创建揽收」',
|
||||
'order.pickupOrder.hint.createFromMyOrders': '可绑定已预报运单,或直接新建运单揽收;多票须同仓',
|
||||
'order.pickupOrder.create.lead': '填写取件与货物信息后提交,系统将生成运单并创建揽收单。',
|
||||
'order.pickupOrder.create.leadBind': '勾选已预报且未绑定揽收的运单(须同仓),填写取件信息后提交。',
|
||||
'order.pickupOrder.create.modeBind': '绑定已有运单',
|
||||
'order.pickupOrder.create.modeNew': '新建运单揽收',
|
||||
'order.pickupOrder.create.sectionPickup': '取件信息',
|
||||
'order.pickupOrder.create.sectionCargo': '货物信息',
|
||||
'order.pickupOrder.create.sectionOrders': '选择运单',
|
||||
'order.pickupOrder.create.orderKeywordPh': '运单号 / 客户单号',
|
||||
'order.pickupOrder.create.selectedOrders': '已选 {count} 票 · 仓库:{warehouse}',
|
||||
'order.pickupOrder.create.warehouseFromOrders': '请先勾选运单(自动带出仓库)',
|
||||
'order.pickupOrder.create.needSelectOrders': '请至少勾选一票运单',
|
||||
'order.pickupOrder.form.cargoNamePlaceholder': '请填写货物中文名称',
|
||||
'order.pickupOrder.form.clientNoPlaceholder': '选填,不填则系统自动生成',
|
||||
'order.pickupOrder.message.noPickupProduct': '暂无可用的国内揽收销售产品,请先在渠道维护',
|
||||
'order.pickupOrder.message.contactRequired': '请先完善手机号和邮箱后再创建揽收',
|
||||
'order.pickupOrder.message.loadOrdersFailed': '加载可绑定运单失败',
|
||||
'order.pickupOrder.table.pickupNo': '系统单号',
|
||||
'order.pickupOrder.table.carrierWaybillNo': '揽收单号',
|
||||
'order.pickupOrder.table.customer': '客户',
|
||||
@ -526,12 +614,15 @@ export default {
|
||||
'order.pickupOrder.table.clientNo': '客户单号',
|
||||
'order.pickupOrder.detail.title': '揽收单详情',
|
||||
'order.pickupOrder.detail.orders': '关联运单',
|
||||
'order.pickupOrder.detail.sectionInfo': '揽收信息',
|
||||
'order.pickupOrder.detail.close': '关闭',
|
||||
'order.pickupOrder.form.freight': '揽收运费',
|
||||
'order.pickupOrder.form.otherFee': '其他费用',
|
||||
'order.pickupOrder.form.carrierWaybillNoPh': '国内承运商回执单号',
|
||||
'order.pickupOrder.rebind.remove': '移除已绑运单',
|
||||
'order.pickupOrder.rebind.add': '追加运单',
|
||||
'order.pickupOrder.rebind.addPh': '输入运单号/客户单号,多个用逗号或换行分隔',
|
||||
'order.pickupOrder.rebind.addHint': '勾选同仓、已预报且未绑定揽收的运单进行追加',
|
||||
'order.pickupOrder.rebind.emptyBound': '当前无关联运单',
|
||||
'order.pickupOrder.status.draft': '草稿',
|
||||
'order.pickupOrder.status.scheduled': '已预约',
|
||||
@ -558,7 +649,7 @@ export default {
|
||||
'order.pickupOrder.message.cancelOk': '已取消',
|
||||
'order.pickupOrder.message.cancelConfirm': '确认取消该揽收单?关联运单将解绑。',
|
||||
'order.pickupOrder.message.rebindOk': '改绑成功',
|
||||
'order.pickupOrder.message.rebindEmpty': '请勾选要移除的运单,或填写要追加的运单号',
|
||||
'order.pickupOrder.message.rebindEmpty': '请勾选要移除的运单,或从列表选择要追加的运单',
|
||||
'order.pickupOrder.message.rebindNotFound': '未找到运单:{nos}',
|
||||
'order.pickupOrder.message.pickedOk': '已标记取件',
|
||||
'order.pickupOrder.message.inboundOk': '已确认到仓',
|
||||
@ -592,6 +683,44 @@ export default {
|
||||
'order.pickupOrder.message.createSuccess': '揽收订单创建成功',
|
||||
'order.pickupOrder.message.createSuccessWithNo': '创建成功,运单号:{no}',
|
||||
'order.pickupOrder.message.createFailed': '创建揽收订单失败',
|
||||
'order.pickupOrder.search.warehouse': '仓库',
|
||||
'order.pickupOrder.search.expectStart': '期望揽收开始',
|
||||
'order.pickupOrder.search.expectEnd': '期望揽收结束',
|
||||
'order.pickupOrder.table.settlement': '结算',
|
||||
'order.pickupOrder.settlementStatus.none': '无',
|
||||
'order.pickupOrder.settlementStatus.pending': '待结算',
|
||||
'order.pickupOrder.settlementStatus.settled': '已结算',
|
||||
'order.pickupOrder.actions.reschedule': '改约',
|
||||
'order.pickupOrder.actions.batchConfirmFreight': '批量确认运费',
|
||||
'order.pickupOrder.actions.batchSchedule': '批量草稿预报',
|
||||
'order.pickupOrder.actions.copyServiceNos': '复制全部运单号',
|
||||
'order.pickupOrder.create.emptyOrders': '暂无可绑定的已预报运单',
|
||||
'order.pickupOrder.create.goPredeclared': '去已预报运单',
|
||||
'order.pickupOrder.createNext.title': '创建成功',
|
||||
'order.pickupOrder.createNext.desc': '揽收单 {no} 已创建,可继续以下操作:',
|
||||
'order.pickupOrder.createNext.goSchedule': '去草稿预报',
|
||||
'order.pickupOrder.createNext.goPay': '去支付',
|
||||
'order.pickupOrder.createNext.viewDetail': '查看详情',
|
||||
'order.pickupOrder.payCheck.amount': '应付金额:¥{amount}',
|
||||
'order.pickupOrder.payCheck.balance': '账户余额:¥{balance}',
|
||||
'order.pickupOrder.payCheck.balanceUnknown': '账户余额:暂无法获取',
|
||||
'order.pickupOrder.payCheck.shortfall': '余额不足,差额:¥{shortfall}',
|
||||
'order.pickupOrder.payCheck.payAnyway': '仍要支付',
|
||||
'order.pickupOrder.detail.timeline': '操作痕迹',
|
||||
'order.pickupOrder.timeline.createTime': '创建时间',
|
||||
'order.pickupOrder.timeline.payTime': '支付时间',
|
||||
'order.pickupOrder.timeline.actualPickupTime': '实际取件时间',
|
||||
'order.pickupOrder.timeline.updateTime': '更新时间',
|
||||
'order.pickupOrder.message.rescheduleOk': '改约成功',
|
||||
'order.pickupOrder.message.noServiceNos': '暂无可复制的运单号',
|
||||
'order.pickupOrder.message.copyServiceNosOk': '运单号已复制',
|
||||
'order.pickupOrder.message.copyFailed': '复制失败',
|
||||
'order.pickupOrder.message.batchFreightNoCandidates': '所选揽收单无可确认运费项',
|
||||
'order.pickupOrder.message.batchFreightHint': '将对已选 {count} 张未支付揽收单统一确认运费',
|
||||
'order.pickupOrder.message.batchFreightResult': '批量确认运费:成功 {success},失败 {fail}',
|
||||
'order.pickupOrder.message.batchScheduleNoCandidates': '所选揽收单无草稿/已预约可预报项',
|
||||
'order.pickupOrder.message.batchScheduleHint': '将对已选 {count} 张揽收单统一草稿预报',
|
||||
'order.pickupOrder.message.batchScheduleResult': '批量预报:成功 {success},失败 {fail}',
|
||||
'order.platformStoreOrder.status.all': '全部',
|
||||
'order.platformStoreOrder.status.pending': '待处理',
|
||||
'order.platformStoreOrder.status.toShip': '待发货',
|
||||
@ -600,19 +729,19 @@ export default {
|
||||
'order.platformStoreOrder.status.canceled': '已取消',
|
||||
'order.platformStoreOrder.status.completed': '已完成',
|
||||
'order.platformStoreOrder.table.platform': '平台',
|
||||
'order.platformStoreOrder.table.country': '国家',
|
||||
'order.platformStoreOrder.table.destCountry': '国家',
|
||||
'order.platformStoreOrder.table.itemCount': '商品数',
|
||||
'order.platformStoreOrder.table.shippingLabel': '面单号',
|
||||
'order.platformStoreOrder.table.warehouse': '发货仓库',
|
||||
'order.platformStoreOrder.table.lastSync': '同步时间',
|
||||
'order.platformStoreOrder.table.orderNo': '平台订单号',
|
||||
'order.platformStoreOrder.table.customsOrderNo': '平台订单号',
|
||||
'order.platformStoreOrder.table.store': '店铺',
|
||||
'order.platformStoreOrder.table.status': '状态',
|
||||
'order.platformStoreOrder.table.amount': '金额',
|
||||
'order.platformStoreOrder.table.orderTime': '下单时间',
|
||||
'order.platformStoreOrder.table.linkedServiceNo': '关联服务单号',
|
||||
'order.platformStoreOrder.form.product': '销售产品',
|
||||
'order.platformStoreOrder.search.orderNo': '平台订单号',
|
||||
'order.platformStoreOrder.search.customsOrderNo': '平台订单号',
|
||||
'order.platformStoreOrder.summary.pending': '待处理',
|
||||
'order.platformStoreOrder.summary.toShip': '待发货',
|
||||
'order.platformStoreOrder.drawer.detail': '订单详情',
|
||||
@ -629,7 +758,7 @@ export default {
|
||||
'order.platformStoreOrder.recipient.phone': '手机',
|
||||
'order.platformStoreOrder.recipient.telePhone': '电话',
|
||||
'order.platformStoreOrder.recipient.email': '邮箱',
|
||||
'order.platformStoreOrder.recipient.country': '国家',
|
||||
'order.platformStoreOrder.recipient.destCountry': '国家',
|
||||
'order.platformStoreOrder.recipient.province': '省/州',
|
||||
'order.platformStoreOrder.recipient.city': '城市',
|
||||
'order.platformStoreOrder.recipient.zipCode': '邮编',
|
||||
@ -855,7 +984,7 @@ export default {
|
||||
'system.mcpTools.selectHint': '请选择左侧工具',
|
||||
'system.mcpTools.testTitle': '试跑工具',
|
||||
'system.mcpTools.testHint': '填写 JSON 参数后直接调用 OMS 封装,不经过大模型。',
|
||||
'system.mcpTools.testArgsPlaceholder': '{ "destinationCountry": "US", "cargoWeight": 0.5 }',
|
||||
'system.mcpTools.testArgsPlaceholder': `{'{ "destinationCountry": "US", "cargoWeight": 0.5 }'}`,
|
||||
'system.mcpTools.runTest': '试跑',
|
||||
'system.mcpTools.testFail': '试跑失败',
|
||||
'system.mcpTools.argsInvalid': '参数必须是合法 JSON',
|
||||
@ -934,14 +1063,14 @@ export default {
|
||||
'查轨迹请提供运单号 / 跟踪号 / 客户单号(任选其一)。\n若已从订单带入运单,可直接点「查轨迹」。',
|
||||
'nanaAssistant.mcp.trackExample': '例:请查运单 NFXXXX 的轨迹',
|
||||
'nanaAssistant.mcp.trackDraft': '运单号:',
|
||||
'nanaAssistant.mcp.field.country': '目的国家',
|
||||
'nanaAssistant.mcp.field.destCountry': '目的国家',
|
||||
'nanaAssistant.mcp.field.weight': '实重(kg)',
|
||||
'nanaAssistant.mcp.field.postal': '邮编',
|
||||
'nanaAssistant.mcp.field.dims': '长宽高(cm)',
|
||||
'nanaAssistant.mcp.field.cargoType': '货物属性',
|
||||
'nanaAssistant.mcp.field.product': '销售产品代码/名称',
|
||||
'nanaAssistant.mcp.field.cargoName': '客户品名',
|
||||
'nanaAssistant.mcp.field.orderNo': '运单号',
|
||||
'nanaAssistant.mcp.field.customsOrderNo': '运单号',
|
||||
'nanaAssistant.human': '联系人工顾问 →',
|
||||
'nanaAssistant.you': '你',
|
||||
'nanaAssistant.backToChat': '返回对话',
|
||||
@ -1609,7 +1738,7 @@ export default {
|
||||
'order.myOrders.drawer.declaration': '申报信息',
|
||||
'order.myOrders.drawer.senderName': '发件人姓名',
|
||||
'order.myOrders.drawer.company': '公司',
|
||||
'order.myOrders.drawer.country': '国家',
|
||||
'order.myOrders.drawer.destCountry': '国家',
|
||||
'order.myOrders.drawer.province': '州省',
|
||||
'order.myOrders.drawer.city': '城市',
|
||||
'order.myOrders.drawer.zipCode': '邮编',
|
||||
@ -1780,7 +1909,7 @@ export default {
|
||||
'order.priceCheck.sort.price': '价格',
|
||||
'order.priceCheck.sort.time': '时效',
|
||||
'order.priceCheck.resultCount': '共 {count} 条',
|
||||
'order.priceCheck.shortcuts.countryAria': '常用目的国家',
|
||||
'order.priceCheck.shortcuts.destCountryAria': '常用目的国家',
|
||||
|
||||
'order.priceCheck.shortcut.US': '美国',
|
||||
'order.priceCheck.shortcut.CA': '加拿大',
|
||||
@ -1796,10 +1925,10 @@ export default {
|
||||
'order.priceCheck.shortcut.ES': '西班牙',
|
||||
'order.priceCheck.shortcut.NL': '荷兰',
|
||||
|
||||
'order.priceCheck.country.US': '美国',
|
||||
'order.priceCheck.country.UK': '英国',
|
||||
'order.priceCheck.country.DE': '德国',
|
||||
'order.priceCheck.country.FR': '法国',
|
||||
'order.priceCheck.destCountry.US': '美国',
|
||||
'order.priceCheck.destCountry.UK': '英国',
|
||||
'order.priceCheck.destCountry.DE': '德国',
|
||||
'order.priceCheck.destCountry.FR': '法国',
|
||||
|
||||
'order.priceCheck.shippingMethod.air': '空运',
|
||||
'order.priceCheck.shippingMethod.sea': '海运',
|
||||
@ -1839,7 +1968,7 @@ export default {
|
||||
'order.priceCheck.table.viewDetail': '详情',
|
||||
'order.priceCheck.table.viewGuide': '渠道说明',
|
||||
'order.priceCheck.table.viewGuideHint': '点击查看线路说明',
|
||||
'order.priceCheck.table.orderNow': '立刻下单',
|
||||
'order.priceCheck.table.customsOrderNow': '立刻下单',
|
||||
'order.priceCheck.table.askAi': '问 AI',
|
||||
'order.priceCheck.table.askAiCanShip': '这个能不能发?',
|
||||
'order.priceCheck.table.askAiCheaper': '有没有更便宜?',
|
||||
@ -1956,7 +2085,7 @@ export default {
|
||||
'order.placeOrder.fromPriceCheck.item.warehouse': '仓库',
|
||||
'order.placeOrder.fromPriceCheck.item.weight': '重量',
|
||||
'order.placeOrder.fromPriceCheck.item.dimensions': '尺寸',
|
||||
'order.placeOrder.fromPriceCheck.item.country': '目的国家',
|
||||
'order.placeOrder.fromPriceCheck.item.destCountry': '目的国家',
|
||||
'order.placeOrder.fromPriceCheck.item.zip': '邮编',
|
||||
'order.placeOrder.fromPriceCheck.item.clientNo': '客户单号',
|
||||
'order.placeOrder.fromPriceCheck.item.receiverName': '收件人名称',
|
||||
@ -2651,12 +2780,14 @@ export default {
|
||||
'system.menu.iconPicker.empty': '未找到匹配图标',
|
||||
|
||||
// 系统 - 角色管理(权限分配相关)
|
||||
'system.role.permission.adminAllHint': 'Admin 角色默认拥有全部菜单与按钮权限,无需分配',
|
||||
'system.role.permission.expandAll': '全部展开',
|
||||
'system.role.permission.collapseAll': '全部收起',
|
||||
'system.role.permission.checkAll': '全选',
|
||||
'system.role.permission.uncheckAll': '取消全选',
|
||||
'system.role.permission.loading': '加载权限数据中...',
|
||||
'system.role.permission.assignSuccess': '权限分配成功',
|
||||
'system.role.permission.refreshHint': '权限已保存,请重新登录后生效',
|
||||
'system.role.permission.search': '搜索权限名称或编码',
|
||||
'system.role.permission.selected': '已选',
|
||||
'system.role.permission.total': '总计',
|
||||
@ -3143,7 +3274,7 @@ export default {
|
||||
'operation.common.keepScanFocus': '焦点维持',
|
||||
'operation.common.voiceBroadcast': '语音播报',
|
||||
'operation.common.feedback.idle': '请扫描单号,结果将在此显示',
|
||||
'operation.common.message.orderNotFound': '未找到对应订单',
|
||||
'operation.common.message.customsOrderNotFound': '未找到对应订单',
|
||||
'operation.common.message.lookupSuccess': '订单匹配成功',
|
||||
|
||||
// 操作模块 - 入库扫描
|
||||
@ -3178,7 +3309,7 @@ export default {
|
||||
'operation.inbound.table.weight': '重量',
|
||||
'operation.inbound.table.dimensions': '尺寸',
|
||||
'operation.inbound.table.product': '产品',
|
||||
'operation.inbound.table.country': '目的国家',
|
||||
'operation.inbound.table.destCountry': '目的国家',
|
||||
'operation.inbound.table.warehouse': '仓库',
|
||||
'operation.inbound.table.operateTime': '操作时间',
|
||||
'operation.inbound.table.operator': '操作人',
|
||||
@ -3228,7 +3359,7 @@ export default {
|
||||
'operation.outbound.table.weight': '重量',
|
||||
'operation.outbound.table.dimensions': '尺寸',
|
||||
'operation.outbound.table.product': '产品',
|
||||
'operation.outbound.table.country': '目的国家',
|
||||
'operation.outbound.table.destCountry': '目的国家',
|
||||
'operation.outbound.table.serviceChannel': '服务渠道',
|
||||
'operation.outbound.table.warehouse': '仓库',
|
||||
'operation.outbound.table.operateTime': '操作时间',
|
||||
@ -3280,7 +3411,7 @@ export default {
|
||||
'operation.fulfillPack.message.batchPrintPartial': '打印完成:成功 {success} 单,失败 {fail} 单',
|
||||
'operation.fulfillPack.message.skuMultiMatch': 'SKU 命中 {count} 单,已筛选队列并勾选',
|
||||
'operation.fulfillPack.message.notInQueue': '命中订单不在当前待拣队列(可能未到仓或未绑定)',
|
||||
'operation.fulfillPack.message.orderNotInQueue': '订单不在当前待拣队列,请核对绑定与到仓状态',
|
||||
'operation.fulfillPack.message.customsOrderNotInQueue': '订单不在当前待拣队列,请核对绑定与到仓状态',
|
||||
'operation.fulfillPack.queue.emptyFiltered': '当前筛选无待拣订单',
|
||||
'operation.printLabel.form.scanNumber': '扫描号码',
|
||||
'operation.printLabel.form.placeholder.scanOrInput': '运单号 / 客户单号 / 跟踪号 / 包裹单号 / SKU',
|
||||
@ -3379,7 +3510,7 @@ export default {
|
||||
'system.batchEmail.preview': '模板编辑与预览',
|
||||
'system.batchEmail.saveTpl': '保存模板',
|
||||
'system.batchEmail.deleteTpl': '删除模板',
|
||||
'system.batchEmail.placeholderHint': '提示:使用{客户单号}、{最新轨迹}、{换单号}作为占位符',
|
||||
'system.batchEmail.placeholderHint': `{'提示:使用{客户单号}、{最新轨迹}、{换单号}作为占位符'}`,
|
||||
'system.batchEmail.emailConfig': '邮箱配置',
|
||||
'system.batchEmail.account': '账号',
|
||||
'system.batchEmail.password': '安全密码',
|
||||
@ -3396,7 +3527,7 @@ export default {
|
||||
'system.batchEmail.saveConfig': '保存配置',
|
||||
'system.batchEmail.testConnection': '测试连接',
|
||||
'system.batchEmail.subject': '邮件主题',
|
||||
'system.batchEmail.subjectPlaceholder': '支持占位符,如:订单通知 - {客户单号}',
|
||||
'system.batchEmail.subjectPlaceholder': `{'支持占位符,如:订单通知 - {客户单号}'}`,
|
||||
'system.batchEmail.contentPlaceholder': '邮件正文',
|
||||
'system.batchEmail.saveTplModalTitle': '保存模板',
|
||||
'system.batchEmail.saveTplNamePlaceholder': '请输入模板名称',
|
||||
@ -4377,7 +4508,7 @@ export default {
|
||||
'channel.salesProducts.form.additionalServices': '附加服务',
|
||||
'channel.salesProducts.billingSimulate.open': '计费模拟',
|
||||
'channel.salesProducts.billingSimulate.title': '计费模拟',
|
||||
'channel.salesProducts.billingSimulate.country': '国家',
|
||||
'channel.salesProducts.billingSimulate.destCountry': '国家',
|
||||
'channel.salesProducts.billingSimulate.province': '省/州',
|
||||
'channel.salesProducts.billingSimulate.city': '城市',
|
||||
'channel.salesProducts.billingSimulate.zipCode': '邮编',
|
||||
@ -4609,7 +4740,7 @@ export default {
|
||||
'channel.area.pagination.page': '页',
|
||||
'channel.area.form.keyword': '关键字',
|
||||
'channel.area.form.areaName': '分区名称',
|
||||
'channel.area.form.country': '国家/地区',
|
||||
'channel.area.form.destCountry': '国家/地区',
|
||||
'channel.area.form.province': '州/省',
|
||||
'channel.area.form.city': '城市',
|
||||
'channel.area.form.leftPostCode': '起始邮编',
|
||||
@ -4618,7 +4749,7 @@ export default {
|
||||
'channel.area.form.placeholder.areaName': '请输入分区名称',
|
||||
'channel.area.form.placeholder.input': '请输入',
|
||||
'channel.area.form.rules.areaNameRequired': '请输入分区名称',
|
||||
'channel.area.form.rules.countryRequired': '请选择国家/地区',
|
||||
'channel.area.form.rules.destCountryRequired': '请选择国家/地区',
|
||||
'channel.area.form.rules.maxLength128': '限制不可超过128个字符',
|
||||
'channel.area.form.rules.maxLength30': '限制不可超过30个字符',
|
||||
'channel.area.form.rules.noChinese': '不能存在中文字符',
|
||||
@ -4676,8 +4807,8 @@ export default {
|
||||
'channel.area.form.rules.maxLength50': '限制不可超过50个字符',
|
||||
'channel.area.form.rules.postcodeRangeInvalid': '起始邮编不能大于截止邮编',
|
||||
'channel.area.modal.deleteConfirmWithName': '确定删除分区「{name}」吗?其下明细将一并删除。',
|
||||
'channel.area.message.countryLoadFailed': '国家列表加载失败,请刷新后重试',
|
||||
'channel.area.message.countryQuickTagMiss': '未匹配到该国家,请从下拉选择',
|
||||
'channel.area.message.destCountryLoadFailed': '国家列表加载失败,请刷新后重试',
|
||||
'channel.area.message.destCountryQuickTagMiss': '未匹配到该国家,请从下拉选择',
|
||||
'channel.area.message.editMissingRow': '未找到要编辑的数据,请刷新后重试',
|
||||
'channel.area.message.deleteFailed': '删除失败,请稍后重试',
|
||||
|
||||
@ -4919,7 +5050,7 @@ export default {
|
||||
'channel.issueTemplate.message.saveFailed': '保存失败',
|
||||
|
||||
// 轨迹中心 - 轨迹订单
|
||||
'track.order.search.placeholder.orderNo': '请输入单号',
|
||||
'track.order.search.placeholder.customsOrderNo': '请输入单号',
|
||||
'track.order.search.placeholder.isExternal': '请选择是否对外',
|
||||
'track.order.search.label.showTrajectory': '显示轨迹',
|
||||
'track.order.search.label.hideTrajectory': '隐藏轨迹',
|
||||
@ -4938,7 +5069,7 @@ export default {
|
||||
'track.order.drawer.tabs.package': '大包',
|
||||
'track.order.drawer.tabs.main': '主单',
|
||||
'track.order.table.orderType': '订单类型',
|
||||
'track.order.table.orderNo': '客户单号',
|
||||
'track.order.table.customsOrderNo': '客户单号',
|
||||
'track.order.table.customerShortName': '客户简称',
|
||||
'track.order.table.thirdPartyOrderNo': '尾程单号',
|
||||
'track.order.table.estimatedDeliveryTime': '预计签收时间',
|
||||
@ -4957,7 +5088,7 @@ export default {
|
||||
'track.order.table.product': '销售产品',
|
||||
'track.order.table.channel': '服务渠道',
|
||||
'track.order.table.apiCode': 'API服务商',
|
||||
'track.order.table.countryCode': '国家',
|
||||
'track.order.table.destCountryCode': '国家',
|
||||
'track.order.table.province': '州省',
|
||||
'track.order.table.city': '城市',
|
||||
'track.order.table.postcode': '邮编',
|
||||
@ -5003,7 +5134,7 @@ export default {
|
||||
'track.order.modal.viewDetail': '查看明细',
|
||||
'track.order.modal.deleteSingleTrajectory': '是否要删除轨迹 {content}?',
|
||||
'track.order.modal.deleteMultipleTrajectory': '是否要删除以下{count}条轨迹?',
|
||||
'track.order.modal.deleteOrder': '是否要删除订单 {orderNo}?',
|
||||
'track.order.modal.deleteOrder': '是否要删除订单 {customsOrderNo}?',
|
||||
'track.order.modal.deleteMultipleOrders': '是否要删除以下 {count} 个订单?',
|
||||
'track.order.message.selectSameOrderType': '请选择同一订单类型和子系统',
|
||||
'track.order.message.selectSameOrderTypeOnly': '请选择同一订单类型',
|
||||
@ -5034,7 +5165,7 @@ export default {
|
||||
'track.order.common.no': '否',
|
||||
'track.order.pullConfig.appKey': '应用Key',
|
||||
'track.order.pullConfig.orderType': '订单类型',
|
||||
'track.order.pullConfig.orderNos': '订单号',
|
||||
'track.order.pullConfig.customsOrderNos': '订单号',
|
||||
'track.order.pullConfig.items': '配置明细',
|
||||
'track.order.pullConfig.noData': '暂无数据',
|
||||
'track.order.pullConfig.table.index': '序号',
|
||||
@ -5095,7 +5226,7 @@ export default {
|
||||
'track.warningOrder.pagination.goTo': '前往',
|
||||
'track.warningOrder.pagination.page': '页',
|
||||
'track.warningOrder.table.serialNo': '序号',
|
||||
'track.warningOrder.table.orderNo': '订单号',
|
||||
'track.warningOrder.table.customsOrderNo': '订单号',
|
||||
'track.warningOrder.table.status': '状态',
|
||||
'track.warningOrder.table.createTime': '创建时间',
|
||||
'track.warningOrder.message.loadFailed': '加载数据失败',
|
||||
|
||||
@ -88,6 +88,17 @@ const ORDER: AppRouteRecordRaw = {
|
||||
permission: 'Order.Pickup',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'customs9610Order',
|
||||
name: 'OrderCustoms9610',
|
||||
component: () => import('@/views/order/customs9610Order/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.order.customs9610Order',
|
||||
requiresAuth: true,
|
||||
order: 5,
|
||||
permission: 'Order.Customs9610',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'templateMapping',
|
||||
name: 'OrderTemplateMapping',
|
||||
|
||||
@ -104,8 +104,8 @@ const SYSTEM_MANAGEMENT: AppRouteRecordRaw = {
|
||||
meta: {
|
||||
locale: 'menu.system.batchEmail',
|
||||
requiresAuth: true,
|
||||
/** RBAC 菜单/接口权限编码(与后端 Permission 配置一致即可) */
|
||||
permission: 'System.BatchEmail.Use',
|
||||
/** 与菜单/权限树编码一致(历史码 Rbac.email) */
|
||||
permission: 'Rbac.email',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -472,6 +472,8 @@ export const useRbacUserStore = defineStore('rbacUser', () => {
|
||||
// 检查权限(与 @/utils/rbac-permission 规则一致:资源码 ↔ Query 互通)
|
||||
function hasPermission(permission: string): boolean {
|
||||
if (!permission?.trim()) return true
|
||||
// Admin 角色默认全权限
|
||||
if (hasRole('Admin')) return true
|
||||
const code = permission.trim()
|
||||
const granted = permissions.value
|
||||
if (granted.includes(code)) return true
|
||||
|
||||
@ -46,7 +46,15 @@
|
||||
|
||||
.wh-action-btn--approve:hover,
|
||||
.wh-action-btn--receive:hover,
|
||||
.wh-action-btn--read:hover {
|
||||
.wh-action-btn--read:hover,
|
||||
.wh-action-btn--pay:hover {
|
||||
color: rgb(var(--green-6));
|
||||
background: rgb(var(--green-1));
|
||||
}
|
||||
|
||||
.wh-action-btn--publish:hover,
|
||||
.wh-action-btn--rebind:hover,
|
||||
.wh-action-btn--ship:hover {
|
||||
color: var(--app-primary-color);
|
||||
background: var(--app-primary-color-light, #e6f7f7);
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ export const MENU_NAME_TO_LOCALE_KEY: Record<string, string> = {
|
||||
我的订单: 'menu.order.myOrders',
|
||||
运单管理: 'menu.order.waybillManage',
|
||||
揽收订单: 'menu.order.pickupOrder',
|
||||
'9610订单': 'menu.order.customs9610Order',
|
||||
批量任务: 'menu.order.batchJobs',
|
||||
店铺授权: 'menu.order.platformStore',
|
||||
店铺订单: 'menu.order.platformStoreOrder',
|
||||
|
||||
@ -29,6 +29,7 @@ export const SUB_MENU_DEFAULT_ICONS: Record<string, string> = {
|
||||
'menu.order.placeOrder': 'IconPlus',
|
||||
'menu.order.myOrders': 'IconOrderedList',
|
||||
'menu.order.waybillManage': 'IconStorage',
|
||||
'menu.order.customs9610Order': 'IconFile',
|
||||
'menu.order.batchJobs': 'IconSchedule',
|
||||
'menu.channel.channelGuide': 'IconBook',
|
||||
'menu.channel.salesProducts': 'IconTag',
|
||||
|
||||
@ -1,5 +1,62 @@
|
||||
import { useRbacUserStore } from '@/store/modules/rbac-user'
|
||||
import { normalizeResourcePermissionCode } from '@/constants/pageButtonCatalog'
|
||||
import {
|
||||
normalizeResourcePermissionCode,
|
||||
PAGE_BUTTON_PARENT_ALIASES,
|
||||
} from '@/constants/pageButtonCatalog'
|
||||
|
||||
/** 页面级动作后缀:有资源码或动作码之一即可进页 */
|
||||
const PAGE_ACTION_SUFFIX = /\.(Query|Use|View)$/i
|
||||
|
||||
/**
|
||||
* 展开权限码候选(含资源级、动作级、历史别名),用于路由/按钮校验互认。
|
||||
*
|
||||
* 例:
|
||||
* - `System.BatchEmail.Use` ↔ `System.BatchEmail` ↔ `Rbac.email`
|
||||
* - `Rbac.Roles.Query` ↔ `Rbac.Roles`
|
||||
*/
|
||||
function expandPermissionCandidates(permission: string): string[] {
|
||||
const code = permission.trim()
|
||||
if (!code) return []
|
||||
|
||||
const set = new Set<string>([code])
|
||||
|
||||
const addAliasFamily = (raw: string) => {
|
||||
if (!raw) return
|
||||
set.add(raw)
|
||||
const mapped = PAGE_BUTTON_PARENT_ALIASES[raw]
|
||||
if (mapped) set.add(mapped)
|
||||
|
||||
if (PAGE_ACTION_SUFFIX.test(raw)) {
|
||||
const resource = raw.replace(PAGE_ACTION_SUFFIX, '')
|
||||
set.add(resource)
|
||||
const mappedResource = PAGE_BUTTON_PARENT_ALIASES[resource]
|
||||
if (mappedResource) set.add(mappedResource)
|
||||
} else {
|
||||
set.add(`${raw}.Query`)
|
||||
set.add(`${raw}.Use`)
|
||||
set.add(`${raw}.View`)
|
||||
}
|
||||
}
|
||||
|
||||
addAliasFamily(code)
|
||||
|
||||
// 反向:别名表里指向同一资源的历史菜单码
|
||||
const resources = Array.from(set)
|
||||
for (const [from, to] of Object.entries(PAGE_BUTTON_PARENT_ALIASES)) {
|
||||
if (resources.includes(to) || resources.includes(from)) {
|
||||
addAliasFamily(from)
|
||||
addAliasFamily(to)
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeResourcePermissionCode 仅剥 .Query,这里再补一层
|
||||
for (const item of Array.from(set)) {
|
||||
const normalized = normalizeResourcePermissionCode(item)
|
||||
if (normalized) set.add(normalized)
|
||||
}
|
||||
|
||||
return Array.from(set)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有权限(三级按钮 / 路由 meta.permission)
|
||||
@ -7,23 +64,16 @@ import { normalizeResourcePermissionCode } from '@/constants/pageButtonCatalog'
|
||||
* 兼容菜单同步后的编码差异:
|
||||
* - 二级菜单可能是资源码 `Operation.PrintLabel`
|
||||
* - 路由 meta 可能是 `Operation.PrintLabel.Query`
|
||||
* 二者互相视为可访问页面。
|
||||
* - 历史菜单码如 `Rbac.email` 与目录码 `System.BatchEmail.Use`
|
||||
* 以上互相视为可访问。
|
||||
*/
|
||||
export function hasPermission(permission: string): boolean {
|
||||
const userStore = useRbacUserStore()
|
||||
if (!permission?.trim()) return true
|
||||
const code = permission.trim()
|
||||
// Admin 角色默认全权限,无需在角色管理中逐项分配
|
||||
if (hasRole('Admin')) return true
|
||||
const granted = userStore.permissions
|
||||
if (granted.includes(code)) return true
|
||||
|
||||
if (/\.Query$/i.test(code)) {
|
||||
const resource = normalizeResourcePermissionCode(code)
|
||||
if (resource && granted.includes(resource)) return true
|
||||
} else if (granted.includes(`${code}.Query`)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return expandPermissionCandidates(permission).some((code) => granted.includes(code))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -221,7 +221,10 @@ export function buildPermissionCascadeContext(
|
||||
}
|
||||
}
|
||||
|
||||
/** 勾选子级时联动勾选上级;取消一级时联动取消其下全部权限 */
|
||||
/**
|
||||
* 勾选:选中自身 + 祖先 + 子孙(一级/二级勾选即带齐其下按钮权限)
|
||||
* 取消:一级/二级取消时清空其下全部;按钮仅取消自身
|
||||
*/
|
||||
export function togglePermissionWithCascade(
|
||||
selected: readonly string[],
|
||||
context: PermissionCascadeContext,
|
||||
@ -237,6 +240,12 @@ export function togglePermissionWithCascade(
|
||||
for (const ancestor of context.ancestors.get(code) ?? []) {
|
||||
next.add(ancestor)
|
||||
}
|
||||
for (const descendant of context.descendantsByLevel1.get(code) ?? []) {
|
||||
next.add(descendant)
|
||||
}
|
||||
for (const descendant of context.descendantsByLevel2.get(code) ?? []) {
|
||||
next.add(descendant)
|
||||
}
|
||||
return Array.from(next)
|
||||
}
|
||||
|
||||
|
||||
@ -112,7 +112,8 @@ function isActive(path: string) {
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 14px calc(6px + env(safe-area-inset-bottom, 0px));
|
||||
/* 下沉贴近底部圆角 / Home Indicator,略侵入 safe-area */
|
||||
padding: 0 14px var(--h5-dock-bottom-inset, max(4px, calc(env(safe-area-inset-bottom, 0px) - 16px)));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
@ -180,9 +180,6 @@
|
||||
>
|
||||
<!-- 顶栏:渠道 + 单号(左上) -->
|
||||
<div class="h5-order-top">
|
||||
<span v-if="isFulfillOrder(row)" class="h5-order-fulfill-badge">
|
||||
{{ t('order.fulfillOrders.badge') }}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="h5-order-no"
|
||||
@ -2158,16 +2155,6 @@ void bootstrap();
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.h5-order-fulfill-badge {
|
||||
flex-shrink: 0;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--app-primary-color, #054f74);
|
||||
background: rgba(29, 114, 129, 0.1);
|
||||
}
|
||||
|
||||
.h5-order-no {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
|
||||
@ -23,7 +23,9 @@
|
||||
--h5-primary-soft: color-mix(in srgb, var(--app-primary-color) 12%, #fff);
|
||||
--h5-primary-softer: color-mix(in srgb, var(--app-primary-color) 8%, #fff);
|
||||
--h5-safe-bottom: env(safe-area-inset-bottom, 0px);
|
||||
--h5-dock-offset: calc(62px + var(--h5-safe-bottom));
|
||||
/* 底部 dock 下沉贴近圆角;有 Home Indicator 时略侵入 safe-area */
|
||||
--h5-dock-bottom-inset: max(4px, calc(var(--h5-safe-bottom) - 16px));
|
||||
--h5-dock-offset: calc(56px + var(--h5-dock-bottom-inset));
|
||||
|
||||
background: var(--h5-bg);
|
||||
color: var(--h5-text);
|
||||
|
||||
1036
src/views/order/customs9610Order/index.vue
Normal file
1036
src/views/order/customs9610Order/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
315
src/views/order/pickupOrder/components/BindableOrderPicker.vue
Normal file
315
src/views/order/pickupOrder/components/BindableOrderPicker.vue
Normal file
@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div class="bindable-order-picker">
|
||||
<div class="bindable-order-picker__toolbar">
|
||||
<a-input
|
||||
v-model="keyword"
|
||||
allow-clear
|
||||
:placeholder="t('order.pickupOrder.create.orderKeywordPh')"
|
||||
style="width: 220px"
|
||||
@press-enter="reload"
|
||||
/>
|
||||
<a-button class="app-btn-search" :loading="loading" @click="reload">
|
||||
{{ t('order.pickupOrder.actions.search') }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
row-key="id"
|
||||
size="small"
|
||||
:loading="loading"
|
||||
:data="rows"
|
||||
:pagination="false"
|
||||
:scroll="{ y: scrollY }"
|
||||
:row-selection="rowSelection"
|
||||
:columns="columns"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<template #empty>
|
||||
<div class="bindable-order-picker__empty">
|
||||
<p>{{ t('order.pickupOrder.create.emptyOrders') }}</p>
|
||||
<a-button type="outline" size="small" @click="goPredeclared">
|
||||
{{ t('order.pickupOrder.create.goPredeclared') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="bindable-order-picker__footer">
|
||||
{{
|
||||
t('order.pickupOrder.create.selectedOrders', {
|
||||
count: selectedRows.length,
|
||||
warehouse: selectedWarehouseLabel || warehouseHint || '-',
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { TableColumnData, TableRowSelection } from '@arco-design/web-vue'
|
||||
import { postOrderList } from '@/api/order'
|
||||
import { $baseMessage } from '@/utils'
|
||||
import { normalizeListRow, pickField } from '@/utils/warehouseListMapper'
|
||||
import { getOrderStatusMeta, resolveOrderStatusCode } from '@/constants/orderStatus'
|
||||
import { useRbacUserStore } from '@/store/modules/rbac-user'
|
||||
|
||||
export type BindableOrderRow = {
|
||||
id: number
|
||||
serviceNo: string
|
||||
clientNo: string
|
||||
warehouseId: number
|
||||
warehouseName: string
|
||||
customerId?: number
|
||||
status: string
|
||||
statusName: string
|
||||
}
|
||||
|
||||
const BINDABLE_STATUSES = new Set([
|
||||
'draft',
|
||||
'predeclared',
|
||||
'pendingAllocation',
|
||||
'pendingPayment',
|
||||
'pendingShip',
|
||||
'pendingOnline',
|
||||
])
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
customerId?: number
|
||||
/** 限定同仓(改绑时传入揽收单仓库) */
|
||||
warehouseId?: number
|
||||
/** 排除已在揽收单中的订单 */
|
||||
excludeOrderIds?: number[]
|
||||
scrollY?: number
|
||||
/** 打开时是否自动加载 */
|
||||
autoLoad?: boolean
|
||||
}>(),
|
||||
{
|
||||
scrollY: 240,
|
||||
autoLoad: true,
|
||||
excludeOrderIds: () => [],
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [rows: BindableOrderRow[]]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const rbacUserStore = useRbacUserStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const keyword = ref('')
|
||||
const rows = ref<BindableOrderRow[]>([])
|
||||
const selectedKeys = ref<Array<number | string>>([])
|
||||
const selectedRows = ref<BindableOrderRow[]>([])
|
||||
|
||||
const columns = computed<TableColumnData[]>(() => [
|
||||
{
|
||||
title: t('order.pickupOrder.table.serviceNo'),
|
||||
dataIndex: 'serviceNo',
|
||||
width: 160,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: t('order.pickupOrder.table.clientNo'),
|
||||
dataIndex: 'clientNo',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: t('order.pickupOrder.table.warehouse'),
|
||||
dataIndex: 'warehouseName',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{ title: t('order.pickupOrder.table.status'), dataIndex: 'statusName', width: 100 },
|
||||
])
|
||||
|
||||
const rowSelection = computed<TableRowSelection>(() => ({
|
||||
type: 'checkbox',
|
||||
showCheckedAll: true,
|
||||
onlyCurrent: true,
|
||||
}))
|
||||
|
||||
const warehouseHint = computed(() => {
|
||||
if (!props.warehouseId) return ''
|
||||
const hit = rows.value.find((x) => x.warehouseId === props.warehouseId)
|
||||
return hit?.warehouseName || String(props.warehouseId)
|
||||
})
|
||||
|
||||
const selectedWarehouseLabel = computed(() => {
|
||||
const ids = [...new Set(selectedRows.value.map((x) => x.warehouseId).filter((x) => x > 0))]
|
||||
if (ids.length !== 1) return ''
|
||||
const fromRow = selectedRows.value.find((x) => x.warehouseId === ids[0])?.warehouseName
|
||||
return fromRow && fromRow !== '-' ? fromRow : String(ids[0])
|
||||
})
|
||||
|
||||
const excludeSet = computed(() => new Set((props.excludeOrderIds || []).map(Number).filter((x) => x > 0)))
|
||||
|
||||
function statusName(code: string) {
|
||||
const meta = getOrderStatusMeta(code)
|
||||
return meta ? t(meta.labelKey) : code
|
||||
}
|
||||
|
||||
function mapOrderRow(raw: any): BindableOrderRow | null {
|
||||
const row = normalizeListRow(raw)
|
||||
const id = Number(pickField(row, 'id', 'Id') || 0)
|
||||
if (!id || excludeSet.value.has(id)) return null
|
||||
const status =
|
||||
resolveOrderStatusCode(row) ||
|
||||
String(pickField(row, 'orderStatus', 'OrderStatus', 'status', 'Status') || '')
|
||||
const pickupOrderId = Number(pickField(row, 'pickupOrderId', 'PickupOrderId') || 0)
|
||||
if (pickupOrderId > 0) return null
|
||||
if (!BINDABLE_STATUSES.has(status)) return null
|
||||
const warehouseId = Number(pickField(row, 'warehouseId', 'WarehouseId') || 0)
|
||||
if (props.warehouseId && props.warehouseId > 0 && warehouseId !== props.warehouseId) return null
|
||||
return {
|
||||
id,
|
||||
serviceNo: String(pickField(row, 'serviceNo', 'ServiceNo') || '-'),
|
||||
clientNo: String(pickField(row, 'clientNo', 'ClientNo') || '-'),
|
||||
warehouseId,
|
||||
warehouseName: String(
|
||||
pickField(row, 'warehouse', 'Warehouse', 'warehouseName', 'WarehouseName') || '-',
|
||||
),
|
||||
customerId: Number(pickField(row, 'customerId', 'CustomerId') || 0) || undefined,
|
||||
status,
|
||||
statusName: statusName(status),
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectionChange(keys: Array<string | number>) {
|
||||
const nextKeys = (keys || []).map((k) => Number(k)).filter((id) => id > 0)
|
||||
const fromCurrent = rows.value.filter((r) => nextKeys.includes(r.id))
|
||||
const kept = selectedRows.value.filter(
|
||||
(r) => nextKeys.includes(r.id) && !fromCurrent.some((n) => n.id === r.id),
|
||||
)
|
||||
let next = [...kept, ...fromCurrent]
|
||||
|
||||
if (!props.warehouseId) {
|
||||
const whIds = [...new Set(next.map((x) => x.warehouseId).filter((x) => x > 0))]
|
||||
if (whIds.length > 1) {
|
||||
const keepWh = next[0]?.warehouseId
|
||||
next = next.filter((x) => x.warehouseId === keepWh)
|
||||
$baseMessage(t('order.myOrders.createPickup.sameWarehouse'), 'warning')
|
||||
}
|
||||
}
|
||||
|
||||
selectedRows.value = next
|
||||
selectedKeys.value = next.map((x) => x.id)
|
||||
emit('change', next)
|
||||
}
|
||||
|
||||
function goPredeclared() {
|
||||
const path = rbacUserStore.isCustomerPortal()
|
||||
? '/order/myOrders'
|
||||
: '/order/waybillManage'
|
||||
router.push({ path, query: { status: 'predeclared' } })
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
if (props.customerId != null && !(props.customerId > 0) && props.customerId !== undefined) {
|
||||
// noop — allow undefined for portal
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
status: 'predeclared',
|
||||
keyword: keyword.value.trim() || undefined,
|
||||
}
|
||||
if (props.customerId && props.customerId > 0) {
|
||||
body.customerId = props.customerId
|
||||
}
|
||||
if (props.warehouseId && props.warehouseId > 0) {
|
||||
body.warehouseId = props.warehouseId
|
||||
}
|
||||
const res = await postOrderList(body as any)
|
||||
rows.value = (res.items || [])
|
||||
.map((raw: any) => mapOrderRow(raw))
|
||||
.filter((x): x is BindableOrderRow => !!x)
|
||||
|
||||
const idSet = new Set(rows.value.map((x) => x.id))
|
||||
// 有关键词时只保留仍可见的勾选;清空搜索则保留已选
|
||||
if (keyword.value.trim()) {
|
||||
selectedRows.value = selectedRows.value.filter((x) => idSet.has(x.id))
|
||||
selectedKeys.value = selectedRows.value.map((x) => x.id)
|
||||
emit('change', selectedRows.value)
|
||||
}
|
||||
} catch (e: any) {
|
||||
rows.value = []
|
||||
$baseMessage(e?.message || t('order.pickupOrder.message.loadOrdersFailed'), 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
keyword.value = ''
|
||||
rows.value = []
|
||||
selectedKeys.value = []
|
||||
selectedRows.value = []
|
||||
emit('change', [])
|
||||
}
|
||||
|
||||
function getSelected() {
|
||||
return [...selectedRows.value]
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.customerId, props.warehouseId, (props.excludeOrderIds || []).join(',')] as const,
|
||||
() => {
|
||||
if (props.autoLoad) void reload()
|
||||
},
|
||||
)
|
||||
|
||||
defineExpose({ reload, clear, getSelected })
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.bindable-order-picker__toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bindable-order-picker__footer {
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
background: var(--color-fill-2);
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
.bindable-order-picker__empty {
|
||||
padding: 24px 12px;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-3);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
:deep(.arco-btn) {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-table-th) {
|
||||
background: var(--color-fill-1);
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,738 @@
|
||||
<template>
|
||||
<ModalPro
|
||||
:title="t('order.pickupOrder.modal.create')"
|
||||
:width="820"
|
||||
:mask-closable="false"
|
||||
@register="registerModal"
|
||||
@cancel="closeModal"
|
||||
>
|
||||
<div class="wh-form-modal pk-create-modal">
|
||||
<a-radio-group v-model="createMode" type="button" class="pk-create-mode" @change="onModeChange">
|
||||
<a-radio value="bind">{{ t('order.pickupOrder.create.modeBind') }}</a-radio>
|
||||
<a-radio value="new">{{ t('order.pickupOrder.create.modeNew') }}</a-radio>
|
||||
</a-radio-group>
|
||||
|
||||
<a-alert type="info" show-icon class="pk-create-alert">
|
||||
{{
|
||||
createMode === 'bind'
|
||||
? t('order.pickupOrder.create.leadBind')
|
||||
: t('order.pickupOrder.create.lead')
|
||||
}}
|
||||
</a-alert>
|
||||
|
||||
<!-- 绑定已有运单 -->
|
||||
<template v-if="createMode === 'bind'">
|
||||
<div class="wh-form-section__head">
|
||||
<div class="wh-form-section__title">{{ t('order.pickupOrder.create.sectionOrders') }}</div>
|
||||
<a-space :size="8">
|
||||
<a-input
|
||||
v-model="orderKeyword"
|
||||
allow-clear
|
||||
:placeholder="t('order.pickupOrder.create.orderKeywordPh')"
|
||||
style="width: 220px"
|
||||
@press-enter="loadBindableOrders"
|
||||
/>
|
||||
<a-button class="app-btn-search" :loading="orderLoading" @click="loadBindableOrders">
|
||||
{{ t('order.pickupOrder.actions.search') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
row-key="id"
|
||||
size="small"
|
||||
:loading="orderLoading"
|
||||
:data="orderRows"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 240 }"
|
||||
:row-selection="orderRowSelection"
|
||||
:columns="orderColumns"
|
||||
v-model:selected-keys="selectedOrderKeys"
|
||||
@selection-change="onOrderSelectionChange"
|
||||
>
|
||||
<template #empty>
|
||||
<div class="pk-create-empty">
|
||||
<p>{{ t('order.pickupOrder.create.emptyOrders') }}</p>
|
||||
<a-button type="outline" size="small" @click="goPredeclared">
|
||||
{{ t('order.pickupOrder.create.goPredeclared') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
<div class="pk-create-selected">
|
||||
{{
|
||||
t('order.pickupOrder.create.selectedOrders', {
|
||||
count: selectedOrders.length,
|
||||
warehouse: selectedWarehouseLabel || '-',
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
|
||||
<a-divider class="wh-form-divider" />
|
||||
|
||||
<div class="wh-form-section__title">{{ t('order.pickupOrder.create.sectionPickup') }}</div>
|
||||
<a-form
|
||||
:model="bindForm"
|
||||
layout="horizontal"
|
||||
:label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 16 }"
|
||||
>
|
||||
<a-form-item :label="t('order.pickupOrder.form.senderAddress')" required>
|
||||
<a-select
|
||||
v-model="bindForm.senderAddressId"
|
||||
:options="senderOptions"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="refLoading"
|
||||
:placeholder="t('order.pickupOrder.form.senderAddressPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('order.pickupOrder.form.warehouse')" required>
|
||||
<a-input :model-value="selectedWarehouseLabel || t('order.pickupOrder.create.warehouseFromOrders')" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('order.pickupOrder.form.pickupTime')" required>
|
||||
<a-date-picker
|
||||
v-model="bindForm.expectPickupTime"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('order.myOrders.createPickup.carrierService')">
|
||||
<a-select
|
||||
v-model="bindForm.carrierService"
|
||||
:options="carrierServiceOptions"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="refLoading"
|
||||
:placeholder="t('order.myOrders.createPickup.carrierServicePh')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('order.myOrders.createPickup.remark')">
|
||||
<a-textarea v-model="bindForm.remark" :max-length="512" allow-clear :auto-size="{ minRows: 2, maxRows: 4 }" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- 新建运单揽收 -->
|
||||
<template v-else>
|
||||
<div class="wh-form-section__title">{{ t('order.pickupOrder.create.sectionPickup') }}</div>
|
||||
<a-form
|
||||
:model="newForm"
|
||||
layout="horizontal"
|
||||
:label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 16 }"
|
||||
>
|
||||
<a-form-item :label="t('order.pickupOrder.form.senderAddress')" required>
|
||||
<a-select
|
||||
v-model="newForm.senderAddressId"
|
||||
:options="senderOptions"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="refLoading"
|
||||
:placeholder="t('order.pickupOrder.form.senderAddressPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('order.pickupOrder.form.warehouse')" required>
|
||||
<a-select
|
||||
v-model="newForm.warehouseId"
|
||||
:options="warehouseOptions"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="refLoading"
|
||||
:placeholder="t('order.pickupOrder.form.warehousePlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('order.pickupOrder.form.pickupTime')" required>
|
||||
<a-date-picker
|
||||
v-model="newForm.pickupTime"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-divider class="wh-form-divider" />
|
||||
|
||||
<div class="wh-form-section__title">{{ t('order.pickupOrder.create.sectionCargo') }}</div>
|
||||
<a-form
|
||||
:model="newForm"
|
||||
layout="horizontal"
|
||||
:label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 16 }"
|
||||
>
|
||||
<a-form-item :label="t('order.pickupOrder.form.product')" required>
|
||||
<a-select
|
||||
v-model="newForm.productCode"
|
||||
:options="productOptions"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="refLoading"
|
||||
:placeholder="t('order.pickupOrder.form.productPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('order.pickupOrder.form.cargoName')" required>
|
||||
<a-input
|
||||
v-model="newForm.cargoName"
|
||||
allow-clear
|
||||
:max-length="200"
|
||||
:placeholder="t('order.pickupOrder.form.cargoNamePlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('order.pickupOrder.form.actualWeight')" required>
|
||||
<a-input-number
|
||||
v-model="newForm.actualWeight"
|
||||
:min="0.001"
|
||||
:precision="3"
|
||||
style="width: 100%"
|
||||
:placeholder="t('order.pickupOrder.form.weightPlaceholder')"
|
||||
>
|
||||
<template #suffix>kg</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('order.pickupOrder.form.clientNo')">
|
||||
<a-input
|
||||
v-model="newForm.clientNo"
|
||||
allow-clear
|
||||
:max-length="64"
|
||||
:placeholder="t('order.pickupOrder.form.clientNoPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<a-space :size="8">
|
||||
<a-button @click="closeModal">{{ t('common.cancel') }}</a-button>
|
||||
<a-button type="primary" :loading="creating" @click="handleCreate">
|
||||
{{ t('order.pickupOrder.actions.submit') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</ModalPro>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import dayjs from 'dayjs'
|
||||
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 { createPickupOrderV2 } from '@/api/order/pickupOrder'
|
||||
import { createPickupOrder, getSenderAddressList } from '@/api/order/senderAddress'
|
||||
import { getWarehouseList } from '@/api/system/warehouse'
|
||||
import { $baseMessage } from '@/utils'
|
||||
import { normalizeListRow, pickField } from '@/utils/warehouseListMapper'
|
||||
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer'
|
||||
import { loadDomesticPickupProductOptions } from '@/utils/domesticPickupProducts'
|
||||
import {
|
||||
checkCustomerContactRequired,
|
||||
isCustomerContactRequiredError,
|
||||
} from '@/utils/customerContactGate'
|
||||
import type { CustomerProfileCompleteness } from '@/api/customer'
|
||||
import { resolveOrderStatusCode, getOrderStatusMeta } from '@/constants/orderStatus'
|
||||
import { useRbacUserStore } from '@/store/modules/rbac-user'
|
||||
|
||||
defineOptions({ name: 'CreatePickupSheetModal' })
|
||||
|
||||
type CreateMode = 'bind' | 'new'
|
||||
|
||||
type BindableOrderRow = {
|
||||
id: number
|
||||
serviceNo: string
|
||||
clientNo: string
|
||||
warehouseId: number
|
||||
warehouseName: string
|
||||
customerId?: number
|
||||
status: string
|
||||
statusName: string
|
||||
}
|
||||
|
||||
const BINDABLE_STATUSES = new Set([
|
||||
'draft',
|
||||
'predeclared',
|
||||
'pendingAllocation',
|
||||
'pendingPayment',
|
||||
'pendingShip',
|
||||
'pendingOnline',
|
||||
])
|
||||
|
||||
export type CreatePickupSuccessPayload = {
|
||||
serviceNo?: string
|
||||
pickupNo?: string
|
||||
id?: number
|
||||
pickupOrderId?: number
|
||||
status?: string
|
||||
payStatus?: string
|
||||
receivableAmount?: number
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: [payload: CreatePickupSuccessPayload]
|
||||
needContact: [profile: CustomerProfileCompleteness | null]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const rbacUserStore = useRbacUserStore()
|
||||
const { isAdmin, withCustomerId, ensureMutationCustomerId } = useWarehouseAdminCustomer()
|
||||
const [registerModal, { openModal, closeModal }] = useModal() as any
|
||||
|
||||
const createMode = ref<CreateMode>('bind')
|
||||
const creating = ref(false)
|
||||
const refLoading = ref(false)
|
||||
const orderLoading = ref(false)
|
||||
const orderKeyword = ref('')
|
||||
const orderRows = ref<BindableOrderRow[]>([])
|
||||
const selectedOrderKeys = ref<Array<number | string>>([])
|
||||
const selectedOrders = ref<BindableOrderRow[]>([])
|
||||
|
||||
const senderOptions = ref<{ value: number; label: string }[]>([])
|
||||
const warehouseOptions = ref<{ value: number; label: string }[]>([])
|
||||
const productOptions = ref<{ value: string; label: string }[]>([])
|
||||
const carrierServiceOptions = ref<{ value: string; label: string }[]>([])
|
||||
const preferredCustomerId = ref<number | undefined>()
|
||||
|
||||
const bindForm = reactive({
|
||||
senderAddressId: undefined as number | undefined,
|
||||
expectPickupTime: '' as string,
|
||||
carrierService: '' as string,
|
||||
remark: '',
|
||||
})
|
||||
|
||||
const newForm = reactive({
|
||||
senderAddressId: undefined as number | undefined,
|
||||
warehouseId: undefined as number | undefined,
|
||||
productCode: '',
|
||||
pickupTime: undefined as string | undefined,
|
||||
cargoName: '',
|
||||
actualWeight: undefined as number | undefined,
|
||||
clientNo: '',
|
||||
})
|
||||
|
||||
const orderColumns = computed<TableColumnData[]>(() => [
|
||||
{ title: t('order.pickupOrder.table.serviceNo'), dataIndex: 'serviceNo', width: 160, ellipsis: true, tooltip: true },
|
||||
{ title: t('order.pickupOrder.table.clientNo'), dataIndex: 'clientNo', width: 120, ellipsis: true, tooltip: true },
|
||||
{ title: t('order.pickupOrder.table.warehouse'), dataIndex: 'warehouseName', width: 120, ellipsis: true, tooltip: true },
|
||||
{ title: t('order.pickupOrder.table.status'), dataIndex: 'statusName', width: 100 },
|
||||
])
|
||||
|
||||
const selectedWarehouseId = computed(() => {
|
||||
const ids = [...new Set(selectedOrders.value.map((x) => x.warehouseId).filter((x) => x > 0))]
|
||||
return ids.length === 1 ? ids[0] : undefined
|
||||
})
|
||||
|
||||
const selectedWarehouseLabel = computed(() => {
|
||||
if (!selectedWarehouseId.value) return ''
|
||||
const fromRow = selectedOrders.value.find((x) => x.warehouseId === selectedWarehouseId.value)?.warehouseName
|
||||
if (fromRow && fromRow !== '-') return fromRow
|
||||
return warehouseOptions.value.find((x) => x.value === selectedWarehouseId.value)?.label || String(selectedWarehouseId.value)
|
||||
})
|
||||
|
||||
const orderRowSelection = computed<TableRowSelection>(() => ({
|
||||
type: 'checkbox',
|
||||
showCheckedAll: true,
|
||||
onlyCurrent: true,
|
||||
}))
|
||||
|
||||
function onOrderSelectionChange(keys: Array<string | number>) {
|
||||
const nextKeys = (keys || []).map((k) => Number(k)).filter((id) => id > 0)
|
||||
const fromCurrent = orderRows.value.filter((r) => nextKeys.includes(r.id))
|
||||
const kept = selectedOrders.value.filter(
|
||||
(r) => nextKeys.includes(r.id) && !fromCurrent.some((n) => n.id === r.id),
|
||||
)
|
||||
let next = [...kept, ...fromCurrent]
|
||||
const whIds = [...new Set(next.map((x) => x.warehouseId).filter((x) => x > 0))]
|
||||
if (whIds.length > 1) {
|
||||
const keepWh = next[0]?.warehouseId
|
||||
next = next.filter((x) => x.warehouseId === keepWh)
|
||||
$baseMessage(t('order.myOrders.createPickup.sameWarehouse'), 'warning')
|
||||
}
|
||||
selectedOrders.value = next
|
||||
selectedOrderKeys.value = next.map((x) => x.id)
|
||||
}
|
||||
|
||||
function resolveMutationCustomerId(): number | undefined {
|
||||
if (!isAdmin.value) return undefined
|
||||
const preferred = preferredCustomerId.value
|
||||
if (preferred != null && preferred > 0) return preferred
|
||||
return ensureMutationCustomerId()
|
||||
}
|
||||
|
||||
function defaultPickupTime() {
|
||||
return dayjs().add(1, 'day').hour(10).minute(0).second(0).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
function statusName(code: string) {
|
||||
const meta = getOrderStatusMeta(code)
|
||||
return meta ? t(meta.labelKey) : code
|
||||
}
|
||||
|
||||
function mapOrderRow(raw: any): BindableOrderRow | null {
|
||||
const row = normalizeListRow(raw)
|
||||
const id = Number(pickField(row, 'id', 'Id') || 0)
|
||||
if (!id) return null
|
||||
const status = resolveOrderStatusCode(row) || String(pickField(row, 'orderStatus', 'OrderStatus', 'status', 'Status') || '')
|
||||
const pickupOrderId = Number(pickField(row, 'pickupOrderId', 'PickupOrderId') || 0)
|
||||
if (pickupOrderId > 0) return null
|
||||
if (!BINDABLE_STATUSES.has(status)) return null
|
||||
return {
|
||||
id,
|
||||
serviceNo: String(pickField(row, 'serviceNo', 'ServiceNo') || '-'),
|
||||
clientNo: String(pickField(row, 'clientNo', 'ClientNo') || '-'),
|
||||
warehouseId: Number(pickField(row, 'warehouseId', 'WarehouseId') || 0),
|
||||
warehouseName: String(pickField(row, 'warehouse', 'Warehouse', 'warehouseName', 'WarehouseName') || '-'),
|
||||
customerId: Number(pickField(row, 'customerId', 'CustomerId') || 0) || undefined,
|
||||
status,
|
||||
statusName: statusName(status),
|
||||
}
|
||||
}
|
||||
|
||||
async function loadReferenceOptions() {
|
||||
const mutationCustomerId = resolveMutationCustomerId()
|
||||
if (isAdmin.value && mutationCustomerId == null) {
|
||||
senderOptions.value = []
|
||||
return false
|
||||
}
|
||||
|
||||
refLoading.value = true
|
||||
try {
|
||||
const senderBody = withCustomerId({
|
||||
page: 1,
|
||||
pageSize: 200,
|
||||
addressType: 'sender',
|
||||
})
|
||||
if (mutationCustomerId) senderBody.customerId = mutationCustomerId
|
||||
|
||||
const [senderRes, warehouseRes, pickupProducts] = await Promise.all([
|
||||
getSenderAddressList(senderBody),
|
||||
getWarehouseList({ page: 1, pageSize: 500 }),
|
||||
loadDomesticPickupProductOptions(),
|
||||
])
|
||||
|
||||
senderOptions.value = (senderRes.items || [])
|
||||
.map((item: any) => {
|
||||
const row = normalizeListRow(item)
|
||||
const id = Number(pickField(row, 'id', 'Id') || 0)
|
||||
const name = String(
|
||||
pickField(row, 'addressName', 'AddressName') ||
|
||||
pickField(row, 'senderName', 'SenderName') ||
|
||||
id,
|
||||
)
|
||||
const phone = String(pickField(row, 'phone', 'Phone') || '')
|
||||
return { value: id, label: phone ? `${name} · ${phone}` : name }
|
||||
})
|
||||
.filter((x) => x.value > 0)
|
||||
|
||||
warehouseOptions.value = (warehouseRes.items || [])
|
||||
.map((item: any) => {
|
||||
const id = Number(item?.id ?? item?.Id ?? 0)
|
||||
const name = String(item?.warehouseName ?? item?.WarehouseName ?? id)
|
||||
return { value: id, label: name }
|
||||
})
|
||||
.filter((x) => x.value > 0)
|
||||
|
||||
const productOpts = pickupProducts.map((p) => ({ value: p.value, label: p.label }))
|
||||
productOptions.value = productOpts
|
||||
carrierServiceOptions.value = productOpts
|
||||
return true
|
||||
} catch (e: any) {
|
||||
$baseMessage(e?.message || t('order.pickupOrder.message.loadRefFailed'), 'error')
|
||||
return false
|
||||
} finally {
|
||||
refLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBindableOrders() {
|
||||
const mutationCustomerId = resolveMutationCustomerId()
|
||||
if (isAdmin.value && mutationCustomerId == null) return
|
||||
|
||||
orderLoading.value = true
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
status: 'predeclared',
|
||||
keyword: orderKeyword.value.trim() || undefined,
|
||||
}
|
||||
if (mutationCustomerId) body.customerId = mutationCustomerId
|
||||
const res = await postOrderList(body as any)
|
||||
const mapped = (res.items || [])
|
||||
.map((raw: any) => mapOrderRow(raw))
|
||||
.filter((x): x is BindableOrderRow => !!x)
|
||||
orderRows.value = mapped
|
||||
|
||||
// 清理不在最新结果中的勾选(关键词搜索后)
|
||||
const idSet = new Set(mapped.map((x) => x.id))
|
||||
selectedOrders.value = selectedOrders.value.filter((x) => idSet.has(x.id) || !orderKeyword.value.trim())
|
||||
selectedOrderKeys.value = selectedOrders.value.map((x) => x.id)
|
||||
} catch (e: any) {
|
||||
orderRows.value = []
|
||||
$baseMessage(e?.message || t('order.pickupOrder.message.loadOrdersFailed'), 'error')
|
||||
} finally {
|
||||
orderLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetBindForm() {
|
||||
bindForm.senderAddressId = senderOptions.value[0]?.value
|
||||
bindForm.expectPickupTime = defaultPickupTime()
|
||||
bindForm.carrierService = ''
|
||||
bindForm.remark = ''
|
||||
orderKeyword.value = ''
|
||||
orderRows.value = []
|
||||
selectedOrderKeys.value = []
|
||||
selectedOrders.value = []
|
||||
}
|
||||
|
||||
function resetNewForm() {
|
||||
Object.assign(newForm, {
|
||||
senderAddressId: senderOptions.value[0]?.value,
|
||||
warehouseId: warehouseOptions.value[0]?.value,
|
||||
productCode: productOptions.value[0]?.value || '',
|
||||
pickupTime: defaultPickupTime(),
|
||||
cargoName: '',
|
||||
actualWeight: undefined,
|
||||
clientNo: '',
|
||||
})
|
||||
}
|
||||
|
||||
async function onModeChange() {
|
||||
if (createMode.value === 'bind' && !orderRows.value.length) {
|
||||
await loadBindableOrders()
|
||||
}
|
||||
}
|
||||
|
||||
function goPredeclared() {
|
||||
const path = rbacUserStore.isCustomerPortal()
|
||||
? '/order/myOrders'
|
||||
: '/order/waybillManage'
|
||||
router.push({ path, query: { status: 'predeclared' } })
|
||||
}
|
||||
|
||||
function buildSuccessPayload(res: any, extra?: Partial<CreatePickupSuccessPayload>): CreatePickupSuccessPayload {
|
||||
const id = Number(res?.id ?? res?.Id ?? extra?.id ?? 0) || undefined
|
||||
const pickupOrderId =
|
||||
Number(res?.pickupOrderId ?? res?.PickupOrderId ?? extra?.pickupOrderId ?? id ?? 0) || undefined
|
||||
const receivableRaw = res?.receivableAmount ?? res?.ReceivableAmount
|
||||
const receivableAmount =
|
||||
receivableRaw != null && Number.isFinite(Number(receivableRaw))
|
||||
? Number(receivableRaw)
|
||||
: undefined
|
||||
return {
|
||||
serviceNo: extra?.serviceNo || res?.serviceNo || res?.ServiceNo || undefined,
|
||||
pickupNo: extra?.pickupNo || res?.pickupNo || res?.PickupNo || undefined,
|
||||
id,
|
||||
pickupOrderId,
|
||||
status: String(extra?.status ?? res?.status ?? res?.Status ?? '') || undefined,
|
||||
payStatus: String(extra?.payStatus ?? res?.payStatus ?? res?.PayStatus ?? '') || undefined,
|
||||
receivableAmount,
|
||||
}
|
||||
}
|
||||
|
||||
async function open(customerId?: number) {
|
||||
preferredCustomerId.value =
|
||||
customerId != null && Number(customerId) > 0 ? Number(customerId) : undefined
|
||||
|
||||
if (isAdmin.value && resolveMutationCustomerId() == null) return
|
||||
|
||||
if (!isAdmin.value) {
|
||||
const { needContact, profile } = await checkCustomerContactRequired()
|
||||
if (needContact) {
|
||||
emit('needContact', profile)
|
||||
$baseMessage(t('order.pickupOrder.message.contactRequired'), 'warning')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const ok = await loadReferenceOptions()
|
||||
if (!ok) return
|
||||
if (!senderOptions.value.length) {
|
||||
$baseMessage(t('order.pickupOrder.message.noSenderAddress'), 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
createMode.value = 'bind'
|
||||
resetBindForm()
|
||||
resetNewForm()
|
||||
openModal(true)
|
||||
await loadBindableOrders()
|
||||
}
|
||||
|
||||
function validateBind() {
|
||||
if (!selectedOrders.value.length) {
|
||||
$baseMessage(t('order.pickupOrder.create.needSelectOrders'), 'warning')
|
||||
return false
|
||||
}
|
||||
const whIds = [...new Set(selectedOrders.value.map((x) => x.warehouseId).filter((x) => x > 0))]
|
||||
if (whIds.length !== 1) {
|
||||
$baseMessage(t('order.myOrders.createPickup.sameWarehouse'), 'warning')
|
||||
return false
|
||||
}
|
||||
if (!bindForm.senderAddressId) {
|
||||
$baseMessage(t('order.pickupOrder.message.senderRequired'), 'warning')
|
||||
return false
|
||||
}
|
||||
if (!bindForm.expectPickupTime) {
|
||||
$baseMessage(t('order.pickupOrder.message.pickupTimeRequired'), 'warning')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function validateNew() {
|
||||
if (!newForm.senderAddressId) {
|
||||
$baseMessage(t('order.pickupOrder.message.senderRequired'), 'warning')
|
||||
return false
|
||||
}
|
||||
if (!newForm.warehouseId) {
|
||||
$baseMessage(t('order.pickupOrder.message.warehouseRequired'), 'warning')
|
||||
return false
|
||||
}
|
||||
if (!newForm.productCode) {
|
||||
$baseMessage(t('order.pickupOrder.message.productRequired'), 'warning')
|
||||
return false
|
||||
}
|
||||
if (!newForm.pickupTime) {
|
||||
$baseMessage(t('order.pickupOrder.message.pickupTimeRequired'), 'warning')
|
||||
return false
|
||||
}
|
||||
if (!newForm.cargoName.trim()) {
|
||||
$baseMessage(t('order.pickupOrder.message.cargoNameRequired'), 'warning')
|
||||
return false
|
||||
}
|
||||
if (!newForm.actualWeight || Number(newForm.actualWeight) <= 0) {
|
||||
$baseMessage(t('order.pickupOrder.message.weightRequired'), 'warning')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function handleCreate() {
|
||||
const customerId = resolveMutationCustomerId()
|
||||
if (isAdmin.value && customerId == null) return
|
||||
|
||||
if (createMode.value === 'bind') {
|
||||
if (!validateBind()) return
|
||||
creating.value = true
|
||||
try {
|
||||
const res: any = await createPickupOrderV2({
|
||||
customerId: customerId || undefined,
|
||||
senderAddressId: bindForm.senderAddressId!,
|
||||
warehouseId: selectedWarehouseId.value!,
|
||||
carrierService: bindForm.carrierService || undefined,
|
||||
expectPickupTime: bindForm.expectPickupTime,
|
||||
orderIds: selectedOrders.value.map((x) => x.id),
|
||||
remark: bindForm.remark || undefined,
|
||||
})
|
||||
const pickupNo = res?.pickupNo || res?.PickupNo || ''
|
||||
$baseMessage(
|
||||
t('order.myOrders.createPickup.success', {
|
||||
no: pickupNo || '',
|
||||
count: selectedOrders.value.length,
|
||||
}),
|
||||
'success',
|
||||
)
|
||||
closeModal()
|
||||
emit('success', buildSuccessPayload(res, { pickupNo }))
|
||||
} catch (e: any) {
|
||||
if (isCustomerContactRequiredError(e)) emit('needContact', null)
|
||||
$baseMessage(e?.message || t('order.pickupOrder.message.createFailed'), 'error')
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!productOptions.value.length) {
|
||||
$baseMessage(t('order.pickupOrder.message.noPickupProduct'), 'warning')
|
||||
return
|
||||
}
|
||||
if (!validateNew()) return
|
||||
|
||||
creating.value = true
|
||||
try {
|
||||
const result: any = await createPickupOrder({
|
||||
senderAddressId: newForm.senderAddressId,
|
||||
warehouseId: newForm.warehouseId,
|
||||
productCode: newForm.productCode,
|
||||
pickupTime: newForm.pickupTime,
|
||||
cargoName: newForm.cargoName.trim(),
|
||||
actualWeight: Number(newForm.actualWeight),
|
||||
clientNo: newForm.clientNo.trim() || undefined,
|
||||
customerId,
|
||||
})
|
||||
const serviceNo = result?.serviceNo || result?.ServiceNo || ''
|
||||
$baseMessage(
|
||||
serviceNo
|
||||
? t('order.pickupOrder.message.createSuccessWithNo', { no: serviceNo })
|
||||
: t('order.pickupOrder.message.createSuccess'),
|
||||
'success',
|
||||
)
|
||||
closeModal()
|
||||
emit('success', buildSuccessPayload(result, { serviceNo }))
|
||||
} catch (e: any) {
|
||||
if (isCustomerContactRequiredError(e)) emit('needContact', null)
|
||||
$baseMessage(e?.message || t('order.pickupOrder.message.createFailed'), 'error')
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import '@/styles/warehouse-form-modal.less';
|
||||
|
||||
.pk-create-mode {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pk-create-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pk-create-selected {
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
background: var(--color-fill-2);
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
.pk-create-empty {
|
||||
padding: 24px 12px;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-3);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
:deep(.arco-btn) {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.pk-create-modal {
|
||||
:deep(.arco-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:deep(.arco-table-th) {
|
||||
background: var(--color-fill-1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@ -79,10 +79,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PaginationPro from '@/components/PaginationPro/index.vue'
|
||||
import { getAccessLogList, getChangeLogList, type AccessLogItem, type ChangeLogItem } from '@/api/rbac/data-perm'
|
||||
import { handleApiError } from '@/utils/rbac-error-handler'
|
||||
|
||||
const { t } = useI18n()
|
||||
const activeTab = ref('change')
|
||||
|
||||
const changeLoading = ref(false)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="template-item-form">
|
||||
<a-form :model="model" layout="vertical" class="template-item-form">
|
||||
<p class="form-tip">{{ dimensionTip }}</p>
|
||||
|
||||
<!-- 组织 -->
|
||||
@ -139,7 +139,7 @@
|
||||
{{ t('system.dataPerm.templates.form.filterExample') }}
|
||||
</a-alert>
|
||||
</template>
|
||||
</div>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -150,7 +150,6 @@ import { DEFAULT_BIZ_TYPES, FILTER_OPERATORS } from '../constants'
|
||||
import type { TemplateItemFormModel } from '../templates/templateItemContent'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: TemplateItemFormModel
|
||||
orgTree: { key: string; title: string; value: string; children?: unknown[] }[]
|
||||
orgScopeOptions: { value: number; label: string }[]
|
||||
dataScopeOptions: { value: number; label: string }[]
|
||||
|
||||
@ -88,13 +88,13 @@
|
||||
{{ t('system.dataPerm.templates.bindApplyHint') }}
|
||||
</a-alert>
|
||||
|
||||
<a-form layout="vertical" class="template-form">
|
||||
<a-form :model="bindForm" layout="vertical" class="template-form">
|
||||
<a-form-item
|
||||
:label="t('system.dataPerm.templates.field.bindRole')"
|
||||
:extra="t('system.dataPerm.templates.field.bindRoleExtra')"
|
||||
>
|
||||
<a-select
|
||||
v-model="boundRoleId"
|
||||
v-model="bindForm.boundRoleId"
|
||||
:options="roleOptions"
|
||||
:placeholder="t('system.dataPerm.templates.field.bindRolePlaceholder')"
|
||||
allow-clear
|
||||
@ -114,7 +114,7 @@
|
||||
<span class="apply-row-desc">{{ t('system.dataPerm.templates.applyToRoleDesc') }}</span>
|
||||
</div>
|
||||
<a-button
|
||||
:disabled="!boundRoleId"
|
||||
:disabled="!bindForm.boundRoleId"
|
||||
:loading="roleApplyLoading"
|
||||
@click="applyToBoundRole"
|
||||
>
|
||||
@ -319,7 +319,9 @@ const pagination = reactive({ current: 1, pageSize: 100, total: 0 })
|
||||
|
||||
const rolesLoading = ref(false)
|
||||
const roleOptions = ref<{ value: string; label: string; code: string }[]>([])
|
||||
const boundRoleId = ref<string>()
|
||||
const bindForm = reactive({
|
||||
boundRoleId: undefined as string | undefined,
|
||||
})
|
||||
const applyLoading = ref(false)
|
||||
const roleApplyLoading = ref(false)
|
||||
const usersLoading = ref(false)
|
||||
@ -487,7 +489,7 @@ function resetForm() {
|
||||
isBuiltIn: false,
|
||||
items: [newItemRow(createDefaultItemForm('data_scope', 1))],
|
||||
})
|
||||
boundRoleId.value = undefined
|
||||
bindForm.boundRoleId = undefined
|
||||
Object.assign(applyForm, {
|
||||
targetUserIds: [],
|
||||
mode: 'replace',
|
||||
@ -521,10 +523,10 @@ async function loadRoles() {
|
||||
|
||||
function syncBoundRoleFromCode() {
|
||||
if (!form.roleCode) {
|
||||
boundRoleId.value = undefined
|
||||
bindForm.boundRoleId = undefined
|
||||
return
|
||||
}
|
||||
boundRoleId.value = roleOptions.value.find((r) => r.code === form.roleCode)?.value
|
||||
bindForm.boundRoleId = roleOptions.value.find((r) => r.code === form.roleCode)?.value
|
||||
}
|
||||
|
||||
function onBoundRoleChange(roleId: string | undefined) {
|
||||
@ -591,13 +593,13 @@ async function runApply(
|
||||
}
|
||||
|
||||
async function applyToBoundRole() {
|
||||
if (!boundRoleId.value) {
|
||||
if (!bindForm.boundRoleId) {
|
||||
Message.warning(t('system.dataPerm.templates.bindRoleFirst'))
|
||||
return
|
||||
}
|
||||
roleApplyLoading.value = true
|
||||
try {
|
||||
await runApply('role', [boundRoleId.value], 'replace')
|
||||
await runApply('role', [bindForm.boundRoleId], 'replace')
|
||||
} catch (e) {
|
||||
handleApiError(e)
|
||||
} finally {
|
||||
|
||||
@ -345,6 +345,19 @@ function actionRenderer(_row: DictDto, _rowIndex: number): string {
|
||||
])
|
||||
}
|
||||
|
||||
/** 固定列序:操作 + 序号 → 数据从 col 2 起 */
|
||||
function customCellRenderer(_row: number, col: number, _value: unknown, rowData: DictDto): string | null {
|
||||
const dataColIndex = col - 2
|
||||
if (dataColIndex < 0 || dataColIndex >= hotColumns.value.length) return null
|
||||
const key = hotColumns.value[dataColIndex].key
|
||||
if (key === 'createTime') {
|
||||
if (!rowData.createTime) return '--'
|
||||
const d = dayjs(rowData.createTime)
|
||||
return d.isValid() ? d.format('YYYY-MM-DD HH:mm:ss') : String(rowData.createTime)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function handleActionClick(row: DictDto, _rowIndex: number, action?: string) {
|
||||
if (action === 'edit') {
|
||||
handleEdit(row)
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
<div class="tool-detail liquid-card">
|
||||
<template v-if="selected">
|
||||
<div class="detail-title">{{ selected.displayName }}</div>
|
||||
<a-form layout="vertical" class="detail-form">
|
||||
<a-form :model="editForm" layout="vertical" class="detail-form">
|
||||
<a-form-item :label="t('system.mcpTools.displayName')">
|
||||
<a-input v-model="editForm.displayName" />
|
||||
</a-form-item>
|
||||
|
||||
@ -32,10 +32,9 @@
|
||||
:empty="!selectedRole"
|
||||
:empty-title="t('system.role.emptySelect')"
|
||||
:empty-desc="t('system.role.emptyDesc')"
|
||||
:title="selectedRole?.name || ''"
|
||||
:subtitle="selectedRole?.code || ''"
|
||||
:title="headerTitle"
|
||||
:subtitle="headerSubtitle"
|
||||
:tags="roleTags"
|
||||
:stats="roleStats"
|
||||
:tabs="roleTabs"
|
||||
v-model:active-tab="activeTab"
|
||||
:loading="permissionLoading && activeTab === 'permissions'"
|
||||
@ -46,44 +45,37 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<a-space>
|
||||
<a-button
|
||||
v-if="hasPermission('Rbac.Roles.Update') && activeTab === 'info'"
|
||||
type="primary"
|
||||
:loading="modalLoading"
|
||||
@click="handleSaveRole"
|
||||
>
|
||||
{{ t('system.role.actions.save') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="hasPermission('Rbac.Roles.AssignPermissions') && activeTab === 'permissions'"
|
||||
type="primary"
|
||||
:loading="permissionSaving"
|
||||
@click="handleSavePermissions"
|
||||
>
|
||||
{{ t('system.role.permission.save') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
<a-button
|
||||
v-if="hasPermission('Rbac.Roles.Update') || isCreating"
|
||||
type="primary"
|
||||
:loading="modalLoading || permissionSaving"
|
||||
@click="handleSaveAll"
|
||||
>
|
||||
{{ t('system.role.actions.save') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<div v-show="activeTab === 'info'" class="section-card">
|
||||
<h3 class="section-card-title">{{ t('system.role.tab.info') }}</h3>
|
||||
<template #header-extra>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="formRules"
|
||||
:label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 16 }"
|
||||
class="role-form"
|
||||
layout="inline"
|
||||
class="role-header-form"
|
||||
>
|
||||
<a-form-item :label="t('system.role.form.name')" field="name">
|
||||
<a-input v-model="formState.name" :placeholder="t('system.role.form.namePlaceholder')" />
|
||||
<a-input
|
||||
v-model="formState.name"
|
||||
:placeholder="t('system.role.form.namePlaceholder')"
|
||||
style="width: 160px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.role.form.code')" field="code">
|
||||
<a-input
|
||||
v-model="formState.code"
|
||||
:placeholder="t('system.role.form.codePlaceholder')"
|
||||
:disabled="!isCreating"
|
||||
style="width: 160px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.role.form.boundWarehouse')" field="boundWarehouseId">
|
||||
@ -92,33 +84,43 @@
|
||||
:placeholder="t('system.role.form.boundWarehousePlaceholder')"
|
||||
allow-clear
|
||||
:options="warehouseOptions"
|
||||
style="width: 180px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!isCreating" :label="t('system.role.table.createTime')">
|
||||
<span>{{ formatTime(selectedRole?.createdAt) }}</span>
|
||||
<span class="role-create-time">{{ formatTime(selectedRole?.createdAt) }}</span>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-show="activeTab === 'permissions'" class="permission-section">
|
||||
<div class="sticky-toolbar">
|
||||
<a-space wrap>
|
||||
<a-input
|
||||
v-model="permissionKeyword"
|
||||
:placeholder="t('system.role.permission.search')"
|
||||
allow-clear
|
||||
style="width: 220px"
|
||||
/>
|
||||
<a-button size="small" @click="checkAllPermissions">{{ t('system.role.permission.checkAll') }}</a-button>
|
||||
<a-button size="small" @click="uncheckAllPermissions">{{ t('system.role.permission.uncheckAll') }}</a-button>
|
||||
</a-space>
|
||||
<span class="perm-summary">
|
||||
{{ t('system.role.permission.selectedCount', { selected: selectedPermissionCount, total: totalPermissionCount }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="section-card permission-list-card">
|
||||
<template v-if="activeTab === 'permissions' && !isSelectedAdminRole" #tabs-extra>
|
||||
<a-space wrap :size="8">
|
||||
<a-input
|
||||
v-model="permissionKeyword"
|
||||
:placeholder="t('system.role.permission.search')"
|
||||
allow-clear
|
||||
style="width: 220px"
|
||||
/>
|
||||
<a-button class="app-btn-toolbar" @click="checkAllPermissions">
|
||||
{{ t('system.role.permission.checkAll') }}
|
||||
</a-button>
|
||||
<a-button class="app-btn-toolbar" @click="uncheckAllPermissions">
|
||||
{{ t('system.role.permission.uncheckAll') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<div v-show="activeTab === 'permissions'" class="permission-body">
|
||||
<a-alert
|
||||
v-if="isSelectedAdminRole"
|
||||
type="info"
|
||||
show-icon
|
||||
:title="t('system.role.permission.adminAllHint')"
|
||||
/>
|
||||
<template v-else>
|
||||
<a-table
|
||||
v-if="filteredPermissionRows.length > 0"
|
||||
class="permission-table"
|
||||
:data="filteredPermissionRows"
|
||||
:pagination="false"
|
||||
:bordered="{ cell: true }"
|
||||
@ -158,10 +160,7 @@
|
||||
</a-table-column>
|
||||
<a-table-column :title="t('system.role.permission.col.level3')">
|
||||
<template #cell="{ record }">
|
||||
<div
|
||||
v-if="record.level2HasChildren"
|
||||
class="permission-button-group"
|
||||
>
|
||||
<div v-if="record.level2HasChildren" class="permission-button-group">
|
||||
<a-checkbox
|
||||
v-for="btn in record.buttons"
|
||||
:key="btn.code"
|
||||
@ -177,9 +176,13 @@
|
||||
</template>
|
||||
</a-table>
|
||||
<div v-else class="permission-empty">
|
||||
{{ permissionKeyword ? t('system.role.permission.noMatch') : t('system.role.permission.loading') }}
|
||||
{{
|
||||
permissionKeyword
|
||||
? t('system.role.permission.noMatch')
|
||||
: t('system.role.permission.loading')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</RbacDetailShell>
|
||||
</div>
|
||||
@ -204,6 +207,7 @@ import type {
|
||||
import { useWarehouseList } from '@/composables/useWarehouseList'
|
||||
import { hasPermission } from '@/utils/rbac-permission'
|
||||
import { handleApiError } from '@/utils/rbac-error-handler'
|
||||
import { useRbacUserStore } from '@/store/modules/rbac-user'
|
||||
import { buildFlatLeftMenuTree, findLeftMenuItemByKey } from '@/utils/rbacLeftMenuTree'
|
||||
import {
|
||||
buildPermissionAssignRows,
|
||||
@ -225,7 +229,7 @@ const searchKeyword = ref('')
|
||||
const selectedRole = ref<RoleDto | null>(null)
|
||||
const isCreating = ref(false)
|
||||
const expandedKeys = ref(['category-roles'])
|
||||
const activeTab = ref('info')
|
||||
const activeTab = ref('permissions')
|
||||
const formRef = ref()
|
||||
const permissionKeyword = ref('')
|
||||
|
||||
@ -250,7 +254,7 @@ const permissionLoading = ref(false)
|
||||
const permissionSaving = ref(false)
|
||||
const permissionTree = ref<PermissionGroupDto[]>([])
|
||||
const selectedPermissions = ref<string[]>([])
|
||||
const permissionTableScrollY = 'calc(100vh - 340px)'
|
||||
const permissionTableScrollY = 'calc(100vh - 300px)'
|
||||
|
||||
const roleTreeData = computed(() =>
|
||||
buildFlatLeftMenuTree('category-roles', t('system.role.pageTitle'), dataSource.value, {
|
||||
@ -275,24 +279,48 @@ const filteredPermissionRows = computed(() =>
|
||||
|
||||
const level1RowSpans = computed(() => computeLevel1RowSpans(filteredPermissionRows.value))
|
||||
|
||||
const totalPermissionCount = computed(() =>
|
||||
collectPermissionCodesFromRows(permissionRows.value).length
|
||||
)
|
||||
|
||||
const selectedPermissionCount = computed(() =>
|
||||
countSelectedAssignablePermissions(selectedPermissions.value, permissionRows.value)
|
||||
)
|
||||
|
||||
const totalPermissionCount = computed(() =>
|
||||
collectPermissionCodesFromRows(permissionRows.value).length
|
||||
)
|
||||
|
||||
const normalizedSelectedPermissions = computed(() =>
|
||||
normalizeSelectedPermissions(selectedPermissions.value, permissionRows.value)
|
||||
)
|
||||
|
||||
function isCustomerRole(role?: RoleDto | null) {
|
||||
const code = role?.code || ''
|
||||
return code === 'Customer' || code.startsWith('CustomerSub_')
|
||||
}
|
||||
|
||||
function isAdminRole(role?: RoleDto | null) {
|
||||
return (role?.code || '').toLowerCase() === 'admin'
|
||||
}
|
||||
|
||||
const isSelectedAdminRole = computed(() => isAdminRole(selectedRole.value))
|
||||
|
||||
const headerTitle = computed(() => {
|
||||
if (isCreating.value) return t('system.role.actions.add')
|
||||
return formState.name || selectedRole.value?.name || ''
|
||||
})
|
||||
|
||||
const headerSubtitle = computed(() => {
|
||||
if (isCreating.value) return formState.code || ''
|
||||
return formState.code || selectedRole.value?.code || ''
|
||||
})
|
||||
|
||||
const roleTags = computed((): RbacDetailTag[] => {
|
||||
if (!selectedRole.value) return []
|
||||
const tags: RbacDetailTag[] = []
|
||||
if (isCreating.value) {
|
||||
tags.push({ label: t('system.role.tag.creating'), color: 'orangered' })
|
||||
}
|
||||
if (isAdminRole(selectedRole.value)) {
|
||||
tags.push({ label: 'Admin', color: 'purple' })
|
||||
}
|
||||
if (isCustomerRole(selectedRole.value)) {
|
||||
tags.push({ label: t('system.role.category.system'), color: 'arcoblue' })
|
||||
}
|
||||
@ -302,21 +330,20 @@ const roleTags = computed((): RbacDetailTag[] => {
|
||||
return tags
|
||||
})
|
||||
|
||||
const roleStats = computed(() => [
|
||||
{ label: t('system.role.permission.selected'), value: selectedPermissionCount.value },
|
||||
{ label: t('system.role.permission.total'), value: totalPermissionCount.value },
|
||||
])
|
||||
|
||||
const roleTabs = computed(() => {
|
||||
const tabs = [{ key: 'info', label: t('system.role.tab.info') }]
|
||||
if (hasPermission('Rbac.Roles.AssignPermissions')) {
|
||||
tabs.push({
|
||||
if (!hasPermission('Rbac.Roles.AssignPermissions')) return []
|
||||
if (isSelectedAdminRole.value) {
|
||||
return [{ key: 'permissions', label: t('system.role.modal.assignPermissions') }]
|
||||
}
|
||||
const selected = selectedPermissionCount.value
|
||||
const total = totalPermissionCount.value
|
||||
return [
|
||||
{
|
||||
key: 'permissions',
|
||||
label: t('system.role.modal.assignPermissions'),
|
||||
badge: selectedPermissionCount.value || undefined,
|
||||
})
|
||||
}
|
||||
return tabs
|
||||
badge: `${selected}/${total}`,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
function permissionSpanMethod({
|
||||
@ -352,12 +379,10 @@ function togglePermission(code: string, checked: boolean | (string | number | bo
|
||||
}
|
||||
|
||||
function formatTime(value?: string | null) {
|
||||
return value ? dayjs(value).format('YYYY-MM-DD HH:mm:ss') : '—'
|
||||
}
|
||||
|
||||
function isCustomerRole(role?: RoleDto | null) {
|
||||
const code = role?.code || ''
|
||||
return code === 'Customer' || code.startsWith('CustomerSub_')
|
||||
if (!value) return '—'
|
||||
const d = dayjs(value)
|
||||
if (!d.isValid() || d.year() < 1970) return '—'
|
||||
return d.format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
function selectRoleFromKey(key: string) {
|
||||
@ -446,7 +471,7 @@ function handleAdd() {
|
||||
isCreating.value = true
|
||||
selectedRole.value = { name: '', code: '' }
|
||||
Object.assign(formState, { name: '', code: '', boundWarehouseId: undefined })
|
||||
activeTab.value = 'info'
|
||||
activeTab.value = 'permissions'
|
||||
selectedPermissions.value = []
|
||||
}
|
||||
|
||||
@ -496,45 +521,80 @@ async function handleSaveRole() {
|
||||
if (newRoleId) {
|
||||
selectedRole.value = dataSource.value.find((r) => r.id === newRoleId) || null
|
||||
}
|
||||
} else if (selectedRole.value?.id) {
|
||||
return newRoleId || null
|
||||
}
|
||||
|
||||
if (selectedRole.value?.id) {
|
||||
const updateData: RoleUpdateDto = { name: formState.name }
|
||||
await RoleService.putRbacRoleUpdate(selectedRole.value.id, updateData)
|
||||
if (formState.boundWarehouseId !== undefined) {
|
||||
roleWarehouseMap.value[selectedRole.value.id] = formState.boundWarehouseId || ''
|
||||
}
|
||||
Message.success(t('system.role.message.updateSuccess'))
|
||||
await loadData()
|
||||
return selectedRole.value.id
|
||||
}
|
||||
return null
|
||||
} catch (error: any) {
|
||||
if (error?.fields) return
|
||||
if (error?.fields) return null
|
||||
handleApiError(error)
|
||||
return null
|
||||
} finally {
|
||||
modalLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSavePermissions() {
|
||||
if (!selectedRole.value?.id || isCreating.value) return
|
||||
async function handleSavePermissions(roleId?: string) {
|
||||
const id = roleId || selectedRole.value?.id
|
||||
if (!id || isCreating.value) return false
|
||||
if (isAdminRole(selectedRole.value)) return true
|
||||
if (!hasPermission('Rbac.Roles.AssignPermissions')) return true
|
||||
|
||||
permissionSaving.value = true
|
||||
try {
|
||||
await RoleService.postRbacRoleAssignPermissions(
|
||||
selectedRole.value.id,
|
||||
normalizedSelectedPermissions.value
|
||||
)
|
||||
Message.success(t('system.role.permission.assignSuccess'))
|
||||
await RoleService.postRbacRoleAssignPermissions(id, normalizedSelectedPermissions.value)
|
||||
const rbacUserStore = useRbacUserStore()
|
||||
const currentHasRole = rbacUserStore.userInfo?.roles?.some((r) => r.id === id)
|
||||
if (currentHasRole) {
|
||||
try {
|
||||
await rbacUserStore.refreshUserInfo()
|
||||
} catch {
|
||||
Message.warning(t('system.role.permission.refreshHint'))
|
||||
}
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
handleApiError(error)
|
||||
return false
|
||||
} finally {
|
||||
permissionSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveAll() {
|
||||
const wasCreating = isCreating.value
|
||||
const roleId = await handleSaveRole()
|
||||
if (!roleId) return
|
||||
|
||||
// 新建已提示成功;Admin 无需写权限
|
||||
if (wasCreating || isAdminRole(selectedRole.value)) {
|
||||
if (!wasCreating) {
|
||||
Message.success(t('system.role.message.updateSuccess'))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const ok = await handleSavePermissions(roleId)
|
||||
if (ok) {
|
||||
Message.success(t('system.role.message.updateSuccess'))
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => selectedRole.value?.id,
|
||||
async (roleId) => {
|
||||
if (!roleId || isCreating.value) return
|
||||
syncFormFromRole(selectedRole.value!)
|
||||
activeTab.value = 'info'
|
||||
activeTab.value = 'permissions'
|
||||
permissionKeyword.value = ''
|
||||
await loadRolePermissions(roleId)
|
||||
}
|
||||
@ -549,18 +609,57 @@ onMounted(async () => {
|
||||
<style scoped lang="less">
|
||||
@import '@/components/RbacSplit/rbac-split-layout.less';
|
||||
|
||||
.role-form {
|
||||
max-width: 640px;
|
||||
.role-header-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 8px;
|
||||
width: 100%;
|
||||
|
||||
:deep(.arco-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
:deep(.arco-form-item-label-col > label) {
|
||||
color: var(--color-text-3);
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.permission-section {
|
||||
.role-create-time {
|
||||
display: inline-block;
|
||||
min-width: 150px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.permission-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.permission-list-card {
|
||||
min-height: 320px;
|
||||
.permission-table {
|
||||
width: 100%;
|
||||
|
||||
:deep(.arco-table-th) {
|
||||
background: var(--color-fill-1);
|
||||
}
|
||||
|
||||
:deep(.arco-table-td) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:deep(.arco-table-td .arco-checkbox) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.arco-table-td .arco-checkbox .arco-checkbox-label) {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.permission-button-group {
|
||||
@ -574,26 +673,8 @@ onMounted(async () => {
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
|
||||
.permission-list-card :deep(.arco-checkbox) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.permission-list-card :deep(.arco-table-th) {
|
||||
background: var(--color-fill-1);
|
||||
}
|
||||
|
||||
.permission-list-card :deep(.arco-table-td) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.perm-summary {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-3);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.permission-empty {
|
||||
padding: 48px 24px;
|
||||
padding: 64px 24px;
|
||||
text-align: center;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
@ -64,6 +64,7 @@ export default defineConfig(({ mode }) => {
|
||||
'/order/fulfillOrders',
|
||||
'/order/waybillManage',
|
||||
'/order/pickupOrder',
|
||||
'/order/customs9610Order',
|
||||
'/order/batchJobs',
|
||||
'/order/templateMapping',
|
||||
'/order/platformStore',
|
||||
@ -448,7 +449,9 @@ export default defineConfig(({ mode }) => {
|
||||
'/order/fulfillOrders',
|
||||
'/order/waybillManage',
|
||||
'/order/pickupOrder',
|
||||
'/order/customs9610Order',
|
||||
'/order/batchJobs',
|
||||
'/order/templateMapping',
|
||||
'/order/platformStore',
|
||||
'/order/platformStoreOrder',
|
||||
// Xhs 相关前端路由(勿用 /xhs 前缀,否则 API 会被误判为 SPA)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user