Browse Source

差音频和不同问题不同图表,充值还需再检查检查,其他完成了

hxl
no99 3 months ago
parent
commit
5df473ac82
  1. 1
      README.md
  2. 40
      package-lock.json
  3. 1
      package.json
  4. BIN
      src/assets/img/Feedback/border.png
  5. BIN
      src/assets/img/Feedback/failure.png
  6. BIN
      src/assets/img/Feedback/save.png
  7. BIN
      src/assets/img/Feedback/success.png
  8. 1
      src/store/audio.js
  9. 140
      src/views/AIchat.vue
  10. 50
      src/views/Announcement.vue
  11. 256
      src/views/Feedback.vue
  12. 32
      src/views/homePage.vue

1
README.md

@ -17,3 +17,4 @@ npm install @coze/api
npm install html-to-text
npm install echarts
npm install lodash 安装 lodash 组件,解决数据处理问题
npm install vue-device-detect 安装 vue-device-detect 组件,解决移动端适配问题

40
package-lock.json

@ -28,6 +28,7 @@
"reset-css": "^5.0.2",
"vconsole": "^3.15.1",
"vue": "^3.2.26",
"vue-device-detect": "^1.0.3",
"vue-i18n": "^10.0.3",
"vue-request": "^1.2.3",
"vue-router": "^4.0.12"
@ -6692,6 +6693,32 @@
"node": ">=4.2.0"
}
},
"node_modules/ua-parser-js": {
"version": "1.0.40",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/ua-parser-js/-/ua-parser-js-1.0.40.tgz",
"integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"license": "MIT",
"bin": {
"ua-parser-js": "script/cli.js"
},
"engines": {
"node": "*"
}
},
"node_modules/ufo": {
"version": "1.5.4",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/ufo/-/ufo-1.5.4.tgz",
@ -7687,6 +7714,19 @@
}
}
},
"node_modules/vue-device-detect": {
"version": "1.0.3",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/vue-device-detect/-/vue-device-detect-1.0.3.tgz",
"integrity": "sha512-3DzdW88bHReUS9hUaqXvUXFmpa9QsZ0Dmo8EjvteBU1OoaDP9jwEj+LvCXjYSzGBC1fEylTHoOXwvqj4jwBHOQ==",
"license": "ISC",
"dependencies": {
"ua-parser-js": "^1.0.2",
"vue-device-detect": "^1.0.0"
},
"peerDependencies": {
"vue": "^3.2.32"
}
},
"node_modules/vue-eslint-parser": {
"version": "8.3.0",
"resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",

1
package.json

@ -35,6 +35,7 @@
"reset-css": "^5.0.2",
"vconsole": "^3.15.1",
"vue": "^3.2.26",
"vue-device-detect": "^1.0.3",
"vue-i18n": "^10.0.3",
"vue-request": "^1.2.3",
"vue-router": "^4.0.12"

BIN
src/assets/img/Feedback/border.png

After

Width: 592  |  Height: 616  |  Size: 298 KiB

BIN
src/assets/img/Feedback/failure.png

After

Width: 325  |  Height: 252  |  Size: 81 KiB

BIN
src/assets/img/Feedback/save.png

After

Width: 261  |  Height: 240  |  Size: 66 KiB

BIN
src/assets/img/Feedback/success.png

After

Width: 325  |  Height: 252  |  Size: 84 KiB

1
src/store/audio.js

@ -5,6 +5,7 @@ export const useAudioStore = defineStore('audio', {
soundInstance: null, // Howl 实例
isPlaying: false, // 播放状态
isVoiceEnabled: true, // 新增声音开关状态
playbackPosition: 0, // 新增播放位置存储
lastVoiceState: null,
ttsUrl:''
}),

140
src/views/AIchat.vue

@ -96,11 +96,10 @@ const playAudio = (url) => {
const handlePlay = () => {
if (audioStore.soundInstance) {
Howler.unload(); //
// audioStore.soundInstance.stop()
}
// if (audioStore.soundInstance) {
// Howler.unload(); //
// // audioStore.soundInstance.stop()
// }
const newSound = new Howl({
src: [url],
html5: true, // HTML5 AudioiOS
@ -149,11 +148,11 @@ const pauseAudio = () => {
}
audioStore.isPlaying = false;
//
nextTick(() => {
Howler.unload();
audioStore.soundInstance = null;
});
// //
// nextTick(() => {
// Howler.unload();
// audioStore.soundInstance = null;
// });
}
};
@ -375,111 +374,6 @@ watch(
}
}
// if (status === null || status.code == 200) {
// if (ans.value.answerG !== "") {
// AIcontent.value = ans.value.answerG;
// const code = ans.value.code;
// const market = ans.value.market;
// const data = JSON.parse(ans.value.duobaoData);
// console.log(" K 线 - ");
// console.log(data, "data");
// const Kline20 = {
// name: data.data.HomePage.StockInformation.Name,
// Kline: data.data.AIBull.KLine20
// }
// // K线
// console.log("K线:", Kline20);
// console.log("K线:", Kline20.name);
// console.log("K线:", Kline20.Kline ? Kline20.Kline.length : 0);
// //
// if (Kline20.Kline && Kline20.Kline.length > 0) {
// console.log("K线:", Kline20.Kline[0]);
// }
// //
// hasValidData.value = true;
// console.log("hasValidData:", hasValidData.value);
// chatStore.messages.pop();
// // K线
// const klineMessageId = `kline-${Date.now()}`;
// console.log("K线ID:", klineMessageId);
// chatStore.messages.push({
// sender: "ai",
// type: "kline",
// chartData: Kline20,
// messageId: klineMessageId,
// hasValidData: true // hasValidData
// });
// console.log("K线");
// //
// chatStore.messages.push({
// sender: "ai",
// content: "AI..."
// });
// //
// nextTick(() => {
// console.log("nextTick - ");
// console.log(":", chatStore.messages);
// // K线
// let klineIndex = -1;
// for (let i = 0; i < chatStore.messages.length; i++) {
// if (chatStore.messages[i].messageId === klineMessageId) {
// klineIndex = i;
// break;
// }
// }
// console.log("K线:", klineIndex);
// if (klineIndex !== -1) {
// const containerId = `kline-container-${klineIndex}`;
// console.log("ID:", containerId);
// // DOM
// setTimeout(() => {
// console.log("DOM");
// KlineCanvsEcharts(containerId);
// }, 100); // DOM
// } else {
// console.warn("K线");
// }
// });
// } else if (ans.value.answerN !== "") {
// AIcontent.value = ans.value.answerN;
// } else if (ans.value.answerO !== "") {
// AIcontent.value = ans.value.answerO;
// }
// // 使markedMarkdownHTML
// AIcontent.value = marked(AIcontent.value,);
// // 使 KaTeX
// const katexRegex = /\$\$(.*?)\$\$/g;
// AIcontent.value = AIcontent.value.replace(katexRegex, (match, formula) => {
// try {
// return katex.renderToString(formula, { throwOnError: false });
// } catch (error) {
// console.error('KaTeX :', error);
// return match;
// }
// });
// chatStore.messages.push({
// sender: "ai",
// content: AIcontent.value
// })
//
const processedContent = marked(AIcontent.value);
const katexRegex = /\$\$(.*?)\$\$/g;
@ -861,13 +755,14 @@ watch(
tryPlay();
} else {
console.log("关闭语音播放");
pauseAudio();
//
Howler.stop();
Howler.unload();
if (audioStore.soundInstance) {
audioStore.soundInstance.off(); //
audioStore.soundInstance = null;
}
// Howler.stop();
// Howler.unload();
// if (audioStore.soundInstance) {
// audioStore.soundInstance.off(); //
// audioStore.soundInstance = null;
// }
}
},
{ immediate: true }
@ -972,7 +867,8 @@ onUnmounted(() => {
{{ questions.title }}
</div>
</div>
<div id="bottom" class="marquee-row bottom" @mouseenter="floatingBottomMouseEnter" @mouseleave="floatingBottomMouseLeave">
<div id="bottom" class="marquee-row bottom" @mouseenter="floatingBottomMouseEnter"
@mouseleave="floatingBottomMouseLeave">
<div v-for="(questions, index) in questionsList.slice(5, 10)" :key="'bottom' + index" class="marquee-item"
@click="showQuestions(questions)">
{{ questions.title }}

50
src/views/Announcement.vue

@ -2,6 +2,17 @@
import { ref, onMounted } from "vue";
import { getAnnouncementAPI, qsArpAamClickAPI } from "../api/AIxiaocaishen";
const codeList = ref([
{ market: '美股', code: ['Tesla', 'NVDA', 'APPL'] },
{ market: 'A股', code: ['1A0001', '002594', '000001'] },
{ market: '港股', code: ['阿里巴巴', '小米集团', '腾讯控股'] },
{ market: '新加坡股', code: [] },
{ market: '马股', code: [] },
{ market: '泰股', code: [] },
{ market: '越南股', code: [] },
{ market: '加拿大股', code: [] },
])
const announcementVideo = ref({});
const getAnnouncement = async () => {
const result = await getAnnouncementAPI()
@ -23,6 +34,16 @@ const handleVideoPlay = () => {
//
}
//
const emit = defineEmits(["updateMessage", "ensureAIchat"]);
const clickCode = (code) => {
console.log(code);
emit("updateMessage", code);
emit("ensureAIchat");
}
onMounted(() => {
getAnnouncement()
})
@ -45,11 +66,22 @@ onMounted(() => {
<p class="announcementItem">以下为各个市场可以查看的股票</p>
<!-- <p class="announcementItem">历软件云版静态市场一致!</p>
<p class="announcementItem">特此公告!</p> -->
<div v-for="(item, index) in codeList" :key="index" class="announcementItem">
<div class="announcementItem">{{ item.market }}
<span class="codeItem" v-for="(code, index) in item.code" :key="index">
<span v-if="index != 0" class="codeItem">
</span>
<span @click="clickCode(code)" class="code">
{{ code }}
</span>
</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.main-wrapper {
height: 100%;
display: flex;
@ -58,8 +90,9 @@ onMounted(() => {
}
.video-container {
max-width: 90%; /* 从 800px 改为百分比 */
width: 100%;
max-width: 800px;
/* 从 800px 改为百分比 */
width: 90%;
margin: 20px auto;
border-radius: 8px;
overflow: hidden;
@ -71,7 +104,8 @@ onMounted(() => {
width: 100%;
aspect-ratio: 16/9;
background-color: #000;
object-fit: contain; /* 从 cover 改为 contain */
object-fit: contain;
/* 从 cover 改为 contain */
}
/* 添加移动端适配 */
@ -98,4 +132,12 @@ onMounted(() => {
margin-bottom: 10px;
}
.codeItem {
color: #4591E7;
}
.code{
cursor: pointer;
}
</style>

256
src/views/Feedback.vue

@ -6,6 +6,10 @@ import feedback from "../assets/img/Feedback/feedback.png";
import feedbackImg from "../assets/img/Feedback/feedbackImg.png";
import feedbackSuccess from "../assets/img/Feedback/feedbackSuccess.png";
import noFeedback from "../assets/img/Feedback/noFeedback.png";
import border from "../assets/img/Feedback/border.png";
import success from "../assets/img/Feedback/success.png";
import failure from "../assets/img/Feedback/failure.png";
import save from "../assets/img/Feedback/save.png";
const dataStore = useDataStore()
@ -14,8 +18,54 @@ const uploadUrl = import.meta.env.VITE_APP_IMG_API_BASE_URL;
const feedbackFileList = ref([]);
const isHistoryFeedback = ref(false);
const submitSuccessDialogVisible = ref(false)
const submitFailureDialogVisible = ref(false)
const submitFailureContent = ref('')
const feedbackSubmit = async () => {
console.log(feedbackContent.value)
console.log(feedbackFileList.value)
if (feedbackContent.value == '' && feedbackFileList.value.length == 0) {
submitFailureDialogVisible.value = true;
submitFailureContent.value = '请输入反馈内容或上传图片';
} else {
try {
submitSuccessDialogVisible.value = true;
} catch (error) {
submitFailureDialogVisible.value = true;
submitFailureContent.value = '反馈提交异常(错误代码:' + error.response.status + '),建议尝试更换网络环境后重新提交。';
}
}
}
const submitSuccessConfirm = () => {
feedbackContent.value = '';
feedbackFileList.value = [];
if (localStorage.getItem('feedbackContent')) {
localStorage.removeItem('feedbackContent');
}
if (localStorage.getItem('feedbackFileList')) {
localStorage.removeItem('feedbackFileList');
}
submitSuccessDialogVisible.value = false;
}
const submitFailureConfirm = () => {
submitFailureDialogVisible.value = false;
}
//
const feedbackContentOverLengthDialogVisible = ref(false)
const feedbackContentLengthJudge = () => {
console.log('字数判断');
console.log(feedbackContent.value.length)
if (feedbackContent.value.length >= 2000) {
feedbackContentOverLengthDialogVisible.value = true;
}
}
const feedbackContentOverLengthConfirm = () => {
feedbackContentOverLengthDialogVisible.value = false;
}
const feedbackContentChange = () => {
@ -59,13 +109,40 @@ const handlePictureCardPreview = (uploadFile) => {
dialogVisible.value = true
}
//
const feedbackBackDialogVisible = ref(false)
const feedbackBack = () => {
feedbackBackDialogVisible.value = true;
}
//
const feedbackBackConfirm = () => {
feedbackBackDialogVisible.value = false;
dataStore.isFeedback = false;
}
//
const feedbackBackCancel = () => {
if (localStorage.getItem('feedbackContent')) {
localStorage.removeItem('feedbackContent');
}
if (localStorage.getItem('feedbackFileList')) {
localStorage.removeItem('feedbackFileList');
}
feedbackBackDialogVisible.value = false;
dataStore.isFeedback = false;
}
onMounted(() => {
feedbackContent.value = localStorage.getItem('feedbackContent');
if (localStorage.getItem('feedbackContent')) {
feedbackContent.value = localStorage.getItem('feedbackContent')
} else {
feedbackContent.value = '';
}
if (localStorage.getItem('feedbackFileList')) {
feedbackFileList.value = JSON.parse(localStorage.getItem('feedbackFileList'));
} else {
feedbackFileList.value = [];
}
console.log(uploadUrl)
})
@ -74,7 +151,11 @@ onMounted(() => {
<template>
<el-container>
<div>
<div @click="feedbackBack">返回</div>
<div @click="feedbackBack">
<el-icon style="font-size: 50px;">
<Back />
</el-icon>
</div>
</div>
<el-scrollbar>
<el-header>
@ -90,8 +171,8 @@ onMounted(() => {
</div>
<div class="header-item">
<el-input class="feedbackContent" v-model="feedbackContent" :rows="5" type="textarea"
maxlength="2000" show-word-limit placeholder="请描写您想反馈的内容..."
@change="feedbackContentChange" />
maxlength="2000" show-word-limit placeholder="请描写您想反馈的内容..." @change="feedbackContentChange"
@input="feedbackContentLengthJudge" />
</div>
<div class="feedbackTitle header-item">
照片上传
@ -137,6 +218,65 @@ onMounted(() => {
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
<el-dialog v-model="feedbackBackDialogVisible" class="save-dialog">
<div class="imgLine">
<img class="dialogImg" :src="save" alt="保存">
</div>
<div class="feedbackBackTitle">
系统提示
</div>
<div class="feedbackBackAttention">
检测到为保存内容离开将丢失修改请选择是否保留此次编辑
</div>
<div class="feedbackBackBtnGroup">
<el-button class="feedbackBackBtn nosave" plain @click="feedbackBackCancel" type="primary">不保留</el-button>
<el-button class="feedbackBackBtn save" @click="feedbackBackConfirm" type="primary">保留</el-button>
</div>
</el-dialog>
<el-dialog v-model="feedbackContentOverLengthDialogVisible" class="save-dialog">
<div class="feedbackContentOverLengthTitle">
温馨提示
</div>
<div class="feedbackContentOverLengthContent">
当前输入字数已达上限
</div>
<div class="feedbackBackBtnGroup">
<el-button class="feedbackContentOverLengthBtn confirm" type="primary"
@click="feedbackContentOverLengthConfirm">确认</el-button>
</div>
</el-dialog>
<el-dialog v-model="submitSuccessDialogVisible" class="save-dialog">
<div class="imgLine">
<img class="dialogImg" :src="success" alt="成功">
</div>
<div class="feedbackSuccessTitle">
提交成功
</div>
<div class="feedbackBackAttention">
感谢您的反馈
</div>
<div class="feedbackBackBtnGroup">
<el-button class="feedbackBackBtn confirm" @click="submitSuccessConfirm" type="primary">确定</el-button>
</div>
</el-dialog>
<el-dialog v-model="submitFailureDialogVisible" class="save-dialog">
<div class="imgLine">
<img class="dialogImg" :src="failure" alt="失败">
</div>
<div class="feedbackFailureTitle">
提交失败
</div>
<div class="feedbackBackAttention">
{{ submitFailureContent }}
</div>
<div class="feedbackBackBtnGroup">
<el-button class="feedbackBackBtn confirm" @click="submitFailureConfirm" type="primary">确定</el-button>
</div>
</el-dialog>
</template>
<style scoped>
@ -235,6 +375,98 @@ onMounted(() => {
width: 200px;
height: 200px;
}
.feedbackBackTitle {
width: 100%;
text-align: center;
font-size: 25px;
font-weight: bold;
color: #2961FF
}
.feedbackBackAttention {
font-size: 15px;
font-weight: bold;
margin: 10px 5px;
text-align: center;
}
.feedbackBackBtnGroup {
display: flex;
align-items: center;
justify-content: center;
margin: 20px auto;
width: 75%;
}
.feedbackBackBtn {
margin: 0 auto;
font-weight: bold;
border-radius: 20px;
}
.save {
background: #816CF6;
border: none;
}
.nosave{
color: #816CF6;
background: white;
border: none;
}
.confirm {
background: #816CF6;
border: none;
}
.feedbackContentOverLengthTitle {
font-size: 25px;
font-weight: bold;
width: 100%;
text-align: center;
margin: 20px 0;
color: #2961FF;
}
.feedbackContentOverLengthContent {
font-size: 15px;
font-weight: bold;
width: 100%;
text-align: center;
margin: 20px 0;
}
.imgLine {
width: 100%;
display: flex;
}
.dialogImg {
max-width: 300px;
width: 50%;
height: 50%;
margin: 0 auto;
}
.feedbackSuccessTitle {
width: 100%;
text-align: center;
font-size: 25px;
font-weight: bold;
color: #2961FF
}
.feedbackFailureTitle {
width: 100%;
text-align: center;
font-size: 25px;
font-weight: bold;
color: #ff4646
}
</style>
<style>
.uploadImg .el-upload {
@ -248,4 +480,20 @@ onMounted(() => {
height: 100px;
}
}
.feedbackContentOverLengthBtn {
margin: 0 auto;
color: white;
&:hover,
&:active,
&:focus {
color: white !important;
}
}
.save-dialog {
background: linear-gradient(90deg, #cac4fe, #b9d0fc, #a7dbfc);
border-radius: 20px;
}
</style>

32
src/views/homePage.vue

@ -163,6 +163,7 @@ const isAnnouncementVisible = ref(false);
const showAnnouncement = async () => {
console.log("打开公告");
dataStore.isFeedback = false; //
isScrolling.value = false; //
setActiveTab('', -1); //
@ -318,6 +319,31 @@ const heightListener = () => {
const throttledHeightListener = _.throttle(heightListener, 500, { trailing: false });
const goToRecharge = () => {
console.log('点击充值')
// http://39.101.133.168:8919/payment/recharge/index?
// url=http%3A%2F%2Flocalhost%3A8080%2FLiveActivity%2Fpck
// &platform=1
// &token=+S4h5QEE1hTIb4CxphrnbZi0+fEeMx8pywnIlrmTmo4QO6IolWnVWu5r+J4rKXMwK41UPfKqyIp+RvWmtM8
const userAgent = navigator.userAgent.toLowerCase();
const mobileKeywords = ['mobile', 'android', 'iphone', 'ipad', 'ipod'];
const isMobile = mobileKeywords.some(keyword => userAgent.includes(keyword));
console.log(isMobile ? '手机' : '电脑')
const url = encodeURI("http://192.168.1.103:3000/homePage")
console.log(url, 'url')
const platform = isMobile ? 2 : 1
const token = localStorage.getItem('localToken')
const rechargeUrl = 'http://39.101.133.168:8919/payment/recharge/index?' + 'url=' + url + '&platform=' + platform + '&token=' + token
console.log(rechargeUrl, 'rechargeUrl')
window.location.href=rechargeUrl
}
const adjustFooterPosition = (height) => {
console.log('调整底部位置', height)
const footer = document.querySelector('.el-footer');
@ -403,6 +429,7 @@ window.addEventListener('resize', () => {
//
document.addEventListener('touchmove', (e) => {
if (!dataStore.isFeedback) {
//
const isScrollableArea = e.target.closest('.tab-content');
@ -410,6 +437,7 @@ document.addEventListener('touchmove', (e) => {
if (!isScrollableArea) {
e.preventDefault();
}
}
}, { passive: false });
onMounted(async () => {
@ -470,7 +498,7 @@ onMounted(async () => {
</section>
<div class="tab-content" ref="tabContent">
<component :is="activeComponent" :messages="messages" @updateMessage="updateMessage"
@sendMessage="sendMessage" />
@sendMessage="sendMessage" @ensureAIchat="ensureAIchat"/>
</div>
</div>
@ -543,7 +571,7 @@ onMounted(async () => {
<template #footer>
<!-- 添加一个div来包裹按钮并设置样式使其居中 -->
<div style="text-align: center">
<el-button style="background-color: orange; color: white; border: none" @click="dialogVisible = false">
<el-button style="background-color: orange; color: white; border: none" @click="goToRecharge">
去充值
</el-button>
</div>

Loading…
Cancel
Save