| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- <template>
- <view class="page-index">
- <!-- Hero header -->
- <view class="hero">
- <text class="hero-title">数字能量</text>
- <text class="hero-sub">输入出生信息,解锁专属命盘</text>
- </view>
- <!-- Birth input section -->
- <view class="input-section">
- <view class="input-card">
- <text class="label">姓名(选填)</text>
- <input
- class="input-field"
- v-model="name"
- placeholder="请输入姓名"
- />
- <text class="label mt-16">出生年份</text>
- <input
- class="input-field"
- type="digit"
- maxlength="4"
- v-model="birthYear"
- placeholder="例如 1990"
- />
- <view class="row-inputs">
- <view class="half-input">
- <text class="label">月份</text>
- <picker class="picker-wrap" mode="selector" :range="monthRange" @change="onMonthChange">
- <view class="input-field picker-field">
- <text :class="birthMonth ? 'picker-val' : 'picker-ph'">{{ birthMonth || '选择' }}</text>
- <text class="picker-arrow">▼</text>
- </view>
- </picker>
- </view>
- <view class="half-input">
- <text class="label">日期</text>
- <picker class="picker-wrap" mode="selector" :range="dayRange" @change="onDayChange">
- <view class="input-field picker-field">
- <text :class="birthDay ? 'picker-val' : 'picker-ph'">{{ birthDay || '选择' }}</text>
- <text class="picker-arrow">▼</text>
- </view>
- </picker>
- </view>
- </view>
- <text class="label mt-16">想了解的问题(选填)</text>
- <view class="question-wrap">
- <textarea
- class="question-input"
- v-model="questions"
- maxlength="100"
- placeholder="例如:我的财运如何?最近适合换工作吗?感情方面有什么建议?"
- />
- <text class="char-counter">{{ questions.length }}/100</text>
- </view>
- <view class="quick-chips">
- <view
- v-for="q in quickQuestions"
- :key="q"
- class="chip"
- :class="{ active: questions === q }"
- @click="selectQuick(q)"
- >
- <text>{{ q }}</text>
- </view>
- </view>
- <button class="analyze-btn" @click="onAnalyze">开始咨询</button>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, computed } from 'vue'
- import { useChartStore } from '@/stores/chart'
- import { consultationApi } from '@/utils/api'
- const store = useChartStore()
- const name = ref('')
- const birthYear = ref('')
- const birthMonth = ref('')
- const birthDay = ref('')
- const questions = ref('')
- const confirming = ref(false) // prevent double-click
- const quickQuestions = ['职业发展', '财务分析', '感情运势', '健康建议']
- function selectQuick(q) {
- questions.value = questions.value === q ? '' : q
- }
- const monthRange = Array.from({ length: 12 }, (_, i) => String(i + 1))
- const dayRange = Array.from({ length: 31 }, (_, i) => String(i + 1))
- function onMonthChange(e) {
- birthMonth.value = monthRange[e.detail.value]
- }
- function onDayChange(e) {
- birthDay.value = dayRange[e.detail.value]
- }
- function formatBirthday(year, month, day) {
- return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
- }
- async function onAnalyze() {
- if (confirming.value) return
- const year = parseInt(birthYear.value, 10)
- const month = parseInt(birthMonth.value, 10)
- const day = parseInt(birthDay.value, 10)
- if (!year || !month || !day) {
- uni.showToast({ title: '请输入完整的出生年月日', icon: 'none' })
- return
- }
- if (year < 1900 || year > new Date().getFullYear()) {
- uni.showToast({ title: '请输入正确的出生年份', icon: 'none' })
- return
- }
- if (month < 1 || month > 12) {
- uni.showToast({ title: '请输入正确的月份(1-12)', icon: 'none' })
- return
- }
- if (day < 1 || day > 31) {
- uni.showToast({ title: '请输入正确的日期(1-31)', icon: 'none' })
- return
- }
- const birthday = formatBirthday(year, month, day)
- confirming.value = true
- try {
- // Check if this birthday has existing records — US-1.1 §7-9
- const isNew = await checkNewBirthday(birthday)
- if (!isNew) {
- // Existing birthday — proceed directly (US-1.1 §7-8: returns existing record)
- confirming.value = false
- await proceedConsultation(year, month, day, birthday)
- return
- }
- } catch (_e) {
- // If check fails (e.g. network), allow proceed without confirm
- }
- // New birthday — show confirm dialog (US-1.1 §9-11)
- uni.showModal({
- title: '确认咨询',
- content: '此生日将开启全新的命盘咨询,确认吗?',
- confirmText: '确认开启',
- cancelText: '再想想',
- success: async (res) => {
- confirming.value = false
- if (res.confirm) {
- await proceedConsultation(year, month, day, birthday)
- }
- // res.cancel → stay on page, do nothing (US-1.1 §11)
- },
- fail: () => { confirming.value = false }
- })
- }
- async function checkNewBirthday(birthday) {
- try {
- const result = await consultationApi.check({ birthday })
- return !result.exists
- } catch {
- return false
- }
- }
- async function proceedConsultation(year, month, day, birthday) {
- const result = await store.startConsultation(name.value, birthday, questions.value)
- // Parse backend chart data into birth props for TriangleChart
- if (store.currentChart) {
- store.currentBirthYear = year
- store.currentBirthMonth = month
- store.currentBirthDay = day
- }
- // Navigate with recordId for URL shareability (US-1.1 §14)
- const recordId = store.currentRecordId || (result.record && result.record.id)
- const url = recordId
- ? `/pages/chart/index?recordId=${recordId}`
- : '/pages/chart/index'
- uni.navigateTo({ url })
- }
- </script>
- <style scoped lang="scss">
- .page-index {
- min-height: 100vh;
- padding-bottom: 40px;
- background: linear-gradient(180deg, #0c0a1a 0%, #120d28 60%, #0f0c1e 100%);
- }
- /* Hero */
- .hero {
- padding: 40px 20px 20px;
- text-align: center;
- }
- .hero-title {
- font-size: 32px;
- font-weight: 700;
- color: #fbbf24;
- text-shadow: 0 0 30px rgba(251, 191, 36, 0.2);
- letter-spacing: 4px;
- }
- .hero-sub {
- display: block;
- margin-top: 10px;
- font-size: 14px;
- color: rgba(255, 255, 255, 0.4);
- letter-spacing: 2px;
- }
- /* Input card */
- .input-section {
- padding: 0 16px;
- }
- .input-card {
- background: rgba(255, 255, 255, 0.03);
- border: 1px solid rgba(255, 255, 255, 0.06);
- border-radius: 16px;
- padding: 24px 20px;
- backdrop-filter: blur(10px);
- }
- .label {
- font-size: 13px;
- color: rgba(255, 255, 255, 0.5);
- margin-bottom: 8px;
- display: block;
- letter-spacing: 1px;
- }
- .input-field {
- height: 44px;
- border: 1px solid rgba(255, 255, 255, 0.08);
- border-radius: 10px;
- padding: 0 14px;
- font-size: 16px;
- width: 100%;
- color: rgba(255, 255, 255, 0.85);
- background: rgba(255, 255, 255, 0.04);
- box-sizing: border-box;
- }
- .mt-16 {
- margin-top: 16px;
- display: block;
- }
- .row-inputs {
- display: flex;
- gap: 12px;
- }
- .half-input {
- flex: 1;
- }
- /* Picker */
- .picker-wrap {
- width: 100%;
- }
- .picker-field {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .picker-val {
- color: rgba(255, 255, 255, 0.85);
- font-size: 16px;
- }
- .picker-ph {
- color: rgba(255, 255, 255, 0.25);
- font-size: 16px;
- }
- .picker-arrow {
- font-size: 10px;
- color: rgba(255, 255, 255, 0.3);
- margin-left: 4px;
- }
- .question-wrap {
- position: relative;
- }
- .question-input {
- width: 100%;
- min-height: 80px;
- border: 1px solid rgba(255, 255, 255, 0.08);
- border-radius: 10px;
- padding: 12px 14px 28px;
- font-size: 14px;
- line-height: 1.5;
- box-sizing: border-box;
- color: rgba(255, 255, 255, 0.85);
- background: rgba(255, 255, 255, 0.04);
- }
- .char-counter {
- position: absolute;
- right: 10px;
- bottom: 8px;
- font-size: 11px;
- color: rgba(255, 255, 255, 0.3);
- }
- .quick-chips {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin: 12px 0 0;
- }
- .chip {
- background: rgba(255, 255, 255, 0.04);
- border: 1px solid rgba(255, 255, 255, 0.08);
- border-radius: 18px;
- padding: 6px 18px;
- font-size: 13px;
- color: rgba(255, 255, 255, 0.6);
- }
- .chip.active {
- background: rgba(251, 191, 36, 0.12);
- border-color: rgba(251, 191, 36, 0.25);
- color: #fbbf24;
- }
- .analyze-btn {
- width: 100%;
- height: 46px;
- background: linear-gradient(135deg, #f59e0b, #d97706);
- color: #fff;
- border: none;
- border-radius: 12px;
- font-size: 16px;
- font-weight: 600;
- line-height: 46px;
- letter-spacing: 2px;
- margin-top: 4px;
- }
- </style>
|