721 lines
21 KiB
Vue
721 lines
21 KiB
Vue
<script lang="ts" setup>
|
||
/**
|
||
* 公账日账单逾期全屏警示:不可点遮罩/Esc/X 关闭
|
||
* 上传在同一锁框内切换展示,不用弹窗、抽屉、整页
|
||
*/
|
||
import { computed, ref, watch } from 'vue';
|
||
|
||
import { useUserStore } from '@vben/stores';
|
||
|
||
import { Button, Modal, message } from 'ant-design-vue';
|
||
|
||
import { useAuthStore } from '#/store';
|
||
import {
|
||
PUBLIC_ACCOUNT_LOCK_PROMPT_Z_INDEX,
|
||
clearPublicAccountLock,
|
||
closePublicAccountLockHandle,
|
||
getPublicAccountLockState,
|
||
normalizeLockAlertColor,
|
||
} from '#/utils/publicAccountLock';
|
||
import {
|
||
generatePublicAccountDailyBill,
|
||
getPublicAccountDailyBillInfo,
|
||
getPublicAccountLockStatus,
|
||
} from '#/views/finance/public-account-pay/api';
|
||
|
||
import LockPayPanel from './LockPayPanel.vue';
|
||
import LockWaitPanel from './LockWaitPanel.vue';
|
||
import LockOrdersPanel from './LockOrdersPanel.vue';
|
||
import LockWarningCanvas from './LockWarningCanvas.vue';
|
||
|
||
const userStore = useUserStore();
|
||
const authStore = useAuthStore();
|
||
const lockState = getPublicAccountLockState();
|
||
const panelMode = ref<'hint' | 'pay' | 'wait' | 'orders'>('hint');
|
||
const payDailyId = ref(0);
|
||
const openingPay = ref(false);
|
||
const loggingOut = ref(false);
|
||
|
||
const roleId = computed(() =>
|
||
Number(userStore.userInfo?.role_id ?? userStore.userInfo?.roles?.id ?? 0),
|
||
);
|
||
|
||
/** 超管/系统管理员/诊所管理员可在锁框内上传 */
|
||
const canPay = computed(() => [1, 2, 8].includes(roleId.value));
|
||
|
||
/** 硬锁或条幅「去处理」软层 */
|
||
const overlayOpen = computed(
|
||
() => lockState.visible || lockState.handleVisible,
|
||
);
|
||
/** 软层:可返回,不是身份硬锁 */
|
||
const softHandle = computed(
|
||
() => lockState.handleVisible && !lockState.visible,
|
||
);
|
||
|
||
const billDateTxt = computed(() => {
|
||
const date = String(lockState.payload.bill_date || '');
|
||
if (date.length === 8) {
|
||
return `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`;
|
||
}
|
||
return date;
|
||
});
|
||
|
||
/** 多张逾期展示几号到几号;单张回落单日 */
|
||
const billDateRangeTxt = computed(() => {
|
||
const range = String(lockState.payload.bill_date_range_txt || '').trim();
|
||
if (range) {
|
||
return range;
|
||
}
|
||
return billDateTxt.value;
|
||
});
|
||
|
||
const billDateLabel = computed(() =>
|
||
billCount.value > 1 ? '账单区间' : '账单日',
|
||
);
|
||
|
||
const amountTxt = computed(
|
||
() =>
|
||
lockState.payload.platform_amount_total ||
|
||
lockState.payload.platform_amount ||
|
||
'0.00',
|
||
);
|
||
const billCount = computed(() => {
|
||
const n = Number(lockState.payload.bill_count || 0);
|
||
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
||
});
|
||
const mergeHint = computed(() => {
|
||
if (billCount.value > 1) {
|
||
return `合并处理 ${billCount.value} 张(${billDateRangeTxt.value}),一次上传覆盖整段。`;
|
||
}
|
||
return '';
|
||
});
|
||
const alertColor = computed(() =>
|
||
normalizeLockAlertColor(lockState.payload.alert_color),
|
||
);
|
||
const unlockMode = computed(() =>
|
||
String(lockState.payload.unlock_mode || 'confirm'),
|
||
);
|
||
const lockTime = computed(() => lockState.payload.lock_time || '09:00');
|
||
const lockDays = computed(() => {
|
||
const days = Number(lockState.payload.lock_days || 1);
|
||
return Number.isFinite(days) && days >= 1 ? Math.floor(days) : 1;
|
||
});
|
||
/** 锁店说明里的宽限文案:1 天写「次日」,多天写「后第 N 天」 */
|
||
const lockDaysLabel = computed(() =>
|
||
lockDays.value === 1 ? '次日' : `后第 ${lockDays.value} 天`,
|
||
);
|
||
const unlockByVoucher = computed(() => unlockMode.value === 'voucher');
|
||
const contactPhones = computed(() => lockState.payload.contact_phones || []);
|
||
/** 尊敬的{诊所名} */
|
||
const storeGreeting = computed(() => {
|
||
const name = String(lockState.payload.store_name || '').trim();
|
||
return name ? `尊敬的${name}` : '尊敬的诊所管理员';
|
||
});
|
||
/** 锁定原因:不重复账单区间/金额,只点明周期已到 */
|
||
const lockReasonTxt = '账单支付周期已到';
|
||
|
||
/** 日汇总已传凭证才露出进度,0 不能盖掉 3 */
|
||
const canViewProgress = computed(() => {
|
||
const payload = lockState.payload || {};
|
||
return Number(payload.status) === 3 || Number(payload.bill_status) === 3;
|
||
});
|
||
|
||
function applyBillStatus(status: number) {
|
||
if (![0, 1, 2, 3].includes(status)) {
|
||
return;
|
||
}
|
||
lockState.payload.status = status;
|
||
lockState.payload.bill_status = status;
|
||
}
|
||
|
||
/** 锁缓存可能停在 0,打开锁框后用现算状态 + 日汇总详情纠正 */
|
||
async function refreshLockStatus() {
|
||
try {
|
||
const live = await getPublicAccountLockStatus();
|
||
if (live && typeof live === 'object') {
|
||
Object.assign(lockState.payload, live);
|
||
if (Number(live.status) === 3 || Number(live.bill_status) === 3) {
|
||
applyBillStatus(3);
|
||
}
|
||
}
|
||
} catch {
|
||
// 刷新失败仍用当前 payload
|
||
}
|
||
const dailyId = Number(lockState.payload.daily_id || 0);
|
||
if (dailyId < 1) {
|
||
return;
|
||
}
|
||
try {
|
||
const detail = await getPublicAccountDailyBillInfo(dailyId);
|
||
// 周期锁店:合计用 platform_amount_total,勿用单日金额覆盖
|
||
const billCount = Number(lockState.payload.bill_count || 0);
|
||
const hasPeriod =
|
||
billCount > 1 ||
|
||
Number(lockState.payload.merge_id || 0) > 0 ||
|
||
!!String(lockState.payload.platform_amount_total || '').trim();
|
||
if (detail?.platform_amount && !hasPeriod) {
|
||
lockState.payload.platform_amount = detail.platform_amount;
|
||
}
|
||
if (detail?.status != null) {
|
||
applyBillStatus(Number(detail.status));
|
||
}
|
||
} catch {
|
||
// 详情失败不挡锁框
|
||
}
|
||
}
|
||
|
||
async function resolveDailyId(): Promise<number> {
|
||
const existId = Number(lockState.payload.daily_id || 0);
|
||
if (existId > 0) {
|
||
return existId;
|
||
}
|
||
const storeId = Number(lockState.payload.store_id || 0);
|
||
if (storeId < 1 || !billDateTxt.value) {
|
||
return 0;
|
||
}
|
||
const res = await generatePublicAccountDailyBill({
|
||
store_id: storeId,
|
||
bill_date: billDateTxt.value,
|
||
});
|
||
const createdId = Number(res?.id || 0);
|
||
if (createdId > 0) {
|
||
lockState.payload.daily_id = createdId;
|
||
}
|
||
return createdId;
|
||
}
|
||
|
||
async function goPay() {
|
||
openingPay.value = true;
|
||
try {
|
||
const dailyId = await resolveDailyId();
|
||
if (dailyId < 1) {
|
||
message.warning('暂无待支付日账单');
|
||
return;
|
||
}
|
||
payDailyId.value = dailyId;
|
||
panelMode.value = 'pay';
|
||
} finally {
|
||
openingPay.value = false;
|
||
}
|
||
}
|
||
|
||
function backToHint() {
|
||
panelMode.value = 'hint';
|
||
}
|
||
|
||
/** 只有这次上传成功且未解锁,才切到等待审核 */
|
||
function onWaiting(payload?: {
|
||
status?: number;
|
||
bill_status?: number;
|
||
contact_phones?: { name?: string; phone: string }[];
|
||
}) {
|
||
if (payload?.contact_phones?.length) {
|
||
lockState.payload.contact_phones = payload.contact_phones;
|
||
}
|
||
applyBillStatus(3);
|
||
panelMode.value = 'wait';
|
||
}
|
||
|
||
function goWaitReupload() {
|
||
panelMode.value = 'pay';
|
||
}
|
||
|
||
function goWait() {
|
||
if (!canViewProgress.value) {
|
||
message.warning('暂无待审核进度');
|
||
return;
|
||
}
|
||
payDailyId.value = Number(lockState.payload.daily_id || 0);
|
||
panelMode.value = 'wait';
|
||
}
|
||
|
||
/** 点击应付款:只读拉日账单 + 商品订单 */
|
||
function goOrders() {
|
||
panelMode.value = 'orders';
|
||
}
|
||
|
||
const ordersMergeId = computed(() => Number(lockState.payload.merge_id || 0));
|
||
const ordersDailyId = computed(() => Number(lockState.payload.daily_id || 0));
|
||
const ordersOverdueIds = computed(() => {
|
||
const raw = lockState.payload.overdue_daily_ids;
|
||
if (!Array.isArray(raw)) {
|
||
return [] as number[];
|
||
}
|
||
return raw.map((id) => Number(id)).filter((id) => id > 0);
|
||
});
|
||
|
||
/**
|
||
* 锁框不可关闭,提供退出登录以免账号卡在锁店态
|
||
* 确认框抬高 zIndex,避免被锁层(2100)挡住
|
||
*/
|
||
function logout() {
|
||
Modal.confirm({
|
||
title: '提示',
|
||
content: '确定退出登录?',
|
||
okText: '退出登录',
|
||
cancelText: '取消',
|
||
zIndex: PUBLIC_ACCOUNT_LOCK_PROMPT_Z_INDEX,
|
||
onOk: async () => {
|
||
if (loggingOut.value) {
|
||
return;
|
||
}
|
||
loggingOut.value = true;
|
||
try {
|
||
clearPublicAccountLock();
|
||
await authStore.logout(false);
|
||
} finally {
|
||
loggingOut.value = false;
|
||
}
|
||
},
|
||
});
|
||
}
|
||
|
||
watch(
|
||
overlayOpen,
|
||
(open) => {
|
||
document.body.classList.toggle('pap-lock-open', !!open);
|
||
if (!open) {
|
||
panelMode.value = 'hint';
|
||
return;
|
||
}
|
||
panelMode.value = 'hint';
|
||
void refreshLockStatus();
|
||
},
|
||
{ immediate: true },
|
||
);
|
||
</script>
|
||
|
||
<template>
|
||
<Teleport to="body">
|
||
<div v-if="overlayOpen" class="pap-lock-mask">
|
||
<div
|
||
class="pap-lock-panel"
|
||
:class="['is-' + alertColor, { 'is-sub': panelMode !== 'hint' }]"
|
||
role="alertdialog"
|
||
aria-modal="true"
|
||
>
|
||
<LockWarningCanvas
|
||
:active="overlayOpen"
|
||
:tapes-only="panelMode !== 'hint'"
|
||
:color="alertColor"
|
||
/>
|
||
<div class="pap-lock-shell">
|
||
<div class="pap-lock-scroll">
|
||
<LockPayPanel
|
||
v-if="panelMode === 'pay'"
|
||
:daily-id="payDailyId"
|
||
:platform-amount="amountTxt"
|
||
:bill-date-txt="billDateRangeTxt"
|
||
:unlock-mode="unlockMode"
|
||
@back="backToHint"
|
||
@waiting="onWaiting"
|
||
/>
|
||
<LockWaitPanel
|
||
v-else-if="panelMode === 'wait'"
|
||
:daily-id="payDailyId || Number(lockState.payload.daily_id || 0)"
|
||
:platform-amount="amountTxt"
|
||
:bill-date-txt="billDateRangeTxt"
|
||
:contacts="contactPhones"
|
||
:can-pay="canPay"
|
||
@reupload="goWaitReupload"
|
||
/>
|
||
<LockOrdersPanel
|
||
v-else-if="panelMode === 'orders'"
|
||
:merge-id="ordersMergeId"
|
||
:daily-id="ordersDailyId"
|
||
:overdue-daily-ids="ordersOverdueIds"
|
||
@back="backToHint"
|
||
/>
|
||
<template v-else>
|
||
<div class="pap-lock-badge">
|
||
{{ softHandle ? '待处理提醒' : '功能已锁定' }}
|
||
</div>
|
||
<p class="pap-lock-hello">{{ storeGreeting }}</p>
|
||
<h2 class="pap-lock-title">公账日账单已逾期</h2>
|
||
<p class="pap-lock-reason">{{ lockReasonTxt }}</p>
|
||
<p class="pap-lock-desc">
|
||
<template v-if="softHandle">
|
||
请尽快处理。完成后可返回继续工作。
|
||
</template>
|
||
<template v-else>
|
||
接诊、开方已暂停,本提示不可关闭。
|
||
</template>
|
||
<template v-if="mergeHint">
|
||
{{ mergeHint }}
|
||
</template>
|
||
</p>
|
||
<div class="pap-lock-stat">
|
||
<div class="pap-lock-stat-item">
|
||
<span class="pap-lock-stat-label">{{ billDateLabel }}</span>
|
||
<strong>{{ billDateRangeTxt || '—' }}</strong>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
class="pap-lock-stat-item is-clickable"
|
||
@click="goOrders"
|
||
>
|
||
<span class="pap-lock-stat-label">合计应付款(点此查看明细)</span>
|
||
<strong class="is-amount">¥{{ amountTxt }}</strong>
|
||
</button>
|
||
</div>
|
||
<div class="pap-lock-flow">
|
||
<div class="pap-lock-flow-title">解锁流程</div>
|
||
<ol class="pap-lock-flow-list">
|
||
<li>
|
||
账单日{{ lockDaysLabel }} {{ lockTime }}
|
||
后未处理即锁定;更早的账单全天锁定。
|
||
</li>
|
||
<li>诊所管理员在本提示中上传付款凭证(转账截图)。</li>
|
||
<li v-if="unlockByVoucher">提交成功后立刻解锁,接诊和开方可继续。</li>
|
||
<li v-else>提交后仍锁定,等平台管理员确认到账后自动解锁。</li>
|
||
<li v-if="!softHandle">未达解锁条件前,无法关闭本提示,也无法继续业务。</li>
|
||
</ol>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<!-- 底部操作固定在可视区内,不随正文滚走 -->
|
||
<div class="pap-lock-foot">
|
||
<template v-if="panelMode === 'hint'">
|
||
<div v-if="canPay" class="pap-lock-actions">
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
class="pap-lock-btn"
|
||
:loading="openingPay"
|
||
@click="goPay"
|
||
>
|
||
去上传凭证
|
||
</Button>
|
||
<Button
|
||
v-if="canViewProgress"
|
||
size="large"
|
||
class="pap-lock-btn is-ghost"
|
||
@click="goWait"
|
||
>
|
||
查看审核进度
|
||
</Button>
|
||
</div>
|
||
<p v-else class="pap-lock-hint">
|
||
请联系诊所管理员按上方流程上传付款凭证。
|
||
</p>
|
||
</template>
|
||
<button
|
||
v-if="softHandle"
|
||
type="button"
|
||
class="pap-lock-logout"
|
||
@click="closePublicAccountLockHandle"
|
||
>
|
||
返回工作台
|
||
</button>
|
||
<button
|
||
v-else
|
||
type="button"
|
||
class="pap-lock-logout"
|
||
:disabled="loggingOut"
|
||
@click="logout"
|
||
>
|
||
退出登录
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Teleport>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* 遮罩占满视口,子面板用 max-height:100% 才能真正不溢出 */
|
||
.pap-lock-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 2100;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
height: 100%;
|
||
padding: max(10px, env(safe-area-inset-top, 0px))
|
||
max(10px, env(safe-area-inset-right, 0px))
|
||
max(10px, env(safe-area-inset-bottom, 0px))
|
||
max(10px, env(safe-area-inset-left, 0px));
|
||
overflow: hidden;
|
||
background: hsl(var(--background) / 62%);
|
||
backdrop-filter: blur(6px);
|
||
}
|
||
.pap-lock-panel {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-sizing: border-box;
|
||
width: min(820px, 100%);
|
||
max-width: 100%;
|
||
/* 相对遮罩内容盒,保证整框含警戒条都在视口内 */
|
||
max-height: 100%;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
--pap-accent: var(--warning);
|
||
/* 顶留白跟警示三角对齐:矮屏大幅压缩 */
|
||
--pap-pad-top-hint: clamp(52px, 9dvh + 20px, 150px);
|
||
--pap-pad-x: clamp(14px, 3vw, 40px);
|
||
--pap-pad-y: clamp(8px, 1.6dvh, 20px);
|
||
--pap-gap-xs: clamp(2px, 0.5dvh, 6px);
|
||
--pap-gap-sm: clamp(6px, 1dvh, 12px);
|
||
--pap-gap-md: clamp(8px, 1.4dvh, 16px);
|
||
--pap-gap-lg: clamp(8px, 1.8dvh, 20px);
|
||
--pap-block-pad: clamp(8px, 1.2dvh, 14px);
|
||
--pap-fs-badge: clamp(11px, 1dvh + 5px, 12px);
|
||
--pap-fs-hello: clamp(12px, 1dvh + 5px, 15px);
|
||
--pap-fs-title: clamp(17px, 1.5dvh + 8px, 26px);
|
||
--pap-fs-reason: clamp(12px, 0.9dvh + 5px, 13px);
|
||
--pap-fs-desc: clamp(12px, 1dvh + 5px, 14px);
|
||
--pap-fs-label: clamp(11px, 0.8dvh + 5px, 12px);
|
||
--pap-fs-stat: clamp(13px, 1.2dvh + 5px, 18px);
|
||
--pap-fs-flow: clamp(11px, 0.9dvh + 4px, 12px);
|
||
--pap-fs-btn: clamp(14px, 1.1dvh + 5px, 16px);
|
||
--pap-btn-h: clamp(36px, 4.2dvh, 44px);
|
||
--pap-stat-cols: 1fr 1fr;
|
||
--pap-content-w: min(480px, 100%);
|
||
/* 给 canvas 上下警戒条留出露边,避免被底栏背景盖住 */
|
||
--pap-tape-h: 16px;
|
||
background: hsl(var(--card, var(--background)));
|
||
border: 1px solid hsl(var(--pap-accent) / 38%);
|
||
border-radius: clamp(10px, 1.2vw, 16px);
|
||
box-shadow: 0 0 6px hsl(var(--pap-accent) / 18%);
|
||
}
|
||
.pap-lock-panel.is-blue {
|
||
--pap-accent: 217 91% 53%;
|
||
}
|
||
.pap-lock-panel.is-yellow {
|
||
--pap-accent: var(--warning);
|
||
}
|
||
.pap-lock-panel.is-red {
|
||
--pap-accent: 0 72% 51%;
|
||
}
|
||
.pap-lock-panel.is-sub {
|
||
--pap-pad-top-hint: clamp(14px, 2dvh, 28px);
|
||
}
|
||
/* shell:上滚下钉;上下内边距露出警戒条动画 */
|
||
.pap-lock-shell {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
flex: 1 1 auto;
|
||
flex-direction: column;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
min-height: 0;
|
||
max-height: 100%;
|
||
padding-top: var(--pap-tape-h);
|
||
padding-bottom: var(--pap-tape-h);
|
||
}
|
||
.pap-lock-scroll {
|
||
display: flex;
|
||
flex: 1 1 auto;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
min-height: 0;
|
||
padding: var(--pap-pad-top-hint) var(--pap-pad-x) var(--pap-gap-sm);
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
-webkit-overflow-scrolling: touch;
|
||
text-align: center;
|
||
}
|
||
.pap-lock-foot {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
padding: var(--pap-gap-sm) var(--pap-pad-x) var(--pap-pad-y);
|
||
border-top: 1px solid hsl(var(--pap-accent) / 22%);
|
||
/* 半透明,滚动区边缘仍能透出粒子;底边警戒条靠 shell padding 露白 */
|
||
background: hsl(var(--card, var(--background)) / 88%);
|
||
backdrop-filter: blur(4px);
|
||
}
|
||
.pap-lock-badge {
|
||
flex-shrink: 0;
|
||
padding: 3px 10px;
|
||
margin-bottom: var(--pap-gap-sm);
|
||
font-size: var(--pap-fs-badge);
|
||
font-weight: 600;
|
||
letter-spacing: 0.08em;
|
||
color: hsl(var(--pap-accent));
|
||
border: 1px solid hsl(var(--pap-accent) / 40%);
|
||
border-radius: 999px;
|
||
background: hsl(var(--pap-accent) / 12%);
|
||
box-shadow: 0 0 6px hsl(var(--pap-accent) / 18%);
|
||
}
|
||
.pap-lock-hello {
|
||
max-width: min(560px, 100%);
|
||
margin: 0 0 var(--pap-gap-xs);
|
||
padding: 0 4px;
|
||
font-size: var(--pap-fs-hello);
|
||
font-weight: 600;
|
||
line-height: 1.35;
|
||
color: hsl(var(--foreground));
|
||
word-break: break-word;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
.pap-lock-title {
|
||
margin: 0 0 var(--pap-gap-xs);
|
||
font-size: var(--pap-fs-title);
|
||
font-weight: 700;
|
||
line-height: 1.25;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
.pap-lock-reason {
|
||
max-width: min(520px, 100%);
|
||
margin: 0 0 var(--pap-gap-xs);
|
||
font-size: var(--pap-fs-reason);
|
||
line-height: 1.4;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-lock-desc {
|
||
max-width: min(520px, 100%);
|
||
margin: 0 0 var(--pap-gap-md);
|
||
font-size: var(--pap-fs-desc);
|
||
line-height: 1.45;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-lock-stat {
|
||
display: grid;
|
||
grid-template-columns: var(--pap-stat-cols);
|
||
gap: var(--pap-gap-sm);
|
||
width: var(--pap-content-w);
|
||
margin-bottom: var(--pap-gap-md);
|
||
}
|
||
.pap-lock-stat-item {
|
||
min-width: 0;
|
||
padding: var(--pap-block-pad) calc(var(--pap-block-pad) + 2px);
|
||
text-align: left;
|
||
border: 1px solid hsl(var(--pap-accent) / 30%);
|
||
border-radius: 10px;
|
||
background: hsl(var(--card, var(--background)));
|
||
box-shadow: 0 0 6px hsl(var(--pap-accent) / 18%);
|
||
}
|
||
.pap-lock-stat-item.is-clickable {
|
||
cursor: pointer;
|
||
color: inherit;
|
||
font: inherit;
|
||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||
}
|
||
.pap-lock-stat-item.is-clickable:hover {
|
||
border-color: hsl(var(--pap-accent) / 55%);
|
||
box-shadow: 0 0 10px hsl(var(--pap-accent) / 28%);
|
||
}
|
||
.pap-lock-stat-label {
|
||
display: block;
|
||
margin-bottom: var(--pap-gap-xs);
|
||
font-size: var(--pap-fs-label);
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-lock-stat-item strong {
|
||
display: block;
|
||
font-size: var(--pap-fs-stat);
|
||
font-weight: 700;
|
||
line-height: 1.25;
|
||
color: hsl(var(--foreground));
|
||
word-break: break-word;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
.pap-lock-stat-item strong.is-amount {
|
||
color: hsl(var(--pap-accent));
|
||
}
|
||
.pap-lock-flow {
|
||
width: var(--pap-content-w);
|
||
padding: var(--pap-block-pad) calc(var(--pap-block-pad) + 2px);
|
||
margin-bottom: 0;
|
||
text-align: left;
|
||
border: 1px solid hsl(var(--pap-accent) / 30%);
|
||
border-radius: 10px;
|
||
background: hsl(var(--card, var(--background)));
|
||
box-shadow: 0 0 6px hsl(var(--pap-accent) / 18%);
|
||
}
|
||
.pap-lock-flow-title {
|
||
margin-bottom: var(--pap-gap-xs);
|
||
font-size: var(--pap-fs-flow);
|
||
font-weight: 600;
|
||
color: hsl(var(--pap-accent));
|
||
}
|
||
.pap-lock-flow-list {
|
||
margin: 0;
|
||
padding-left: 16px;
|
||
font-size: var(--pap-fs-flow);
|
||
line-height: 1.45;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-lock-actions {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: var(--pap-gap-sm);
|
||
width: var(--pap-content-w);
|
||
}
|
||
.pap-lock-btn {
|
||
width: 100%;
|
||
height: var(--pap-btn-h);
|
||
font-size: var(--pap-fs-btn);
|
||
}
|
||
.pap-lock-btn.is-ghost {
|
||
color: hsl(var(--foreground));
|
||
border-color: hsl(var(--pap-accent) / 40%);
|
||
}
|
||
.pap-lock-hint {
|
||
max-width: var(--pap-content-w);
|
||
margin: 0;
|
||
font-size: var(--pap-fs-desc);
|
||
line-height: 1.45;
|
||
color: hsl(var(--pap-accent));
|
||
}
|
||
.pap-lock-logout {
|
||
margin-top: var(--pap-gap-sm);
|
||
padding: 0;
|
||
font-size: var(--pap-fs-desc);
|
||
line-height: 1.4;
|
||
color: hsl(var(--muted-foreground));
|
||
text-decoration: underline;
|
||
cursor: pointer;
|
||
background: transparent;
|
||
border: 0;
|
||
}
|
||
.pap-lock-logout:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
}
|
||
@media (max-height: 720px) {
|
||
.pap-lock-mask {
|
||
align-items: flex-start;
|
||
}
|
||
.pap-lock-panel {
|
||
/* 矮屏仍双列,省纵向高度;过窄才单列 */
|
||
--pap-pad-top-hint: clamp(44px, 7dvh + 16px, 96px);
|
||
}
|
||
}
|
||
@media (max-width: 560px) {
|
||
.pap-lock-mask {
|
||
padding: 8px;
|
||
}
|
||
.pap-lock-panel {
|
||
--pap-stat-cols: 1fr;
|
||
--pap-pad-x: 12px;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
/* 锁层 z-index 保持 2100;antd message 默认 1010,锁屏后抬到 5000 才能看见提示 */
|
||
body.pap-lock-open .ant-message,
|
||
body.pap-lock-open .ant-notification {
|
||
z-index: 5000 !important;
|
||
}
|
||
/* 退出确认等 Modal 也要压过锁层 */
|
||
body.pap-lock-open .ant-modal-wrap,
|
||
body.pap-lock-open .ant-modal-mask {
|
||
z-index: 5000 !important;
|
||
}
|
||
</style>
|