Browse Source

前台差发送逻辑

hxl
hongxilin 2 months ago
parent
commit
150c590019
  1. 2
      .env.development
  2. 2
      .env.production
  3. 7
      README.md
  4. 1138
      package-lock.json
  5. 9
      package.json
  6. 76
      src/api/AIxiaocaishen.js
  7. BIN
      src/assets/img/homePage/tail/voice-no-active.png
  8. 50
      src/assets/js/useAppBridge.js
  9. 159
      src/assets/js/useProjectTracking.js
  10. 6
      src/main.js
  11. 2
      src/router/index.js
  12. 33
      src/store/audio.js
  13. 16
      src/store/chat.js
  14. 186
      src/store/dataList.js
  15. 2
      src/store/userPessionCode.js
  16. 2
      src/utils/languageService.js
  17. 2
      src/utils/request.js
  18. 420
      src/views/AIchat.vue
  19. 53
      src/views/AIfind.vue
  20. 105
      src/views/Announcement.vue
  21. 219
      src/views/Echarts/KLine.vue
  22. 251
      src/views/homePage.vue

2
.env.development

@ -7,6 +7,8 @@ VITE_PUBLIC_PATH = /
#新数据接口 #新数据接口
VITE_APP_API_BASE_URL = "http://39.101.133.168:8828/link" VITE_APP_API_BASE_URL = "http://39.101.133.168:8828/link"
#MJ API
VITE_APP_MJ_API_BASE_URL = "http://192.168.9.19:8080/api"
# Whether to open mock # Whether to open mock
VITE_USE_MOCK = true VITE_USE_MOCK = true

2
.env.production

@ -10,6 +10,8 @@ VITE_USE_MOCK = true
#新数据接口 #新数据接口
VITE_APP_API_BASE_URL = https://api.homilychart.com/link VITE_APP_API_BASE_URL = https://api.homilychart.com/link
#MJ API
VITE_APP_MJ_API_BASE_URL = "http://192.168.9.19:8080/api"
# Whether to enable gzip or brotli compression # Whether to enable gzip or brotli compression
# Optional: gzip | brotli | none # Optional: gzip | brotli | none
# If you need multiple forms, you can use `,` to separate # If you need multiple forms, you can use `,` to separate

7
README.md

@ -9,3 +9,10 @@ npm install axios
npm install dayjs --save npm install dayjs --save
npm install element-plus --save element-plus组件库 npm install element-plus --save element-plus组件库
npm install @element-plus/icons-vue npm install @element-plus/icons-vue
npm install pinia @pinia-plugin-persistedstate/nuxt
npm install howler
npm install marked 解析markdown 解析文本样式
npm install katex 数学公式 解析数学公式样式
npm install @coze/api
npm install html-to-text
npm install echarts

1138
package-lock.json
File diff suppressed because it is too large
View File

9
package.json

@ -16,15 +16,20 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@pinia-plugin-persistedstate/nuxt": "^1.2.1",
"@types/jszip": "^3.4.1", "@types/jszip": "^3.4.1",
"@vueuse/core": "^7.5.3", "@vueuse/core": "^7.5.3",
"axios": "^0.24.0", "axios": "^0.24.0",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"echarts": "^3.8.2",
"echarts": "^3.8.5",
"element-plus": "^2.9.6", "element-plus": "^2.9.6",
"fast-glob": "^3.3.3", "fast-glob": "^3.3.3",
"howler": "^2.2.4",
"html-to-text": "^9.0.5",
"katex": "^0.16.21",
"marked": "^15.0.7",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"pinia": "^2.0.9",
"pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^1.0.3", "pinia-plugin-persistedstate": "^1.0.3",
"reset-css": "^5.0.2", "reset-css": "^5.0.2",
"vconsole": "^3.15.1", "vconsole": "^3.15.1",

76
src/api/AIxiaocaishen.js

@ -1,7 +1,7 @@
import request from '../utils/request' import request from '../utils/request'
const APIurl = import.meta.env.VITE_APP_API_BASE_URL const APIurl = import.meta.env.VITE_APP_API_BASE_URL
const MJAPIurl = import.meta.env.VITE_APP_MJ_API_BASE_URL
//各个模块权限code接口 //各个模块权限code接口
export const pessionAPI = function (params) { export const pessionAPI = function (params) {
return request({ return request({
@ -43,24 +43,69 @@ export const computedUsersAPI = function (params) {
}) })
} }
export const getNewsAPI = function () {
return request({
url: `http://192.168.9.19:8080/api/ai_god/news`,
method: 'POST'
})
}
export const getQuestionAPI = function () {
return request({
url: `http://192.168.9.19:8080/api/ai_god/shows`,
method: 'POST',
data: new URLSearchParams({
"type": "1",
"num": "10"
})
})
}
export const getAnnouncementAPI = function () {
return request({
url: `http://192.168.9.19:8080/api/ai_god/shows`,
method: 'POST',
data: new URLSearchParams({
"type": "3",
"num": "1"
})
})
}
export const getUserCountAPI = function (params) {
return request({
url: `http://192.168.9.19:8080/api/ai_god/userUsageInfo`,
method: 'POST',
data: new URLSearchParams(params),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
}
// 获取回复接口 // 获取回复接口
export const getReplyAPI = function (params) { export const getReplyAPI = function (params) {
return request({
url: `https://api.coze.cn/v1/workflow/stream_run`,
method: 'post',
data: JSON.stringify(params),
return fetch('https://api.coze.cn/v1/workflow/run', {
method: 'POST',
body: JSON.stringify({
"workflow_id": "7481159261435854860",
"parameters": params,
}),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: 'Bearer pat_TJbuxUiZdl6U3oiiSeceQnHg5XdaZsWpxc6oIozc2Auhd9YuyBvFslJJQUFUym1F'
'Authorization': 'Bearer pat_TJbuxUiZdl6U3oiiSeceQnHg5XdaZsWpxc6oIozc2Auhd9YuyBvFslJJQUFUym1F'
} }
}) })
} }
// 获取回复接口流式
export const getReplyStreamAPI = function (params) { export const getReplyStreamAPI = function (params) {
return fetch(`https://api.coze.cn/v1/workflow/stream_run`, return fetch(`https://api.coze.cn/v1/workflow/stream_run`,
{ {
method: 'POST', method: 'POST',
body: JSON.stringify(params),
body: JSON.stringify({
"workflow_id": "7481159261435854860",
"parameters": params,
}),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: 'Bearer pat_TJbuxUiZdl6U3oiiSeceQnHg5XdaZsWpxc6oIozc2Auhd9YuyBvFslJJQUFUym1F' Authorization: 'Bearer pat_TJbuxUiZdl6U3oiiSeceQnHg5XdaZsWpxc6oIozc2Auhd9YuyBvFslJJQUFUym1F'
@ -68,3 +113,18 @@ export const getReplyStreamAPI = function (params) {
} }
) )
} }
// 接受音频
export const TTSAPI = function (params) {
return fetch('https://api.coze.cn/v1/workflow/run', {
method: 'POST',
body: JSON.stringify({
"workflow_id": "7481639836165275702",
"parameters": params,
}),
headers: {
'Authorization': 'Bearer pat_TJbuxUiZdl6U3oiiSeceQnHg5XdaZsWpxc6oIozc2Auhd9YuyBvFslJJQUFUym1F',
'Content-Type': 'application/json'
}
})
}

BIN
src/assets/img/homePage/tail/voice-no-active.png

After

Width: 61  |  Height: 61  |  Size: 2.1 KiB

50
src/assets/js/useAppBridge.js

@ -0,0 +1,50 @@
//跳转app方法
export function useAppBridge() {
const fullClose = (n, m) => {
let result = Math.random() * (m + 1 - n) + n
while (result > m) {
result = Math.random() * (m + 1 - n) + n
}
return Math.floor(result)
}
const packageFun = (funName, fun = () => {}, platform, data = {}) => {
const JWrandom = fullClose(10000, 99999)
data.JWrandom = JWrandom
window[funName + JWrandom] = fun
try {
const params = {
name: funName,
extra: { data }
}
switch (platform) {
case 2: // app apicloud
window.api.sendEvent(params)
break
case 3: // app ios
window.webkit.messageHandlers.getTouJiaoData.postMessage(JSON.stringify(params))
break
case 4: // app android
window.android.getTouJiaoData(JSON.stringify(params))
break
case 5: // app uniapp
window.uni.postMessage({
data: {
val: JSON.stringify(params)
}
})
break
}
} catch (e) {
console.error('Error in packageFun:', e)
}
}
return {
packageFun,
fullClose
}
}

159
src/assets/js/useProjectTracking.js

@ -0,0 +1,159 @@
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import { useRouter } from 'vue-router'
import { computedUsersAPI } from '@/api/sword'
export function useProjectTracking(projectRoutes) {
const router = useRouter()
const entryTime = ref(Date.now())
const isInProject = ref(true)
const hasRecordedEntry = ref(sessionStorage.getItem('hasRecordedEntry') === 'true')
// const parentUrl = window.parent.location.href
// console.log('Link平台地址:', parentUrl)
let isPageRefreshing = false // 标志位:是否刷新页面
// 记录用户进入项目的时间
const recordEntryTime = () => {
if (hasRecordedEntry.value) {
return
}
entryTime.value = Date.now()
const date = new Date(entryTime.value)
const formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1)
.toString()
.padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date
.getHours()
.toString()
.padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date
.getSeconds()
.toString()
.padStart(2, '0')}`
sessionStorage.setItem('projectEntryTime', formattedDate)
sessionStorage.setItem('hasRecordedEntry', 'true')
isInProject.value = true
hasRecordedEntry.value = true
console.log('记录首次进入时间:', formattedDate)
}
// 发送追踪数据到后端
const sendTrackingData = async () => {
if (!isInProject.value) return
const storedEntryTime = sessionStorage.getItem('projectEntryTime')
if (!storedEntryTime) {
console.warn('未找到存储的进入时间,取消发送跟踪数据')
return
}
let timestamp
try {
timestamp = new Date(storedEntryTime.replace(' ', 'T')).getTime()
if (isNaN(timestamp)) throw new Error('无效日期')
} catch (error) {
console.error('解析存储的进入时间时出错:', error)
return
}
const exitTime = Date.now()
const duration = Math.floor((exitTime - timestamp) / 1000)
const localToken = localStorage.getItem('localToken')
console.log('进入项目的时间', storedEntryTime)
console.log('停留时间', duration)
const params = {
stayTime: duration,
loginTime: storedEntryTime,
token: localToken
}
if (localToken) {
try {
const res = await computedUsersAPI(params)
console.log('跟踪数据已发送:', res)
sessionStorage.removeItem('projectEntryTime')
sessionStorage.removeItem('hasRecordedEntry')
isInProject.value = false
hasRecordedEntry.value = false
} catch (error) {
console.error('发送跟踪数据失败:', error)
}
}
}
// 页面可见性变化时触发
const handleVisibilityChange = () => {
// console.log(window.location.pathname.includes('duobaoqibing'), '路径是否包含了页面不可见触发')
// if (window.location.pathname.includes('duobaoqibing')) {
// console.log('在 searchCode.html 页面,不发送数据')
// return
// }
if (document.visibilityState === 'hidden') {
console.log('页面不可见,用户可能离开或切换标签页')
sendTrackingData()
}
}
// 页面关闭或刷新时触发
const handleBeforeUnload = (event) => {
// console.log(window.location.pathname)
// console.log(
// window.location.pathname.includes('duobaoqibing'),
// '路径是否包含了页面关闭了啦啦啦啦啦啦触发'
// )
// if (window.location.pathname.includes('duobaoqibing')) {
// console.log('在 searchCode.html 页面,不发送数据')
// return
// }
if (isPageRefreshing) {
console.log('页面刷新,不触发数据发送')
return
}
console.log('页面即将关闭或跳转')
sendTrackingData()
}
const handleRefreshDetection = () => {
isPageRefreshing = true
}
// 监听路由变化
watch(
() => router.currentRoute.value.path,
(newPath) => {
const isProjectRoute = projectRoutes.some((route) => newPath.startsWith(route))
let isProjectRouteName = projectRoutes[0]
console.log(isProjectRouteName)
// 判断是否是 searchCode.html 的访问
const isSearchCodePage = window.location.pathname.includes('duobaoqibing')
if (!isProjectRoute && !isSearchCodePage) {
console.log('离开项目路由:', newPath)
sendTrackingData()
} else if (isProjectRouteName && !hasRecordedEntry.value) {
console.log('首次进入项目路由:', newPath)
recordEntryTime()
}
}
)
// 添加事件监听
onMounted(() => {
document.addEventListener('visibilitychange', handleVisibilityChange)
window.addEventListener('beforeunload', handleBeforeUnload)
window.addEventListener('unload', handleRefreshDetection)
})
// 移除事件监听
onBeforeUnmount(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange)
window.removeEventListener('beforeunload', handleBeforeUnload)
window.removeEventListener('unload', handleRefreshDetection)
})
return {
entryTime,
isInProject,
sendTrackingData
}
}

6
src/main.js

@ -4,11 +4,15 @@ import router from './router'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue' import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import 'reset-css';
// import 'reset-css';
import { createPinia } from 'pinia'
const app = createApp(App) const app = createApp(App)
const pinia = createPinia()
for (const [key, component] of Object.entries(ElementPlusIconsVue)) { for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component) app.component(key, component)
} }
app.use(router) app.use(router)
app.use(ElementPlus) app.use(ElementPlus)
app.use(pinia)
app.mount('#app') app.mount('#app')

2
src/router/index.js

@ -20,7 +20,7 @@ const routes = [
name: 'AIfind', name: 'AIfind',
component: () => import('@/views/AIfind.vue'), component: () => import('@/views/AIfind.vue'),
meta: { title: '发现' } meta: { title: '发现' }
},
}
] ]
// 创建路由实例 // 创建路由实例
const router = createRouter({ const router = createRouter({

33
src/store/audio.js

@ -0,0 +1,33 @@
import { defineStore } from 'pinia'
export const useAudioStore = defineStore('audio', {
state: () => ({
soundInstance: null, // Howl 实例
isPlaying: false, // 播放状态
isVoiceEnabled: true // 新增声音开关状态
}),
actions: {
// 设置音频实例
setAudioInstance(instance) {
this.soundInstance = instance
},
// 播放控制
play() {
if (this.soundInstance) {
this.soundInstance.play()
this.isPlaying = true
}
},
// 暂停控制
pause() {
if (this.soundInstance) {
this.soundInstance.pause()
this.isPlaying = false
}
},
toggleVoice() {
this.isVoiceEnabled = !this.isVoiceEnabled
}
}
})

16
src/store/chat.js

@ -0,0 +1,16 @@
import { defineStore } from 'pinia';
export const useChatStore = defineStore('chat', {
state: () => ({
messages: []
}),
persist: {
enabled: true,
strategies: [
{
key: 'chat_messages',
storage: localStorage
}
]
}
});

186
src/store/dataList.js

@ -1,12 +1,13 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { useUserStore } from './userPessionCode' import { useUserStore } from './userPessionCode'
import { useLanguage } from '@/utils/languageService' import { useLanguage } from '@/utils/languageService'
import { dataListAPI } from '@/api/sword'
import { dataListAPI } from '@/api/AIxiaocaishen'
// import { useSkeletonStore } from '@/utils/skeletonLoader' // import { useSkeletonStore } from '@/utils/skeletonLoader'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
// const skeletonStore = useSkeletonStore() // const skeletonStore = useSkeletonStore()
const { t } = useLanguage()
// const { t } = useLanguage()
export const useDataStore = defineStore('data', () => { export const useDataStore = defineStore('data', () => {
const route = useRoute() const route = useRoute()
@ -22,6 +23,11 @@ export const useDataStore = defineStore('data', () => {
const AIGoldBull = ref(null) const AIGoldBull = ref(null)
const AIRadar = ref(null) const AIRadar = ref(null)
const loading = ref(false) const loading = ref(false)
const klineData = ref(null)
const setKlineData = (data) => {
klineData.value = data
}
const getQueryVariable = (variable) => { const getQueryVariable = (variable) => {
const query = window.location.search.substring(1) const query = window.location.search.substring(1)
@ -35,54 +41,69 @@ export const useDataStore = defineStore('data', () => {
return '' return ''
} }
const getAnswer = (result) => {
const data = result.data;
const vars = query.split('&')
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split('=')
if (pair[0] === variable) {
return pair[1]
}
}
return ''
}
// 使用示例,获取地址栏参数 // 使用示例,获取地址栏参数
// const token = ref(getQueryVariable('token')) // const token = ref(getQueryVariable('token'))
// const market = ref(getQueryVariable('market')) // const market = ref(getQueryVariable('market'))
// const code = ref(getQueryVariable('code')) // const code = ref(getQueryVariable('code'))
// console.log(token.value) // 输出 token 的值,例如 "111" // console.log(token.value) // 输出 token 的值,例如 "111"
const fetchChartData = async () => {
const getTokenString = String(localStorage.getItem('localToken'))
const getMarketString = String(localStorage.getItem('localMarket'))
const getCodeString = String(localStorage.getItem('localCode'))
// const getMarket = String()
try {
const res = await dataListAPI({
token: getTokenString || '',
market: getMarket() || getMarketString || 'gb',
code: getQueryVariable('code') || getCodeString || 'NDX',
language: t.value.suoxie,
brainPrivilegeState: userStore.brainPerssion,
swordPrivilegeState: userStore.swordPerssion,
stockForecastPrivilegeState: userStore.pricePerssion,
spaceForecastPrivilegeState: userStore.timePerssion,
aibullPrivilegeState: userStore.aibullPerssion,
aigoldBullPrivilegeState: userStore.aiGnbullPerssion,
airadarPrivilegeState: userStore.airadarPerssion,
marketList: userStore.aiGoldMarketList
})
brainDataList.value = res.data.Brain
swordDataList.value = res.data.Sword
priceDataList.value = res.data.StockForecast
timeDataList.value = res.data.SpaceForecast
showALLData.value = res.data.ShowAll
HomePage.value = res.data.HomePage
AIBull.value = res.data.AIBull
AIGoldBull.value = res.data.AIGoldBull
AIRadar.value = res.data.AIRadar
} catch (error) {
console.error('获取图表数据出错:', error)
} finally {
loading.value = false
console.log('数据获取过程结束')
}
}
// const fetchChartData = async () => {
// const getTokenString = String(localStorage.getItem('localToken'))
// const getMarketString = String(localStorage.getItem('localMarket'))
// const getCodeString = String(localStorage.getItem('localCode'))
// // const getMarket = String()
// try {
// const res = await dataListAPI({
// token: getTokenString || '',
// market: getMarket() || getMarketString || 'gb',
// code: getQueryVariable('code') || getCodeString || 'NDX',
// language: 'cn', //t.value.suoxie,
// brainPrivilegeState: userStore.brainPerssion,
// swordPrivilegeState: userStore.swordPerssion,
// stockForecastPrivilegeState: userStore.pricePerssion,
// spaceForecastPrivilegeState: userStore.timePerssion,
// aibullPrivilegeState: userStore.aibullPerssion,
// aigoldBullPrivilegeState: userStore.aiGnbullPerssion,
// airadarPrivilegeState: userStore.airadarPerssion,
// marketList: userStore.aiGoldMarketList
// })
// brainDataList.value = res.data.Brain
// swordDataList.value = res.data.Sword
// priceDataList.value = res.data.StockForecast
// timeDataList.value = res.data.SpaceForecast
// showALLData.value = res.data.ShowAll
// HomePage.value = res.data.HomePage
// AIBull.value = res.data.AIBull
// AIGoldBull.value = res.data.AIGoldBull
// AIRadar.value = res.data.AIRadar
// } catch (error) {
// console.error('获取图表数据出错:', error)
// } finally {
// loading.value = false
// console.log('数据获取过程结束')
// }
// }
// 获取路径上market的值 // 获取路径上market的值
const getMarket = () => { const getMarket = () => {
let market = '' let market = ''
const queryMarket = getQueryVariable('market') const queryMarket = getQueryVariable('market')
if (queryMarket) { if (queryMarket) {
if (['sg', 'my', 'in', 'hk', 'th', 'vi', 'usa', 'can', 'gb', 'cn'].includes(queryMarket)) {
if (
['sg', 'my', 'in', 'hk', 'th', 'vi', 'usa', 'can', 'gb', 'cn'].includes(queryMarket)
) {
return queryMarket return queryMarket
} else { } else {
switch (queryMarket) { switch (queryMarket) {
@ -106,13 +127,14 @@ export const useDataStore = defineStore('data', () => {
} }
} }
} }
return market
return ''
} }
const isLoading = ref(true) const isLoading = ref(true)
const initData = async () => { const initData = async () => {
isLoading.value = true isLoading.value = true
try { try {
// 初始化逻辑
} catch (error) { } catch (error) {
console.error('Error loading data:', error) console.error('Error loading data:', error)
} finally { } finally {
@ -120,63 +142,43 @@ export const useDataStore = defineStore('data', () => {
} }
} }
watch(
() => [userStore.isReady, t.value?.suoxie],
([isReady, language]) => {
console.log('isReady 或 language 变化:', { isReady, language })
if (isReady) {
// // 在组件挂载时显示骨架屏
// skeletonStore.startLoading()
setTimeout(() => {
fetchChartData()
}, 500) // 重新发起 API 请求
}
},
{ immediate: true, deep: true }
),
// watchEffect(() => {
// const isReady = userStore.isReady;
// const getCodeString1 = ref(localStorage.getItem('localCode'));
// const getMarketString1 = ref(localStorage.getItem('localMarket'));
// watch(
// () => [userStore.isReady, t.value?.suoxie],
// ([isReady]) => {
// console.log('isReady 或 language 变化:', isReady)
// if (isReady) {
// setTimeout(() => fetchChartData(), 500)
// }
// },
// { immediate: true, deep: true }
// )
// console.log({isReady, getCodeString1, getMarketString1}, '监听变化');
// watch(() => route.query, () => {
// if (route.query.market || route.query.code) {
// fetchChartData()
// }
// })
// watch(() => userStore.aibullPerssion, (newValue) => {
// if (newValue === 1) fetchChartData()
// })
// if (isReady && getCodeString1.value && getMarketString1.value) {
// console.log('条件满足,发起 API 请求');
// skeletonStore.startLoading(); // 显示骨架屏
// fetchChartData(); // 发起 API 请求
// }
// }),
watch(
() => route.query,
(newQuery, oldQuery) => {
if (newQuery.market || newQuery.code) {
fetchChartData()
}
}
),
watch(
() => userStore.aibullPerssion,
(newValue) => {
if (newValue === 1) {
fetchChartData()
}
}
)
initData() initData()
return { return {
brainDataList,
swordDataList,
priceDataList,
timeDataList,
showALLData,
HomePage,
AIBull,
AIGoldBull,
loading,
AIRadar,
fetchChartData,
// brainDataList,
// swordDataList,
// priceDataList,
// timeDataList,
// showALLData,
// HomePage,
// AIBull,
// AIGoldBull,
// loading,
// AIRadar,
// fetchChartData,
klineData,
setKlineData,
initData, initData,
getMarket, getMarket,
getQueryVariable getQueryVariable

2
src/store/userPessionCode.js

@ -1,6 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { pessionAPI } from '@/api/sword'
import { pessionAPI } from '@/api/AIxiaocaishen'
// import VConsole from 'vconsole' // import VConsole from 'vconsole'
import { useAppBridge } from '@/assets/js/useAppBridge.js' import { useAppBridge } from '@/assets/js/useAppBridge.js'

2
src/utils/languageService.js

@ -1,5 +1,5 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { t, changeLanguage, availableLanguages, languagePacks } from '../lang/index'
// import { t, changeLanguage, availableLanguages, languagePacks } from '../lang/index'
export const useLanguage = () => { export const useLanguage = () => {
const translate = computed(() => (key) => { const translate = computed(() => (key) => {

2
src/utils/request.js

@ -22,7 +22,7 @@ const ERROR_MESSAGES = {
const service = axios.create({ const service = axios.create({
baseURL: '', // url = base url + request url+ baseURL: '', // url = base url + request url+
timeout: 5000,
// timeout: 50000,
withCredentials: false // send cookies when cross-domain requests withCredentials: false // send cookies when cross-domain requests
// headers: { // headers: {
// // clear cors // // clear cors

420
src/views/AIchat.vue

@ -1,65 +1,334 @@
<script setup> <script setup>
import { ref, onMounted, watch, nextTick } from "vue"; import { ref, onMounted, watch, nextTick } from "vue";
import { ElDialog } from "element-plus"; import { ElDialog } from "element-plus";
import { getReplyStreamAPI } from "../api/AIxiaocaishen";
import { getReplyStreamAPI, getReplyAPI, TTSAPI, getQuestionAPI } from "../api/AIxiaocaishen";
import { useUserStore } from '../store/userPessionCode'
import { useChatStore } from '../store/chat'
import { useAudioStore } from '../store/audio'
import { useDataStore } from '@/store/dataList.js'
import { marked } from 'marked'; // marked
import katex from 'katex'; // KaTeX
import { htmlToText } from 'html-to-text';
import { Howl, Howler } from 'howler';
import KLine from './Echarts/KLine.vue'
const chatStore = useChatStore()
const audioStore = useAudioStore()
const dataStore = useDataStore()
// GIF // GIF
const currentGif = ref(""); const currentGif = ref("");
//
const newsList = ref(
Array(10)
.fill()
.map((_, i) => ({
title: `引导提出问题 ${i + 1}`,
content: `新闻 ${i + 1} 的详细内容...`,
}))
);
//
const questionsList = ref([])
const getQuestionsList = async () => {
const result = await getQuestionAPI()
questionsList.value = result.data
}
// //
const emit = defineEmits(["updateMessage", "sendMessage"]); const emit = defineEmits(["updateMessage", "sendMessage"]);
// //
const dialogVisible = ref(false); const dialogVisible = ref(false);
const currentNews = ref("");
const showNews = (news) => {
currentNews.value = news;
const currentQuestions = ref("");
const showQuestions = (questions) => {
currentQuestions.value = questions;
// //
emit("updateMessage", news.title);
emit("updateMessage", questions.title);
emit("sendMessage"); emit("sendMessage");
}; };
//
const playAudio = (url) => {
if (audioStore.soundInstance) {
audioStore.soundInstance.stop()
}
const newSound = new Howl({
src: [url],
html5: true, // HTML5 AudioiOS
format: ['mp3', 'wav'],
onplay: () => {
audioStore.isPlaying = true // store
newSound.volume(1) //
},
onend: () => audioStore.isPlaying = false,
onstop: () => audioStore.isPlaying = false,
onloaderror: (id, err) => {
console.error('音频加载失败:', err);
ElMessage.error('音频播放失败,请检查网络连接');
}
});
//
newSound.play()
audioStore.setAudioInstance(newSound);
//
if (Howler.autoUnlock) {
Howler.autoUnlock();
}
Howler.volume(1.0) //
};
// //
const chatMsg = computed(() => chatStore.messages)
const props = defineProps({ const props = defineProps({
messages: Array, messages: Array,
}); });
//
const isLoading = ref(false);
//
const typewriterContent = ref("")
const isTyping = ref(false)
const typeWriter = (text, callback) => {
let index = 0
isTyping.value = true
typewriterContent.value = ""
const typingInterval = setInterval(() => {
if (index < text.length) {
typewriterContent.value += text.charAt(index)
index++
//
nextTick(() => {
const container = document.querySelector('.message-area')
if (container) container.scrollTop = container.scrollHeight
})
} else {
clearInterval(typingInterval)
isTyping.value = false
if (callback) callback()
}
}, 50) // ()
}
watch( watch(
() => props.messages, () => props.messages,
async (newVal, oldVal) => { async (newVal, oldVal) => {
console.log("消息列表已更新,最新消息:", newVal[newVal.length - 1]);
//
const response = await getReplyStreamAPI({
workflow_id: "7480464341100494863",
parameters: {
input: newVal[newVal.length - 1].content,
},
});
const reader = response.body.getReader();
console.log(response, "response");
console.log(reader, "reader");
//
if (!newVal?.length || newVal === oldVal) return;
if (newVal.length > 0) {
console.log("消息列表已更新,最新消息:", newVal[newVal.length - 1])
chatStore.messages.push(newVal[newVal.length - 1])
isLoading.value = true;
console.log("消息列表已更新,最新消息:", chatMsg[chatMsg.length - 1]);
console.log('token', localStorage.getItem('localToken'));
//
const userStore = useUserStore();
const params = {
content: newVal[newVal.length - 1].content,
userData: {
// token: localStorage.getItem('localToken'),
// language: "cn",
// brainPrivilegeState: userStore.brainPerssion,
// swordPrivilegeState: userStore.swordPerssion,
// stockForecastPrivile: userStore.pricePerssion,
// spaceForecastPrivile: userStore.timePerssion,
// aibullPrivilegeState: userStore.aibullPerssion,
// aigoldBullPrivilegeS: userStore.aiGnbullPerssion,
// airadarPrivilegeStat: userStore.airadarPerssion,
// marketList: "hk,cn,usa,my,sg,vi,in,gb"
token: "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w",
language: "cn",
brainPrivilegeState: "1",
swordPrivilegeState: "1",
stockForecastPrivile: "1",
spaceForecastPrivile: "1",
aibullPrivilegeState: "1",
aigoldBullPrivilegeS: "1",
airadarPrivilegeStat: "1",
marketList: "hk,cn,usa,my,sg,vi,in,gb"
}
}
try {
chatStore.messages.push({
sender: "ai",
content:
`<div>
<span>AI正在思考中</span>
<el-icon class="is-loading">
<Loading />
</el-icon>
</div>`
});
//
const result = (await getReplyAPI(params)).json();
const ans = ref();
// console.log(result, "result");
// data JSON
await result.then((res) => {
// console.log(res.data, "res");
// data JSON
ans.value = JSON.parse(res.data);
})
// console.log(ans.value, "ans");
const AIcontent = ref("");
// answer
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(data, "data");
const Kline20 = {
name: data.data.HomePage.StockInformation.Name,
Kline: data.data.AIBull.KLine20
}
dataStore.setKlineData(Kline20);
chatStore.messages.pop();
chatStore.messages.push({
sender: "ai",
type: "kline",
});
chatStore.messages.push({
sender: "ai",
content: "AI正在思考中..."
});
console.log(Kline20, "Kline20");
console.log(code, "code");
console.log(market, "market");
console.log(data, "data");
} 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;
const plainTextContent = htmlToText(processedContent);
//
const TTSResult = (await TTSAPI({
language: "cn",
content: plainTextContent
})).json()
const tts = ref();
await TTSResult.then((res) => {
tts.value = JSON.parse(res.data);
})
const ttsUrl = ref();
if (tts.value.tts_cn !== null) {
ttsUrl.value = tts.value.tts_cn.url;
} else if (tts.value.tts_en !== null) {
ttsUrl.value = tts.value.tts_en.url;
}
if (ttsUrl.value) {
nextTick(() => {
if (audioStore.isVoiceEnabled) {
console.log("ttsUrl.value", ttsUrl.value)
//
playAudio(ttsUrl.value)
}
});
}
chatStore.messages.pop();
//
const aiMessage = reactive({
sender: "ai",
content: "",
isTyping: true,
});
chatStore.messages.push(aiMessage);
let index = 0;
const typingInterval = setInterval(() => {
if (index < processedContent.length) {
aiMessage.content += processedContent.charAt(index);
index++;
} else {
clearInterval(typingInterval);
aiMessage.isTyping = false;
// KaTeXDOM
nextTick(() => {
aiMessage.content = aiMessage.content.replace(katexRegex, (match, formula) => {
try {
return katex.renderToString(formula, { throwOnError: false });
} catch (error) {
console.error('KaTeX 渲染错误:', error);
return match;
}
});
});
}
}, 50); // 50ms/
//
isLoading.value = false;
}
catch (e) {
console.error('请求失败:', e);
chatStore.messages.push({
sender: "ai",
content: "AI思考失败,请稍后再试... ",
});
}
}
}, },
{ deep: true, immediate: true }
{ deep: true }
); );
// GIF // GIF
onMounted(() => { onMounted(() => {
const random = Math.floor(Math.random() * 4) + 1; const random = Math.floor(Math.random() * 4) + 1;
console.log(random, "random");
currentGif.value = `src/assets/img/AIchat/AIgif${random}.gif`; currentGif.value = `src/assets/img/AIchat/AIgif${random}.gif`;
getQuestionsList();
}); });
</script> </script>
@ -71,42 +340,44 @@ onMounted(() => {
<div class="marquee-container"> <div class="marquee-container">
<div class="marquee-row top"> <div class="marquee-row top">
<div
v-for="(news, index) in newsList.slice(0, 5)"
:key="'top' + index"
class="marquee-item"
@click="showNews(news)"
>
{{ news.title }}
<div v-for="(questions, index) in questionsList.slice(0, 5)" :key="'top' + index" class="marquee-item"
@click="showQuestions(questions)">
{{ questions.title }}
</div> </div>
</div> </div>
<div class="marquee-row bottom"> <div class="marquee-row bottom">
<div
v-for="(news, index) in newsList.slice(5, 10)"
:key="'bottom' + index"
class="marquee-item"
@click="showNews(news)"
>
{{ news.title }}
<div v-for="(questions, index) in questionsList.slice(5, 10)" :key="'bottom' + index" class="marquee-item"
@click="showQuestions(questions)">
{{ questions.title }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 消息区域 --> <!-- 消息区域 -->
<div class="message-area">
<div
v-for="(msg, index) in messages"
:key="index"
:class="['message-bubble', msg.sender]"
>
<!-- <div class="message-area">
<div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]">
{{ msg.content }} {{ msg.content }}
</div> </div>
</div> -->
<!-- <div class="message-area">
<div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]" v-html="msg.content">
</div>
</div> -->
<!-- <div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]" v-html="msg.content">
</div> -->
<div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]">
<div v-if="msg.type === 'kline'" class="kline-container">
<KLine />
</div>
<div v-else v-html="msg.content"></div>
</div> </div>
<!-- 新闻弹窗 --> <!-- 新闻弹窗 -->
<el-dialog v-model="dialogVisible" title="新闻详情" width="60%"> <el-dialog v-model="dialogVisible" title="新闻详情" width="60%">
<p>{{ currentNews.content }}</p>
<p>{{ currentQuestions.content }}</p>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -119,12 +390,13 @@ onMounted(() => {
.gif-area { .gif-area {
/* position: relative; */ /* position: relative; */
height: 30vh;
/* height: 30vh; */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-shrink: 0; /* 防止GIF区域被压缩 */
flex-shrink: 0;
/* 防止GIF区域被压缩 */
} }
.gif-area img { .gif-area img {
@ -139,15 +411,18 @@ onMounted(() => {
transition: all 0.3s; transition: all 0.3s;
/* 添加过渡效果 */ /* 添加过渡效果 */
} }
.message-area { .message-area {
margin-top: 2%; margin-top: 2%;
flex: 1; /* 消息区域占据剩余空间 */
flex: 1;
/* 消息区域占据剩余空间 */
overflow-y: auto; overflow-y: auto;
padding: 20px; padding: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 15px; gap: 15px;
} }
.marquee-container { .marquee-container {
/* position: absolute; */ /* position: absolute; */
bottom: 0; bottom: 0;
@ -198,7 +473,7 @@ onMounted(() => {
.message-bubble { .message-bubble {
max-width: 70%; max-width: 70%;
margin: 10px 20px;
margin: 10px 0px;
padding: 15px 25px; padding: 15px 25px;
border-radius: 10px; border-radius: 10px;
position: relative; position: relative;
@ -208,13 +483,42 @@ onMounted(() => {
background: #8263f0; background: #8263f0;
color: white; color: white;
margin-left: auto; margin-left: auto;
margin-right: 20px;
/* border-bottom-right-radius: 5px; */ /* border-bottom-right-radius: 5px; */
} }
.message-bubble.ai { .message-bubble.ai {
background: #f0f0f0;
background: #ffffff;
color: #333; color: #333;
margin-right: auto; margin-right: auto;
border-bottom-left-radius: 5px;
margin-left: 20px;
/* border-bottom-left-radius: 5px; */
}
.kline-container {
margin-top: 10px;
width: 100%;
/* 最小移动端尺寸 */
min-width: 320px;
/* 最小高度 */
min-height: 320px;
/* 视口高度单位 */
height: 40vh;
/* 最大宽度限制 */
max-width: 800px;
}
@media (min-width: 768px) {
.kline-container {
height: 40vh;
min-height: 400px;
}
}
@media (min-width: 1024px) {
.kline-container {
height: 50vh;
min-height: 400px;
}
} }
</style> </style>

53
src/views/AIfind.vue

@ -1,19 +1,39 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { getNewsAPI } from "../api/AIxiaocaishen"
import { marked } from 'marked'; // marked
import katex from 'katex'; // KaTeX
// //
const newsList = ref(Array(10).fill().map((_, i) => ({
title: `每日资讯 ${i + 1}`,
content: `资讯 ${i + 1} 的详细内容...`
})))
const newsList = ref([])
const getNewsList = async () => {
const result = await getNewsAPI()
newsList.value = result.data
}
// //
const dialogVisible = ref(false) const dialogVisible = ref(false)
const currentNews = ref('') const currentNews = ref('')
const showNews = (news) => { const showNews = (news) => {
currentNews.value = news currentNews.value = news
// 使markedMarkdownHTML
currentNews.value.description = marked(currentNews.value.description);
// 使 KaTeX
const katexRegex = /\$\$(.*?)\$\$/g;
currentNews.value.description = currentNews.value.description.replace(katexRegex, (match, formula) => {
try {
return katex.renderToString(formula, { throwOnError: false });
} catch (error) {
console.error('KaTeX 渲染错误:', error);
return match;
}
});
dialogVisible.value = true dialogVisible.value = true
} }
onMounted(() => {
getNewsList()
})
</script> </script>
<template> <template>
@ -32,9 +52,10 @@ const showNews = (news) => {
</div> </div>
<!-- 新闻弹窗 --> <!-- 新闻弹窗 -->
<el-dialog v-model="dialogVisible" title="每日资讯详情" width="60%" >
<p>{{ currentNews.content }}</p>
</el-dialog>
<el-dialog v-model="dialogVisible" :title="currentNews.title" width="80%" class="newsDialog">
<!-- <img :src="currentNews.img" alt="加载失败" class="newsDialogImg"> -->
<div v-html="currentNews.description"></div>
</el-dialog>
</template> </template>
<style scoped> <style scoped>
@ -81,4 +102,22 @@ const showNews = (news) => {
text-align: center; text-align: center;
/* 保持首行不换行 */ /* 保持首行不换行 */
} }
.newsDialog:deep(.el-dialog__body) {
display: flex;
flex-direction: column;
align-items: center;
display: flex;
padding: 20px;
}
.newsDialog:deep(.el-dialog) {
text-align: center;
}
.newsDialogImg {
width: 80%;
height: auto;
margin: 0 auto;
}
</style> </style>

105
src/views/Announcement.vue

@ -1,40 +1,95 @@
<script setup></script>
<script setup>
import { ref, onMounted } from "vue";
import { getAnnouncementAPI } from "../api/AIxiaocaishen";
const announcementVideo = ref({});
const getAnnouncement = async () => {
const result = await getAnnouncementAPI()
console.log(result.data, "result.data");
announcementVideo.value.url = result.data[0].url;
announcementVideo.value.img = result.data[0].img;
console.log(announcementVideo.value, "announcementVideo");
}
const handleVideoPlay = () => {
console.log('视频开始播放');
//
}
onMounted(() => {
getAnnouncement()
})
</script>
<template> <template>
<el-main class="homepage-body">
<div class="main-wrapper"> <div class="main-wrapper">
<img
src="src\assets\img\AIchat\AIgif1.gif"
alt="图片加载失败"
class="logo1"
/>
<div class="video-container">
<video ref="videoPlayer" :poster="announcementVideo.img" :src="announcementVideo.url" controls
class="video-player" @play="handleVideoPlay">
Your browser does not support the video tag.
</video>
</div>
<!-- 一段文字水平居中宽度为500px --> <!-- 一段文字水平居中宽度为500px -->
<div style="width: 500px; margin: 0 auto; text-align: center">
<p>
欢迎使用AI智能问答系统本系统基于OpenAI的GPT-3.5模型为您提供智能问答服务
</p>
<p>这个是公告部分</p>
<div class="announcement">
<p class="announcementItem">各位AI小财神的用户,大家好!</p>
<p class="announcementItem">试运行期间,用户可在AI小财神中查看全</p>
<p class="announcementItem">市场数据,每个市场可查看20只股票.</p>
<p class="announcementItem">试运行结束后,会员用户可查看市场与弘</p>
<p class="announcementItem">历软件云版静态市场一致!</p>
<p class="announcementItem">特此公告!</p>
</div> </div>
</div> </div>
</el-main>
</template> </template>
<style scoped> <style scoped>
.homepage-body {
padding: 0px;
height: calc(100% - 70px);
/* 根据底部高度调整 */
}
.main-wrapper { .main-wrapper {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-y: auto;
} }
.logo1 {
/* 居中显示 30%大小 不要拉伸*/
display: block;
width: 30%;
height: auto;
/* 水平居中 */
margin: 0 auto;
.video-container {
max-width: 90%; /* 从 800px 改为百分比 */
width: 100%;
margin: 20px auto;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
flex-shrink: 0;
} }
.video-player {
width: 100%;
aspect-ratio: 16/9;
background-color: #000;
object-fit: contain; /* 从 cover 改为 contain */
}
/* 添加移动端适配 */
@media (max-width: 768px) {
.video-container {
margin: 10px auto;
border-radius: 4px;
}
.announcement {
max-width: 90%;
font-size: 14px;
}
}
.announcement {
max-width: 500px;
margin: 20px 10%;
font-size: 20px;
font-weight: bold;
}
.announcementItem {
margin-bottom: 10px;
}
</style> </style>

219
src/views/Echarts/KLine.vue

@ -0,0 +1,219 @@
<template>
<!-- 趋势研判K线图 -->
<div ref="KlineCanvs" class="KlineClass"></div>
</template>
<script setup>
import * as echarts from 'echarts'
import { useDataStore } from '@/store/dataList'
// import { useLanguage } from '@/utils/languageService'
// const { translate, t } = useLanguage()
const KlineCanvs = ref() // Echarts
const dataStore = useDataStore()
const klineData = computed(() => dataStore.klineData)
watch(
klineData,
(newValue) => {
const currentData = newValue //
if (currentData) {
nextTick(() => {
KlineCanvsEcharts(currentData)
})
}
},
{ immediate: true, deep: true }
)
//
// watch(
// () => t.value,
// (newLang) => {
// // textEcharts
// if (klineData.value) {
// const currentData = klineData
// nextTick(() => {
// KlineCanvsEcharts(currentData)
// })
// }
// },
// { immediate: true, deep: true }
// )
function KlineCanvsEcharts(datatok) {
// const textEcahrts = t.value //
const data = datatok.Kline
//
const spliteDate = (a) => {
const categoryData = []
let value = []
for (let i = 0; i < a.length; i++) {
categoryData.push(a[i][0])
value.push([a[i][1],a[i][2],a[i][3],a[i][4]])
}
return { categoryData, value }
}
const dealData = spliteDate(data)
//
const KlineOption = {
title: {
// text: k_name,
// Canvs
text: datatok.name,
top: 20,
left: 20
},
tooltip: {
trigger: 'axis', //
//
formatter: function (a, b, d) {
let def =
a[0].name +
'<br/>' +
'开盘价' +
a[0].data[1] +
'<br/>' +
'收盘价' +
a[0].data[2] +
'<br/>' +
'最低价' +
a[0].data[3] +
'<br/>' +
'最高价' +
a[0].data[4]
// a[1]
if (a[1] && a[1].seriesName) {
def += '<br/>' + a[1].seriesName + ':' + a[1].value
}
return def
},
//
axisPointer: {
animation: false,
type: 'line',
linestyle: {
color: '#376df4',
width: 2,
opacity: 1
}
}
},
//
xAxis: {
type: 'category',
data: dealData.categoryData,
axisLine: { lineStyle: { color: '#8392A5' } }
},
//
grid: {
left: '12%',
right: '10%',
bottom: '10%',
top: '18%'
},
yAxis: {
scale: !0, //true
//
axisLabel: {
formatter: function (value) {
return value //
}
},
axisLine: { lineStyle: { color: '#8392A5' } },
splitLine: {
show: !1
}
},
//
dataZoom: [
{
textStyle: {
color: '#8392A5'
},
handleIcon:
'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
handleSize: '80%',
dataBackground: {
areaStyle: {
color: '#8392A5'
},
lineStyle: {
opacity: 0.8,
color: '#8392A5'
}
},
handleStyle: {
color: '#fff',
shadowBlur: 3,
shadowColor: 'rgba(0, 0, 0, 0.6)',
shadowOffsetX: 2,
shadowOffsetY: 2
}
},
{
show: !1,
type: 'slider'
},
{
type: 'inside'
}
],
animation: !1, //false
// 线
series: [
{
type: 'candlestick',
name: '\u65e5K',
//
data: dealData.value,
itemStyle: {
normal: {
color0: '#FD1050',
color: '#0CF49B',
borderColor0: '#FD1050',
borderColor: '#0CF49B'
}
}
},
{
name: 'MA5',
type: 'line',
//
// MA5
data: (function (a) {
for (var MA5 = [], d = 0, g = dealData.value.length; d < g; d++) {
if (d < a) {
MA5.push('-')
} else {
for (var f = 0, e = 0; e < a; e++) {
f += dealData.value[d - e][1]
}
MA5.push((f / a).toFixed(2))
}
}
return MA5
})(5),
smooth: !0
}
]
}
// echarts
const KlineCanvsChart = echarts.init(KlineCanvs.value)
KlineCanvsChart.setOption(KlineOption)
//
window.addEventListener('resize', () => {
KlineCanvsChart.resize()
})
}
onMounted(() => {
// fnGetData()
})
</script>
<style scoped>
.KlineClass {
position: relative;
width: 100%;
height: 100%;
left: 5%;
bottom: 8%;
}
</style>

251
src/views/homePage.vue

@ -2,10 +2,22 @@
// //
import { ref, computed, onMounted, watch, nextTick } from "vue"; import { ref, computed, onMounted, watch, nextTick } from "vue";
import { setHeight } from "../utils/setHeight"; import { setHeight } from "../utils/setHeight";
import { getReplyAPI } from "../api/AIxiaocaishen";
import AIchat from "../views/AIchat.vue";
import AIfind from "../views/AIfind.vue";
import { getUserCountAPI } from "../api/AIxiaocaishen";
import AIchat from "./AIchat.vue";
import AIfind from "./AIfind.vue";
import { useAppBridge } from '../assets/js/useAppBridge.js'
import { useDataStore } from '@/store/dataList.js'
import { useChatStore } from '../store/chat'
import { useAudioStore } from '../store/audio'
// import { useUserStore } from "../store/userPessionCode.js";
const { getQueryVariable } = useDataStore()
// //
//
const audioStore = useAudioStore()
const isVoice = computed(() => audioStore.isVoiceEnabled)
const toggleVoice = () => {
audioStore.toggleVoice()
}
// sessionStorage 使 'aifindCow'tab // sessionStorage 使 'aifindCow'tab
const activeTab = ref(sessionStorage.getItem("activeTabAI") || "AIchat"); const activeTab = ref(sessionStorage.getItem("activeTabAI") || "AIchat");
const activeIndex = ref( const activeIndex = ref(
@ -54,12 +66,19 @@ const ensureAIchat = () => {
}; };
// //
const getCount = () => {
console.log("获取次数");
const UserCount = ref(0);
const getUserCount = async () => {
const result = await getUserCountAPI({ token: localStorage.getItem('localToken') });
UserCount.value = result.data.hasCount;
}; };
const getCount = () => {
console.log('点击了获取次数的按钮')
}
// //
const isThinking = ref(false);
const isThinking = ref(true);
const toggleThink = () => { const toggleThink = () => {
isThinking.value = !isThinking.value; isThinking.value = !isThinking.value;
}; };
@ -70,13 +89,11 @@ const message = ref("");
const messages = ref([]); const messages = ref([]);
// //
const isLoading = ref(false); const isLoading = ref(false);
// sendMessage
const triggerFetch = ref(false);
// //
const updateMessage = (title) => { const updateMessage = (title) => {
message.value = title; message.value = title;
console.log("updateMessage 的值:", title);
// console.log("updateMessage :", title);
}; };
const sendMessage = async () => { const sendMessage = async () => {
// ensureAIchat AIchat // ensureAIchat AIchat
@ -85,18 +102,15 @@ const sendMessage = async () => {
if (!message.value) return; if (!message.value) return;
if (isLoading.value) return; if (isLoading.value) return;
const sendMessage = () => {
//
console.log("sendMessage 的值:", message.value);
messages.value.push({ content: message.value, sender: "user" });
};
messages.value.push({
sender: "user",
content: message.value,
timestamp: new Date().toISOString(),
});
// isLoading true
messages.value = [
...messages.value,
{
sender: "user",
content: message.value,
timestamp: new Date().toISOString(),
}
];
// //
@ -113,6 +127,7 @@ const isAnnouncementVisible = ref(false);
const showAnnouncement = () => { const showAnnouncement = () => {
console.log("打开公告"); console.log("打开公告");
isAnnouncementVisible.value = true; // isAnnouncementVisible.value = true; //
activeTab.value = 'Announcement`'
}; };
// //
@ -126,22 +141,100 @@ const showCount = () => {
console.log("dialogVisible 的值:", dialogVisible.value); // console.log("dialogVisible 的值:", dialogVisible.value); //
}; };
// //
const chatStore = useChatStore()
const tabContent = ref(null); const tabContent = ref(null);
const isScrolling = ref(false); //
const smoothScrollToBottom = async () => {
await nextTick();
const container = tabContent.value;
if (!container) return;
if (!isScrolling.value)
container.scrollTop = container.scrollHeight - container.offsetHeight;
requestAnimationFrame(() => {
container.scrollTop = container.scrollHeight - container.offsetHeight;
});
}
const handleScroll = function () {
const scrollContainer = tabContent.value
const scrollTop = scrollContainer.scrollTop
const scrollHeight = scrollContainer.scrollHeight
const offsetHeight = scrollContainer.offsetHeight
console.log(scrollTop, scrollHeight, offsetHeight, "scrollTop, scrollHeight, offsetHeight");
if (scrollTop + offsetHeight < scrollHeight) {
//
isScrolling.value = true
} else {
//
isScrolling.value = false
}
console.log(isScrolling.value)
}
watch( watch(
messages,
async () => {
await nextTick();
if (tabContent.value) {
tabContent.value.scrollTop = tabContent.value.scrollHeight;
}
() => chatStore.messages,
() => {
smoothScrollToBottom();
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }
); );
onMounted(() => {
watch(
activeTab,
async (newVal) => {
smoothScrollToBottom();
});
// setTimeout
setTimeout(() => {
fnGetToken()
}, 800)
// token
const fnGetToken = () => {
window.JWready = (ress) => {
//
if (!ress.data.platform) {
// AppURL
localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token'))))
// localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
} else {
// App
useAppBridge().packageFun(
'JWgetStorage',
(response) => {
const res = JSON.parse(response) //
localStorage.setItem('localToken', res.data)
// localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
},
5,
{
key: 'token'
}
)
}
}
// App
useAppBridge().packageFun('JWwebReady', () => { }, 5, {})
}
onMounted(async () => {
setHeight(document.getElementById("testId")); // setHeight(document.getElementById("testId")); //
});
getUserCount();
smoothScrollToBottom();
//
tabContent.value.addEventListener('scroll', handleScroll)
})
</script> </script>
<template> <template>
@ -151,31 +244,17 @@ onMounted(() => {
<el-header class="homepage-head"> <el-header class="homepage-head">
<!-- logo --> <!-- logo -->
<div class="homepage-logo"> <div class="homepage-logo">
<img
src="src\assets\img\homePage\logo.png"
alt="图片加载失败"
class="logo1"
/>
<img
src="src\assets\img\homePage\madeInHL.png"
alt="图片加载失败"
class="logo2"
/>
<img src="src\assets\img\homePage\logo.png" alt="图片加载失败" class="logo1" />
<img src="src\assets\img\homePage\madeInHL.png" alt="图片加载失败" class="logo2" />
</div> </div>
<div class="homepage-right-group"> <div class="homepage-right-group">
<div class="count-badge" @click="showCount"> <div class="count-badge" @click="showCount">
<img
src="src\assets\img\homePage\get-count-all.png"
class="action-btn"
/>
<div class="count-number">1000</div>
<img src="src\assets\img\homePage\get-count-all.png" class="action-btn" />
<div class="count-number">{{ UserCount }}</div>
</div> </div>
<img
src="src\assets\img\homePage\announcement.png"
class="announcement-btn action-btn"
@click="showAnnouncement"
/>
<img src="src\assets\img\homePage\announcement.png" class="announcement-btn action-btn"
@click="showAnnouncement" />
</div> </div>
</el-header> </el-header>
<!-- 主体部分小人 问题轮询图 对话内容 --> <!-- 主体部分小人 问题轮询图 对话内容 -->
@ -183,23 +262,15 @@ onMounted(() => {
<div class="main-wrapper"> <div class="main-wrapper">
<section class="tab-section"> <section class="tab-section">
<div class="tab-container"> <div class="tab-container">
<div
v-for="(tab, index) in tabs"
:key="tab.name"
@click="setActiveTab(tab.name, index)"
:class="['tab-item', { active: activeIndex === index }]"
>
<div v-for="(tab, index) in tabs" :key="tab.name" @click="setActiveTab(tab.name, index)"
:class="['tab-item', { active: activeIndex === index }]">
<span>{{ tab.label }}</span> <span>{{ tab.label }}</span>
</div> </div>
</div> </div>
</section> </section>
<div class="tab-content" ref="tabContent"> <div class="tab-content" ref="tabContent">
<component
:is="activeComponent"
:messages="messages"
@updateMessage="updateMessage"
@sendMessage="sendMessage"
/>
<component :is="activeComponent" :messages="messages" @updateMessage="updateMessage"
@sendMessage="sendMessage" />
</div> </div>
</div> </div>
</el-main> </el-main>
@ -208,55 +279,30 @@ onMounted(() => {
<!-- 第一行按钮 --> <!-- 第一行按钮 -->
<div class="footer-first-line"> <div class="footer-first-line">
<div class="left-group"> <div class="left-group">
<img
v-if="isThinking"
src="src\assets\img\homePage\tail\think-active.png"
@click="toggleThink"
class="action-btn"
/>
<img
v-else
src="src\assets\img\homePage\tail\think-no-active.png"
@click="toggleThink"
class="action-btn"
/>
<img
src="src\assets\img\homePage\tail\language.png"
@click="changeLanguage"
class="action-btn"
/>
<img
src="src\assets\img\homePage\tail\voice.png"
@click="toggleVoice"
class="action-btn"
/>
<img v-if="isThinking" src="src\assets\img\homePage\tail\think-active.png" @click="toggleThink"
class="action-btn" />
<img v-else src="src\assets\img\homePage\tail\think-no-active.png" @click="toggleThink"
class="action-btn" />
<img src="src\assets\img\homePage\tail\language.png" @click="changeLanguage" class="action-btn" />
<img v-if="isVoice" src="src\assets\img\homePage\tail\voice.png" @click="toggleVoice" class="action-btn" />
<img v-else src="src\assets\img\homePage\tail\voice-no-active.png" @click="toggleVoice"
class="action-btn" />
</div> </div>
<img
src="src\assets\img\homePage\tail\send.png"
@click="sendMessage"
class="action-btn send-btn"
/>
<img src="src\assets\img\homePage\tail\send.png" @click="sendMessage" class="action-btn send-btn" />
</div> </div>
<!-- 第二行输入框 --> <!-- 第二行输入框 -->
<div class="footer-second-line"> <div class="footer-second-line">
<img src="src\assets\img\homePage\tail\msg.png" class="msg-icon" /> <img src="src\assets\img\homePage\tail\msg.png" class="msg-icon" />
<el-input
type="textarea"
v-model="message"
:autosize="{ minRows: 1, maxRows: 4 }"
placeholder="给AI小财神发消息..."
class="msg-input"
@keydown.enter.exact.prevent="isLoading ? null : sendMessage()"
resize="none"
>
<el-input type="textarea" v-model="message" :autosize="{ minRows: 1, maxRows: 4 }" placeholder="给AI小财神发消息..."
class="msg-input" @keydown.enter.exact.prevent="isLoading ? null : sendMessage()" resize="none">
</el-input> </el-input>
</div> </div>
</el-footer> </el-footer>
</el-container> </el-container>
<!-- 弹窗 --> <!-- 弹窗 -->
<!-- 新增弹窗组件 --> <!-- 新增弹窗组件 -->
<el-dialog v-model="dialogVisible" width="500">
<el-dialog v-model="dialogVisible" width="65%">
<!-- 自定义标题插槽实现居中显示 --> <!-- 自定义标题插槽实现居中显示 -->
<template #header> <template #header>
<div style="text-align: center"> <div style="text-align: center">
@ -270,10 +316,7 @@ onMounted(() => {
<template #footer> <template #footer>
<!-- 添加一个div来包裹按钮并设置样式使其居中 --> <!-- 添加一个div来包裹按钮并设置样式使其居中 -->
<div style="text-align: center"> <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="dialogVisible = false">
去充值 去充值
</el-button> </el-button>
</div> </div>
@ -313,9 +356,11 @@ onMounted(() => {
} }
.tab-content { .tab-content {
/* height: 100%; */
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
scroll-behavior: smooth; /* 添加平滑滚动效果 */
scroll-behavior: smooth;
/* 添加平滑滚动效果 */
} }
@media (max-width: 768px) { @media (max-width: 768px) {

Loading…
Cancel
Save