1201 lines
42 KiB
Vue
1201 lines
42 KiB
Vue
<template>
|
||
<view class="ai-chat-page">
|
||
<!-- 导航栏 -->
|
||
<view class="navbar">
|
||
<!-- 新增:语言切换按钮(左侧) -->
|
||
<button class="lang-switch-btn" @click="toggleLanguage">
|
||
{{ currentLang === 'zh' ? 'English' : '中文' }}
|
||
</button>
|
||
<!-- 中间标题:居中显示 -->
|
||
<view class="navbar-title">{{ langConfig.navbarTitle }}</view>
|
||
|
||
|
||
<!-- 新增:编辑昵称按钮 -->
|
||
<button class="edit-nickname-btn" @click="editNickname" aria-label="编辑昵称">
|
||
<text class="iconfont">✏️</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 快捷功能入口 -->
|
||
<view class="quick-functions">
|
||
<button class="quick-btn expression-btn" @click="toggleExpressionPanel">
|
||
<text class="iconfont icon-expression">😊</text>
|
||
</button>
|
||
<button class="quick-btn" @click="openShoppingBag">
|
||
<text class="iconfont icon-bag">🛒</text>
|
||
|
||
</button>
|
||
<!-- 新增更换头像按钮 -->
|
||
<button class="quick-btn" @click="chooseUserAvatar">
|
||
<text class="iconfont icon-avatar">👤</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 更换头像弹窗 -->
|
||
<view class="avatar-modal" v-if="showAvatarModal">
|
||
<view class="avatar-modal-mask" @click="showAvatarModal = false"></view>
|
||
<view class="avatar-modal-content">
|
||
<view class="avatar-modal-header">
|
||
<text class="avatar-modal-title">{{ langConfig.changeAvatarTitle }}</text>
|
||
<button class="avatar-modal-close" @click="showAvatarModal = false">×</button>
|
||
</view>
|
||
<view class="avatar-modal-body">
|
||
<!-- 预览当前头像 -->
|
||
<view class="avatar-preview">
|
||
<image :src="userAvatarUrl || defaultAvatar" mode="aspectFill" class="preview-img"/>
|
||
</view>
|
||
<!-- 选择头像按钮 -->
|
||
<button class="avatar-select-btn" @click="pickAvatar">{{ langConfig.chooseFromAlbum }}</button>
|
||
<button class="avatar-select-btn camera-btn" @click="captureAvatar">{{ langConfig.takePhoto }}</button>
|
||
<button class="avatar-reset-btn" @click="resetAvatar" v-if="userAvatarUrl">{{ langConfig.resetAvatar }}</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 购物车面板(点击购物袋显示) -->
|
||
<view class="cart-panel" v-if="showCartPanel">
|
||
<view class="cart-header">
|
||
<text class="cart-title">{{ langConfig.myCart }}</text>
|
||
|
||
</view>
|
||
<view class="cart-list">
|
||
<view
|
||
class="cart-item"
|
||
v-for="(product, idx) in cartProducts"
|
||
:key="idx"
|
||
@click="consultCartProduct(product)"
|
||
>
|
||
<image :src="product.imageUrl" mode="widthFix" class="cart-product-img"/>
|
||
<view class="cart-product-info">
|
||
<text class="cart-product-name">{{ product.name }}</text>
|
||
<text class="cart-product-spec">{{ product.spec }}</text>
|
||
<text class="cart-product-price">{{ langConfig.currency }}{{ product.price }}</text>
|
||
<text class="cart-product-count">×{{ product.count }}</text>
|
||
</view>
|
||
<text class="consult-icon">{{ langConfig.consult }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="cart-footer">
|
||
<button class="cart-btn clear-cart" @click="clearCart">{{ langConfig.clearCart }}</button>
|
||
<button class="cart-btn checkout-cart" @click="checkoutCart">{{ langConfig.checkout }}</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 聊天内容区域 -->
|
||
<view class="chat-container">
|
||
<scroll-view
|
||
class="chat-scroll"
|
||
scroll-y
|
||
:scroll-top="scrollTop"
|
||
@scroll="onScroll"
|
||
scroll-with-animation
|
||
>
|
||
<view class="chat-messages">
|
||
<!-- AI消息项 -->
|
||
<view
|
||
class="message-item ai-message"
|
||
v-for="(msg, idx) in chatMessages"
|
||
:key="msg.id"
|
||
>
|
||
<!-- AI头像 -->
|
||
<view class="avatar ai-avatar">
|
||
<svg width="72" height="72" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||
<circle cx="50" cy="50" r="48" fill="#007aff" stroke="#e8f4ff" stroke-width="2"/>
|
||
<circle cx="50" cy="45" r="25" fill="#ffffff"/>
|
||
<circle cx="40" cy="40" r="4" fill="#007aff"/>
|
||
<circle cx="60" cy="40" r="4" fill="#007aff"/>
|
||
<path d="M35,55 Q50,60 65,55" fill="none" stroke="#007aff" stroke-width="2"/>
|
||
<path d="M50,55 L50,65" fill="none" stroke="#007aff" stroke-width="2"/>
|
||
</svg>
|
||
</view>
|
||
<!-- 聊天气泡 -->
|
||
<view class="message-bubble ai-bubble">
|
||
<text class="message-content">{{ msg.content }}</text>
|
||
<!-- 操作按钮 -->
|
||
<view class="message-actions" v-if="msg.showActions">
|
||
<button
|
||
class="action-btn"
|
||
@click="handleLike(msg)"
|
||
:class="{ active: msg.isLiked }"
|
||
>
|
||
<text class="iconfont">👍</text> {{ langConfig.useful }}
|
||
</button>
|
||
<button
|
||
class="action-btn"
|
||
@click="handleDislike(msg)"
|
||
:class="{ active: msg.isDisliked }"
|
||
>
|
||
<text class="iconfont">👎</text> {{ langConfig.useless }}
|
||
</button>
|
||
</view>
|
||
<text class="message-time">{{ formatTime(msg.timestamp) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 用户消息项(头像在气泡右侧) -->
|
||
<view
|
||
class="message-item user-message"
|
||
v-for="(msg, idx) in userMessages"
|
||
:key="msg.id"
|
||
>
|
||
<!-- 用户头像(在右) -->
|
||
<view class="avatar user-avatar">
|
||
<image
|
||
:src="userAvatarUrl || defaultAvatar"
|
||
mode="aspectFill"
|
||
class="user-avatar-img"
|
||
/>
|
||
</view>
|
||
<!-- 聊天气泡(在左) -->
|
||
<view class="message-bubble user-bubble">
|
||
<!-- 新增:用户昵称 -->
|
||
<text class="user-nickname">{{ userNickname }}</text>
|
||
<text class="message-content">{{ msg.content }}</text>
|
||
<text class="message-time">{{ formatTime(msg.timestamp) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- AI加载中 -->
|
||
<view class="message-item ai-message" v-if="isLoading">
|
||
<view class="avatar ai-avatar">
|
||
<svg width="72" height="72" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||
<circle cx="50" cy="50" r="48" fill="#007aff" stroke="#e8f4ff" stroke-width="2"/>
|
||
<circle cx="50" cy="45" r="25" fill="#ffffff"/>
|
||
<circle cx="40" cy="40" r="4" fill="#007aff"/>
|
||
<circle cx="60" cy="40" r="4" fill="#007aff"/>
|
||
<path d="M35,55 Q50,60 65,55" fill="none" stroke="#007aff" stroke-width="2"/>
|
||
<path d="M50,55 L50,65" fill="none" stroke="#007aff" stroke-width="2"/>
|
||
</svg>
|
||
</view>
|
||
<view class="message-bubble ai-bubble loading">
|
||
<view class="loading-dots">
|
||
<view class="dot"></view>
|
||
<view class="dot"></view>
|
||
<view class="dot"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 表情面板(点击表情按钮显示) -->
|
||
<view class="expression-panel" v-if="showExpressionPanel">
|
||
<view class="expression-grid">
|
||
<text class="expression-item" @click="addExpression('😊')">😊</text>
|
||
<text class="expression-item" @click="addExpression('😂')">😂</text>
|
||
<text class="expression-item" @click="addExpression('👍')">👍</text>
|
||
<text class="expression-item" @click="addExpression(langConfig.thanks)">{{ langConfig.thanks }}</text>
|
||
<text class="expression-item" @click="addExpression('?')">?</text>
|
||
<text class="expression-item" @click="addExpression(langConfig.ok)">{{ langConfig.ok }}</text>
|
||
<text class="expression-item" @click="addExpression('👌')">👌</text>
|
||
<text class="expression-item" @click="addExpression(langConfig.trouble)">{{ langConfig.trouble }}</text>
|
||
<text class="expression-item" @click="addExpression('🙏')">🙏</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部输入区域 -->
|
||
<view class="input-area">
|
||
<textarea
|
||
class="input-box"
|
||
v-model="inputContent"
|
||
:placeholder="langConfig.inputPlaceholder"
|
||
@confirm="sendMessage"
|
||
:disabled="isLoading"
|
||
/>
|
||
<button
|
||
class="send-btn"
|
||
@click="sendMessage"
|
||
:disabled="!inputContent.trim() || isLoading"
|
||
>
|
||
<text class="iconfont icon-send">↑</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
// 新增:语言配置
|
||
currentLang: 'zh', // 默认中文:zh / 英文:en
|
||
langConfigs: {
|
||
zh: {
|
||
// 导航/功能按钮
|
||
navbarTitle: 'AI智能客服',
|
||
|
||
// 头像弹窗
|
||
changeAvatarTitle: '更换头像',
|
||
chooseFromAlbum: '从相册选择',
|
||
takePhoto: '拍照上传',
|
||
resetAvatar: '重置默认头像',
|
||
// 购物车
|
||
myCart: '我的购物车',
|
||
currency: '¥',
|
||
consult: '💬 咨询',
|
||
clearCart: '清空购物车',
|
||
checkout: '结算',
|
||
// 聊天相关
|
||
useful: '有用',
|
||
useless: '无用',
|
||
inputPlaceholder: '请输入您的问题...',
|
||
sendBtn: '发送',
|
||
// 表情/通用文本
|
||
thanks: '谢谢',
|
||
ok: '好的',
|
||
trouble: '麻烦了',
|
||
// 系统提示/回复模板
|
||
systemTips: {
|
||
evaluateOpen: '打开了【评价客服】功能,您可以对服务进行打分和评价',
|
||
selfServiceOpen: '打开了【自助服务】功能,包含常见问题解答和自助办理入口',
|
||
cartOpen: '打开了【购物车】,您可以点击任意产品进行咨询',
|
||
cartClear: '购物车已清空',
|
||
cartCheckout: '发起购物车结算',
|
||
chatClear: '聊天记录已清空,有什么可以帮助您的吗?',
|
||
avatarSuccess: '头像更换成功',
|
||
avatarReset: '已重置为默认头像',
|
||
likeSuccess: '感谢您的认可!',
|
||
dislikeSuccess: '我们会继续改进!'
|
||
},
|
||
// AI回复模板
|
||
aiReplies: {
|
||
welcome: '您好!我是智能客服,很高兴为您服务~ 您可以咨询产品相关问题,也可以点击购物袋查看已选产品进行咨询。',
|
||
evaluateGood: '感谢您的认可!我们会继续努力提供更好的产品咨询服务~',
|
||
evaluateBad: '非常抱歉给您带来不好的体验,您可以告诉我具体问题,我会尽力解答!',
|
||
selfService: (item) => `您选择了【${item}】,正在为您提供相关服务...`,
|
||
cartProductReply: {
|
||
dryer: (p) => `您咨询的【${p.name}】相关信息:✨ 产品特点:• 鼓风再生技术,节能30%• 露点稳定在-40℃以下• 全自动运行,无需人工值守💰 价格:¥${p.price}(含13%增值税)📅 货期:现货,下单后48小时发货🔧 质保:2年免费保修,终身维护您想了解安装、参数还是报价详情?`,
|
||
filter: (p) => `您咨询的【${p.name}】相关信息:✨ 产品特点:• 过滤精度可达0.01μm• 压差小,能耗低• 可更换滤芯,维护成本低💰 价格:¥${p.price}(含运费)📅 货期:3-5个工作日🔧 适用介质:压缩空气、氮气、氧气您想了解滤芯寿命、安装方式还是批量采购折扣?`,
|
||
valve: (p) => `您咨询的【${p.name}】相关信息:✨ 产品特点:• 三偏心设计,密封性能好• 耐温范围:-20℃~450℃• 操作扭矩小,启闭轻便💰 价格:¥${p.price}(含安装配件)📅 货期:7-10个工作日🔧 适用场景:化工、冶金、电力行业您想了解压力等级、材质还是定制服务?`
|
||
},
|
||
dryerReply: `关于干燥机的常见问题解答:1. 干燥机露点不稳定?→ 检查再生风机是否正常,吸附剂是否需要更换2. 能耗过高?→ 可调整再生周期,或升级为变频控制3. 维护成本?→ 每年更换一次吸附剂,成本约1200元您想了解具体哪方面的问题?`,
|
||
filterReply: `关于过滤器的常见问题解答:1. 滤芯更换周期?→ 一般6-8个月,根据使用工况调整2. 压差过大?→ 及时更换滤芯,检查进气杂质含量3. 过滤精度选择?→ 气动设备选0.01μm,一般用气选1μm您想了解具体哪方面的问题?`,
|
||
valveReply: `关于阀门的常见问题解答:1. 密封泄漏?→ 检查阀座磨损情况,及时更换密封件2. 操作卡顿?→ 加注专用润滑油,检查阀杆是否变形3. 耐高温范围?→ 最高可达450℃,特殊定制可到600℃您想了解具体哪方面的问题?`,
|
||
generalReply: (content) => `您咨询的"${content}"我已收到!如果是产品相关问题,您可以:1. 告诉我具体产品名称/型号2. 打开购物车点击对应产品咨询3. 描述您遇到的具体问题我会尽力为您解答~`,
|
||
cartCheckoutReply: (products) => `您购物车中有以下产品待结算:` +
|
||
products.map(p => `• ${p.name} ×${p.count} ¥${p.price}`).join('') +
|
||
`总计:¥${products.reduce((sum, p) => sum + p.price * p.count, 0)}您可以选择:1. 在线支付2. 线下转账3. 申请报价单请告诉我您的选择~`,
|
||
aboutUs: '智能客服系统 v1.0专注于工业流体设备咨询服务~'
|
||
}
|
||
},
|
||
en: {
|
||
|
||
// 头像弹窗
|
||
changeAvatarTitle: 'Change Avatar',
|
||
chooseFromAlbum: 'Select from Album',
|
||
takePhoto: 'Take Photo',
|
||
resetAvatar: 'Reset to Default',
|
||
// 购物车
|
||
myCart: 'My Cart',
|
||
currency: '$',
|
||
consult: '💬 Consult',
|
||
clearCart: 'Clear Cart',
|
||
checkout: 'Checkout',
|
||
// 聊天相关
|
||
useful: 'Useful',
|
||
useless: 'Useless',
|
||
inputPlaceholder: 'Please enter your question...',
|
||
sendBtn: 'Send',
|
||
// 表情/通用文本
|
||
thanks: 'Thanks',
|
||
ok: 'OK',
|
||
trouble: 'Sorry to trouble you',
|
||
// 系统提示/回复模板
|
||
systemTips: {
|
||
evaluateOpen: 'Opened [Rate Service] function, you can rate and comment on the service',
|
||
selfServiceOpen: 'Opened [Self Service] function, including FAQ and self-service entry',
|
||
cartOpen: 'Opened [Shopping Cart], you can click any product to consult',
|
||
cartClear: 'Cart cleared',
|
||
cartCheckout: 'Initiated cart checkout',
|
||
chatClear: 'Chat history cleared, how can I help you?',
|
||
avatarSuccess: 'Avatar changed successfully',
|
||
avatarReset: 'Reset to default avatar',
|
||
likeSuccess: 'Thank you for your recognition!',
|
||
dislikeSuccess: 'We will continue to improve!'
|
||
},
|
||
// AI回复模板
|
||
aiReplies: {
|
||
welcome: 'Hello! I am an intelligent customer service, glad to serve you~ You can consult product-related questions, or click the shopping bag to view selected products for consultation.',
|
||
evaluateGood: 'Thank you for your recognition! We will continue to strive to provide better product consulting services~',
|
||
evaluateBad: 'We are very sorry for the bad experience. You can tell me the specific problem and I will try my best to answer it!',
|
||
selfService: (item) => `You selected [${item}], we are providing relevant services for you...`,
|
||
cartProductReply: {
|
||
dryer: (p) => `Information about [${p.name}] you consulted:✨ Product Features:• Blast regeneration technology, 30% energy saving• Dew point stable below -40℃• Fully automatic operation, no manual on duty💰 Price: $${p.price} (including 13% VAT)📅 Delivery: In stock, shipped within 48 hours after order🔧 Warranty: 2 years free warranty, lifetime maintenanceDo you want to know about installation, parameters or quotation details?`,
|
||
filter: (p) => `Information about [${p.name}] you consulted:✨ Product Features:• Filtration accuracy up to 0.01μm• Low pressure difference and energy consumption• Replaceable filter element, low maintenance cost💰 Price: $${p.price} (including shipping)📅 Delivery: 3-5 working days🔧 Applicable media: Compressed air, nitrogen, oxygenDo you want to know about filter element life, installation method or bulk purchase discount?`,
|
||
valve: (p) => `Information about [${p.name}] you consulted:✨ Product Features:• Triple eccentric design, good sealing performance• Temperature resistance range: -20℃~450℃• Small operating torque, easy to open and close💰 Price: $${p.price} (including installation accessories)📅 Delivery: 7-10 working days🔧 Applicable scenarios: Chemical, metallurgical, power industryDo you want to know about pressure rating, material or customization service?`
|
||
},
|
||
dryerReply: `FAQ about dryers:1. Unstable dryer dew point? → Check if the regeneration fan is normal and if the adsorbent needs replacement2. High energy consumption? → Adjust regeneration cycle or upgrade to frequency conversion control3. Maintenance cost? → Replace adsorbent once a year, cost about $1200Which specific aspect do you want to know about?`,
|
||
filterReply: `FAQ about filters:1. Filter element replacement cycle? → Generally 6-8 months, adjusted according to working conditions2. Excessive pressure difference? → Replace filter element in time and check inlet impurity content3. Filtration accuracy selection? → 0.01μm for pneumatic equipment, 1μm for general gas useWhich specific aspect do you want to know about?`,
|
||
valveReply: `FAQ about valves:1. Seal leakage? → Check valve seat wear and replace seal in time2. Operation jamming? → Add special lubricating oil and check if valve stem is deformed3. High temperature resistance range? → Up to 450℃, special customization up to 600℃Which specific aspect do you want to know about?`,
|
||
generalReply: (content) => `I have received your inquiry about "${content}"!If it is a product-related question, you can:1. Tell me the specific product name/model2. Open the shopping cart and click the corresponding product to consult3. Describe the specific problem you encounteredI will try my best to answer you~`,
|
||
cartCheckoutReply: (products) => `You have the following products to checkout in your cart:` +
|
||
products.map(p => `• ${p.name} ×${p.count} $${p.price}`).join('') +
|
||
`Total: $${products.reduce((sum, p) => sum + p.price * p.count, 0)}You can choose:1. Online payment2. Offline transfer3. Apply for quotationPlease tell me your choice~`,
|
||
aboutUs: 'Smart Customer Service System v1.0Focus on industrial fluid equipment consulting services~'
|
||
}
|
||
}
|
||
},
|
||
|
||
// 聊天数据
|
||
chatMessages: [],
|
||
userMessages: [],
|
||
inputContent: '',
|
||
isLoading: false,
|
||
scrollTop: 0,
|
||
|
||
// 功能面板控制
|
||
showExpressionPanel: false,
|
||
showCartPanel: false,
|
||
|
||
// 头像相关
|
||
showAvatarModal: false,
|
||
userAvatarUrl: '',
|
||
defaultAvatar: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDgiIGZpbGw9IiNmNWY3ZmEiIHN0cm9rZT0iI2UwZTBlMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PGNpcmNsZSBjeD0iNTAiIGN5PSIzNSIgcj0iMTUiIGZpbGw9IiNkOWUzZjAiLz48cGF0aCBkPSJNMjAsODAgUTUwLDYwIDgwLDgwIiBmaWxsPSIjZDllM2YwIi8+PC9zdmc+',
|
||
|
||
// 购物车产品数据
|
||
cartProducts: [
|
||
{id: 1,name: 'PFW无热再生压缩空气干燥机 / PFW Heatless Regeneration Compressed Air Dryer',spec: '型号:PFW-06 压力:1.6MPa / Model: PFW-06 Pressure: 1.6MPa',price: 3580,count: 1,imageUrl: 'https://p3-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/d2554fd9ddbb40a990951499a4c1fabe.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'dryer'},
|
||
{id: 2,name: 'PFG鼓风再生压缩空气干燥机 / PFG Blast Regeneration Compressed Air Dryer',spec: '型号:PFG-100 处理量:10m³/min / Model: PFG-100 Capacity: 10m³/min',price: 12800,count: 1,imageUrl: 'https://p11-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/d4dc4adb45464911b21764d4585d3602.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'dryer'},
|
||
{id: 3,name: 'PFY压缩热再生压缩空气干燥机 / PFY Compression Heat Regeneration Dryer',spec: '型号:PFY-200 处理量:20m³/min / Model: PFY-200 Capacity: 20m³/min',price: 18600,count: 1,imageUrl: 'https://p11-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/d1b86bda771d464fadb6bcc087c1ac4d.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'dryer'},
|
||
{id: 4,name: 'PFA组合式低露点干燥机 / PFA Combined Low Dew Point Dryer',spec: '型号:PFA-300 露点:-70℃ / Model: PFA-300 Dew Point: -70℃',price: 28900,count: 1,imageUrl: 'https://p11-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/e419a257befb4209953dabc636ffb276.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'dryer'},
|
||
{id: 5,name: 'PFB微热再生压缩空气干燥机 / PFB Micro Heat Regeneration Dryer',spec: '型号:PFB-150 处理量:15m³/min / Model: PFB-150 Capacity: 15m³/min',price: 15600,count: 1,imageUrl: 'https://p26-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/1b0dbc17b2ef4b0bb646af100cff57dc.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'dryer'},
|
||
{id: 6,name: 'PFD冷冻式压缩空气干燥机 / PFD Refrigerated Compressed Air Dryer',spec: '型号:PFD-080 处理量:8m³/min / Model: PFD-080 Capacity: 8m³/min',price: 9800,count: 1,imageUrl: 'https://p26-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/d7d7ddd2ad714c62a61752fcaf5c0847.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'dryer'},
|
||
{id: 7,name: '高性能程控阀 / High Performance Programmed Valve',spec: '型号:V-100 材质:铸铁 / Model: V-100 Material: Cast Iron',price: 2380,count: 1,imageUrl: 'https://p26-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/ac81fe92afee4243b0e1698bab2cbd68.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'valve'},
|
||
{id: 8,name: '三偏心蝶阀 / Triple Eccentric Butterfly Valve',spec: '型号:D373H-16C 口径:DN100 / Model: D373H-16C Caliber: DN100',price: 3580,count: 1,imageUrl: 'https://p26-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/0a4279a4ee034fc0b8f3c192fd3dcb2a.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'valve'},
|
||
{id: 9,name: '制氮专用角座阀 / Nitrogen Making Angle Seat Valve',spec: '型号:JZ-20 材质:不锈钢304 / Model: JZ-20 Material: Stainless Steel 304',price: 1860,count: 1,imageUrl: 'https://p3-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/d4a98659c8b743a59cf84953b4e67ce0.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'valve'},
|
||
{id: 10,name: '智能精控调节阀 / Intelligent Precision Control Valve',spec: '型号:ZDLP-16 调节精度:±1% / Model: ZDLP-16 Adjustment Accuracy: ±1%',price: 5680,count: 1,imageUrl: 'https://p3-flow-imagex-download-sign.byteimg.com/tos-cn-i-a9rns2rl98/96622539231f4bd4bacce9a2e62dca96.png~tplv-a9rns2rl98-resize-jpeg-v1.png',type: 'valve'},
|
||
|
||
],
|
||
|
||
// ====== 新增:昵称相关 ======
|
||
userNickname: '', // 当前昵称
|
||
defaultNicknames: { zh: '顾客', en: 'Customer' } // 默认昵称
|
||
};
|
||
},
|
||
computed: {
|
||
// 新增:当前语言配置(简化模板调用)
|
||
langConfig() {
|
||
return this.langConfigs[this.currentLang];
|
||
}
|
||
},
|
||
onLoad() {
|
||
// 初始化欢迎语(根据当前语言)
|
||
this.chatMessages.push({
|
||
id: 1,
|
||
content: this.langConfig.aiReplies.welcome,
|
||
timestamp: Date.now() - 60000,
|
||
showActions: true,
|
||
isLiked: false,
|
||
isDisliked: false
|
||
});
|
||
// 加载本地存储的头像和昵称
|
||
this.loadUserAvatar();
|
||
this.loadUserNickname(); // 👈 新增
|
||
this.scrollToBottom();
|
||
},
|
||
// 适配PC端挂载
|
||
mounted() {
|
||
this.loadUserAvatar();
|
||
this.loadUserNickname(); // 👈 新增
|
||
this.scrollToBottom();
|
||
// 监听窗口大小变化,保持样式一致
|
||
if (typeof window !== 'undefined') {
|
||
window.addEventListener('resize', this.handleResize);
|
||
}
|
||
},
|
||
beforeDestroy() {
|
||
if (typeof window !== 'undefined') {
|
||
window.removeEventListener('resize', this.handleResize);
|
||
}
|
||
},
|
||
methods: {
|
||
// ====== 新增:加载用户昵称 ======
|
||
loadUserNickname() {
|
||
let saved = null;
|
||
try {
|
||
// 兼容 uni-app 和 H5
|
||
if (typeof uni !== 'undefined' && uni.getStorageSync) {
|
||
saved = uni.getStorageSync('userNickname');
|
||
} else if (typeof localStorage !== 'undefined') {
|
||
saved = localStorage.getItem('userNickname');
|
||
}
|
||
} catch (e) {
|
||
console.warn('Failed to load user nickname:', e);
|
||
}
|
||
// 若无保存,则使用当前语言默认值
|
||
this.userNickname = saved || this.defaultNicknames[this.currentLang];
|
||
},
|
||
|
||
// ====== 新增:保存用户昵称 ======
|
||
saveUserNickname(nickname) {
|
||
try {
|
||
if (typeof uni !== 'undefined' && uni.setStorageSync) {
|
||
uni.setStorageSync('userNickname', nickname);
|
||
} else if (typeof localStorage !== 'undefined') {
|
||
localStorage.setItem('userNickname', nickname);
|
||
}
|
||
} catch (e) {
|
||
console.warn('Failed to save user nickname:', e);
|
||
}
|
||
},
|
||
|
||
// ====== 新增:编辑昵称弹窗 ======
|
||
editNickname() {
|
||
const confirmText = this.currentLang === 'zh' ? '确定' : 'OK';
|
||
const cancelText = this.currentLang === 'zh' ? '取消' : 'Cancel';
|
||
const title = this.currentLang === 'zh' ? '修改昵称' : 'Change Nickname';
|
||
|
||
uni.showModal({
|
||
title,
|
||
placeholderText: this.userNickname,
|
||
editable: true,
|
||
confirmText,
|
||
cancelText,
|
||
success: (res) => {
|
||
if (res.confirm && res.content?.trim()) {
|
||
const newNick = res.content.trim();
|
||
this.userNickname = newNick;
|
||
this.saveUserNickname(newNick);
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 原有方法:返回上一页
|
||
goBack() {
|
||
uni.navigateBack();
|
||
},
|
||
|
||
// 原有方法:滚动到底部
|
||
scrollToBottom() {
|
||
this.$nextTick(() => {
|
||
this.scrollTop = 999999;
|
||
});
|
||
},
|
||
|
||
// 原有方法:窗口大小变化处理
|
||
handleResize() {
|
||
this.$nextTick(this.scrollToBottom);
|
||
},
|
||
|
||
// 原有方法:滚动事件
|
||
onScroll(e) {
|
||
// 可扩展:判断是否在底部等
|
||
},
|
||
|
||
// 原有方法:切换语言
|
||
toggleLanguage() {
|
||
this.currentLang = this.currentLang === 'zh' ? 'en' : 'zh';
|
||
// 切换语言时,若用户未自定义昵称,则更新为对应默认值
|
||
if (!this.userNickname ||
|
||
this.userNickname === this.defaultNicknames.zh ||
|
||
this.userNickname === this.defaultNicknames.en) {
|
||
this.userNickname = this.defaultNicknames[this.currentLang];
|
||
}
|
||
// 保存语言设置
|
||
if (typeof uni !== 'undefined') {
|
||
uni.setStorageSync('appLang', this.currentLang);
|
||
}
|
||
},
|
||
|
||
// 原有方法:发送消息
|
||
sendMessage() {
|
||
const content = this.inputContent.trim();
|
||
if (!content || this.isLoading) return;
|
||
|
||
// 添加用户消息
|
||
const userMsg = {
|
||
id: Date.now(),
|
||
content,
|
||
timestamp: Date.now()
|
||
};
|
||
this.userMessages.push(userMsg);
|
||
this.inputContent = '';
|
||
this.showExpressionPanel = false;
|
||
|
||
// 滚动到底部
|
||
this.scrollToBottom();
|
||
|
||
// 模拟AI回复(简化版,实际可调用API)
|
||
this.isLoading = true;
|
||
setTimeout(() => {
|
||
this.isLoading = false;
|
||
let aiReply = '';
|
||
|
||
// 匹配关键词回复
|
||
if (content.includes('干燥机') || content.includes('dryer')) {
|
||
aiReply = this.langConfig.aiReplies.dryerReply;
|
||
} else if (content.includes('过滤器') || content.includes('filter')) {
|
||
aiReply = this.langConfig.aiReplies.filterReply;
|
||
} else if (content.includes('阀门') || content.includes('valve')) {
|
||
aiReply = this.langConfig.aiReplies.valveReply;
|
||
} else if (content.includes('关于我们') || content.includes('about us')) {
|
||
aiReply = this.langConfig.aiReplies.aboutUs;
|
||
} else {
|
||
aiReply = this.langConfig.aiReplies.generalReply(content);
|
||
}
|
||
|
||
this.chatMessages.push({
|
||
id: Date.now() + 1,
|
||
content: aiReply,
|
||
timestamp: Date.now(),
|
||
showActions: true,
|
||
isLiked: false,
|
||
isDisliked: false
|
||
});
|
||
this.scrollToBottom();
|
||
}, 800);
|
||
},
|
||
|
||
// 原有方法:表情面板
|
||
toggleExpressionPanel() {
|
||
this.showExpressionPanel = !this.showExpressionPanel;
|
||
},
|
||
addExpression(text) {
|
||
this.inputContent += text;
|
||
this.showExpressionPanel = false;
|
||
},
|
||
|
||
// 原有方法:购物车
|
||
openShoppingBag() {
|
||
this.showCartPanel = true;
|
||
},
|
||
consultCartProduct(product) {
|
||
const msg = `我想咨询购物车中的【${product.name}】`;
|
||
this.inputContent = msg;
|
||
this.showCartPanel = false;
|
||
},
|
||
clearCart() {
|
||
this.cartProducts = [];
|
||
uni.showToast({ title: this.langConfig.systemTips.cartClear, icon: 'none' });
|
||
this.showCartPanel = false;
|
||
},
|
||
checkoutCart() {
|
||
if (this.cartProducts.length === 0) return;
|
||
const reply = this.langConfig.aiReplies.cartCheckoutReply(this.cartProducts);
|
||
this.chatMessages.push({
|
||
id: Date.now(),
|
||
content: reply,
|
||
timestamp: Date.now(),
|
||
showActions: true,
|
||
isLiked: false,
|
||
isDisliked: false
|
||
});
|
||
this.showCartPanel = false;
|
||
this.scrollToBottom();
|
||
},
|
||
|
||
// 原有方法:评价与自助服务
|
||
openEvaluateService() {
|
||
uni.showToast({ title: this.langConfig.systemTips.evaluateOpen, icon: 'none' });
|
||
},
|
||
openSelfService() {
|
||
uni.showToast({ title: this.langConfig.systemTips.selfServiceOpen, icon: 'none' });
|
||
},
|
||
|
||
// 原有方法:消息反馈
|
||
handleLike(msg) {
|
||
if (msg.isDisliked) return;
|
||
msg.isLiked = !msg.isLiked;
|
||
msg.isDisliked = false;
|
||
if (msg.isLiked) {
|
||
uni.showToast({ title: this.langConfig.systemTips.likeSuccess, icon: 'success' });
|
||
}
|
||
},
|
||
handleDislike(msg) {
|
||
if (msg.isLiked) return;
|
||
msg.isDisliked = !msg.isDisliked;
|
||
msg.isLiked = false;
|
||
if (msg.isDisliked) {
|
||
uni.showToast({ title: this.langConfig.systemTips.dislikeSuccess, icon: 'none' });
|
||
}
|
||
},
|
||
|
||
// 原有方法:头像更换
|
||
chooseUserAvatar() {
|
||
this.showAvatarModal = true;
|
||
},
|
||
pickAvatar() {
|
||
uni.chooseImage({
|
||
count: 1,
|
||
success: (res) => {
|
||
this.userAvatarUrl = res.tempFilePaths[0];
|
||
this.saveUserAvatar(res.tempFilePaths[0]);
|
||
uni.showToast({ title: this.langConfig.systemTips.avatarSuccess, icon: 'success' });
|
||
}
|
||
});
|
||
this.showAvatarModal = false;
|
||
},
|
||
captureAvatar() {
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sourceType: ['camera'],
|
||
success: (res) => {
|
||
this.userAvatarUrl = res.tempFilePaths[0];
|
||
this.saveUserAvatar(res.tempFilePaths[0]);
|
||
uni.showToast({ title: this.langConfig.systemTips.avatarSuccess, icon: 'success' });
|
||
}
|
||
});
|
||
this.showAvatarModal = false;
|
||
},
|
||
resetAvatar() {
|
||
this.userAvatarUrl = '';
|
||
this.saveUserAvatar('');
|
||
uni.showToast({ title: this.langConfig.systemTips.avatarReset, icon: 'none' });
|
||
this.showAvatarModal = false;
|
||
},
|
||
saveUserAvatar(url) {
|
||
try {
|
||
if (typeof uni !== 'undefined') {
|
||
uni.setStorageSync('userAvatar', url);
|
||
}
|
||
} catch (e) {
|
||
console.warn('Save avatar failed:', e);
|
||
}
|
||
},
|
||
loadUserAvatar() {
|
||
try {
|
||
if (typeof uni !== 'undefined') {
|
||
const saved = uni.getStorageSync('userAvatar');
|
||
if (saved) {
|
||
this.userAvatarUrl = saved;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.warn('Load avatar failed:', e);
|
||
}
|
||
},
|
||
|
||
// 原有方法:时间格式化
|
||
formatTime(timestamp) {
|
||
const date = new Date(timestamp);
|
||
const now = new Date();
|
||
const diff = now - date;
|
||
const minutes = Math.floor(diff / 60000);
|
||
if (minutes < 1) return this.currentLang === 'zh' ? '刚刚' : 'Just now';
|
||
if (minutes < 60) return `${minutes}${this.currentLang === 'zh' ? '分钟前' : 'm ago'}`;
|
||
const hours = Math.floor(minutes / 60);
|
||
if (hours < 24) return `${hours}${this.currentLang === 'zh' ? '小时前' : 'h ago'}`;
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const hour = String(date.getHours()).padStart(2, '0');
|
||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||
return `${month}-${day} ${hour}:${minute}`;
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* ========== 新增:用户昵称样式 ========== */
|
||
.user-nickname {
|
||
font-size: 12px;
|
||
color: #888;
|
||
opacity: 0.9;
|
||
margin-bottom: 4px;
|
||
display: block;
|
||
text-align: left;
|
||
}
|
||
|
||
/* ========== 原有样式保持不变 ========== */
|
||
.ai-chat-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
background-color: #f5f7fa;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.lang-switch-btn {
|
||
padding: 6px 12px;
|
||
background: rgba(0, 122, 255, 0.8);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 20px;
|
||
font-size: 14px;
|
||
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
|
||
}
|
||
|
||
.top-function-bar {
|
||
display: flex;
|
||
justify-content: center;
|
||
padding: 12px 0;
|
||
background: white;
|
||
border-bottom: 1px solid #eee;
|
||
gap: 30px;
|
||
}
|
||
.function-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
background: none;
|
||
border: none;
|
||
color: #666;
|
||
font-size: 12px;
|
||
}
|
||
.function-btn .icon {
|
||
font-size: 20px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.navbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 16px;
|
||
background-color: #007aff;
|
||
color: white;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
.navbar-title {
|
||
flex: 1;
|
||
text-align: center;
|
||
margin: 0 10px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.edit-nickname-btn {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 50%;
|
||
background: rgba(255,255,255,0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: none;
|
||
color: white;
|
||
font-size: 16px;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.quick-functions {
|
||
display: flex;
|
||
justify-content: center;
|
||
padding: 12px 0;
|
||
background: white;
|
||
border-bottom: 1px solid #eee;
|
||
gap: 40px;
|
||
position: relative;
|
||
}
|
||
.quick-btn {
|
||
position: relative;
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
color: #666;
|
||
}
|
||
.cart-badge {
|
||
position: absolute;
|
||
top: -6px;
|
||
right: -6px;
|
||
background: red;
|
||
color: white;
|
||
font-size: 10px;
|
||
padding: 2px 4px;
|
||
border-radius: 10px;
|
||
min-width: 14px;
|
||
text-align: center;
|
||
}
|
||
|
||
.avatar-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 2000;
|
||
}
|
||
.avatar-modal-mask {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0,0,0,0.5);
|
||
}
|
||
.avatar-modal-content {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
background: white;
|
||
border-top-left-radius: 20px;
|
||
border-top-right-radius: 20px;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
}
|
||
.avatar-modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
.avatar-modal-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
.avatar-modal-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
color: #999;
|
||
}
|
||
.avatar-preview {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
.preview-img {
|
||
width: 100px;
|
||
height: 100px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
border: 2px solid #eee;
|
||
}
|
||
.avatar-select-btn,
|
||
.avatar-reset-btn {
|
||
width: 100%;
|
||
padding: 12px;
|
||
margin-bottom: 10px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
background: #007aff;
|
||
color: white;
|
||
font-size: 16px;
|
||
}
|
||
.camera-btn {
|
||
background: #34c759;
|
||
}
|
||
.avatar-reset-btn {
|
||
background: #ff3b30;
|
||
}
|
||
|
||
.cart-panel {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: white;
|
||
z-index: 1500;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
overflow-y: auto;
|
||
}
|
||
.cart-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.cart-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
.close-cart {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
color: #999;
|
||
}
|
||
.cart-list {
|
||
margin-bottom: 20px;
|
||
}
|
||
.cart-item {
|
||
display: flex;
|
||
padding: 12px;
|
||
border: 1px solid #eee;
|
||
border-radius: 12px;
|
||
margin-bottom: 12px;
|
||
cursor: pointer;
|
||
}
|
||
.cart-product-img {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 8px;
|
||
margin-right: 12px;
|
||
object-fit: cover;
|
||
}
|
||
.cart-product-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
}
|
||
.cart-product-name {
|
||
font-weight: bold;
|
||
font-size: 14px;
|
||
margin-bottom: 4px;
|
||
}
|
||
.cart-product-spec {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-bottom: 4px;
|
||
}
|
||
.cart-product-price {
|
||
color: #ff3b30;
|
||
font-weight: bold;
|
||
}
|
||
.cart-product-count {
|
||
color: #999;
|
||
font-size: 12px;
|
||
}
|
||
.consult-icon {
|
||
color: #007aff;
|
||
font-size: 18px;
|
||
margin-left: 10px;
|
||
align-self: center;
|
||
}
|
||
.cart-footer {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
.cart-btn {
|
||
flex: 1;
|
||
padding: 12px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
color: white;
|
||
}
|
||
.clear-cart {
|
||
background: #ff3b30;
|
||
}
|
||
.checkout-cart {
|
||
background: #34c759;
|
||
}
|
||
|
||
.chat-container {
|
||
flex: 1;
|
||
padding: 12px;
|
||
overflow: hidden;
|
||
}
|
||
.chat-scroll {
|
||
height: 100%;
|
||
}
|
||
.chat-messages {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.message-item {
|
||
display: flex;
|
||
margin-bottom: 16px;
|
||
max-width: 80%;
|
||
}
|
||
.ai-message {
|
||
flex-direction: row;
|
||
}
|
||
.user-message {
|
||
flex-direction: row-reverse;
|
||
align-self: flex-end;
|
||
}
|
||
.avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
margin: 0 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
.ai-avatar {
|
||
margin-right: 8px;
|
||
}
|
||
.user-avatar {
|
||
margin-left: 8px;
|
||
}
|
||
.avatar svg,
|
||
.avatar image {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
|
||
/* ======== 重点:美化 AI 气泡 ======== */
|
||
.message-bubble {
|
||
padding: 10px 16px;
|
||
border-radius: 16px;
|
||
position: relative;
|
||
max-width: 85%;
|
||
word-break: break-word;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||
transition: all 0.2s ease;
|
||
font-size: 15px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.ai-bubble {
|
||
background-color: white;
|
||
border: 1px solid #e0e0e0;
|
||
color: #333;
|
||
border-radius: 16px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||
}
|
||
|
||
.user-bubble {
|
||
background-color: #007aff;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 16px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.message-time {
|
||
font-size: 10px;
|
||
opacity: 0.7;
|
||
margin-top: 6px;
|
||
display: block;
|
||
text-align: right;
|
||
}
|
||
|
||
.message-actions {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.message-actions {
|
||
margin-top: 12px;
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
/* ✅ 修改点:让“有用/无用”按钮更扁平 */
|
||
.action-btn {
|
||
padding: 6px 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 12px;
|
||
background: white;
|
||
color: #666;
|
||
font-size: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||
transition: all 0.2s ease;
|
||
height: 28px;
|
||
}
|
||
|
||
.action-btn.active {
|
||
background: #007aff;
|
||
color: white;
|
||
border-color: #007aff;
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.loading-dots {
|
||
display: flex;
|
||
gap: 4px;
|
||
justify-content: flex-start;
|
||
}
|
||
.dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
background: #007aff;
|
||
border-radius: 50%;
|
||
animation: loading 1.4s infinite ease-in-out both;
|
||
}
|
||
.dot:nth-child(1) { animation-delay: -0.32s; }
|
||
.dot:nth-child(2) { animation-delay: -0.16s; }
|
||
@keyframes loading {
|
||
0%, 80%, 100% { transform: scale(0); }
|
||
40% { transform: scale(1); }
|
||
}
|
||
|
||
.expression-panel {
|
||
position: fixed;
|
||
bottom: 80px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 12px;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||
z-index: 100;
|
||
}
|
||
.expression-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 12px;
|
||
}
|
||
.expression-item {
|
||
font-size: 20px;
|
||
text-align: center;
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
background: #f5f7fa;
|
||
cursor: pointer;
|
||
}
|
||
.expression-item:hover {
|
||
background: #e0e6ed;
|
||
}
|
||
|
||
.input-area {
|
||
display: flex;
|
||
padding: 12px;
|
||
background: white;
|
||
border-top: 1px solid #eee;
|
||
gap: 10px;
|
||
align-items: center; /* 👈 关键:使子元素垂直居中 */
|
||
}
|
||
.input-box {
|
||
flex: 1;
|
||
padding: 12px 16px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 24px;
|
||
font-size: 16px;
|
||
min-height: 20px;
|
||
max-height: 100px;
|
||
resize: none;
|
||
outline: none;
|
||
box-shadow: none;
|
||
/* 新增:防止文字过长撑开 */
|
||
overflow-y: auto;
|
||
/* 确保内容不会破坏布局 */
|
||
height: 44px; /* 👈 固定高度,与 send-btn 一致 */
|
||
}
|
||
.send-btn {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 50%;
|
||
background: #007aff;
|
||
color: white;
|
||
border: none;
|
||
font-size: 18px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: background 0.2s ease;
|
||
}
|
||
/* 确保图标居中 */
|
||
.send-btn .iconfont {
|
||
font-size: 20px;
|
||
line-height: 1;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* 响应式适配 */
|
||
@media (min-width: 768px) {
|
||
.ai-chat-page {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
box-shadow: 0 0 20px rgba(0,0,0,0.1);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
}
|
||
.lang-switch-btn {
|
||
margin-right: 10px;
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
}
|
||
}
|
||
</style> |