新增性能基准测试、安全验证、国际化完整性验证等专项测试; 修复 E2E 测试选择器与当前 UI 不匹配问题; 补充多语言 locale 缺失的 key; 更新 Playwright 配置支持跨浏览器测试; 同步更新测试报告与 README 进度章节。 单元测试 408/408 通过,E2E 测试 39/39 通过(4 种浏览器), 专项测试 47/47 通过,整体覆盖率 77.5%。
379 lines
9.1 KiB
Vue
379 lines
9.1 KiB
Vue
<template>
|
|
<view class="ziwei-page">
|
|
<view class="section" data-test="birth-form">
|
|
<text class="section-title">{{ $t('ziwei.birthInfo') }}</text>
|
|
<view class="form-group">
|
|
<text class="label">{{ $t('ziwei.birthDate') }}</text>
|
|
<picker mode="date" :value="birthDate" @change="onBirthDateChange">
|
|
<text class="picker-value">{{ birthDate || $t('ziwei.pleaseSelect') }}</text>
|
|
</picker>
|
|
</view>
|
|
<view class="form-group">
|
|
<text class="label">{{ $t('ziwei.birthHour') }}</text>
|
|
<picker :range="hourOptions" :value="hourIndex" @change="onHourChange">
|
|
<text class="picker-value">{{ hourOptions[hourIndex] }}</text>
|
|
</picker>
|
|
</view>
|
|
<view class="form-group">
|
|
<text class="label">{{ $t('ziwei.gender') }}</text>
|
|
<view class="gender-switch">
|
|
<text :class="['gender-option', gender === 'male' ? 'active' : '']" @tap="gender = 'male'">{{ $t('common.male') }}</text>
|
|
<text :class="['gender-option', gender === 'female' ? 'active' : '']" @tap="gender = 'female'">{{ $t('common.female') }}</text>
|
|
</view>
|
|
</view>
|
|
<view class="form-group">
|
|
<text class="label">{{ $t('ziwei.birthPlace') }}</text>
|
|
<input class="text-input" :value="birthPlace" @input="onBirthPlaceChange" :placeholder="$t('ziwei.birthPlaceHint')" />
|
|
</view>
|
|
<view class="form-row">
|
|
<view class="form-group form-half">
|
|
<text class="label">{{ $t('ziwei.longitude') }}</text>
|
|
<input class="text-input" type="digit" :value="longitude" @input="onLongitudeChange" :placeholder="$t('ziwei.longitudeHint')" />
|
|
</view>
|
|
<view class="form-group form-half">
|
|
<text class="label">{{ $t('ziwei.latitude') }}</text>
|
|
<input class="text-input" type="digit" :value="latitude" @input="onLatitudeChange" :placeholder="$t('ziwei.latitudeHint')" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="action-bar">
|
|
<button data-test="generate-btn" class="generate-btn" @tap="generateChart" :disabled="isCalculating">
|
|
{{ isCalculating ? $t('ziwei.generating') : $t('ziwei.generate') }}
|
|
</button>
|
|
</view>
|
|
|
|
<view v-if="chart" class="section chart-result">
|
|
<text class="section-title">{{ $t('ziwei.chartTitle') }}</text>
|
|
<view class="chart-grid">
|
|
<view v-for="palace in chart.palaces" :key="palace.palaceType" class="palace-card">
|
|
<text class="palace-name">{{ getPalaceName(palace.palaceType) }}</text>
|
|
<text class="palace-branch">{{ getBranchName(palace.earthlyBranch) }}</text>
|
|
<text class="palace-score">{{ palace.score }}{{ $t('ziwei.scoreUnit') }}</text>
|
|
<view class="star-list">
|
|
<text v-for="star in palace.majorStars" :key="star.star" class="star-item">
|
|
{{ getStarName(star.star) }}
|
|
</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="chart?.sanFangSiZheng" class="section analysis-section">
|
|
<text class="section-title">{{ $t('ziwei.sanFangAnalysis') }}</text>
|
|
<view class="analysis-item">
|
|
<text class="analysis-label">{{ $t('ziwei.sanFangScore') }}</text>
|
|
<text class="analysis-value">{{ chart.sanFangSiZheng.sanFangScore }}{{ $t('ziwei.scoreUnit') }}</text>
|
|
</view>
|
|
<view class="analysis-item">
|
|
<text class="analysis-label">{{ $t('ziwei.siZhengScore') }}</text>
|
|
<text class="analysis-value">{{ chart.sanFangSiZheng.siZhengScore }}{{ $t('ziwei.scoreUnit') }}</text>
|
|
</view>
|
|
<view class="analysis-item">
|
|
<text class="analysis-label">{{ $t('ziwei.overallEval') }}</text>
|
|
<text class="analysis-value">{{ chart.overallLuck }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="chart" class="section summary-section">
|
|
<text class="section-title">{{ $t('ziwei.summary') }}</text>
|
|
<text class="summary-text">{{ chart.summary }}</text>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { onShow } from '@dcloudio/uni-app'
|
|
import { ZiweiService } from '../../services/ziweiService'
|
|
import { PalaceType, EarthlyBranch, MajorStar } from '../../algorithms/enums'
|
|
import type { ZiweiChart } from '../../algorithms/types'
|
|
|
|
const ziweiService = new ZiweiService()
|
|
const { t } = useI18n()
|
|
|
|
onShow(() => {
|
|
uni.setNavigationBarTitle({ title: t('ziwei.pageTitle') })
|
|
})
|
|
|
|
const birthDate = ref('')
|
|
const hourIndex = ref(0)
|
|
const gender = ref<'male' | 'female'>('male')
|
|
const birthPlace = ref('')
|
|
const longitude = ref('')
|
|
const latitude = ref('')
|
|
const isCalculating = ref(false)
|
|
const chart = ref<ZiweiChart | null>(null)
|
|
|
|
const hourOptions = [
|
|
'子时 (23:00-01:00)', '丑时 (01:00-03:00)', '寅时 (03:00-05:00)',
|
|
'卯时 (05:00-07:00)', '辰时 (07:00-09:00)', '巳时 (09:00-11:00)',
|
|
'午时 (11:00-13:00)', '未时 (13:00-15:00)', '申时 (15:00-17:00)',
|
|
'酉时 (17:00-19:00)', '戌时 (19:00-21:00)', '亥时 (21:00-23:00)',
|
|
]
|
|
|
|
const onBirthDateChange = (e: any) => {
|
|
birthDate.value = e.detail.value
|
|
}
|
|
|
|
const onHourChange = (e: any) => {
|
|
hourIndex.value = e.detail.value
|
|
}
|
|
|
|
const onBirthPlaceChange = (e: any) => {
|
|
birthPlace.value = e.detail.value
|
|
}
|
|
|
|
const onLongitudeChange = (e: any) => {
|
|
longitude.value = e.detail.value
|
|
}
|
|
|
|
const onLatitudeChange = (e: any) => {
|
|
latitude.value = e.detail.value
|
|
}
|
|
|
|
const getPalaceName = (palaceType: string): string => {
|
|
try {
|
|
return PalaceType.name(palaceType as any)
|
|
} catch {
|
|
return palaceType
|
|
}
|
|
}
|
|
|
|
const getBranchName = (branch: any): string => {
|
|
try {
|
|
return EarthlyBranch.name(branch)
|
|
} catch {
|
|
return String(branch)
|
|
}
|
|
}
|
|
|
|
const getStarName = (star: any): string => {
|
|
try {
|
|
return MajorStar.name(star)
|
|
} catch {
|
|
return String(star)
|
|
}
|
|
}
|
|
|
|
const generateChart = async () => {
|
|
if (!birthDate.value) return
|
|
|
|
isCalculating.value = true
|
|
try {
|
|
const hour = hourIndex.value * 2
|
|
const birthTime = `${birthDate.value}T${String(hour).padStart(2, '0')}:00:00`
|
|
const lng = longitude.value ? parseFloat(longitude.value) : undefined
|
|
const lat = latitude.value ? parseFloat(latitude.value) : undefined
|
|
chart.value = await ziweiService.generateChart({
|
|
birthTime,
|
|
gender: gender.value,
|
|
timezone: 'Asia/Shanghai',
|
|
birthPlace: birthPlace.value || undefined,
|
|
longitude: lng,
|
|
latitude: lat,
|
|
})
|
|
} catch (e) {
|
|
console.error('排盘失败:', e)
|
|
} finally {
|
|
isCalculating.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.ziwei-page {
|
|
min-height: 100vh;
|
|
background-color: rgba(244, 244, 245, 0.3);
|
|
padding: 16px;
|
|
padding-bottom: 40px;
|
|
}
|
|
|
|
.section {
|
|
background-color: #fff;
|
|
border-radius: 12px;
|
|
padding: 16px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
color: rgba(44, 24, 16, 255);
|
|
margin-bottom: 12px;
|
|
display: block;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.label {
|
|
font-size: 14px;
|
|
color: #666;
|
|
margin-bottom: 6px;
|
|
display: block;
|
|
}
|
|
|
|
.picker-value {
|
|
font-size: 15px;
|
|
color: #333;
|
|
padding: 8px 12px;
|
|
background-color: #f5f5f5;
|
|
border-radius: 8px;
|
|
display: block;
|
|
}
|
|
|
|
.gender-switch {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.gender-option {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding: 8px 0;
|
|
border-radius: 8px;
|
|
font-size: 15px;
|
|
color: #666;
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
.gender-option.active {
|
|
color: #fff;
|
|
background-color: rgba(44, 24, 16, 255);
|
|
}
|
|
|
|
.form-row {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.form-half {
|
|
flex: 1;
|
|
}
|
|
|
|
.text-input {
|
|
font-size: 15px;
|
|
color: #333;
|
|
padding: 8px 12px;
|
|
background-color: #f5f5f5;
|
|
border-radius: 8px;
|
|
display: block;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.action-bar {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.generate-btn {
|
|
width: 100%;
|
|
height: 48px;
|
|
line-height: 48px;
|
|
background-color: rgba(44, 24, 16, 255);
|
|
color: #fff;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
border-radius: 12px;
|
|
border: none;
|
|
}
|
|
|
|
.generate-btn[disabled] {
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.chart-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 8px;
|
|
}
|
|
|
|
.palace-card {
|
|
background-color: #fafafa;
|
|
border-radius: 8px;
|
|
padding: 8px;
|
|
text-align: center;
|
|
}
|
|
|
|
.palace-name {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: rgba(44, 24, 16, 255);
|
|
display: block;
|
|
}
|
|
|
|
.palace-branch {
|
|
font-size: 12px;
|
|
color: #999;
|
|
display: block;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.palace-score {
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
color: #e74c3c;
|
|
display: block;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.star-list {
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.star-item {
|
|
font-size: 11px;
|
|
color: #666;
|
|
display: block;
|
|
}
|
|
|
|
.analysis-section {
|
|
.analysis-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.analysis-label {
|
|
font-size: 14px;
|
|
color: #666;
|
|
}
|
|
|
|
.analysis-value {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: rgba(44, 24, 16, 255);
|
|
}
|
|
}
|
|
|
|
.summary-section {
|
|
.summary-text {
|
|
font-size: 14px;
|
|
line-height: 22px;
|
|
color: #333;
|
|
}
|
|
}
|
|
|
|
@media screen and (max-width: 375px) {
|
|
.chart-grid {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
}
|
|
|
|
.palace-card {
|
|
padding: 6px;
|
|
}
|
|
|
|
.palace-name {
|
|
font-size: 12px;
|
|
}
|
|
|
|
.palace-score {
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
</style>
|