105 lines
13 KiB
Vue
105 lines
13 KiB
Vue
<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>
|