style(sass): 调整聊天图标样式

- 调整聊天图标的左右边距- 优化 voice.svg 图标的视觉效果
- 修复公告组件中的样式问题
- 优化热门产品、消息、导航列表等组件的样式
This commit is contained in:
陈昱达
2025-06-10 18:18:11 +08:00
parent db11e06a12
commit 7b3947008a
12 changed files with 477 additions and 312 deletions

View File

@@ -3,5 +3,5 @@ NODE_ENV = 'dev' // 如果是生产环境请记得切换为production
# flag
VUE_APP_FLAG='dev'
VUE_APP_BASE='http://ebiz-fooge.320.io:7015'
VUE_APP_BASE='https://weixin.devops.ebiz-digits.com:7718'

View File

@@ -55,4 +55,7 @@ body{
.chat-icon{
width: 16px;
height: 16px;
margin-left: 0!important;
margin-right: 2px!important;
}

View File

@@ -1 +1 @@
<svg t="1730875233854" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7506" width="30" height="30"><path d="M512 938.666667C277.333333 938.666667 85.333333 746.666667 85.333333 512S277.333333 85.333333 512 85.333333s426.666667 192 426.666667 426.666667-192 426.666667-426.666667 426.666667z m0-768c-187.733333 0-341.333333 153.6-341.333333 341.333333s153.6 341.333333 341.333333 341.333333 341.333333-153.6 341.333333-341.333333-153.6-341.333333-341.333333-341.333333z" fill="#707070" p-id="7507"></path><path d="M512 256c46.933333 0 85.333333 38.4 85.333333 85.333333v170.666667c0 46.933333-38.4 85.333333-85.333333 85.333333s-85.333333-38.4-85.333333-85.333333V341.333333c0-46.933333 38.4-85.333333 85.333333-85.333333z" fill="#707070" p-id="7508"></path><path d="M512 704c-106.666667 0-192-85.333333-192-192 0-12.8 8.533333-21.333333 21.333333-21.333333s21.333333 8.533333 21.333334 21.333333c0 81.066667 68.266667 149.333333 149.333333 149.333333s149.333333-68.266667 149.333333-149.333333c0-12.8 8.533333-21.333333 21.333334-21.333333s21.333333 8.533333 21.333333 21.333333c0 106.666667-85.333333 192-192 192z" fill="#707070" p-id="7509"></path><path d="M490.666667 682.666667h42.666666v170.666666h-42.666666z" fill="#707070" p-id="7510"></path></svg>
<svg t="1730875233854" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7506" width="40" height="40"><path d="M512 938.666667C277.333333 938.666667 85.333333 746.666667 85.333333 512S277.333333 85.333333 512 85.333333s426.666667 192 426.666667 426.666667-192 426.666667-426.666667 426.666667z m0-768c-187.733333 0-341.333333 153.6-341.333333 341.333333s153.6 341.333333 341.333333 341.333333 341.333333-153.6 341.333333-341.333333-153.6-341.333333-341.333333-341.333333z" fill="#707070" p-id="7507"></path><path d="M512 256c46.933333 0 85.333333 38.4 85.333333 85.333333v170.666667c0 46.933333-38.4 85.333333-85.333333 85.333333s-85.333333-38.4-85.333333-85.333333V341.333333c0-46.933333 38.4-85.333333 85.333333-85.333333z" fill="#707070" p-id="7508"></path><path d="M512 704c-106.666667 0-192-85.333333-192-192 0-12.8 8.533333-21.333333 21.333333-21.333333s21.333333 8.533333 21.333334 21.333333c0 81.066667 68.266667 149.333333 149.333333 149.333333s149.333333-68.266667 149.333333-149.333333c0-12.8 8.533333-21.333333 21.333334-21.333333s21.333333 8.533333 21.333333 21.333333c0 106.666667-85.333333 192-192 192z" fill="#707070" p-id="7509"></path><path d="M490.666667 682.666667h42.666666v170.666666h-42.666666z" fill="#707070" p-id="7510"></path></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,19 +1,15 @@
<template>
<div class="announcement" >
<van-notice-bar
background='#2e5ca9'
left-icon="volume-o"
color='#fff'
:text="announcements[0].content"/>
<div class="announcement">
<van-notice-bar background="#2e5ca9" left-icon="volume-o" color="#fff" :text="announcements[0].content" />
</div>
</template>
<script>
import { NoticeBar } from 'vant';
import { NoticeBar } from 'vant'
export default {
name: 'Announcement',
components: {
[NoticeBar.name]:NoticeBar
[NoticeBar.name]: NoticeBar,
},
data() {
return {
@@ -21,12 +17,12 @@ export default {
{
title: '新功能上线',
content: '我们很高兴地宣布,全新的“智能问答”功能现已上线!您可以体验更高效、更智能的问答服务。',
date: '2023-09-25'
date: '2023-09-25',
},
]
};
}
};
],
}
},
}
</script>
<style scoped lang="scss">

View File

@@ -71,6 +71,7 @@ export default {
async getHotProducts() {
const res = await haslProducts()
this.hotProducts = res.content
this.$emit('getHotList',this.hotProducts)
},
},
components: {

View File

@@ -1,28 +1,27 @@
<template>
<van-swipe class="my-swipe" >
<van-swipe-item class='item'>
<div v-for='item in navigationItems'>
<div class='icon-contact mt20'>
<svg-icon :icon-class='item.icon' class-name='icon '></svg-icon>
</div>
<div class='nav-title mb10'>{{item.title}}</div>
<van-swipe class="my-swipe">
<van-swipe-item class="item">
<div v-for="item in navigationItems">
<div class="icon-contact mt20">
<svg-icon :icon-class="item.icon" class-name="icon "></svg-icon>
</div>
</van-swipe-item>
<!-- <van-swipe-item>2</van-swipe-item>-->
</van-swipe>
<div class="nav-title mb10">{{ item.title }}</div>
</div>
</van-swipe-item>
<!-- <van-swipe-item>2</van-swipe-item>-->
</van-swipe>
</template>
<script>
import {Swipe,SwipeItem} from 'vant'
import { Swipe, SwipeItem } from 'vant'
import SvgIcon from '@/components/svg-icon/index.vue'
export default {
name: 'NavigationList',
components: {
SvgIcon,
[Swipe.name]: Swipe,
[SwipeItem.name]:SwipeItem,
[Swipe.name]: Swipe,
[SwipeItem.name]: SwipeItem,
},
data() {
return {
@@ -32,56 +31,55 @@ export default {
{ title: '停售产品', icon: 'stopSale' },
{ title: '服务中心', icon: 'service' },
],
};
}
},
};
}
</script>
<style scoped lang="scss">
// 主题颜色定义
$primary-color: #2E5CA9;
$primary-text-color: #F6AA21;
$primary-trans-color: #87A2D0;
$primary-color: #2e5ca9;
$primary-text-color: #f6aa21;
$primary-trans-color: #87a2d0;
.my-swipe{
background: $primary-color;
border-radius: 5px;
.item{
display: flex;
justify-content: space-around;
align-items: center;
justify-items: center;
text-align: center;
div{
flex:1;
padding:10px 5px;
text-align: center;
.icon-contact{
padding: 5px;
border-radius: 50%;
overflow: hidden;
margin: 0 auto;
background: $primary-trans-color;
text-align: center;
width: 35px;
height:35px;
display: flex;
//justify-items: center;
align-items: center;
justify-items: center;
.icon{
.my-swipe {
background: $primary-color;
border-radius: 5px;
.item {
display: flex;
justify-content: space-around;
align-items: center;
justify-items: center;
text-align: center;
div {
flex: 1;
padding: 10px 5px;
text-align: center;
.icon-contact {
padding: 5px;
border-radius: 50%;
overflow: hidden;
margin: 0 auto;
background: $primary-trans-color;
text-align: center;
width: 35px;
height: 35px;
display: flex;
//justify-items: center;
align-items: center;
justify-items: center;
.icon {
flex: 1;
width: 25px;
height: 25px;
}
}
.nav-title{
font-size: 12px;
font-weight: 600;
color:#fff;
}
}
}
.nav-title {
font-size: 12px;
font-weight: 600;
color: #fff;
}
}
}
}
</style>

View File

@@ -1,105 +1,97 @@
<template>
<div >
<div class='tab-box' v-for='item in list'>
<!-- <div class='box-title'>-->
<!-- {{item.title}}-->
<!-- </div>-->
<div class='box-container'>
<van-cell v-for='list in item.list' :class='list.value?"link":"" ' @click='view(list)' is-link>
{{list.title}}
</van-cell>
</div>
</div>
<van-dialog v-model="show" width='90%' :title='title' confirm-button-color='#2E5CA9'>
<div style='height: 70vh;overflow: scroll' >
<pdf :url='url' v-if='show'></pdf>
<div>
<div class="tab-box" v-for="item in list">
<!-- <div class='box-title'>-->
<!-- {{item.title}}-->
<!-- </div>-->
<div class="box-container">
<van-cell v-for="list in item.list" :class="list.value ? 'link' : ''" @click="view(list)" is-link>
{{ list.title }}
</van-cell>
</div>
</div>
<van-dialog v-model="show" width="90%" :title="title" confirm-button-color="#2E5CA9">
<div style="height: 70vh; overflow: scroll">
<pdf :url="url" v-if="show"></pdf>
</div>
</van-dialog>
</div>
</template>
<script>
import {Dialog,CellGroup,Cell} from 'vant'
import { Dialog, CellGroup, Cell } from 'vant'
import pdf from '@/views/AI/components/pdf.vue'
export default {
name: 'box',
data() {
return {
url:'',
show:false,
title:''
url: '',
show: false,
title: '',
}
},
props: {
list: {
type: Array,
default: () => []
}
default: () => [],
},
},
watch: {},
components: {
[Dialog.name]:Dialog,
[CellGroup.name]:CellGroup,
[Cell.name]:Cell,
pdf
[Dialog.name]: Dialog,
[CellGroup.name]: CellGroup,
[Cell.name]: Cell,
pdf,
},
filters: {},
methods: {
view(item){
view(item) {
console.log(item)
if(!item.value) return
if (!item.value) return
this.url = item.value
this.title = item.title
this.show = true
},
},
created() {
},
mounted() {
},
computed: {}
created() {},
mounted() {},
computed: {},
}
</script>
<style scoped lang='scss'>
<style scoped lang="scss">
// 主题颜色定义
$primary-color: #2E5CA9; // 修复了 SCSS 变量的定义方式
$primary-text-color: #F6AA21; // 修复了 SCSS 变量的定义方式
$primary-color: #2e5ca9; // 修复了 SCSS 变量的定义方式
$primary-text-color: #f6aa21; // 修复了 SCSS 变量的定义方式
$primary-trans-color: rgba(135, 162, 208, 0.5); // 使用rgba定义颜色透明度为0.8
.tab-box{
.tab-box {
border-radius: 5px;
overflow: hidden;
margin-bottom: 10px;
background: #fff;
//border: 1px solid $primary-trans-color;
.box-title{
.box-title {
padding: 10px 15px;
background:$primary-trans-color ;
color : #2E5CA9; // 修复了 SCSS 变量的定义方式
background: $primary-trans-color;
color: #2e5ca9; // 修复了 SCSS 变量的定义方式
font-size: 13px;
font-weight: 600;
}
.box-container{
.box-container {
//padding: 10px;
//display: flex;
align-items: center;
//justify-content: space-between;
flex-wrap: wrap;
//gap: 8px;
.link{
div,i{
color:$primary-color;
.link {
div,
i {
color: $primary-color;
}
//padding: 5px 12px;
border-radius: 5px;
//background: $primary-trans-color;
}
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<section>
<div v-for="(message, index) in messagesList" :key="index" class="message-item ">
<div v-for="(message, index) in messagesList" :key="index" class="message-item">
<!--用户消息-->
<div v-if="message.type === 'user'" class="user-message mb10">
<p>{{ message.text }}</p>
@@ -18,15 +18,15 @@
<p v-html="md.render(message.think)" v-if="message.think && message.showThink" class="thinkText" />
</span>
<div>
<p v-html="md.render(message.text)" v-if="message.text "></p>
<span class="speakLoadingToast pv10" v-else-if='!message.text && !thinkOk && !message.isThink'>
<p v-html="md.render(message.text)" v-if="message.text"></p>
<span class="speakLoadingToast pv10" v-else-if="!message.text && !thinkOk && !message.isThink">
<van-loading type="spinner" color="#2e5ca9" size="20px" v-if="!message.text" />
</span>
</div>
</div>
<!--百宝箱-->
<div v-else class='mb10'>
<treasure-box :item="message" @setProductName='setProductName'></treasure-box>
<div v-else class="mb10">
<treasure-box :item="message" @setProductName="setProductName"></treasure-box>
</div>
<!-- 新增点赞和踩按钮 -->
<div class="reaction-buttons mb10" v-if="message.type !== 'user'">
@@ -51,8 +51,7 @@ const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
})
.use(markdownItKatex)
}).use(markdownItKatex)
export default {
name: 'message',
components: { TreasureBox, [Icon.name]: Icon },
@@ -76,8 +75,8 @@ export default {
}
},
methods: {
setProductName(e){
this.$emit('setProductName',e)
setProductName(e) {
this.$emit('setProductName', e)
},
showThink(message) {

View File

@@ -1,9 +1,9 @@
<template>
<div style='height: 100%;'>
<div v-if='!numPages' style='height: 100%;display: flex;align-items: center;justify-content: center;'>
<van-loading color="#2E5CA9" vertical>加载中...</van-loading>
<div style="height: 100%">
<div v-if="!numPages" style="height: 100%; display: flex; align-items: center; justify-content: center">
<van-loading color="#2E5CA9" vertical>加载中...</van-loading>
</div>
<pdf-container :src="url" ref="pdfContainer" v-for="index in numPages" :key="index" :page="index" ></pdf-container>
<pdf-container :src="url" ref="pdfContainer" v-for="index in numPages" :key="index" :page="index"></pdf-container>
</div>
</template>
<script>
@@ -14,17 +14,17 @@ export default {
return {
// url: 'https://www.gjtool.cn/pdfh5/git.pdf',
// url: 'http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf',
numPages: null
numPages: null,
}
},
props: {
url: {
type: String,
default: ''
}
default: '',
},
},
components: {
pdfContainer
pdfContainer,
},
created() {
this.getNumPages()
@@ -32,17 +32,14 @@ export default {
methods: {
getNumPages() {
let loadingTask = pdfContainer.createLoadingTask(this.url)
loadingTask.promise.then(pdf => {
loadingTask.promise.then((pdf) => {
this.numPages = pdf.numPages
this.url = loadingTask
})
// .catch(err => {
// console.log('pdf加载失败', err)
// })
}
}
},
},
}
</script>

View File

@@ -0,0 +1,194 @@
<template>
<div
class="sticky"
v-if="productName"
:style="{
color: isDisabled ? '#999' : '#2e5ca9',
}"
>
<!--产品选择-->
<van-dropdown-menu active-color="#2e5ca9" :disabled="isDisabled">
<van-dropdown-item v-model="value1" :options="options" @change="changeName" :disabled="isDisabled">
<template #title>
<div
class="fs14 title"
:style="{
color: isDisabled ? '#999' : '#2e5ca9',
}"
>
{{ value1 }}
</div>
</template>
</van-dropdown-item>
</van-dropdown-menu>
<!--工具箱或其他模块-->
<van-dropdown-menu active-color="#2e5ca9" :disabled="isDisabled" style="width: 50px">
<template> </template>
<van-dropdown-item v-model="value2" :options="treasureList" @change="changeTreasureBox" :disabled="isDisabled" title-class="more-drown">
<template #title>
<van-icon name="ellipsis" class="more-treasure"></van-icon>
</template>
</van-dropdown-item>
</van-dropdown-menu>
</div>
</template>
<script>
import { DropdownMenu, DropdownItem, Cell, Icon } from 'vant'
export default {
name: 'sticky',
components: {
[DropdownMenu.name]: DropdownMenu,
[DropdownItem.name]: DropdownItem,
[Cell.name]: Cell,
[Icon.name]: Icon,
},
props: {
isDisabled: {
type: Boolean,
default: false,
},
autoScrollEnabled: {
type: Boolean,
default: false,
},
messagesList: {
type: Array,
default: () => [],
},
hotList: {
type: Array,
default: () => [],
},
productName: {
type: String,
default: '',
},
},
watch: {
productName: {
handler(val) {
this.value1 = val
console.log(val)
},
},
//
hotList: {
handler(val) {
if (val) {
this.options = val.map((item) => {
return {
text: item.productName,
value: item.productName,
}
})
}
},
},
},
data() {
return {
prdName: '',
value1: '',
options: [],
value2: '',
treasureList: [
{
text: '工具箱',
value: '1',
// icon: 'more-o',
},
],
//
}
},
methods: {
changeTreasureBox(item) {
switch (item) {
case '1':
// this.$emit('view', item)
this.messagesList.push({
type: 'box',
text: this.productName,
})
this.$emit('update:messageList', this.messagesList)
break
}
this.value2 = ''
},
changeName(item) {
this.$emit('cellClick')
this.prdName = item
// this.messagesList.push({
// type: 'box',
// text: item,
// })
// this.$emit('update:messageList', this.messagesList)
// this.$emit('update:autoScrollEnabled', true)
// 只暴露名称
this.$emit('setProductName', item)
},
},
created() {
//
},
mounted() {
//
},
}
</script>
<style lang="scss" scoped>
.title {
//width: 80%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.sticky {
display: flex;
align-items: center;
position: sticky;
justify-content: space-between;
top: 0;
left: 0;
right: 0;
z-index: 10;
//height: 10px;
padding: 5px 10px;
background: #fff;
//gap: 10px;
}
::v-deep .more-treasure {
//flex:1;
//width: 20px;
font-size: 16px;
margin-left: 10px;
}
::v-deep .van-dropdown-menu__bar {
box-shadow: unset;
}
::v-deep .van-dropdown-menu {
//flex: 1;
width: 85%;
}
::v-deep .van-dropdown-menu__bar {
height: auto;
}
::v-deep .van-dropdown-menu__item {
justify-content: start;
}
::v-deep .van-dropdown-menu__title {
padding: 8px 12px 8px 8px;
width: 80%;
}
::v-deep .van-cell {
font-size: 14px;
}
::v-deep .more-drown {
width: 100%;
&::after {
display: none;
}
}
</style>

View File

@@ -2,62 +2,27 @@
<div class="treasure-box">
<h3>{{ item.text.indexOf('工具箱') !== -1 ? item.text : item.text + '工具箱' }}</h3>
<van-tabs v-model="active" color="#2E5CA9" title-active-color="#2E5CA9" line-width="20">
<!-- <van-tab title="爆款图文">-->
<!-- <TabBox></TabBox>-->
<!-- </van-tab>-->
<van-tab title="产品知识">
<!-- <van-collapse v-model="activeNames">-->
<!-- <van-collapse-item :title="item.title" :name="index" v-for="(item, index) in knowledge" >-->
<!-- <span v-for="(it, index) in item.list" :key="index" :title="item.title" class='cells'>-->
<!-- {{ it.title}}-->
<!-- </span>-->
<!-- </van-collapse-item>-->
<!-- </van-collapse>-->
<!-- -->
<table style='border :1px solid #b6ccd9;text-align: left;border-radius:8px;overflow: hidden ' cellspacing='0' cellpadding='0' class='my-table' >
<tr v-for='item in knowledge'>
<th class='fs12 fw600'>{{item.title}}</th>
<th class='fs12 flex '>
<span v-for="(it, index) in item.list" :key="index" :title="item.title" class='cells'>
{{ it.title}}
<table style="border: 1px solid #b6ccd9; text-align: left; border-radius: 8px; overflow: hidden" cellspacing="0" cellpadding="0" class="my-table">
<tr v-for="item in knowledge">
<th class="fs12 fw600">{{ item.title }}</th>
<th class="fs12 flex">
<span v-for="(it, index) in item.list" :key="index" :title="item.title" class="cells">
{{ it.title }}
</span>
</th>
</tr>
</table>
<!-- <van-cell-group>-->
<!-- <van-cell v-for="(item, index) in knowledge" :key="index" :title="item.title" class='cells'>-->
<!-- <template #default>-->
<!--&lt;!&ndash; 截取钱10个&ndash;&gt;-->
<!-- <span v-for='it in item.list'>{{-->
<!-- it.title.substring(0,8)}}...</span>-->
<!-- </template>-->
<!-- <template #right-icon>-->
<!-- <van-icon name="arrow" ></van-icon>-->
<!-- </template>-->
<!-- </van-cell>-->
<!-- </van-cell-group>-->
<!-- <TabBox :list='knowledge'></TabBox>-->
</van-tab>
<van-tab title="常用工具">
<TabBox :list='tools'></TabBox>
<TabBox :list="tools"></TabBox>
</van-tab>
</van-tabs>
<!-- 在这里添加百宝箱的具体内容 -->
</div>
</template>
<script>
import { Tabs, Tab,CellGroup,Cell,Icon,Collapse, CollapseItem } from 'vant'
import { Tabs, Tab, CellGroup, Cell, Icon, Collapse, CollapseItem } from 'vant'
import TabBox from '@/views/AI/components/TabBox.vue'
import { productDetail } from '@/api/generatedApi'
export default {
@@ -82,60 +47,57 @@ export default {
return {
active: 0,
// 可以在这里添加百宝箱相关的数据
// 工具列表
// 工具列表
tools: [],
knowledge: [],
activeNames:[1],
activeNames: [1],
}
},
watch: {
'item.text': {
handler(newValue, oldValue) {
if(!this.item.detail){
this.getTreasureBox()
if (!this.item.detail) {
this.getTreasureBox()
} else {
this.setList(this.item.detail)
this.$emit('setProductName',this.item.detail.productName)
this.$emit('setProductName', this.item.detail.productName)
}
},
immediate: true,
},
},
methods: {
setList(){
setList() {
this.tools = [
{title: '工具',
list:[
{value:this.item.detail.instructionUrl,title:'产品说明书' },
{value:this.item.detail.clauseUrl,title:'条款' },
]},
{
title: '工具',
list: [
{ value: this.item.detail.instructionUrl, title: '产品说明书' },
{ value: this.item.detail.clauseUrl, title: '条款' },
],
},
]
this.knowledge = []
for(let i in this.item.detail.knowledge){
for (let i in this.item.detail.knowledge) {
this.knowledge.push({
title:i,
list:this.item.detail.knowledge[i].split(',').map(item=>{
return {
title:item,
title: i,
list: this.item.detail.knowledge[i].split(',').map((item) => {
return {
title: item,
// value:item
}
})
}),
})
}
},
// 可以在这里添加百宝箱相关的功能方法
async getTreasureBox() {
productDetail({ productName: this.item.text }).then((res) => {
if(res){
this.$set(this.item,'detail',res.content)
this.$emit('setProductName',res.content.productName)
if (res) {
this.$set(this.item, 'detail', res.content)
this.$emit('setProductName', res.content.productName)
this.setList()
}
})
@@ -169,43 +131,38 @@ $primary-trans-color: #87a2d0;
}
}
.cells{
.cells {
display: flex;
align-items: center;
}
.my-table{
tr{
.my-table {
tr {
padding: 6px 8px;
}
tr:nth-child(2n){
tr:nth-child(2n) {
background: #e9f3ff;
}
tr:last-child{
th{
tr:last-child {
th {
border-bottom: none;
}
}
th:nth-child(1){
th:nth-child(1) {
padding: 6px 8px;
width:80px;
background: #2976e8 ;
color:#fff;
width: 80px;
background: #2976e8;
color: #fff;
text-align: center;
//border-right:1px solid #b6ccd9;
border-bottom:1px solid #b6ccd9;
border-bottom: 1px solid #b6ccd9;
}
th:nth-child(2){
th:nth-child(2) {
padding: 6px 8px;
color:#253243;
color: #253243;
font-weight: unset;
gap:5px;
border-bottom:1px solid #b6ccd9
gap: 5px;
border-bottom: 1px solid #b6ccd9;
}
}
</style>

View File

@@ -1,10 +1,25 @@
<template>
<div class="chat-page">
<sticky
:hotList="hotList"
:productName="productName"
:messagesList.sync="messages"
:autoScrollEnabled.sync="autoScrollEnabled"
@setProductName="setProductName"
:isDisabled="messageStatus === 'send'"
></sticky>
<main class="chat-main">
<div class="chat-content">
<div class="message-area" ref="messageArea" @scroll="handleScroll">
<HotProducts class="mb10" :messagesList.sync="messages"></HotProducts>
<messageComponent :messagesList="messages" :is-deep="isDeep" :is-search="isSearching" :think-ok='isThink' @setProductName='setProductName'></messageComponent>
<HotProducts class="mb10" :messagesList.sync="messages" @getHotList="getHotProducts"></HotProducts>
<messageComponent
:messagesList="messages"
:is-deep="isDeep"
:is-search="isSearching"
:think-ok="isThink"
@setProductName="setProductName"
></messageComponent>
</div>
</div>
<!-- 滚动到顶部按钮 -->
@@ -13,12 +28,12 @@
</div>
</main>
<div v-if='isVoiceMode && newMessage' class='isVoiceModeText'>
<textarea class="textarea" placeholder="请输入内容" v-model='newMessage' ></textarea>
<div v-if="isVoiceMode && newMessage" class="isVoiceModeText">
<textarea class="textarea" placeholder="请输入内容" v-model="newMessage"></textarea>
</div>
<section class="section">
<button @click="searchInternet" :class="{ active: isSearching }">
<button @click="searchInternet" :class="{ active: isSearching }" class="ml10">
<svg-icon icon-class="earth" class-name="chat-icon"></svg-icon>
联网搜索
</button>
@@ -33,48 +48,36 @@
</section>
<footer class="chat-footer">
<!-- 输入框 or 按住说话提示 -->
<div class="input-wrapper">
<input
v-if="!isVoiceMode"
type="text"
v-model="newMessage"
placeholder="请简短描述您的问题"
@keyup.enter="sendMessage"
/>
<div v-else class="voice-hint-container"
:class="{disabled:messageStatus === 'send' }"
@mousedown="startRecording"
@selectstart="() => false"
@mouseup="stopRecording"
@mouseleave="stopRecording"
@touchend="stopRecording"
@touchstart="startRecording">
<div class="waveform" :class="{ active: isRecording}" >
<div class="input-wrapper ml10">
<input v-if="!isVoiceMode" type="text" v-model="newMessage" placeholder="请简短描述您的问题" @keyup.enter="sendMessage" />
<div
v-else
class="voice-hint-container"
:class="{ disabled: messageStatus === 'send' }"
@mousedown="startRecording"
@selectstart="() => false"
@mouseup="stopRecording"
@mouseleave="stopRecording"
@touchend="stopRecording"
@touchstart="startRecording"
>
<div class="waveform" :class="{ active: isRecording }">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</div>
<!-- <div class="hint-text" v-if='!isRecording'>按住说话</div>-->
<!-- <div class="hint-text" v-if='!isRecording'>按住说话</div>-->
</div>
</div>
<!-- 语音按钮按住说话 -->
<button
@click='isVoiceMode = !isVoiceMode'
class="mic-button ml10 mr10"
>
<svg-icon v-if='!isVoiceMode' icon-class="voice" class-name="chat-icon" style='width: 20px;height: 20px'></svg-icon>
<span v-else style='font-size: 18px;color:#707070' class='ml5 mr5'></span>
<button @click="isVoiceMode = !isVoiceMode" class="mic-button ml10 mr10">
<svg-icon v-if="!isVoiceMode" icon-class="voice" class-name="chat-icon ml10" style="width: 25px; height: 25px"></svg-icon>
<span v-else style="font-size: 20px; color: #707070" class="ml15 mr5"></span>
</button>
<!-- 发送按钮 -->
<button
@click="sendMessage"
:disabled="messageStatus === 'send'"
:class="{ disabled: messageStatus === 'send' }"
>
发送
</button>
<button @click="sendMessage" :disabled="messageStatus === 'send'" :class="{ disabled: messageStatus === 'send' }" class="mr10 fs16">发送</button>
</footer>
</div>
</template>
@@ -84,7 +87,8 @@ import { Icon } from 'vant'
import messageComponent from './components/message.vue'
import SvgIcon from '@/components/svg-icon/index.vue'
import HotProducts from '@/views/AI/components/HotProducts.vue'
import { chat, chatProduct,audioToText } from '@/api/generatedApi'
import sticky from '@/views/AI/components/sticky.vue'
import { chat, chatProduct, audioToText } from '@/api/generatedApi'
export default {
components: {
@@ -92,13 +96,15 @@ export default {
[Icon.name]: Icon,
messageComponent,
HotProducts,
sticky,
},
data() {
return {
productName:'',
answerMap:'',
timer:null,
answerIndex:0,
hotList: [],
productName: '',
answerMap: '',
timer: null,
answerIndex: 0,
conversationId: '',
currentMessage: null,
messageStatus: 'stop',
@@ -119,6 +125,10 @@ export default {
}
},
methods: {
getHotProducts(e) {
console.log(e)
this.hotList = e
},
deepInternet() {
this.isDeep = !this.isDeep
},
@@ -131,15 +141,17 @@ export default {
this.productName = ''
},
hasTreasureBox() {
chatProduct({ query: this.newMessage }).then((res) => {
if (res) {
chatProduct({ query: this.newMessage })
.then((res) => {
if (res) {
this.messageStatus = 'stop'
this.messages.push({ type: 'box', text: this.newMessage, detail: res.content })
this.newMessage = ''
}
})
.catch(() => {
this.messageStatus = 'stop'
this.messages.push({ type: 'box', text: this.newMessage, detail: res.content })
this.newMessage = ''
}
}).catch(() => {
this.messageStatus = 'stop'
})
})
},
sendMessage() {
if (this.messageStatus === 'send') return
@@ -185,20 +197,18 @@ export default {
const messageArea = this.$refs.messageArea
if (!messageArea) return
const threshold = 10
const isAtBottom =
messageArea.scrollHeight - messageArea.clientHeight <=
messageArea.scrollTop + threshold
const isAtBottom = messageArea.scrollHeight - messageArea.clientHeight <= messageArea.scrollTop + threshold
this.autoScrollEnabled = isAtBottom
this.scrollPosition = messageArea.scrollTop
},
async startRecording() {
if(this.messageStatus === 'send') return
if (this.messageStatus === 'send') return
if (this.isRecognizing) return
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
this.mediaRecorder = new MediaRecorder(stream)
this.audioChunks = []
this.mediaRecorder.ondataavailable = event => {
this.mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
this.audioChunks.push(event.data)
}
@@ -238,17 +248,19 @@ export default {
},
callVoiceRecognitionAPI(blob) {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', blob);
formData.append('appType', 'haslBigHelper');
formData.append('user', 'chenyuda');
audioToText(formData).then(res => {
if(res){
resolve(res.content)
}
}).catch(err => {
reject(err)
})
const formData = new FormData()
formData.append('file', blob)
formData.append('appType', 'haslBigHelper')
formData.append('user', 'chenyuda')
audioToText(formData)
.then((res) => {
if (res) {
resolve(res.content)
}
})
.catch((err) => {
reject(err)
})
})
},
axiosGetAiChat() {
@@ -262,7 +274,7 @@ export default {
think: '',
isLike: false,
isDisLike: false,
})
}),
)
this.messages.push(this.currentMessage)
const params = {
@@ -317,7 +329,7 @@ export default {
const cleanLine = line.replace(/^data:\s*/, '')
if (!cleanLine) return null
const data = JSON.parse(cleanLine)
console.log(data)
// console.log(data)
if (data.answer) {
this.answerMap += data.answer
}
@@ -343,8 +355,8 @@ export default {
this.messageStatus = 'stop'
}
console.log(answer)
console.log(this.currentMessage)
// console.log(answer)
// console.log(this.currentMessage)
if (!this.currentMessage || !answer) return
const mode = this.isThink ? 'think' : 'text'
this.currentMessage[mode] += answer
@@ -382,7 +394,6 @@ $primary-trans-color: rgba(135, 162, 208, 0.5);
background: #f7f8fa;
position: relative;
.button-container {
position: fixed;
bottom: 150px;
@@ -399,10 +410,10 @@ $primary-trans-color: rgba(135, 162, 208, 0.5);
}
}
}
.isVoiceModeText{
.isVoiceModeText {
display: flex;
textarea{
flex:1;
textarea {
flex: 1;
max-height: 80px;
resize: none;
background: #fff;
@@ -421,7 +432,7 @@ $primary-trans-color: rgba(135, 162, 208, 0.5);
gap: 10px;
button {
padding: 5px 12px;
padding: 4px 8px;
border: none;
background-color: $primary-trans-color;
color: #fff;
@@ -439,7 +450,9 @@ $primary-trans-color: rgba(135, 162, 208, 0.5);
.chat-footer {
display: flex;
align-items: center;
padding: 10px;
padding: 10px 10px 20px 10px;
//padding-bottom: constant(safe-area-inset-bottom);
//padding-bottom: env(safe-area-inset-bottom);
background-color: #fff;
.input-wrapper {
@@ -474,7 +487,6 @@ $primary-trans-color: rgba(135, 162, 208, 0.5);
background-color: #eaeaea;
}
.waveform {
display: flex;
align-items: flex-end;
@@ -482,7 +494,7 @@ $primary-trans-color: rgba(135, 162, 208, 0.5);
//height: 30px;
//width: 60px;
gap: 2px;
&.disabled{
&.disabled {
cursor: not-allowed;
background: red;
}
@@ -495,11 +507,26 @@ $primary-trans-color: rgba(135, 162, 208, 0.5);
background-color: $primary-color;
margin: 0 1px;
&:nth-child(1) { height: 10px; animation-delay: 0s; }
&:nth-child(2) { height: 16px; animation-delay: 0.1s; }
&:nth-child(3) { height: 12px; animation-delay: 0.2s; }
&:nth-child(4) { height: 18px; animation-delay: 0.3s; }
&:nth-child(5) { height: 14px; animation-delay: 0.4s; }
&:nth-child(1) {
height: 10px;
animation-delay: 0s;
}
&:nth-child(2) {
height: 16px;
animation-delay: 0.1s;
}
&:nth-child(3) {
height: 12px;
animation-delay: 0.2s;
}
&:nth-child(4) {
height: 18px;
animation-delay: 0.3s;
}
&:nth-child(5) {
height: 14px;
animation-delay: 0.4s;
}
}
}
@@ -548,7 +575,8 @@ $primary-trans-color: rgba(135, 162, 208, 0.5);
}
@keyframes wave-animation {
0%, 100% {
0%,
100% {
transform: scaleY(1);
}
50% {