feat(product): 添加产品推荐功能并优化相关页面

- 新增产品推荐页面,提供个性化的产品推荐服务
- 优化首页布局,增加产品推荐入口
- 调整聊天组件,支持产品推荐功能
- 优化样式文件,统一主题颜色和样式
- 更新路由配置,添加产品推荐路由
This commit is contained in:
陈昱达
2025-06-30 13:27:29 +08:00
parent ac240892dd
commit 8cb956ba6d
7 changed files with 254 additions and 64 deletions

View File

@@ -4,7 +4,7 @@
@import './utils.scss';
@import './transition.scss';
@import './public.scss';
@import "./loading.scss";
@import './loading.scss';
* {
margin: 0;
@@ -33,7 +33,7 @@ a:hover {
outline: none;
text-decoration: none;
}
::-webkit-scrollbar{
::-webkit-scrollbar {
display: none;
}
// 宽度设置

View File

@@ -1,61 +1,62 @@
body{
background-color: #EEEEEE!important;
body {
background-color: #eeeeee !important;
}
.van-cell-group--inset{
margin:0 10px!important;
.van-cell-group--inset {
margin: 0 10px !important;
}
.van-cell-group__title--inset{
padding:15px 15px 5px 15px!important;
.van-cell-group__title--inset {
padding: 15px 15px 5px 15px !important;
}
.van-toast--text {
.van-toast--text {
max-width: 70vw !important;
min-width: 60vw !important;
}
.button-group-container{
.button-group-container {
display: flex;
align-items: center;
justify-content: space-between;
}
#app .van-col{
#app .van-col {
margin: 5px 0;
}
#app .button-group-container{
margin: 5px!important;
.van-button{
#app .button-group-container {
margin: 5px !important;
.van-button {
margin: unset;
}
}
#app .van-button + .van-button{
#app .van-button + .van-button {
margin-left: 5px;
}
#app .van-cell__title{
#app .van-cell__title {
color: #646566;
}
.flexPrice{
display: flex; justify-content: space-between; justify-items: center; align-items: center; width: 100%
.flexPrice {
display: flex;
justify-content: space-between;
justify-items: center;
align-items: center;
width: 100%;
}
.van-tag+.van-tag{margin-left: 5px;}
.speakLoadingToast{
z-index: 2100!important;
.van-tag + .van-tag {
margin-left: 5px;
}
.speakLoadingToast {
z-index: 2100 !important;
width: 200px;
}
.gs-color-g{
.gs-color-g {
color: #009960;
}
.gs-bg-color-g{
.gs-bg-color-g {
background: #009960;
}
.chat-icon{
.chat-icon {
width: 16px;
height: 16px;
margin-left: 0!important;
margin-right: 2px!important;
margin-left: 0 !important;
margin-right: 2px !important;
}

View File

@@ -23,4 +23,12 @@ export default [
title: '产品对比',
},
},
{
path: '/recommend',
name: 'recommend',
component: () => import('@/views/recommend/index.vue'),
meta: {
title: '产品推荐',
},
},
]

View File

@@ -90,6 +90,10 @@ export default {
type: Boolean,
default: false,
},
chatData: {
type: Object,
default: () => ({}),
},
},
data() {
return {
@@ -220,6 +224,12 @@ export default {
conversationId: this.conversationId,
productName: this.productName,
}
// 如果有自定义参数
if (this.chatData) {
for (let k in this.chatData) {
params[k] = this.chatData[k]
}
}
if (this.$route.query.compareId) {
params.compareResult = JSON.parse(sessionStorage.getItem('results'))
}

View File

@@ -1,8 +1,8 @@
<template>
<div class="home-container">
<van-swipe class="my-swipe">
<van-swipe-item class="item">
<div v-for="item in navigationItems" @click="jumpPage(item)">
<van-swipe-item class="item" v-for="swiper in swiper">
<div v-for="item in swiper" @click="jumpPage(item)">
<div class="icon-contact mt20">
<svg-icon :icon-class="item.icon" class-name="icon "></svg-icon>
</div>
@@ -39,10 +39,14 @@ export default {
},
data() {
return {
navigationItems: [
{ title: 'AI智能助手', icon: 'product', path: '/chatPage' },
{ title: 'AI客服助手', icon: 'sale', path: '/customer' },
{ title: '产品对比', icon: 'earth', path: '/comparison' },
swiper: [
[
{ title: 'AI智能助手', icon: 'product', path: '/chatPage' },
{ title: '产品对比', icon: 'earth', path: '/comparison' },
{ title: '产品推荐', icon: 'product', path: '/recommend' },
],
[{ title: 'AI客服助手', icon: 'sale', path: '/customer' }],
],
list: [
{
@@ -63,6 +67,17 @@ export default {
}
</script>
<style lang="scss">
// 主题颜色定义
$primary-color: #2e5ca9;
$primary-text-color: #f6aa21;
$primary-trans-color: #87a2d0;
.van-nav-bar__text,
.van-nav-bar .van-icon {
color: $primary-color !important;
}
</style>
<style scoped lang="scss">
// 主题颜色定义
$primary-color: #2e5ca9;

View File

@@ -0,0 +1,157 @@
<template>
<div class="chat-page">
<van-nav-bar title="产品推荐" left-text="返回" left-arrow @click-left="$router.history.go(-1)" />
<main class="chat-main">
<div class="chat-content">
<div class="message-area" ref="messageArea" @scroll="handleScroll">
<!-- -->
<messageComponent
:messagesList="messages"
:is-deep="isDeep"
:is-search="isSearching"
:think-ok="isThink"
@setProductName="setProductName"
></messageComponent>
</div>
</div>
<!-- 滚动到顶部按钮 -->
<div class="button-container" style="background: #fff">
<van-icon name="upgrade" size="2em" @click="scrollToTop" />
</div>
</main>
<chatMessage
:messages.sync="messages"
:messageStatus.sync="messageStatus"
:is-deep.sync="isDeep"
:conversation-id.sync="conversationId"
:is-searching.sync="isSearching"
:product-name.sync="productName"
:autoScrollEnabled.sync="autoScrollEnabled"
:chatData="{ action: 'product_recommand' }"
@getIsThink="getIsThink"
></chatMessage>
</div>
</template>
<script>
import { Icon, NavBar } from 'vant'
import SvgIcon from '@/components/svg-icon/index.vue'
import HotProducts from '@/views/AI/components/HotProducts.vue'
import chatMessage from '@/views/AI/components/chat.vue'
import messageComponent from '@/views/AI/components/message.vue'
export default {
components: {
SvgIcon,
messageComponent,
[Icon.name]: Icon,
[NavBar.name]: NavBar,
HotProducts,
chatMessage,
},
data() {
return {
hotList: [],
productName: '',
conversationId: '',
messageStatus: 'stop',
isThink: null,
messages: [
{
type: 'bot',
text: '欢迎使用产品推荐智能助手,我将推荐符合您的产品',
},
],
isSearching: false,
isDeep: false,
autoScrollEnabled: true,
scrollPosition: 0,
}
},
methods: {
getIsThink(e) {
this.isThink = e
},
getHotProducts(e) {
console.log(e)
this.hotList = e
},
scrollToTop() {
const messageArea = this.$refs.messageArea
if (messageArea) {
messageArea.scrollTop = 0
}
},
scrollToBottom() {
if (!this.autoScrollEnabled) return
this.$nextTick(() => {
const messageArea = this.$refs.messageArea
if (messageArea) {
messageArea.scrollTop = messageArea.scrollHeight
}
})
},
setProductName(e) {
this.productName = e
},
handleScroll() {
const messageArea = this.$refs.messageArea
if (!messageArea) return
const threshold = 10
const isAtBottom = messageArea.scrollHeight - messageArea.clientHeight <= messageArea.scrollTop + threshold
this.autoScrollEnabled = isAtBottom
this.scrollPosition = messageArea.scrollTop
},
},
watch: {
messages: {
handler() {
this.$nextTick(() => this.scrollToBottom())
},
deep: true,
},
},
}
</script>
<style lang="scss" scoped>
$primary-color: #2e5ca9;
$primary-text-color: #f6aa21;
$primary-trans-color: rgba(135, 162, 208, 0.5);
.chat-page {
display: flex;
flex-direction: column;
height: 100vh;
//-webkit-user-select: none;
//-moz-user-select: none;
//-ms-user-select: none;
//user-select: none;
.chat-main {
flex: 1;
overflow-y: auto;
padding: 10px;
background: #f7f8fa;
position: relative;
.button-container {
position: fixed;
bottom: 150px;
right: 10px;
border-radius: 50%;
}
.chat-content {
height: 100%;
.message-area {
height: 100%;
overflow-y: auto;
transition: all 0.2s ease-in-out;
}
}
}
}
</style>

View File

@@ -1,9 +1,9 @@
const autoprefixer = require('autoprefixer');
const pxtoviewport = require('postcss-px-to-viewport');
const path = require('path');
const autoprefixer = require('autoprefixer')
const pxtoviewport = require('postcss-px-to-viewport')
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir);
return path.join(__dirname, dir)
}
module.exports = {
@@ -12,37 +12,36 @@ module.exports = {
outputDir: 'dist',
productionSourceMap: false,
devServer: {
https: false,
https: true,
host: '0.0.0.0',
disableHostCheck: true,
},
css: {
sourceMap: true,
loaderOptions: {
loaderOptions: {
postcss: {
postcssOptions: {
plugins: [
autoprefixer(), // 自动加浏览器前缀
pxtoviewport({ // px 转 viewport
pxtoviewport({
// px 转 viewport
viewportWidth: 375,
selectorBlackList: ['van-circle__layer'] // 排除vant circle组件样式转换
})
]
}
}
}
selectorBlackList: ['van-circle__layer'], // 排除vant circle组件样式转换
}),
],
},
},
},
},
chainWebpack: (config) => {
// 设置路径别名
config.resolve.alias
.set('@', resolve('src'))
.set('@utils', resolve('src/assets/js/utils'));
config.resolve.alias.set('@', resolve('src')).set('@utils', resolve('src/assets/js/utils'))
// 移除 prefetch 插件(减少初始加载体积)
config.plugins.delete('prefetch');
config.plugins.delete('prefetch')
// SVG 图标配置
config.module.rule('svg').exclude.add(resolve('src/icons')).end();
config.module.rule('svg').exclude.add(resolve('src/icons')).end()
config.module
.rule('icons')
.test(/\.svg$/)
@@ -52,7 +51,7 @@ module.exports = {
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]',
});
})
// 处理 .mjs 文件(用于 mermaid 等模块)
config.module
@@ -61,7 +60,7 @@ module.exports = {
.include.add(/node_modules/)
.end()
.use('babel-loader')
.loader('babel-loader');
.loader('babel-loader')
// 添加对现代 JS 的支持(如 ?? 和 ?.
config.module
@@ -71,19 +70,19 @@ module.exports = {
.loader('babel-loader')
.options({
presets: ['@babel/preset-env'],
});
})
},
configureWebpack: (config) => {
// 强制使用 CommonJS 模块加载 mermaid
config.resolve = config.resolve || {};
config.resolve.alias = config.resolve.alias || {};
config.resolve = config.resolve || {}
config.resolve.alias = config.resolve.alias || {}
// config.resolve.alias.mermaid$ = path.resolve(
// __dirname,
// './node_modules/mermaid/dist/mermaid.common.js'
// );
// Webpack devtool 设置
config.devtool = 'source-map';
config.devtool = 'source-map'
// 打包性能提示设置
config.performance = {
@@ -91,8 +90,8 @@ module.exports = {
maxEntrypointSize: 7168000, // 7MB
maxAssetSize: 7168000, // 7MB
assetFilter: function (assetFilename) {
return assetFilename.endsWith('.js');
return assetFilename.endsWith('.js')
},
};
}
},
};
}