Files
client-web/src/modules/dashboard/views/DashboardView.vue
T
2026-10-20 14:52:22 +08:00

105 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from 'vue'
import { ArrowRight, Forklift, PackageOpen, RefreshCcw, Ship, ShoppingCart, Tags, Truck, Warehouse } from '@lucide/vue'
import { useAuthStore } from '@/modules/auth/public'
import { FirstLegOrdersView } from '@/modules/first-leg/public'
const auth = useAuthStore()
const primaryActions = computed(() => [
{ title: '头程下单', description: '国内提货 · 海运订舱 · 清关派送', icon: Ship, route: '/orders/first-leg/create', permission: 'order:first-leg:create', tone: 'blue', featured: true },
{ title: '退货换标', description: '退件接收 · 标签更换 · 重新入仓', icon: RefreshCcw, route: '/overseas-warehouse/return-relabel/create', permission: 'merchant:overseas-warehouse:return-order:create', tone: 'purple', featured: false },
{ title: '美国本土卡', description: '港口提柜 · 仓到仓 · 仓到门', icon: Truck, route: '/cross-border/domestic-trucking/create', permission: 'merchant:cross-border:domestic-trucking:create', tone: 'green', featured: false },
{ title: '美本土散板', description: '拼车拼载 · 打盘服务 · 灵活派送', icon: Forklift, route: '/cross-border/pallet-shipping/create', permission: 'merchant:cross-border:pallet-shipping:create', tone: 'cyan', featured: false },
{ title: '海外采购', description: '采购代购 · 验货质检 · 集中发货', icon: ShoppingCart, route: '/cross-border/procurement/create', permission: 'merchant:cross-border:procurement:create', tone: 'orange', featured: false },
{ title: '贴标申请', description: '产品建档 · 标签上传 · 批量贴标', icon: Tags, route: '/overseas-warehouse/labeling/create', permission: 'merchant:overseas-warehouse:relabel:create', tone: 'rose', featured: false },
{ title: '仓储转运', description: '仓储管理 · 货物转运', icon: Warehouse, route: '/overseas-warehouse/storage-transfer/create', permission: 'merchant:overseas-warehouse:transfer:create', tone: 'teal', featured: false, actionLabel: '开始办理' },
{ title: '货物废弃', description: '废弃申请 · 处理进度', icon: PackageOpen, route: '/overseas-warehouse/cargo-disposal/create', permission: 'merchant:overseas-warehouse:discard:create', tone: 'brown', featured: false, actionLabel: '开始办理' },
].filter((action) => auth.hasPermission(action.permission)))
</script>
<template>
<div class="client-dashboard">
<header class="business-intro">
<h1>选择业务,快速下单</h1>
</header>
<section class="quick-actions" aria-label="业务选择">
<RouterLink v-for="(action, index) in primaryActions" :key="action.route" :to="action.route" class="quick-action" :class="[`quick-action--${action.tone}`, { 'quick-action--featured': action.featured }]" :style="{ '--entry-index': index }">
<span class="action-icon"><component :is="action.icon" :size="25" :stroke-width="2.2" aria-hidden="true" /></span>
<span class="action-copy"><strong>{{ action.title }}</strong><small>{{ action.description }}</small></span>
<span class="action-enter"><span>{{ action.actionLabel ?? '开始下单' }}</span><ArrowRight :size="15" aria-hidden="true" /></span>
</RouterLink>
<p v-if="!primaryActions.length" class="no-actions">当前账号暂无常用业务权限,请通过已授权菜单办理业务,或联系企业管理员。</p>
</section>
<FirstLegOrdersView v-if="auth.hasPermission('order:first-leg:list')" :show-create-action="false" class="dashboard-order-list" />
<section v-else class="orders-permission-empty">
<strong>暂无头程订单查看权限</strong>
<span>需要查看企业订单时,请联系企业管理员开通。</span>
</section>
</div>
</template>
<style scoped>
.client-dashboard { display: flex; min-width: 0; container-name: dashboard-content; container-type: inline-size; flex-direction: column; gap: 14px; padding: 0 8px 18px; color: var(--navy); animation: dashboard-enter .3s ease-out both; }
.business-intro { padding-top: 0; }
.business-intro h1 { margin: 0; color: #172033; font-size: 27px; font-weight: 750; letter-spacing: -.5px; line-height: 1.3; }
.business-intro p { margin: 5px 0 0; color: #7a8495; font-size: 13px; line-height: 1.55; }
.quick-actions { display: grid; grid-template-columns: repeat(8, minmax(150px, 1fr)); gap: 10px; overflow-x: auto; padding: 0 0 6px; scroll-snap-type: x proximity; scrollbar-color: #bdc9dc transparent; scrollbar-width: thin; }
.quick-actions::-webkit-scrollbar { height: 7px; }
.quick-actions::-webkit-scrollbar-thumb { border-radius: 999px; background: #bdc9dc; }
.quick-actions::-webkit-scrollbar-track { background: transparent; }
.quick-action { scroll-snap-align: start; }
.quick-action { --action-a: #2563eb; --action-b: #3730a3; --action-soft: rgba(37, 99, 235, .18); position: relative; isolation: isolate; display: flex; min-width: 0; min-height: 190px; overflow: hidden; align-items: center; flex-direction: column; padding: 21px 10px 18px; border: 1px solid rgba(148, 163, 184, .2); border-radius: 17px; background: linear-gradient(155deg, #fff 8%, rgba(248, 250, 252, .96) 100%); color: inherit; text-align: center; text-decoration: none; box-shadow: 0 12px 34px rgba(15, 23, 42, .07); backdrop-filter: blur(10px); animation: action-enter .4s cubic-bezier(.22, 1, .36, 1) both; animation-delay: calc(var(--entry-index) * 55ms); transition: border-color .35s ease, box-shadow .35s ease, transform .35s cubic-bezier(.22, 1, .36, 1); }
.quick-action::before { position: absolute; width: 120px; height: 120px; border-radius: 50%; background: radial-gradient(circle, var(--action-soft), transparent 70%); content: ''; right: -62px; top: -66px; transition: transform .4s ease; }
.quick-action::after { position: absolute; right: 20%; bottom: 0; left: 20%; height: 3px; border-radius: 99px 99px 0 0; background: linear-gradient(90deg, var(--action-a), var(--action-b)); content: ''; opacity: .55; transform: scaleX(.35); transition: all .35s ease; }
.quick-action--blue { --action-a: #2563eb; --action-b: #3730a3; --action-soft: rgba(37, 99, 235, .18); }
.quick-action--purple { --action-a: #8b5cf6; --action-b: #c026d3; --action-soft: rgba(139, 92, 246, .18); }
.quick-action--green { --action-a: #059669; --action-b: #0f766e; --action-soft: rgba(5, 150, 105, .18); }
.quick-action--cyan { --action-a: #0891b2; --action-b: #0e7490; --action-soft: rgba(8, 145, 178, .18); }
.quick-action--orange { --action-a: #f59e0b; --action-b: #dc6b12; --action-soft: rgba(245, 158, 11, .2); }
.quick-action--rose { --action-a: #e11d48; --action-b: #be185d; --action-soft: rgba(225, 29, 72, .17); }
.quick-action--teal { --action-a: #0f766e; --action-b: #0e7490; --action-soft: rgba(15, 118, 110, .18); }
.quick-action--brown { --action-a: #b45309; --action-b: #c2410c; --action-soft: rgba(180, 83, 9, .18); }
.quick-action:hover { border-color: color-mix(in srgb, var(--action-a) 35%, transparent); box-shadow: 0 24px 54px rgba(15, 23, 42, .14), 0 0 0 1px color-mix(in srgb, var(--action-a) 16%, transparent); transform: translateY(-9px); }
.quick-action:hover::before { transform: scale(1.25); }
.quick-action:hover::after { right: 12%; left: 12%; opacity: 1; transform: scaleX(1); }
.action-icon { position: relative; display: grid; width: 58px; height: 58px; flex: 0 0 auto; place-items: center; margin-bottom: 16px; border-radius: 18px; background: linear-gradient(135deg, var(--action-a), var(--action-b)); color: #fff; box-shadow: 0 11px 22px color-mix(in srgb, var(--action-a) 25%, transparent); transition: transform .35s ease; }
.action-icon::before { position: absolute; inset: 7px; border-radius: 12px; background: linear-gradient(135deg, rgba(255, 255, 255, .32), transparent 42%); content: ''; pointer-events: none; }
.action-icon::after { position: absolute; border: 1px solid color-mix(in srgb, var(--action-a) 20%, transparent); border-radius: 22px; content: ''; inset: -5px; }
.quick-action:hover .action-icon { transform: translateY(-3px) scale(1.06) rotate(-3deg); }
.action-copy { position: relative; display: flex; min-width: 0; width: 100%; align-items: center; flex-direction: column; }
.action-copy strong { color: #172033; font-size: 16px; font-weight: 800; line-height: 1.35; white-space: nowrap; }
.action-copy small { overflow: hidden; width: 100%; margin-top: 6px; color: #718096; font-size: 11px; line-height: 1.5; text-overflow: ellipsis; white-space: nowrap; }
.action-enter { position: relative; display: inline-flex; align-items: center; gap: 6px; margin-top: auto; color: var(--action-a); font-size: 11px; font-weight: 750; }
.action-enter svg { transition: transform .25s ease; }
.quick-action:hover .action-enter svg { transform: translateX(4px); }
.quick-action--featured { border-color: rgba(49, 126, 255, .62); background: radial-gradient(circle at 8% 2%, rgba(34, 211, 238, .23), transparent 32%), radial-gradient(circle at 100% 100%, rgba(79, 70, 229, .22), transparent 44%), linear-gradient(145deg, #f2fbff 0%, #f8faff 45%, #eef2ff 100%); box-shadow: 0 20px 48px rgba(37, 99, 235, .2), 0 0 0 1px rgba(255, 255, 255, .88) inset, 0 0 34px rgba(34, 211, 238, .1); }
.quick-action--featured::before { width: 220px; height: 220px; right: -88px; top: -112px; background: radial-gradient(circle, rgba(34, 211, 238, .35) 0%, rgba(59, 130, 246, .2) 34%, transparent 69%); filter: blur(1px); animation: featured-aura 5s ease-in-out infinite; }
.quick-action--featured::after { right: 8%; left: 8%; height: 4px; background: linear-gradient(90deg, #22d3ee 0%, #2563eb 48%, #4f46e5 100%); box-shadow: 0 -4px 16px rgba(37, 99, 235, .44), 0 -1px 6px rgba(34, 211, 238, .55); opacity: 1; transform: scaleX(1); }
.quick-action--featured:hover { border-color: rgba(34, 211, 238, .8); box-shadow: 0 30px 70px rgba(37, 99, 235, .27), 0 0 0 1px rgba(255, 255, 255, .95) inset, 0 0 40px rgba(34, 211, 238, .15); transform: translateY(-11px) scale(1.015); }
.quick-action--featured .action-icon { width: 64px; height: 64px; margin-bottom: 17px; border-radius: 20px; background: linear-gradient(145deg, #22c7df 0%, #2875ef 48%, #4338ca 100%); box-shadow: 0 16px 32px rgba(37, 99, 235, .37), 0 0 0 7px rgba(59, 130, 246, .09), 0 0 26px rgba(34, 211, 238, .24); }
.quick-action--featured .action-icon::after { inset: -6px; border-color: rgba(59, 130, 246, .25); border-radius: 25px; box-shadow: 0 0 0 1px rgba(34, 211, 238, .09); }
.quick-action--featured .action-copy strong { color: #15233e; font-size: 17px; }
.quick-action--featured .action-enter { overflow: hidden; padding: 8px 15px; border-radius: 999px; background: linear-gradient(100deg, #12b8d0 0%, #2563eb 52%, #4338ca 100%); box-shadow: 0 10px 24px rgba(37, 99, 235, .3), 0 0 18px rgba(34, 211, 238, .12); color: #fff; }
.quick-action--featured .action-enter::before { position: absolute; top: -60%; bottom: -60%; left: -55%; width: 38%; transform: rotate(18deg); background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent); content: ''; animation: featured-shine 3.6s ease-in-out infinite; }
.quick-action--featured .action-enter > * { position: relative; z-index: 1; }
.no-actions { margin: 0; padding: 20px; border: 1px solid var(--border); border-radius: 10px; background: #fff; color: #667085; font-size: 14px; }
.dashboard-order-list { min-width: 0; }
.orders-permission-empty { display: flex; min-height: 180px; align-items: center; justify-content: center; flex-direction: column; gap: 8px; border: 1px solid var(--border); border-radius: 10px; background: #fff; color: #7b8494; }
.orders-permission-empty strong { color: #475467; font-size: 15px; }
.orders-permission-empty span { font-size: 12px; }
.client-dashboard a:focus-visible { border-radius: 6px; outline: 2px solid var(--primary); outline-offset: 4px; }
@keyframes dashboard-enter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
@keyframes action-enter { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: translateY(0); } }
@keyframes featured-aura { 0%, 100% { opacity: .86; transform: translate3d(0, 0, 0) scale(1); } 50% { opacity: 1; transform: translate3d(-10px, 9px, 0) scale(1.08); } }
@keyframes featured-shine { 0%, 55% { left: -55%; opacity: 0; } 65% { opacity: 1; } 88%, 100% { left: 125%; opacity: 0; } }
@container dashboard-content (max-width: 1180px) { .quick-actions { grid-template-columns: repeat(8, minmax(178px, 1fr)); } }
@media (max-width: 720px) { .client-dashboard { padding: 0; gap: 16px; } .business-intro h1 { font-size: 25px; } .quick-actions { grid-template-columns: repeat(2, minmax(150px, 1fr)); } }
@media (max-width: 420px) { .quick-actions { grid-template-columns: repeat(8, minmax(168px, 1fr)); } }
@media (prefers-reduced-motion: reduce) { .client-dashboard, .quick-action, .quick-action--featured::before, .quick-action--featured .action-enter::before { animation: none; } .quick-action, .quick-action::before, .quick-action::after, .action-icon, .action-enter svg { transition: none; } }
</style>