Files
lucky_shop/pages_tool/ai-chat/index.vue

869 lines
25 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="wrapper">
<!--自定义导航头部 - 绝对定位在顶部,不随键盘上移 -->
<!-- #ifdef MP-WEIXIN -->
<view class="custom-navbar" ref="pageHeader" v-if="showCustomNavbar">
<view class="header-left">
<button class="back-btn" v-show="showBackButton" @click="goBack">
<text class="iconfont icon-back"></text>
</button>
<view v-show="!showMenuButton" class="placeholder"></view>
</view>
</view>
<!-- #endif -->
<!-- 聊天内容区域:从导航栏下方到容器底部 -->
<!-- AI 智能页始终全屏,不让位给底部 tabBar,聊天区直接占满到底 -->
<view class="chat-content" :style="{ bottom: '0rpx' }">
<ai-chat-message
v-if="uiCfgReady"
ref="chat"
:preloaded-ui-config="preloadedUiCfg"
:initial-messages="initialMessages"
:fallback-welcome="fallbackWelcome"
:user-avatar="userAvatar"
:ai-avatar="aiAvatar"
@message-sent="onMessageSent"
@ai-response="onAIResponse"
@action-click="onActionClick"
@history-loaded="onHistoryLoaded"
@file-preview="onFilePreview"
@audio-play="onAudioPlay"
@audio-pause="onAudioPause"
@video-play="onVideoPlay"
@video-pause="onVideoPause"
@link-open="onLinkOpen"
@product-view="onProductView"
@input-change="onInputChange" />
</view>
<!-- 设置面板 -->
<view v-if="showSettingsPanel" class="settings-popup">
<view class="settings-mask" @click="hideSettings"></view>
<view class="settings-panel">
<view class="settings-header">
<text class="settings-title">{{ $lang('ai-chat.index.settingsTitle') }}</text>
<view class="close-btn" @click="hideSettings">
<text>✕</text>
</view>
</view>
<view class="settings-content">
<view class="setting-item" @click="openAvatarSelector">
<view class="setting-icon">
<text class="iconfont icon-user">📷</text>
</view>
<view class="setting-info">
<text class="setting-label">{{ $lang('ai-chat.index.changeAvatar') }}</text>
<text class="setting-desc">{{ $lang('ai-chat.index.changeAvatarDesc') }}</text>
</view>
<text class="iconfont icon-right">›</text>
</view>
<view class="setting-item" @click="openNicknameEditor">
<view class="setting-icon">
<text class="iconfont icon-edit">✏️</text>
</view>
<view class="setting-info">
<text class="setting-label">{{ $lang('ai-chat.index.changeNickname') }}</text>
<text class="setting-desc">{{ $lang('ai-chat.index.changeNicknameDesc') }}</text>
</view>
<text class="iconfont icon-right">›</text>
</view>
</view>
</view>
</view>
<!-- 底部导航栏:AI 智能页是全屏聊天体验,强制不渲染底部 tabBar,
让输入框贴到屏幕底,不论从哪个入口进入都不显示。 -->
<diy-bottom-nav v-if="false"></diy-bottom-nav>
</view>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import navigationHelper from '@/common/js/navigation';
import { EventSafety } from '@/common/js/event-safety';
import aiChatMessage from './ai-chat-message.vue';
import IMAGE_CONFIG from '@/common/js/image-config.js';
import aiService from '@/common/js/ai-service.js';
export default {
components: {
aiChatMessage
},
data() {
return {
initialMessages: [],
fallbackWelcome: '',
// AI Agent 通用入参(链接系统透传;老客服入口不传也能跑,默认 agent_aikefu)
agentId: 'agent_aikefu',
pageTitle: '',
// PR-CHAT-UI-CONFIG-PRELOAD (2026-09-24): 聊天 UI 配置预取
// uiCfgReady 控制聊天组件渲染时机 — 配置就绪(缓存命中或预取完成)才挂载,
// 避免"首帧用默认值渲染 → 异步配置到达后名称/头像/时间跳变"的闪烁
uiCfgReady: false,
preloadedUiCfg: null,
// --- 系统导航栏/状态栏相关 ---
navBarHeight: 44, // uni-page-head 系统导航栏,默认高度为44px
statusBarHeight: 0, // 状态栏高度,默认0
// --- 自定义导航栏 ---
showCustomNavbar: true, // 是否显示自定义导航栏
showBackButton: false, // 是否显示返回按钮
showMenuButton: true, // 是否显示设置菜单按钮
showSettingsPanel: false, // 是否显示设置面板
// --- 聊天内容区域 ---
scrollViewHeight: '0px',
// --- 键盘高度 ---
keyboardHeight: 0,
// 事件处理器引用(用于清理)
safeEventHandlers: new Map(),
// 进入 AI 智能页时是否临时隐藏了底部 tabBar。
// 标记进入时已调用 setBottomNavHidden(true),在 onUnload 中根据此标记还原,
// 避免返回其他 tabBar 页时底部导航栏保持隐藏状态。
isFromFloatBtn: false,
}
},
computed: {
...mapGetters([
'globalAIKefuConfig'
]),
/// ---- 关于AI客服的配置,支持远程配置 ----
userAvatar() {
return this.globalAIKefuConfig?.userAvatar || this.$util.img(IMAGE_CONFIG.userAvatar)
},
aiAvatar() {
return this.globalAIKefuConfig?.aiAvatar || this.$util.img(IMAGE_CONFIG.aiAvatar)
},
/// ---- others ----
},
async onLoad(options) {
this.fallbackWelcome = this.$lang('ai-chat.index.fallbackWelcome');
// ====== PR-CHAT-UI-CONFIG-PRELOAD (2026-09-24): 配置先行,再渲染聊天组件 ======
// 1) 本地持久缓存命中 → 同步就绪, 立即渲染 (零延迟, 用上次会话的真实配置)
// 2) 未命中(首次访问) → await 强制拉取 (race 2.5s 超时兜底), 拿到后再渲染
// 3) 拉取失败/超时 → 也放行渲染, 组件内部仍有默认值兜底, 不白屏
// force 拉取同时预热 /info 缓存, 欢迎语/模型列表等后续请求直接复用
try {
const cached = aiService.getChatUiConfigSync();
if (cached) {
this.preloadedUiCfg = cached;
this.uiCfgReady = true;
console.log('[AI Chat] chatUiConfig 命中本地缓存, 首帧即用真实配置');
}
} catch (e) { /* 缓存读取失败走预取 */ }
if (!this.uiCfgReady) {
try {
const cfg = await Promise.race([
aiService.getChatUiConfig(true),
new Promise(resolve => setTimeout(() => resolve(null), 6000))
]);
if (cfg) this.preloadedUiCfg = cfg;
else console.warn('[AI Chat] chatUiConfig 预取超时(6s), 先渲染后由组件异步刷新');
} catch (e) { /* 失败兜底默认值 */ }
this.uiCfgReady = true;
console.log('[AI Chat] chatUiConfig 预取完成, 渲染聊天组件:', !!this.preloadedUiCfg);
}
// 最新值刷新: 组件 created 的 _loadChatUiConfig() 会异步拉取,
// 与此处 force 拉取共享 60s TTL 内存缓存, 不会产生二次 /info 请求
// ====== AI Agent 通用入参(链接系统 -> 通用 agent_id/title) ======
// 老客服入口不传 options 也能跑(智能客服是首个 agent,默认 agent_aikefu)。
// 新增 AI 销售/HR/...智能体只需在 link.page_params.agent_id 传不同值,
// 当前 onShow/initChat 不区分 agent 行为,后续按业务扩展按 agentId 路由即可。
try {
this.agentId = (options && options.agent_id) || 'agent_aikefu';
const rawTitle = (options && options.title) || this.$lang('ai-chat.index.title');
this.pageTitle = decodeURIComponent(rawTitle);
// 同步 agentId 给 ai-service,后续 sendMessage / WS auth 自动带新 agent_id
try { aiService.setAgentId(this.agentId); } catch (e) { /* 兼容旧 ai-service 版本 */ }
} catch (e) {
this.agentId = 'agent_aikefu';
this.pageTitle = this.$lang('ai-chat.index.title');
}
// ✅ 修改:取消权限校验,允许所有客服类型访问此页面
// const hasAccess = this.checkAccessPermission();
// if (!hasAccess) {
// // 如果校验失败,不继续初始化
// return;
// }
this.$langConfig.title(this.pageTitle || this.$lang('ai-chat.index.title'));
// ====== 进入 AI 智能页始终隐藏底部 tabBar ======
// 原因:AI 智能页是全屏聊天体验,不论是
// 1) 底部导航栏"智能客服"项直接点击进入
// 2) diy-float-btn 浮动按钮(注入 __dyu=FloatBtn)进入
// 都不应被 tabBar 占用屏幕,否则输入框会被挤到 tabBar 上方。
// 进入时 setBottomNavHidden(true) 临时隐藏,onUnload 还原,避免污染其他页面。
this.isFromFloatBtn = true; // 标记进入时已隐藏,onUnload 据此还原
try {
this.$store.commit('setBottomNavHidden', true);
} catch (e) { /* 兼容没有该 mutation 的旧 store */ }
this.initChat();
},
async onReady() {
await this.initNavigation();
},
onShow() {
console.log(this.$lang('ai-chat.index.logOnShowTriggered'));
// 监听键盘高度变化(H5 可能不支持,try-catch 保护)
try {
this._keyboardChangeHandler = (res) => {
this.keyboardHeight = res.height;
if (this.$refs.chat && this.$refs.chat.updateKeyboardHeight) {
this.$refs.chat.updateKeyboardHeight(res.height);
}
};
uni.onKeyboardHeightChange(this._keyboardChangeHandler);
} catch (e) {
console.warn(this.$lang('ai-chat.index.logKeyboardChangeUnavailable') + ':', e.message);
}
// 每次页面显示时尝试加载历史记录(H5可能组件尚未创建,用 nextTick 确保 ref 就绪)
this.$nextTick(() => {
if (this.$refs.chat && this.$refs.chat.loadHistoryIfNeeded) {
this.$refs.chat.loadHistoryIfNeeded();
}
});
},
onHide() {
// 取消监听键盘高度变化
if (this._keyboardChangeHandler) {
uni.offKeyboardHeightChange(this._keyboardChangeHandler);
this._keyboardChangeHandler = null;
}
this.keyboardHeight = 0;
if (this.$refs.chat && this.$refs.chat.updateKeyboardHeight) {
this.$refs.chat.updateKeyboardHeight(0);
}
},
onUnload() {
console.log(this.$lang('ai-chat.index.logOnUnloadTriggered'));
this.cleanup();
aiService.disconnectWebSocket();
// 还原底部导航栏显示状态:如果 onLoad 时因为来自浮动按钮而临时隐藏,
// 必须在页面卸载时还原,否则返回到 tabBar 页时 tabBar 会保持隐藏状态。
if (this.isFromFloatBtn) {
try {
this.$store.commit('setBottomNavHidden', false);
} catch (e) { /* 兼容没有该 mutation 的旧 store */ }
this.isFromFloatBtn = false;
}
},
methods: {
// ========== 安全事件处理 ==========
setupSafeEventListeners() {
const safeHandlers = {
serviceRequest: EventSafety.wrapEventHandler(
this.handleServiceRequest.bind(this),
{ onError: this.handleEventError.bind(this) }
),
navigationRequest: EventSafety.wrapEventHandler(
this.handleNavigationRequest.bind(this),
{ onError: this.handleEventError.bind(this) }
),
componentInteraction: EventSafety.wrapEventHandler(
this.handleComponentInteraction.bind(this),
{ onError: this.handleEventError.bind(this) }
)
}
this. $on('service.requestComponentInfo', safeHandlers.serviceRequest);
this. $on('navigation.requestInfo', safeHandlers.navigationRequest);
this. $on('component.interaction', safeHandlers.componentInteraction);
this.safeEventHandlers.set('serviceRequest', safeHandlers.serviceRequest);
this.safeEventHandlers.set('navigationRequest', safeHandlers.navigationRequest);
this.safeEventHandlers.set('componentInteraction', safeHandlers.componentInteraction);
},
setupNavigationEvents() {
uni.onWindowResize((res) => {
this.calculateScrollViewHeight();
});
},
// ========== 事件处理方法 ==========
async handleServiceRequest(event) {
if (event.matches('.service-component') ||
event.detail?.componentType === 'service') {
await this.processServiceRequest(event);
}
},
async handleNavigationRequest(event) {
this.emitNavigationInfo(event);
},
handleComponentInteraction(event) {
this.processComponentInteraction(event);
},
handleEventError(error, event) {
console.error(this.$lang('ai-chat.index.logEventHandleError') + ':', {
error: error.message,
eventType: event?.type,
component: this. $options.name
});
this.showError(this.$lang('ai-chat.index.operationFailed'));
},
// ========== 初始化页面 ==========
initChat() {
// 检查是否有从商品分类传递过来的商品信息
const selectedProduct = uni.getStorageSync('selectedProduct')
if (selectedProduct) {
// 清除存储的商品信息,避免重复发送
uni.removeStorageSync('selectedProduct')
// 延迟发送商品信息,确保聊天组件已经初始化
setTimeout(() => {
this.sendProductToAI(selectedProduct)
}, 500)
}
},
async initNavigation() {
try {
this.navBarHeight = await navigationHelper.getNavigationHeight(this, {forceRefresh: false});
this.statusBarHeight = navigationHelper.getStatusBarHeight();
this.calculateScrollViewHeight();
this.setupNavigationEvents();
} catch (error) {
console.error(this.$lang('ai-chat.index.logInitNavbarFailed') + ':', error);
this.setFallbackNavigationValues();
}
},
calculateScrollViewHeight() {
const safeArea = navigationHelper.getSafeAreaInsets();
const bottomInset = safeArea.bottom || 0;
const inputHeight = 120;
this.scrollViewHeight = `calc(100vh - $ {this.navBarHeight + this.statusBarHeight + inputHeight + bottomInset}px)`;
},
setFallbackNavigationValues() {
// #ifdef MP-WEIXIN
this.navBarHeight = 44;
this.statusBarHeight = 20;
// #endif
// #ifdef H5
this.navBarHeight = 44;
this.statusBarHeight = 0;
// #endif
// #ifdef APP-PLUS
this.navBarHeight = 88;
this.statusBarHeight = 44;
// #endif
},
cleanup() {
if (this._keyboardChangeHandler) {
uni.offKeyboardHeightChange(this._keyboardChangeHandler);
this._keyboardChangeHandler = null;
}
this.safeEventHandlers.forEach((handler, eventType) => {
this. $off(eventType, handler);
});
this.safeEventHandlers.clear();
},
showError(message) {
uni.showToast({
title: message,
icon: 'none',
duration: 2000
});
},
// 返回上一页
goBack() {
uni.navigateBack();
},
// 显示菜单
showMenu() {
uni.showActionSheet({
itemList: [this.$lang('ai-chat.index.menuClearChat'), this.$lang('ai-chat.index.menuExport'), this.$lang('ai-chat.index.menuSettings'), this.$lang('ai-chat.index.menuHelp')],
success: (res) => {
switch (res.tapIndex) {
case 0:
this.clearChat();
break;
case 1:
this.exportChat();
break;
case 2:
this.showSettings();
break;
case 3:
this.showHelp();
break;
}
}
});
},
clearChat() {
uni.showModal({
title: this.$lang('ai-chat.index.prompt'),
content: this.$lang('ai-chat.index.confirmClearChat'),
success: (res) => {
if (res.confirm) {
this. $refs.chat.clearMessages();
this. $refs.chat.addMessage({
id: Date.now(),
role: 'ai',
type: 'text',
content: this.$lang('ai-chat.index.chatCleared'),
timestamp: Date.now()
});
}
}
});
},
exportChat() {
uni.showToast({
title: this.$lang('ai-chat.index.featureDeveloping'),
icon: 'none'
});
},
showSettings() {
this.showSettingsPanel = true;
},
hideSettings() {
this.showSettingsPanel = false;
},
openAvatarSelector() {
this.hideSettings();
this.$refs.chat.openAvatarSelector();
},
openNicknameEditor() {
this.hideSettings();
this.$refs.chat.openNicknameEditor();
},
showHelp() {
const helpMessage = {
id: Date.now(),
role: 'ai',
type: 'markdown',
content: this.$lang('ai-chat.index.helpContent'),
timestamp: Date.now()
};
this.$refs.chat.addMessage(helpMessage);
},
onMessageSent(message) {
},
onAIResponse(message) {
},
generateAIResponse(userMessage) {
const responses = [
this.$lang('ai-chat.index.aiResponseTemplate1'),
this.$lang('ai-chat.index.aiResponseTemplate2'),
this.$lang('ai-chat.index.aiResponseTemplate3'),
this.$lang('ai-chat.index.aiResponseTemplate4'),
this.$lang('ai-chat.index.aiResponseTemplate5'),
];
const randomResponse = responses[Math.floor(Math.random() * responses.length)];
const aiMessage = {
id: Date.now(),
role: 'ai',
type: 'text',
content: `${randomResponse}\n\n${this.$lang('ai-chat.index.aiProcessingMessage', { question: userMessage.content })}`,
timestamp: Date.now(),
actions: [
{ type: 'like', count: 0 },
{ type: 'dislike', count: 0 }
]
};
this.$refs.chat.addMessage(aiMessage);
this.$emit('ai-response', aiMessage);
},
onActionClick({ action, message }) {
switch (action.type) {
case 'like':
uni.showToast({ title: this.$lang('ai-chat.index.thanksFeedback'), icon: 'success' });
break;
case 'dislike':
uni.showToast({ title: this.$lang('ai-chat.index.improveService'), icon: 'none' });
break;
}
},
onHistoryLoaded(messages) {
},
onFilePreview(message) {
uni.showToast({ title: this.$lang('ai-chat.index.openingFileName', { name: message.fileName }), icon: 'none' });
},
onAudioPlay(message) {
},
onAudioPause(message) {
},
onVideoPlay(message) {
},
onVideoPause(message) {
},
sendProductToAI(product) {
if (!this.$refs.chat) {
console.error(this.$lang('ai-chat.index.logChatComponentNotInit'))
return
}
const productContent = this.$lang('ai-chat.index.productInquiryTemplate', {
name: product.goods_name,
price: product.price,
id: product.goods_id
})
// 使用sendMessageWithContent方法发送商品信息
this.$refs.chat.sendMessageWithContent(productContent)
},
onLinkOpen(message) {
uni.showModal({
title: this.$lang('ai-chat.index.openLink'),
content: this.$lang('ai-chat.index.confirmOpenLinkText', { url: message.url }),
success: (res) => {
if (res.confirm) {
// #ifdef H5
window.open(message.url, '_blank');
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(message.url);
// #endif
}
}
});
},
onProductView(message) {
uni.showToast({ title: this.$lang('ai-chat.index.openingFileTitle', { title: message.title }), icon: 'none' });
},
onInputChange(value) {
},
// ✅ 新增:权限校验(核心修复)
checkAccessPermission() {
const servicerConfig = this. $store.state.servicerConfig;
// 如果配置未加载,延迟重试(最多3次,防白屏)
if (!servicerConfig) {
console.warn(this.$lang('ai-chat.index.logServicerConfigNotLoaded'));
setTimeout(() => {
this.checkAccessPermission();
}, 200);
return false;
}
let currentType = 'none';
// #ifdef MP-WEIXIN
currentType = servicerConfig?.weapp?.type || 'none';
// #endif
// #ifdef H5
currentType = servicerConfig?.h5?.type || 'none';
// #endif
if (currentType !== 'aikefu') {
uni.showToast({ title: this.$lang('ai-chat.index.serviceTypeNotSupported'), icon: 'none' });
setTimeout(() => {
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
} else {
// 回到首页(假设首页是 tab 页)
uni.switchTab({ url: '/pages/index/index' });
}
}, 1500);
return false;
}
return true;
}
}
}
</script>
<style lang="scss">
/* 引入图标字体: 由 main.wxss 全局提供, 不再重复 import */
/* 页面样式 */
.wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
/* 自定义导航头部:固定在wrapper顶部,不随键盘上移 */
.custom-navbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background-color: white; /* 白色 */
border-bottom: 1rpx solid #e5e5e5;
.header-left {
flex: 0 0 auto;
display: flex;
align-items: center;
.back-btn {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #ffffff; /* 白色按钮 */
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 32rpx;
color: #666;
}
}
.placeholder {
width: 120rpx;
height: 60rpx;
}
}
.header-center {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 20rpx;
.header-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.header-subtitle {
font-size: 24rpx;
color: #999;
}
}
.header-right {
flex: 0 0 auto;
display: flex;
align-items: center;
.menu-btn {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #ffffff; /* 白色按钮 */
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 32rpx;
color: #666;
}
}
.placeholder {
width: 120rpx;
height: 60rpx;
}
}
}
/* 聊天内容区域:从导航栏下方到容器底部 */
.chat-content {
position: absolute;
/* #ifdef MP-WEIXIN */
top: 0;
/* #endif */
/* #ifndef MP-WEIXIN */
top: 44px;
/* #endif */
left: 0;
right: 0;
/* bottom 由模板动态绑定到 tabBarHeight:
- 浮动按钮入口 / 其他隐藏 tabBar 场景:0rpx,占满到底部
- tabBar 项入口:tabBarHeight,避免被底部 tabBar 遮挡聊天内容 */
}
/* 底部tabBar占位样式 */
.page-bottom {
width: 100%;
background-color: transparent;
}
/* 设置面板 */
.settings-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
.settings-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(6rpx);
}
.settings-panel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #ffffff;
border-radius: 24rpx;
padding: 32rpx;
width: calc(100% - 80rpx);
max-width: 520rpx;
box-shadow: 0 8rpx 36rpx rgba(0, 0, 0, 0.15);
box-sizing: border-box;
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
border-bottom: 2rpx solid #f0f4f8;
padding-bottom: 16rpx;
.settings-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.close-btn {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background-color: #f8fafc;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #999;
transition: all 0.2s ease;
&:hover {
background-color: #e9edf2;
}
}
}
.settings-content {
display: flex;
flex-direction: column;
gap: 16rpx;
.setting-item {
display: flex;
align-items: center;
padding: 24rpx;
border-radius: 16rpx;
background-color: #f8fafc;
border: 2rpx solid #f0f4f8;
transition: all 0.2s ease;
&:hover {
background-color: #e9edf2;
border-color: #d9e3ed;
}
.setting-icon {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background-color: #c4e0ff;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
flex-shrink: 0;
}
.setting-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
gap: 4rpx;
.setting-label {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.setting-desc {
font-size: 22rpx;
color: #999;
}
}
.iconfont.icon-right {
font-size: 36rpx;
color: #ccc;
flex-shrink: 0;
}
}
}
}
}
</style>