优化
This commit is contained in:
@@ -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