569 lines
24 KiB
Vue
569 lines
24 KiB
Vue
<template>
|
||
<view class="image-container" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd">
|
||
<view class="media-wrapper">
|
||
<swiper class="image-swiper" :current="currentIndex" :circular="true" :autoplay="false" @change="onSwiperChange">
|
||
<block v-for="(img, idx) in images" :key="idx">
|
||
<swiper-item>
|
||
<view class="image-slide" @click="onImageTap(idx)">
|
||
<view class="zoom-wrapper" :style="{ transform: 'scale(' + ((scaleMap[idx] || 1)) + ')' }">
|
||
<image class="display-image"
|
||
:src="img.displayUrl"
|
||
mode="aspectFill"
|
||
lazy-load
|
||
@load="onImageLoad(idx)"
|
||
@error="onImageError(idx)" />
|
||
</view>
|
||
</view>
|
||
</swiper-item>
|
||
</block>
|
||
</swiper>
|
||
</view>
|
||
|
||
<view class="top-controls" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="left-controls">
|
||
<view class="back-btn" @click="onBack">
|
||
<text class="icon-back">‹</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="bottom-info">
|
||
<view class="article-status" v-if="isLoadingArticle || articleError">
|
||
<view class="loading-indicator" v-if="isLoadingArticle">
|
||
<text class="loading-text">正在加载图片详情...</text>
|
||
</view>
|
||
<view class="error-message" v-if="articleError">
|
||
<text class="error-text">{{ articleError }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="creator-info">
|
||
<image class="creator-avatar" :src="creatorAvatar" @click="onCreatorTap" />
|
||
<view class="creator-details">
|
||
<text class="creator-name">{{ creatorName }}</text>
|
||
<view :class="['follow-btn', isFollowing ? 'following' : '']" @click="onFollow">
|
||
{{ isFollowing ? '已关注' : '关注' }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="image-info" v-if="imageDescription">
|
||
<view :class="['image-description', isDescriptionExpanded ? 'expanded' : '']" @click="onToggleDescription">
|
||
{{ imageDescription }}
|
||
</view>
|
||
<view class="expand-toggle" @click="onToggleDescription" v-if="imageDescription.length > 50">
|
||
<text class="iconfont" :class="isDescriptionExpanded ? 'icon-xiangshang' : 'icon-xiangxia'"></text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="interaction-area">
|
||
<view class="like-section">
|
||
<view :class="['like-btn', isLiked ? 'liked' : '']" @click="onLike">
|
||
<text class="heart-icon">{{ isLiked ? '❤️' : '🤍' }}</text>
|
||
</view>
|
||
<text class="like-count">{{ likeCount }}</text>
|
||
</view>
|
||
<view class="action-buttons">
|
||
<view class="consult-btn" @click="onConsult">立即咨询</view>
|
||
<view class="action-btn" @click="onCreateSimilar">做同款</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="ai-notice">
|
||
<text class="ai-notice-text">内容由AI生成</text>
|
||
</view>
|
||
|
||
<view class="page-indicator">
|
||
<text class="indicator-text">{{ currentIndex + 1 }} / {{ images.length }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="floating-actions">
|
||
<view class="floating-btn download-btn" @click="onDownload">
|
||
<text class="floating-label">下载</text>
|
||
</view>
|
||
<view class="floating-btn share-btn" @click="onShare">
|
||
<text class="floating-label">分享</text>
|
||
</view>
|
||
<view class="floating-btn compare-btn" @click="onCompare">
|
||
<text class="floating-label">对比</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import api from '@/api/models-api.js';
|
||
import Cache from '@/utils/cache';
|
||
import { mapGetters } from 'vuex';
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
images: [],
|
||
currentIndex: 0,
|
||
currentTime: '00:25',
|
||
creatorName: '开心海浪',
|
||
creatorAvatar: '/static/images/creator-avatar.png',
|
||
isFollowing: false,
|
||
imageDescription: '',
|
||
isDescriptionExpanded: false,
|
||
isLiked: false,
|
||
likeCount: 6830,
|
||
isLoadingArticle: false,
|
||
articleError: null,
|
||
articleData: null,
|
||
currentArticleId: null,
|
||
touchStart: null,
|
||
touchDistance: 0,
|
||
scaleMap: {},
|
||
minScale: 1,
|
||
maxScale: 3
|
||
,statusBarHeight: 0
|
||
};
|
||
},
|
||
computed: mapGetters(['chatUrl']),
|
||
onLoad(options) {
|
||
const articleId = options.id;
|
||
this.setData({ currentArticleId: articleId });
|
||
const sys = uni.getSystemInfoSync();
|
||
const sbh = sys.statusBarHeight;
|
||
this.statusBarHeight = (sbh && sbh > 0) ? sbh : (sys.platform === 'ios' ? 44 : 24);
|
||
this.statusBarHeight +=30;
|
||
const cached = Cache.getItem('imageDetail_' + articleId);
|
||
if (cached) {
|
||
this.initFromData(cached);
|
||
}
|
||
if (articleId) {
|
||
this.loadArticleDetail(articleId);
|
||
}
|
||
},
|
||
methods: {
|
||
setData(data) {
|
||
let that = this;
|
||
Object.keys(data).forEach(key => { that[key] = data[key]; });
|
||
},
|
||
isImageUrl(url) {
|
||
if (!url) return false;
|
||
const exts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'];
|
||
const u = (url.split('?')[0].split('#')[0] || '').toLowerCase();
|
||
return exts.some(ext => u.endsWith(ext));
|
||
},
|
||
// 处理头像URL,添加前缀
|
||
getAvatarUrl(avatarUrl) {
|
||
if (!avatarUrl || avatarUrl === '/static/images/avatar-default.png') {
|
||
return '/static/images/avatar-default.png';
|
||
}
|
||
// 如果已经是完整URL,直接返回
|
||
if (avatarUrl.startsWith('http://') || avatarUrl.startsWith('https://')) {
|
||
return avatarUrl;
|
||
}
|
||
// 如果是相对路径,添加前缀
|
||
const prefix = 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/';
|
||
// 如果URL已经以/开头,去掉开头的/
|
||
const cleanUrl = avatarUrl.startsWith('/') ? avatarUrl.substring(1) : avatarUrl;
|
||
return prefix + cleanUrl;
|
||
},
|
||
initFromData(data) {
|
||
console.log("===initFromData===",data);
|
||
const imgs = [];
|
||
const vUrl = data.videoUrl || '';
|
||
const iUrl = data.imageInput || data.image_input || '';
|
||
if (this.isImageUrl(vUrl)) {
|
||
imgs.push({ displayUrl: vUrl, originalUrl: vUrl });
|
||
}
|
||
if (iUrl) {
|
||
imgs.push({ displayUrl: iUrl, originalUrl: iUrl });
|
||
}
|
||
if (!imgs.length) {
|
||
// 尝试从 imageOutput 获取图片
|
||
let imageOutput = data.imageOutput || '';
|
||
if (imageOutput) {
|
||
if (typeof imageOutput === 'string') {
|
||
try {
|
||
const parsed = JSON.parse(imageOutput);
|
||
if (Array.isArray(parsed)) {
|
||
parsed.forEach(u => imgs.push({ displayUrl: u, originalUrl: u }));
|
||
} else {
|
||
imgs.push({ displayUrl: parsed, originalUrl: parsed });
|
||
}
|
||
} catch (e) {
|
||
imgs.push({ displayUrl: imageOutput, originalUrl: imageOutput });
|
||
}
|
||
} else if (Array.isArray(imageOutput)) {
|
||
imageOutput.forEach(u => imgs.push({ displayUrl: u, originalUrl: u }));
|
||
}
|
||
}
|
||
if (!imgs.length) {
|
||
const list = data.images || data.image_urls || [];
|
||
if (Array.isArray(list) && list.length) {
|
||
list.forEach(u => imgs.push({ displayUrl: u, originalUrl: u }));
|
||
} else {
|
||
const u = data.cover || '';
|
||
if (u) imgs.push({ displayUrl: u, originalUrl: u });
|
||
}
|
||
}
|
||
}
|
||
const like = data.likeCount || data.visit || 0;
|
||
|
||
// 获取作者头像
|
||
const authorAvatar = this.getAvatarUrl(data.authorAvatar || data.avatar || '');
|
||
|
||
// 获取描述信息,优先使用 prompt,然后是 title、content
|
||
const description = data.prompt || data.title || data.content || data.synopsis || '';
|
||
|
||
// 获取作者名称
|
||
const authorName = data.authorName || data.author || this.creatorName;
|
||
|
||
this.setData({
|
||
images: imgs,
|
||
likeCount: like,
|
||
imageDescription: description,
|
||
creatorName: authorName,
|
||
creatorAvatar: authorAvatar
|
||
});
|
||
},
|
||
loadArticleDetail(articleId) {
|
||
this.setData({ isLoadingArticle: true, articleError: null });
|
||
api.getArticleById(articleId).then(response => {
|
||
const data = response.data || response;
|
||
this.articleData = data;
|
||
Cache.setItem({ name: 'imageDetail_' + articleId, value: data, expires: 3600 * 1000 });
|
||
this.initFromData(data);
|
||
this.setData({ isLoadingArticle: false });
|
||
}).catch(error => {
|
||
this.setData({ articleError: error.message || '获取图片详情失败', isLoadingArticle: false });
|
||
uni.showToast({ title: '获取图片详情失败', icon: 'none', duration: 2000 });
|
||
});
|
||
},
|
||
onImageLoad(idx) {
|
||
this.scaleMap[idx] = 1;
|
||
},
|
||
onImageError(idx) {
|
||
uni.showToast({ title: '图片加载失败', icon: 'none' });
|
||
},
|
||
onImageTap(idx) {
|
||
const urls = this.images.map(i => i.originalUrl);
|
||
uni.previewImage({ urls, current: idx });
|
||
},
|
||
onSwiperChange(e) {
|
||
const i = e.detail.current || 0;
|
||
this.setData({ currentIndex: i });
|
||
},
|
||
onTouchStart(e) {
|
||
if (!e.touches || e.touches.length < 1) return;
|
||
if (e.touches.length === 2) {
|
||
const d = this.distance(e.touches[0], e.touches[1]);
|
||
this.touchDistance = d;
|
||
} else {
|
||
this.touchStart = e.touches[0];
|
||
}
|
||
},
|
||
onTouchMove(e) {
|
||
if (e.touches && e.touches.length === 2) {
|
||
const d = this.distance(e.touches[0], e.touches[1]);
|
||
const delta = d - this.touchDistance;
|
||
const cur = this.scaleMap[this.currentIndex] || 1;
|
||
let next = cur + delta / 300;
|
||
if (next < this.minScale) next = this.minScale;
|
||
if (next > this.maxScale) next = this.maxScale;
|
||
this.scaleMap[this.currentIndex] = next;
|
||
this.touchDistance = d;
|
||
}
|
||
},
|
||
onTouchEnd() {
|
||
this.touchStart = null;
|
||
this.touchDistance = 0;
|
||
},
|
||
distance(p1, p2) {
|
||
const dx = p1.clientX - p2.clientX;
|
||
const dy = p1.clientY - p2.clientY;
|
||
return Math.sqrt(dx * dx + dy * dy);
|
||
},
|
||
onDownload() {
|
||
if (!this.images.length) {
|
||
uni.showToast({ title: '无可下载图片', icon: 'none' });
|
||
return;
|
||
}
|
||
const url = this.images[this.currentIndex].originalUrl;
|
||
uni.showLoading({ title: '下载中...', mask: true });
|
||
uni.downloadFile({
|
||
url,
|
||
success: (res) => {
|
||
if (res.statusCode === 200) {
|
||
this.compressAndSave(res.tempFilePath);
|
||
} else {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '下载失败', icon: 'none' });
|
||
}
|
||
},
|
||
fail: () => {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '下载失败,请重试', icon: 'none' });
|
||
}
|
||
});
|
||
},
|
||
compressAndSave(path) {
|
||
uni.compressImage({
|
||
src: path,
|
||
quality: 80,
|
||
success: (res) => {
|
||
const filePath = res.tempFilePath || path;
|
||
uni.saveImageToPhotosAlbum({
|
||
filePath,
|
||
success: () => {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '已保存到相册', icon: 'success', duration: 2000 });
|
||
},
|
||
fail: (error) => {
|
||
uni.hideLoading();
|
||
if ((error.errMsg || '').includes('auth')) {
|
||
uni.showModal({
|
||
title: '需要相册权限',
|
||
content: '请在设置中开启相册权限',
|
||
confirmText: '去设置',
|
||
success: (modalRes) => { if (modalRes.confirm) uni.openSetting(); }
|
||
});
|
||
} else {
|
||
uni.showToast({ title: '保存失败', icon: 'none' });
|
||
}
|
||
}
|
||
});
|
||
},
|
||
fail: () => {
|
||
uni.saveImageToPhotosAlbum({ filePath: path });
|
||
}
|
||
});
|
||
},
|
||
onShare() {
|
||
uni.showActionSheet({
|
||
itemList: ['分享到微信', '分享到朋友圈', '复制链接'],
|
||
success: (res) => {
|
||
if (res.tapIndex === 2) this.copyLink();
|
||
}
|
||
});
|
||
},
|
||
onCompare() {
|
||
// 跳转到设计效果页
|
||
const params = {
|
||
id: this.currentArticleId || ''
|
||
};
|
||
|
||
// 如果有图片数据,传递图片URL
|
||
if (this.images && this.images.length > 0) {
|
||
if (this.images.length >= 2) {
|
||
// 如果有两张图片,第一张作为改造前,第二张作为改造后
|
||
params.beforeImage = encodeURIComponent(this.images[0].originalUrl || this.images[0].displayUrl);
|
||
params.afterImage = encodeURIComponent(this.images[1].originalUrl || this.images[1].displayUrl);
|
||
} else if (this.images.length === 1) {
|
||
// 如果只有一张图片,作为改造后
|
||
params.afterImage = encodeURIComponent(this.images[0].originalUrl || this.images[0].displayUrl);
|
||
}
|
||
}
|
||
|
||
// 传递提示词
|
||
if (this.imageDescription) {
|
||
params.promptText = encodeURIComponent(this.imageDescription);
|
||
} else if (this.articleData && this.articleData.prompt) {
|
||
params.promptText = encodeURIComponent(this.articleData.prompt);
|
||
}
|
||
|
||
const queryString = Object.keys(params)
|
||
.filter(key => params[key])
|
||
.map(key => `${key}=${params[key]}`)
|
||
.join('&');
|
||
|
||
uni.navigateTo({
|
||
url: `/pages/ai-generate/effect?${queryString}`,
|
||
fail: (err) => {
|
||
console.error('Navigate to effect page failed:', err);
|
||
uni.showToast({
|
||
title: '跳转失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
});
|
||
},
|
||
onBack() {
|
||
uni.navigateBack();
|
||
},
|
||
copyLink() {
|
||
const shareUrl = this.currentArticleId
|
||
? `https://yourapp.com/pages/ai-generate/image?id=${this.currentArticleId}`
|
||
: 'https://yourapp.com/pages/ai-generate/image';
|
||
uni.setClipboardData({ data: shareUrl, success: () => { uni.showToast({ title: '链接已复制', icon: 'success' }); } });
|
||
},
|
||
onToggleDescription() {
|
||
this.isDescriptionExpanded = !this.isDescriptionExpanded;
|
||
},
|
||
onCreatorTap() {
|
||
uni.showToast({ title: `查看${this.creatorName}的主页`, icon: 'none' });
|
||
},
|
||
onFollow() {
|
||
const isFollowing = !this.isFollowing;
|
||
this.setData({ isFollowing });
|
||
uni.showToast({ title: isFollowing ? '关注成功' : '取消关注', icon: 'success' });
|
||
},
|
||
onLike() {
|
||
const isLiked = !this.isLiked;
|
||
const likeCount = isLiked ? this.likeCount + 1 : this.likeCount - 1;
|
||
this.setData({ isLiked, likeCount });
|
||
if (isLiked) uni.vibrateShort();
|
||
},
|
||
onCreateSimilar() {
|
||
const description = this.imageDescription || '';
|
||
const encodedDescription = encodeURIComponent(description);
|
||
|
||
// 获取imageInput作为参考图
|
||
let referenceImage = '';
|
||
if (this.articleData && this.articleData.imageInput) {
|
||
referenceImage = this.articleData.imageInput;
|
||
} else if (this.images && this.images.length > 0) {
|
||
// 如果没有imageInput,使用第一张图片
|
||
referenceImage = this.images[0].originalUrl || this.images[0].displayUrl;
|
||
}
|
||
|
||
const params = {
|
||
description: encodedDescription,
|
||
from: 'image',
|
||
articleId: this.currentArticleId || ''
|
||
};
|
||
|
||
if (referenceImage) {
|
||
params.referenceImage = encodeURIComponent(referenceImage);
|
||
}
|
||
|
||
const queryString = Object.keys(params)
|
||
.filter(key => params[key])
|
||
.map(key => `${key}=${params[key]}`)
|
||
.join('&');
|
||
|
||
uni.navigateTo({
|
||
url: `/pages/ai-generate/index?${queryString}`
|
||
});
|
||
},
|
||
onConsult() {
|
||
// #ifdef MP-WEIXIN
|
||
// 微信小程序环境,打开微信客服
|
||
wx.openCustomerServiceChat({
|
||
extInfo: {
|
||
url: 'https://work.weixin.qq.com/kfid/your_kfid' // 替换为实际的客服链接
|
||
},
|
||
corpId: 'your_corp_id', // 替换为实际的企业ID
|
||
success: (res) => {
|
||
console.log('打开客服成功', res);
|
||
},
|
||
fail: (err) => {
|
||
console.error('打开客服失败', err);
|
||
// 降级方案:跳转到客服页面
|
||
const url = this.chatUrl;
|
||
if (url) {
|
||
uni.navigateTo({ url: `/pages/users/web_page/index?webUel=${encodeURIComponent(url)}&title=客服` });
|
||
} else {
|
||
uni.showToast({ title: '暂无客服', icon: 'none' });
|
||
}
|
||
}
|
||
});
|
||
// #endif
|
||
|
||
// #ifndef MP-WEIXIN
|
||
// 非微信环境,使用原有逻辑
|
||
const url = this.chatUrl;
|
||
if (!url) {
|
||
uni.showToast({ title: '暂无客服链接', icon: 'none' });
|
||
return;
|
||
}
|
||
uni.navigateTo({ url: `/pages/users/web_page/index?webUel=${encodeURIComponent(url)}&title=客服` });
|
||
// #endif
|
||
},
|
||
onShareAppMessage() {
|
||
let title = '精彩图片分享';
|
||
if (this.articleData && this.articleData.title) {
|
||
title = this.articleData.title;
|
||
if (title.length > 28) title = title.substring(0, 25) + '...';
|
||
} else if (this.creatorName) {
|
||
title = `${this.creatorName}的作品`;
|
||
}
|
||
let imageUrl = (this.images[0] && this.images[0].originalUrl) || '/static/images/video-share.png';
|
||
const path = this.currentArticleId ? `/pages/ai-generate/image?id=${this.currentArticleId}` : '/pages/ai-generate/assets';
|
||
return { title, path, imageUrl };
|
||
},
|
||
onShareTimeline() {
|
||
let title = '精彩图片分享';
|
||
if (this.articleData && this.articleData.title) {
|
||
title = this.articleData.title;
|
||
if (title.length > 20) title = title.substring(0, 17) + '...';
|
||
} else if (this.creatorName) {
|
||
title = `${this.creatorName}的作品`;
|
||
}
|
||
let imageUrl = (this.images[0] && this.images[0].originalUrl) || '/static/images/video-share.png';
|
||
return { title, imageUrl };
|
||
},
|
||
onPullDownRefresh() {
|
||
if (this.currentArticleId) this.loadArticleDetail(this.currentArticleId);
|
||
setTimeout(() => { uni.stopPullDownRefresh(); }, 800);
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style>
|
||
.image-container {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
background: #000000;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.media-wrapper { position: absolute; top: 0; left: 0; right: 0; bottom: 0; }
|
||
.image-swiper { width: 100%; height: 100%; }
|
||
.image-swiper swiper-item { height: 100%; }
|
||
.image-slide { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
|
||
.zoom-wrapper { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; transition: transform 0.1s linear; }
|
||
.display-image { width: 100%; height: 100%; }
|
||
.top-controls { position: absolute; top: 0; left: 0; right: 0; height: 48px; display: flex; justify-content: space-between; align-items: flex-end; padding: 0 16px 16px; background: linear-gradient(180deg, rgba(0,0,0,0.6) 0%, transparent 100%); z-index: 10; }
|
||
.left-controls { display: flex; align-items: center; }
|
||
.back-btn { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; margin-right: 12px; }
|
||
.icon-back { font-size: 24px; color: #ffffff; font-weight: bold; }
|
||
.time-display { font-size: 14px; color: #ffffff; font-weight: 500; }
|
||
.bottom-info { position: absolute; bottom: 40rpx; left: 0; right: 0; padding: 11px; background: linear-gradient(0deg, rgba(0,0,0,0.8) 0%, transparent 100%); z-index: 10; }
|
||
.article-status { margin-bottom: 16px; padding: 12px 16px; background: rgba(0, 0, 0, 0.6); border-radius: 8px; }
|
||
.loading-text { color: #ffffff; font-size: 14px; opacity: 0.8; }
|
||
.error-text { color: #ff6b6b; font-size: 14px; }
|
||
.creator-info { display: flex; align-items: center; margin-bottom: 12px; }
|
||
.creator-avatar { width: 40px; height: 40px; border-radius: 20px; margin-right: 12px; border: 2px solid rgba(255, 255, 255, 0.3); }
|
||
.creator-details { flex: 1; display: flex; align-items: center; justify-content: space-between; }
|
||
.creator-name { font-size: 16px; color: #ffffff; font-weight: 500; }
|
||
.follow-btn { padding: 6px 16px; background: #ffffff; color: #333333; border-radius: 16px; font-size: 14px; font-weight: 500; transition: all 0.3s ease; }
|
||
.follow-btn.following { background: rgba(255, 255, 255, 0.3); color: #ffffff; }
|
||
.image-info { margin-bottom: 16px; }
|
||
.image-description { font-size: 14px; color: #ffffff; line-height: 1.4; opacity: 0.9; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; text-overflow: ellipsis; word-break: break-all; cursor: pointer; }
|
||
.image-description.expanded { -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; }
|
||
.expand-toggle { display: flex; justify-content: center; align-items: center; margin-top: 8px; padding: 4px 0; cursor: pointer; }
|
||
.expand-toggle .iconfont { font-size: 16px; color: rgba(255, 255, 255, 0.7); transition: all 0.3s ease; }
|
||
.expand-toggle:active .iconfont { color: rgba(255, 255, 255, 1); transform: scale(1.1); }
|
||
.interaction-area { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
||
.like-section { display: flex; align-items: center; }
|
||
.like-btn { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; margin-right: 8px; transition: transform 0.2s ease; }
|
||
.like-btn:active { transform: scale(1.2); }
|
||
.heart-icon { font-size: 24px; }
|
||
.like-count { font-size: 16px; color: #ffffff; font-weight: 500; }
|
||
.action-buttons { display: flex; align-items: center; gap: 12px; }
|
||
.floating-actions { position: fixed; right: 16px; bottom: 35%; display: flex; flex-direction: column; gap: 20px; z-index: 100; }
|
||
.floating-btn { width: 42px; height: 42px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; background: linear-gradient(135deg, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0.25) 100%); border: 2px solid rgba(255, 255, 255, 0.15); border-radius: 50%; backdrop-filter: blur(20px); transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), 0 4px 8px rgba(0, 0, 0, 0.2); }
|
||
.download-btn { background: linear-gradient(135deg, rgba(76, 175, 80, 0.2) 0%, rgba(56, 142, 60, 0.4) 100%); border-color: rgba(139, 195, 74, 0.3); }
|
||
.share-btn { background: linear-gradient(135deg, rgba(33, 150, 243, 0.2) 0%, rgba(25, 118, 210, 0.4) 100%); border-color: rgba(100, 181, 246, 0.3); }
|
||
.floating-label { font-size: 12px; color: #ffffff; font-weight: 500; letter-spacing: 0.3px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }
|
||
.consult-btn { padding: 12px 24px; background: rgba(255, 255, 255, 0.15); color: #ffffff; border: 1px solid rgba(255, 255, 255, 0.5); border-radius: 24px; font-size: 14px; font-weight: 500; transition: transform 0.2s ease, background 0.2s ease; }
|
||
.consult-btn:active { transform: scale(0.95); background: rgba(255, 255, 255, 0.25); }
|
||
.action-btn { padding: 12px 24px; background: #ffffff; color: #333333; border-radius: 24px; font-size: 14px; font-weight: 500; transition: transform 0.2s ease; }
|
||
.action-btn:active { transform: scale(0.95); }
|
||
.ai-notice { position: absolute; bottom: 180rpx; left: 14.5%; transform: translateX(-50%); display: flex; align-items: center; justify-content: center; gap: 8rpx; padding: 12rpx 24rpx; background: rgba(0, 0, 0, 0.3); border: 1rpx solid rgba(66, 202, 77, 0.5); border-radius: 40rpx; backdrop-filter: blur(10rpx); z-index: 100; }
|
||
.ai-notice { position: fixed; width: 100px;
|
||
height: 30px; top: 99px; left: 72%; transform: none; }
|
||
.ai-notice-text { font-size: 20rpx; color: #42ca4d; line-height: 1.4; }
|
||
.page-indicator { display: flex; justify-content: center; align-items: center; }
|
||
.indicator-text { font-size: 12px; color: rgba(255,255,255,0.8); }
|
||
@keyframes likeAnimation { 0% { transform: scale(1); } 50% { transform: scale(1.3); } 100% { transform: scale(1); } }
|
||
.like-btn.liked .heart-icon { animation: likeAnimation 0.6s ease; }
|
||
</style> |