|
- <template>
- <view class="page">
- <view class="wrap">
- <view class="hero">
- <view class="hero-copy">
- <text class="eyebrow">AI NAME STUDIO</text>
- <text class="hero-title">给宝宝取一个有出处的好名字</text>
- <text class="hero-desc">
- 结合姓氏、性别、诗词典故和音律寓意,生成可直接挑选的名字方案。
- </text>
- </view>
- <view class="hero-preview">
- <view class="preview-card main">
- <text class="preview-name">{{ previewName }}</text>
- <text class="preview-meta">李白《独坐敬亭山》</text>
- </view>
- <view class="preview-card sub">
- <text>音律</text>
- <text>寓意</text>
- <text>出处</text>
- </view>
- </view>
- </view>
-
- <view class="primary-card">
- <view class="card-head">
- <text class="card-title">基础信息</text>
- <text class="card-tip">必填</text>
- </view>
-
- <view class="surname-box">
- <view class="surname-field">
- <text class="field-label">姓氏</text>
- <input v-model="form.surname" class="surname-input" maxlength="5" placeholder="输入姓氏" />
- </view>
- <view class="gender-toggle">
- <view
- v-for="item in sexOptions"
- :key="item.id"
- class="gender-item"
- :class="{ active: form.sex === item.id }"
- @click="form.sex = item.id"
- >
- {{ item.label }}
- </view>
- </view>
- </view>
-
- <view class="birth-panel">
- <view class="birth-switch" :class="{ off: birthInfoIgnored }" @tap="toggleBirthInfoReference">
- <view class="birth-switch-copy">
- <text class="birth-switch-title">参考出生信息</text>
- <text class="birth-switch-desc">
- {{ birthInfoIgnored ? '已关闭,生成时不参考农历和五行' : '开启后会结合农历和五行倾向' }}
- </text>
- </view>
- <view class="birth-switch-track" :class="{ on: !birthInfoIgnored }">
- <view class="birth-switch-thumb"></view>
- </view>
- </view>
-
- <view v-if="!birthInfoIgnored" class="birth-grid">
- <view class="date-row" @tap="openBirthCalendar">
- <view>
- <text class="date-label">出生日期</text>
- <text class="date-value">{{ birthDateText }}</text>
- </view>
- <text class="date-arrow">›</text>
- </view>
-
- <view class="date-row" :class="{ muted: birthTimeUnknown }" @tap="openBirthTimePicker">
- <view>
- <text class="date-label">出生时分</text>
- <text class="date-value">{{ birthClockText }}</text>
- </view>
- <text class="date-arrow">›</text>
- </view>
- </view>
-
- <view v-if="!birthInfoIgnored && birthLunarText" class="lunar-card">
- <text class="lunar-label">农历</text>
- <text class="lunar-value">{{ birthLunarText }}</text>
- </view>
-
- <view v-if="!birthInfoIgnored" class="unknown-time" :class="{ active: birthTimeUnknown }" @tap="toggleBirthTimeUnknown">
- <text class="unknown-dot"></text>
- <text>不知道具体时间</text>
- </view>
- </view>
- </view>
-
- <view class="section">
- <view class="name-section-head">
- <text class="name-section-title">名字出处</text>
- <text class="name-section-desc">选择偏好的文化来源</text>
- </view>
- <view class="source-grid">
- <view
- v-for="item in visibleSourceOptions"
- :key="item.id"
- class="source-item"
- :class="{ active: form.name_source === item.id }"
- @click="form.name_source = item.id"
- >
- <text class="source-title">{{ item.label }}</text>
- <text class="source-desc">{{ item.desc }}</text>
- </view>
- <view class="source-item more" @click="sourceExpanded = !sourceExpanded">
- <text class="source-title">{{ sourceExpanded ? '收起' : '更多' }}</text>
- <text class="source-desc">{{ sourceExpanded ? '精简显示' : '展开全部' }}</text>
- </view>
- </view>
- </view>
-
- <view class="section">
- <view class="name-section-head">
- <text class="name-section-title">名字偏好</text>
- <text class="name-section-desc">用于控制生成结果的方向</text>
- </view>
-
- <view class="preference-block">
- <text class="preference-label">名字字数 <text class="muted">包含姓氏</text></text>
- <view class="chip-row">
- <view
- v-for="item in wordOptions"
- :key="item.id"
- class="chip"
- :class="{ active: form.words_num === item.id }"
- @click="form.words_num = item.id"
- >
- {{ item.label }}
- </view>
- </view>
- </view>
-
- <view class="preference-block">
- <text class="preference-label">是否叠词</text>
- <view class="chip-row">
- <view
- v-for="item in redupOptions"
- :key="item.id"
- class="chip"
- :class="{ active: form.is_redup === item.id }"
- @click="form.is_redup = item.id"
- >
- {{ item.label }}
- </view>
- </view>
- </view>
-
- <view class="special-row">
- <view>
- <text class="preference-label">指定包含字</text>
- <text class="special-tip">可选,限制 1 个字</text>
- </view>
- <input v-model="form.special_word" class="special-input" maxlength="1" placeholder="如:辰" />
- </view>
-
- <view class="extra-row">
- <view class="extra-head">
- <view>
- <text class="preference-label">补充描述</text>
- <text class="special-tip">额外要求、寓意和期望</text>
- </view>
- <text class="extra-count">{{ (form.content || '').length }}/200</text>
- </view>
- <textarea
- v-model="form.content"
- class="extra-textarea"
- maxlength="200"
- auto-height
- cursor-spacing="80"
- placeholder="您可以补充您的额外要求,也可以添加您的寓意和期望。200字以内..."
- />
- </view>
- </view>
-
- <view class="promise">
- <view class="promise-item">
- <text class="promise-mark">01</text>
- <text>可关闭出生信息参考,关闭后不结合农历和五行</text>
- </view>
- <view class="promise-item">
- <text class="promise-mark">02</text>
- <text>开启后可选择日期和时分,不确定时辰可选“不知道具体时间”</text>
- </view>
- </view>
- </view>
-
- <view class="bottom-bar">
- <view class="balance">
- <text class="balance-copy">{{ balanceText || 'AI取名' }}</text>
- <view v-if="isBalanceInsufficient" class="recharge-link" @click="goRecharge">去充值</view>
- </view>
- <view class="buttons">
- <view class="history-btn" @click="goHistory">历史记录</view>
- <view class="submit-btn" :class="{ disabled: submitting }" @click="submit">
- <image v-if="!submitting" class="submit-icon" src="https://cdn.aionline.cc/ai/icons/bolt.svg" mode="aspectFit" />
- <text>{{ submitting ? '生成中...' : submitText }}</text>
- </view>
- </view>
- </view>
-
- <up-calendar
- :show="birthCalendarVisible"
- title="选择出生日期"
- mode="single"
- color="#0b8cff"
- :default-date="calendarDefaultDate"
- :min-date="calendarMinDate"
- :max-date="calendarMaxDate"
- :month-num="calendarMonthNum"
- show-lunar
- :show-today="false"
- :close-on-click-overlay="true"
- confirm-text="确定"
- z-index="300"
- @confirm="onBirthDateConfirm"
- @close="birthCalendarVisible = false"
- />
-
- <up-datetime-picker
- v-model="birthClockPickerValue"
- :show="birthTimePickerVisible"
- mode="time"
- title="选择出生时间"
- :formatter="formatBirthTimeColumn"
- confirm-color="#0b8cff"
- :close-on-click-overlay="true"
- z-index="310"
- @confirm="onBirthTimeConfirm"
- @cancel="birthTimePickerVisible = false"
- @close="birthTimePickerVisible = false"
- />
- </view>
- </template>
-
- <script setup>
- import { computed, reactive, ref } from 'vue'
- import { onLoad, onShareAppMessage, onShow } from '@dcloudio/uni-app'
- import dayjs from 'dayjs'
- import { billingApi, toolApi, userApi } from '@/api/index.js'
- import { useUserStore } from '@/store/user.js'
- import { useApp } from '@/utils/useApp.js'
- import { buildShareOptions, showPageShareMenu } from '@/utils/share.js'
- import LunarCalendar from '@/uni_modules/uview-plus/libs/util/calendar.js'
-
- const userStore = useUserStore()
- const { toast } = useApp()
-
- const pageShare = {
- title: 'AI取名|给宝宝取一个有出处的好名字',
- source: 'share_ai_name'
- }
-
- onShow(() => showPageShareMenu())
- onShareAppMessage(() => buildShareOptions(pageShare))
-
- const appDetail = ref({})
- const robotInfo = ref({})
- const todayDate = dayjs().format('YYYY-MM-DD')
- const birthCacheKey = 'ai_name_birth_cache'
- const birthDate = ref(todayDate)
- const birthClock = ref('')
- const birthClockPickerValue = ref('08:00')
- const birthTimeUnknown = ref(true)
- const birthInfoIgnored = ref(false)
- const birthCalendarVisible = ref(false)
- const birthTimePickerVisible = ref(false)
- const submitting = ref(false)
- const sourceExpanded = ref(false)
- const balance = ref({ points: 0 })
- const billingConfig = ref({})
- const quote = ref({ points: 0 })
-
- const form = reactive({
- model: 4,
- language_type: 1,
- write_type: 1,
- tone_type: 1,
- length_type: 1,
- work_type: 1,
- reporting: '',
- department: '',
- content: '',
- surname: '',
- sex: '1',
- name_source: '0',
- birth_time: dayjs(todayDate).valueOf(),
- birth_date: todayDate,
- birth_clock: '',
- birth_time_unknown: 1,
- words_num: '0',
- is_redup: '0',
- special_word: ''
- })
-
- const sexOptions = [
- { id: '1', label: '男孩' },
- { id: '2', label: '女孩' }
- ]
-
- const sourceOptions = [
- { id: '0', label: '不限', desc: '综合推荐' },
- { id: '1', label: '诗经', desc: '温润雅致' },
- { id: '2', label: '楚辞', desc: '浪漫开阔' },
- { id: '3', label: '离骚', desc: '清朗有骨' },
- { id: '4', label: '周易', desc: '稳重含蓄' },
- { id: '5', label: '史记', desc: '大气有典' },
- { id: '6', label: '诗词歌赋', desc: '文采舒展' },
- { id: '7', label: '成语典故', desc: '寓意明确' },
- { id: '8', label: '佛教', desc: '清净平和' },
- { id: '9', label: '道教', desc: '自然洒脱' },
- { id: '10', label: '自然景观', desc: '明亮灵动' }
- ]
-
- const wordOptions = [
- { id: '0', label: '不限' },
- { id: '2', label: '2字' },
- { id: '3', label: '3字' },
- { id: '4', label: '4字' },
- { id: '5', label: '5字' }
- ]
-
- const redupOptions = [
- { id: '0', label: '不限' },
- { id: '1', label: '是' },
- { id: '2', label: '否' }
- ]
-
- const visibleSourceOptions = computed(() => {
- if (sourceExpanded.value) return sourceOptions
-
- const selected = sourceOptions.find((item) => item.id === form.name_source)
- const rows = [sourceOptions[0]]
- if (selected && selected.id !== '0') rows.push(selected)
-
- sourceOptions.slice(1).forEach((item) => {
- if (rows.length >= 5) return
- if (!rows.some((row) => row.id === item.id)) rows.push(item)
- })
-
- return rows
- })
-
- const previewName = computed(() => {
- return '白敬亭'
- })
-
- const calendarMinDate = dayjs().subtract(2, 'year').format('YYYY-MM-DD')
- const calendarMaxDate = dayjs().add(2, 'year').format('YYYY-MM-DD')
- const calendarMonthNum = dayjs(calendarMaxDate).diff(dayjs(calendarMinDate), 'month') + 1
- const calendarDefaultDate = computed(() => birthDate.value || todayDate)
-
- const birthDateText = computed(() => {
- return birthDate.value || '请选择日期'
- })
-
- const birthLunarText = computed(() => {
- return getLunarText(birthDate.value)
- })
-
- const birthClockText = computed(() => {
- if (birthTimeUnknown.value) return '不知道具体时间'
- return birthClock.value || '请选择时分'
- })
-
- const balanceText = computed(() => {
- const cost = Number(quote.value.points || 0)
- if (!userStore.isLogin) return cost ? `AI取名 · ${cost}点/次` : 'AI取名'
- return cost ? `余额${currentPoints.value}点 · 预计消耗${cost}点` : `余额${currentPoints.value}点`
- })
-
- const currentPoints = computed(() => Number(balance.value.points || balance.value.point_balance || 0))
-
- const isBalanceInsufficient = computed(() => {
- const cost = Number(quote.value.points || 0)
- return userStore.isLogin && cost > 0 && currentPoints.value >= 0 && currentPoints.value < cost
- })
-
- const submitText = computed(() => {
- const cost = Number(quote.value.points || 0)
- if (!userStore.isLogin) return cost ? `登录后取名 · ${cost}点` : '登录后取名'
- return cost ? `立即取名 · ${cost}点` : '立即取名'
- })
-
- onLoad(() => {
- loadAppDetail()
- loadBillingConfig()
- })
-
- onShow(() => {
- if (userStore.isLogin) {
- loadBalance()
- loadRobot()
- }
- loadBillingConfig()
- restoreCache()
- })
-
- async function loadAppDetail() {
- try {
- const res = await toolApi.getAppDetail({ key: 'naming' })
- appDetail.value = res.data || {}
- if (res.data && res.data.write_type) form.write_type = res.data.write_type
- } catch (e) {}
- }
-
- async function loadRobot() {
- try {
- const res = await toolApi.getNameRobot()
- robotInfo.value = res.data || {}
- } catch (e) {}
- }
-
- async function loadBalance() {
- try {
- const res = await userApi.getBalance()
- balance.value = res.data || {}
- } catch (e) {}
- }
-
- async function loadBillingConfig() {
- try {
- const res = await billingApi.getConfig({ app_key: 'ai-name' })
- billingConfig.value = res.data || {}
- quote.value = billingConfig.value.quote || {}
- if (billingConfig.value.balance) balance.value = billingConfig.value.balance
- } catch (e) {}
- }
-
- function restoreCache() {
- const cache = uni.getStorageSync('writing_form')
- if (cache && cache.surname && !form.surname) {
- const cachedForm = { ...cache }
- delete cachedForm.birth_time
- delete cachedForm.birth_date
- delete cachedForm.birth_clock
- delete cachedForm.birth_time_unknown
- Object.assign(form, cachedForm)
- form.content = String(form.content || '').slice(0, 200)
- }
- restoreBirthCache()
- syncBirthFields()
- }
-
- function restoreBirthCache() {
- const cache = uni.getStorageSync(birthCacheKey)
- if (!cache || typeof cache !== 'object') {
- birthDate.value = todayDate
- birthClock.value = ''
- birthClockPickerValue.value = '08:00'
- birthTimeUnknown.value = true
- return
- }
-
- const cachedDate = normalizeBirthDate(cache.birth_date)
- const cachedClock = normalizeBirthClock(cache.birth_clock)
- birthInfoIgnored.value = Number(cache.birth_info_ignored) === 1
- birthDate.value = cachedDate || todayDate
- birthTimeUnknown.value = Number(cache.birth_time_unknown) === 1 || !cachedClock
- birthClock.value = birthTimeUnknown.value ? '' : cachedClock
- birthClockPickerValue.value = cachedClock || '08:00'
- }
-
- function saveBirthCache() {
- uni.setStorageSync(birthCacheKey, {
- birth_date: normalizeBirthDate(birthDate.value) || todayDate,
- birth_clock: birthTimeUnknown.value ? '' : normalizeBirthClock(birthClock.value),
- birth_time_unknown: birthTimeUnknown.value ? 1 : 0,
- birth_info_ignored: birthInfoIgnored.value ? 1 : 0
- })
- }
-
- function openBirthCalendar() {
- if (birthInfoIgnored.value) return
- birthCalendarVisible.value = true
- }
-
- function openBirthTimePicker() {
- if (birthInfoIgnored.value) return
- birthClockPickerValue.value = birthClock.value || '08:00'
- birthTimePickerVisible.value = true
- }
-
- function onBirthDateConfirm(value) {
- const selected = resolveCalendarDate(value)
- if (selected) {
- birthDate.value = selected
- syncBirthFields()
- saveBirthCache()
- }
- birthCalendarVisible.value = false
- }
-
- function onBirthTimeConfirm(e) {
- const value = e && e.value ? String(e.value) : birthClockPickerValue.value
- birthClock.value = normalizeBirthClock(value) || '08:00'
- birthClockPickerValue.value = birthClock.value
- birthTimeUnknown.value = false
- syncBirthFields()
- saveBirthCache()
- birthTimePickerVisible.value = false
- }
-
- function formatBirthTimeColumn(type, value) {
- if (type === 'hour') return `${value}时`
- if (type === 'minute') return `${value}分`
- return value
- }
-
- function toggleBirthTimeUnknown() {
- if (birthInfoIgnored.value) return
- birthTimeUnknown.value = !birthTimeUnknown.value
- if (birthTimeUnknown.value) {
- birthClock.value = ''
- } else {
- birthClock.value = birthClockPickerValue.value || '08:00'
- birthClockPickerValue.value = birthClock.value
- }
- syncBirthFields()
- saveBirthCache()
- }
-
- function toggleBirthInfoReference() {
- birthInfoIgnored.value = !birthInfoIgnored.value
- if (!birthInfoIgnored.value && !birthDate.value) {
- birthDate.value = todayDate
- }
- syncBirthFields()
- saveBirthCache()
- }
-
- function syncBirthFields() {
- if (birthInfoIgnored.value) {
- form.birth_date = ''
- form.birth_clock = ''
- form.birth_time_unknown = 1
- form.birth_time = ''
- return
- }
- if (!birthDate.value) {
- birthDate.value = todayDate
- }
- form.birth_date = birthDate.value || ''
- form.birth_clock = birthTimeUnknown.value ? '' : (birthClock.value || '')
- form.birth_time_unknown = birthTimeUnknown.value ? 1 : 0
- if (!birthDate.value) {
- form.birth_time = ''
- return
- }
- const clock = birthTimeUnknown.value || !birthClock.value ? '00:00' : birthClock.value
- form.birth_time = dayjs(`${birthDate.value} ${clock}`).valueOf()
- }
-
- function resolveCalendarDate(payload) {
- if (Array.isArray(payload)) return normalizeBirthDate(payload[0])
- if (payload && typeof payload === 'object') {
- if (Array.isArray(payload.value)) return normalizeBirthDate(payload.value[0])
- return normalizeBirthDate(payload.value || payload.date || payload.result)
- }
- return normalizeBirthDate(payload)
- }
-
- function normalizeBirthDate(value) {
- if (!value) return ''
- if (value instanceof Date) {
- const parsedDate = dayjs(value)
- return parsedDate.isValid() ? parsedDate.format('YYYY-MM-DD') : ''
- }
-
- const rawValue = String(value).trim()
- if (!rawValue) return ''
-
- if (/^\d{8}$/.test(rawValue)) {
- const compactDate = `${rawValue.slice(0, 4)}-${rawValue.slice(4, 6)}-${rawValue.slice(6, 8)}`
- const parsedCompactDate = dayjs(compactDate)
- return parsedCompactDate.isValid() ? parsedCompactDate.format('YYYY-MM-DD') : ''
- }
-
- if (/^\d{10,13}$/.test(rawValue)) {
- const timestamp = Number(rawValue.length === 10 ? `${rawValue}000` : rawValue)
- const parsedTimestamp = dayjs(timestamp)
- return parsedTimestamp.isValid() ? parsedTimestamp.format('YYYY-MM-DD') : ''
- }
-
- const parsed = dayjs(rawValue.replace(/\//g, '-'))
- return parsed.isValid() ? parsed.format('YYYY-MM-DD') : ''
- }
-
- function normalizeBirthClock(value) {
- if (!value) return ''
- const match = String(value).trim().match(/^(\d{1,2}):(\d{1,2})/)
- if (!match) return ''
- const hour = Number(match[1])
- const minute = Number(match[2])
- if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return ''
- return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`
- }
-
- function getLunarText(date) {
- if (!date) return ''
- const parsed = dayjs(date)
- if (!parsed.isValid()) return ''
- try {
- const lunar = LunarCalendar.solar2lunar(parsed.year(), parsed.month() + 1, parsed.date())
- if (!lunar || lunar === -1) return ''
- const leap = lunar.isLeap ? '闰' : ''
- return `农历${lunar.gzYear || ''}年 ${leap}${lunar.IMonthCn || ''}${lunar.IDayCn || ''}`
- } catch (e) {
- return ''
- }
- }
-
- function requireLogin() {
- if (userStore.isLogin) return true
- uni.navigateTo({ url: '/pages/login/login' })
- return false
- }
-
- async function ensureRobot() {
- if (robotInfo.value.robot_open_id) return true
- await loadRobot()
- return !!robotInfo.value.robot_open_id
- }
-
- async function goHistory() {
- if (!requireLogin()) return
- await ensureRobot()
- uni.navigateTo({ url: `/pages/tool/name-history?robot_id=${robotInfo.value.robot_open_id || ''}` })
- }
-
- function goRecharge() {
- uni.navigateTo({ url: '/pages/recharge/recharge' })
- }
-
- async function submit() {
- if (!requireLogin()) return
- if (!form.surname) return toast('请输入姓氏')
- if (!form.sex) return toast('请选择性别')
- if (submitting.value) return
- await ensureRobot()
- form.content = String(form.content || '').trim().slice(0, 200)
- syncBirthFields()
- saveBirthCache()
-
- uni.setStorageSync('writing_form', { ...form })
- const params = {
- ...robotInfo.value,
- ...form,
- model: 4
- }
- if (params.birth_time) params.birth_time = dayjs(params.birth_time).unix()
-
- submitting.value = true
- try {
- const checkRes = await toolApi.checkName(params)
- if (checkRes.data && checkRes.data.quote) quote.value = checkRes.data.quote
- if (checkRes.data && checkRes.data.balance) balance.value = checkRes.data.balance
- uni.setStorageSync('name_stream_params', params)
- uni.navigateTo({
- url: `/pages/tool/name-result?create=1&robot_id=${robotInfo.value.robot_open_id || ''}&key=naming`
- })
- } catch (e) {
- if (e.code === 1011) {
- uni.showModal({
- title: '温馨提示',
- content: e.msg || '点数不足,请充值后继续使用',
- confirmText: '知道了',
- showCancel: false
- })
- } else {
- toast(e.msg || '发送失败')
- }
- } finally {
- submitting.value = false
- }
- }
- </script>
-
- <style lang="scss" scoped>
- .page {
- min-height: 100vh;
- background: #f3f7fb;
- }
-
- .wrap {
- padding: 22rpx 28rpx 300rpx;
- }
-
- .hero {
- position: relative;
- min-height: 310rpx;
- padding: 34rpx 30rpx;
- border-radius: 28rpx;
- color: #fff;
- background: linear-gradient(135deg, #138cff 0%, #34c3ff 54%, #79e0df 100%);
- overflow: hidden;
- box-sizing: border-box;
- }
-
- .hero::before {
- content: '';
- position: absolute;
- width: 360rpx;
- height: 360rpx;
- right: -110rpx;
- top: -140rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.2);
- }
-
- .hero::after {
- content: '';
- position: absolute;
- width: 240rpx;
- height: 240rpx;
- right: 86rpx;
- bottom: -130rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.16);
- }
-
- .hero-copy,
- .hero-preview {
- position: relative;
- z-index: 1;
- }
-
- .eyebrow {
- display: block;
- color: rgba(255, 255, 255, 0.76);
- font-size: 21rpx;
- font-weight: 700;
- letter-spacing: 0;
- }
-
- .hero-title {
- display: block;
- margin-top: 18rpx;
- width: 420rpx;
- color: #fff;
- font-size: 42rpx;
- font-weight: 800;
- line-height: 1.24;
- }
-
- .hero-desc {
- display: block;
- margin-top: 18rpx;
- width: 420rpx;
- color: rgba(255, 255, 255, 0.88);
- font-size: 25rpx;
- line-height: 1.55;
- }
-
- .hero-preview {
- position: absolute;
- right: 16rpx;
- bottom: 28rpx;
- width: 220rpx;
- }
-
- .preview-card {
- border: 1rpx solid rgba(255, 255, 255, 0.42);
- background: rgba(255, 255, 255, 0.2);
- box-shadow: 0 18rpx 34rpx rgba(0, 92, 190, 0.16);
- }
-
- .preview-card.main {
- padding: 22rpx;
- border-radius: 24rpx;
- }
-
- .preview-name {
- display: block;
- color: #fff;
- font-size: 34rpx;
- font-weight: 800;
- }
-
- .preview-meta {
- display: block;
- margin-top: 6rpx;
- color: rgba(255, 255, 255, 0.82);
- font-size: 21rpx;
- }
-
- .preview-card.sub {
- margin-top: 12rpx;
- height: 48rpx;
- padding: 0 14rpx;
- border-radius: 18rpx;
- display: flex;
- align-items: center;
- justify-content: space-between;
- color: rgba(255, 255, 255, 0.86);
- font-size: 20rpx;
- }
-
- .primary-card,
- .section,
- .promise {
- margin-top: 22rpx;
- border-radius: 24rpx;
- background: #fff;
- box-shadow: 0 8rpx 28rpx rgba(34, 68, 112, 0.05);
- }
-
- .primary-card,
- .section {
- padding: 28rpx;
- }
-
- .card-head,
- .name-section-head {
- display: flex;
- align-items: baseline;
- }
-
- .card-title,
- .name-section-title {
- color: #172033;
- font-size: 31rpx;
- font-weight: 800;
- }
-
- .card-tip,
- .name-section-desc {
- margin-left: auto;
- color: #8a95a6;
- font-size: 23rpx;
- }
-
- .surname-box {
- margin-top: 22rpx;
- padding: 20rpx;
- border-radius: 22rpx;
- background: #f6f9fe;
- display: flex;
- align-items: center;
- gap: 18rpx;
- }
-
- .surname-field {
- flex: 1;
- min-width: 0;
- }
-
- .field-label,
- .date-label,
- .preference-label {
- display: block;
- color: #172033;
- font-size: 25rpx;
- font-weight: 700;
- }
-
- .surname-input {
- margin-top: 8rpx;
- height: 58rpx;
- color: #172033;
- font-size: 40rpx;
- font-weight: 800;
- }
-
- .gender-toggle {
- width: 196rpx;
- padding: 6rpx;
- border-radius: 34rpx;
- background: #fff;
- display: flex;
- box-shadow: inset 0 0 0 1rpx #edf2f7;
- }
-
- .gender-item {
- flex: 1;
- height: 54rpx;
- border-radius: 28rpx;
- color: #7f8896;
- font-size: 24rpx;
- font-weight: 700;
- line-height: 54rpx;
- text-align: center;
- }
-
- .gender-item.active {
- color: #fff;
- background: #0b8cff;
- }
-
- .birth-panel {
- margin-top: 18rpx;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- }
-
- .birth-switch {
- width: 100%;
- padding: 18rpx 20rpx;
- border-radius: 20rpx;
- background: #eff8ff;
- border: 2rpx solid rgba(11, 140, 255, 0.16);
- display: flex;
- align-items: center;
- box-sizing: border-box;
- }
-
- .birth-switch.off {
- background: #f7f9fd;
- border-color: #edf2f7;
- }
-
- .birth-switch-copy {
- min-width: 0;
- flex: 1;
- }
-
- .birth-switch-title {
- display: block;
- color: #172033;
- font-size: 25rpx;
- font-weight: 800;
- line-height: 1.3;
- }
-
- .birth-switch-desc {
- display: block;
- margin-top: 6rpx;
- color: #7f8896;
- font-size: 22rpx;
- line-height: 1.35;
- }
-
- .birth-switch-track {
- width: 76rpx;
- height: 42rpx;
- margin-left: 18rpx;
- padding: 4rpx;
- border-radius: 999rpx;
- background: #d7deea;
- box-sizing: border-box;
- flex-shrink: 0;
- }
-
- .birth-switch-track.on {
- background: #0b8cff;
- }
-
- .birth-switch-thumb {
- width: 34rpx;
- height: 34rpx;
- border-radius: 50%;
- background: #fff;
- box-shadow: 0 4rpx 12rpx rgba(23, 32, 51, 0.18);
- }
-
- .birth-switch-track.on .birth-switch-thumb {
- transform: translateX(34rpx);
- }
-
- .birth-grid {
- width: 100%;
- margin-top: 14rpx;
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 14rpx;
- }
-
- .date-row {
- width: 100%;
- min-height: 118rpx;
- padding: 20rpx 22rpx;
- border-radius: 20rpx;
- background: #f6f9fe;
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- box-sizing: border-box;
- }
-
- .date-row.muted {
- opacity: 0.82;
- }
-
- .date-value {
- display: block;
- margin-top: 12rpx;
- color: #7f8896;
- font-size: 25rpx;
- line-height: 1.32;
- }
-
- .date-arrow {
- margin-left: 12rpx;
- color: #a0aabb;
- font-size: 36rpx;
- line-height: 1;
- flex-shrink: 0;
- }
-
- .lunar-card {
- width: 100%;
- margin-top: 14rpx;
- padding: 16rpx 20rpx;
- border-radius: 18rpx;
- display: flex;
- align-items: center;
- background: #eff8ff;
- box-sizing: border-box;
- }
-
- .lunar-label {
- margin-right: 14rpx;
- padding: 4rpx 12rpx;
- border-radius: 999rpx;
- color: #0b8cff;
- font-size: 20rpx;
- font-weight: 800;
- background: #fff;
- flex-shrink: 0;
- }
-
- .lunar-value {
- min-width: 0;
- color: #3a5878;
- font-size: 24rpx;
- font-weight: 700;
- line-height: 1.35;
- }
-
- .unknown-time {
- margin-top: 14rpx;
- padding: 12rpx 18rpx;
- border-radius: 999rpx;
- display: flex;
- align-items: center;
- color: #7f8896;
- font-size: 23rpx;
- font-weight: 700;
- background: #f6f9fe;
- border: 2rpx solid transparent;
- }
-
- .unknown-time.active {
- color: #0b8cff;
- background: #eff8ff;
- border-color: #0b8cff;
- }
-
- .unknown-dot {
- width: 14rpx;
- height: 14rpx;
- margin-right: 10rpx;
- border-radius: 50%;
- background: currentColor;
- }
-
- .source-grid {
- margin-top: 22rpx;
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 14rpx;
- }
-
- .source-item {
- min-height: 86rpx;
- padding: 14rpx 10rpx;
- border-radius: 18rpx;
- background: #f7f9fd;
- border: 2rpx solid transparent;
- box-sizing: border-box;
- }
-
- .source-item.active {
- background: #eff8ff;
- border-color: #0b8cff;
- box-shadow: 0 8rpx 20rpx rgba(11, 140, 255, 0.1);
- }
-
- .source-item.more {
- background: #fff;
- border-color: #e7edf5;
- }
-
- .source-title {
- display: block;
- color: #172033;
- font-size: 25rpx;
- font-weight: 800;
- line-height: 1.25;
- text-align: center;
- }
-
- .source-desc {
- display: block;
- margin-top: 6rpx;
- color: #8a95a6;
- font-size: 20rpx;
- line-height: 1.25;
- text-align: center;
- }
-
- .preference-block {
- margin-top: 24rpx;
- }
-
- .muted,
- .special-tip {
- color: #8a95a6;
- font-size: 23rpx;
- font-weight: 400;
- }
-
- .chip-row {
- margin-top: 16rpx;
- display: flex;
- flex-wrap: wrap;
- gap: 14rpx;
- }
-
- .chip {
- height: 58rpx;
- min-width: 104rpx;
- padding: 0 24rpx;
- border-radius: 30rpx;
- color: #596579;
- font-size: 25rpx;
- font-weight: 700;
- line-height: 58rpx;
- text-align: center;
- background: #f6f9fe;
- border: 2rpx solid transparent;
- box-sizing: border-box;
- }
-
- .chip.active {
- color: #0b8cff;
- background: #eff8ff;
- border-color: #0b8cff;
- }
-
- .special-row {
- margin-top: 26rpx;
- padding: 20rpx 22rpx;
- border-radius: 20rpx;
- background: #f7f9fd;
- display: flex;
- align-items: center;
- }
-
- .extra-row {
- margin-top: 20rpx;
- padding: 20rpx 22rpx 18rpx;
- border-radius: 20rpx;
- background: #f7f9fd;
- }
-
- .extra-head {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 20rpx;
- }
-
- .extra-count {
- color: #9aa5b5;
- font-size: 22rpx;
- line-height: 1.35;
- flex-shrink: 0;
- }
-
- .special-tip {
- display: block;
- margin-top: 8rpx;
- }
-
- .extra-textarea {
- width: 100%;
- min-height: 116rpx;
- margin-top: 16rpx;
- color: #172033;
- font-size: 26rpx;
- line-height: 1.55;
- box-sizing: border-box;
- }
-
- .special-input {
- margin-left: auto;
- width: 128rpx;
- height: 66rpx;
- border-radius: 18rpx;
- color: #172033;
- font-size: 32rpx;
- font-weight: 800;
- text-align: center;
- background: #fff;
- }
-
- .promise {
- padding: 24rpx 26rpx;
- }
-
- .promise-item {
- display: flex;
- align-items: center;
- color: #596579;
- font-size: 24rpx;
- line-height: 1.5;
-
- & + & {
- margin-top: 16rpx;
- }
- }
-
- .promise-mark {
- width: 48rpx;
- height: 34rpx;
- margin-right: 14rpx;
- border-radius: 18rpx;
- color: #0b8cff;
- font-size: 19rpx;
- font-weight: 800;
- line-height: 34rpx;
- text-align: center;
- background: #eef6ff;
- flex-shrink: 0;
- }
-
- .bottom-bar {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 50;
- padding: 18rpx 28rpx calc(18rpx + env(safe-area-inset-bottom));
- background: rgba(255, 255, 255, 0.96);
- box-shadow: 0 -8rpx 24rpx rgba(35, 55, 90, 0.08);
- }
-
- .balance {
- color: #7b8797;
- font-size: 24rpx;
- margin-bottom: 14rpx;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 20rpx;
- line-height: 1.35;
- }
-
- .balance-copy {
- min-width: 0;
- flex: 1;
- }
-
- .recharge-link {
- flex-shrink: 0;
- color: #0b8cff;
- font-size: 24rpx;
- font-weight: 800;
- }
-
- .buttons {
- display: flex;
- gap: 22rpx;
- }
-
- .history-btn,
- .submit-btn {
- height: 84rpx;
- border-radius: 44rpx;
- font-size: 30rpx;
- margin: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .history-btn {
- width: 210rpx;
- color: #0b8cff;
- background: #eef6ff;
- }
-
- .submit-btn {
- flex: 1;
- color: #fff;
- font-weight: 700;
- background: #0b8cff;
- box-shadow: 0 12rpx 24rpx rgba(11, 140, 255, 0.2);
- }
-
- .submit-icon {
- width: 24rpx;
- height: 24rpx;
- margin-right: 8rpx;
- flex-shrink: 0;
- }
-
- .submit-btn.disabled {
- opacity: 0.72;
- }
- </style>
|