upload project source code

This commit is contained in:
2026-04-30 18:49:43 +08:00
commit 9b394ba682
2277 changed files with 660945 additions and 0 deletions

View File

@@ -0,0 +1,590 @@
<template>
<view class="auspicious-form">
<view class="auspicious-texture"></view>
<!-- 固定Header -->
<view class="auspicious-fixed-header">
<view class="status-bar-placeholder"></view>
<view class="auspicious-header">
<view class="auspicious-back" @click="$emit('back')"></view>
<text class="auspicious-title">精准八字择吉</text>
<view class="auspicious-header-spacer"></view>
</view>
</view>
<!-- 头部占位 -->
<view class="auspicious-header-placeholder"></view>
<scroll-view scroll-y class="auspicious-scroll">
<view class="auspicious-container">
<view class="auspicious-intro">
<text class="auspicious-intro-title">顺天时 · 得地利 · 人和顺</text>
<text class="auspicious-intro-sub">根据您的生辰八字精准测算最佳黄道吉日</text>
</view>
<!-- 事项 -->
<view class="auspicious-section">
<label class="auspicious-label">
您要求测的事项
</label>
<view class="auspicious-grid">
<button v-for="type in eventTypes" :key="type.id" @click="form.eventType = type.id"
:class="['auspicious-event-card', form.eventType === type.id ? 'is-active' : '']">
<text class="auspicious-event-text">{{ type.label }}</text>
</button>
</view>
<view v-if="form.eventType === 'other'" class="auspicious-custom">
<input v-model="form.customEvent" type="text" placeholder="请输入您要求测的事项 (如: 签约, 出行, 动土...)"
class="auspicious-input" />
</view>
</view>
<!-- 福主信息 -->
<view class="auspicious-section">
<label class="auspicious-label">
福主信息
</label>
<view class="auspicious-card">
<view class="auspicious-field">
<label class="auspicious-field-label">您的姓名</label>
<input v-model="form.name" type="text" placeholder="请输入真实姓名" class="auspicious-field-input" />
</view>
<view class="auspicious-field">
<label class="auspicious-field-label">性别</label>
<view class="auspicious-gender">
<button :class="['auspicious-gender-btn', form.gender === 'male' ? 'is-active' : '']"
@click="form.gender = 'male'">
</button>
<button :class="['auspicious-gender-btn', form.gender === 'female' ? 'is-active' : '']"
@click="form.gender = 'female'">
</button>
</view>
</view>
<view class="auspicious-field">
<label class="auspicious-field-label">出生日期时辰</label>
<view class="auspicious-date-trigger" @click="showDatePicker = true">
<text class="auspicious-date-text" :class="{ 'is-placeholder': !form.birthDateDisplay }">
{{ form.birthDateDisplay || '请选择出生日期时辰' }}
</text>
<text class="auspicious-date-arrow"></text>
</view>
</view>
<view class="auspicious-field">
<label class="auspicious-field-label">出生地</label>
<input v-model="form.birthPlace" type="text" placeholder="请输入出生地(如:临沂市)" class="auspicious-field-input" />
</view>
</view>
</view>
<!-- 择吉目的 -->
<view class="auspicious-section">
<label class="auspicious-label">
择吉目的
</label>
<view class="auspicious-card">
<textarea v-model="form.zejiPurpose" placeholder="请描述您的择吉目的(如:选择结婚吉日,希望婚姻美满幸福)" class="auspicious-textarea"
maxlength="200" />
</view>
</view>
<!-- 期望日期范围 -->
<view class="auspicious-section">
<label class="auspicious-label">
期望日期范围
</label>
<view class="auspicious-card">
<view class="auspicious-field">
<label class="auspicious-field-label">开始日期</label>
<view class="auspicious-date-trigger" @click="showStartDatePicker = true">
<text class="auspicious-date-text" :class="{ 'is-placeholder': !form.dateRangeStartDisplay }">
{{ form.dateRangeStartDisplay || '请选择开始日期' }}
</text>
<text class="auspicious-date-arrow"></text>
</view>
</view>
<view class="auspicious-field">
<label class="auspicious-field-label">结束日期</label>
<view class="auspicious-date-trigger" @click="showEndDatePicker = true">
<text class="auspicious-date-text" :class="{ 'is-placeholder': !form.dateRangeEndDisplay }">
{{ form.dateRangeEndDisplay || '请选择结束日期' }}
</text>
<text class="auspicious-date-arrow"></text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 日期选择器 -->
<MysticDatePicker :is-open="showDatePicker" title="请择良辰" :default-value="form.birthDateDisplay"
@close="showDatePicker = false" @confirm="handleDateConfirm" />
<!-- 开始日期不可晚于今天年份为过去至今年 -->
<MysticDatePicker :is-open="showStartDatePicker" title="选择开始日期" :default-value="form.dateRangeStartDisplay"
:min-year="zejiStartMinYear" :max-year="zejiStartMaxYear" cap-at-today
footer-tip="开始日期不可选择今天之后的日期滑动选择后自动对应农历干支"
@close="showStartDatePicker = false" @confirm="handleStartDateConfirm" />
<!-- 结束日期选择器 -->
<MysticDatePicker :is-open="showEndDatePicker" title="选择结束日期" :default-value="form.dateRangeEndDisplay"
:min-year="zejiExpectRangeMinYear" :max-year="zejiExpectRangeMaxYear"
footer-tip="期望日期区间支持选择至未来多年滑动选择后自动对应农历干支"
@close="showEndDatePicker = false" @confirm="handleEndDateConfirm" />
<!-- Footer -->
<view class="auspicious-footer">
<button class="auspicious-submit" @click="submit">
立即测算
</button>
<text class="auspicious-footer-tip">
已有 28,392 人通过壹梵择得良辰吉日
</text>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from "vue";
import MysticDatePicker from "../MysticDatePicker.vue";
import { baziZejiApi, type BaziZejiCalculateRequest } from '../../api';
declare const uni: any;
const emit = defineEmits<{
submit: [data: any];
back: [];
}>();
const form = reactive({
eventType: "wedding",
customEvent: "",
name: "",
gender: "male",
birthDateDisplay: "",
birthDateApi: "",
birthPlace: "",
zejiPurpose: "",
dateRangeStartDisplay: "",
dateRangeStart: "",
dateRangeEndDisplay: "",
dateRangeEnd: "",
});
const showDatePicker = ref(false);
const showStartDatePicker = ref(false);
const showEndDatePicker = ref(false);
/** 精准八字择吉 · 结束日期:从当年起可往后选多年 */
const ZEJI_RANGE_FORWARD_YEARS = 50;
const zejiExpectRangeMinYear = computed(() => new Date().getFullYear());
const zejiExpectRangeMaxYear = computed(() => new Date().getFullYear() + ZEJI_RANGE_FORWARD_YEARS);
/** 开始日期:至多为今天,年份列与生辰类似(当年往前若干年) */
const zejiStartMinYear = computed(() => new Date().getFullYear() - 85);
const zejiStartMaxYear = computed(() => new Date().getFullYear());
const eventTypes = [
{ id: "wedding", label: "婚嫁择吉", icon: "💒" },
{ id: "business", label: "开业择吉", icon: "🧧" },
{ id: "move", label: "搬家择吉", icon: "🏠" },
{ id: "travel", label: "出行择吉", icon: "✈️" },
{ id: "investment", label: "投资择吉", icon: "💰" },
{ id: "surgery", label: "手术择吉", icon: "🏥" },
{ id: "contract", label: "签约择吉", icon: "📝" },
{ id: "other", label: "其他择吉", icon: "✍️" },
];
const handleDateConfirm = (displayVal: string, apiVal: string) => {
form.birthDateDisplay = displayVal;
form.birthDateApi = apiVal;
showDatePicker.value = false;
};
const handleStartDateConfirm = (displayVal: string, apiVal: string) => {
form.dateRangeStartDisplay = displayVal;
// 从API格式中提取日期部分 (YYYY-MM-DD)
form.dateRangeStart = apiVal.split(' ')[0];
showStartDatePicker.value = false;
};
const handleEndDateConfirm = (displayVal: string, apiVal: string) => {
form.dateRangeEndDisplay = displayVal;
// 从API格式中提取日期部分 (YYYY-MM-DD)
form.dateRangeEnd = apiVal.split(' ')[0];
showEndDatePicker.value = false;
};
const submit = async () => {
if (form.eventType === "other" && !form.customEvent.trim()) {
uni.showToast({ title: "请输入您要求测的事项", icon: "none" });
return;
}
if (!form.name || !form.birthDateDisplay) {
uni.showToast({ title: "请填写真实信息以确保准确", icon: "none" });
return;
}
if (!form.birthPlace) {
uni.showToast({ title: "请输入出生地", icon: "none" });
return;
}
if (!form.zejiPurpose) {
uni.showToast({ title: "请输入择吉目的", icon: "none" });
return;
}
if (!form.dateRangeStart || !form.dateRangeEnd) {
uni.showToast({ title: "请选择期望日期范围", icon: "none" });
return;
}
uni.showLoading({ title: '测算中...', mask: true });
try {
const requestData: BaziZejiCalculateRequest = {
name: form.name,
gender: form.gender as 'male' | 'female',
birth_date: form.birthDateDisplay,
birth_date_api: form.birthDateApi,
birth_place: form.birthPlace,
zeji_type: form.eventType as any,
zeji_purpose: form.eventType === 'other' ? form.customEvent : form.zejiPurpose,
date_range_start: form.dateRangeStart,
date_range_end: form.dateRangeEnd,
};
const result = await baziZejiApi.calculateBaziZeji(requestData);
uni.hideLoading();
emit('submit', result);
} catch (error: any) {
uni.hideLoading();
// 如果是认证失败错误不显示toast因为API函数中已经处理了跳转
if (error.message !== '认证失败,请登录后再试') {
uni.showToast({
title: error.message || '测算失败,请稍后重试',
icon: 'none',
duration: 2000,
});
}
}
};
</script>
<style scoped>
.auspicious-form {
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;
}
.auspicious-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;
}
/* 固定头部容器 */
.auspicious-fixed-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: #f0efe9;
}
/* 状态栏占位 */
.status-bar-placeholder {
height: var(--status-bar-height, 0);
width: 100%;
}
/* 头部占位 */
.auspicious-header-placeholder {
height: calc(var(--status-bar-height, 0) + 100rpx);
flex-shrink: 0;
}
.auspicious-header {
position: relative;
z-index: 10;
padding: 24rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #eaddcf;
}
.auspicious-back {
padding: 16rpx;
margin-left: -8rpx;
color: #5a5a5a;
background: transparent;
border: none;
}
.auspicious-title {
font-size: 18px;
font-weight: bold;
color: #2c2c2c;
letter-spacing: 0.3em;
}
.auspicious-header-spacer {
width: 32rpx;
}
.auspicious-scroll {
flex: 1;
position: relative;
z-index: 10;
height: 0;
}
.auspicious-container {
max-width: 700rpx;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 32rpx;
}
.auspicious-intro {
text-align: center;
margin-bottom: 48rpx;
}
.auspicious-intro-title {
font-size: 24px;
font-weight: bold;
color: #2c2c2c;
display: block;
margin-bottom: 12rpx;
letter-spacing: 0.1em;
}
.auspicious-intro-sub {
font-size: 12px;
color: #5a5a5a;
letter-spacing: 0.05em;
}
.auspicious-section {
margin-bottom: 48rpx;
}
.auspicious-label {
display: block;
font-size: 14px;
font-weight: bold;
color: #2c2c2c;
margin-bottom: 20rpx;
padding-left: 12rpx;
border-left: 4rpx solid #8b2323;
letter-spacing: 0.05em;
}
.auspicious-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
align-items: stretch;
}
.auspicious-event-card {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 24rpx 16rpx;
border-radius: 16rpx;
border: 1px solid #e5e5e5;
background: #fffdf9;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
color: #5a5a5a;
transition: all 0.2s ease;
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.04);
}
.auspicious-event-card.is-active {
background: #8b2323;
border-color: #8b2323;
color: #fdfbf7;
box-shadow: 0 10rpx 16rpx -4rpx rgba(139, 35, 35, 0.35);
}
.auspicious-event-icon {
font-size: 22px;
}
.auspicious-event-text {
font-size: 14px;
font-weight: bold;
letter-spacing: 0.05em;
}
.auspicious-custom {
margin-top: 16rpx;
}
.auspicious-input {
width: 100%;
background: #fffdf9;
border: 1px solid #e5e5e5;
border-radius: 14rpx;
padding: 20rpx;
font-size: 14px;
color: #2c2c2c;
outline: none;
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.04);
}
.auspicious-input::placeholder {
color: #bfbfbf;
}
.auspicious-card {
background: #fffdf9;
border: 1px solid #e5e5e5;
border-radius: 16rpx;
padding: 28rpx;
box-shadow: 0 6rpx 12rpx -4rpx rgba(0, 0, 0, 0.08);
display: flex;
flex-direction: column;
gap: 20rpx;
}
.auspicious-field {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.auspicious-field-label {
font-size: 12px;
color: #8a8a8a;
}
.auspicious-field-input {
width: 100%;
background: transparent;
border: none;
border-bottom: 1px solid #e5e5e5;
padding: 14rpx 0;
font-size: 14px;
color: #2c2c2c;
outline: none;
}
.auspicious-field-input::placeholder {
color: #bfbfbf;
}
.auspicious-textarea {
width: 100%;
min-height: 120rpx;
background: transparent;
border: none;
border-bottom: 1px solid #e5e5e5;
padding: 14rpx 0;
font-size: 14px;
color: #2c2c2c;
outline: none;
resize: none;
font-family: inherit;
}
.auspicious-textarea::placeholder {
color: #bfbfbf;
}
.auspicious-gender {
display: flex;
gap: 16rpx;
}
.auspicious-gender-btn {
flex: 1;
border-radius: 12rpx;
border: 1px solid #e5e5e5;
background: transparent;
color: #5a5a5a;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.auspicious-gender-btn.is-active {
background: #2c2c2c;
color: #d4af37;
border-color: #2c2c2c;
}
.auspicious-date-trigger {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e5e5e5;
padding: 14rpx 0;
}
.auspicious-date-text {
font-size: 14px;
color: #2c2c2c;
}
.auspicious-date-text.is-placeholder {
color: #bfbfbf;
}
.auspicious-date-arrow {
color: #8b2323;
font-size: 24rpx;
opacity: 0.6;
}
.auspicious-footer {
padding: 32rpx;
background: #fdfbf7;
border-top: 1px solid #e5e5e5;
position: relative;
z-index: 20;
}
.auspicious-submit {
width: 100%;
background: #8b2323;
color: #fdfbf7;
font-weight: bold;
padding: 18rpx 0;
border-radius: 16rpx;
box-shadow: 0 12rpx 18rpx -8rpx rgba(139, 35, 35, 0.35);
font-size: 16px;
border: none;
}
.auspicious-footer-tip {
display: block;
text-align: center;
font-size: 10px;
color: #999;
margin-top: 12rpx;
}
</style>