style: 统一压感卡片阴影并精简充值/监控页交互
All checks were successful
Build and Deploy Front / deploy (push) Successful in 6m41s
All checks were successful
Build and Deploy Front / deploy (push) Successful in 6m41s
对齐登录、工作台、H5、官网与充值页的轻压 inset;监控改胶囊 Tab 减闪;充值页去步骤条并收窄布局。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
07b71ca565
commit
fbf1064ab8
@ -2,9 +2,14 @@
|
||||
<div
|
||||
ref="rootRef"
|
||||
class="t-tabs"
|
||||
:class="[`t-tabs--${size}`]"
|
||||
:class="[`t-tabs--${size}`, { 't-tabs--dragging': isDragging }]"
|
||||
role="tablist"
|
||||
:aria-label="ariaLabel"
|
||||
@pointerdown="onPointerDown"
|
||||
@pointermove="onPointerMove"
|
||||
@pointerup="onPointerUp"
|
||||
@pointercancel="onPointerUp"
|
||||
@click.capture="onClickCapture"
|
||||
>
|
||||
<span ref="pillRef" class="t-tabs-pill" aria-hidden="true" />
|
||||
<button
|
||||
@ -59,6 +64,14 @@ const emit = defineEmits<{
|
||||
const rootRef = ref<HTMLElement | null>(null)
|
||||
const pillRef = ref<HTMLElement | null>(null)
|
||||
const tabRefs = new Map<string, HTMLElement>()
|
||||
const isDragging = ref(false)
|
||||
|
||||
const DRAG_THRESHOLD = 6
|
||||
let dragPointerId: number | null = null
|
||||
let dragStartX = 0
|
||||
let dragScrollLeft = 0
|
||||
let didDrag = false
|
||||
let suppressClick = false
|
||||
|
||||
function setTabRef(key: string, el: unknown) {
|
||||
const node = el as HTMLElement | null
|
||||
@ -66,6 +79,12 @@ function setTabRef(key: string, el: unknown) {
|
||||
else tabRefs.delete(key)
|
||||
}
|
||||
|
||||
function canScroll() {
|
||||
const el = rootRef.value
|
||||
if (!el) return false
|
||||
return el.scrollWidth > el.clientWidth + 1
|
||||
}
|
||||
|
||||
function movePill(animate: boolean) {
|
||||
const pill = pillRef.value
|
||||
const tab = tabRefs.get(props.modelValue)
|
||||
@ -88,7 +107,21 @@ function movePill(animate: boolean) {
|
||||
pill.style.width = `${width}px`
|
||||
}
|
||||
|
||||
function scrollActiveIntoView() {
|
||||
const root = rootRef.value
|
||||
const tab = tabRefs.get(props.modelValue)
|
||||
if (!root || !tab || !canScroll()) return
|
||||
const left = tab.offsetLeft - 8
|
||||
const right = tab.offsetLeft + tab.offsetWidth + 8
|
||||
if (left < root.scrollLeft) {
|
||||
root.scrollTo({ left: Math.max(0, left), behavior: 'smooth' })
|
||||
} else if (right > root.scrollLeft + root.clientWidth) {
|
||||
root.scrollTo({ left: right - root.clientWidth, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
function onSelect(key: string) {
|
||||
if (suppressClick) return
|
||||
if (key === props.modelValue) return
|
||||
const item = props.items.find((i) => i.key === key)
|
||||
if (item?.disabled) return
|
||||
@ -96,6 +129,64 @@ function onSelect(key: string) {
|
||||
emit('change', key)
|
||||
}
|
||||
|
||||
function onPointerDown(e: PointerEvent) {
|
||||
if (e.button !== 0 || !canScroll()) return
|
||||
const root = rootRef.value
|
||||
if (!root) return
|
||||
// 仅记录起点;真正拖动超阈值后再 capture,避免吞掉点击选中
|
||||
dragPointerId = e.pointerId
|
||||
dragStartX = e.clientX
|
||||
dragScrollLeft = root.scrollLeft
|
||||
didDrag = false
|
||||
isDragging.value = false
|
||||
}
|
||||
|
||||
function onPointerMove(e: PointerEvent) {
|
||||
if (dragPointerId == null || e.pointerId !== dragPointerId) return
|
||||
const root = rootRef.value
|
||||
if (!root) return
|
||||
const dx = e.clientX - dragStartX
|
||||
if (!didDrag) {
|
||||
if (Math.abs(dx) < DRAG_THRESHOLD) return
|
||||
didDrag = true
|
||||
isDragging.value = true
|
||||
try {
|
||||
root.setPointerCapture(e.pointerId)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
root.scrollLeft = dragScrollLeft - dx
|
||||
}
|
||||
|
||||
function onPointerUp(e: PointerEvent) {
|
||||
if (dragPointerId == null || e.pointerId !== dragPointerId) return
|
||||
const root = rootRef.value
|
||||
const wasDragging = didDrag
|
||||
if (root && wasDragging) {
|
||||
try {
|
||||
root.releasePointerCapture(e.pointerId)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
dragPointerId = null
|
||||
didDrag = false
|
||||
isDragging.value = false
|
||||
if (wasDragging) {
|
||||
suppressClick = true
|
||||
window.setTimeout(() => {
|
||||
suppressClick = false
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
function onClickCapture(e: MouseEvent) {
|
||||
if (!suppressClick) return
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
movePill(false)
|
||||
}
|
||||
@ -103,7 +194,10 @@ function onWindowResize() {
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => movePill(false))
|
||||
nextTick(() => {
|
||||
movePill(false)
|
||||
scrollActiveIntoView()
|
||||
})
|
||||
if (typeof ResizeObserver !== 'undefined' && rootRef.value) {
|
||||
resizeObserver = new ResizeObserver(() => movePill(false))
|
||||
resizeObserver.observe(rootRef.value)
|
||||
@ -122,6 +216,7 @@ watch(
|
||||
async () => {
|
||||
await nextTick()
|
||||
movePill(true)
|
||||
scrollActiveIntoView()
|
||||
}
|
||||
)
|
||||
|
||||
@ -130,6 +225,7 @@ watch(
|
||||
async () => {
|
||||
await nextTick()
|
||||
movePill(false)
|
||||
scrollActiveIntoView()
|
||||
}
|
||||
)
|
||||
|
||||
@ -162,10 +258,21 @@ defineExpose({ syncPill: () => movePill(false) })
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none;
|
||||
box-sizing: border-box;
|
||||
touch-action: pan-x;
|
||||
user-select: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&--dragging {
|
||||
cursor: grabbing;
|
||||
|
||||
.t-tab {
|
||||
pointer-events: none;
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.t-tab {
|
||||
|
||||
@ -423,7 +423,9 @@ export default {
|
||||
'warehouse.inbound.tip.currentWarehouse': 'Notice belongs to the current warehouse; switch it from the top bar',
|
||||
'warehouse.inbound.tip.markReference': 'Optional; usually the tracking number or master mark for receiving checks',
|
||||
'warehouse.inbound.tip.inboundType': 'Normal or return inbound; used for later reporting',
|
||||
'warehouse.inbound.tip.boxSkuDetail': 'Maintain SKUs and planned qty per box; add one box or five at once',
|
||||
'warehouse.inbound.tip.boxSkuDetail': 'Maintain SKUs and planned qty per box; “Copy & add” duplicates weight, size, SKU and planned qty from the current box',
|
||||
'warehouse.inbound.tip.copyAddBox': 'Copies weight, size, SKU and planned qty from the selected box',
|
||||
'warehouse.inbound.form.batchAddCount': 'Boxes to add',
|
||||
'warehouse.inbound.tip.markBoxNo': 'Per-box mark / box number (optional)',
|
||||
'warehouse.inbound.tip.skuLines': 'Select at least one SKU per box; a blank row is added after selection',
|
||||
'warehouse.inbound.form.markReference': 'Mark Ref',
|
||||
@ -449,6 +451,9 @@ export default {
|
||||
'warehouse.inbound.form.autoAddSkuRow': 'A new row is added after you select a SKU',
|
||||
'warehouse.inbound.actions.addBox': 'Add box',
|
||||
'warehouse.inbound.actions.addBox5': 'Add 5 boxes',
|
||||
'warehouse.inbound.actions.addBoxN': 'Add {n} boxes',
|
||||
'warehouse.inbound.actions.copyAddBox': 'Copy & add',
|
||||
'warehouse.inbound.actions.copyAddBoxTitle': 'Copy current box',
|
||||
'warehouse.inbound.actions.removeBox': 'Remove boxes',
|
||||
'warehouse.inbound.actions.removeCurrentBox': 'Remove current box',
|
||||
'warehouse.inbound.actions.addSku': 'Add SKU line',
|
||||
@ -458,7 +463,7 @@ export default {
|
||||
'warehouse.inbound.drawer.detail': 'Inbound Detail',
|
||||
'warehouse.inbound.box': 'Box',
|
||||
'warehouse.inbound.line.skuCode': 'SKU',
|
||||
'warehouse.inbound.line.plannedQty': 'Planned',
|
||||
'warehouse.inbound.line.plannedQty': 'Planned Qty',
|
||||
'warehouse.inbound.line.receivedQty': 'Received',
|
||||
'warehouse.inbound.line.binLocation': 'Putaway Bin',
|
||||
'warehouse.inbound.form.selectBin': 'Select bin',
|
||||
@ -4666,7 +4671,7 @@ export default {
|
||||
|
||||
// User - Recharge
|
||||
'user.recharge.deposit.panel.subtitle': 'Fund Recharge',
|
||||
'user.recharge.deposit.panel.title': 'Complete Recharge in 3 Steps',
|
||||
'user.recharge.deposit.panel.title': 'Account Recharge',
|
||||
'user.recharge.deposit.panel.desc': 'Select Amount · Upload Voucher · Wait for Review',
|
||||
'user.recharge.deposit.steps.selectAmount.title': 'Select Amount',
|
||||
'user.recharge.deposit.steps.selectAmount.desc': 'Select amount and confirm payment method',
|
||||
@ -4683,10 +4688,13 @@ export default {
|
||||
'user.recharge.deposit.tips.item2': 'Upload clear and complete payment screenshot',
|
||||
'user.recharge.deposit.tips.item3': 'SMS/In-site message reminder after funds arrive',
|
||||
'user.recharge.deposit.form.currentAmount': 'Current recharge amount',
|
||||
'user.recharge.deposit.form.stepChip': 'Step {step} · {tip}',
|
||||
'user.recharge.deposit.form.stepChip': 'Step {step}/3',
|
||||
'user.recharge.deposit.form.selectAmount': 'Select Amount',
|
||||
'user.recharge.deposit.form.custom': 'Custom',
|
||||
'user.recharge.deposit.form.inputAmount': 'Enter Amount',
|
||||
'user.recharge.deposit.form.customPlaceholder': 'Enter recharge amount',
|
||||
'user.recharge.deposit.form.customConfirm': 'Confirm',
|
||||
'user.recharge.deposit.form.customCancel': 'Cancel',
|
||||
'user.recharge.deposit.form.paymentMethod': 'Payment Method',
|
||||
'user.recharge.deposit.form.qrTip': 'Click QR code to zoom in and view details',
|
||||
'user.recharge.deposit.form.uploadVoucher': 'Upload Voucher',
|
||||
|
||||
@ -423,7 +423,9 @@ export default {
|
||||
'warehouse.inbound.tip.currentWarehouse': '预报归属当前仓库;可在顶部切换仓库',
|
||||
'warehouse.inbound.tip.markReference': '选填;一般填物流快递单号或主唛头,便于到仓核对',
|
||||
'warehouse.inbound.tip.inboundType': '普通入库或退货入库,影响后续统计分类',
|
||||
'warehouse.inbound.tip.boxSkuDetail': '按箱维护 SKU 与计划数量;可加箱或一次加 5 箱',
|
||||
'warehouse.inbound.tip.boxSkuDetail': '按箱维护 SKU 与计划数量;「复制加箱」可按当前箱批量复制重量、尺寸、SKU 与计划',
|
||||
'warehouse.inbound.tip.copyAddBox': '按当前选中箱复制重量、尺寸、SKU 与计划数量',
|
||||
'warehouse.inbound.form.batchAddCount': '添加箱数',
|
||||
'warehouse.inbound.tip.markBoxNo': '单箱唛头/箱号,选填',
|
||||
'warehouse.inbound.tip.skuLines': '每箱至少选一个 SKU;选完后自动新增空行',
|
||||
'warehouse.inbound.form.markReference': '唛头',
|
||||
@ -449,6 +451,9 @@ export default {
|
||||
'warehouse.inbound.form.autoAddSkuRow': '选择 SKU 后会自动新增下一行',
|
||||
'warehouse.inbound.actions.addBox': '加箱',
|
||||
'warehouse.inbound.actions.addBox5': '一次加 5 箱',
|
||||
'warehouse.inbound.actions.addBoxN': '添加 {n} 箱',
|
||||
'warehouse.inbound.actions.copyAddBox': '复制加箱',
|
||||
'warehouse.inbound.actions.copyAddBoxTitle': '复制当前箱',
|
||||
'warehouse.inbound.actions.removeBox': '移除箱子',
|
||||
'warehouse.inbound.actions.removeCurrentBox': '删除当前箱',
|
||||
'warehouse.inbound.actions.addSku': '选择库内 SKU',
|
||||
@ -458,7 +463,7 @@ export default {
|
||||
'warehouse.inbound.drawer.detail': '入库详情',
|
||||
'warehouse.inbound.box': '箱',
|
||||
'warehouse.inbound.line.skuCode': 'SKU',
|
||||
'warehouse.inbound.line.plannedQty': '计划',
|
||||
'warehouse.inbound.line.plannedQty': '计划数量',
|
||||
'warehouse.inbound.line.receivedQty': '实收',
|
||||
'warehouse.inbound.line.binLocation': '上架库位',
|
||||
'warehouse.inbound.form.selectBin': '请选择库位',
|
||||
@ -4690,7 +4695,7 @@ export default {
|
||||
|
||||
// 用户 - 充值
|
||||
'user.recharge.deposit.panel.subtitle': '资金充值',
|
||||
'user.recharge.deposit.panel.title': '3 步完成充值',
|
||||
'user.recharge.deposit.panel.title': '账户充值',
|
||||
'user.recharge.deposit.panel.desc': '选择金额 · 上传凭证 · 等待审核',
|
||||
'user.recharge.deposit.steps.selectAmount.title': '选择充值金额',
|
||||
'user.recharge.deposit.steps.selectAmount.desc': '选择金额并确认支付方式',
|
||||
@ -4707,10 +4712,13 @@ export default {
|
||||
'user.recharge.deposit.tips.item2': '上传清晰完整的支付截图',
|
||||
'user.recharge.deposit.tips.item3': '资金到账后会短信/站内信提醒',
|
||||
'user.recharge.deposit.form.currentAmount': '当前充值金额',
|
||||
'user.recharge.deposit.form.stepChip': '步骤 {step} · {tip}',
|
||||
'user.recharge.deposit.form.stepChip': '步骤 {step}/3',
|
||||
'user.recharge.deposit.form.selectAmount': '选择金额',
|
||||
'user.recharge.deposit.form.custom': '自定义',
|
||||
'user.recharge.deposit.form.inputAmount': '输入金额',
|
||||
'user.recharge.deposit.form.customPlaceholder': '请输入充值金额',
|
||||
'user.recharge.deposit.form.customConfirm': '确定',
|
||||
'user.recharge.deposit.form.customCancel': '取消',
|
||||
'user.recharge.deposit.form.paymentMethod': '支付方式',
|
||||
'user.recharge.deposit.form.qrTip': '点击二维码可放大查看详情',
|
||||
'user.recharge.deposit.form.uploadVoucher': '上传凭证',
|
||||
|
||||
@ -204,13 +204,16 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.h5-login-card {
|
||||
background: var(--app-acrylic-bg);
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
/* 对齐官网/PC 登录 pressed:白底 + inset 凹陷 */
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 28px 20px 22px;
|
||||
box-shadow: none;
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
box-shadow:
|
||||
inset 0 2px 8px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
.h5-login-card__brand {
|
||||
@ -262,11 +265,27 @@ onMounted(() => {
|
||||
.h5-login-form :deep(.arco-input-wrapper),
|
||||
.h5-login-form :deep(.arco-input-password) {
|
||||
border-radius: 12px;
|
||||
background: var(--app-acrylic-bg);
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
background: #f0f3f4;
|
||||
border: none;
|
||||
min-height: 48px;
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
box-shadow: inset 0 2px 8px rgba(15, 23, 42, 0.08);
|
||||
transition: box-shadow 0.2s ease, background 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: #f0f3f4;
|
||||
box-shadow: inset 0 3px 10px rgba(15, 23, 42, 0.1);
|
||||
}
|
||||
|
||||
&.arco-input-wrapper-focus,
|
||||
&.arco-input-focus {
|
||||
background: #f0f3f4;
|
||||
border: none;
|
||||
box-shadow:
|
||||
inset 0 3px 10px rgba(15, 23, 42, 0.1),
|
||||
0 0 0 3px color-mix(in srgb, var(--app-primary-color) 12%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.h5-login-error {
|
||||
|
||||
@ -468,8 +468,9 @@ onMounted(() => {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--h5-border, #e5e6eb);
|
||||
background: var(--h5-surface, #fff);
|
||||
border: none;
|
||||
background: var(--h5-well-bg, #f0f3f4);
|
||||
box-shadow: var(--h5-well-shadow, inset 0 2px 5px rgba(15, 23, 42, 0.08));
|
||||
color: var(--h5-text-2, #4e5969);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
@ -518,8 +519,8 @@ onMounted(() => {
|
||||
padding: 14px;
|
||||
border-radius: var(--h5-radius, 16px);
|
||||
background: var(--h5-surface, #fff);
|
||||
border: 1px solid var(--h5-border-soft, #f2f3f5);
|
||||
box-shadow: var(--h5-shadow, 0 4px 16px rgba(15, 23, 42, 0.05));
|
||||
border: none;
|
||||
box-shadow: var(--h5-shadow);
|
||||
}
|
||||
|
||||
.h5-mon__section-head {
|
||||
@ -582,8 +583,9 @@ onMounted(() => {
|
||||
min-height: 76px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--h5-border, #e5e6eb);
|
||||
background: transparent;
|
||||
border: none;
|
||||
background: var(--h5-well-bg, #f0f3f4);
|
||||
box-shadow: var(--h5-well-shadow, inset 0 2px 5px rgba(15, 23, 42, 0.08));
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
@ -770,8 +772,9 @@ onMounted(() => {
|
||||
gap: 6px;
|
||||
padding: 12px 8px;
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--h5-border, #e5e6eb);
|
||||
background: var(--h5-well-bg, #f0f3f4);
|
||||
border: none;
|
||||
box-shadow: var(--h5-well-shadow, inset 0 2px 5px rgba(15, 23, 42, 0.08));
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
|
||||
@ -12,8 +12,16 @@
|
||||
--h5-border-soft: #f2f3f5;
|
||||
--h5-radius: 16px;
|
||||
--h5-radius-sm: 12px;
|
||||
--h5-shadow: 0 4px 16px rgba(15, 23, 42, 0.05);
|
||||
--h5-shadow-lift: 0 8px 24px rgba(15, 23, 42, 0.08);
|
||||
/* pressed:外层白卡 / 内层浅井(阴影偏淡) */
|
||||
--h5-shadow:
|
||||
inset 0 2px 8px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
--h5-shadow-lift:
|
||||
inset 0 3px 10px rgba(15, 23, 42, 0.09),
|
||||
inset 0 1px 2px rgba(15, 23, 42, 0.05);
|
||||
--h5-well-bg: #f7f8fa;
|
||||
--h5-well-shadow: inset 0 2px 4px rgba(15, 23, 42, 0.06);
|
||||
--h5-well-shadow-deep: inset 0 3px 6px rgba(15, 23, 42, 0.08);
|
||||
--h5-warning: #ff7d00;
|
||||
--h5-warning-bg: #fff7e8;
|
||||
--h5-danger: #f53f3f;
|
||||
|
||||
@ -479,8 +479,9 @@ onMounted(() => {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--h5-border, #e5e6eb);
|
||||
background: var(--h5-surface, #fff);
|
||||
border: none;
|
||||
background: var(--h5-well-bg, #f0f3f4);
|
||||
box-shadow: var(--h5-well-shadow, inset 0 2px 5px rgba(15, 23, 42, 0.08));
|
||||
color: var(--h5-text-2, #4e5969);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
@ -491,7 +492,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background: #f7f8fa;
|
||||
box-shadow: var(--h5-well-shadow-deep, inset 0 3px 8px rgba(15, 23, 42, 0.1));
|
||||
}
|
||||
}
|
||||
|
||||
@ -511,8 +512,8 @@ onMounted(() => {
|
||||
padding: 14px;
|
||||
border-radius: var(--h5-radius, 16px);
|
||||
background: var(--h5-surface, #fff);
|
||||
border: 1px solid var(--h5-border-soft, #f2f3f5);
|
||||
box-shadow: var(--h5-shadow, 0 4px 16px rgba(15, 23, 42, 0.05));
|
||||
border: none;
|
||||
box-shadow: var(--h5-shadow);
|
||||
}
|
||||
|
||||
.h5-wp__section-head {
|
||||
@ -570,9 +571,10 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
min-height: 76px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--h5-border, #e5e6eb);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
background: var(--h5-well-bg, #f0f3f4);
|
||||
box-shadow: var(--h5-well-shadow, inset 0 2px 5px rgba(15, 23, 42, 0.08));
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
@ -660,8 +662,9 @@ onMounted(() => {
|
||||
min-height: 64px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--h5-border, #e5e6eb);
|
||||
background: transparent;
|
||||
border: none;
|
||||
background: var(--h5-well-bg, #f0f3f4);
|
||||
box-shadow: var(--h5-well-shadow, inset 0 2px 5px rgba(15, 23, 42, 0.08));
|
||||
}
|
||||
|
||||
.h5-wp__stat-main {
|
||||
|
||||
@ -75,15 +75,11 @@ function clearPriceContext() {
|
||||
gap: 10px;
|
||||
padding: 12px 12px 12px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.78);
|
||||
background: linear-gradient(
|
||||
155deg,
|
||||
rgba(255, 255, 255, 0.92) 0%,
|
||||
rgba(255, 255, 255, 0.7) 100%
|
||||
);
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.9),
|
||||
0 6px 16px color-mix(in srgb, var(--app-primary-color) 10%, transparent);
|
||||
inset 0 2px 8px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@ -150,13 +146,13 @@ function clearPriceContext() {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
border-radius: 18px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
border: 1px solid rgba(255, 255, 255, 0.78);
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.85),
|
||||
0 8px 24px rgba(15, 23, 42, 0.05);
|
||||
inset 0 2px 8px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
|
||||
@ -439,13 +439,16 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.h5-login-card {
|
||||
background: var(--app-acrylic-bg);
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
/* 对齐官网/PC 登录 pressed:白底 + inset 凹陷 */
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 28px 20px 22px;
|
||||
box-shadow: none;
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
box-shadow:
|
||||
inset 0 2px 8px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
/* 品牌放进卡片:一个完整登录单元(Clerk / Vercel 鉴权卡常见) */
|
||||
@ -504,22 +507,26 @@ onMounted(async () => {
|
||||
.h5-login-form :deep(.arco-input-wrapper),
|
||||
.h5-login-form :deep(.arco-input-password) {
|
||||
border-radius: 12px;
|
||||
background: var(--app-acrylic-bg);
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
background: #f0f3f4;
|
||||
border: none;
|
||||
min-height: 48px;
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
transition:
|
||||
border-color 0.2s var(--login-ease),
|
||||
box-shadow 0.2s var(--login-ease),
|
||||
background 0.2s var(--login-ease);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
box-shadow: inset 0 2px 8px rgba(15, 23, 42, 0.08);
|
||||
transition: box-shadow 0.2s var(--login-ease), background 0.2s var(--login-ease);
|
||||
|
||||
&:hover {
|
||||
background: #f0f3f4;
|
||||
box-shadow: inset 0 3px 10px rgba(15, 23, 42, 0.1);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.arco-input-wrapper-focus,
|
||||
&.arco-input-focus {
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-primary-color) 14%, transparent);
|
||||
background: #f0f3f4;
|
||||
border: none;
|
||||
box-shadow:
|
||||
inset 0 3px 10px rgba(15, 23, 42, 0.1),
|
||||
0 0 0 3px color-mix(in srgb, var(--app-primary-color) 12%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1406,10 +1406,14 @@ onMounted(() => {
|
||||
|
||||
|
||||
.quote-form {
|
||||
background: #fff;
|
||||
border-radius: 18px;
|
||||
/* 对齐工作台 pressed:白底 + inset 凹陷 */
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
padding: 18px 16px 14px;
|
||||
box-shadow: 0 8px 28px rgba(15, 23, 42, 0.06);
|
||||
box-shadow:
|
||||
inset 0 2px 8px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
text-align: left;
|
||||
|
||||
&.is-collapsed {
|
||||
@ -2130,7 +2134,7 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
box-shadow: 0 8px 20px color-mix(in srgb, var(--app-primary-color) 28%, transparent);
|
||||
box-shadow: none;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.985);
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
<template>
|
||||
<div ref="logisticsMonitorRoot" class="logistics-monitor-container">
|
||||
<div class="logistics-monitor-card">
|
||||
<div class="logistics-monitor-header">
|
||||
<h2 class="logistics-monitor-title">{{ $t('monitor.logistics.pageTitle') }}</h2>
|
||||
<div class="monitor-topbar">
|
||||
<h2 class="logistics-monitor-title">{{ $t('monitor.logistics.pageTitle') }}</h2>
|
||||
<SlidingTabs
|
||||
v-model="monitorTab"
|
||||
class="monitor-top-tabs"
|
||||
:items="monitorTabItems"
|
||||
:aria-label="$t('monitor.logistics.pageTitle')"
|
||||
/>
|
||||
<div class="monitor-topbar__spacer" />
|
||||
<div v-show="monitorTab === 'order'" class="monitor-ops-toolbar">
|
||||
<span class="monitor-ops-toolbar__label">{{ $t('monitor.logistics.filter.opsPeriod') }}</span>
|
||||
<a-radio-group v-model="opsDimension" type="button" size="small">
|
||||
<a-radio value="day">{{ $t('monitor.logistics.filter.periodDay') }}</a-radio>
|
||||
<a-radio value="week">{{ $t('monitor.logistics.filter.periodWeek') }}</a-radio>
|
||||
<a-radio value="month">{{ $t('monitor.logistics.filter.periodMonth') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logistics-monitor-body">
|
||||
<a-spin :loading="overviewLoading" style="width: 100%">
|
||||
<a-tabs
|
||||
v-model:active-key="monitorTab"
|
||||
type="rounded"
|
||||
:animation="false"
|
||||
:justify="true"
|
||||
class="monitor-top-tabs"
|
||||
>
|
||||
<a-tab-pane key="order" :title="$t('monitor.logistics.tab.order')">
|
||||
<div class="monitor-ops-toolbar">
|
||||
<span class="monitor-ops-toolbar__label">{{ $t('monitor.logistics.filter.opsPeriod') }}</span>
|
||||
<a-radio-group v-model="opsDimension" type="button" size="small">
|
||||
<a-radio value="day">{{ $t('monitor.logistics.filter.periodDay') }}</a-radio>
|
||||
<a-radio value="week">{{ $t('monitor.logistics.filter.periodWeek') }}</a-radio>
|
||||
<a-radio value="month">{{ $t('monitor.logistics.filter.periodMonth') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="logistics-monitor-body">
|
||||
<a-spin :loading="overviewLoading" style="width: 100%; height: 100%">
|
||||
<div class="monitor-shell">
|
||||
<div class="monitor-tab-panels">
|
||||
<div v-show="monitorTab === 'order'" class="monitor-tab-panel">
|
||||
<a-row :gutter="[12, 12]" align="stretch" class="logistics-monitor-stats">
|
||||
<a-col
|
||||
v-for="card in opsStatCards"
|
||||
@ -34,16 +34,23 @@
|
||||
:lg="8"
|
||||
:xl="8"
|
||||
>
|
||||
<a-card hoverable class="stat-card" :body-style="{ padding: '12px', height: '100%' }">
|
||||
<a-card hoverable class="stat-card" :body-style="{ padding: '12px 14px 10px', height: '100%' }">
|
||||
<div class="stat-item">
|
||||
<div class="stat-item__head">
|
||||
<div class="stat-label">{{ $t(card.labelKey) }}</div>
|
||||
<div class="stat-item__icon stat-item__icon--primary">
|
||||
<img v-if="card.iconSrc" :src="card.iconSrc" alt="" class="stat-item__icon-img" />
|
||||
<icon-bar-chart v-else />
|
||||
<div class="stat-item__main">
|
||||
<div class="stat-item__meta">
|
||||
<img
|
||||
v-if="card.iconSrc"
|
||||
:src="card.iconSrc"
|
||||
alt=""
|
||||
class="stat-item__avatar"
|
||||
/>
|
||||
<icon-bar-chart v-else class="stat-item__avatar-icon" />
|
||||
<span class="stat-label">{{ $t(card.labelKey) }}</span>
|
||||
</div>
|
||||
<div class="stat-item__body">
|
||||
<span class="stat-value">{{ card.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-value">{{ card.value }}</div>
|
||||
<div v-if="card.growth.show" class="stat-footer">
|
||||
<div class="stat-trend" :class="card.growth.up ? 'up' : 'down'">
|
||||
<icon-arrow-up v-if="card.growth.up" />
|
||||
@ -67,20 +74,24 @@
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
>
|
||||
<a-card hoverable class="stat-card" :body-style="{ padding: '12px', height: '100%' }">
|
||||
<a-card hoverable class="stat-card" :body-style="{ padding: '12px 14px 10px', height: '100%' }">
|
||||
<div class="stat-item">
|
||||
<div class="stat-item__head">
|
||||
<div class="stat-label">{{ $t(card.labelKey) }}</div>
|
||||
<div
|
||||
class="stat-item__icon"
|
||||
:class="card.warning ? 'stat-item__icon--warning' : 'stat-item__icon--primary'"
|
||||
>
|
||||
<img v-if="card.iconSrc" :src="card.iconSrc" alt="" class="stat-item__icon-img" />
|
||||
<icon-bar-chart v-else />
|
||||
<div class="stat-item__main">
|
||||
<div class="stat-item__meta">
|
||||
<img
|
||||
v-if="card.iconSrc"
|
||||
:src="card.iconSrc"
|
||||
alt=""
|
||||
class="stat-item__avatar"
|
||||
/>
|
||||
<icon-bar-chart v-else class="stat-item__avatar-icon" />
|
||||
<span class="stat-label">{{ $t(card.labelKey) }}</span>
|
||||
</div>
|
||||
<div class="stat-item__body">
|
||||
<span class="stat-value" :class="{ 'stat-value--warning': card.warning }">
|
||||
{{ card.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-value" :class="{ 'stat-value--warning': card.warning }">
|
||||
{{ card.value }}
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
@ -100,9 +111,9 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</div>
|
||||
|
||||
<a-tab-pane key="customer" :title="$t('monitor.logistics.tab.customer')">
|
||||
<div v-show="monitorTab === 'customer'" class="monitor-tab-panel">
|
||||
<a-row :gutter="[12, 12]" align="stretch" class="logistics-monitor-stats">
|
||||
<a-col
|
||||
v-for="card in customerStatCards"
|
||||
@ -113,19 +124,29 @@
|
||||
:lg="12"
|
||||
:xl="6"
|
||||
>
|
||||
<a-card hoverable class="stat-card" :body-style="{ padding: '12px', height: '100%' }">
|
||||
<a-card hoverable class="stat-card" :body-style="{ padding: '12px 14px 10px', height: '100%' }">
|
||||
<div class="stat-item">
|
||||
<div class="stat-item__head">
|
||||
<div class="stat-label">{{ $t(card.labelKey) }}</div>
|
||||
<div
|
||||
class="stat-item__icon"
|
||||
:class="card.warning ? 'stat-item__icon--warning' : card.icon === 'online' ? 'stat-item__icon--online' : 'stat-item__icon--primary'"
|
||||
>
|
||||
<icon-user-group />
|
||||
<div class="stat-item__main">
|
||||
<div class="stat-item__meta">
|
||||
<icon-user-group
|
||||
class="stat-item__avatar-icon"
|
||||
:class="{
|
||||
'is-warning': card.warning,
|
||||
'is-online': card.icon === 'online',
|
||||
}"
|
||||
/>
|
||||
<span class="stat-label">{{ $t(card.labelKey) }}</span>
|
||||
</div>
|
||||
<div class="stat-item__body">
|
||||
<span class="stat-value" :class="{ 'stat-value--warning': card.warning }">
|
||||
{{ card.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-value" :class="{ 'stat-value--warning': card.warning }">{{ card.value }}</div>
|
||||
<div class="stat-footer">
|
||||
<div
|
||||
v-if="card.growth.show || card.hintKey || card.extraHintKey"
|
||||
class="stat-footer"
|
||||
>
|
||||
<div v-if="card.growth.show" class="stat-trend" :class="card.growth.up ? 'up' : 'down'">
|
||||
<icon-arrow-up v-if="card.growth.up" />
|
||||
<icon-arrow-down v-else />
|
||||
@ -186,9 +207,9 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</div>
|
||||
|
||||
<a-tab-pane key="track" :title="$t('monitor.logistics.tab.track')">
|
||||
<div v-show="monitorTab === 'track'" class="monitor-tab-panel">
|
||||
<a-row :gutter="[16, 16]" class="logistics-monitor-sign-rate-row">
|
||||
<a-col :span="24">
|
||||
<a-card class="chart-card">
|
||||
@ -258,10 +279,10 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -279,6 +300,7 @@
|
||||
type MonitorDimension,
|
||||
type MonitorOverview,
|
||||
} from '@/api/monitor';
|
||||
import SlidingTabs from '@/components/SlidingTabs/index.vue';
|
||||
import iconOrderNew from '@/assets/icons/workplace/order-new.svg?url';
|
||||
import iconPayment from '@/assets/icons/workplace/order-payment.svg?url';
|
||||
import iconGrowth from '@/assets/icons/workplace/order-growth.svg?url';
|
||||
@ -290,18 +312,26 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const monitorTabItems = computed(() => [
|
||||
{ key: 'order', label: t('monitor.logistics.tab.order') },
|
||||
{ key: 'customer', label: t('monitor.logistics.tab.customer') },
|
||||
{ key: 'track', label: t('monitor.logistics.tab.track') },
|
||||
]);
|
||||
|
||||
const overview = ref<MonitorOverview | null>(null);
|
||||
const overviewLoading = ref(true);
|
||||
const opsDimension = ref<MonitorDimension>('day');
|
||||
|
||||
async function loadOverview() {
|
||||
overviewLoading.value = true;
|
||||
/** silent:切换日/周/月时不整页 Spin,避免闪白 */
|
||||
async function loadOverview(opts?: { silent?: boolean }) {
|
||||
const silent = opts?.silent ?? false;
|
||||
if (!silent) overviewLoading.value = true;
|
||||
try {
|
||||
overview.value = await getMonitorOverview(opsDimension.value);
|
||||
} catch {
|
||||
overview.value = null;
|
||||
if (!silent) overview.value = null;
|
||||
} finally {
|
||||
overviewLoading.value = false;
|
||||
if (!silent) overviewLoading.value = false;
|
||||
await nextTick();
|
||||
refreshChartsForTab(monitorTab.value);
|
||||
}
|
||||
@ -919,17 +949,17 @@
|
||||
series: [
|
||||
{
|
||||
name: t('monitor.logistics.series.revenue'),
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
type: 'bar',
|
||||
barMaxWidth: 28,
|
||||
data: revenueValues,
|
||||
itemStyle: { color: revenueColor },
|
||||
itemStyle: { color: revenueColor, borderRadius: [4, 4, 0, 0] },
|
||||
},
|
||||
{
|
||||
name: t('monitor.logistics.series.grossProfit'),
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
type: 'bar',
|
||||
barMaxWidth: 28,
|
||||
data: grossValues,
|
||||
itemStyle: { color: grossColor },
|
||||
itemStyle: { color: grossColor, borderRadius: [4, 4, 0, 0] },
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -987,10 +1017,8 @@
|
||||
|
||||
watch(
|
||||
monitorTab,
|
||||
(key, prev) => {
|
||||
if (prev === 'order') disposeOrderCharts();
|
||||
if (prev === 'track') disposeTrackCharts();
|
||||
if (prev === 'customer') disposeCustomerCharts();
|
||||
(key) => {
|
||||
// 面板用 v-show 常驻,切 Tab 只 resize / 补画,不 dispose,避免闪一下
|
||||
nextTick(() => {
|
||||
if (key === 'order') ensureOrderLineCharts();
|
||||
if (key === 'track') ensureTrackCharts();
|
||||
@ -1002,7 +1030,7 @@
|
||||
);
|
||||
|
||||
watch(opsDimension, () => {
|
||||
void loadOverview();
|
||||
void loadOverview({ silent: true });
|
||||
});
|
||||
|
||||
watch([filterLine, filterSignDays], () => {
|
||||
@ -1027,27 +1055,22 @@
|
||||
layoutResizeObserver?.disconnect();
|
||||
layoutResizeObserver = null;
|
||||
window.removeEventListener('resize', onResize);
|
||||
orderChartInstance?.dispose();
|
||||
profitChartInstance?.dispose();
|
||||
signRateChartInstance?.dispose();
|
||||
customerOrderTrendInstance?.dispose();
|
||||
customerReceivableTrendInstance?.dispose();
|
||||
pieTrackCompleteInstance?.dispose();
|
||||
pieTrackTimelyInstance?.dispose();
|
||||
pieOtdInstance?.dispose();
|
||||
orderChartInstance = null;
|
||||
profitChartInstance = null;
|
||||
signRateChartInstance = null;
|
||||
customerOrderTrendInstance = null;
|
||||
customerReceivableTrendInstance = null;
|
||||
pieTrackCompleteInstance = null;
|
||||
pieTrackTimelyInstance = null;
|
||||
pieOtdInstance = null;
|
||||
disposeOrderCharts();
|
||||
disposeTrackCharts();
|
||||
disposeCustomerCharts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.logistics-monitor-container {
|
||||
--lm-well-bg: #ffffff;
|
||||
--lm-well-shadow:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.06),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
--lm-well-shadow-deep:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.08),
|
||||
inset 0 2px 6px rgba(15, 23, 42, 0.07);
|
||||
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@ -1059,26 +1082,46 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logistics-monitor-card {
|
||||
.monitor-topbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
background: var(--app-card-bg);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 16px;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 12px;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.logistics-monitor-header {
|
||||
margin-bottom: 16px;
|
||||
.monitor-topbar__spacer {
|
||||
flex: 1 1 auto;
|
||||
min-width: 8px;
|
||||
}
|
||||
|
||||
.logistics-monitor-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
line-height: 32px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monitor-top-tabs {
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.monitor-ops-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monitor-ops-toolbar__label {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logistics-monitor-body {
|
||||
@ -1088,72 +1131,41 @@
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monitor-top-tabs {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
:deep(.arco-spin) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs) {
|
||||
:deep(.arco-spin-children) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav) {
|
||||
justify-content: flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav-tab-list) {
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-content) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
padding: 12px 0 0;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-content-list) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-pane) {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.logistics-monitor-title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.monitor-ops-toolbar {
|
||||
.monitor-shell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monitor-ops-toolbar__label {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
white-space: nowrap;
|
||||
.monitor-tab-panels {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.monitor-tab-panel {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.monitor-queue-title {
|
||||
@ -1223,12 +1235,19 @@
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
min-height: 84px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: none !important;
|
||||
border-radius: 14px !important;
|
||||
background: var(--lm-well-bg, #ffffff) !important;
|
||||
box-shadow: var(--lm-well-shadow) !important;
|
||||
transition: box-shadow 0.2s ease;
|
||||
|
||||
:deep(.arco-card) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&:hover {
|
||||
background: var(--lm-well-bg, #ffffff) !important;
|
||||
box-shadow: var(--lm-well-shadow-deep) !important;
|
||||
}
|
||||
|
||||
:deep(.arco-card-body) {
|
||||
@ -1236,77 +1255,89 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/* 对齐工作台:左 icon+标题竖排,右数值;icon 无底框 */
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.stat-item__head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.stat-item__icon {
|
||||
flex-shrink: 0;
|
||||
margin-top: -2px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
.stat-item__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-item__icon-img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
.stat-item__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stat-item__avatar {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: contain;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.stat-item__icon--primary {
|
||||
background: var(--app-primary-color-light);
|
||||
.stat-item__avatar-icon {
|
||||
display: inline-flex;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
color: var(--app-primary-color);
|
||||
background: transparent;
|
||||
|
||||
&.is-warning {
|
||||
color: rgb(var(--orange-6));
|
||||
}
|
||||
|
||||
&.is-online {
|
||||
color: rgb(var(--cyan-6));
|
||||
}
|
||||
}
|
||||
|
||||
.stat-item__icon--warning {
|
||||
background: rgb(var(--orange-1));
|
||||
color: rgb(var(--orange-6));
|
||||
}
|
||||
|
||||
.stat-item__icon--danger {
|
||||
background: rgb(var(--red-1));
|
||||
color: rgb(var(--red-6));
|
||||
}
|
||||
|
||||
.stat-item__icon--online {
|
||||
background: rgb(var(--cyan-1));
|
||||
color: rgb(var(--cyan-6));
|
||||
.stat-item__body {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.35;
|
||||
color: var(--color-text-2);
|
||||
padding-top: 2px;
|
||||
padding-right: 4px;
|
||||
max-width: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
color: var(--color-text-3);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: clamp(18px, 2.4vw, 22px);
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--color-text-1);
|
||||
word-break: break-all;
|
||||
}
|
||||
@ -1324,9 +1355,10 @@
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
padding-top: 8px;
|
||||
gap: 2px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.stat-trend {
|
||||
@ -1358,6 +1390,16 @@
|
||||
.chart-card {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
border: none !important;
|
||||
border-radius: 12px !important;
|
||||
background: var(--lm-well-bg, #ffffff) !important;
|
||||
box-shadow: var(--lm-well-shadow, inset 0 2px 5px rgba(15, 23, 42, 0.08)) !important;
|
||||
|
||||
:deep(.arco-card-header),
|
||||
:deep(.arco-card-body) {
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-host {
|
||||
|
||||
@ -240,18 +240,24 @@ onMounted(() => {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
background: var(--app-acrylic-bg);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.06),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
color: var(--aw-ink);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.2s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.08),
|
||||
inset 0 2px 6px rgba(15, 23, 42, 0.07);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@ -285,9 +291,27 @@ onMounted(() => {
|
||||
.stat-card {
|
||||
min-height: 84px;
|
||||
padding: 12px 14px 10px;
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.06),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
|
||||
&.is-primary {
|
||||
border-color: color-mix(in srgb, var(--app-primary-color) 22%, var(--app-acrylic-border));
|
||||
border: none;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--app-primary-color) 22%, transparent),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
&--clickable:hover {
|
||||
border-color: transparent;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.08),
|
||||
inset 0 2px 6px rgba(15, 23, 42, 0.07);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -99,6 +99,21 @@ const items = computed(() =>
|
||||
margin: 0;
|
||||
padding: 10px 12px 8px;
|
||||
border-radius: 14px;
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.06),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
|
||||
&.stat-card--clickable:hover {
|
||||
border-color: transparent;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.08),
|
||||
inset 0 2px 6px rgba(15, 23, 42, 0.07);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-grid--inner .stat-card__value {
|
||||
|
||||
@ -156,13 +156,28 @@ function go(path: string, query?: Record<string, string>) {
|
||||
|
||||
<style scoped lang="less">
|
||||
.admin-workplace {
|
||||
/* 对齐官网首页 pressed 卡片:无描边 + inset 凹陷 */
|
||||
--aw-ink: #1a2e38;
|
||||
--aw-muted: rgba(26, 46, 56, 0.68);
|
||||
--aw-soft: rgba(26, 46, 56, 0.48);
|
||||
--aw-edge: var(--app-acrylic-border);
|
||||
--aw-shadow: none;
|
||||
--aw-edge: transparent;
|
||||
--aw-radius: 16px;
|
||||
--aw-panel-bg: var(--app-acrylic-bg);
|
||||
/* 外层面板白底压入;内卡同白 + 更轻 inset(上方灰感减淡) */
|
||||
--aw-panel-bg: #ffffff;
|
||||
--aw-panel-shadow:
|
||||
inset 0 2px 8px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
--aw-panel-shadow-deep:
|
||||
inset 0 2px 10px rgba(15, 23, 42, 0.09),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.05);
|
||||
--aw-well-bg: #ffffff;
|
||||
/* 轻压 + 一圈细边,避免只压顶导致底边“断线” */
|
||||
--aw-well-shadow:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.06),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
--aw-well-shadow-deep:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.08),
|
||||
inset 0 2px 6px rgba(15, 23, 42, 0.07);
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -217,13 +232,14 @@ function go(path: string, query?: Record<string, string>) {
|
||||
}
|
||||
|
||||
.aw-panel {
|
||||
border: 1px solid var(--aw-edge);
|
||||
border: none;
|
||||
border-radius: var(--aw-radius);
|
||||
background: var(--aw-panel-bg);
|
||||
box-shadow: var(--aw-shadow);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
box-shadow: var(--aw-panel-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
min-width: 0;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.aw-panel--hero {
|
||||
@ -319,7 +335,6 @@ function go(path: string, query?: Record<string, string>) {
|
||||
|
||||
.aw-link {
|
||||
--tone: var(--app-primary-color);
|
||||
--tone-edge: var(--app-acrylic-border);
|
||||
--tone-strong: var(--app-primary-color);
|
||||
|
||||
display: flex;
|
||||
@ -328,19 +343,18 @@ function go(path: string, query?: Record<string, string>) {
|
||||
min-height: 56px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--tone-edge);
|
||||
background: var(--app-acrylic-bg);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
background: var(--aw-well-bg);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
box-shadow: var(--aw-well-shadow);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, background 0.2s ease;
|
||||
transition: box-shadow 0.2s ease, background 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
box-shadow: none;
|
||||
background: var(--aw-well-bg);
|
||||
box-shadow: var(--aw-well-shadow-deep);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@ -351,31 +365,26 @@ function go(path: string, query?: Record<string, string>) {
|
||||
|
||||
.aw-tone--ember {
|
||||
--tone: #c45a3a;
|
||||
--tone-edge: rgba(196, 90, 58, 0.35);
|
||||
--tone-strong: #b84a2e;
|
||||
}
|
||||
|
||||
.aw-tone--apricot {
|
||||
--tone: #d9924a;
|
||||
--tone-edge: rgba(217, 146, 74, 0.35);
|
||||
--tone-strong: #c47d32;
|
||||
}
|
||||
|
||||
.aw-tone--honey {
|
||||
--tone: #d4a05c;
|
||||
--tone-edge: rgba(212, 160, 92, 0.35);
|
||||
--tone-strong: #c08a3e;
|
||||
}
|
||||
|
||||
.aw-tone--clay {
|
||||
--tone: #c17a52;
|
||||
--tone-edge: rgba(193, 122, 82, 0.35);
|
||||
--tone-strong: #a8663f;
|
||||
}
|
||||
|
||||
.aw-tone--sand {
|
||||
--tone: #c9a07a;
|
||||
--tone-edge: rgba(201, 160, 122, 0.35);
|
||||
--tone-strong: #b08a62;
|
||||
}
|
||||
|
||||
@ -420,6 +429,68 @@ function go(path: string, query?: Record<string, string>) {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* 内层统计卡 / 世界时钟 / 刷新钮:浅灰井 + inset,嵌进白面板 */
|
||||
.admin-workplace :deep(.stat-card) {
|
||||
border: none;
|
||||
background: var(--aw-well-bg);
|
||||
box-shadow: var(--aw-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
transition: box-shadow 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.admin-workplace :deep(.stat-card--clickable:hover) {
|
||||
border-color: transparent;
|
||||
background: var(--aw-well-bg);
|
||||
box-shadow: var(--aw-well-shadow-deep);
|
||||
}
|
||||
|
||||
.admin-workplace :deep(.stat-card.is-primary) {
|
||||
border: none;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--app-primary-color) 22%, transparent),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.admin-workplace :deep(.aw-ops__refresh),
|
||||
.admin-workplace :deep(.care-clockout) {
|
||||
border: none;
|
||||
background: var(--aw-well-bg);
|
||||
box-shadow: var(--aw-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
.admin-workplace :deep(.aw-ops__refresh:hover:not(:disabled)),
|
||||
.admin-workplace :deep(.care-clockout:hover) {
|
||||
border-color: transparent;
|
||||
background: var(--aw-well-bg);
|
||||
box-shadow: var(--aw-well-shadow-deep);
|
||||
}
|
||||
|
||||
.admin-workplace :deep(.wt-card) {
|
||||
border: none;
|
||||
background: var(--aw-well-bg);
|
||||
box-shadow: var(--aw-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
.admin-workplace :deep(.announcement-item) {
|
||||
border: none;
|
||||
background: var(--aw-well-bg);
|
||||
box-shadow: var(--aw-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
transition: box-shadow 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.admin-workplace :deep(.announcement-item:hover) {
|
||||
border-color: transparent;
|
||||
background: var(--aw-well-bg);
|
||||
box-shadow: var(--aw-well-shadow-deep);
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.admin-workplace__container {
|
||||
flex-direction: column;
|
||||
|
||||
@ -42,11 +42,21 @@ defineOptions({ name: 'CustomerWorkplace' })
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-container {
|
||||
/* 对齐管理台:外层轻压,内卡细边 + 轻 inset(上方不发灰) */
|
||||
--wp-card-radius: 16px;
|
||||
--wp-inner-radius: 12px;
|
||||
--wp-card-border: 1px solid var(--app-acrylic-border);
|
||||
--wp-card-bg: var(--app-acrylic-bg);
|
||||
--wp-card-shadow: none;
|
||||
--wp-card-border: none;
|
||||
--wp-card-bg: #ffffff;
|
||||
--wp-card-shadow:
|
||||
inset 0 2px 8px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
--wp-well-bg: #ffffff;
|
||||
--wp-well-shadow:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.06),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
--wp-well-shadow-deep:
|
||||
inset 0 0 0 1px rgba(15, 23, 42, 0.08),
|
||||
inset 0 2px 6px rgba(15, 23, 42, 0.07);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
@ -185,12 +195,14 @@ defineOptions({ name: 'CustomerWorkplace' })
|
||||
border-radius: var(--wp-card-radius);
|
||||
background: var(--wp-card-bg);
|
||||
box-shadow: var(--wp-card-shadow);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
flex-shrink: 0;
|
||||
|
||||
:deep(.arco-card) {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.arco-card-header),
|
||||
@ -266,12 +278,66 @@ defineOptions({ name: 'CustomerWorkplace' })
|
||||
}
|
||||
|
||||
:deep(.marketing-shortcuts-panel) {
|
||||
border: var(--wp-card-border);
|
||||
border: none;
|
||||
border-radius: var(--wp-card-radius);
|
||||
background: var(--wp-card-bg);
|
||||
box-shadow: var(--wp-card-shadow);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
/* 内层统计卡 / 世界时钟 / 快捷入口 / 站内信 */
|
||||
.workplace-container :deep(.stat-card) {
|
||||
border: none;
|
||||
background: var(--wp-well-bg);
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.workplace-container :deep(.stat-card--clickable:hover) {
|
||||
border-color: transparent;
|
||||
background: var(--wp-well-bg);
|
||||
box-shadow: var(--wp-well-shadow-deep);
|
||||
}
|
||||
|
||||
.workplace-container :deep(.wt-card) {
|
||||
border: none;
|
||||
background: var(--wp-well-bg);
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
.workplace-container :deep(.marketing-chip) {
|
||||
border: none;
|
||||
background: var(--wp-well-bg);
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.workplace-container :deep(.marketing-chip:hover) {
|
||||
border-color: transparent;
|
||||
background: var(--wp-well-bg);
|
||||
box-shadow: var(--wp-well-shadow-deep);
|
||||
}
|
||||
|
||||
.workplace-container :deep(.announcement-item) {
|
||||
border: none;
|
||||
background: var(--wp-well-bg);
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.workplace-container :deep(.announcement-item:hover) {
|
||||
border-color: transparent;
|
||||
background: var(--wp-well-bg);
|
||||
box-shadow: var(--wp-well-shadow-deep);
|
||||
}
|
||||
|
||||
:deep(.chart-controls) {
|
||||
|
||||
@ -325,10 +325,11 @@
|
||||
margin-left: 4px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--app-acrylic-border-strong);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow, var(--aw-well-shadow));
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@ -361,18 +362,19 @@
|
||||
flex-shrink: 0;
|
||||
height: 44px;
|
||||
padding: 0 12px 0 14px;
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: var(--app-acrylic-bg);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow, var(--aw-well-shadow));
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
transition: box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
box-shadow: none;
|
||||
border-color: transparent;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow-deep, var(--aw-well-shadow-deep));
|
||||
|
||||
.balance-chip__cta {
|
||||
opacity: 1;
|
||||
@ -380,8 +382,11 @@
|
||||
}
|
||||
|
||||
&--low {
|
||||
border-color: rgba(255, 125, 0, 0.45);
|
||||
border: none;
|
||||
background: rgba(255, 125, 0, 0.06);
|
||||
box-shadow:
|
||||
inset 0 2px 5px rgba(255, 125, 0, 0.12),
|
||||
inset 0 0 0 1px rgba(255, 125, 0, 0.35);
|
||||
|
||||
.balance-chip__value {
|
||||
color: #ff7d00;
|
||||
|
||||
@ -280,10 +280,21 @@ onMounted(() => {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* 首页统计卡与营销入口统一磨砂质感(继承 stat-card.less) */
|
||||
/* 统计卡:白底轻 inset(外层面板由 CustomerWorkplace 压入) */
|
||||
.stat-card {
|
||||
min-height: 84px;
|
||||
padding: 12px 14px 10px;
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
|
||||
&--clickable:hover {
|
||||
border-color: transparent;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow-deep);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card--compact {
|
||||
|
||||
@ -150,22 +150,22 @@ onMounted(async () => {
|
||||
min-height: 64px;
|
||||
margin: 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
border: none;
|
||||
border-radius: var(--wp-inner-radius, 12px);
|
||||
background: var(--app-acrylic-bg);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
box-shadow: none;
|
||||
background: #ffffff;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
transition: box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
box-shadow: none;
|
||||
border-color: transparent;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow-deep);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
|
||||
@ -278,26 +278,30 @@
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
border: none;
|
||||
border-radius: var(--wp-inner-radius, 12px);
|
||||
background: var(--app-acrylic-bg);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
color: var(--color-text-2);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, background 0.2s, color 0.2s;
|
||||
transition: box-shadow 0.2s ease, color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
border-color: transparent;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow-deep);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-color: color-mix(in srgb, var(--app-primary-color) 22%, var(--app-acrylic-border));
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--app-primary-color) 28%, transparent),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
color: var(--app-primary-color, rgb(var(--primary-6)));
|
||||
|
||||
.biz-switch__icon {
|
||||
@ -327,18 +331,19 @@
|
||||
}
|
||||
|
||||
.flow-step {
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
border: none;
|
||||
border-radius: var(--wp-inner-radius, 12px);
|
||||
background: var(--app-acrylic-bg);
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
transition: box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
box-shadow: none;
|
||||
border-color: transparent;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow-deep);
|
||||
}
|
||||
|
||||
&__main {
|
||||
@ -412,18 +417,22 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
background: transparent;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
color: var(--color-text-2);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, color 0.2s, background 0.2s;
|
||||
transition: box-shadow 0.2s ease, color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
border-color: transparent;
|
||||
color: var(--app-primary-color, rgb(var(--primary-6)));
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--app-primary-color) 22%, transparent),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,18 +453,22 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
background: transparent;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--wp-well-shadow);
|
||||
color: var(--color-text-2);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, color 0.2s, background 0.2s;
|
||||
transition: box-shadow 0.2s ease, color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
border-color: transparent;
|
||||
color: var(--app-primary-color, rgb(var(--primary-6)));
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px color-mix(in srgb, var(--app-primary-color) 22%, transparent),
|
||||
inset 0 1px 4px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
|
||||
@ -7,9 +7,6 @@
|
||||
}"
|
||||
aria-live="polite"
|
||||
>
|
||||
<span class="login-care-banner__blob login-care-banner__blob--a" aria-hidden="true" />
|
||||
<span class="login-care-banner__blob login-care-banner__blob--b" aria-hidden="true" />
|
||||
<span class="login-care-banner__blob login-care-banner__blob--c" aria-hidden="true" />
|
||||
<div class="login-care-banner__body">
|
||||
<p class="login-care-banner__greeting">{{ greeting }}</p>
|
||||
<p class="login-care-banner__note">{{ note }}</p>
|
||||
@ -90,10 +87,11 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login-care-banner {
|
||||
--care-ink: #5c3d2e;
|
||||
--care-muted: rgba(92, 61, 46, 0.72);
|
||||
--care-edge: rgba(255, 214, 170, 0.55);
|
||||
--care-glow: rgba(255, 176, 120, 0.35);
|
||||
/* 与登录卡片同系:贴合页底 + 单层 inset,无彩斑 */
|
||||
--care-ink: #1e293b;
|
||||
--care-muted: rgba(71, 85, 105, 0.88);
|
||||
--care-bg: var(--app-page-bg, #f0f3f4);
|
||||
--care-shadow: inset 0 3px 12px rgba(15, 23, 42, 0.1);
|
||||
|
||||
position: absolute;
|
||||
top: calc(16px + env(safe-area-inset-top, 0px));
|
||||
@ -101,99 +99,34 @@ onBeforeUnmount(() => {
|
||||
z-index: 40;
|
||||
width: min(360px, calc(100vw - 40px));
|
||||
padding: 16px 18px;
|
||||
border-radius: 22px;
|
||||
border-radius: 20px;
|
||||
text-align: left;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
border: 1px solid var(--care-edge);
|
||||
background:
|
||||
linear-gradient(145deg, rgba(255, 248, 240, 0.82), rgba(255, 236, 220, 0.55)),
|
||||
rgba(255, 250, 245, 0.65);
|
||||
backdrop-filter: blur(16px) saturate(1.25);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(1.25);
|
||||
box-shadow:
|
||||
0 10px 28px rgba(180, 110, 70, 0.12),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.75);
|
||||
border: none;
|
||||
background: var(--care-bg);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
box-shadow: var(--care-shadow);
|
||||
opacity: 0;
|
||||
transform: translate3d(18px, -12px, 0) scale(0.92);
|
||||
transform: translate3d(12px, -8px, 0);
|
||||
transform-origin: top right;
|
||||
transition:
|
||||
opacity 0.42s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
transform 0.5s cubic-bezier(0.34, 1.35, 0.64, 1),
|
||||
box-shadow 0.42s ease,
|
||||
border-radius 0.8s ease;
|
||||
opacity 0.35s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
transform 0.4s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 0.3s ease;
|
||||
|
||||
&.is-visible {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0) scale(1);
|
||||
border-radius: 24px 18px 26px 20px;
|
||||
box-shadow:
|
||||
0 14px 36px rgba(180, 110, 70, 0.16),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.35) inset;
|
||||
animation: care-liquid-morph 8s ease-in-out infinite;
|
||||
transform: translate3d(0, 0, 0);
|
||||
box-shadow: inset 0 4px 14px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
&.is-after-hours {
|
||||
--care-ink: #4a3a55;
|
||||
--care-muted: rgba(74, 58, 85, 0.72);
|
||||
--care-edge: rgba(220, 190, 230, 0.5);
|
||||
--care-glow: rgba(190, 150, 220, 0.28);
|
||||
background:
|
||||
linear-gradient(145deg, rgba(252, 246, 255, 0.88), rgba(240, 228, 250, 0.6)),
|
||||
rgba(250, 246, 255, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.login-care-banner__blob {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(18px);
|
||||
opacity: 0.85;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.login-care-banner__blob--a {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
top: -48px;
|
||||
right: -36px;
|
||||
background: radial-gradient(circle, #ffb48a 0%, rgba(255, 180, 138, 0) 70%);
|
||||
animation: care-blob-a 7s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.login-care-banner__blob--b {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
bottom: -50px;
|
||||
left: -28px;
|
||||
background: radial-gradient(circle, #ffd6a5 0%, rgba(255, 214, 165, 0) 72%);
|
||||
animation: care-blob-b 9s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.login-care-banner__blob--c {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
top: 40%;
|
||||
left: 42%;
|
||||
background: radial-gradient(circle, #ffe8c8 0%, rgba(255, 232, 200, 0) 70%);
|
||||
animation: care-blob-c 6.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.login-care-banner.is-after-hours {
|
||||
.login-care-banner__blob--a {
|
||||
background: radial-gradient(circle, #d7b8ef 0%, rgba(215, 184, 239, 0) 70%);
|
||||
}
|
||||
|
||||
.login-care-banner__blob--b {
|
||||
background: radial-gradient(circle, #f2c9de 0%, rgba(242, 201, 222, 0) 72%);
|
||||
}
|
||||
|
||||
.login-care-banner__blob--c {
|
||||
background: radial-gradient(circle, #e8d9f8 0%, rgba(232, 217, 248, 0) 70%);
|
||||
--care-ink: #1e293b;
|
||||
--care-muted: rgba(71, 85, 105, 0.88);
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,9 +137,9 @@ onBeforeUnmount(() => {
|
||||
|
||||
.login-care-banner__greeting {
|
||||
margin: 0 0 6px;
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.35;
|
||||
color: var(--care-ink);
|
||||
}
|
||||
@ -218,49 +151,6 @@ onBeforeUnmount(() => {
|
||||
color: var(--care-muted);
|
||||
}
|
||||
|
||||
@keyframes care-liquid-morph {
|
||||
0%,
|
||||
100% {
|
||||
border-radius: 24px 18px 26px 20px;
|
||||
}
|
||||
33% {
|
||||
border-radius: 20px 26px 18px 24px;
|
||||
}
|
||||
66% {
|
||||
border-radius: 26px 20px 22px 28px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes care-blob-a {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translate(-18px, 14px) scale(1.12);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes care-blob-b {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translate(16px, -12px) scale(1.18);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes care-blob-c {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translate(-10px, -8px) scale(1.25);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.login-care-banner {
|
||||
top: calc(12px + env(safe-area-inset-top, 0px));
|
||||
@ -270,21 +160,15 @@ onBeforeUnmount(() => {
|
||||
transform-origin: top center;
|
||||
|
||||
&:not(.is-visible) {
|
||||
transform: translate3d(0, -10px, 0) scale(0.94);
|
||||
transform: translate3d(0, -8px, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.login-care-banner,
|
||||
.login-care-banner__blob {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.login-care-banner {
|
||||
transition: opacity 0.2s ease;
|
||||
transform: none;
|
||||
border-radius: 16px;
|
||||
|
||||
&.is-visible {
|
||||
transform: none;
|
||||
|
||||
@ -13,57 +13,36 @@
|
||||
<form class="nova-login__form" autocomplete="off" @submit.prevent="handleSubmit">
|
||||
<div class="nova-login__input-group">
|
||||
<div class="nova-login__field-label">{{ accountFieldLabel }}</div>
|
||||
<div class="nova-login__input-wrapper">
|
||||
<span class="nova-login__prefix-icon" aria-hidden="true">
|
||||
<icon-user :size="16" />
|
||||
</span>
|
||||
<div class="nova-login__input-shell">
|
||||
<input
|
||||
v-model="loginForm.account"
|
||||
type="text"
|
||||
class="nova-login__input"
|
||||
:placeholder="accountPlaceholder"
|
||||
autocomplete="username"
|
||||
@focus="onAccountFocus"
|
||||
@blur="onAccountBlur"
|
||||
@keydown.enter.prevent="focusPasswordInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<a-input
|
||||
v-model="loginForm.account"
|
||||
size="large"
|
||||
allow-clear
|
||||
class="nova-login__arco-input"
|
||||
:placeholder="accountPlaceholder"
|
||||
autocomplete="username"
|
||||
@focus="onAccountFocus"
|
||||
@blur="onAccountBlur"
|
||||
@press-enter="focusPasswordInput"
|
||||
/>
|
||||
<div v-if="accountError" class="nova-login__field-error">{{ accountError }}</div>
|
||||
</div>
|
||||
|
||||
<div class="nova-login__input-group">
|
||||
<div class="nova-login__field-label">{{ t('login.field.password') }}</div>
|
||||
<div class="nova-login__input-wrapper">
|
||||
<span class="nova-login__prefix-icon" aria-hidden="true">
|
||||
<icon-lock :size="16" />
|
||||
</span>
|
||||
<div class="nova-login__input-shell">
|
||||
<input
|
||||
ref="passwordInputRef"
|
||||
v-model="loginForm.password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
class="nova-login__input"
|
||||
:placeholder="t('login.form.password.placeholder')"
|
||||
autocomplete="current-password"
|
||||
@focus="onPasswordFocus"
|
||||
@blur="onPasswordBlur"
|
||||
@input="syncPasswordCharacterState"
|
||||
@keydown.enter.prevent="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="nova-login__eye-toggle"
|
||||
tabindex="-1"
|
||||
:aria-label="showPassword ? t('login.hidePassword') : t('login.showPassword')"
|
||||
@click="togglePasswordVisible"
|
||||
>
|
||||
<icon-eye-invisible v-if="!showPassword" :size="16" />
|
||||
<icon-eye v-else :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
<a-input-password
|
||||
ref="passwordInputRef"
|
||||
v-model="loginForm.password"
|
||||
size="large"
|
||||
allow-clear
|
||||
class="nova-login__arco-input"
|
||||
:placeholder="t('login.form.password.placeholder')"
|
||||
autocomplete="current-password"
|
||||
@focus="onPasswordFocus"
|
||||
@blur="onPasswordBlur"
|
||||
@input="syncPasswordCharacterState"
|
||||
@press-enter="handleSubmit"
|
||||
@visibility-change="onPasswordVisibilityChange"
|
||||
/>
|
||||
<div v-if="passwordError" class="nova-login__field-error">{{ passwordError }}</div>
|
||||
</div>
|
||||
|
||||
@ -117,13 +96,7 @@ import { ref, reactive, computed, nextTick, inject, type ShallowRef } from 'vue'
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import {
|
||||
IconUser,
|
||||
IconLock,
|
||||
IconWechat,
|
||||
IconEye,
|
||||
IconEyeInvisible,
|
||||
} from '@arco-design/web-vue/es/icon';
|
||||
import { IconWechat } from '@arco-design/web-vue/es/icon';
|
||||
import { OmsLoginType } from '@/api/rbac';
|
||||
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
||||
import { setToken } from '@/utils/auth';
|
||||
@ -153,7 +126,7 @@ const emit = defineEmits<{
|
||||
const router = useRouter();
|
||||
const rbacUserStore = useRbacUserStore();
|
||||
|
||||
const passwordInputRef = ref<HTMLInputElement | null>(null);
|
||||
const passwordInputRef = ref<{ focus?: () => void } | null>(null);
|
||||
const loading = ref(false);
|
||||
const showPassword = ref(false);
|
||||
const accountError = ref('');
|
||||
@ -290,8 +263,8 @@ function syncPasswordCharacterState() {
|
||||
loginCharacters.value?.onPasswordInput(loginForm.password.length, showPassword.value);
|
||||
}
|
||||
|
||||
function togglePasswordVisible() {
|
||||
showPassword.value = !showPassword.value;
|
||||
function onPasswordVisibilityChange(visible: boolean) {
|
||||
showPassword.value = visible;
|
||||
syncPasswordCharacterState();
|
||||
}
|
||||
|
||||
@ -456,102 +429,35 @@ async function handleSubmit() {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nova-login__input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
background: var(--app-acrylic-bg);
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
border-radius: 14px;
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
|
||||
/* 与 H5 `.h5-login-form` 输入井一致 */
|
||||
.nova-login__form :deep(.arco-input-wrapper),
|
||||
.nova-login__form :deep(.arco-input-password) {
|
||||
border-radius: 12px;
|
||||
background: #f0f3f4;
|
||||
border: none;
|
||||
min-height: 48px;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
box-shadow: inset 0 2px 8px rgba(15, 23, 42, 0.08);
|
||||
transition: box-shadow 0.2s ease, background 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
background: #f0f3f4;
|
||||
box-shadow: inset 0 3px 10px rgba(15, 23, 42, 0.1);
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--app-primary-color);
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--app-primary-color) 15%, transparent);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
&.arco-input-wrapper-focus,
|
||||
&.arco-input-focus {
|
||||
background: #f0f3f4;
|
||||
border: none;
|
||||
box-shadow:
|
||||
inset 0 3px 10px rgba(15, 23, 42, 0.1),
|
||||
0 0 0 3px color-mix(in srgb, var(--app-primary-color) 12%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.nova-login__prefix-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
padding-left: 14px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.nova-login__input-shell {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nova-login__input-wrapper:focus-within .nova-login__prefix-icon {
|
||||
color: var(--app-primary-color);
|
||||
}
|
||||
|
||||
.nova-login__input {
|
||||
.nova-login__arco-input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
color: #111827;
|
||||
padding: 0 12px 0 4px;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
|
||||
&::placeholder {
|
||||
color: #9aa4b2;
|
||||
}
|
||||
|
||||
&:-webkit-autofill,
|
||||
&:-webkit-autofill:hover,
|
||||
&:-webkit-autofill:focus,
|
||||
&:-webkit-autofill:active {
|
||||
-webkit-text-fill-color: #111827;
|
||||
caret-color: #111827;
|
||||
box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.42) inset;
|
||||
-webkit-box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.42) inset;
|
||||
transition: background-color 99999s ease-out 0s;
|
||||
}
|
||||
}
|
||||
|
||||
.nova-login__input-wrapper:focus-within .nova-login__input:-webkit-autofill,
|
||||
.nova-login__input-wrapper:hover .nova-login__input:-webkit-autofill {
|
||||
box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.52) inset;
|
||||
-webkit-box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.52) inset;
|
||||
}
|
||||
|
||||
.nova-login__eye-toggle {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
padding-right: 14px;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--app-primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.nova-login__field-error {
|
||||
|
||||
@ -47,13 +47,6 @@
|
||||
/>
|
||||
</aside>
|
||||
|
||||
<div
|
||||
v-if="shellReady && !isCustomerLogin"
|
||||
class="login-characters-panel login-characters-panel--admin"
|
||||
>
|
||||
<LoginCharacters v-if="showCharacters" @register="registerLoginCharacters" />
|
||||
</div>
|
||||
|
||||
<div v-if="shellReady" ref="rightPaneRef" class="login-pane login-pane--right">
|
||||
<div ref="loginFormRef" :class="['login-form', { 'login-form--ready': shellReady }]">
|
||||
<div class="login-form-toolbar">
|
||||
@ -346,7 +339,6 @@ onBeforeUnmount(() => {
|
||||
|
||||
/* Safety net: admin overlays are outside pane clip-paths */
|
||||
.login-hero-panel--admin,
|
||||
.login-characters-panel--admin,
|
||||
:deep(.login-care-banner) {
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
@ -481,22 +473,6 @@ onBeforeUnmount(() => {
|
||||
padding: 0 0 16px 8px;
|
||||
}
|
||||
|
||||
.login-characters-panel--admin {
|
||||
left: auto;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 6;
|
||||
padding: 0;
|
||||
transform: scale(0.82);
|
||||
transform-origin: bottom right;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.login-characters-panel--admin :deep(.login-characters__stage) {
|
||||
width: 450px;
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
|
||||
.login-form-toolbar {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
@ -535,29 +511,35 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.login-form {
|
||||
/* 对齐 H5 登录卡:白底 + 轻压 inset */
|
||||
--login-card-bg: #ffffff;
|
||||
--login-card-shadow:
|
||||
inset 0 2px 8px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 1px rgba(15, 23, 42, 0.04);
|
||||
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
min-height: auto;
|
||||
padding: 51px 32px 45px;
|
||||
background: var(--app-acrylic-bg);
|
||||
border: 1px solid var(--app-acrylic-border);
|
||||
border-radius: 24px;
|
||||
box-shadow: none;
|
||||
backdrop-filter: var(--app-acrylic-blur);
|
||||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||||
background: var(--login-card-bg);
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 20px;
|
||||
box-shadow: var(--login-card-shadow);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
transition: border-color 0.2s ease, background 0.2s ease;
|
||||
transition: box-shadow 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.login-form:hover {
|
||||
border-color: var(--app-acrylic-border-strong);
|
||||
background: var(--app-acrylic-hover-bg);
|
||||
box-shadow: none;
|
||||
background: var(--login-card-bg);
|
||||
box-shadow: var(--login-card-shadow);
|
||||
}
|
||||
|
||||
.login-form--ready {
|
||||
|
||||
@ -29,9 +29,9 @@
|
||||
parallaxStrength?: number;
|
||||
grain?: boolean;
|
||||
grainIntensity?: number;
|
||||
/** Cap canvas DPR (retina 2–3 is expensive for raymarch). */
|
||||
/** Cap canvas DPR (retina is expensive for raymarch). */
|
||||
maxDpr?: number;
|
||||
/** Draw buffer scale 0–1; CSS stretches to full size (softer, cheaper). */
|
||||
/** Draw buffer scale 0–1; CSS stretches to full size. */
|
||||
renderScale?: number;
|
||||
/** Target animation FPS; lower = less GPU. */
|
||||
fps?: number;
|
||||
@ -380,7 +380,7 @@ void main() {
|
||||
const currentMouse: [number, number] = [0.5, 0.5];
|
||||
const targetMouse: [number, number] = [0.5, 0.5];
|
||||
|
||||
// Window-level so parallax works behind UI
|
||||
// Window-level so parallax works behind UI overlays
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
if (!props.mouseInteraction) return;
|
||||
const rect = container.getBoundingClientRect();
|
||||
@ -404,10 +404,10 @@ void main() {
|
||||
|
||||
const paint = (t: number) => {
|
||||
mu.iTime.value = (t - t0) * 0.001;
|
||||
if (props.mouseInteraction) {
|
||||
currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);
|
||||
currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);
|
||||
}
|
||||
const tx = props.mouseInteraction ? targetMouse[0] : 0.5;
|
||||
const ty = props.mouseInteraction ? targetMouse[1] : 0.5;
|
||||
currentMouse[0] += 0.05 * (tx - currentMouse[0]);
|
||||
currentMouse[1] += 0.05 * (ty - currentMouse[1]);
|
||||
const m = mu.uMouse.value;
|
||||
m[0] = currentMouse[0];
|
||||
m[1] = currentMouse[1];
|
||||
|
||||
@ -3,26 +3,26 @@
|
||||
<div class="ai-shell-bg" aria-hidden="true">
|
||||
<GradientWaves
|
||||
class="ai-shell-bg__waves"
|
||||
horizon-color="#0369a1"
|
||||
wave-color="#0ea5c9"
|
||||
crest-color="#e0f7fc"
|
||||
:speed="waveMotion ? 0.2 : 0"
|
||||
:amplitude="waveMotion ? 2.7 : 0"
|
||||
:wave-scale="0.64"
|
||||
:wave-ratio="0.85"
|
||||
:swell="30"
|
||||
:turbulence="waveMotion ? 10 : 0"
|
||||
:tilt="1.06"
|
||||
horizon-color="#023e8a"
|
||||
wave-color="#0077b6"
|
||||
crest-color="#caf0f8"
|
||||
:speed="0.34"
|
||||
:amplitude="3.3"
|
||||
:wave-scale="0.72"
|
||||
:wave-ratio="0.82"
|
||||
:swell="42"
|
||||
:turbulence="26"
|
||||
:tilt="1.08"
|
||||
:zoom="0.92"
|
||||
:height="4.8"
|
||||
:fog-depth="22"
|
||||
detail="low"
|
||||
:brightness="1.02"
|
||||
:opacity="0.82"
|
||||
:mouse-interaction="false"
|
||||
:parallax-strength="0"
|
||||
:brightness="1.08"
|
||||
:opacity="1.0"
|
||||
:mouse-interaction="true"
|
||||
:parallax-strength="0.4"
|
||||
:grain="false"
|
||||
:fps="waveMotion ? 28 : 12"
|
||||
:fps="30"
|
||||
:render-scale="0.72"
|
||||
:max-dpr="1.25"
|
||||
/>
|
||||
@ -339,7 +339,6 @@
|
||||
const growthDoors = HOME_DOORS.filter((d) => !d.primary);
|
||||
const shortTitle = ref(false);
|
||||
const titleAnimate = ref(true);
|
||||
const waveMotion = ref(true);
|
||||
const draft = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
@ -381,7 +380,6 @@
|
||||
|
||||
function syncMediaPrefs() {
|
||||
shortTitle.value = !!mqNarrow?.matches;
|
||||
waveMotion.value = !mqReduce?.matches;
|
||||
titleAnimate.value = !mqReduce?.matches;
|
||||
}
|
||||
|
||||
|
||||
@ -56,7 +56,8 @@
|
||||
}>();
|
||||
|
||||
const partners = SITE_PARTNERS;
|
||||
const loop = !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
/** Fixed strip — no auto-scroll / drag loop */
|
||||
const loop = false;
|
||||
const PX_PER_SEC = 26;
|
||||
|
||||
const viewRef = ref<HTMLElement | null>(null);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
body.command-home {
|
||||
background: var(--kc-page-bg);
|
||||
background: #f3f5f6;
|
||||
color: var(--kc-text);
|
||||
font-family: var(--font-default);
|
||||
font-size: 1.05rem;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@
|
||||
import { computed, defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Modal } from '@arco-design/web-vue'
|
||||
import { IconDelete, IconPlus, IconQuestionCircle } from '@arco-design/web-vue/es/icon'
|
||||
import { IconDelete, IconDown, IconPlus, IconQuestionCircle } from '@arco-design/web-vue/es/icon'
|
||||
import HandsontablePro from '@/components/HandsontablePro/index.vue'
|
||||
import PaginationPro from '@/components/PaginationPro/index.vue'
|
||||
import ModalPro from '@/components/ModalPro/index.vue'
|
||||
@ -51,13 +51,24 @@ interface InboundBoxForm {
|
||||
key: string
|
||||
boxSeqNo: string
|
||||
markBoxNo: string
|
||||
weightG?: number
|
||||
/** 表单展示/录入用 kg;提交时换算为后端 WeightG */
|
||||
weightKg?: number
|
||||
lengthCm?: number
|
||||
widthCm?: number
|
||||
heightCm?: number
|
||||
lines: InboundLineForm[]
|
||||
}
|
||||
|
||||
function gramsToKg(weightG?: number | null) {
|
||||
if (weightG == null || Number.isNaN(Number(weightG))) return undefined
|
||||
return Number((Number(weightG) / 1000).toFixed(3))
|
||||
}
|
||||
|
||||
function kgToGrams(weightKg?: number | null) {
|
||||
if (weightKg == null || Number.isNaN(Number(weightKg))) return undefined
|
||||
return Number((Number(weightKg) * 1000).toFixed(2))
|
||||
}
|
||||
|
||||
interface ReceiveLineForm {
|
||||
lineId: number
|
||||
skuCode: string
|
||||
@ -138,6 +149,10 @@ const formModel = reactive({
|
||||
remark: '',
|
||||
})
|
||||
const boxRows = ref<InboundBoxForm[]>([])
|
||||
/** 复制加箱:按当前箱复制重量/尺寸/SKU/计划 */
|
||||
const batchAddCount = ref(5)
|
||||
const batchAddPresets = [3, 5, 10] as const
|
||||
const batchAddPopoverVisible = ref(false)
|
||||
|
||||
const boxTabItems = computed(() =>
|
||||
boxRows.value.map((box) => {
|
||||
@ -175,8 +190,8 @@ function displayOrDash(value: unknown) {
|
||||
}
|
||||
|
||||
function formatBoxWeight(box: InboundBoxForm) {
|
||||
if (box.weightG == null || Number.isNaN(Number(box.weightG))) return '-'
|
||||
return `${box.weightG} g`
|
||||
if (box.weightKg == null || Number.isNaN(Number(box.weightKg))) return '-'
|
||||
return `${box.weightKg} kg`
|
||||
}
|
||||
|
||||
function formatBoxSize(box: InboundBoxForm) {
|
||||
@ -258,6 +273,28 @@ function createBox(seq: number): InboundBoxForm {
|
||||
}
|
||||
}
|
||||
|
||||
function cloneBoxFrom(source: InboundBoxForm, seq: number): InboundBoxForm {
|
||||
const filledLines = source.lines
|
||||
.filter((l) => l.skuId)
|
||||
.map((l) => ({
|
||||
skuId: l.skuId,
|
||||
skuCode: l.skuCode,
|
||||
plannedQty: Number(l.plannedQty) > 0 ? Number(l.plannedQty) : 1,
|
||||
}))
|
||||
return {
|
||||
key: `box-${Date.now()}-${seq}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
boxSeqNo: String(seq),
|
||||
markBoxNo: `BOX-${String(seq).padStart(3, '0')}`,
|
||||
weightKg: source.weightKg,
|
||||
lengthCm: source.lengthCm,
|
||||
widthCm: source.widthCm,
|
||||
heightCm: source.heightCm,
|
||||
lines: filledLines.length
|
||||
? [...filledLines, { skuId: undefined, skuCode: '', plannedQty: 1 }]
|
||||
: [{ skuId: undefined, skuCode: '', plannedQty: 1 }],
|
||||
}
|
||||
}
|
||||
|
||||
function resetBoxes() {
|
||||
const first = createBox(1)
|
||||
boxRows.value = [first]
|
||||
@ -272,23 +309,49 @@ function addBox() {
|
||||
activeBoxKey.value = box.key
|
||||
}
|
||||
|
||||
function addBoxes(count: number) {
|
||||
function addBoxes(count?: number) {
|
||||
const n = Math.max(1, Math.min(99, Math.floor(Number(count ?? batchAddCount.value) || 1)))
|
||||
batchAddCount.value = n
|
||||
const source =
|
||||
boxRows.value.find((b) => b.key === activeBoxKey.value) || boxRows.value[boxRows.value.length - 1]
|
||||
const start = boxRows.value.length
|
||||
const added = Array.from({ length: count }, (_, i) => createBox(start + i + 1))
|
||||
const added = Array.from({ length: n }, (_, i) =>
|
||||
source ? cloneBoxFrom(source, start + i + 1) : createBox(start + i + 1),
|
||||
)
|
||||
boxRows.value = [...boxRows.value, ...added]
|
||||
if (added.length) activeBoxKey.value = added[added.length - 1].key
|
||||
}
|
||||
|
||||
function confirmBatchAdd() {
|
||||
addBoxes()
|
||||
batchAddPopoverVisible.value = false
|
||||
}
|
||||
|
||||
function resequenceBoxes(boxes: InboundBoxForm[]) {
|
||||
boxes.forEach((box, i) => {
|
||||
const seq = i + 1
|
||||
const prevAutoMark = `BOX-${String(Number(box.boxSeqNo) || 0).padStart(3, '0')}`
|
||||
box.boxSeqNo = String(seq)
|
||||
// 箱号仍是系统默认 BOX-xxx 时同步重排,自定义唛头保留
|
||||
if (!box.markBoxNo?.trim() || box.markBoxNo === prevAutoMark) {
|
||||
box.markBoxNo = `BOX-${String(seq).padStart(3, '0')}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function removeActiveBox() {
|
||||
if (boxRows.value.length <= 1) {
|
||||
$baseMessage(t('warehouse.inbound.message.keepOneBox'), 'warning')
|
||||
return
|
||||
}
|
||||
const key = activeBoxKey.value
|
||||
const idx = boxRows.value.findIndex((b) => b.key === key)
|
||||
const remain = boxRows.value.filter((b) => b.key !== key)
|
||||
resequenceBoxes(remain)
|
||||
boxRows.value = remain
|
||||
selectedBoxKeys.value = selectedBoxKeys.value.filter((k) => k !== key)
|
||||
activeBoxKey.value = remain[remain.length - 1]?.key || ''
|
||||
const nextIdx = Math.min(Math.max(idx, 0), remain.length - 1)
|
||||
activeBoxKey.value = remain[nextIdx]?.key || remain[remain.length - 1]?.key || ''
|
||||
}
|
||||
|
||||
function removeLine(box: InboundBoxForm, index: number) {
|
||||
@ -599,7 +662,7 @@ async function openInboundForm(row: any) {
|
||||
key: `box-open-${pickField(b, 'id', 'Id') || index}-${Date.now()}`,
|
||||
boxSeqNo: String(pickField(b, 'boxSeqNo', 'BoxSeqNo') ?? index + 1),
|
||||
markBoxNo: String(pickField(b, 'markBoxNo', 'MarkBoxNo') ?? ''),
|
||||
weightG: pickField<number>(b, 'weightG', 'WeightG') ?? undefined,
|
||||
weightKg: gramsToKg(pickField<number>(b, 'weightG', 'WeightG')),
|
||||
lengthCm: pickField<number>(b, 'lengthCm', 'LengthCm') ?? undefined,
|
||||
widthCm: pickField<number>(b, 'widthCm', 'WidthCm') ?? undefined,
|
||||
heightCm: pickField<number>(b, 'heightCm', 'HeightCm') ?? undefined,
|
||||
@ -638,7 +701,7 @@ async function handleSave() {
|
||||
.map((box) => ({
|
||||
boxSeqNo: box.boxSeqNo,
|
||||
markBoxNo: box.markBoxNo,
|
||||
weightG: box.weightG,
|
||||
weightG: kgToGrams(box.weightKg),
|
||||
lengthCm: box.lengthCm,
|
||||
widthCm: box.widthCm,
|
||||
heightCm: box.heightCm,
|
||||
@ -1020,9 +1083,60 @@ onMounted(async () => {
|
||||
<template #icon><IconPlus :size="12" /></template>
|
||||
{{ t('warehouse.inbound.actions.addBox') }}
|
||||
</a-button>
|
||||
<a-button size="mini" @click="addBoxes(5)">
|
||||
{{ t('warehouse.inbound.actions.addBox5') }}
|
||||
</a-button>
|
||||
<a-popover
|
||||
v-model:popup-visible="batchAddPopoverVisible"
|
||||
trigger="click"
|
||||
position="bl"
|
||||
content-class="inbound-batch-add-popover"
|
||||
>
|
||||
<a-button size="mini">
|
||||
{{ t('warehouse.inbound.actions.copyAddBox') }}
|
||||
<IconDown :size="12" class="inbound-batch-add-trigger-icon" />
|
||||
</a-button>
|
||||
<template #content>
|
||||
<div class="inbound-batch-add">
|
||||
<div class="inbound-batch-add__title">
|
||||
{{ t('warehouse.inbound.actions.copyAddBoxTitle') }}
|
||||
</div>
|
||||
<div class="inbound-batch-add__hint">
|
||||
{{ t('warehouse.inbound.tip.copyAddBox') }}
|
||||
</div>
|
||||
<div class="inbound-batch-add__presets">
|
||||
<a-button
|
||||
v-for="n in batchAddPresets"
|
||||
:key="n"
|
||||
size="mini"
|
||||
:class="{ 'app-btn-primary-soft': batchAddCount === n }"
|
||||
@click="batchAddCount = n"
|
||||
>
|
||||
{{ n }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="inbound-batch-add__row">
|
||||
<span class="inbound-batch-add__label">
|
||||
{{ t('warehouse.inbound.form.batchAddCount') }}
|
||||
</span>
|
||||
<a-input-number
|
||||
v-model="batchAddCount"
|
||||
:min="1"
|
||||
:max="99"
|
||||
size="mini"
|
||||
hide-button
|
||||
class="inbound-batch-add__n"
|
||||
/>
|
||||
</div>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
long
|
||||
class="inbound-batch-add__confirm"
|
||||
@click="confirmBatchAdd"
|
||||
>
|
||||
{{ t('warehouse.inbound.actions.addBoxN', { n: batchAddCount || 1 }) }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<a-button
|
||||
size="mini"
|
||||
status="danger"
|
||||
@ -1075,13 +1189,14 @@ onMounted(async () => {
|
||||
<div class="inbound-box-meta__field inbound-box-meta__field--weight">
|
||||
<span class="inbound-box-meta__label">{{ t('warehouse.inbound.form.boxWeight') }}</span>
|
||||
<a-input-number
|
||||
v-model="box.weightG"
|
||||
v-model="box.weightKg"
|
||||
:min="0"
|
||||
:precision="3"
|
||||
hide-button
|
||||
style="width: 100%"
|
||||
:placeholder="t('warehouse.inbound.form.optional')"
|
||||
>
|
||||
<template #suffix>g</template>
|
||||
<template #suffix>kg</template>
|
||||
</a-input-number>
|
||||
</div>
|
||||
<div class="inbound-box-meta__field inbound-box-meta__field--size">
|
||||
@ -1230,6 +1345,14 @@ onMounted(async () => {
|
||||
|
||||
.wh-form-box-tabs {
|
||||
margin-bottom: 12px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.inbound-batch-add-trigger-icon {
|
||||
margin-left: 2px;
|
||||
vertical-align: -1px;
|
||||
}
|
||||
|
||||
.inbound-view-basic {
|
||||
@ -1387,3 +1510,53 @@ onMounted(async () => {
|
||||
min-height: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- popover 内容 teleport 到 body,需非 scoped -->
|
||||
<style lang="less">
|
||||
.inbound-batch-add-popover {
|
||||
.inbound-batch-add {
|
||||
width: 220px;
|
||||
|
||||
&__title {
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
&__hint {
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
&__presets {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
&__n {
|
||||
width: 72px;
|
||||
}
|
||||
|
||||
&__confirm {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user