Browse Source

阅读体

dev_des
1054425342@qq.com 2 months ago
parent
commit
d418613a1c
  1. 8
      components/ActivateAgentPopup.vue
  2. 4
      components/DynamicIsland.vue
  3. BIN
      components/GPT/.DS_Store
  4. 414
      components/GPT/components/client-chat.vue
  5. 232
      components/GPT/components/pending.vue
  6. 799
      components/GPT/index.vue
  7. 347
      components/GPT/utils/ClientData.js
  8. 50
      components/GPT/utils/EventHub.js
  9. 12
      components/GPT/utils/chat_constant.js
  10. 8
      components/GPT/utils/global.js
  11. 56
      components/GPT/utils/message.js
  12. 45
      components/GPT/utils/observer.js
  13. 283
      components/GPT/utils/socket.js
  14. 119
      components/GPT/utils/util.js
  15. 174
      components/ProductSection.vue
  16. 555
      components/WaterfallLayout.vue
  17. 3
      manifest.json
  18. 43
      pages/agent/index.vue
  19. 3196
      pages/index/iSoul.vue
  20. 12
      pages/index/index.vue
  21. 851
      pages/index/readingBody.vue
  22. 4
      pages/index/sensoryStore.vue
  23. 34
      pages/index/timeShopBank.vue
  24. 2
      pages/notes/detail.vue
  25. 5
      pages/stratIndex.vue
  26. BIN
      static/imgs/icon-bf-active.png
  27. BIN
      static/imgs/icon-bf.png
  28. BIN
      static/imgs/icon-copy.png
  29. BIN
      static/imgs/icon-pic.png
  30. BIN
      static/imgs/icon-txt.png
  31. BIN
      static/imgs/icon-video.png
  32. BIN
      static/imgs/more.png
  33. 2
      static/js/request.js
  34. 1586
      subPackages/equityGoods/detail.vue
  35. 31
      subPackages/other/evita.vue

8
components/ActivateAgentPopup.vue

@ -73,13 +73,11 @@ export default {
// AGENT
handleAgentClick(agent) {
console.log("点击了AGENT:", agent);
if (agent.status ==1) {
//
uni.navigateTo({
url: "/pages/agent/index?id="+agent.agentId
});
uni.navigateTo({
url: "/subPackages/other/evita?id="+agent.agentId
});
} else {
uni.navigateTo({
url:"/subPackages/equityGoods/detail?id="+agent.vos[0].benefitPackageId

4
components/DynamicIsland.vue

@ -25,7 +25,7 @@
<text class="profile-name">EVITA</text>
</view>
<view class="platform-link">
<view class="link-text" @click="toDesInfo" style="margin-bottom: 15rpx;">DES介绍 >>
<view class="link-text" @click="toDesInfo" style="margin-bottom: 30rpx;">DES介绍 >>
</view>
<view class="link-text">DES广播 >></view>
</view>
@ -363,7 +363,7 @@
// "https://des.js-dyyj.com/dist/#/",
// });
uni.navigateTo({
url:'/subPackages/other/evita'
url:'/subPackages/other/evita?id=1'
})
},

BIN
components/GPT/.DS_Store

Binary file not shown.

414
components/GPT/components/client-chat.vue

@ -0,0 +1,414 @@
<template>
<view class="client-chat" style="height: 100%">
<view ref="first" clazz="pop-demo" :use-arrow="true">
<view class="pop-demo-list" v-for="pop in popperList" :key="pop.id">
<v-button type="link" kind="primary" @click="openSearchUrl(pop, pop.id)">
{{ pop.id }}.{{ pop.name }}
<v-icon remote name="arrow_right_line" size="12" valign="-1"></v-icon>
</v-button>
</view>
</view>
<view class="qa-item" v-for="(item, index) in msgList" :key="index">
<!-- 时间戳 -->
<view class="timestamp"
v-if="index === 0 || (index !== 0 && item.timestamp && (Number(item.timestamp) - Number(msgList[index - 1].timestamp)) > timestampGap)">
{{ moment(new Date(String(item.timestamp).length === 10 ? item.timestamp * 1000 :
Number(item.timestamp))).format('MM-DD HH:mm') }}
</view>
<!-- 问题 -->
<view class="question-item" v-if="item.is_from_self">
<v-spinner status="default" class="qs-loading" v-if="item.is_loading"></v-spinner>
<!-- <VueMarkdown class="question-text" style="max-width: 352px" :source="item.content"
:anchorAttributes="{ target: '_blank' }" :linkify="false" /> -->
</view>
<!-- 答案 -->
<view class="answer-item" v-if="!item.is_from_self">
<!-- 头像 -->
<view class="answer-avatar">
<img class="robot-avatar" :src="item.from_avatar" />
</view>
<!-- 答案信息 -->
<view class="answer-info" :ref="item.record_id">
<view v-if="item.agent_thought && item.agent_thought.procedures && item.agent_thought.procedures.length > 0">
<!-- 思考部分 -->
<MsgThought
v-for="(thought, i) in item.agent_thought.procedures"
:key="i"
:content="thought.debugging.content"
:title="thought.title"
:titleIcon="thought.icon"
:nodeName="thought.name"
:status="thought.status"
:elapsed="thought.elapsed"
:detailVisible="thought.detailVisible"
/>
</view>
<view class="loading" v-if="item.loading_message">正在思考中</view>
<!-- 回复主体 -->
<MsgContent :showTags="true"
:recordId="item.record_id"
:isReplaceLinks="true"
:loadingMessage="item.loading_message"
:content="item.content"
:isFinal="item.is_final"
:isMdExpand="item.isMdExpand"
@littleTagClick="littleTagClick"
/>
<!-- 参考来源 -->
<Reference v-if="item.references && item.references.length>0" :references-list="item.references"/>
<!-- 运行状态 -->
<TokensBoardBfr class="tokens-board-class" :showDtl="true" :tokensData="item.tokens_msg"></TokensBoardBfr>
</view>
</view>
</view>
</view>
</template>
<script>
import clone from 'clone';
// import VueMarkdown from 'vue-markdown';
import elementResizeDetectorMaker from 'element-resize-detector';
import { scrollToBottom } from './../utils/util';
import { MESSAGE_TYPE, ACCESS_TYPE } from './../constants';
import TokensBoardBfr from './tokens-board-brif.vue';
import Reference from './reference-component.vue';
export default {
name: 'ClientChat',
components: {
// VueMarkdown,
Reference,
TokensBoardBfr
},
data () {
return {
popperList: [],
oldPopDemo: null,
loading: false,
historyLoading: false,
timestampGap: 5 * 60, // 5min
msgList: [], //
robotName: '', //
chatBoxHeight: document.body.clientHeight,
jsScrolling: false,
userScrolling: false
};
},
created () {
// /ws
this.listenClientAndManageEvent();
// ws
this.listenCommonEvent();
},
mounted () {
const erd = elementResizeDetectorMaker();
const bodyDom = document.body;
erd.listenTo(bodyDom, (element) => {
this.chatBoxHeight = element.clientHeight - 113; // 57+56
});
document.addEventListener('click', this.handleOutsideClick);
const sDom = document.querySelector('.client-chat');
sDom.addEventListener('scroll', () => {
if (this.msgList[this.msgList.length - 1].is_final === false && !this.jsScrolling) {
this.userScrolling = true;
} else {
this.jsScrolling = false;
}
});
},
beforeDestroy () {
//
document.removeEventListener('click', this.handleOutsideClick);
},
methods: {
openSearchUrl (refer, index) {
window.open(refer.url);
},
// /ws
listenClientAndManageEvent () {
//
let cachedConfig = null;
if (ACCESS_TYPE === 'ws') {
cachedConfig = this.$clientData.getConfigInfo();
} else {
cachedConfig = this.$SseCls.sseQueryConfigInfo();
}
if (cachedConfig) {
this.robotName = cachedConfig.name;
}
//
this.$eventHub.$on('client_msgContentChange', (res) => {
const { chatsContent, type } = res;
// PS $clientData ws
this.renderMsgList(chatsContent, type);
});
},
// ws
listenCommonEvent () {
this.$eventHub.$on('data_history', () => {
this.historyLoading = false;
});
this.$eventHub.$on('data_historyError', () => {
this.historyLoading = false;
});
},
//
renderMsgList (data, type) {
// ws
const noScrollEvt = [MESSAGE_TYPE.HISTORY, MESSAGE_TYPE.STOP, MESSAGE_TYPE.WORKBENCH_HISTORY, MESSAGE_TYPE.FEEDBACK];
const list = data.map(el => {
return { ...el, showPop: true };
});
this.msgList = clone(list);
// console.log('=======list========', clone(list));
// ws
this.$nextTick(() => {
const sDom = document.querySelector('.client-chat');
if (!sDom) return;
if (!this.userScrolling && (!noScrollEvt.includes(type))) {
this.jsScrolling = true;
scrollToBottom(sDom, sDom.scrollHeight);
}
if (this.msgList.length > 0 && this.msgList[this.msgList.length - 1].is_final === true) {
this.userScrolling = false;
}
});
},
handleOutsideClick (event) {
if (!this.oldPopDemo) { return; };
const firstElement = document.getElementsByClassName('pop-demo')[0];
if (this.oldPopDemo.contains(event.target) || firstElement.contains(event.target)) {
} else {
if (this.oldPopDemo) {
this.$refs['first'] && this.$refs['first'].unbindTrigger(this.oldPopDemo);
}
//
this.$refs['first'] && this.$refs['first'].hide();
this.oldPopDemo = null;
}
},
littleTagClick (e, r) {
const findMsg = this.$clientData.getMsgById(r);
let innerDome = e.querySelectorAll('.little-tags');
let outerTextArr = [];
if (innerDome && innerDome.length > 0) {
innerDome.forEach(dom => {
outerTextArr.push(dom.outerText);
});
}
this.popperList = findMsg.references.filter(e => outerTextArr.includes(e.id));
if (e) {
this.$refs['first'] && this.$refs['first'].bindTrigger(e, 'manual');
this.$refs['first'] && this.$refs['first'].update();
this.$refs['first'] && this.$refs['first'].show();
this.oldPopDemo = e;
}
}
}
};
</script>
<style lang="scss">
.client-chat::-webkit-scrollbar {
display: none;
}
.pop-demo{
// background-color: pink;
padding: 10px;
display: flex;
min-width: var(--size-l);
padding: var(--spacing-base);
flex-direction: column;
justify-content: center;
// align-items: center;
gap: var(--spacing-tight);
border-radius: var(--radius-normal);
border: 0.5px solid var(--color-border-normal);
background: var(--color-bg-2);
/* shadow/--shadow-medium */
box-shadow: var(--shadow-medium-x-1) var(--shadow-medium-y-2) var(--shadow-medium-blur-1) var(--shadow-medium-spread-1) var(--shadow-medium-color-1), var(--shadow-medium-x-2) var(--shadow-medium-y-2) var(--shadow-medium-blur-2) var(--shadow-medium-spread-2) var(--shadow-medium-color-2), var(--shadow-medium-x-3) var(--shadow-medium-y-3) var(--shadow-medium-blur-3) var(--shadow-medium-spread-3) var(--shadow-medium-color-3);
.v-popper__arrow{
display: block;
}
.pop-demo-list{
color: var(--color-link-normal);
/* caption/--caption-regular */
font-family: var(--font-family-normal);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
.v-button {
text-decoration: none;
text-align: left;
}
}
}
.client-chat {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: overlay;
padding: 0 12px;
.loading {
margin: 1em 0;
width: 150px;
&:after {
content: ".";
animation: ellipsis 1.5s steps(1, end) infinite;
}
}
@keyframes ellipsis {
0% {
content: ".";
}
33% {
content: "..";
}
66% {
content: "...";
}
100% {
content: ".";
}
}
.qa-item {
display: flex;
flex-direction: column;
margin-bottom: 16px;
font-weight: 400;
font-size: 14px;
color: var(--color-text-primary);
.timestamp {
font-weight: 400;
font-size: 12px;
line-height: 16px;
text-align: center;
color: var(--color-text-caption);
margin: 16px 0;
}
.question-item {
display: flex;
align-items: center;
width: fit-content;
text-align: center;
align-self: flex-end;
padding-left: 44px;
.qs-error {
min-width: 16px;
margin-right: 10px;
color: var(--color-error-normal);
}
.qs-loading {
margin-right: 10px;
}
.question-text {
background: #DBE8FF;
border-radius: 6px;
padding: 0 12px;
text-align: left;
word-break: break-all;
word-wrap: break-word;
code {
white-space: break-spaces;
}
img {
max-width: 80%;
}
}
}
.summary-item {
align-self: center;
margin: 12px 0;
}
.answer-item {
display: flex;
.contacter-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.answer-info {
position: relative;
display: flex;
flex-direction: column;
padding: 12px;
background: #F4F5F7;
border-radius: 6px;
width: calc(100% - 67px);
.answer-expand {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
width: 44px;
height: 24px;
margin-bottom: 12px;
background: var(--color-bg-2);
box-shadow: var(--shadow-small-light);
border-radius: 16px;
align-self: center;
}
.stop-ws {
color: var(--color-text-caption);
margin-left: 5px;
}
.answer-source {
margin: 12px 0;
font-size: 14px;
color: var(--color-text-caption);
text-align: left;
.v-button {
text-decoration: none;
text-align: left;
}
}
}
}
}
.qa-item:last-child {
padding-bottom: 120px;
}
}
</style>

232
components/GPT/components/pending.vue

@ -0,0 +1,232 @@
<template>
<view class="pending">
{{title}}
</view>
</template>
<script>
export default {
name: 'Pending',
data() {
return {
timeObj: null,
title: '正在思考中...'
};
},
mounted() {
this.init()
},
beforeDestroy() {
this.timeObj && clearInterval(this.timeObj);
},
methods: {
init() {
this.timeObj = setInterval(() => {
this.title = this.title == '正在思考中...' ? this.title === '正在思考中..' ? '正在思考中.' : '正在思考中..' : '正在思考中...'
}, 500)
}
}
};
</script>
<style lang="scss">
.client-chat::-webkit-scrollbar {
display: none;
}
.pop-demo {
// background-color: pink;
padding: 10px;
display: flex;
min-width: var(--size-l);
padding: var(--spacing-base);
flex-direction: column;
justify-content: center;
// align-items: center;
gap: var(--spacing-tight);
border-radius: var(--radius-normal);
border: 0.5px solid var(--color-border-normal);
background: var(--color-bg-2);
/* shadow/--shadow-medium */
box-shadow: var(--shadow-medium-x-1) var(--shadow-medium-y-2)
var(--shadow-medium-blur-1) var(--shadow-medium-spread-1)
var(--shadow-medium-color-1),
var(--shadow-medium-x-2) var(--shadow-medium-y-2)
var(--shadow-medium-blur-2) var(--shadow-medium-spread-2)
var(--shadow-medium-color-2),
var(--shadow-medium-x-3) var(--shadow-medium-y-3)
var(--shadow-medium-blur-3) var(--shadow-medium-spread-3)
var(--shadow-medium-color-3);
.v-popper__arrow {
display: block;
}
.pop-demo-list {
color: var(--color-link-normal);
/* caption/--caption-regular */
font-family: var(--font-family-normal);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
.v-button {
text-decoration: none;
text-align: left;
}
}
}
.client-chat {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: overlay;
padding: 0 12px;
.loading {
margin: 1em 0;
width: 150px;
&:after {
content: ".";
animation: ellipsis 1.5s steps(1, end) infinite;
}
}
@keyframes ellipsis {
0% {
content: ".";
}
33% {
content: "..";
}
66% {
content: "...";
}
100% {
content: ".";
}
}
.qa-item {
display: flex;
flex-direction: column;
margin-bottom: 16px;
font-weight: 400;
font-size: 14px;
color: var(--color-text-primary);
.timestamp {
font-weight: 400;
font-size: 12px;
line-height: 16px;
text-align: center;
color: var(--color-text-caption);
margin: 16px 0;
}
.question-item {
display: flex;
align-items: center;
width: fit-content;
text-align: center;
align-self: flex-end;
padding-left: 44px;
.qs-error {
min-width: 16px;
margin-right: 10px;
color: var(--color-error-normal);
}
.qs-loading {
margin-right: 10px;
}
.question-text {
background: #dbe8ff;
border-radius: 6px;
padding: 0 12px;
text-align: left;
word-break: break-all;
word-wrap: break-word;
code {
white-space: break-spaces;
}
img {
max-width: 80%;
}
}
}
.summary-item {
align-self: center;
margin: 12px 0;
}
.answer-item {
display: flex;
.contacter-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.answer-info {
position: relative;
display: flex;
flex-direction: column;
padding: 12px;
background: #f4f5f7;
border-radius: 6px;
width: calc(100% - 67px);
.answer-expand {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
width: 44px;
height: 24px;
margin-bottom: 12px;
background: var(--color-bg-2);
box-shadow: var(--shadow-small-light);
border-radius: 16px;
align-self: center;
}
.stop-ws {
color: var(--color-text-caption);
margin-left: 5px;
}
.answer-source {
margin: 12px 0;
font-size: 14px;
color: var(--color-text-caption);
text-align: left;
.v-button {
text-decoration: none;
text-align: left;
}
}
}
}
}
.qa-item:last-child {
padding-bottom: 120px;
}
}
</style>

799
components/GPT/index.vue

@ -0,0 +1,799 @@
<template>
<view class="chat-wrap__main">
<view class="chat-wrap__main-content" :style="{ 'margin-bottom': `${chatMainMrgBottom}px` }">
<!-- <view>显示内容</view> -->
<!-- <ClientChat @send="onSendQuestion" /> -->
<scroll-view class="chat-wrapper" :scroll-into-view="bottom" :scroll-y="true" :scroll-with-animation="true" scroll-into-view-offset="10">
<view class="list-container">
<!-- for循环 -->
<view class="chat-list" v-for="n,index in msgList" :key="index">
<view class="msg-container other" v-if="n.type =='reply' ">
<image class="ava" :src="robotObj.headImage"></image>
<view class="msg">
<view class="msg-nickname">{{robotObj.name}}</view>
<view :class="n.contentType==='img' ? 'msg-content-img' : '' " class="msg-content">
<template v-if="n.pending">
<Pending></Pending>
</template>
<template v-else>
<template v-if="n.contentType === 'text'">
<rich-text v-if="n.contentType === 'text'" :nodes="n.content"></rich-text>
<view v-if="n.is_final" class="msg-btns">
<image mode="widthFix" class="btn-img" @click="copy(n.content)" src="./../../static/imgs/icon-copy.png"></image>
<image mode="widthFix" class="btn-img" @click="audio(n)" :src="audioActive == n.timestamp ? './../../static/imgs/icon-bf-active.png':'./../../static/imgs/icon-bf.png'"></image>
</view>
</template>
<image v-else-if="n.contentType === 'img'" mode="widthFix" class="cimg" src="./../../static/imgs/more.png"></image>
</template>
</view>
</view>
</view>
<view v-if="n.type==='send' " class="msg-container self">
<view class="msg">
<!-- <view class="msg-nickname">本人</view> -->
<view class="msg-content" :class="n.contentType==='img' ? 'msg-content-img' : '' ">
<text v-if="n.contentType === 'text'">{{n.content}}</text>
<image v-else-if="n.contentType === 'img'" mode="widthFix" class="cimg" :src="n.content"></image>
</view>
</view>
<!-- <image class="ava" src="./../../static/imgs/more.png"></image> -->
</view>
</view>
</view>
<view id="bottom" style="opacity:0">
</view>
</scroll-view>
</view>
<view class="chat-wrap__main-footer">
<view class="disabled-loadding" v-if="disabledStatus"></view>
<view class="chatinput-wrapper">
<view class="chatinput-content">
<view class="chatinput-btn-wrap">
<image :src="videoStatus ? './../../static/imgs/icon-txt.png' :'./../../static/imgs/icon-video.png'" class='chatinput-img' @click="changeVideoStatus"></image>
</view>
<view class='chatinput-input-wrap' v-if="videoStatus">
<view style="text-align: center; flex: 1" @longpress="startVideo" @touchend="stopVideo">
按住 说话
</view>
</view>
<view v-else class='chatinput-input-wrap'>
<input v-model="inputValue" @focus="inputFocus" @input="inputChange" @confirm="inputSend" placeholder="想对TA说点什么呢…" confirm-type='send' />
</view>
<view class="chatinput-btn-wrap">
<image v-if="!isEditInput" src="./../../static/imgs/icon-pic.png" class='chatinput-img' @click="tapChooseImage"></image>
<button v-if="isEditInput" class="chatinput-send">发送</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import Socket from './utils/socket'
import { splitTextForTTS } from './utils/util'
import {
getHistroyMsg,
} from './utils/message'
import ClientData from './utils/ClientData';
import Pending from './components/pending';
export default {
props: {
agentId: {
type: String,
default: '',
}
},
components: {
Pending
},
watch: {
agentId: {
handler(val) {
if (val) {
this.init();
}
},
immediate: true,
},
},
data() {
return {
title: 'Hello',
socketObj: null,
isEditInput: false,
inputValue: '',
chatMainMrgBottom: '',
bottom: '',
videoStatus: false,
msgList: [],
recorderManager: null,
audioCtx: '',
audioActive: '',
$ClientData: '',
robotObj: {},
audioArray: [],
asrStatus: false,
tmpMsg: {},
lastTxt: {},
txtAudioStaus: {},
disabledStatus: false
}
},
onLoad() {
},
mounted() {
console.log('【init message connect type------>】',);
// this.init()
},
destroyed() {
console.log('【destroy message connect type------>】',);
this.socketObj.selfCloseStatus = true
this.socketObj.destroy()
this.audioCtx && this.audioCtx.destroy()
this.audioCtx = null
this.audioActive = ''
this.audioStatus = false
this.asrStatus = false
},
onShow() {
this.scrollToBottom()
},
methods: {
async init() {
this.audioStatus = true
this.asrStatus = true
uni.showLoading()
console.log('【init message connect type------>】',);
this.socketObj = new Socket({
agentId: this.agentId,
onMessage: (e) => {
const index = this.msgList.findIndex(it => it.request_id == e.request_id && it.type == 'reply')
console.log(e.request_id + '回复内容', e.content)
// this.tmpMsg[e.request_id] = this.tmpMsg[e.request_id] ?? []
// if (this.tmpMsg[e.request_id].length) {
// const txt = this.removeSpecificText(e.content??'', this.lastTxt[e.request_id] ?? '')
// if (txt) {
// this.tmpMsg[e.request_id].push(txt)
// }
// } else {
// this.tmpMsg[e.request_id].push(e.content)
// }
// this.lastTxt[e.request_id] = e.content
// // console.log(this.tmpMsg[e.request_id])
// if (!this.txtAudioStaus[e.request_id]) {
// this.txtAudioStaus[e.request_id] = true
// this.renderTxt(e)
// }
// console.log(e.content)
e.pending = false
// if (index > -1) {
// this.msgList[index] = e
// } else {
// if (this.msgList.length) {
// const obj = this.msgList[this.msgList.length - 1]
// if (obj.pending) {
// this.$nextTick(() => {
// this.msgList[this.msgList.length - 1] = e
// })
// } else {
// this.msgList.push(e)
// }
// } else {
// this.msgList.push(e)
// }
// }
if (!e.is_from_self && e.is_final) {
const audioParams = e
console.log( audioParams);
//
this.audio(audioParams, (txt)=> {
const index = this.msgList.findIndex(it => it.request_id == e.request_id && it.type == 'reply')
if (index > -1) {
this.$nextTick(() => {
this.msgList[index].content += txt
this.scrollToBottom()
})
} else {
if (this.msgList.length) {
const obj = this.msgList[this.msgList.length - 1]
if (obj.pending) {
this.$nextTick(() => {
const tmp = {
...e,
pending: false,
content: txt
}
this.msgList[this.msgList.length - 1] = tmp
this.scrollToBottom()
})
} else {
this.msgList.push(e)
}
} else {
const tmp = {
...e,
pending: false
}
this.$nextTick(() => {
this.msgList.push(tmp)
this.scrollToBottom()
})
}
}
this.scrollToBottom()
})
//
this.disabledStatus = false
}
this.scrollToBottom()
}
})
this.getRecord(this.agentId)
this.scrollToBottom(1000)
await this.socketObj.init()
this.$ClientData = new ClientData()
this.$ClientData.init()
this.$ClientData.setAttr({ socketObj: this.socketObj })
this.robotObj = this.socketObj.robotObj
wx.hideLoading()
this.sendMsg()
},
async renderTxt(e) {
for (let index = 0; index < this.tmpMsg[e.request_id].length; index++) {
const txt = this.tmpMsg[e.request_id][index];
const params = {
content: txt,
timestamp: e.timestamp
}
await this.audio(params)
}
},
sendMsg() {
if (!this.msgList.length) {
this.$ClientData.triggerSendMsg(this.socketObj.robotObj.firstWord, 'text', false);
}
},
removeSpecificText(originalText, textToRemove) {
// 使
return originalText.replace(new RegExp(textToRemove, 'g'), '');
},
//
copy(e) {
wx.setClipboardData({
data: e,
success: function () {
wx.showToast({ title: '复制成功' });
}
});
},
async audio(n, cb) {
console.log('【audio------>】', n);
const txt = this.stripHtmlTags(n.content)
if (!txt) { return }
this.audioCtx && this.audioCtx.destroy()
this.audioCtx = null
if (this.audioActive == n.timestamp) {
this.audioActive = ''
return
}
const texts = splitTextForTTS(txt)
console.log('语言划分', texts)
this.audioActive = n.timestamp
for (let index = 0; index < texts.length; index++) {
const txt = texts[index];
const nextText = texts[index + 1] ?? ''
if (nextText) {
this.loadAudioUrl(nextText)
}
await this.audioText(txt, cb)
}
this.disabledStatus = false //
this.audioActive = ''
},
audioText(text, cb) {
return new Promise(async (resolve, reject) => {
if (!this.audioStatus) { reject() }
const url = await this.loadAudioUrl(text)
console.log('播放文字', text)
cb && cb(text)
this.audioCtx = wx.createInnerAudioContext();
this.audioCtx.src = url //'data:audio/wav;base64,' + res.data.data
this.audioCtx.play()
this.audioCtx.onStop(() => {
this.delAudioFile(url)
resolve()
})
this.audioCtx.onEnded(() => {
this.delAudioFile(url)
resolve()
})
});
},
loadAudioUrl(text) {
if (this.audioArray[text]) {
return Promise.resolve(this.audioArray[text])
} else {
return new Promise((resolve, reject) => {
uni.request({
method: "post",
data: {
text,
voiceType: this.socketObj.robotObj.voiceType ?? 1002
},
dataType: "json",
url: `https://des.js-dyyj.com/xcx/api/voice/tts`,
success: (res) => {
console.log('文字合成语音', res)
if (res.data.code) {
this.audioArray[text] = res.data.msg
const audio = wx.createInnerAudioContext();
audio.src = res.data.msg;
audio.onCanplay(() => {
audio.destroy(); //
});
resolve(res.data.msg)
} else {
wx.showToast({ title: '文字转化失败' });
reject()
}
},
fail: (err) => {
console.log("【init msg-------getDemoToken---->】", err);
},
});
})
}
},
stripHtmlTags(html) {
return html.replace(/<\/?[^>]+(>\$)/g, '')
.replace(/\u00a0/g, '')
// .replace(/\s+/g, ' ')
.replace(/\([^)]*\)/g, '')
.replace(/\([^)]*\)/g, '')
.replace(/\<br\/\>/g, '')
.replace(/\<br\>/g, '')
},
getRecord(id) {
//
let { msgList } = getHistroyMsg(id)
if (msgList && msgList.length) {
this.msgList = msgList.slice(-10)
} else {
this.msgList = []
}
},
changeVideoStatus() {
this.videoStatus = !this.videoStatus
},
startVideo() {
if (!this.recorderManager) {
this.recorderManager = wx.getRecorderManager()
}
this.recorderManager.start({
duration: 10000,
sampleRate: 44100,
numberOfChannels: 1,
encodeBitRate: 192000,
format: 'aac',
frameSize: 50
})
this.recorderManager.onStart(() => {
console.log('开始录音')
wx.showLoading({
title: '录音中',
})
})
this.recorderManager.onStop(async (res) => {
wx.showLoading({
title: '识别中',
mask: true
})
uni.uploadFile({
url: 'https://des.js-dyyj.com/xcx/system/oss/upload',
filePath: res.tempFilePath,
name: 'file',
success: async (res) => {
let data = JSON.parse(res.data);
console.log('上传成功', res, data);
if (!this.asrStatus) { return }
if (data.code == 200) {
uni.request({
method: "get",
dataType: "json",
url: `https://des.js-dyyj.com/xcx/api/voice/asr?audioFilePath=${encodeURI(data.data.url)}`,
success: (res) => {
console.log("【init msg-------res---->】", res);
if (!this.asrStatus) { return }
if (res.data.code == 200) {
wx.hideLoading()
if (res.data.msg) {
this.onSendQuestion(res.data.msg);
this.scrollToBottom()
} else {
wx.showToast({
title: '没有听清,请再说一遍',
icon: 'none'
});
}
} else {
wx.showToast({
title: '识别失败',
icon: 'error'
});
}
},
fail: (err) => {
wx.showToast({
title: '识别失败',
icon: 'error'
});
},
complete: () => {
this.delAudioFile(data.data.url)
}
});
}
},
fail: (err) => {
console.log('上传失败', err);
wx.showToast({
title: '识别失败',
icon: 'error'
});
}
});
})
this.recorderManager.onFrameRecorded((res) => {
const { frameBuffer } = res
console.log('frameBuffer', frameBuffer)
//
this.getVideoText(frameBuffer)
})
},
delAudioFile(path) {
if (path) {
uni.request({
method: "get",
dataType: "json",
url: `https://des.js-dyyj.com/xcx/api/voice/deleteSingleFile?audioFilePath=${encodeURI(path)}`,
success: (res) => {
console.log("【删除文件msg-------res---->】", res);
},
fail: (err) => {
console.log("【init msg-------getDemoToken---->】", err);
},
});
}
},
getVideoText(frameBuffer) {
const params = {
Action: 'SentenceRecognition',
Version: '2019-06-14',
EngineModelType: '8k_zh',
ChannelNum: 1,
VoiceFormat: 'acc',
ResTextFormat: 3,
ResTextFormat: 1
}
},
stopVideo() {
console.log('结束录音')
this.recorderManager && this.recorderManager.stop && this.recorderManager.stop()
this.recorderManager = null
},
inputSend() {
console.log('【inputSend------>】',);
this.onSendQuestion(this.inputValue)
this.scrollToBottom()
},
inputFocus() {
console.log('inputFocus------>】',);
this.scrollToBottom()
},
inputChange() {
console.log('inputChange------>】',);
},
// *
async tapChooseImage() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (res) => {
uni.uploadFile({
url: 'https://des.js-dyyj.com/xcx/system/oss/upload',
filePath: res.tempFiles[0].tempFilePath,
name: 'file',
success: (res) => {
console.log('上传成功', res);
let data = JSON.parse(res.data);
if (data.code == 200) {
const img = data.data.url
this.$ClientData.triggerSendMsg(img, 'img');
const postData = {
content: img,
type: 'send',
contentType: 'img',
timestamp: +new Date(),
chatId: this.agentId
}
console.log('postData', postData)
this.msgList.push(postData)
//
this.inputTmpReply()
this.scrollToBottom()
this.disabledStatus = true
}
},
fail: (err) => {
console.log('上传失败', err);
}
});
}
})
},
onSendQuestion(e) {
let self = this
if (e === '') {
return wx.showToast({ title: '不能发送空白消息', icon: 'none' })
}
this.disabledStatus = true
console.log('发送问题', e)
this.$ClientData.triggerSendMsg(e, 'text');
const postData = {
content: e,
type: 'send',
contentType: 'text',
timestamp: +new Date(),
chatId: this.agentId
}
self.msgList.push(postData)
self.inputValue = ''
this.inputTmpReply()
},
inputTmpReply() {
//
const reData = {
content: '',
pending: true,
type: 'reply',
contentType: 'text',
// timestamp: +new Date(),
chatId: this.agentId,
nickName: this.socketObj.robotObj.name,
headImage: this.socketObj.robotObj.headImage,
}
this.msgList.push(reData)
},
// !
scrollToBottom(time = 300) {
this.$nextTick(() => {
setTimeout(() => {
this.bottom = ''
this.$nextTick(function () {
this.bottom = 'bottom'
})
}, time)
});
}
}
}
</script>
<style scoped lang="scss">
.chat-wrap__main {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 700px;
overflow: hidden;
background: var(--color-bg-0);
border-radius: 12px;
&-chat-content {
flex: 1;
display: flex;
justify-content: flex-end;
align-items: flex-end;
}
&-content {
height: calc(100% - 80px);
display: flex;
justify-content: flex-end;
align-items: flex-end;
}
&-footer {
position: relative;
z-index: 3;
}
}
.chat-wrap__main-content {
flex: 1;
}
.chat-wrap__main-footer {
height: 140rpx;
padding: 10rpx;
padding-bottom: 10px;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
background-color: #f8f8f8;
position: relative;
.disabled-loadding {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba($color: #ffffff, $alpha: 0.4);
z-index: 9;
}
}
//
.chatinput-wrapper {
width: 100%;
// border-top: 1px solid #ddd;
// border-bottom: 1px solid #ddd;
.chatinput-content {
height: 100rpx;
padding: 10rpx 0 20rpx;
display: flex;
align-items: center;
.chatinput-input-wrap {
flex: 1;
height: 80rpx;
padding: 0 20rpx;
box-sizing: border-box;
display: flex;
align-items: center;
background: #fff;
input {
height: 76rpx;
font-size: 16px;
display: inline-block;
width: 100%;
margin: 10px;
}
}
.chatinput-btn-wrap {
width: 50px;
display: flex;
align-items: center;
justify-content: center;
image {
width: 30px;
height: 30px;
}
.chatinput-send {
height: 35px;
width: 43px;
border-radius: 4px;
font-size: 12px;
line-height: 35px;
padding: 0;
margin: 0 8px;
color: #fff;
background: #0097ff;
&::after {
border: none;
}
}
}
}
}
.chat-wrapper {
height: 100%;
.chat-list {
padding: 10px 0;
.msg-container {
display: flex;
.ava {
width: 35px;
height: 35px;
border-radius: 4px;
margin: 0 10px;
}
.msg-nickname {
color: #999;
line-height: 1;
margin-bottom: 4px;
}
.msg-content {
box-sizing: border-box;
word-wrap: break-word;
max-width: 60vw;
padding: 8px;
border-radius: 5rpx;
background: #fff;
border: 1px solid #e7e7e7;
max-width: "calc(100vw - 110px)";
.msg-btns {
border-top: 1px solid #e7e7e7;
margin-top: 5px;
padding-top: 10px;
.btn-img {
margin: 0 10px;
width: 20px;
height: 20px;
cursor: pointer;
}
}
}
&.self {
justify-content: flex-end;
padding-right: 10px;
.msg {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
}
.msg-nickname {
text-align: right;
}
.msg-content {
width: auto;
color: #fff;
background-color: #0097ff;
border-color: #0097ff;
}
}
&.other {
.msg {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
}
}
.msg-content.msg-content-img {
background: none;
border: none;
padding: 0;
image {
max-width: 120px;
border-radius: 2px;
}
}
}
}
}
</style>

347
components/GPT/utils/ClientData.js

@ -0,0 +1,347 @@
import Vue from "vue";
import { MESSAGE_TYPE } from "./chat_constant";
import { getQueryVariable, generateRequestId, arrayUnique } from "./util";
import { v4 as uuidv4 } from "uuid";
import GLOBAL from "./global";
import Observer from "./observer";
const $e = Observer;
// const $s = this.$s;
let cache = null; // 缓存
let timeoutTasks = {}; // 超时任务管理
const msgSendTimeout = 2 * 60 * 1000; // 发送消息超时ms,此处超时默认为2min
class ClientData {
constructor(option) {
cache = {
session_id: "", // 会话ID
configInfo: null, // 配置信息
chatsContent: [], // 会话聊天内容
systemEvents: [], // 系统事件栈
transferInfo: {
transferStatus: false,
transferAvatar: "",
}, // 当前转人工状态
};
this.$s = null
}
init() {
// 获取基础配置
this.queryConfigInfo();
}
setAttr(options) {
this.$s =options.socketObj
}
// 获取基础配置
async queryConfigInfo() {
try {
const seatBizId = ''// getQueryVariable("seat_biz_id");
console.log("seatBizId", seatBizId);
const sessionInfo = await this.createSession();
console.log("createsession, res", sessionInfo);
if (sessionInfo.code === 0) {
cache.seat_biz_id = seatBizId;
cache.session_id = sessionInfo.data.session_id;
} else {
uni.showModal({
title: "获取会话ID失败,请重试",
icon: "none",
});
}
// 接着获取机器人基础信息
const botInfo = {
code: 0,
data: {
name: "测试机器人",
avatar:
"https://qbot-1251316161.cos.ap-nanjing.myqcloud.com/avatar.png",
is_available: true,
bot_biz_id: "1664519736704069632",
},
};
if (botInfo.data) {
cache.configInfo = botInfo.data;
cache.configInfo.session_id = sessionInfo.data.session_id;
$e.$emit("client_configChange", cache.configInfo);
} else {
uni.showModal({
title: "获取机器人信息失败",
icon: "none",
});
}
} catch (e) {
console.log("获取机器人信息失败", e);
uni.showModal({
title: "获取会话信息失败,请刷新页面重试",
icon: "none",
});
}
}
async createSession() {
const session_id = uuidv4();
return { code: 0, data: { session_id: session_id } };
}
// 消息上行事件(用户端)
triggerSendMsg =async (msg, type='text', status= true) => {
console.log("[triggerSendMsg]", msg, wx);
if (!cache.configInfo || !cache.configInfo.session_id) {
await this.queryConfigInfo();
}
const requestId = generateRequestId();
const params = {
request_id: requestId,
session_id: cache.configInfo ? cache.configInfo.session_id : 0,
is_msg_status: status
};
if (type == "text") {
params.content = msg;
} else if (type == "img") {
params.content = `![](${msg})`;
params.realContent = msg;
}
this.$s.emit("send", params, type);
}
// 监听token用量和详情事件
listenTokenStat() {
this.$s.on("token_stat", (data) => {
$e.$emit("token_state_change", data);
if (data.session_id !== cache.session_id) return; // 若新消息不属于当前机器人时,则不做处理
let loadingMsg = cache.chatsContent.find((el) => el.loading_message);
let loadingText = "思考中";
if (loadingMsg) {
if (data.procedures && data.procedures.length > 0) {
loadingText =
data.procedures[data.procedures.length - 1].title || "思考中";
}
let currentList = cache.chatsContent;
currentList.forEach((el) => {
if (el.loading_message) {
el.text = loadingText;
el.record_id = data.record_id;
el.tokens_msg = data;
// 只有标准模式加这个
if (GLOBAL.webimToken[0].pattern === "standard") {
el.is_final = false;
}
}
});
$e.$emit("client_msgContentChange", {
chatsContent: cache.chatsContent,
type: MESSAGE_TYPE.ANSWER,
});
} else {
let findedMsg = cache.chatsContent.find(
(el) => el.record_id === data.record_id
);
if (!findedMsg) return;
findedMsg.tokens_msg = data;
$e.$emit("client_msgContentChange", {
chatsContent: cache.chatsContent,
type: MESSAGE_TYPE.ANSWER,
});
}
});
}
// 组装消息队列数据
// 问题确认消息:根据request_id关联覆盖(服务端收到问题后的确认消息)
// 答案消息:倒序遍历插入(服务端答案消息)
assembleMsgContent(msgList, type) {
console.log("assembleMsgContent", msgList, type);
let newMsg = msgList;
if (type === MESSAGE_TYPE.QUESTION) {
// 发送的问题消息由前端临时插入消息队列
cache.chatsContent.push(newMsg);
} else if (type === MESSAGE_TYPE.ANSWER) {
if (cache.chatsContent.length < 1) {
cache.chatsContent.push(newMsg);
} else {
let currentList = cache.chatsContent;
timeoutTasks[newMsg.request_id] &&
clearTimeout(timeoutTasks[newMsg.request_id]);
if (currentList.length === 2 && newMsg.can_rating) {
currentList[0].transferRobot = true;
}
if (newMsg.transfer && newMsg.loading_message) {
currentList.pop();
currentList[currentList.length - 1].loading_message = false;
currentList[currentList.length - 1] = {
...newMsg,
...currentList[currentList.length - 1],
transfer: true,
transferRobot: false,
};
} else {
for (let i = currentList.length - 1; i >= 0; i--) {
const { transfer, quit, transferRobot } = currentList[i];
let tmp = {
...newMsg,
transfer,
quit,
transferRobot,
};
// 保留tokens_msg,防止覆盖
if (currentList[i].tokens_msg) {
tmp = { ...tmp, tokens_msg: currentList[i].tokens_msg };
}
// 保留thought 放置被覆盖
if (currentList[i].agent_thought) {
tmp = { ...tmp, agent_thought: currentList[i].agent_thought };
}
// 保留reference
if (currentList[i].references) {
tmp = { ...tmp, references: currentList[i].references };
}
// 答案消息流式输出覆盖(record_id)
if (newMsg.record_id === currentList[i].record_id) {
currentList[i] = tmp;
break;
}
// 服务端问题消息确认数据,覆盖前端插入的临时问题消息数据(request_id匹配 & 自己发出的问题消息)
if (
newMsg.request_id &&
newMsg.request_id === currentList[i].request_id &&
newMsg.is_from_self
) {
newMsg.is_loading = false; // 服务端确认收到问题消息,则去除”发送中“状态
currentList[i] = tmp;
// 非人工状态时, 并且用户发送的不是敏感消息。插入临时[正在思考中...]消息
if (!newMsg.is_evil && !cache.transferInfo.transferStatus) {
currentList.push({
loading_message: true,
is_from_self: false,
content: "",
from_avatar: cache.configInfo.avatar,
timestamp: Number(currentList[i].timestamp), // 精确到秒
});
}
break;
}
// 插入最新答案消息
if (Number(newMsg.timestamp) >= Number(currentList[i].timestamp)) {
if (currentList[i].loading_message) {
// 删除原来的[正在思考中...]消息
currentList[currentList.length - 1] = newMsg;
} else {
currentList.splice(i + 1, 0, newMsg);
}
break;
}
if (
i === 0 &&
Number(newMsg.timestamp) < Number(currentList[i].timestamp)
) {
currentList.splice(0, 0, newMsg);
}
}
}
}
} else if (type === MESSAGE_TYPE.HISTORY) {
let currentList = cache.chatsContent;
// 历史数据打上标签,无需展示”重新生成“和”停止生成“操作
msgList = msgList.map((r) => {
return {
...r,
is_history: true,
is_final: true,
};
});
if (currentList.length === 0) {
// 若消息队列为空(用户端,初始拉取历史记录,用做判断欢迎页展示场景)
cache.chatsContent = [].concat(msgList);
} else {
// 若消息队列不为空
let oldMsgCurrent = currentList[0];
let newMsgHistory = msgList[msgList.length - 1];
// 将历史数据拼装到消息队列中(按照时间戳重排数据)
if (Number(newMsgHistory.timestamp) < Number(oldMsgCurrent.timestamp)) {
cache.chatsContent = [].concat(msgList).concat(cache.chatsContent);
} else {
msgList.reverse().forEach((msg) => {
for (let i = 0; i < cache.chatsContent.length; i++) {
if (msg.record_id === cache.chatsContent[i].record_id) {
// 重复覆盖
cache.chatsContent[i] = msg;
break;
} else if (
Number(msg.timestamp) <= Number(cache.chatsContent[i].timestamp)
) {
cache.chatsContent.splice(i, 0, msg);
break;
} else if (
i === cache.chatsContent.length - 1 &&
Number(msg.timestamp) > Number(cache.chatsContent[i].timestamp)
) {
cache.chatsContent.splice(i + 1, 0, msg);
}
}
});
}
}
}
// 消息去重。同一record_id取最新,同时保留消息最早的时间戳
cache.chatsContent = arrayUnique(
cache.chatsContent,
"record_id",
"timestamp"
);
// 消息队列变更通知事件
$e.$emit("client_msgContentChange", {
chatsContent: cache.chatsContent,
type,
});
}
// 修改指定msgId的消息内容
modifyMsgContent(msgId) {
const findedMsg = this.getMsgById(msgId);
if (findedMsg) {
findedMsg.is_final = true;
findedMsg.content = findedMsg.content.concat(
`<span class="stop-ws">| 已停止生成</span>`
);
$e.$emit("client_msgContentChange", {
chatsContent: cache.chatsContent,
type: MESSAGE_TYPE.STOP, // ”停止生成“事件
});
}
}
// 根据msgId获取消息
getMsgById(msgId) {
const findedMsg = cache.chatsContent.find((r) => r.record_id === msgId);
return findedMsg;
}
// 根据msgId获取其关联问题消息
getQmsgById(msgId) {
let findedQmsg = null;
const findedMsg = this.getMsgById(msgId);
if (findedMsg) {
findedQmsg = cache.chatsContent.find(
(r) => r.record_id === findedMsg.related_record_id
);
}
return findedQmsg;
}
releaseCache() {}
destroy() {
// be careful to clear the cache to avoid errors
this.releaseCache();
}
}
export default ClientData;

50
components/GPT/utils/EventHub.js

@ -0,0 +1,50 @@
import Vue from 'vue';
class EventHub {
constructor (vm) {
this.vm = vm;
this.curVm = null;
this.events = {};
this.eventMapUid = {};
}
$register (vm) {
this.curVm = vm;
}
setEventMapUid (uid, type) {
if (!this.eventMapUid[uid]) {
this.eventMapUid[uid] = [];
}
this.eventMapUid[uid].push(type);
}
$on (type, fn) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(fn);
if (this.curVm instanceof this.vm) {
this.setEventMapUid(this.curVm._uid, type);
}
}
$emit (type, ...args) {
if (this.events[type]) {
this.events[type].forEach(fn => fn(...args));
}
}
$off (type, fn) {
if (fn && this.events[type]) {
const index = this.events[type].findIndex(f => f === fn);
if (index !== -1) {
this.events[type].splice(index, 1);
}
return;
}
delete this.events[type];
}
$offAll (uid) {
const curAllTypes = this.eventMapUid[uid] || [];
curAllTypes.forEach(type => this.$off(type));
delete this.eventMapUid[uid];
}
}
export default new EventHub(Vue);;

12
components/GPT/utils/chat_constant.js

@ -0,0 +1,12 @@
// type: Q-问题,A-答案,H-历史消息,S-停止生成,C-结束会话,T-转接会话,R-参考来源,F-点赞/点踩回执
export const MESSAGE_TYPE = {
QUESTION: 'Q',
ANSWER: 'A',
HISTORY: 'H',
STOP: 'S',
CLOSE: 'C',
TRANSFER: 'T',
REFERENCE: 'R',
FEEDBACK: 'F',
WORKBENCH_HISTORY: 'WH'
};

8
components/GPT/utils/global.js

@ -0,0 +1,8 @@
wx.GLOBE = {
webimToken: [],
SOCKET: null,
}
export default wx.GLOBE

56
components/GPT/utils/message.js

@ -0,0 +1,56 @@
/**
* !插入数据
* @param roomId
* @param sourceId
* @param orderId
* @param type
* @param fileId
* @param txt
* @param time
* @param userId
* @param nickName
* @param sendOrReceive
* @param address
*/
export const setMsgData = data => {
console.log('setMsgData', data)
const resData = data
const session_id = resData.chatId // 群id
let msgData = wx.getStorageSync('imMsgData') || {}
// * 插入群数据
if (msgData[session_id]) {
if (resData.timestamp > msgData[session_id].timestamp) {
msgData[session_id].timestamp = resData.timestamp
}
msgData[session_id].listMsg.push(resData)
} else {
msgData[session_id] = {
listMsg: [resData],
timestamp: resData.timestamp,
session_id: resData.session_id,
}
}
wx.setStorageSync('imMsgData', msgData)
return resData
}
// ! 获取群消息
export const getHistroyMsg = id => {
let msgData = wx.getStorageSync('imMsgData') || {}
let msgList = []
if (msgData[id]) {
const data = msgData[id]
// *处理历史消息并按时间排序
const compare = property => {
return function(a, b) {
var value1 = a[property]
var value2 = b[property]
return value1 - value2
}
}
msgList = data.listMsg.sort(compare('time'))
}
return { msgList }
}

45
components/GPT/utils/observer.js

@ -0,0 +1,45 @@
import GLOBAL from './global';
class Observer {
constructor() {
this._event = {}
}
$on(eventName, handler) {
if (this._event[eventName]) {
this._event[eventName].push(handler)
} else {
this._event[eventName] = [handler]
}
}
$emit(eventName) {
let events = this._event[eventName]
let otherArgs = Array.prototype.slice.call(arguments, 1)
let that = this
if (events) {
events.forEach(event => {
event.apply(that, otherArgs)
})
}
}
$off(eventName, handler) {
let events = this._event[eventName]
if (events) {
this._event[eventName] = events.filter(event => {
return event !== handler
})
}
}
$once(eventName, handler) {
let that = this
function func() {
let args = Array.prototype.slice.call(arguments, 0)
handler.apply(that, args)
this.off(eventName, func)
}
this.on(eventName, func)
}
}
if (!GLOBAL.observer) {
GLOBAL.observer = new Observer()
}
export default GLOBAL.observer

283
components/GPT/utils/socket.js

@ -0,0 +1,283 @@
import Vue from "vue";
import GLOBAL_OBJ from "./global";
import "./EventHub";
import { setMsgData } from "./message";
// 心跳间隔
const HEART_BEAT_TIME = 20000;
// 心跳最大失败次数(超过此次数重连)
const HEART_BEAT_FAIL_NUM = 1;
// 重连间隔
const RECONNECT_TIME = 1000;
export default class Socket {
constructor(option) {
this.socket = null;
this._options = option;
this.timeoutObj = null;
this.robotObj = {};
this.selfCloseStatus = false
this.reconnectLock = false
console.log("Socket init", GLOBAL_OBJ);
}
getToken() {
return new Promise((resolve, reject) => {
uni.request({
method: "GET",
dataType: "json",
// url: `http://192.168.124.118:8083/xcx/framework/agent/${this._options.agentId}`,
url: `https://des.js-dyyj.com/getDemoToken?id=${this._options.agentId}`,
success: (res) => {
console.log("请求token成功", res);
resolve(res);
},
fail: (err) => {
wx.showToast({
title: "创建失败",
icon: "none",
});
console.log("请求token失败", err);
reject()
},
});
});
}
async init() {
return this.createSocket();
}
async createSocket(options) {
return new Promise(async (resolve, reject) => {
const origin = "wss://wss.lke.cloud.tencent.com";
let path = "/v1/qbot/chat/conn/";
let initSocket = 1;
let mainToken
const res = await this.getToken();
if (
res &&
res.data &&
res.data.apiResponse &&
res.data.apiResponse.Token
) {
mainToken = res.data.apiResponse.Token;
}
// 机器人信息
this.robotObj = res.data.requestInfo;
console.log("获取token:", mainToken);
// 建立连接
const socket = wx.connectSocket({
url: `${origin}${path}?EIO=4&transport=websocket`,
success: (e) => {
console.log("创建链接成功", e, socket);
},
complete: (e) => {
console.log("socket - complete", e);
},
});
this.socket = socket;
GLOBAL_OBJ.SOCKET = this;
let systemEventEmit = (eventName, data) => {
Vue.prototype.$eventHub.$emit(eventName, data);
};
socket.onOpen((e) => {
// 监听发送
if (initSocket === 1) {
const token = mainToken || "";
if (token) {
// cb({ token: token });
this.send({
data:
"40" +
JSON.stringify({
token: token,
}),
});
} else {
// cb({ token: '' });
this.send({
data: JSON.stringify({
token: "",
}),
});
}
initSocket++;
} else {
const token = mainToken || "";
// cb({ token: token });
this.send({
data: JSON.stringify({
token: token,
}),
});
initSocket++;
}
options && options.complete && options.complete()
});
socket.onMessage((e) => {
// console.log("socket.onMessage", e);
const { data } = e;
if (data == 2) {
// 触发浪涌
this.send({
data: 3,
});
}
const params = this.getMsgData(data);
const { type } = params ?? {};
this.on(type, params);
if (params && params.type === "reply" && !params.is_from_self) {
// 回复消息
this._options.onMessage && this._options.onMessage(params);
}
const num = "" + data.substring(0, 2);
if (num == "40") {
resolve();
}
this.createInter();
});
// 失败
socket.onError((e) => {
console.log("websocket error 长链接报错", e);
// 失败重建
this.doConnectTimeout();
//
});
// 关闭
socket.onClose((e) => {
console.log("websocket close 长链接关闭", e);
this.connectSocketTimeOut && clearTimeout(this.connectSocketTimeOut);
//非自动关闭重连
if (e.code == 1006) {
this.doConnectTimeout();
}
});
});
}
doConnectTimeout() {
// 重连一次
if (!this.reconnectLock) {
this.reconnectLock = true;
this.connectSocketTimeOut = setTimeout(() => {
this.createSocket({
complete: function (res) {
this.reconnectLock = false;
},
});
}, RECONNECT_TIME);
}
}
onConnect(e) {
console.log("websocket connect", e);
}
send(e, t) {
this.socket && this.socket.send(e);
this.createInter();
}
getMsgData(e) {
if (e && typeof e == "string") {
const status = e.indexOf("42");
const index = e.indexOf("[");
if (status > -1 && index > -1) {
const txt = e.substring(index);
const item = JSON.parse(txt);
const [type, obj] = item;
const payload = obj.payload ? obj.payload : {};
const params = {
chatId: this._options.agentId,
type,
contentType: "text", //默认文字
timestamp: new Date().getTime(),
...payload,
};
// 缓存聊天记录
return params;
}
}
return null;
}
emit(type, params, contentType) {
console.log("emit", type, params);
const data = {
payload: params,
};
switch (type) {
case "send":
// 发送消息
const socketParams = { data: "42" + JSON.stringify(["send", data]) };
this.send(socketParams, contentType);
console.log(params.request_id + '发送内容', data)
if (!params.is_msg_status) {
// 不写入缓存中
return;
}
const msgParams = {
chatId: this._options.agentId,
contentType,
type,
timestamp: new Date().getTime(),
...params,
content: params.realContent ? params.realContent : params.content,
};
msgParams && setMsgData(msgParams);
break;
}
}
// 监听
on(type, params) {
switch (type) {
case "reply":
// 回复结束
const tmpParams = {
...params,
name: this.robotObj.name,
headImage: this.robotObj.headImage,
};
if (
params &&
params.type === "reply" &&
!params.is_from_self &&
params.can_rating
) {
// 回复消息
this._options.onMessage && this._options.onMessage(tmpParams);
}
if (!params.is_from_self && params.is_final) {
// 回复结束 写入缓存
setMsgData(tmpParams);
}
// 回复
break;
}
}
// 关闭socket
destroy() {
if (this.socket && this.socket.readyState == 1) {
this.socket && this.socket.close();
this.socket = null;
}
}
createInter() {
if (this.timeoutObj) {
clearTimeout(this.timeoutObj);
}
this.timeoutObj = setTimeout(() => {
this.socket && this.socket.send && this.socket.send({ data: 3 });
}, HEART_BEAT_TIME);
}
}

119
components/GPT/utils/util.js

@ -0,0 +1,119 @@
/**
* 获取 location hash 参数
* @param {string} variable 参数名
* @returns {string} 参数值
*/
export const getQueryVariable = (variable) => {
const query = window.location.hash.split('?');
if (query.length < 2) {
return '';
}
const vars = query[1].split('&');
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split('=');
if (pair[0] === variable) {
return decodeURI(pair[1]);
}
}
return '';
};
/**
* 滚动至指定dom底部
*/
export const scrollToBottom = (sDom, sTop) => {
if (!sDom) return;
sDom.scrollTo({
top: sTop
// behavior: 'smooth'
});
};
/**
* 数组去重
*/
export const arrayUnique = (arr, replaceKey, holdKey) => {
let temp = {};
return arr.reduce((prev, cur) => {
if (!temp[cur[replaceKey]]) {
temp[cur[replaceKey]] = {index: prev.length};
prev.push(cur);
} else {
const oldItem = temp[cur[replaceKey]];
cur[holdKey] = oldItem[holdKey];
prev.splice(oldItem['index'], 1, cur);
}
return prev;
}, []);
};
export const generateRequestId = (length = 10) => {
const data =
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
let nums = '';
for (let i = 0; i < length; i++) {
const r = parseInt(Math.random() * 61, 10);
nums += data[r];
}
return nums + '-' + parseInt(Math.random() * 10000000000, 10);
};
function escapeHtml (str) {
return str.replace(/[&<>"'/]/g, function (match) {
return {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;'
}[match];
});
}
export const splitTextForTTS = (text, maxLength = 30) => {
// 定义优先分割的标点符号
const punctuation = ['。', ',', ';', '?', '!', ',', ';', '?', '!', '、'];
let segments = [];
let segment = '';
let tempSegment = '';
for (let i = 0; i < text.length; i++) {
tempSegment += text[i]; // 如果超过最大长度,则尝试在上一个标点符号处分割
if (tempSegment.length > maxLength) {
let lastPunctuationIndex = -1;
for (let j = tempSegment.length - 1; j >= 0; j--) {
if (punctuation.includes(tempSegment[j])) {
lastPunctuationIndex = j;
break;
}
} // 如果找到标点符号,则在标点符号后分割
if (lastPunctuationIndex !== -1) {
segments.push(tempSegment.slice(0, lastPunctuationIndex + 1).trim());
tempSegment = tempSegment.slice(lastPunctuationIndex + 1).trim();
} else {
// 如果没有找到标点符号,则在最大长度处分割
segments.push(tempSegment.slice(0, maxLength).trim());
tempSegment = tempSegment.slice(maxLength).trim();
}
}
} // 添加最后一个段落
if (tempSegment.length > 0) {
segments.push(tempSegment.trim());
}
return segments;
}

174
components/ProductSection.vue

@ -4,83 +4,92 @@
<view class="title-section" >
<div style="display: flex; align-items: center;justify-content: space-between;" @click="handleMoreClick">
<text class="title">{{ title }}</text>
<view class="more-btn">更多 <uni-icons size="16" color="#000000" type="arrowright" /></view>
<view class="more-btn" v-if="type!=3">更多 <uni-icons size="16" color="#000000" type="arrowright" /></view>
</div>
</view>
<!-- 商品列表 -->
<view class="product-list">
<view
class="product-card"
v-for="(item, index) in productList"
:key="index"
@click="handleProductClick(item)"
>
<view class="card-image-container">
<image
class="card-image"
:src="showImg(item.image)"
mode="aspectFill"
></image>
<!-- 图片蒙层 -->
<view class="image-overlay" v-if="!isFeel"></view>
<!-- 智能体标签 -->
<view class="content-box-info" v-if="!isFeel&&item.agent" @click.stop="toAgent(item)">
<!-- 头像 -->
<image
class="avatar"
:src="showImg(item.agent.headImage)"
mode="aspectFill"
></image>
<view class="ai-tag">
<view
class="ai-label"
:style="{
borderColor: aiTagBorderColor,
color: aiTagTextColor,
}"
>智能体</view
>
</view>
<view class="ai-name">{{ item.agent.name }}</view>
</view>
</view>
<view class="card-content">
<view class="title-price-heart">
<view class="card-title">{{ item.title }}</view>
<view class="card-price">{{ item.price }}</view>
<template v-if="!isFeel">
<image
v-if="!item.type"
class="heart-icon"
src="https://epic.js-dyyj.com/uploads/20250728/2f3ae212c01fa3b67be81abc5723cf5c.png"
@click.stop="handleLikeClick(item, index)"
></image>
<image
v-else
class="heart-icon"
src="https://epic.js-dyyj.com/uploads/20250728/dd7ed269b24e84a2dd141da6ab980fd6.png"
@click.stop="handleLikeClick(item, index)"
></image>
</template>
<!-- <template v-if="isFeel">
<image
v-if="!item.isShop"
class="shop-icon"
src="https://epic.js-dyyj.com/uploads/20250728/195bfc195a54b93c13595a01a5d8bb3b.png"
@click.stop="handleLikeClick(item, index)"
></image>
<image
v-else
class="shop-icon"
src="https://epic.js-dyyj.com/uploads/20250728/77c4546ac6415f9db69bb10888d2a975.png"
@click.isShop="handleLikeClick(item, index)"
></image>
</template> -->
</view>
</view>
</view>
<view class="" v-if="type==3">
<image
style="height: 357rpx;width: 657rpx;border-radius: 20rpx;"
:src="showImg('/uploads/20250829/66732062c2d566802a7842525e9b92e6.png')"
mode="aspectFill"
></image>
</view>
<template v-else>
<view
class="product-card"
v-for="(item, index) in productList"
:key="index"
@click="handleProductClick(item)"
>
<view class="card-image-container">
<image
class="card-image"
:src="showImg(item.image)"
mode="aspectFill"
></image>
<!-- 图片蒙层 -->
<view class="image-overlay" v-if="type==1"></view>
<!-- 智能体标签 -->
<view class="content-box-info" v-if="item.agent" @click.stop="toAgent(item)">
<!-- 头像 -->
<image
class="avatar"
:src="showImg(item.agent.headImage)"
mode="aspectFill"
></image>
<view class="ai-tag">
<view
class="ai-label"
:style="{
borderColor: aiTagBorderColor,
color: aiTagTextColor,
}"
>智能体</view
>
</view>
<view class="ai-name">{{ item.agent.name }}</view>
</view>
</view>
<view class="card-content">
<view class="title-price-heart">
<view class="card-title">{{ item.title }}</view>
<view class="card-price">{{ item.price }}</view>
<template v-if="type==1">
<image
v-if="!item.type"
class="heart-icon"
src="https://epic.js-dyyj.com/uploads/20250728/2f3ae212c01fa3b67be81abc5723cf5c.png"
@click.stop="handleLikeClick(item, index)"
></image>
<image
v-else
class="heart-icon"
src="https://epic.js-dyyj.com/uploads/20250728/dd7ed269b24e84a2dd141da6ab980fd6.png"
@click.stop="handleLikeClick(item, index)"
></image>
</template>
<!-- <template v-if="isFeel">
<image
v-if="!item.isShop"
class="shop-icon"
src="https://epic.js-dyyj.com/uploads/20250728/195bfc195a54b93c13595a01a5d8bb3b.png"
@click.stop="handleLikeClick(item, index)"
></image>
<image
v-else
class="shop-icon"
src="https://epic.js-dyyj.com/uploads/20250728/77c4546ac6415f9db69bb10888d2a975.png"
@click.isShop="handleLikeClick(item, index)"
></image>
</template> -->
</view>
</view>
</view>
</template>
</view>
</view>
</template>
@ -124,10 +133,11 @@ export default {
type: String,
default: "#02fcfc",
},
isFeel: {
type: Boolean,
default: false,
type: {
type: Number,
default: 1,
},
color: {
type: String,
default: "#D3FCFF",
@ -180,9 +190,17 @@ export default {
},
toAgent(e){
uni.navigateTo({
url: "/pages/agent/index?id="+e.agent.agentId
});
if(e.agentStatus){
uni.navigateTo({
url: "/subPackages/other/evita?id="+e.agent.agentId
});
}else{
uni.showToast({
title:'请先购买当前商品',
icon:'none'
})
}
},
//

555
components/WaterfallLayout.vue

@ -1,320 +1,299 @@
<template>
<view class="waterfall-layout">
<view class="waterfall-container">
<!-- 左列 -->
<view class="column">
<view
v-for="(item, index) in leftItems"
:key="item.id || index"
class="waterfall-item"
@click="handleItemClick(item)"
>
<image
v-if="item.image"
:src="showImg(item.image)"
class="item-image"
mode="aspectFill"
/>
<view class="item-content">
<text v-if="item.title" class="item-title">{{ item.title }}</text>
<view class="item-footer">
<view class="user-info">
<image
src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&w=100"
class="user-avatar"
mode="aspectFill"
/>
<text class="username">风景之旅</text>
</view>
<view class="like-info">
<image :src="showImg('/uploads/20250731/0260884d7a44a483885a026da524e0b8.png')" style="height: 22rpx;width: 25rpx;"></image>
<text class="like-count">100</text>
</view>
</view>
</view>
</view>
</view>
<!-- 右列 -->
<view class="column">
<view
v-for="(item, index) in rightItems"
:key="item.id || index"
class="waterfall-item"
@click="handleItemClick(item)"
>
<image
v-if="item.image"
:src="showImg(item.image)"
class="item-image"
mode="aspectFill"
/>
<view class="item-content">
<text v-if="item.title" class="item-title">{{ item.title }}</text>
<view class="item-footer">
<view class="user-info">
<image
src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&w=100"
class="user-avatar"
mode="aspectFill"
/>
<text class="username">风景之旅</text>
</view>
<view class="like-info">
<image :src="showImg('/uploads/20250731/0260884d7a44a483885a026da524e0b8.png')" style="height: 22rpx;width: 25rpx;"></image>
<text class="like-count">120</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="waterfall-layout">
<view class="waterfall-container">
<!-- 左列 -->
<view class="column">
<view v-for="(item, index) in leftItems" :key="item.id || index" class="waterfall-item"
@click="handleItemClick(item)">
<image v-if="item.image" :src="showImg(item.image)" class="item-image" mode="aspectFill" />
<view class="item-content">
<text v-if="item.title" class="item-title">{{ item.title }}</text>
<view class="item-footer">
<view class="user-info">
<image
src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&w=100"
class="user-avatar" mode="aspectFill" />
<text class="username">风景之旅</text>
</view>
<view class="like-info">
<image :src="showImg('/uploads/20250731/0260884d7a44a483885a026da524e0b8.png')"
style="height: 22rpx;width: 25rpx;"></image>
<text class="like-count">100</text>
</view>
</view>
</view>
</view>
</view>
<!-- 右列 -->
<view class="column">
<view v-for="(item, index) in rightItems" :key="item.id || index" class="waterfall-item"
@click="handleItemClick(item)">
<image v-if="item.image" :src="showImg(item.image)" class="item-image" mode="aspectFill" />
<view class="item-content">
<text v-if="item.title" class="item-title">{{ item.title }}</text>
<view class="item-footer">
<view class="user-info">
<image
src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&w=100"
class="user-avatar" mode="aspectFill" />
<text class="username">风景之旅</text>
</view>
<view class="like-info">
<image :src="showImg('/uploads/20250731/0260884d7a44a483885a026da524e0b8.png')"
style="height: 22rpx;width: 25rpx;"></image>
<text class="like-count">120</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "WaterfallLayout",
props: {
//
items: {
type: Array,
default: () => [],
},
// 2
columnCount: {
type: Number,
default: 2,
},
// rpx
columnGap: {
type: Number,
default: 16,
},
// rpx
itemGap: {
type: Number,
default: 16,
},
},
data() {
return {
leftItems: [],
rightItems: [],
};
},
watch: {
items: {
handler(newItems) {
this.calculateLayout(newItems);
},
immediate: true,
deep: true,
},
},
mounted() {
this.calculateLayout(this.items);
},
methods: {
// DOM
getColumnHeight(columnRef) {
if (!columnRef) return 0;
const query = uni.createSelectorQuery().in(this);
return new Promise((resolve) => {
query.select(columnRef).boundingClientRect((data) => {
resolve(data ? data.height : 0);
}).exec();
});
},
export default {
name: "WaterfallLayout",
props: {
//
items: {
type: Array,
default: () => [],
},
// 2
columnCount: {
type: Number,
default: 2,
},
// rpx
columnGap: {
type: Number,
default: 16,
},
// rpx
itemGap: {
type: Number,
default: 16,
},
},
data() {
return {
leftItems: [],
rightItems: [],
};
},
watch: {
items: {
handler(newItems) {
this.calculateLayout(newItems);
},
immediate: true,
deep: true,
},
},
mounted() {
this.calculateLayout(this.items);
},
methods: {
// DOM
getColumnHeight(columnRef) {
if (!columnRef) return 0;
const query = uni.createSelectorQuery().in(this);
return new Promise((resolve) => {
query.select(columnRef).boundingClientRect((data) => {
resolve(data ? data.height : 0);
}).exec();
});
},
//
calculateLayout(items) {
if (!items || !items.length) {
this.leftItems = [];
this.rightItems = [];
return;
}
//
calculateLayout(items) {
if (!items || !items.length) {
this.leftItems = [];
this.rightItems = [];
return;
}
//
this.leftItems = [];
this.rightItems = [];
//
this.leftItems = [];
this.rightItems = [];
//
for (let i = 0; i < items.length; i++) {
this.addItem(items[i]);
}
},
//
for (let i = 0; i < items.length; i++) {
this.addItem(items[i]);
}
},
//
addItem(item) {
//
if (this.leftItems.length <= this.rightItems.length) {
this.leftItems.push(item);
} else {
this.rightItems.push(item);
}
},
//
addItem(item) {
//
if (this.leftItems.length <= this.rightItems.length) {
this.leftItems.push(item);
} else {
this.rightItems.push(item);
}
},
//
clearItems() {
this.leftItems = [];
this.rightItems = [];
this.$emit("items-cleared");
},
//
clearItems() {
this.leftItems = [];
this.rightItems = [];
this.$emit("items-cleared");
},
//
handleItemClick(item) {
this.$emit("item-click", item);
},
//
handleItemClick(item) {
this.$emit("item-click", item);
},
//
getAllItems() {
return [...this.leftItems, ...this.rightItems];
},
//
getAllItems() {
return [...this.leftItems, ...this.rightItems];
},
//
removeItem(itemId) {
//
let index = this.leftItems.findIndex(item => item.id === itemId);
if (index !== -1) {
this.leftItems.splice(index, 1);
this.$emit("item-removed", itemId);
return;
}
//
removeItem(itemId) {
//
let index = this.leftItems.findIndex(item => item.id === itemId);
if (index !== -1) {
this.leftItems.splice(index, 1);
this.$emit("item-removed", itemId);
return;
}
//
index = this.rightItems.findIndex(item => item.id === itemId);
if (index !== -1) {
this.rightItems.splice(index, 1);
this.$emit("item-removed", itemId);
}
},
},
};
//
index = this.rightItems.findIndex(item => item.id === itemId);
if (index !== -1) {
this.rightItems.splice(index, 1);
this.$emit("item-removed", itemId);
}
},
},
};
</script>
<style scoped>
.waterfall-layout {
width: 100%;
box-sizing: border-box;
}
.waterfall-layout {
width: 100%;
box-sizing: border-box;
}
.waterfall-container {
display: flex;
gap: 16rpx;
padding: 0 20rpx;
box-sizing: border-box;
}
.waterfall-container {
display: flex;
gap: 16rpx;
padding: 0 20rpx;
box-sizing: border-box;
}
.column {
flex: 1;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.column {
flex: 1;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.waterfall-item {
box-sizing: border-box;
border-radius: 12rpx;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
transition: transform 0.2s ease;
}
.waterfall-item {
box-sizing: border-box;
border-radius: 12rpx;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
transition: transform 0.2s ease;
}
.waterfall-item:active {
transform: scale(0.98);
}
.waterfall-item:active {
transform: scale(0.98);
}
.item-image {
width: 100%;
height: 476rpx;
object-fit: cover;
}
.item-image {
width: 100%;
height: 476rpx;
object-fit: cover;
}
.item-content {
padding: 16rpx;
}
.item-content {
padding: 16rpx;
}
.item-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
line-height: 1.3;
margin-bottom: 12rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.item-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
line-height: 1.3;
margin-bottom: 12rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.item-desc {
font-size: 24rpx;
color: #666;
line-height: 1.4;
margin-bottom: 16rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.item-desc {
font-size: 24rpx;
color: #666;
line-height: 1.4;
margin-bottom: 16rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.item-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-bottom: 16rpx;
}
.item-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-bottom: 16rpx;
}
.tag {
padding: 4rpx 12rpx;
background: #f5f5f5;
color: #666;
font-size: 20rpx;
border-radius: 12rpx;
white-space: nowrap;
}
.tag {
padding: 4rpx 12rpx;
background: #f5f5f5;
color: #666;
font-size: 20rpx;
border-radius: 12rpx;
white-space: nowrap;
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
}
.user-info {
display: flex;
align-items: center;
gap: 12rpx;
}
.user-info {
display: flex;
align-items: center;
gap: 12rpx;
}
.user-avatar {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
}
.user-avatar {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
}
.username {
font-size: 22rpx;
color: #666;
}
.username {
font-size: 22rpx;
color: #666;
}
.like-info {
display: flex;
align-items: center;
gap: 6rpx;
}
.like-info {
display: flex;
align-items: center;
gap: 6rpx;
}
.like-icon {
font-size: 24rpx;
color: #ff6b6b;
}
.like-icon {
font-size: 24rpx;
color: #ff6b6b;
}
.like-count {
font-size: 22rpx;
color: #666;
}
</style>
.like-count {
font-size: 22rpx;
color: #666;
}
</style>

3
manifest.json

@ -64,7 +64,8 @@
}
},
"lazyCodeLoading" : "requiredComponents",
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ],
"requiredBackgroundModes" : [ "audio" ]
},
"mp-alipay" : {
"usingComponents" : true

43
pages/agent/index.vue

@ -1,8 +1,45 @@
<template>
<view class="content">
<GPT :agentId="agentId"></GPT>
</view>
</template>
<script>
import GPT from '@/components/GPT/index.vue'
export default {
components: {
GPT,
},
data() {
return {
agentId:1
}
},
onLoad(option) {
const app = getApp();
const bgMusic = app.globalData.bgMusic;
bgMusic.pause();
if(option){
this.agentId = option.id
}
},
onLaunch: options => {
this.options = options
},
onShow() {
console.log('【init message connect type------>】', );
},
methods: {
},
}
</script>
<style scoped lang="scss">
.content {
height: 100vh;
}
<style>
</style>
</style>

3196
pages/index/iSoul.vue

File diff suppressed because it is too large

12
pages/index/index.vue

@ -27,6 +27,7 @@
detailUrlPrefix="/subPackages/equityGoods/detail"
@like-toggle="handleLikeToggle"
color="#D2FFDE"
:type="1"
/>
<ProductSection
@ -38,9 +39,18 @@
moreUrl="/pages/index/sensoryStore"
detailUrlPrefix="/subPackages/techan/detail"
@like-toggle="handleLikeToggle"
isFeel
:type="2"
color="#D3FCFF"
/>
<ProductSection
titleBgColor="#92FF8F"
aiTagTextColor="#08FB05"
aiTagBorderColor="#6EAA3D"
title="CGC-ICH 大运河非物质文化遗产"
@like-toggle="handleLikeToggle"
:type="3"
color="#FFEAD2"
/>
</view>
<CustomTabBar :currentTab="0" />

851
pages/index/readingBody.vue

@ -1,17 +1,87 @@
<template>
<view class="bg">
<headerVue :type="'article'" />
<view class="start-swiper" v-show="isShow && startSwiper && startSwiper.length > 0" @click="this.isShow = false">
<swiper class="start-swiper" :indicator-dots="false" :autoplay="true" :interval="5000" :duration="500"
@change="onSwiperChange">
<swiper-item v-for="(item, index) in startSwiper" :key="index">
<image :src="showImg(item.head_img)" mode="aspectFill"></image>
</swiper-item>
</swiper>
</view>
<view class="bg">
<headerVue :type="'article'" />
<view class="category-scroll">
<video
src="https://static.ticket.sz-trip.com/epicSoul/categorys.mp4"
objectFit="cover"
></video>
</view>
<view class="title-box-about">
关于阅读题
<image
src="https://des.js-dyyj.com/data/2025/08/29/a1bfbd3e-f63c-4dea-8aa6-9008bcf129c6.png"
mode=""
>
</image>
</view>
<view
class="start-swiper"
v-show="isShow && startSwiper && startSwiper.length > 0"
@click="this.isShow = false"
>
<swiper
class="start-swiper"
:indicator-dots="false"
:autoplay="true"
:interval="5000"
:duration="500"
@change="onSwiperChange"
>
<swiper-item v-for="(item, index) in startSwiper" :key="index">
<image :src="showImg(item.head_img)" mode="aspectFill"></image>
</swiper-item>
</swiper>
</view>
<view class="desc-box">
<view class="">
在这里你的每一次人文漫游每一次灵感闪现都值得被郑重记录我们鼓励你分享高质量的图文笔记将旅途中的美与感动化为这座精神星球上的璀璨星辰
</view>
<view class="">
Epic
soul交响阅读体以数字文化资产IP为核心,以消费产品创新为导向,每个生命都有史诗般的灵魂作为创作宗旨,通过文字图像音视频交互式展览等多种技术工具创作完成的轻量化数字文化内容产品,邀您一起收藏这颗星球所有未被传唱的英雄史诗
</view>
<view class="">
Epic
soul交响阅读体将长期关注乡村振兴城市更新文化焕新等领域,践行探索未来文化信息的解构分发和互动方式
</view>
</view>
<image
style="width: 700rpx; height: 14rpx; margin: 80rpx auto; display: block"
:src="showImg('/uploads/20250829/f7214bc2a4f4e236561de893ca7b9113.png')"
></image>
<view class="title-box">
阅读合集
<image
src="https://static.ticket.sz-trip.com/epicSoul/readingBody/titleSpan.png"
mode=""
></image>
</view>
<!-- <view class="top-box">
<image
style="width: 700rpx; height: 23.5rpx; margin: 30rpx auto; display: block"
src="https://des.js-dyyj.com/data/2025/08/29/ee445087-f64d-40a1-861b-586d1a9c7e31.png"
></image>
<view class="new-book-body" v-if="readingList">
<view
class="newBookCard"
style=""
:style="{ backgroundImage: `url(${showImg(readingList[0].image)})` }"
>
</view>
<!-- <image
style="width: 583rpx; height: 706rpx"
mode="aspectFill"
:src="showImg(readingList[0].image)"
></image> -->
<view class="new-book-body-title" style="">
今夜我们都有一艘秘密飞船
</view>
<view class="new-book-body-desc">
本期由DAYUN远见文化工厂与吴文化博物馆联合推出将其馆藏文物元代朱碧山造银槎杯活化为数字IP这不仅是一次文物活化的深度实践更是一份献给现代人的精神远航指南邀你随时登船自在漂浮
</view>
</view>
<!-- <view class="top-box">
<swiper class="carousel-swiper" :indicator-dots="false" :autoplay="true" :interval="5000" :duration="500"
@change="onSwiperChange">
<swiper-item v-for="(item, index) in swiperList" :key="index">
@ -25,331 +95,470 @@
:class="['indicator-dot', {'active': index === currentIndex}]"></view>
</view>
</view> -->
<uni-swiper-dot class="uni-swiper-dot-box" :info="swiperList" :current="current" :mode="mode"
<!-- <uni-swiper-dot class="uni-swiper-dot-box" :info="swiperList" :current="current" :mode="mode"
:dots-styles="dotsStyles" field="content">
<swiper class="swiper-box" @change="change" :current="swiperDotIndex" :style="{height : swiperHeight + 'px'}">
<swiper-item v-for="(item, index) in swiperList" :key="index" @click="gotoUrlNew(item)">
<image :src="showImg(item.head_img)" mode="widthFix" :id="`swiper-item-${index}`"></image>
</swiper-item>
</swiper>
</uni-swiper-dot>
<!-- <swiper class="category-scroll">
</uni-swiper-dot> -->
<image
style="
width: 700rpx;
height: 23.5rpx;
margin: 50rpx auto 30rpx;
display: block;
"
src="https://des.js-dyyj.com/data/2025/08/29/d23d170b-9ae8-4306-a6f3-193327d844f8.png"
></image>
<!-- <swiper class="category-scroll">
<swiper-item v-for="(item, index) in categories" :key="index" @click="gotoUrlNew(item)">
<image :src="showImg(item.head_img)" mode="aspectFill"></image>
</swiper-item>
</swiper> -->
<!-- <view class="category-scroll" v-if="categories && categories.length > 0">
<!-- <view class="category-scroll" v-if="categories && categories.length > 0">
<video src="https://static.ticket.sz-trip.com/epicSoul/category.mp4" :poster="showImg(categories[0].head_img)" objectFit="cover"></video>
</view> -->
<view class="category-scroll">
<video src="https://static.ticket.sz-trip.com/epicSoul/categorys.mp4" objectFit="cover"></video>
</view>
<view class="title-box">
阅读合集
<image src="https://static.ticket.sz-trip.com/epicSoul/readingBody/titleSpan.png" mode=""></image>
</view>
<view class="reading-title flex-between" v-if="false">
<!-- <view v-for="(item,index) in readingType" :key="index" @click="changeType(index)">{{item.title}}</view>
<view class="reading-title flex-between" v-if="false">
<!-- <view v-for="(item,index) in readingType" :key="index" @click="changeType(index)">{{item.title}}</view>
<span :style="{left: readingIndex == 0 ? '32rpx' : (readingIndex == 1 ? '245rpx' : '448rpx')}"></span> -->
</view>
<view class="reading-box">
<image v-for="(item,index) in readingList" :key="index" :src="showImg(item.image)" @click="gotoUrlNew(item)"></image>
</view>
<CustomTabBar :currentTab="1" />
<MusicControl />
</view>
</view>
<view class="reading-box">
<view
class="reading-box-item"
v-for="(item, index) in readingList.slice(1)"
v-if="item.image && item.front_model && item.front_model.mini"
:key="index"
>
<image
mode="aspectFill"
:src="showImg(item.image)"
@click="gotoUrlNew(item)"
></image>
<view class="reading-box-item-title">
{{ item.title }}
</view>
<view class="reading-box-item-desc">
{{ descList[index] }}
</view>
</view>
</view>
<CustomTabBar :currentTab="1" />
<MusicControl />
</view>
</template>
<script>
import headerVue from "@/components/header.vue";
import CustomTabBar from '@/components/CustomTabBar.vue';
import MusicControl from '@/components/MusicControl.vue';
export default {
components: {
headerVue,
CustomTabBar,
MusicControl
},
data() {
return {
swiperList: [],
current: 0,
swiperDotIndex: 0,
swiperHeight: 524,
mode: 'round',
dotsStyles: {
selectedBackgroundColor: 'rgb(133, 133, 133)',
selectedBorder: 'none',
backgroundColor: 'rgba(150, 150, 150, 0.5)',
border: 'none'
},
currentIndex: 0,
categories: [],
readingList: [],
readingType: [{title: '2025', id: 3}, {title: '2026', id: 6}, {title: '全部', id: ''},],
readingIndex: 0,
startSwiper: [],
isShow: true
}
},
onReady() {
this.sendRequest()
},
onShow() {
const app = getApp();
if(app.globalData.musicSrc != 'https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3') {
app.updateMusicSrc('https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3');
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
}
},
methods: {
sendRequest() {
this.Post({
type_id: 3,
position: 2,
}, '/api/adv/getAdv').then(res => {
if(res.data) {
this.swiperList = res.data;
setTimeout(() => {
this.$nextTick(() => {
this.change({detail: {current: 0}})
})
},500)
}
});
this.Post({
type_id: 3,
position: 1,
}, '/api/adv/getAdv').then(res => {
if(res.data) {
this.categories = res.data;
}
});
this.Post({
type_id: 3,
position: 4,
}, '/api/adv/getAdv').then(res => {
if(res.data) {
this.startSwiper = res.data;
}
});
this.getList()
},
//
getList() {
this.Post({
type_id: this.readingType[this.readingIndex].id,
offset: 0,
limit: 1
}, '/api/article/getArticleByType').then(res => {
if(res.data) {
this.readingList = res.data
}
})
},
change(e) {
this.current = e.detail.current
let element = '#swiper-item-' + e.detail.current
let query = uni.createSelectorQuery().in(this)
query.select(element).boundingClientRect()
query.exec((res) => {
if (res && res[0]) {
this.swiperHeight = res[0].height
}
})
},
onSwiperChange(e) {
this.currentIndex = e.detail.current;
},
changeType(index) {
this.readingIndex = index
this.getList()
},
viewDetail(item) {
if (item.url) {
uni.navigateTo({
url:"/subPackages/webPage/webPage?url="+encodeURIComponent(item.url)
})
return
}
uni.navigateTo({
url:'/subPackages/letter/detail?id='+item.id
})
}
}
}
import headerVue from "@/components/header.vue";
import CustomTabBar from "@/components/CustomTabBar.vue";
import MusicControl from "@/components/MusicControl.vue";
export default {
components: {
headerVue,
CustomTabBar,
MusicControl,
},
data() {
return {
swiperList: [],
current: 0,
swiperDotIndex: 0,
swiperHeight: 524,
mode: "round",
dotsStyles: {
selectedBackgroundColor: "rgb(133, 133, 133)",
selectedBorder: "none",
backgroundColor: "rgba(150, 150, 150, 0.5)",
border: "none",
},
currentIndex: 0,
categories: [],
readingList: [],
readingType: [
{
title: "2025",
id: 3,
},
{
title: "2026",
id: 6,
},
{
title: "全部",
id: "",
},
],
readingIndex: 0,
startSwiper: [],
isShow: true,
descList: [
"颠覆“笨猪”的刻板印象,讲述一段关于文明、家国、美味与勇气的趣味进化史。为新中式烘焙品牌圣百合提供文化寻根的灵感,让最寻常的食物也能讲述不凡的故事。",
"以一根不眠之“丝”为线索,展现其从天虫吐丝的生命史诗,到织就华夏文明经纬的惊人跨度。致力于让古老的丝绸非遗,重焕新生,成为可被穿戴的东方美学。",
"以一缕千年柏香为引,探索人类被低估的“嗅觉指纹” 。通过对千年古柏背后人文风骨的挖掘,为新中式香氛产业提供深度文化叙事,将无形的意境转化为可被感知的灵魂香气。",
"以“三个桃子”数字IP为核心,追溯一颗果实260万年的迁徙史与文化象征,赋能国家地理标志农产品,将一颗普通的果实,塑造成蕴含中国桃园江湖情义的味觉与美学信物 。",
],
};
},
onReady() {
this.sendRequest();
},
onShow() {
const app = getApp();
if (
app.globalData.musicSrc !=
"https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3"
) {
app.updateMusicSrc(
"https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3"
);
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
}
},
methods: {
sendRequest() {
this.Post(
{
type_id: 3,
position: 2,
},
"/api/adv/getAdv"
).then((res) => {
if (res.data) {
this.swiperList = res.data;
setTimeout(() => {
this.$nextTick(() => {
this.change({
detail: {
current: 0,
},
});
});
}, 500);
}
});
this.Post(
{
type_id: 3,
position: 1,
},
"/api/adv/getAdv"
).then((res) => {
if (res.data) {
this.categories = res.data;
}
});
this.Post(
{
type_id: 3,
position: 4,
},
"/api/adv/getAdv"
).then((res) => {
if (res.data) {
this.startSwiper = res.data;
}
});
this.getList();
},
//
getList() {
this.Post(
{
type_id: this.readingType[this.readingIndex].id,
offset: 0,
limit: 1,
},
"/api/article/getArticleByType"
).then((res) => {
if (res.data) {
this.readingList = res.data;
}
});
},
change(e) {
this.current = e.detail.current;
let element = "#swiper-item-" + e.detail.current;
let query = uni.createSelectorQuery().in(this);
query.select(element).boundingClientRect();
query.exec((res) => {
if (res && res[0]) {
this.swiperHeight = res[0].height;
}
});
},
onSwiperChange(e) {
this.currentIndex = e.detail.current;
},
changeType(index) {
this.readingIndex = index;
this.getList();
},
viewDetail(item) {
if (item.url) {
uni.navigateTo({
url:
"/subPackages/webPage/webPage?url=" + encodeURIComponent(item.url),
});
return;
}
uni.navigateTo({
url: "/subPackages/letter/detail?id=" + item.id,
});
},
},
};
</script>
<style lang="scss" scoped>
.bg {
padding-bottom: 200rpx;
}
.swiper-box, .top-box {
margin: 60rpx 25rpx 0;
height: 830rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
border-radius: 20rpx;
overflow: hidden;
// padding-top: 10rpx;
// background-color: #fff;
image {
width: 100%;
height: 100%;
}
}
.carousel-swiper {
width: 582.13rpx;
height: 706.24rpx;
margin: -40rpx auto 0;
image {
width: 100%;
height: 100%;
}
}
.exhibit-issue {
font-family: serif;
font-size: 28rpx;
color: #999;
font-style: italic;
padding: 40rpx 30rpx 0 0;
text-align: right;
}
.custom-indicators {
display: flex;
justify-content: center;
gap: 8rpx;
margin-top: 40rpx;
.indicator-dot {
width: 8rpx;
height: 8rpx;
border-radius: 4rpx;
background-color: rgba(150, 150, 150, 0.5);
transition: all 0.3s;
}
.indicator-dot.active {
width: 20rpx;
background-color: rgb(133, 133, 133);
}
}
.category-scroll {
// padding: 40rpx 0 0 40rpx;
// display: flex;
height: 393rpx;
margin: 50rpx 25rpx 0;
image {
width: 100%;
height: 393rpx;
flex-shrink: 0;
// margin-right: 20rpx;
border-radius: 20rpx;
}
video {
width: 100%;
height: 393rpx;
border-radius: 20rpx;
}
}
.title-box {
font-size: 36rpx;
font-weight: 500;
margin-top: 40rpx;
padding-left: 60rpx;
image {
width: 185.85rpx;
height: 20.98rpx;
margin-left: 21rpx;
}
}
.reading-title {
margin: 60rpx auto 0;
width: 585rpx;
height: 30rpx;
border-radius: 15rpx;
background-color: rgba(46, 220, 78, .8);
font-weight: bold;
font-size: 28rpx;
color: #000000;
padding: 0 40rpx;
position: relative;
view {
position: absolute;
z-index: 20;
bottom: 15rpx;
}
&>view:nth-child(2) {
left: 268rpx;
}
&>view:nth-child(3) {
right: 55rpx;
}
span {
width: 111rpx;
height: 100%;
background-color: rgb(9, 154, 60);
border-radius: 15rpx;
position: absolute;
left: 32rpx;
}
}
.reading-box {
margin: 20rpx 25rpx 0;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
padding: 35rpx 10rpx 5rpx;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
position: relative;
// top: -15rpx;
background-color: #fff;
image {
width: 212rpx;
height: 376rpx;
margin-bottom: 30rpx;
}
}
.start-swiper {
width: 100vw;
height: calc(100vh - 123rpx);
position: fixed;
top: 0;
left: 0;
z-index: 100;
image {
width: 100%;
height: 100%;
}
}
</style>
.bg {
padding-bottom: 200rpx;
}
.swiper-box,
.top-box {
margin: 10rpx 25rpx 0;
height: 830rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
border-radius: 20rpx;
overflow: hidden;
// padding-top: 10rpx;
// background-color: #fff;
image {
width: 100%;
height: 100%;
}
}
.carousel-swiper {
width: 582.13rpx;
height: 706.24rpx;
margin: -40rpx auto 0;
image {
width: 100%;
height: 100%;
}
}
.exhibit-issue {
font-family: serif;
font-size: 28rpx;
color: #999;
font-style: italic;
padding: 40rpx 30rpx 0 0;
text-align: right;
}
.custom-indicators {
display: flex;
justify-content: center;
gap: 8rpx;
margin-top: 40rpx;
.indicator-dot {
width: 8rpx;
height: 8rpx;
border-radius: 4rpx;
background-color: rgba(150, 150, 150, 0.5);
transition: all 0.3s;
}
.indicator-dot.active {
width: 20rpx;
background-color: rgb(133, 133, 133);
}
}
.category-scroll {
// padding: 40rpx 0 0 40rpx;
// display: flex;
height: 393rpx;
margin: 50rpx 25rpx 0;
image {
width: 100%;
height: 393rpx;
flex-shrink: 0;
// margin-right: 20rpx;
border-radius: 20rpx;
}
video {
width: 100%;
height: 393rpx;
border-radius: 20rpx;
}
}
.title-box {
font-size: 36rpx;
font-weight: 500;
margin-top: 40rpx;
padding-left: 25rpx;
image {
width: 185.85rpx;
height: 20.98rpx;
margin-left: 21rpx;
}
}
.title-box-about {
font-size: 36rpx;
font-weight: 500;
margin-top: 50rpx;
padding-left: 25rpx;
image {
width: 106.5rpx;
height: 20.98rpx;
margin-left: 21rpx;
}
}
.reading-title {
margin: 60rpx auto 0;
width: 585rpx;
height: 30rpx;
border-radius: 15rpx;
background-color: rgba(46, 220, 78, 0.8);
font-weight: bold;
font-size: 28rpx;
color: #000000;
padding: 0 40rpx;
position: relative;
view {
position: absolute;
z-index: 20;
bottom: 15rpx;
}
& > view:nth-child(2) {
left: 268rpx;
}
& > view:nth-child(3) {
right: 55rpx;
}
span {
width: 111rpx;
height: 100%;
background-color: rgb(9, 154, 60);
border-radius: 15rpx;
position: absolute;
left: 32rpx;
}
}
.reading-box {
margin: 20rpx 25rpx 0;
padding: 35rpx 10rpx 5rpx;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
position: relative;
// top: -15rpx;
gap: 16rpx;
image {
width: 100%;
height: 346rpx;
}
.reading-box-item {
flex: 0 0 calc(50% - 8rpx);
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
background-color: #fff;
padding: 20rpx;
font-size: 30rpx;
}
.reading-box-item-title {
margin: 15rpx 0;
font-weight: bold;
}
.reading-box-item-desc {
font-size: 20rpx;
color: #808080;
}
}
.start-swiper {
width: 100vw;
height: calc(100vh - 123rpx);
position: fixed;
top: 0;
left: 0;
z-index: 100;
image {
width: 100%;
height: 100%;
}
}
.desc-box {
padding: 0 25rpx;
color: #616161;
margin: 30rpx 0;
font-size: 22rpx;
view {
margin-bottom: 20rpx;
}
}
.new-book-body {
margin: 20rpx 25rpx 0;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
padding: 50rpx 0rpx 30rpx;
background-color: #fff;
text-align: left;
image {
margin: 0 auto;
display: block;
}
.new-book-body-title {
width: 583rpx;
font-size: 37rpx;
font-weight: bold;
margin: 20rpx auto;
}
.new-book-body-desc {
width: 583rpx;
color: #808080;
font-size: 24rpx;
margin: 20rpx auto;
}
}
.newBookCard {
width: 583rpx;
height: 706rpx;
margin: 0 auto;
background-size: 100% auto;
background-repeat: no-repeat;
background-position: top;
background-color: #fff;
background-image: url(https://des.js-dyyj.com/data/2025/08/29/a1a0009c-9248-48e3-b5a7-5d2970a1eb19.png);
}
</style>

4
pages/index/sensoryStore.vue

@ -1,5 +1,6 @@
<template>
<view class="bg">
<BackButton />
<!-- <headerVue :type="'goods'"></headerVue> -->
<view class="banner-content">
@ -51,8 +52,9 @@
import headerVue from "@/components/header.vue"
import CustomTabBar from '@/components/CustomTabBar.vue';
import MusicControl from '@/components/MusicControl.vue';
import BackButton from "@/components/BackButton.vue";
export default {
components: {CustomTabBar,headerVue,MusicControl},
components: {CustomTabBar,headerVue,MusicControl,BackButton},
data() {
return {
topBanner: [],

34
pages/index/timeShopBank.vue

@ -9,6 +9,22 @@
:page-id="'timeShopBank_page'"
:style-type="'timeShop'"
/>
<view class="desc-box">
<view class="">
欢迎来到旅行时间行你的精神财富储蓄所
</view>
<view class="">
在这里你的每一次人文漫游每一次灵感闪现都值得被郑重记录我们鼓励你分享高质量的图文笔记将旅途中的美与感动化为这座精神星球上的璀璨星辰
</view>
<view class="">
为他人的美好驻足点赞留言,每一次真诚的互动,都是在为你的时间银行存入-笔宝贵的精神货币这些资产不仅可以兑换独家福利与实体好物,更能为你解锁专属的荣誉身份,让你成为这座星球上最闪耀的共创者
</view>
<view class="">
即刻发布你的第一篇笔记,开启你的财富积累之旅吧!
</view>
</view>
<image style="width: 700rpx;height: 14rpx;margin: 20rpx auto;display: block;" :src="showImg('/uploads/20250829/f7214bc2a4f4e236561de893ca7b9113.png')"></image>
<!-- Tab切换组件 -->
<view class="tab-container">
@ -187,7 +203,7 @@ export default {
{};
},
onShow() {
if (this.userInfo && this.userInfo.token) {
if (this.userInfo && this.userInfo.id) {
this.getUserInfo();
}
},
@ -374,7 +390,7 @@ page {
.tab-container {
background: #ffffff;
padding: 0 32rpx;
margin-top: 40rpx;
margin-top: 20rpx;
margin-bottom: 20rpx;
}
@ -492,6 +508,20 @@ page {
color: #999;
}
}
.desc-box{
padding: 0 20rpx;
color: #616161;
margin: 30rpx 0;
font-size: 22rpx;
view{
margin-bottom: 20rpx;
&:nth-child(1){
font-size: 24rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
}
}
/* 自定义样式已移至WaterfallLayout组件内部 */
</style>

2
pages/notes/detail.vue

@ -433,7 +433,7 @@ padding: 12rpx 30rpx;
font-size: 30rpx;
line-height: 1.6;
color: #333;
font-weight: bold;
font-weight: 500;
}
}

5
pages/stratIndex.vue

@ -18,7 +18,8 @@
},
data() {
return {
screenPng: ""
screenPng: "",
count:1
}
},
@ -26,6 +27,7 @@
},
onReady () {
//
this.Post({
type_id: 3,
@ -44,6 +46,7 @@
const app = getApp();
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
},
methods: {
goIndex () {

BIN
static/imgs/icon-bf-active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
static/imgs/icon-bf.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
static/imgs/icon-copy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
static/imgs/icon-pic.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
static/imgs/icon-txt.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
static/imgs/icon-video.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
static/imgs/more.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

2
static/js/request.js

@ -8,7 +8,7 @@ const DEV_API_URL = 'https://epic.new.js-dyyj.com';
// const PROD_API_URL = 'https://epic.js-dyyj.com';
const PROD_API_URL = 'https://epic.new.js-dyyj.com';
const NEWAPIURL = process.env.NODE_ENV === 'development' ? DEV_API_URL : PROD_API_URL;
const DEV_API_URL_DES = 'http://192.168.124.118:8083/xcx';
const DEV_API_URL_DES = 'http://192.168.124.177:8083/xcx';
// const DEV_API_URL_DES = 'https://des.js-dyyj.com/xcx';
const PROD_API_URL_DES = 'https://des.js-dyyj.com/xcx';
const NEWAPIURL_DES = process.env.NODE_ENV === 'development' ? DEV_API_URL_DES : PROD_API_URL_DES;

1586
subPackages/equityGoods/detail.vue

File diff suppressed because it is too large

31
subPackages/other/evita.vue

@ -2,8 +2,7 @@
<template>
<view class="" style="font-size: 0;position: relative;">
<BackButton />
<image class="bannerImg" mode="widthFix" :src="showImg('/uploads/20250825/a5e4ad2654df63d751ffd547e3359776.png')"> </image>
<image @click="toWebView" mode="aspectFill" style="width: 147rpx;height: 147rpx;position: absolute;bottom: 300rpx;left: 301rpx;" :src="showImg('/uploads/20250825/688bc64132881464a23b44ecbc66ec85.png')"> </image>
<image v-if="agentInfo" @click="toWebView" class="bannerImg" mode="aspectFill" :src="showImg(agentInfo.posterUrl)"> </image>
</view>
</template>
@ -16,15 +15,36 @@
},
data(){
return{
img:''
img:'',
agentId:'',
agentInfo:null
}
},
onLoad() {
onLoad(e) {
this.agentId = e.id
this.getInfo()
},
methods:{
getInfo(){
this.Post(
{
},
"/framework/agent/"+this.agentId,
"DES"
).then((res) => {
if (res.code == 200) {
this.agentInfo = res.data
} else {
uni.showToast({
title: res.msg,
icon: "none",
});
}
});
},
toWebView() {
uni.navigateTo({
url: "/pages/agent/index"
url: "/pages/agent/index?id="+this.agentId
});
},
@ -35,5 +55,6 @@
<style lang="scss" scoped>
.bannerImg{
width: 100vw;
height: 100vh;
}
</style>
Loading…
Cancel
Save