Files
----/前端源码/uni-app/components/screens/AuspiciousResult.vue

1370 lines
33 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="aus-result">
<view class="aus-result-texture"></view>
<!-- 固定Header -->
<view ref="fixedHeaderRef" class="aus-result-fixed-header">
<view class="status-bar-placeholder" :style="{ height: statusBarHeight }"></view>
<view class="aus-result-header">
<view class="aus-result-back" @click="$emit('back')"></view>
<text class="aus-result-title">择吉结果</text>
<view class="aus-result-header-spacer"></view>
</view>
</view>
<!-- 头部占位 -->
<view class="aus-result-header-placeholder" :style="{ height: headerPlaceholderHeight }"></view>
<scroll-view scroll-y class="aus-result-scroll">
<view class="aus-result-container">
<!-- Advice -->
<view class="aus-advice-card">
<view class="aus-advice-glow"></view>
<view class="aus-advice-header">
<view class="aus-advice-icon"></view>
<view>
<text class="aus-advice-title">择吉分析报告</text>
<text class="aus-advice-sub">福主: {{ data.name }} · {{ data.birth_date }}</text>
</view>
</view>
<view class="aus-advice-body">
<view class="aus-advice-bullet"></view>
<text class="aus-advice-text">
{{ advice }}
</text>
</view>
</view>
<!-- Dates timeline -->
<view class="aus-timeline">
<view class="aus-timeline-line"></view>
<view class="aus-timeline-list">
<view v-for="(item, idx) in resolvedDates" :key="item.date + '_' + idx" class="aus-timeline-item"
@click="handleDate(item, idx)">
<view class="aus-timeline-dot" :class="isLocked(idx) ? 'is-locked' : 'is-open'"></view>
<view class="aus-date-card" :class="isLocked(idx) ? 'is-locked' : 'is-open'">
<view v-if="isLocked(idx)" class="aus-date-lock">
<view class="aus-date-lock-icon">🔒</view>
<view class="aus-date-lock-text">解锁后查看完整吉时与冲煞</view>
</view>
<view class="aus-date-inner">
<view class="aus-date-head">
<view>
<text class="aus-date-day">{{ item.date }}</text>
<text class="aus-date-lunar">{{ item.lunar }}</text>
</view>
<view class="aus-date-score">
<text class="aus-date-score-num">{{ item.score }}</text>
<text class="aus-date-score-label">吉运指数</text>
</view>
</view>
<view class="aus-date-desc">
<text>{{ item.desc }}</text>
</view>
<view class="aus-date-meta">
<view class="aus-date-meta-item">
<text>吉时</text>
<text>{{ displayHours(item, idx) }}</text>
</view>
<view class="aus-date-meta-item">
<text></text>
<text>{{ displayClash(item, idx) }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view v-if="!resolvedDates.length" class="aus-empty">
<text class="aus-empty-title">{{ repairingDates ? '正在恢复择吉结果...' : '暂无可展示的择吉日期' }}</text>
<text class="aus-empty-sub">
{{ repairingDates ? '系统正在自动补全数据,请稍候' : (repairError || '请返回重试,或稍后再查看报告结果') }}
</text>
<button v-if="!repairingDates" class="aus-empty-action" @click="attemptRecalculate(true)">
重新获取择吉结果
</button>
</view>
<view class="aus-bottom-gap"></view>
</view>
</scroll-view>
<!-- Pay sheet -->
<view v-if="false && showPay && unlocked !== 'premium'" class="aus-paymask" @tap="showPay = false" @touchmove.stop.prevent>
<view class="aus-paycard" @tap.stop>
<view class="aus-pay-title-row">
<text class="aus-pay-title">解锁完整择吉报告</text>
</view>
<view class="aus-pay-close" @tap="showPay = false">×</view>
<view class="aus-pay-cards">
<button class="aus-pay-card aus-pay-card-basic" @click="unlock('basic')">
<view class="aus-pay-card-title">近期吉日</view>
<view class="aus-pay-card-sub">未来30天精选</view>
<view class="aus-pay-price">
<text class="aus-pay-price-sign">¥</text>
<text class="aus-pay-price-num">9.9</text>
</view>
</button>
<button class="aus-pay-card aus-pay-card-premium" @click="unlock('premium')">
<view class="aus-pay-card-top">
<view class="aus-pay-card-title">全年尊享</view>
<view class="aus-pay-chip">推荐</view>
</view>
<view class="aus-pay-card-sub">1年吉日 + 时辰</view>
<view class="aus-pay-price">
<text class="aus-pay-price-sign">¥</text>
<text class="aus-pay-price-num">38</text>
<text class="aus-pay-price-origin">99.9</text>
</view>
</button>
</view>
<button class="aus-pay-share" open-type="share" @click="prepareShareUnlock">
<text class="aus-pay-share-text">分享到朋友圈免费解锁</text>
</button>
<text class="aus-pay-footnote">*每月限 1 次免费解锁机会</text>
</view>
</view>
<!-- Poster -->
<view v-if="showPoster && activeDate" class="aus-poster-mask" @click="showPoster = false">
<view class="aus-poster" @click.stop>
<div class="aus-poster-close" @click="showPoster = false">×</div>
<view class="aus-poster-body">
<view class="aus-poster-bar"></view>
<view class="aus-poster-content">
<view class="aus-poster-icon"></view>
<text class="aus-poster-title">{{ label(data.zeji_type, data.zeji_purpose) }}大吉</text>
<text class="aus-poster-sub">壹梵玄学 · 精准八字择吉</text>
<view class="aus-poster-card">
<view class="aus-poster-card-line"></view>
<text class="aus-poster-date">{{ activeDate.date }}</text>
<text class="aus-poster-lunar">{{ activeDate.lunar }}</text>
<view class="aus-poster-meta">
<view class="aus-poster-meta-block">
<text class="aus-poster-meta-label">吉时</text>
<text class="aus-poster-meta-text">{{ activeDate.hours }}</text>
</view>
</view>
<view class="aus-poster-divider"></view>
<text class="aus-poster-desc">{{ activeDate.desc }}</text>
</view>
</view>
<!-- <view class="aus-poster-footer">
<view class="aus-poster-owner">
<text class="aus-poster-owner-label">福主</text>
<text class="aus-poster-owner-name">{{ data.name }}</text>
</view>
<view class="aus-poster-qr">
<view class="aus-poster-qr-box"></view>
<text class="aus-poster-qr-tip">扫码测算</text>
</view>
</view> -->
</view>
<!-- <view class="aus-poster-actions">
<button class="aus-poster-btn aus-poster-btn-dark">保存图片</button>
<button class="aus-poster-btn aus-poster-btn-gold" @click="showPoster=false">关闭</button>
</view> -->
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { baziZejiApi, type BaziZejiCalculateRequest, type BaziZejiCalculateResponse } from "../../api";
const props = defineProps<{ data: BaziZejiCalculateResponse | Record<string, any> | null }>();
defineEmits<{
back: [];
}>();
type UnlockLevel = "none" | "basic" | "premium";
type AnyRecord = Record<string, any>;
const unlocked = ref<UnlockLevel>("premium");
const showPay = ref(false);
const showPoster = ref(false);
const activeDate = ref<AnyRecord | null>(null);
const fixedHeaderRef = ref<any>(null);
const headerPlaceholderHeight = ref("120px");
const repairingDates = ref(false);
const repairedOnce = ref(false);
const repairError = ref("");
const hydratedData = ref<AnyRecord | null>(null);
declare const uni: any;
const statusBarHeight = ref("0px");
try {
const systemInfo = uni?.getSystemInfoSync?.();
statusBarHeight.value = `${Number(systemInfo?.statusBarHeight || 20)}px`;
} catch {
statusBarHeight.value = "44px";
}
const updateHeaderPlaceholderHeight = () => {
if (typeof window === "undefined") return;
const el =
(fixedHeaderRef.value?.$el as HTMLElement | undefined) ||
(fixedHeaderRef.value as HTMLElement | null) ||
(document.querySelector(".aus-result-fixed-header") as HTMLElement | null);
if (!el) return;
const measured = Math.ceil(el.getBoundingClientRect().height);
if (measured > 0) {
headerPlaceholderHeight.value = `${measured}px`;
return;
}
headerPlaceholderHeight.value = `calc(${statusBarHeight.value} + 120rpx)`;
};
const DEFAULT_ADVICE = "顺应天时,趋吉避凶。所选吉日均为黄道瑞日,结合福主八字喜用神,可助事半功倍,平安顺遂。";
const tryParseJson = (value: any): any => {
if (value == null) return null;
if (typeof value === "object") return value;
if (typeof value !== "string") return null;
const raw = value.trim();
if (!raw) return null;
try {
return JSON.parse(raw);
} catch {
return null;
}
};
const toText = (value: any): string => {
if (value == null) return "";
if (typeof value === "string") return value.trim();
if (typeof value === "number" || typeof value === "boolean") return String(value);
return "";
};
const toNumber = (value: any, fallback = 0): number => {
const num = Number(value);
return Number.isFinite(num) ? num : fallback;
};
const firstNonEmpty = (...values: any[]): any => {
for (const value of values) {
if (Array.isArray(value) && value.length) return value;
const text = toText(value);
if (text) return value;
if (value && typeof value === "object" && !Array.isArray(value)) return value;
}
return null;
};
const toStringArray = (value: any): string[] => {
if (Array.isArray(value)) {
return value.map((item) => toText(item)).filter(Boolean);
}
if (typeof value === "string") {
return value
.split(/[,\uFF0C\u3001|/]/)
.map((item) => item.trim())
.filter(Boolean);
}
return [];
};
const normalizeHours = (value: any): string => {
if (Array.isArray(value)) {
return value.map((item) => toText(item)).filter(Boolean).join(", ");
}
if (value && typeof value === "object") {
return Object.values(value)
.map((item) => toText(item))
.filter(Boolean)
.join(", ");
}
return toText(value);
};
const looksLikeDateItem = (value: any): boolean => {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
return Boolean(
firstNonEmpty(
value.date,
value.day,
value.selected_date,
value.good_date,
value.target_date,
value.lunar,
value.desc,
value.hours,
value.clash,
value.score
)
);
};
const DATE_LIST_KEYS = [
"dates",
"date_list",
"auspicious_dates",
"good_dates",
"result_dates",
"days",
"list",
"items",
];
const extractDateList = (source: any, depth = 0, visited = new Set<any>()): any[] => {
if (source == null || depth > 8) return [];
const parsed = tryParseJson(source);
if (parsed && parsed !== source) {
return extractDateList(parsed, depth + 1, visited);
}
if (Array.isArray(source)) {
if (source.some((item) => looksLikeDateItem(tryParseJson(item) ?? item))) {
return source;
}
for (const item of source) {
const nested = extractDateList(item, depth + 1, visited);
if (nested.length) return nested;
}
return [];
}
if (typeof source !== "object") return [];
if (visited.has(source)) return [];
visited.add(source);
for (const key of DATE_LIST_KEYS) {
const nested = extractDateList((source as AnyRecord)[key], depth + 1, visited);
if (nested.length) return nested;
}
if (looksLikeDateItem(source)) {
return [source];
}
for (const value of Object.values(source as AnyRecord)) {
const nested = extractDateList(value, depth + 1, visited);
if (nested.length) return nested;
}
return [];
};
const normalizeDateItem = (item: any): AnyRecord | null => {
const value = tryParseJson(item) ?? item;
if (typeof value === "string") {
const date = value.trim();
return date ? { date, lunar: "", desc: "", score: 0, hours: "", clash: "", suitable: [], avoid: [] } : null;
}
if (!value || typeof value !== "object") return null;
const date = toText(
firstNonEmpty(
value.date,
value.day,
value.selected_date,
value.good_date,
value.target_date,
value.gregorian_date,
value.solar_date
)
);
const lunar = toText(firstNonEmpty(value.lunar, value.lunar_date, value.nongli));
const desc = toText(firstNonEmpty(value.desc, value.description, value.reason, value.summary, value.comment));
const hours = normalizeHours(
firstNonEmpty(value.hours, value.good_hours, value.lucky_hours, value.best_hours, value.time_ranges, value.time)
);
const clash = toText(firstNonEmpty(value.clash, value.chong, value.conflict, value.avoid_zodiac));
const score = toNumber(firstNonEmpty(value.score, value.rate, value.rating), 0);
const suitable = toStringArray(firstNonEmpty(value.suitable, value.yi, value.recommend, value.fit));
const avoid = toStringArray(firstNonEmpty(value.avoid, value.ji, value.taboo, value.not_fit));
if (!date && !desc && !hours && !clash) return null;
return { date, lunar, desc, score, hours, clash, suitable, avoid };
};
const uniqueBy = (items: AnyRecord[]): AnyRecord[] => {
const seen = new Set<string>();
const result: AnyRecord[] = [];
for (const item of items) {
const key = `${item.date}|${item.lunar}|${item.desc}|${item.hours}|${item.clash}`;
if (seen.has(key)) continue;
seen.add(key);
result.push(item);
}
return result;
};
const sourceData = computed<AnyRecord | null>(() => {
return (hydratedData.value || props.data || null) as AnyRecord | null;
});
const resolvedDates = computed<AnyRecord[]>(() => {
const rawList = extractDateList(sourceData.value);
const normalized = uniqueBy(
rawList.map((item) => normalizeDateItem(item)).filter(Boolean) as AnyRecord[]
);
if (normalized.length) return normalized;
const fallback = normalizeDateItem(sourceData.value);
return fallback ? [fallback] : [];
});
const safeData = computed<AnyRecord>(() => {
const base =
sourceData.value && typeof sourceData.value === "object" && !Array.isArray(sourceData.value)
? (sourceData.value as AnyRecord)
: {};
const nested = [
base.data,
base.result,
base.report,
base.payload,
tryParseJson(base.result_json),
tryParseJson(base.result_data),
tryParseJson(base.report_json),
tryParseJson(base.detail_json),
tryParseJson(base.extra_data),
];
const merged: AnyRecord = { ...base };
for (const candidate of nested) {
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
Object.assign(merged, candidate);
}
}
return merged;
});
const advice = computed(() => {
return (
toText(
firstNonEmpty(
safeData.value.advice,
safeData.value.summary,
safeData.value.recommendation,
safeData.value.description
)
) || DEFAULT_ADVICE
);
});
const normalizeGender = (input: any): "male" | "female" | null => {
const value = toText(input).toLowerCase();
if (!value) return null;
if (value === "male" || value === "m" || value === "1" || value === "男") return "male";
if (value === "female" || value === "f" || value === "0" || value === "女") return "female";
return null;
};
const buildRepairRequest = (): BaziZejiCalculateRequest | null => {
const data = safeData.value;
const name = toText(firstNonEmpty(data.name, data.user_name, data.username));
const gender = normalizeGender(firstNonEmpty(data.gender, data.sex, data.user_gender));
const birthDate = toText(firstNonEmpty(data.birth_date, data.birthDate, data.birth_date_display));
const birthDateApi = toText(firstNonEmpty(data.birth_date_api, data.birth_datetime, data.birth_date_api_value, data.birth_date));
const birthPlace = toText(firstNonEmpty(data.birth_place, data.birthPlace, data.city, data.location));
const zejiType = toText(firstNonEmpty(data.zeji_type, data.event_type, data.type));
const zejiPurpose = toText(firstNonEmpty(data.zeji_purpose, data.event_name, data.purpose, data.target));
const dateRangeStart = toText(firstNonEmpty(data.date_range_start, data.start_date, data.range_start));
const dateRangeEnd = toText(firstNonEmpty(data.date_range_end, data.end_date, data.range_end));
if (!name || !gender || !birthDate || !birthDateApi || !birthPlace || !zejiType || !zejiPurpose || !dateRangeStart || !dateRangeEnd) {
return null;
}
return {
name,
gender,
birth_date: birthDate,
birth_date_api: birthDateApi,
birth_place: birthPlace,
zeji_type: zejiType as any,
zeji_purpose: zejiPurpose,
date_range_start: dateRangeStart,
date_range_end: dateRangeEnd,
};
};
const attemptRecalculate = async (force = false) => {
if (repairingDates.value) return;
if (resolvedDates.value.length && !force) return;
if (repairedOnce.value && !force) return;
const request = buildRepairRequest();
if (!request) {
repairError.value = "缺少关键参数,无法自动恢复,请返回择吉表单重新提交。";
return;
}
repairedOnce.value = true;
repairingDates.value = true;
repairError.value = "";
try {
const result = await baziZejiApi.calculateBaziZeji(request);
hydratedData.value = result as AnyRecord;
} catch (error: any) {
repairError.value = toText(error?.message || error?.msg) || "自动恢复失败,请稍后重试";
} finally {
repairingDates.value = false;
nextTick(updateHeaderPlaceholderHeight);
}
};
const label = (type: string, purpose?: string) => {
const map: Record<string, string> = {
wedding: "婚嫁择吉",
business: "开业择吉",
move: "搬家择吉",
travel: "出行择吉",
investment: "投资择吉",
surgery: "手术择吉",
contract: "签约择吉",
other: purpose || "择吉",
};
return map[type] || purpose || "择吉";
};
const isLocked = (_idx: number) => false;
const displayHours = (item: AnyRecord, idx: number): string => {
if (isLocked(idx)) return "解锁查看";
const hours = normalizeHours(item?.hours);
if (!hours) return "暂无";
const segments = hours
.split(/[,\uFF0C\u3001|/]/)
.map((value) => value.trim())
.filter(Boolean);
return segments.length > 1 ? `${segments[0]}...` : segments[0];
};
const displayClash = (item: AnyRecord, idx: number): string => {
if (isLocked(idx)) return "解锁查看";
return toText(item?.clash) || "暂无";
};
const prepareShareUnlock = () => {
showPay.value = false;
};
const handleDate = (item: AnyRecord, _idx: number) => {
activeDate.value = item;
showPoster.value = true;
};
const unlock = async (_level: "basic" | "premium") => {
unlocked.value = "premium";
showPay.value = false;
};
onMounted(() => {
nextTick(() => {
updateHeaderPlaceholderHeight();
setTimeout(updateHeaderPlaceholderHeight, 0);
});
if (typeof window !== "undefined") {
window.addEventListener("resize", updateHeaderPlaceholderHeight);
}
// 不在此自动调用 calculate 接口:会重复创建择吉任务(例如从「我的方案」进入详情、
// 或返回上一页时若误触发 watch/挂载)。需要补算时由用户点击「重新获取择吉结果」。
});
onBeforeUnmount(() => {
if (typeof window !== "undefined") {
window.removeEventListener("resize", updateHeaderPlaceholderHeight);
}
});
watch(
() => props.data,
() => {
hydratedData.value = null;
repairedOnce.value = false;
repairError.value = "";
nextTick(() => {
updateHeaderPlaceholderHeight();
});
},
{ deep: true }
);
</script>
<style scoped>
.aus-result {
height: 100%;
display: flex;
flex-direction: column;
background: #f0efe9;
position: relative;
overflow: hidden;
font-family: SimSun, "Songti SC", "Songti TC", "Noto Serif SC", STSong, serif;
}
.aus-result-texture {
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0.4;
mix-blend-mode: multiply;
background-image: url("https://www.transparenttextures.com/patterns/rice-paper.png");
z-index: 0;
}
/* 固定头部容器 */
.aus-result-fixed-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: #f0efe9;
}
/* 状态栏占位 */
.status-bar-placeholder {
width: 100%;
}
/* 头部占位 */
.aus-result-header-placeholder {
height: calc(var(--status-bar-height, 0) + 100rpx);
flex-shrink: 0;
}
.aus-result-header {
position: relative;
z-index: 10;
padding: 24rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(253, 251, 247, 0.9);
border-bottom: 1px solid #e5e5e5;
backdrop-filter: blur(8px);
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.05);
}
.aus-result-back {
padding: 16rpx;
margin-left: -8rpx;
color: #5a5a5a;
background: transparent;
border: none;
}
.aus-result-title {
font-size: 18px;
font-weight: bold;
color: #2c2c2c;
letter-spacing: 0.3em;
}
.aus-result-header-spacer {
width: 32rpx;
}
.aus-result-scroll {
flex: 1;
position: relative;
z-index: 5;
padding: 24rpx 24rpx 40rpx;
height: 0;
}
.aus-result-container {
width: 96%;
display: flex;
flex-direction: column;
gap: 32rpx;
}
.aus-advice-card {
position: relative;
background: linear-gradient(135deg, #2c2c2c, #1a1a1a);
color: #f2e6d8;
border-radius: 20rpx;
padding: 32rpx;
overflow: hidden;
box-shadow: 0 16rpx 24rpx -12rpx rgba(0, 0, 0, 0.35);
}
.aus-advice-glow {
position: absolute;
top: -24rpx;
right: -24rpx;
width: 160rpx;
height: 160rpx;
background: #d4af37;
opacity: 0.18;
filter: blur(80rpx);
border-radius: 50%;
}
.aus-advice-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 16rpx;
}
.aus-advice-icon {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: #d4af37;
color: #1a1a1a;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 22px;
border: 2rpx solid rgba(255, 255, 255, 0.2);
}
.aus-advice-title {
font-size: 18px;
font-weight: bold;
color: #f2e6d8;
display: block;
}
.aus-advice-sub {
font-size: 10px;
color: #ccc;
letter-spacing: 0.1em;
}
.aus-advice-body {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12rpx;
padding: 20rpx;
display: flex;
align-items: flex-start;
gap: 10rpx;
backdrop-filter: blur(4px);
}
.aus-advice-bullet {
color: #d4af37;
margin-top: 2rpx;
flex-shrink: 0;
}
.aus-advice-text {
font-size: 14px;
line-height: 1.7;
color: #e5e5e5;
}
.aus-timeline {
position: relative;
}
.aus-timeline-line {
position: absolute;
left: 48rpx;
top: 16rpx;
bottom: 16rpx;
width: 2rpx;
background: #dcd3c9;
}
.aus-timeline-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.aus-timeline-item {
position: relative;
padding-left: 88rpx;
}
.aus-timeline-dot {
position: absolute;
left: 42rpx;
top: 32rpx;
width: 12rpx;
height: 12rpx;
border-radius: 50%;
border: 4rpx solid #f0efe9;
z-index: 2;
}
.aus-timeline-dot.is-open {
background: #8b2323;
}
.aus-timeline-dot.is-locked {
background: #ccc;
}
.aus-date-card {
position: relative;
background: #fffdf9;
border: 1px solid #d4af37;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 10rpx 16rpx -8rpx rgba(0, 0, 0, 0.16);
}
.aus-date-card.is-locked {
border-color: #e5e5e5;
opacity: 0.85;
}
.aus-date-card.is-open {
border-color: rgba(212, 175, 55, 0.3);
}
.aus-date-lock {
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(2px);
z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
}
.aus-date-lock-icon {
width: 64rpx;
height: 64rpx;
background: #2c2c2c;
color: #d4af37;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 14rpx rgba(0, 0, 0, 0.2);
}
.aus-date-lock-text {
font-size: 12px;
font-weight: bold;
color: #2c2c2c;
background: rgba(255, 255, 255, 0.8);
padding: 6rpx 12rpx;
border-radius: 8rpx;
}
.aus-date-inner {
padding: 24rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.aus-date-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12rpx;
}
.aus-date-day {
font-size: 20px;
font-weight: bold;
color: #2c2c2c;
display: block;
}
.aus-date-lunar {
font-size: 12px;
color: #8b2323;
}
.aus-date-score {
text-align: right;
}
.aus-date-score-num {
font-size: 24px;
font-weight: bold;
color: #d4af37;
display: block;
}
.aus-date-score-label {
font-size: 10px;
color: #999;
}
.aus-date-desc {
background: #f9f7f2;
border-radius: 12rpx;
padding: 14rpx;
font-size: 12px;
color: #5a5a5a;
line-height: 1.6;
}
.aus-date-meta {
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid #f0f0f0;
padding-top: 12rpx;
font-size: 10px;
color: #999;
}
.aus-date-meta-item {
display: flex;
align-items: center;
gap: 6rpx;
}
.aus-empty {
background: #fffdf9;
border: 1px dashed #d4af37;
border-radius: 16rpx;
padding: 28rpx 24rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
margin-left: 88rpx;
}
.aus-empty-title {
font-size: 14px;
font-weight: bold;
color: #2c2c2c;
}
.aus-empty-sub {
font-size: 12px;
color: #6a6a6a;
line-height: 1.6;
}
.aus-empty-action {
margin-top: 8rpx;
align-self: flex-start;
background: #2c2c2c;
color: #fdfbf7;
border: none;
border-radius: 999rpx;
padding: 0 20rpx;
height: 56rpx;
line-height: 56rpx;
font-size: 12px;
}
.aus-bottom-gap {
height: 120rpx;
}
.aus-paymask {
position: fixed;
inset: 0;
z-index: 10000;
display: flex;
flex-direction: column;
justify-content: flex-end;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(6px);
}
.aus-pay-close {
position: absolute;
right: 28rpx;
top: 24rpx;
width: 56rpx;
height: 56rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.06);
color: #5a5a5a;
font-size: 22px;
}
.aus-paycard {
background: #fdfbf7;
border-top-left-radius: 24rpx;
border-top-right-radius: 24rpx;
position: relative;
padding: 40rpx 32rpx;
padding-bottom: calc(150rpx + env(safe-area-inset-bottom, 0px));
}
.aus-pay-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.aus-pay-title {
font-weight: bold;
color: #2c2c2c;
font-size: 18px;
}
.aus-pay-badge {
font-size: 10px;
color: #8b2323;
background: rgba(139, 35, 35, 0.1);
padding: 6rpx 12rpx;
border-radius: 999rpx;
border: 1rpx solid rgba(139, 35, 35, 0.2);
}
.aus-pay-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 30rpx;
margin-bottom: 12rpx;
}
.aus-pay-card {
border-radius: 16rpx;
padding: 14rpx;
border: 1rpx solid #e5e5e5;
text-align: left;
box-shadow: 0 10rpx 20rpx -12rpx rgba(0, 0, 0, 0.16);
display: flex;
flex-direction: column;
gap: 2rpx;
width: 100%;
}
.aus-pay-card-basic {
background: #fff;
color: #2c2c2c;
}
.aus-pay-card-premium {
background: #2c2c2c;
color: #fdfbf7;
border-color: #2c2c2c;
}
.aus-pay-card-title {
font-weight: bold;
font-size: 14px;
}
.aus-pay-card-sub {
font-size: 10px;
color: #5a5a5a;
}
.aus-pay-card-premium .aus-pay-card-sub {
color: #dcd3c9;
}
.aus-pay-price {
display: flex;
align-items: baseline;
gap: 2rpx;
margin-top: 0;
}
.aus-pay-price-sign {
font-size: 11px;
color: inherit;
}
.aus-pay-price-num {
font-size: 20px;
font-weight: bold;
color: inherit;
}
.aus-pay-price-origin {
font-size: 10px;
color: #999;
text-decoration: line-through;
}
.aus-pay-card-premium .aus-pay-price-origin {
color: rgba(253, 251, 247, 0.6);
}
.aus-pay-card-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.aus-pay-chip {
font-size: 10px;
color: #2c2c2c;
background: #f7d774;
padding: 4rpx 10rpx;
border-radius: 999rpx;
}
.aus-pay-share {
width: 100%;
background: #16c05d;
color: #fdfbf7;
border: none;
border-radius: 12rpx;
padding: 20rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
font-weight: bold;
box-shadow: 0 12rpx 24rpx -12rpx rgba(22, 192, 93, 0.5);
}
.aus-pay-share-icon {
font-size: 16px;
}
.aus-pay-share-text {
font-size: 14px;
}
.aus-pay-footnote {
display: block;
text-align: center;
font-size: 10px;
color: #999;
margin-top: 12rpx;
}
.aus-poster-mask {
position: fixed;
inset: 0;
z-index: 70;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(6px);
padding: 16rpx;
}
.aus-poster {
background: #fdfbf7;
width: 100%;
max-width: 640rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 18rpx 32rpx rgba(0, 0, 0, 0.35);
position: relative;
display: flex;
flex-direction: column;
}
.aus-poster-close {
position: absolute;
top: 24rpx;
right: 24rpx;
z-index: 20;
color: #5a5a5a;
background: rgba(255, 255, 255, 0.6);
border-radius: 50%;
padding: 10rpx;
border: none;
width: 15px;
height: 15px;
}
.aus-poster-body {
width: 100%;
background: url("https://www.transparenttextures.com/patterns/rice-paper.png");
padding-bottom: 24rpx;
}
.aus-poster-bar {
height: 8rpx;
background: #8b2323;
width: 100%;
}
.aus-poster-content {
padding: 48rpx 32rpx 0;
text-align: center;
}
.aus-poster-icon {
width: 128rpx;
height: 128rpx;
margin: 0 auto 24rpx;
background: #8b2323;
color: #fdfbf7;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 6rpx solid #d4af37;
box-shadow: 0 10rpx 18rpx rgba(0, 0, 0, 0.2);
font-size: 28px;
font-weight: bold;
}
.aus-poster-title {
font-size: 20px;
font-weight: bold;
color: #2c2c2c;
letter-spacing: 0.2em;
display: block;
margin-bottom: 6rpx;
}
.aus-poster-sub {
font-size: 12px;
color: #5a5a5a;
margin-bottom: 24rpx;
}
.aus-poster-card {
background: #fffdf9;
border: 1px solid #e5e5e5;
border-radius: 12rpx;
padding: 24rpx;
position: relative;
overflow: hidden;
}
.aus-poster-card-line {
position: absolute;
top: 0;
left: 0;
height: 6rpx;
width: 100%;
background: linear-gradient(90deg, transparent, #d4af37, transparent);
}
.aus-poster-date {
font-size: 32px;
font-weight: bold;
color: #8b2323;
display: block;
margin-bottom: 8rpx;
}
.aus-poster-lunar {
font-size: 16px;
font-weight: bold;
color: #2c2c2c;
display: block;
margin-bottom: 12rpx;
}
.aus-poster-meta {
display: flex;
justify-content: center;
align-items: center;
gap: 16rpx;
font-size: 12px;
color: #5a5a5a;
margin-bottom: 16rpx;
}
.aus-poster-meta-block {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
}
.aus-poster-meta-label {
color: #8b2323;
}
.aus-poster-meta-text {
color: #5a5a5a;
}
.aus-poster-divider {
height: 2rpx;
width: 100%;
background: #e5e5e5;
margin: 12rpx 0;
}
.aus-poster-desc {
font-size: 14px;
color: #2c2c2c;
font-style: italic;
}
.aus-poster-footer {
margin-top: 24rpx;
padding: 0 32rpx 24rpx;
display: flex;
align-items: flex-end;
justify-content: space-between;
}
.aus-poster-owner-label {
font-size: 10px;
color: #999;
display: block;
}
.aus-poster-owner-name {
font-size: 14px;
font-weight: bold;
color: #2c2c2c;
}
.aus-poster-qr {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
}
.aus-poster-qr-box {
width: 72rpx;
height: 72rpx;
background: #2c2c2c;
}
.aus-poster-qr-tip {
font-size: 10px;
color: #999;
}
.aus-poster-actions {
background: #fff;
border-top: 1px solid #e5e5e5;
padding: 24rpx 16rpx;
padding-bottom: calc(32rpx + env(safe-area-inset-bottom, 0px));
display: flex;
gap: 12rpx;
}
.aus-poster-btn {
flex: 1;
padding: 16rpx;
border-radius: 12rpx;
font-weight: bold;
border: none;
font-size: 14px;
}
.aus-poster-btn-dark {
background: #2c2c2c;
color: #fdfbf7;
}
.aus-poster-btn-gold {
background: #d4af37;
color: #2c2c2c;
}
</style>