Files
everything-is-suitable/everything-is-suitable-uniapp/src/pages/ziwei/index.vue
T
zhangxiang 3a428d8977 test: 补充 Phase 3 专项测试并更新测试报告
新增性能基准测试、安全验证、国际化完整性验证等专项测试;
修复 E2E 测试选择器与当前 UI 不匹配问题;
补充多语言 locale 缺失的 key;
更新 Playwright 配置支持跨浏览器测试;
同步更新测试报告与 README 进度章节。

单元测试 408/408 通过,E2E 测试 39/39 通过(4 种浏览器),
专项测试 47/47 通过,整体覆盖率 77.5%。
2026-08-12 20:10:39 +08:00

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>