优化
This commit is contained in:
@@ -82,7 +82,6 @@ watch(() => [props.modelValue, props.inboundCode] as const, ([open]) => {
|
||||
<div><span>入仓码</span><strong>{{ displayValue(association.inboundCode) }}</strong></div>
|
||||
</header>
|
||||
<dl class="inbound-association-grid">
|
||||
<div><dt>订单号</dt><dd>{{ displayValue(association.orderNo) }}</dd></div>
|
||||
<div><dt>订单状态</dt><dd>{{ getFirstLegStatusLabel(association.orderStatus) }}</dd></div>
|
||||
<div><dt>客户名称</dt><dd>{{ displayValue(association.customerName) }}</dd></div>
|
||||
<div><dt>目的国家</dt><dd>{{ displayValue(association.destinationCountryCode) }}</dd></div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FileText, Ship, Zap } from '@lucide/vue'
|
||||
import { FileText, Ship } from '@lucide/vue'
|
||||
import type { ClientModuleManifest } from '../types.ts'
|
||||
|
||||
export const firstLegModule = {
|
||||
@@ -17,8 +17,7 @@ export const firstLegModule = {
|
||||
{
|
||||
path: 'orders/first-leg/create',
|
||||
name: 'first-leg-order-create',
|
||||
component: () => import('./views/FirstLegOrdersView.vue'),
|
||||
props: { createPage: true },
|
||||
component: () => import('./views/FirstLegOrderCreateHubView.vue'),
|
||||
release: 'active',
|
||||
meta: { title: '创建订单', workspaceTitle: '头程订单 · 新建', description: '填写发货、目的地和货物信息', permission: 'order:first-leg:create' },
|
||||
},
|
||||
@@ -44,7 +43,6 @@ export const firstLegModule = {
|
||||
component: () => import('./views/QuickOrderView.vue'),
|
||||
release: 'active',
|
||||
meta: { title: '快速下单', description: '按渠道、目的仓和产品 SKU 快速生成订单草稿', permission: 'order:first-leg:quick-create' },
|
||||
navigation: { key: 'first-leg-quick-order', title: '快速下单', icon: Zap },
|
||||
},
|
||||
],
|
||||
} satisfies ClientModuleManifest
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { FilePenLine, Zap } from '@lucide/vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/modules/auth/public'
|
||||
import FirstLegOrdersView from './FirstLegOrdersView.vue'
|
||||
import QuickOrderView from './QuickOrderView.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const canQuickOrder = computed(() => auth.hasPermission('order:first-leg:quick-create'))
|
||||
const activeMode = computed<'standard' | 'quick'>(() => route.query.mode === 'quick' && canQuickOrder.value ? 'quick' : 'standard')
|
||||
|
||||
function selectMode(mode: 'standard' | 'quick') {
|
||||
if (mode === 'quick' && !canQuickOrder.value) return
|
||||
const query = { ...route.query }
|
||||
if (mode === 'quick') query.mode = 'quick'
|
||||
else delete query.mode
|
||||
void router.replace({ name: 'first-leg-order-create', query })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="first-leg-create-hub">
|
||||
<header class="create-hub-heading">
|
||||
<h1>头程下单</h1>
|
||||
<p>填写货物信息,创建新的头程运输订单</p>
|
||||
</header>
|
||||
<nav v-if="canQuickOrder" class="order-mode-switch" aria-label="头程下单方式">
|
||||
<button type="button" :class="{ active: activeMode === 'standard' }" :aria-current="activeMode === 'standard' ? 'page' : undefined" @click="selectMode('standard')"><FilePenLine :size="18" />普通下单</button>
|
||||
<button type="button" :class="{ active: activeMode === 'quick' }" :aria-current="activeMode === 'quick' ? 'page' : undefined" @click="selectMode('quick')"><Zap :size="18" />快速下单</button>
|
||||
</nav>
|
||||
<FirstLegOrdersView v-if="activeMode === 'standard'" create-page />
|
||||
<QuickOrderView v-else />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.first-leg-create-hub{display:grid;min-width:0;gap:12px}.create-hub-heading{display:grid;gap:5px}.create-hub-heading h1{margin:0;color:var(--navy);font-size:24px;line-height:1.25}.create-hub-heading p{margin:0;color:var(--muted);font-size:13px}.order-mode-switch{display:inline-flex;width:max-content;padding:5px;border:1px solid #dbe4f1;border-radius:12px;background:#fff;box-shadow:0 8px 20px rgba(26,43,75,.06)}.order-mode-switch button{display:inline-flex;height:38px;align-items:center;gap:8px;padding:0 20px;border:0;border-radius:8px;background:transparent;color:#607087;font:inherit;font-size:13px;font-weight:650;cursor:pointer}.order-mode-switch button.active{background:linear-gradient(135deg,#4f73ee,#8a56dc);color:#fff;box-shadow:0 6px 14px rgba(79,115,238,.22)}.order-mode-switch button:focus-visible{outline:2px solid var(--primary);outline-offset:2px}
|
||||
</style>
|
||||
@@ -12,6 +12,7 @@ const progressSource = readFileSync(new URL('../components/OrderProgressPanel.vu
|
||||
const customsDialogSource = readFileSync(new URL('../components/CustomsDocumentsDialog.vue', import.meta.url), 'utf8')
|
||||
const customsApiSource = readFileSync(new URL('../api/customs.ts', import.meta.url), 'utf8')
|
||||
const orderStoreSource = readFileSync(new URL('../store/firstLegOrders.ts', import.meta.url), 'utf8')
|
||||
const createHubSource = readFileSync(new URL('./FirstLegOrderCreateHubView.vue', import.meta.url), 'utf8')
|
||||
|
||||
test('routes the confirmed first-leg path to the implemented order view', () => {
|
||||
assert.match(routerSource, /path: 'orders\/first-leg'[\s\S]*?component: \(\) => import\('\.\/views\/FirstLegOrdersView\.vue'\)[\s\S]*?release: 'active'/)
|
||||
@@ -19,7 +20,8 @@ test('routes the confirmed first-leg path to the implemented order view', () =>
|
||||
|
||||
test('opens order creation as a dedicated protected page while keeping draft editing in a large dialog', () => {
|
||||
assert.match(routerSource, /path: 'orders\/first-leg\/create'[\s\S]*?name: 'first-leg-order-create'[\s\S]*?release: 'active'[\s\S]*?permission: 'order:first-leg:create'/)
|
||||
assert.match(routerSource, /name: 'first-leg-order-create'[\s\S]*?props: \{ createPage: true \}/)
|
||||
assert.match(routerSource, /name: 'first-leg-order-create'[\s\S]*?FirstLegOrderCreateHubView\.vue/)
|
||||
assert.match(createHubSource, /<FirstLegOrdersView v-if="activeMode === 'standard'" create-page \/>/)
|
||||
assert.match(source, /router\.push\(\{ name: 'first-leg-order-create' \}\)/)
|
||||
assert.match(source, /defineProps<\{ createPage\?: boolean; detailPage\?: boolean; showCreateAction\?: boolean \}>/)
|
||||
assert.match(source, /const isCreatePage = computed\(\(\) => props\.createPage\)/)
|
||||
@@ -250,7 +252,9 @@ test('keeps all progress stages and business summary usable on mobile', () => {
|
||||
test('keeps the first viewport compact and moves create into the table toolbar', () => {
|
||||
const tableToolbarSource = source.slice(source.indexOf('#toolbar-actions'), source.indexOf('class="order-eligibility is-pending"'))
|
||||
assert.match(source, /#toolbar-actions/)
|
||||
assert.match(source, /class="order-queue-tabs"[\s\S]*确认计费重[\s\S]*上传资料[\s\S]*共 \{\{ filteredOrders\.length \}\} 条/)
|
||||
assert.match(source, /class="order-queue-tabs"[\s\S]*确认计费重[\s\S]*上传资料[\s\S]*草稿/)
|
||||
assert.match(source, /activeOrderQueue\.value === 'draft'[\s\S]*order\.status === 'DRAFT'/)
|
||||
assert.doesNotMatch(source, /共 \{\{ filteredOrders\.length \}\} 条/)
|
||||
assert.match(source, /#toolbar-actions[\s\S]*order:first-leg:create[\s\S]*创建订单[\s\S]*class="order-eligibility is-pending"/)
|
||||
assert.match(tableToolbarSource, /下载唛头[\s\S]*导出订单信息[\s\S]*批量提交草稿[\s\S]*创建订单/)
|
||||
assert.doesNotMatch(source.slice(source.indexOf('<QueryToolbar'), source.indexOf('<div class="client-card order-table-card">')), /下载唛头|导出订单信息|创建订单/)
|
||||
|
||||
@@ -191,7 +191,7 @@ const selectedOrders = ref<FirstLegOrder[]>([])
|
||||
const selectedOrderCount = computed(() => selectedOrders.value.length)
|
||||
const selectedDraftOrders = computed(() => selectedOrders.value.filter((order) => order.status === 'DRAFT'))
|
||||
const orderTableVersion = ref(0)
|
||||
type OrderQueue = 'all' | 'fee' | 'customs'
|
||||
type OrderQueue = 'all' | 'fee' | 'customs' | 'draft'
|
||||
const activeOrderQueue = ref<OrderQueue>('all')
|
||||
const editorOpen = ref(false)
|
||||
const orderFormRef = ref<FormInstance>()
|
||||
@@ -471,10 +471,12 @@ const orderQueueCounts = computed(() => ({
|
||||
all: queryMatchedOrders.value.length,
|
||||
fee: queryMatchedOrders.value.filter((order) => order.businessSummary.customerFeeConfirmationRequired).length,
|
||||
customs: queryMatchedOrders.value.filter((order) => Boolean(eligibleCustomsDetail(order))).length,
|
||||
draft: queryMatchedOrders.value.filter((order) => order.status === 'DRAFT').length,
|
||||
}))
|
||||
const filteredOrders = computed(() => queryMatchedOrders.value.filter((order) => {
|
||||
if (activeOrderQueue.value === 'fee') return order.businessSummary.customerFeeConfirmationRequired
|
||||
if (activeOrderQueue.value === 'customs') return Boolean(eligibleCustomsDetail(order))
|
||||
if (activeOrderQueue.value === 'draft') return order.status === 'DRAFT'
|
||||
return true
|
||||
}))
|
||||
const pagedOrders = computed(() => {
|
||||
@@ -1945,8 +1947,9 @@ async function searchOrders(model: QueryModel) {
|
||||
<button type="button" :class="{ active: activeOrderQueue === 'all' }" @click="selectOrderQueue('all')">全部</button>
|
||||
<button type="button" :class="{ active: activeOrderQueue === 'fee' }" @click="selectOrderQueue('fee')">确认计费重<span v-if="orderQueueCounts.fee" class="queue-count">{{ orderQueueCounts.fee }}</span></button>
|
||||
<button type="button" :class="{ active: activeOrderQueue === 'customs' }" @click="selectOrderQueue('customs')">上传资料<span v-if="orderQueueCounts.customs" class="queue-count">{{ orderQueueCounts.customs }}</span></button>
|
||||
<button type="button" :class="{ active: activeOrderQueue === 'draft' }" @click="selectOrderQueue('draft')">草稿<span v-if="orderQueueCounts.draft" class="queue-count">{{ orderQueueCounts.draft }}</span></button>
|
||||
</nav>
|
||||
<span>共 {{ filteredOrders.length }} 条</span><span v-if="selectedOrderCount">已选 {{ selectedOrderCount }} 条</span>
|
||||
<span v-if="selectedOrderCount">已选 {{ selectedOrderCount }} 条</span>
|
||||
</template>
|
||||
<template #toolbar-actions>
|
||||
<el-popover placement="bottom-end" :width="320" trigger="click" popper-class="order-column-popper">
|
||||
|
||||
@@ -6,14 +6,23 @@ const source = readFileSync(new URL('./QuickOrderView.vue', import.meta.url), 'u
|
||||
const routesSource = readFileSync(new URL('../routes.ts', import.meta.url), 'utf8')
|
||||
const contractsSource = readFileSync(new URL('../../tenant-management/contracts.ts', import.meta.url), 'utf8')
|
||||
const orderViewSource = readFileSync(new URL('./FirstLegOrdersView.vue', import.meta.url), 'utf8')
|
||||
const createHubSource = readFileSync(new URL('./FirstLegOrderCreateHubView.vue', import.meta.url), 'utf8')
|
||||
|
||||
test('exposes quick order as an active first-leg route with its dedicated permission', () => {
|
||||
test('keeps quick order active but moves it from the menu into the head-leg create page', () => {
|
||||
assert.match(routesSource, /path: 'first-leg\/quick-order'[\s\S]*?release: 'active'[\s\S]*?permission: 'order:first-leg:quick-create'/)
|
||||
assert.match(contractsSource, /code: 'order:first-leg:quick-create'[\s\S]*?label: '快速下单'/)
|
||||
assert.match(contractsSource, /'order:first-leg:quick-create': \['merchant:order:quick-create', 'merchant:order:create'\]/)
|
||||
assert.match(contractsSource, /FRONTEND_PERMISSIONS_REQUIRING_ALL_BACKEND_CODES[\s\S]*?'order:first-leg:quick-create'/)
|
||||
assert.match(routesSource, /path: 'orders\/first-leg\/:draftId\/edit'[\s\S]*?name: 'first-leg-order-edit'[\s\S]*?permission: 'order:first-leg:edit'/)
|
||||
assert.match(orderViewSource, /store\.loadDraftDetail\(id\)/)
|
||||
assert.doesNotMatch(routesSource, /navigation: \{ key: 'first-leg-quick-order'/)
|
||||
assert.match(routesSource, /path: 'orders\/first-leg\/create'[\s\S]*?FirstLegOrderCreateHubView\.vue/)
|
||||
assert.match(createHubSource, />普通下单<\/button>/)
|
||||
assert.match(createHubSource, />快速下单<\/button>/)
|
||||
assert.match(createHubSource, /<h1>头程下单<\/h1>/)
|
||||
assert.match(createHubSource, /auth\.hasPermission\('order:first-leg:quick-create'\)/)
|
||||
assert.match(createHubSource, /<FirstLegOrdersView v-if="activeMode === 'standard'" create-page \/>/)
|
||||
assert.match(createHubSource, /<QuickOrderView v-else \/>/)
|
||||
})
|
||||
|
||||
test('keeps the primary quick-order table focused on inbound code policy, channel, destination and cargo', () => {
|
||||
|
||||
@@ -398,7 +398,7 @@ async function submitQuickOrder() {
|
||||
else {
|
||||
activeBatchKey.value = ''
|
||||
await Promise.all(successes.map((item) => store.loadDraftDetail(item.draftId)))
|
||||
workspace.close(['first-leg-quick-order'])
|
||||
workspace.close(['first-leg-quick-order', 'first-leg-order-create'])
|
||||
await router.push({ name: 'first-leg-orders' })
|
||||
ElMessage.success(`已生成 ${successes.length} 张草稿单,提交后将生成入仓码`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user