|
- <template>
- <view class="page">
- <scroll-view class="scroll" scroll-y>
- <view class="wrap">
- <view class="hero">
- <view class="hero-copy">
- <text class="eyebrow">ANIME AVATAR STUDIO</text>
- <text class="hero-title">上传自拍,生成你的动漫头像</text>
- <text class="hero-desc">用真实对比看效果。支持竖图、半身照和普通自拍,脸部清晰会更稳定。</text>
- <view class="hero-actions">
- <view class="hero-pill" @click="openCompare">查看对比</view>
- <view class="hero-link" @click="chooseImage">上传照片</view>
- </view>
- </view>
- <view class="hero-compare-card" @click="openCompare">
- <view class="mini-compare">
- <image v-if="demoPair.anime" class="compare-img" :src="demoPair.anime" mode="aspectFill" />
- <view class="compare-before mini-before">
- <image v-if="demoPair.original" class="compare-img" :src="demoPair.original" mode="aspectFill" />
- </view>
- <view class="compare-line mini-line"></view>
- <view class="compare-tag left">原图</view>
- <view class="compare-tag right">动漫</view>
- </view>
- </view>
- </view>
-
- <view class="upload-card">
- <view class="card-head">
- <view>
- <text class="card-title">参考照片</text>
- <text class="card-desc">建议使用正面、上半身、脸部无遮挡的照片</text>
- </view>
- <text class="required">必选</text>
- </view>
-
- <view class="upload-strip" @click="chooseImage">
- <view class="upload-thumb">
- <image v-if="previewImage" class="upload-img" :src="previewImage" mode="aspectFill" />
- <view v-else class="upload-empty">
- <text class="plus">+</text>
- </view>
- <view v-if="uploading" class="upload-mask">上传中</view>
- </view>
- <view class="upload-copy">
- <text class="upload-title">{{ previewImage ? '已选择照片' : '选择一张清晰自拍' }}</text>
- <text class="upload-sub">不用裁成正方形,竖图和半身照也可以</text>
- </view>
- <view class="upload-action">{{ previewImage ? '更换' : '上传' }}</view>
- </view>
-
- <view class="upload-tips">
- <view class="upload-tip">正面</view>
- <view class="upload-tip">脸部清晰</view>
- <view class="upload-tip">避免遮挡</view>
- </view>
- </view>
-
- <view v-if="compareVisible" class="compare-modal">
- <view class="compare-backdrop" @click="closeCompare"></view>
- <view class="compare-panel">
- <view class="compare-panel-head">
- <text>效果对比</text>
- <view class="close-btn" @click="closeCompare">×</view>
- </view>
- <view
- class="large-compare"
- @touchstart.stop="onCompareTouch"
- @touchmove.stop.prevent="onCompareTouch"
- @click.stop="onCompareTap"
- >
- <image v-if="demoPair.anime" class="compare-img" :src="demoPair.anime" mode="aspectFill" />
- <view class="compare-before" :style="{ width: comparePercent + '%' }">
- <image v-if="demoPair.original" class="compare-img" :src="demoPair.original" mode="aspectFill" />
- </view>
- <view class="compare-line large" :style="{ left: comparePercent + '%' }">
- <view class="compare-handle large">
- <text>‹</text>
- <text>›</text>
- </view>
- </view>
- <view class="compare-tag left">原图</view>
- <view class="compare-tag right">动漫图</view>
- </view>
- <text class="compare-guide">左右滑动中间按钮查看变化</text>
- </view>
- </view>
-
- <view class="section">
- <view class="section-head">
- <text class="anime-section-title">头像风格</text>
- <text class="section-tip">横向滑动选择</text>
- </view>
- <scroll-view class="style-scroll" scroll-x>
- <view class="style-list">
- <view
- v-for="item in styleList"
- :key="item.value"
- class="style-card"
- :class="{ active: form.img2img_style === String(item.value) }"
- @click="selectStyle(item)"
- >
- <image v-if="item.pic" class="style-img" :src="item.pic" mode="aspectFill" />
- <view v-else class="style-img placeholder">
- <text>{{ item.name }}</text>
- </view>
- <text class="style-name">{{ item.name }}</text>
- </view>
- </view>
- </scroll-view>
- </view>
-
- <view class="section compact">
- <view class="section-head">
- <view>
- <text class="anime-section-title">自由度</text>
- <text class="section-desc">{{ strengthCopy }}</text>
- </view>
- <text class="strength-value">{{ form.img2img_strength.toFixed(2) }}</text>
- </view>
- <slider
- :value="Number(form.img2img_strength)"
- :min="0.1"
- :max="1"
- :step="0.01"
- activeColor="#1f8cff"
- backgroundColor="#e7edf7"
- block-color="#ffffff"
- @changing="onStrengthChange"
- @change="onStrengthChange"
- />
- <view class="strength-scale">
- <text>更像原图</text>
- <text>更有创意</text>
- </view>
- </view>
-
- <view class="section">
- <view class="section-head">
- <text class="anime-section-title">画面偏好</text>
- <text class="section-tip">可选</text>
- </view>
- <view class="input-block">
- <text class="input-label">描述词</text>
- <input v-model="form.content" class="input" maxlength="100" placeholder="例如:浅色背景、清爽、微笑" />
- </view>
- <view class="input-block">
- <text class="input-label">反面提示词</text>
- <input v-model="form.no" class="input" maxlength="100" placeholder="例如:模糊、变形、低清晰度" />
- </view>
- </view>
-
- <view class="tips-card">
- <view class="tip-line">
- <text class="tip-mark">01</text>
- <text>上传照片后可直接生成,处理完成会进入作品详情页。</text>
- </view>
- <view class="tip-line">
- <text class="tip-mark">02</text>
- <text>历史记录里可以查看生成进度,也可以继续打开已完成作品。</text>
- </view>
- </view>
- </view>
- </scroll-view>
-
- <view class="bottom-bar">
- <view class="balance">
- <text class="balance-copy">{{ balanceText || '动漫头像' }}</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>
- </view>
- </template>
-
- <script setup>
- import { computed, reactive, ref } from 'vue'
- import { onLoad, onShow } from '@dcloudio/uni-app'
- import { billingApi, commonApi, toolApi, userApi } from '@/api/index.js'
- import { useUserStore } from '@/store/user.js'
- import { useApp } from '@/utils/useApp.js'
- import { uploadImage } from '@/utils/upload.js'
-
- const userStore = useUserStore()
- const { toast } = useApp()
- const ANIME_BILLING_MODEL = 'tencent_anime'
-
- const appDetail = ref({})
- const demoList = ref([])
- const styleList = ref([{ value: '201', name: '默认', model: 2, pic: 'https://cdn.aionline.cc/ai/toolbox/home_card_avatar.png' }])
- const previewImage = ref('')
- const uploading = ref(false)
- const submitting = ref(false)
- const compareVisible = ref(false)
- const comparePercent = ref(50)
- const balance = ref({ points: 0 })
- const billingConfig = ref({})
- const quote = ref({ points: 0 })
- const copyId = ref('')
-
- const form = reactive({
- imgs: [],
- content: '',
- ai_type: 3,
- style_type: 1,
- ar: '1:1',
- no: '',
- model: ANIME_BILLING_MODEL,
- model_type: 6,
- light: '',
- text: '',
- speed: '',
- img2img_style: '201',
- age: 80,
- change_sex_type: '0',
- img2img_strength: 0.6,
- num: 1
- })
-
- const balanceText = computed(() => {
- const cost = Number(quote.value.points || 0)
- if (!userStore.isLogin) return cost ? `动漫头像 · ${cost}点/张` : '动漫头像'
- 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}点` : '立即生成'
- })
-
- const strengthCopy = computed(() => {
- return '推荐 0.6 - 0.8,保留人脸特征'
- })
-
- const demoPair = computed(() => demoList.value[0] || { original: '', anime: '' })
-
- onLoad((options) => {
- copyId.value = options.copy_id || ''
- loadAppDetail()
- loadBillingConfig()
- loadDemo()
- loadStyles()
- if (copyId.value) loadCopyDetail()
- })
-
- onShow(() => {
- if (userStore.isLogin) loadBalance()
- loadBillingConfig()
- const storageCopyId = uni.getStorageSync('anime_copy_id')
- if (storageCopyId) {
- uni.removeStorageSync('anime_copy_id')
- copyId.value = storageCopyId
- loadCopyDetail()
- }
- })
-
- async function loadAppDetail() {
- try {
- const res = await toolApi.getAppDetail({ key: 'anime_avatar' })
- appDetail.value = res.data || {}
- } catch (e) {}
- }
-
- async function loadDemo() {
- try {
- const res = await commonApi.getConfig({ keys: 'anime_demo_v2,anime_demo' })
- const rows = Array.isArray(res.list) ? res.list : []
- const v2 = rows.find((it) => it.key === 'anime_demo_v2')
- const old = rows.find((it) => it.key === 'anime_demo')
- demoList.value = normalizeDemoList(v2 && v2.value)
- if (!demoList.value.length) {
- const legacyPics = normalizeImageList(old && old.value)
- if (legacyPics.length) {
- demoList.value = [{ original: legacyPics[1] || legacyPics[0], anime: legacyPics[0] }]
- }
- }
- if (!demoList.value.length) {
- demoList.value = [{ original: 'https://cdn.aionline.cc/ai/toolbox/home_card_avatar.png', anime: 'https://cdn.aionline.cc/ai/toolbox/home_card_avatar.png' }]
- }
- } catch (e) {}
- }
-
- async function loadStyles() {
- try {
- const res = await toolApi.getAnimeStyleList({ model: '2' })
- const rows = Array.isArray(res.list) ? res.list : (Array.isArray(res.data) ? res.data : (res.data && res.data.list) || [])
- const list = rows.map((item) => ({
- ...item,
- value: String(item.value),
- pic: getStyleImage(item)
- }))
- if (list.length) {
- styleList.value = list
- selectStyle(list[0], false)
- }
- } catch (e) {}
- }
-
- async function loadCopyDetail() {
- try {
- const res = await toolApi.getAnimeDetail({ chat_open_id: copyId.value })
- if (!res.data) return
- form.img2img_style = String(res.data.img2img_style || form.img2img_style)
- form.img2img_strength = Number(res.data.img2img_strength || form.img2img_strength)
- form.no = res.data.no || ''
- form.content = res.data.prompts || ''
- } 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: 'anime-avatar', model: ANIME_BILLING_MODEL })
- billingConfig.value = res.data || {}
- quote.value = billingConfig.value.quote || {}
- if (billingConfig.value.balance) balance.value = billingConfig.value.balance
- } catch (e) {}
- }
-
- function selectStyle(item, resetStrength = true) {
- form.img2img_style = String(item.value)
- form.ai_type = 3
- form.img2img_strength = resetStrength ? 0.6 : form.img2img_strength || 0.6
- }
-
- function normalizeImageList(value) {
- if (Array.isArray(value)) return value.filter(Boolean)
- if (!value) return []
- return String(value).split(',').map((item) => item.trim()).filter(Boolean)
- }
-
- function normalizeDemoList(value) {
- if (!value) return []
- try {
- const data = typeof value === 'string' ? JSON.parse(value) : value
- const list = Array.isArray(data) ? data : [data]
- return list
- .map((item) => ({
- original: item.original || item.before || item.source || '',
- anime: item.anime || item.after || item.result || ''
- }))
- .filter((item) => item.original && item.anime)
- .slice(0, 1)
- } catch (e) {
- const pics = normalizeImageList(value)
- if (pics.length >= 2) return [{ original: pics[0], anime: pics[1] }]
- return []
- }
- }
-
- function getStyleImage(item) {
- return item.pic || item.img || item.image || item.cover || item.icon || item.url || ''
- }
-
- function onStrengthChange(e) {
- form.img2img_strength = Number(e.detail.value)
- }
-
- function openCompare() {
- if (!demoPair.value.original || !demoPair.value.anime) return
- comparePercent.value = 50
- compareVisible.value = true
- }
-
- function closeCompare() {
- compareVisible.value = false
- }
-
- function onCompareTap(e) {
- updateComparePercent(e)
- }
-
- function onCompareTouch(e) {
- updateComparePercent(e)
- }
-
- function updateComparePercent(e) {
- const touch = e.touches && e.touches[0] ? e.touches[0] : e
- const x = touch.clientX || 0
- uni.createSelectorQuery()
- .select('.large-compare')
- .boundingClientRect((rect) => {
- if (!rect || !rect.width) return
- const percent = ((x - rect.left) / rect.width) * 100
- comparePercent.value = Math.max(8, Math.min(92, Number(percent.toFixed(1))))
- })
- .exec()
- }
-
- function requireLogin() {
- if (userStore.isLogin) return true
- uni.navigateTo({ url: '/pages/login/login' })
- return false
- }
-
- function showBalanceModal() {
- const cost = Number(quote.value.points || 0)
- uni.showModal({
- title: '温馨提示',
- content: cost ? `本次生成需要 ${cost} 点,当前点数不足,请充值后继续。` : '点数不足,请充值后继续。',
- confirmText: '去充值',
- cancelText: '知道了',
- success: (res) => {
- if (res.confirm) uni.navigateTo({ url: '/pages/recharge/recharge' })
- }
- })
- }
-
- function chooseImage() {
- if (!requireLogin()) return
- uni.chooseImage({
- count: 1,
- sizeType: ['compressed'],
- sourceType: ['album', 'camera'],
- success: async (res) => {
- const filePath = res.tempFilePaths[0]
- previewImage.value = filePath
- uploading.value = true
- try {
- const uploadRes = await uploadImage(filePath, res.tempFiles && res.tempFiles[0])
- form.imgs = [uploadRes.data.url]
- if (uploadRes.data.width) form.width = uploadRes.data.width
- if (uploadRes.data.height) form.height = uploadRes.data.height
- } catch (e) {
- previewImage.value = ''
- form.imgs = []
- toast(e.msg || '上传失败')
- } finally {
- uploading.value = false
- }
- }
- })
- }
-
- function goHistory() {
- if (!requireLogin()) return
- uni.navigateTo({ url: '/pages/tool/anime-history' })
- }
-
- function goRecharge() {
- uni.navigateTo({ url: '/pages/recharge/recharge' })
- }
-
- async function submit() {
- if (!requireLogin()) return
- if (uploading.value) return toast('图片正在上传')
- if (!form.imgs.length) return toast('请上传参考图')
- if (submitting.value) return
-
- const params = {
- ...form,
- imgs: form.imgs.slice(0, 1).join(',')
- }
-
- submitting.value = true
- try {
- const res = await toolApi.submitAnime(params)
- if (res.data && res.data.quote) quote.value = res.data.quote
- const id = res.data && res.data.answer_chat_open_id
- if (id) {
- uni.navigateTo({ url: `/pages/tool/work-detail?id=${id}_0&from=anime` })
- } else {
- toast('提交成功')
- }
- } catch (e) {
- if (e.code === 1011) {
- showBalanceModal()
- } else if (e.code === 1012) {
- form.content = ''
- uni.showModal({ title: '温馨提示', content: e.msg || '', showCancel: false, confirmText: '知道了' })
- } else {
- toast(e.msg || '生成失败')
- }
- } finally {
- submitting.value = false
- }
- }
- </script>
-
- <style lang="scss" scoped>
- .page {
- height: 100vh;
- background: #f5f7fb;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
-
- .scroll {
- flex: 1;
- height: 0;
- }
-
- .wrap {
- padding: 24rpx 28rpx 300rpx;
- }
-
- .hero {
- min-height: 348rpx;
- padding: 32rpx;
- border-radius: 28rpx;
- background: linear-gradient(135deg, #ffffff 0%, #edf6ff 58%, #fff7f0 100%);
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 24rpx;
- overflow: hidden;
- position: relative;
- }
-
- .hero-copy {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- position: relative;
- z-index: 1;
- }
-
- .eyebrow {
- color: #1f8cff;
- font-size: 20rpx;
- font-weight: 800;
- letter-spacing: 0;
- }
-
- .hero-title {
- margin-top: 12rpx;
- color: #172033;
- font-size: 42rpx;
- line-height: 1.18;
- font-weight: 800;
- }
-
- .hero-desc {
- margin-top: 18rpx;
- color: #66758a;
- font-size: 24rpx;
- line-height: 1.55;
- }
-
- .hero-actions {
- margin-top: 24rpx;
- display: flex;
- align-items: center;
- gap: 16rpx;
- }
-
- .hero-pill,
- .hero-link {
- height: 56rpx;
- padding: 0 22rpx;
- border-radius: 999rpx;
- font-size: 24rpx;
- line-height: 56rpx;
- font-weight: 800;
- }
-
- .hero-pill {
- color: #ffffff;
- background: #1f8cff;
- box-shadow: 0 10rpx 20rpx rgba(31, 140, 255, 0.22);
- }
-
- .hero-link {
- color: #1f8cff;
- background: #ffffff;
- }
-
- .hero-compare-card {
- width: 188rpx;
- flex-shrink: 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
-
- .mini-compare,
- .large-compare {
- position: relative;
- overflow: hidden;
- background: #dfe8f4;
- }
-
- .mini-compare {
- width: 168rpx;
- height: 252rpx;
- border-radius: 28rpx;
- box-shadow: 0 18rpx 38rpx rgba(31, 62, 112, 0.18);
- }
-
- .compare-img {
- width: 100%;
- height: 100%;
- }
-
- .compare-before {
- position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
- overflow: hidden;
- }
-
- .mini-before {
- width: 50%;
- }
-
- .compare-before .compare-img {
- width: 168rpx;
- }
-
- .compare-line {
- position: absolute;
- top: 0;
- bottom: 0;
- width: 4rpx;
- margin-left: -2rpx;
- background: rgba(255, 255, 255, 0.94);
- box-shadow: 0 0 12rpx rgba(20, 37, 66, 0.22);
- }
-
- .compare-line.mini-line {
- left: 50%;
- }
-
- .compare-line.large {
- width: 6rpx;
- margin-left: -3rpx;
- }
-
- .compare-handle {
- position: absolute;
- left: 50%;
- top: 50%;
- width: 38rpx;
- height: 58rpx;
- margin-left: -19rpx;
- margin-top: -29rpx;
- border-radius: 999rpx;
- background: #ffffff;
- box-shadow: 0 8rpx 18rpx rgba(20, 37, 66, 0.22);
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 1rpx;
- color: #1f8cff;
- font-size: 24rpx;
- font-weight: 900;
- }
-
- .compare-handle.large {
- width: 54rpx;
- height: 78rpx;
- margin-left: -27rpx;
- margin-top: -39rpx;
- font-size: 32rpx;
- }
-
- .compare-tag {
- position: absolute;
- top: 12rpx;
- padding: 6rpx 12rpx;
- border-radius: 999rpx;
- color: #ffffff;
- background: rgba(15, 23, 42, 0.46);
- font-size: 20rpx;
- font-weight: 700;
- }
-
- .compare-tag.left {
- left: 12rpx;
- }
-
- .compare-tag.right {
- right: 12rpx;
- }
-
- .upload-card,
- .section,
- .tips-card {
- margin-top: 22rpx;
- padding: 28rpx;
- border-radius: 24rpx;
- background: #ffffff;
- box-shadow: 0 10rpx 28rpx rgba(32, 56, 92, 0.05);
- }
-
- .card-head,
- .section-head {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 20rpx;
- }
-
- .card-title,
- .anime-section-title {
- display: block;
- color: #172033;
- font-size: 32rpx;
- font-weight: 800;
- }
-
- .card-desc,
- .section-desc,
- .section-tip {
- display: block;
- margin-top: 8rpx;
- color: #8a97a8;
- font-size: 23rpx;
- line-height: 1.45;
- }
-
- .required {
- padding: 8rpx 16rpx;
- border-radius: 999rpx;
- color: #ff6b6b;
- background: #fff1f1;
- font-size: 22rpx;
- font-weight: 700;
- }
-
- .upload-strip {
- margin-top: 24rpx;
- min-height: 162rpx;
- padding: 18rpx;
- border: 2rpx solid #e5edf7;
- border-radius: 22rpx;
- background: #f7faff;
- display: flex;
- align-items: center;
- gap: 20rpx;
- }
-
- .upload-thumb {
- width: 96rpx;
- height: 144rpx;
- border-radius: 18rpx;
- background: #edf3fb;
- overflow: hidden;
- position: relative;
- flex-shrink: 0;
- }
-
- .upload-img {
- width: 100%;
- height: 100%;
- }
-
- .upload-empty,
- .upload-mask {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
-
- .plus {
- width: 54rpx;
- height: 54rpx;
- border-radius: 50%;
- color: #ffffff;
- background: #1f8cff;
- font-size: 42rpx;
- font-weight: 300;
- display: flex;
- align-items: center;
- justify-content: center;
- line-height: 1;
- }
-
- .upload-copy {
- flex: 1;
- min-width: 0;
- }
-
- .upload-title {
- display: block;
- color: #172033;
- font-size: 30rpx;
- font-weight: 800;
- }
-
- .upload-sub {
- display: block;
- margin-top: 10rpx;
- color: #8a97a8;
- font-size: 24rpx;
- line-height: 1.4;
- }
-
- .upload-action {
- width: 96rpx;
- height: 58rpx;
- border-radius: 999rpx;
- color: #ffffff;
- background: #1f8cff;
- font-size: 24rpx;
- line-height: 58rpx;
- text-align: center;
- font-weight: 800;
- flex-shrink: 0;
- }
-
- .upload-mask {
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- color: #ffffff;
- background: rgba(17, 30, 52, 0.62);
- font-size: 22rpx;
- }
-
- .upload-tips {
- margin-top: 18rpx;
- display: flex;
- gap: 14rpx;
- }
-
- .upload-tip {
- padding: 8rpx 18rpx;
- border-radius: 999rpx;
- color: #526174;
- background: #f1f5fa;
- font-size: 22rpx;
- font-weight: 700;
- }
-
- .compare-modal {
- position: fixed;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- z-index: 120;
- }
-
- .compare-backdrop {
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- background: rgba(15, 23, 42, 0.58);
- }
-
- .compare-panel {
- position: absolute;
- left: 36rpx;
- right: 36rpx;
- top: 50%;
- transform: translateY(-50%);
- padding: 24rpx;
- border-radius: 28rpx;
- background: #ffffff;
- }
-
- .compare-panel-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- color: #172033;
- font-size: 32rpx;
- font-weight: 800;
- margin-bottom: 20rpx;
- }
-
- .close-btn {
- width: 56rpx;
- height: 56rpx;
- border-radius: 50%;
- color: #64748b;
- background: #f1f5f9;
- font-size: 42rpx;
- line-height: 50rpx;
- text-align: center;
- font-weight: 300;
- }
-
- .large-compare {
- width: 100%;
- height: 900rpx;
- max-height: 70vh;
- border-radius: 22rpx;
- }
-
- .large-compare .compare-before .compare-img {
- width: calc(100vw - 120rpx);
- }
-
- .compare-guide {
- display: block;
- margin-top: 18rpx;
- color: #64748b;
- font-size: 24rpx;
- text-align: center;
- }
-
- .style-scroll {
- margin-top: 24rpx;
- white-space: nowrap;
- }
-
- .style-list {
- display: inline-flex;
- gap: 18rpx;
- }
-
- .style-card {
- width: 172rpx;
- padding: 10rpx;
- border: 2rpx solid transparent;
- border-radius: 22rpx;
- background: #f6f9fd;
- text-align: center;
- box-sizing: border-box;
- }
-
- .style-card.active {
- border-color: #1f8cff;
- background: #eef7ff;
- }
-
- .style-img {
- width: 148rpx;
- height: 148rpx;
- border-radius: 18rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- color: #77869a;
- background: #eaf0f8;
- font-size: 24rpx;
- }
-
- .style-name {
- display: block;
- margin-top: 10rpx;
- color: #334155;
- font-size: 24rpx;
- font-weight: 700;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .compact {
- padding-bottom: 22rpx;
- }
-
- .strength-value {
- color: #1f8cff;
- font-size: 32rpx;
- font-weight: 800;
- }
-
- .strength-scale {
- margin-top: -4rpx;
- display: flex;
- justify-content: space-between;
- color: #8a97a8;
- font-size: 22rpx;
- }
-
- .input-block {
- margin-top: 24rpx;
- }
-
- .input-label {
- display: block;
- margin-bottom: 12rpx;
- color: #526174;
- font-size: 24rpx;
- font-weight: 700;
- }
-
- .input {
- height: 86rpx;
- padding: 0 24rpx;
- border-radius: 18rpx;
- background: #f6f9fd;
- color: #172033;
- font-size: 28rpx;
- }
-
- .tips-card {
- color: #5f6f83;
- font-size: 24rpx;
- line-height: 1.55;
- }
-
- .tip-line {
- display: flex;
- gap: 16rpx;
- }
-
- .tip-line + .tip-line {
- margin-top: 16rpx;
- }
-
- .tip-mark {
- width: 44rpx;
- height: 32rpx;
- border-radius: 999rpx;
- color: #1f8cff;
- background: #eef7ff;
- font-size: 20rpx;
- line-height: 32rpx;
- text-align: center;
- font-weight: 800;
- flex-shrink: 0;
- }
-
- .bottom-bar {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 80;
- padding: 18rpx 28rpx calc(18rpx + env(safe-area-inset-bottom));
- background: rgba(255, 255, 255, 0.96);
- box-shadow: 0 -10rpx 30rpx rgba(32, 56, 92, 0.1);
- }
-
- .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: 86rpx;
- border-radius: 999rpx;
- font-size: 30rpx;
- font-weight: 800;
- margin: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .history-btn {
- width: 210rpx;
- color: #1f8cff;
- background: #eef7ff;
- }
-
- .submit-btn {
- flex: 1;
- color: #ffffff;
- background: linear-gradient(135deg, #1f8cff, #41b9ff);
- box-shadow: 0 12rpx 24rpx rgba(31, 140, 255, 0.28);
- }
-
- .submit-icon {
- width: 24rpx;
- height: 24rpx;
- margin-right: 8rpx;
- flex-shrink: 0;
- }
-
- .submit-btn.disabled {
- opacity: 0.72;
- }
- </style>
|