28 changed files with 3054 additions and 1 deletions
			
			
		- 
					11.env.development
 - 
					19.env.production
 - 
					24.gitignore
 - 
					3.vscode/extensions.json
 - 
					14README.md
 - 
					17index.html
 - 
					1710package-lock.json
 - 
					22package.json
 - 
					1public/vite.svg
 - 
					16src/App.vue
 - 
					44src/api/sword.js
 - 
					BINsrc/assets/img/avatar/小柒.png
 - 
					BINsrc/assets/img/avatar/超级云脑按钮.png
 - 
					50src/assets/js/useAppBridge.js
 - 
					159src/assets/js/useProjectTracking.js
 - 
					1src/assets/vue.svg
 - 
					43src/components/HelloWorld.vue
 - 
					10src/config/env.development.js
 - 
					9src/config/env.production.js
 - 
					32src/config/index.js
 - 
					14src/main.js
 - 
					20src/router/index.js
 - 
					65src/store/userPermissionCode.js
 - 
					79src/style.css
 - 
					130src/utils/request.js
 - 
					43src/utils/storage.js
 - 
					444src/views/chat.vue
 - 
					75vite.config.js
 
@ -0,0 +1,11 @@ | 
			
		|||||
 | 
				# must start with VITE_ | 
			
		||||
 | 
				VITE_ENV='development' | 
			
		||||
 | 
				VITE_OUTPUT_DIR='dev' | 
			
		||||
 | 
				# public path | 
			
		||||
 | 
				VITE_PUBLIC_PATH=/ | 
			
		||||
 | 
				# VITE_APP_API_API = '/api' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				#新数据接口 | 
			
		||||
 | 
				VITE_APP_API_BASE_URL="http://39.101.133.168:8828/link" | 
			
		||||
 | 
				# Whether to open mock | 
			
		||||
 | 
				VITE_USE_MOCK=true | 
			
		||||
@ -0,0 +1,19 @@ | 
			
		|||||
 | 
				# must start with VITE_ | 
			
		||||
 | 
				VITE_ENV = 'production' | 
			
		||||
 | 
				VITE_OUTPUT_DIR = 'dist' | 
			
		||||
 | 
				# public path | 
			
		||||
 | 
				VITE_PUBLIC_PATH = / | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				# Whether to open mock | 
			
		||||
 | 
				VITE_USE_MOCK = true | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				#新数据接口 | 
			
		||||
 | 
				VITE_APP_API_BASE_URL = https://api.homilychart.com/link | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				# Whether to enable gzip or brotli compression | 
			
		||||
 | 
				# Optional: gzip | brotli | none | 
			
		||||
 | 
				# If you need multiple forms, you can use `,` to separate | 
			
		||||
 | 
				VITE_BUILD_COMPRESS = 'none' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				# Whether to delete origin files when using compress, default false | 
			
		||||
 | 
				VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false | 
			
		||||
@ -0,0 +1,24 @@ | 
			
		|||||
 | 
				# Logs | 
			
		||||
 | 
				logs | 
			
		||||
 | 
				*.log | 
			
		||||
 | 
				npm-debug.log* | 
			
		||||
 | 
				yarn-debug.log* | 
			
		||||
 | 
				yarn-error.log* | 
			
		||||
 | 
				pnpm-debug.log* | 
			
		||||
 | 
				lerna-debug.log* | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				node_modules | 
			
		||||
 | 
				dist | 
			
		||||
 | 
				dist-ssr | 
			
		||||
 | 
				*.local | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				# Editor directories and files | 
			
		||||
 | 
				.vscode/* | 
			
		||||
 | 
				!.vscode/extensions.json | 
			
		||||
 | 
				.idea | 
			
		||||
 | 
				.DS_Store | 
			
		||||
 | 
				*.suo | 
			
		||||
 | 
				*.ntvs* | 
			
		||||
 | 
				*.njsproj | 
			
		||||
 | 
				*.sln | 
			
		||||
 | 
				*.sw? | 
			
		||||
@ -0,0 +1,3 @@ | 
			
		|||||
 | 
				{ | 
			
		||||
 | 
				  "recommendations": ["Vue.volar"] | 
			
		||||
 | 
				} | 
			
		||||
@ -1,2 +1,14 @@ | 
			
		|||||
# znkf-vue | 
				 | 
			
		||||
 | 
				# Vue 3 + Vite | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support). | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				npm install  安装依赖 | 
			
		||||
 | 
				npm run dev  运行 | 
			
		||||
 | 
				npm run build 打包 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				npm install router 路由 | 
			
		||||
 | 
				npm install axios 网络请求 | 
			
		||||
 | 
				npm install element-plus --save element-plus组件库 | 
			
		||||
 | 
				npm install @element-plus/icons-vue 组件库图标 | 
			
		||||
@ -0,0 +1,17 @@ | 
			
		|||||
 | 
				<!doctype html> | 
			
		||||
 | 
				<html lang="en"> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<head> | 
			
		||||
 | 
				  <meta charset="UTF-8" /> | 
			
		||||
 | 
				  <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | 
			
		||||
 | 
				  <meta name="viewport" | 
			
		||||
 | 
				    content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" /> | 
			
		||||
 | 
				  <title>智能客服</title> | 
			
		||||
 | 
				</head> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<body> | 
			
		||||
 | 
				  <div id="app"></div> | 
			
		||||
 | 
				  <script type="module" src="/src/main.js"></script> | 
			
		||||
 | 
				</body> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				</html> | 
			
		||||
						
							
						
						
							1710
	
						
						package-lock.json
						
							File diff suppressed because it is too large
							
							
								
									View File
								
							
						
					
				File diff suppressed because it is too large
							
							
								
									View File
								
							
						@ -0,0 +1,22 @@ | 
			
		|||||
 | 
				{ | 
			
		||||
 | 
				  "name": "ics", | 
			
		||||
 | 
				  "private": true, | 
			
		||||
 | 
				  "version": "0.0.0", | 
			
		||||
 | 
				  "type": "module", | 
			
		||||
 | 
				  "scripts": { | 
			
		||||
 | 
				    "dev": "vite --host --port 8080", | 
			
		||||
 | 
				    "build": "vite build", | 
			
		||||
 | 
				    "preview": "vite preview" | 
			
		||||
 | 
				  }, | 
			
		||||
 | 
				  "dependencies": { | 
			
		||||
 | 
				    "@element-plus/icons-vue": "^2.3.1", | 
			
		||||
 | 
				    "axios": "^1.7.9", | 
			
		||||
 | 
				    "element-plus": "^2.9.4", | 
			
		||||
 | 
				    "vue": "^3.5.13", | 
			
		||||
 | 
				    "vue-router": "^4.5.0" | 
			
		||||
 | 
				  }, | 
			
		||||
 | 
				  "devDependencies": { | 
			
		||||
 | 
				    "@vitejs/plugin-vue": "^5.2.1", | 
			
		||||
 | 
				    "vite": "^6.1.0" | 
			
		||||
 | 
				  } | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1 @@ | 
			
		|||||
 | 
				<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg> | 
			
		||||
@ -0,0 +1,16 @@ | 
			
		|||||
 | 
				<script setup> | 
			
		||||
 | 
				import { ref, provide, onMounted } from 'vue' | 
			
		||||
 | 
				// import { useProjectTracking } from './assets/js/useProjectTracking.js' | 
			
		||||
 | 
				// 项目路由前缀 | 
			
		||||
 | 
				const projectRoutes = [ | 
			
		||||
 | 
				  '/chat' | 
			
		||||
 | 
				] | 
			
		||||
 | 
				// 项目追踪 | 
			
		||||
 | 
				// useProjectTracking(projectRoutes) | 
			
		||||
 | 
				// alert(location.href) | 
			
		||||
 | 
				</script> | 
			
		||||
 | 
				<template> | 
			
		||||
 | 
				  <router-view></router-view> | 
			
		||||
 | 
				</template> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<style></style> | 
			
		||||
@ -0,0 +1,44 @@ | 
			
		|||||
 | 
				import request from '../utils/request' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 路径
 | 
			
		||||
 | 
				const APIurl = import.meta.env.VITE_APP_API_BASE_URL | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				//统计用户行为接口
 | 
			
		||||
 | 
				export const computedUsersAPI = function (params) { | 
			
		||||
 | 
				  return request({ | 
			
		||||
 | 
				    url: `${APIurl}/BrainStatistics/getStatistic`, | 
			
		||||
 | 
				    method: 'post', | 
			
		||||
 | 
				    data: new URLSearchParams(params), | 
			
		||||
 | 
				    headers: { | 
			
		||||
 | 
				      'Content-Type': 'application/x-www-form-urlencoded' | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				  }) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				//各个模块权限code接口
 | 
			
		||||
 | 
				export const permissionAPI = function (params) { | 
			
		||||
 | 
				  return request({ | 
			
		||||
 | 
				    url: `${APIurl}/api/brain/privilege`, | 
			
		||||
 | 
				    method: 'post', | 
			
		||||
 | 
				    data: new URLSearchParams(params), | 
			
		||||
 | 
				    headers: { | 
			
		||||
 | 
				      'Content-Type': 'application/x-www-form-urlencoded' | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				  }) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				//数据接口
 | 
			
		||||
 | 
				export const dataListAPI = function (params) { | 
			
		||||
 | 
				  // URLSearchParams只接受全部字符串的数据
 | 
			
		||||
 | 
				  // 将传入数据转化成字符串
 | 
			
		||||
 | 
				  const StringParams = new URLSearchParams( | 
			
		||||
 | 
				    Object.entries(params).map(([key, value]) => [key, String(value)]) | 
			
		||||
 | 
				  ) | 
			
		||||
 | 
				  return request({ | 
			
		||||
 | 
				    url: `${APIurl}/api/brain/data`, | 
			
		||||
 | 
				    method: 'post', | 
			
		||||
 | 
				    data: StringParams, | 
			
		||||
 | 
				    headers: { | 
			
		||||
 | 
				      'Content-Type': 'application/x-www-form-urlencoded' | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				  }) | 
			
		||||
 | 
				} | 
			
		||||
| 
		 After Width: 250 | Height: 250 | Size: 68 KiB  | 
| 
		 After Width: 160 | Height: 160 | Size: 30 KiB  | 
@ -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.getData.postMessage(JSON.stringify(params)); | 
			
		||||
 | 
				                    break; | 
			
		||||
 | 
				                case 4: // APP - Android 系统
 | 
			
		||||
 | 
				                    window.android.getData(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 | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				} | 
			
		||||
@ -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 | 
			
		||||
 | 
					} | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1 @@ | 
			
		|||||
 | 
				<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg> | 
			
		||||
@ -0,0 +1,43 @@ | 
			
		|||||
 | 
				<script setup> | 
			
		||||
 | 
				import { ref } from 'vue' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				defineProps({ | 
			
		||||
 | 
				  msg: String, | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const count = ref(0) | 
			
		||||
 | 
				</script> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<template> | 
			
		||||
 | 
				  <h1>{{ msg }}</h1> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  <div class="card"> | 
			
		||||
 | 
				    <button type="button" @click="count++">count is {{ count }}</button> | 
			
		||||
 | 
				    <p> | 
			
		||||
 | 
				      Edit | 
			
		||||
 | 
				      <code>components/HelloWorld.vue</code> to test HMR | 
			
		||||
 | 
				    </p> | 
			
		||||
 | 
				  </div> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  <p> | 
			
		||||
 | 
				    Check out | 
			
		||||
 | 
				    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank" | 
			
		||||
 | 
				      >create-vue</a | 
			
		||||
 | 
				    >, the official Vue + Vite starter | 
			
		||||
 | 
				  </p> | 
			
		||||
 | 
				  <p> | 
			
		||||
 | 
				    Learn more about IDE Support for Vue in the | 
			
		||||
 | 
				    <a | 
			
		||||
 | 
				      href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support" | 
			
		||||
 | 
				      target="_blank" | 
			
		||||
 | 
				      >Vue Docs Scaling up Guide</a | 
			
		||||
 | 
				    >. | 
			
		||||
 | 
				  </p> | 
			
		||||
 | 
				  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p> | 
			
		||||
 | 
				</template> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<style scoped> | 
			
		||||
 | 
				.read-the-docs { | 
			
		||||
 | 
				  color: #888; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				</style> | 
			
		||||
@ -0,0 +1,10 @@ | 
			
		|||||
 | 
				// 本地环境配置
 | 
			
		||||
 | 
				export default { | 
			
		||||
 | 
					env: 'development', | 
			
		||||
 | 
					title: '开发', | 
			
		||||
 | 
					baseUrl: '', // 项目地址
 | 
			
		||||
 | 
					// baseApi: 'http://39.101.133.168:8828/link', // 本地api请求地址,如果使用了代理,设置成'/'
 | 
			
		||||
 | 
					baseApi: '', // 使用了代理设置成'/'
 | 
			
		||||
 | 
					APPSECRET: 'xxx', | 
			
		||||
 | 
					$cdn: 'https://imgs.solui.cn' | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,9 @@ | 
			
		|||||
 | 
				// 正式
 | 
			
		||||
 | 
				export default { | 
			
		||||
 | 
					env: 'production', | 
			
		||||
 | 
					title: '生产', | 
			
		||||
 | 
					baseUrl: '', // 正式项目地址
 | 
			
		||||
 | 
					baseApi: 'https://api.homilychart.com/scms/api', // 正式api请求地址
 | 
			
		||||
 | 
					APPSECRET: 'xxx', | 
			
		||||
 | 
					$cdn: 'https://imgs.solui.cn' | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,32 @@ | 
			
		|||||
 | 
				// 定义配置对象结构
 | 
			
		||||
 | 
				const IConfig = { | 
			
		||||
 | 
				    env: '', // 开发环境
 | 
			
		||||
 | 
				    title: '', // 项目title
 | 
			
		||||
 | 
				    baseUrl: '', // 项目地址
 | 
			
		||||
 | 
				    baseApi: '', // api请求地址
 | 
			
		||||
 | 
				    $cdn: '' // cdn公共资源路径
 | 
			
		||||
 | 
				}; | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const envMap = {}; // 存储不同环境下的配置文件
 | 
			
		||||
 | 
				// import.meta.globEager 批量导入指定目录下的模块-------导入所有的js文件(路径:模块内容)
 | 
			
		||||
 | 
				const globalModules = import.meta.glob('./*.js', { eager: true }); | 
			
		||||
 | 
				Object.entries(globalModules).forEach(([key, value]) => { | 
			
		||||
 | 
				    // key.match(/\.\/env\.(\S*)\.ts/)
 | 
			
		||||
 | 
				    const name = key.replace(/\.\/env\.(.*)\.js$/, '$1'); // 解析出环境名称
 | 
			
		||||
 | 
				    envMap[name] = value; // 模块的内容
 | 
			
		||||
 | 
				}); | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 检查环境变量是否存在
 | 
			
		||||
 | 
				if (!import.meta.env.VITE_ENV) { | 
			
		||||
 | 
				    console.error('VITE_ENV 环境变量未定义,请检查配置。'); | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 根据环境引入不同配置
 | 
			
		||||
 | 
				const config = envMap[import.meta.env.VITE_ENV] ? envMap[import.meta.env.VITE_ENV].default : null; | 
			
		||||
 | 
				if (!config) { | 
			
		||||
 | 
				    console.error(`未找到对应 ${import.meta.env.VITE_ENV} 环境的配置文件,请检查。`); | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				console.log('根据环境引入不同配置', config); | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export { config }; | 
			
		||||
@ -0,0 +1,14 @@ | 
			
		|||||
 | 
				import { createApp } from 'vue' | 
			
		||||
 | 
				import './style.css' | 
			
		||||
 | 
				import App from './App.vue' | 
			
		||||
 | 
				import router from './router' | 
			
		||||
 | 
				import ElementPlus from 'element-plus' | 
			
		||||
 | 
				import 'element-plus/dist/index.css' | 
			
		||||
 | 
				import * as ElementPlusIconsVue from '@element-plus/icons-vue' | 
			
		||||
 | 
				const app = createApp(App) | 
			
		||||
 | 
				for (const [key, component] of Object.entries(ElementPlusIconsVue)) { | 
			
		||||
 | 
				    app.component(key, component) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				app.use(router) | 
			
		||||
 | 
				app.use(ElementPlus) | 
			
		||||
 | 
				app.mount('#app') | 
			
		||||
@ -0,0 +1,20 @@ | 
			
		|||||
 | 
				import { createRouter, createWebHistory } from 'vue-router' | 
			
		||||
 | 
				const routes = [ | 
			
		||||
 | 
					{ | 
			
		||||
 | 
						path: '/', | 
			
		||||
 | 
						redirect: 'chat' | 
			
		||||
 | 
					}, | 
			
		||||
 | 
					{ | 
			
		||||
 | 
						path: '/chat', | 
			
		||||
 | 
						name: 'chat', | 
			
		||||
 | 
						component: () => import('../views/chat.vue') | 
			
		||||
 | 
					}, | 
			
		||||
 | 
					 | 
			
		||||
 | 
				] | 
			
		||||
 | 
				// 创建路由实例
 | 
			
		||||
 | 
				const router = createRouter({ | 
			
		||||
 | 
					history: createWebHistory(import.meta.env.VITE_PUBLIC_PATH), | 
			
		||||
 | 
					routes | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				// 导出
 | 
			
		||||
 | 
				export default router | 
			
		||||
@ -0,0 +1,65 @@ | 
			
		|||||
 | 
				import { ref, onMounted } from 'vue' | 
			
		||||
 | 
				// 修正拼写错误
 | 
			
		||||
 | 
				import { permissionAPI } from '../api/sword' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 封装成一个普通的组合式函数
 | 
			
		||||
 | 
				export const useUserInfo = () => { | 
			
		||||
 | 
				    const userRole = ref('') | 
			
		||||
 | 
				    const loading = ref(false) | 
			
		||||
 | 
				    const isReady = ref(false) | 
			
		||||
 | 
				    const getAppToken = ref('') | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const getQueryVariable = (variable) => { | 
			
		||||
 | 
				        const query = window.location.search.substring(1) | 
			
		||||
 | 
				        console.log('query', query) | 
			
		||||
 | 
				        const vars = query.split('&') | 
			
		||||
 | 
				        console.log('vars', vars) | 
			
		||||
 | 
				        for (let i = 0; i < vars.length; i++) { | 
			
		||||
 | 
				            const pair = vars[i].split('=') | 
			
		||||
 | 
				            if (pair[0] === variable) { | 
			
		||||
 | 
				                return pair[1] | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				        return '' | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const fetchUserInfo = async () => { | 
			
		||||
 | 
				        getAppToken.value = localStorage.getItem('localToken') | 
			
		||||
 | 
				           ? String(localStorage.getItem('localToken')) | 
			
		||||
 | 
				            : '' | 
			
		||||
 | 
				        loading.value = true | 
			
		||||
 | 
				        try { | 
			
		||||
 | 
				            const requestParams = { | 
			
		||||
 | 
				                ...{ token: getAppToken.value || '' } | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				            // 修正拼写错误
 | 
			
		||||
 | 
				            const res = await permissionAPI(requestParams) | 
			
		||||
 | 
				            // 更新状态,移除类型断言
 | 
			
		||||
 | 
				            userRole.value = res.data.userRole | 
			
		||||
 | 
				            isReady.value = true | 
			
		||||
 | 
				        } catch (err) { | 
			
		||||
 | 
				            console.error('Error fetching user data:', err) | 
			
		||||
 | 
				        } finally { | 
			
		||||
 | 
				            loading.value = false | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const init = () => { | 
			
		||||
 | 
				        if (!isReady.value) { | 
			
		||||
 | 
				            fetchUserInfo() | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    onMounted(() => { | 
			
		||||
 | 
				        init() | 
			
		||||
 | 
				    }) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return { | 
			
		||||
 | 
				        userRole, | 
			
		||||
 | 
				        loading, | 
			
		||||
 | 
				        isReady, | 
			
		||||
 | 
				        init, | 
			
		||||
 | 
				        fetchUserInfo, | 
			
		||||
 | 
				        getQueryVariable | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,79 @@ | 
			
		|||||
 | 
				:root { | 
			
		||||
 | 
				  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; | 
			
		||||
 | 
				  line-height: 1.5; | 
			
		||||
 | 
				  font-weight: 400; | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  color-scheme: light dark; | 
			
		||||
 | 
				  color: rgba(255, 255, 255, 0.87); | 
			
		||||
 | 
				  background-color: #242424; | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  font-synthesis: none; | 
			
		||||
 | 
				  text-rendering: optimizeLegibility; | 
			
		||||
 | 
				  -webkit-font-smoothing: antialiased; | 
			
		||||
 | 
				  -moz-osx-font-smoothing: grayscale; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				a { | 
			
		||||
 | 
				  font-weight: 500; | 
			
		||||
 | 
				  color: #646cff; | 
			
		||||
 | 
				  text-decoration: inherit; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				a:hover { | 
			
		||||
 | 
				  color: #535bf2; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				body { | 
			
		||||
 | 
				  margin: 0; | 
			
		||||
 | 
				  display: flex; | 
			
		||||
 | 
				  place-items: center; | 
			
		||||
 | 
				  min-width: 320px; | 
			
		||||
 | 
				  min-height: 100vh; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				h1 { | 
			
		||||
 | 
				  font-size: 3.2em; | 
			
		||||
 | 
				  line-height: 1.1; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				button { | 
			
		||||
 | 
				  border-radius: 8px; | 
			
		||||
 | 
				  border: 1px solid transparent; | 
			
		||||
 | 
				  padding: 0.6em 1.2em; | 
			
		||||
 | 
				  font-size: 1em; | 
			
		||||
 | 
				  font-weight: 500; | 
			
		||||
 | 
				  font-family: inherit; | 
			
		||||
 | 
				  background-color: #1a1a1a; | 
			
		||||
 | 
				  cursor: pointer; | 
			
		||||
 | 
				  transition: border-color 0.25s; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				button:hover { | 
			
		||||
 | 
				  border-color: #646cff; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				button:focus, | 
			
		||||
 | 
				button:focus-visible { | 
			
		||||
 | 
				  outline: 4px auto -webkit-focus-ring-color; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.card { | 
			
		||||
 | 
				  padding: 2em; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				#app { | 
			
		||||
 | 
				  max-width: 1280px; | 
			
		||||
 | 
				  margin: 0; | 
			
		||||
 | 
				  padding: 0; | 
			
		||||
 | 
				  text-align: center; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				@media (prefers-color-scheme: light) { | 
			
		||||
 | 
				  :root { | 
			
		||||
 | 
				    color: #213547; | 
			
		||||
 | 
				    background-color: #ffffff; | 
			
		||||
 | 
				  } | 
			
		||||
 | 
				  a:hover { | 
			
		||||
 | 
				    color: #747bff; | 
			
		||||
 | 
				  } | 
			
		||||
 | 
				  button { | 
			
		||||
 | 
				    background-color: #f9f9f9; | 
			
		||||
 | 
				  } | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,130 @@ | 
			
		|||||
 | 
				import axios from 'axios' | 
			
		||||
 | 
				import { ElMessageBox, ElMessage } from 'element-plus' | 
			
		||||
 | 
				import { config } from '../config' | 
			
		||||
 | 
				import router from '../router' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const ERROR_MESSAGES = { | 
			
		||||
 | 
				    badRequest: '请求错误(400)', | 
			
		||||
 | 
				    unauthorized: '未授权,请登录(401)', | 
			
		||||
 | 
				    forbidden: '拒绝访问(403)', | 
			
		||||
 | 
				    notFound: `请求地址出错: ${'[具体 URL 将在这里被替换]'}`, | 
			
		||||
 | 
				    methodNotAllowed: '请求方法未允许(405)', | 
			
		||||
 | 
				    requestTimeout: '请求超时(408)', | 
			
		||||
 | 
				    internalServerError: '服务器内部错误(500)', | 
			
		||||
 | 
				    notImplemented: '服务未实现(501)', | 
			
		||||
 | 
				    badGateway: '网络错误(502)', | 
			
		||||
 | 
				    serviceUnavailable: '服务不可用(503)', | 
			
		||||
 | 
				    gatewayTimeout: '网络超时(504)', | 
			
		||||
 | 
				    httpVersionNotSupported: 'HTTP 版本不受支持(505)', | 
			
		||||
 | 
				    defaultConnectionError: '连接错误: [原始错误消息]', | 
			
		||||
 | 
				    networkError: '网络异常,请检查后重试!', | 
			
		||||
 | 
				    serverFailure: '连接到服务器失败,请联系管理员' | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const service = axios.create({ | 
			
		||||
 | 
				    baseURL: '/zhinengkefu', // url = base url + request url+
 | 
			
		||||
 | 
				    timeout: 5000, | 
			
		||||
 | 
				    withCredentials: false // send cookies when cross-domain requests
 | 
			
		||||
 | 
				    // headers: {
 | 
			
		||||
 | 
				    // 	// clear cors
 | 
			
		||||
 | 
				    // 	'Cache-Control': 'no-cache',
 | 
			
		||||
 | 
				    // 	Pragma: 'no-cache'
 | 
			
		||||
 | 
				    // }
 | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const setErrorMsg = (error) => { | 
			
		||||
 | 
				    if (error && error.response) { | 
			
		||||
 | 
				        switch (error.response.status) { | 
			
		||||
 | 
				            case 400: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.badRequest | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 401: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.unauthorized | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 403: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.forbidden | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 404: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.notFound.replace( | 
			
		||||
 | 
				                    '[具体 URL 将在这里被替换]', | 
			
		||||
 | 
				                    error.response.config.url | 
			
		||||
 | 
				                ) | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 405: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.methodNotAllowed | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 408: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.requestTimeout | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 500: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.internalServerError | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 501: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.notImplemented | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 502: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.badGateway | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 503: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.serviceUnavailable | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 504: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.gatewayTimeout | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            case 505: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.httpVersionNotSupported | 
			
		||||
 | 
				                break | 
			
		||||
 | 
				            default: | 
			
		||||
 | 
				                error.message = ERROR_MESSAGES.defaultConnectionError.replace( | 
			
		||||
 | 
				                    '[原始错误消息]', | 
			
		||||
 | 
				                    error.message | 
			
		||||
 | 
				                ) | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } else { | 
			
		||||
 | 
				        if (error.message === 'Network Error') { | 
			
		||||
 | 
				            error.message = ERROR_MESSAGES.networkError | 
			
		||||
 | 
				        } else { | 
			
		||||
 | 
				            error.message = ERROR_MESSAGES.serverFailure | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				    return error.message | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// Request interceptors
 | 
			
		||||
 | 
				service.interceptors.request.use( | 
			
		||||
 | 
				    (config) => { | 
			
		||||
 | 
				        // 在此处添加请求头等,如添加 token
 | 
			
		||||
 | 
				        // if (store.state.token) {
 | 
			
		||||
 | 
				        // config.headers['Authorization'] = `Bearer ${store.state.token}`
 | 
			
		||||
 | 
				        // }
 | 
			
		||||
 | 
				        return config | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    (error) => { | 
			
		||||
 | 
				        return Promise.reject(error) | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// Response interceptors
 | 
			
		||||
 | 
				service.interceptors.response.use( | 
			
		||||
 | 
				    async (response) => { | 
			
		||||
 | 
				        // await new Promise(resolve => setTimeout(resolve, 3000)); // 修正拼写错误
 | 
			
		||||
 | 
				        // if (response.config.loadingInstance) {
 | 
			
		||||
 | 
				        //     response.config.loadingInstance.close();
 | 
			
		||||
 | 
				        // }
 | 
			
		||||
 | 
				        const res = response.data | 
			
		||||
 | 
				        if (res.code !== 200) { | 
			
		||||
 | 
				            const errorMsg = res.msg || 'Unknown error' // 修正拼写错误
 | 
			
		||||
 | 
				            ElMessage.error(errorMsg) | 
			
		||||
 | 
				            // return Promise.reject(new Error(res.msg || 'Error'))
 | 
			
		||||
 | 
				        } else { | 
			
		||||
 | 
				            return response.data | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    (error) => { | 
			
		||||
 | 
				        const errorMessage = setErrorMsg(error) | 
			
		||||
 | 
				        ElMessage.error(errorMessage) | 
			
		||||
 | 
				        return Promise.reject(error) | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default service | 
			
		||||
@ -0,0 +1,43 @@ | 
			
		|||||
 | 
				/** | 
			
		||||
 | 
				 * 封装操作localstorage本地存储的方法 | 
			
		||||
 | 
				 */ | 
			
		||||
 | 
				export const storage = { | 
			
		||||
 | 
				    // 存储
 | 
			
		||||
 | 
				    set(key, value) { | 
			
		||||
 | 
				        localStorage.setItem(key, JSON.stringify(value)); | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    // 取出数据
 | 
			
		||||
 | 
				    get(key) { | 
			
		||||
 | 
				        const value = localStorage.getItem(key); | 
			
		||||
 | 
				        if (value && value !== 'undefined' && value !== 'null') { | 
			
		||||
 | 
				            return JSON.parse(value); | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				        return null; | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    // 删除数据
 | 
			
		||||
 | 
				    remove(key) { | 
			
		||||
 | 
				        localStorage.removeItem(key); | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				}; | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				/** | 
			
		||||
 | 
				 * 封装操作sessionStorage本地存储的方法 | 
			
		||||
 | 
				 */ | 
			
		||||
 | 
				export const sessionStorageUtil = { | 
			
		||||
 | 
				    // 存储
 | 
			
		||||
 | 
				    set(key, value) { | 
			
		||||
 | 
				        window.sessionStorage.setItem(key, JSON.stringify(value)); | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    // 取出数据
 | 
			
		||||
 | 
				    get(key) { | 
			
		||||
 | 
				        const value = window.sessionStorage.getItem(key); | 
			
		||||
 | 
				        if (value && value !== 'undefined' && value !== 'null') { | 
			
		||||
 | 
				            return JSON.parse(value); | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				        return null; | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    // 删除数据
 | 
			
		||||
 | 
				    remove(key) { | 
			
		||||
 | 
				        window.sessionStorage.removeItem(key); | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				}; | 
			
		||||
@ -0,0 +1,444 @@ | 
			
		|||||
 | 
				<script setup> | 
			
		||||
 | 
				// 移除未使用的导入 | 
			
		||||
 | 
				import { ref, nextTick, watch,onMounted } from 'vue' | 
			
		||||
 | 
				import { useAppBridge } from '../assets/js/useAppBridge' | 
			
		||||
 | 
				import { useUserInfo } from '../store/userPermissionCode' | 
			
		||||
 | 
				const { getQueryVariable } = useUserInfo() | 
			
		||||
 | 
				const isTokenValid = ref(false) | 
			
		||||
 | 
				const fnGetToken = () => { | 
			
		||||
 | 
				    localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token')))) | 
			
		||||
 | 
				    console.log(localStorage.getItem('localToken')); | 
			
		||||
 | 
				} | 
			
		||||
 | 
				setTimeout(() => { | 
			
		||||
 | 
				    fnGetToken() | 
			
		||||
 | 
				}, 800) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 公共参数 | 
			
		||||
 | 
				const commonParams = { | 
			
		||||
 | 
				    token: localStorage.getItem('localToken'), | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 验证 token | 
			
		||||
 | 
				const validateToken = async () => { | 
			
		||||
 | 
				    const token = localStorage.getItem('localToken') | 
			
		||||
 | 
				    if (!token) { | 
			
		||||
 | 
				        console.error('未找到 token,请重新登录') | 
			
		||||
 | 
				        return false | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				    //   const isValid = await validateTokenAPI(token) | 
			
		||||
 | 
				    //   if (!isValid) { | 
			
		||||
 | 
				    //     console.error('Token 无效,请重新登录') | 
			
		||||
 | 
				    //     return false | 
			
		||||
 | 
				    //   } | 
			
		||||
 | 
				    return true | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 页面加载时验证 token | 
			
		||||
 | 
				validateToken().then((isValid) => { | 
			
		||||
 | 
				    isTokenValid.value = isValid | 
			
		||||
 | 
				    if (!isValid) { | 
			
		||||
 | 
				        // 可以在这里添加跳转到登录页等逻辑 | 
			
		||||
 | 
				        console.error('Token 验证失败,请重新登录') | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 定义Props(可配置参数) | 
			
		||||
 | 
				const props = defineProps({ | 
			
		||||
 | 
				    apiUrl: { | 
			
		||||
 | 
				        type: String, | 
			
		||||
 | 
				        default: 'http://localhost:5000/ask' | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    initialGreeting: { | 
			
		||||
 | 
				        type: String, | 
			
		||||
 | 
				        default: '您好!请问有什么可以帮助您?' | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				// 响应式数据 | 
			
		||||
 | 
				const messages = ref([ | 
			
		||||
 | 
				    { | 
			
		||||
 | 
				        content: props.initialGreeting, | 
			
		||||
 | 
				        sender: 'bot', | 
			
		||||
 | 
				        timestamp: new Date() | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				]) | 
			
		||||
 | 
				const inputMessage = ref('') | 
			
		||||
 | 
				const messageContainer = ref(null) | 
			
		||||
 | 
				const isLoading = ref(false) // 新增:加载状态 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 自动滚动到底部 | 
			
		||||
 | 
				const scrollToBottom = () => { | 
			
		||||
 | 
				    nextTick(() => { | 
			
		||||
 | 
				        if (messageContainer.value) { | 
			
		||||
 | 
				            messageContainer.value.scrollTop = messageContainer.value.scrollHeight | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    }) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				// 发送消息 | 
			
		||||
 | 
				const sendMessage = async () => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if (!isTokenValid.value) { | 
			
		||||
 | 
				        console.error('Token 验证失败,无法发送消息') | 
			
		||||
 | 
				        return | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if (isLoading.value) return; | 
			
		||||
 | 
				    const content = inputMessage.value.trim() | 
			
		||||
 | 
				    if (!content) return | 
			
		||||
 | 
				    // 添加用户消息 | 
			
		||||
 | 
				    messages.value.push({ | 
			
		||||
 | 
				        content, | 
			
		||||
 | 
				        sender: 'user', | 
			
		||||
 | 
				        timestamp: new Date() | 
			
		||||
 | 
				    }) | 
			
		||||
 | 
				    // 清空输入框 | 
			
		||||
 | 
				    inputMessage.value = '' | 
			
		||||
 | 
				    scrollToBottom() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    // 显示加载动画 | 
			
		||||
 | 
				    isLoading.value = true | 
			
		||||
 | 
				    messages.value.push({ | 
			
		||||
 | 
				        content: '我正在思考...', | 
			
		||||
 | 
				        sender: 'bot', | 
			
		||||
 | 
				        timestamp: new Date(), | 
			
		||||
 | 
				        isLoading: true | 
			
		||||
 | 
				    }) | 
			
		||||
 | 
				    scrollToBottom() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    try { | 
			
		||||
 | 
				        // 调用API获取回复 | 
			
		||||
 | 
				        const response = await fetch(props.apiUrl, { | 
			
		||||
 | 
				            method: 'POST', | 
			
		||||
 | 
				            headers: { 'Content-Type': 'application/json' }, | 
			
		||||
 | 
				            body: JSON.stringify({ question: content }) | 
			
		||||
 | 
				        }) | 
			
		||||
 | 
				        const data = await response.json() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        // 移除加载消息 | 
			
		||||
 | 
				        messages.value = messages.value.filter(msg => !msg.isLoading) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        // 添加机器人回复 | 
			
		||||
 | 
				        messages.value.push({ | 
			
		||||
 | 
				            content: data.answer, | 
			
		||||
 | 
				            sender: 'bot', | 
			
		||||
 | 
				            timestamp: new Date() | 
			
		||||
 | 
				        }) | 
			
		||||
 | 
				        scrollToBottom() | 
			
		||||
 | 
				    } catch (error) { | 
			
		||||
 | 
				        console.error('API请求失败:', error) | 
			
		||||
 | 
				        // 移除加载消息 | 
			
		||||
 | 
				        messages.value = messages.value.filter(msg => !msg.isLoading) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        messages.value.push({ | 
			
		||||
 | 
				            content: '服务暂时不可用,请稍后再试', | 
			
		||||
 | 
				            sender: 'bot', | 
			
		||||
 | 
				            timestamp: new Date() | 
			
		||||
 | 
				        }) | 
			
		||||
 | 
				        scrollToBottom() | 
			
		||||
 | 
				    } finally { | 
			
		||||
 | 
				        // 隐藏加载动画 | 
			
		||||
 | 
				        isLoading.value = false | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				} | 
			
		||||
 | 
				// 格式化时间显示 | 
			
		||||
 | 
				const formatTime = (date) => { | 
			
		||||
 | 
				    return new Date(date).toLocaleTimeString([], { | 
			
		||||
 | 
				        hour: '2-digit', | 
			
		||||
 | 
				        minute: '2-digit' | 
			
		||||
 | 
				    }) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				// 自适应输入框高度 | 
			
		||||
 | 
				const adjustInputHeight = () => { | 
			
		||||
 | 
				    const textarea = document.querySelector('.message-input') | 
			
		||||
 | 
				    textarea.style.height = 'auto' | 
			
		||||
 | 
				    textarea.style.height = `${textarea.scrollHeight}px` | 
			
		||||
 | 
				} | 
			
		||||
 | 
				// 监听输入内容变化 | 
			
		||||
 | 
				watch(inputMessage, adjustInputHeight) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 在组件挂载后按顺序执行操作 | 
			
		||||
 | 
				onMounted(async () => { | 
			
		||||
 | 
				    // 先获取 token | 
			
		||||
 | 
				    fnGetToken() | 
			
		||||
 | 
				    // 再验证 token | 
			
		||||
 | 
				    const isValid = await validateToken() | 
			
		||||
 | 
				    isTokenValid.value = isValid | 
			
		||||
 | 
				    if (!isValid) { | 
			
		||||
 | 
				        console.error('Token 验证失败,请重新登录') | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				</script> | 
			
		||||
 | 
				<template> | 
			
		||||
 | 
				    <!-- 聊天容器 --> | 
			
		||||
 | 
				    <div class="chat-container"> | 
			
		||||
 | 
				        <!-- 聊天框头部 --> | 
			
		||||
 | 
				        <div class="chat-header">夺宝奇兵智能客服</div> | 
			
		||||
 | 
				        <!-- 消息展示区域 --> | 
			
		||||
 | 
				        <div class="message-list" ref="messageContainer"> | 
			
		||||
 | 
				            <div v-for="(message, index) in messages" :key="index" class="message-item" :class="[message.sender]"> | 
			
		||||
 | 
				                <!-- 机器人头像 --> | 
			
		||||
 | 
				                <div v-if="message.sender === 'bot'" class="bot-avatar"> | 
			
		||||
 | 
				                    <img src="/src/assets/img/avatar/超级云脑按钮.png" alt="Bot Avatar"> | 
			
		||||
 | 
				                </div> | 
			
		||||
 | 
				                <div class="message-bubble"> | 
			
		||||
 | 
				                    <div class="message-content"> | 
			
		||||
 | 
				                        <!-- 显示加载动画 --> | 
			
		||||
 | 
				                        <span v-if="message.isLoading"> | 
			
		||||
 | 
				                            {{ message.content }} | 
			
		||||
 | 
				                            <el-icon class="is-loading"> | 
			
		||||
 | 
				                                <Loading /> | 
			
		||||
 | 
				                            </el-icon> | 
			
		||||
 | 
				                        </span> | 
			
		||||
 | 
				                        <span v-else>{{ message.content }}</span> | 
			
		||||
 | 
				                    </div> | 
			
		||||
 | 
				                    <div class="message-time">{{ formatTime(message.timestamp) }}</div> | 
			
		||||
 | 
				                </div> | 
			
		||||
 | 
				                <!-- 用户头像 --> | 
			
		||||
 | 
				                <div v-if="message.sender === 'user'" class="user-avatar"> | 
			
		||||
 | 
				                    <img src="/src/assets/img/avatar/小柒.png" alt="User Avatar"> | 
			
		||||
 | 
				                </div> | 
			
		||||
 | 
				            </div> | 
			
		||||
 | 
				        </div> | 
			
		||||
 | 
				        <!-- 输入区域 --> | 
			
		||||
 | 
				        <div class="input-area"> | 
			
		||||
 | 
				            <textarea v-model="inputMessage" @keydown.enter.exact.prevent="isLoading ? null : sendMessage()" | 
			
		||||
 | 
				                placeholder="输入您的问题..." rows="1" class="message-input"></textarea> | 
			
		||||
 | 
				            <el-tooltip content="机器人正在思考" :disabled="!isLoading"> | 
			
		||||
 | 
				                <template #content> | 
			
		||||
 | 
				                    机器人正在思考 | 
			
		||||
 | 
				                </template> | 
			
		||||
 | 
				                <button @click="sendMessage" :disabled="!isTokenValid || isLoading" class="send-button"> | 
			
		||||
 | 
				                    <!-- 使用ElementPlus的发送图标 --> | 
			
		||||
 | 
				                    <span v-if="isLoading"> | 
			
		||||
 | 
				                        <el-icon class="is-loading"> | 
			
		||||
 | 
				                            <Loading /> | 
			
		||||
 | 
				                        </el-icon> | 
			
		||||
 | 
				                    </span> | 
			
		||||
 | 
				                    <span v-else class="send-button-content"> | 
			
		||||
 | 
				                        <el-icon> | 
			
		||||
 | 
				                            <Position /> | 
			
		||||
 | 
				                        </el-icon> | 
			
		||||
 | 
				                        <span> 发送</span> | 
			
		||||
 | 
				                    </span> | 
			
		||||
 | 
				                </button> | 
			
		||||
 | 
				            </el-tooltip> | 
			
		||||
 | 
				        </div> | 
			
		||||
 | 
				        <!-- 未登录覆盖层 --> | 
			
		||||
 | 
				        <div v-if="!isTokenValid" class="overlay"> | 
			
		||||
 | 
				            <div class="overlay-content">用户未登录</div> | 
			
		||||
 | 
				        </div> | 
			
		||||
 | 
				    </div> | 
			
		||||
 | 
				</template> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<style scoped> | 
			
		||||
 | 
				.chat-container { | 
			
		||||
 | 
				    display: flex; | 
			
		||||
 | 
				    flex-direction: column; | 
			
		||||
 | 
				    height: 90vh; | 
			
		||||
 | 
				    width: 90vw; | 
			
		||||
 | 
				    max-width: 800px; | 
			
		||||
 | 
				    margin: 0; | 
			
		||||
 | 
				    border: 1px solid #e0e0e0; | 
			
		||||
 | 
				    border-radius: 12px; | 
			
		||||
 | 
				    background: #f8f9fa; | 
			
		||||
 | 
				    overflow: hidden; | 
			
		||||
 | 
				    /* 新增样式,实现水平和垂直居中 */ | 
			
		||||
 | 
				    position: absolute; | 
			
		||||
 | 
				    top: 50%; | 
			
		||||
 | 
				    left: 50%; | 
			
		||||
 | 
				    transform: translate(-50%, -50%); | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				/* 聊天框头部样式 */ | 
			
		||||
 | 
				.chat-header { | 
			
		||||
 | 
				    background-color: #007bff; | 
			
		||||
 | 
				    color: white; | 
			
		||||
 | 
				    padding: 16px; | 
			
		||||
 | 
				    font-size: 1.2rem; | 
			
		||||
 | 
				    text-align: center; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-list { | 
			
		||||
 | 
				    flex: 1; | 
			
		||||
 | 
				    padding: 20px; | 
			
		||||
 | 
				    overflow-y: auto; | 
			
		||||
 | 
				    background: white; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-item { | 
			
		||||
 | 
				    display: flex; | 
			
		||||
 | 
				    margin-bottom: 16px; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-item.user { | 
			
		||||
 | 
				    justify-content: flex-end; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.bot-avatar { | 
			
		||||
 | 
				    margin-right: 10px; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.bot-avatar img { | 
			
		||||
 | 
				    width: 40px; | 
			
		||||
 | 
				    height: 40px; | 
			
		||||
 | 
				    border-radius: 50%; | 
			
		||||
 | 
				    object-fit: cover; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.user-avatar { | 
			
		||||
 | 
				    margin-left: 10px; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.user-avatar img { | 
			
		||||
 | 
				    width: 40px; | 
			
		||||
 | 
				    height: 40px; | 
			
		||||
 | 
				    border-radius: 50%; | 
			
		||||
 | 
				    object-fit: cover; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-bubble { | 
			
		||||
 | 
				    max-width: 80%; | 
			
		||||
 | 
				    padding: 12px 16px; | 
			
		||||
 | 
				    border-radius: 15px; | 
			
		||||
 | 
				    position: relative; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-content span { | 
			
		||||
 | 
				    display: block; | 
			
		||||
 | 
				    /* 确保元素显示 */ | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-item.user .message-bubble { | 
			
		||||
 | 
				    background: #007bff; | 
			
		||||
 | 
				    color: white; | 
			
		||||
 | 
				    border-bottom-right-radius: 4px; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-item.bot .message-bubble { | 
			
		||||
 | 
				    background: #f1f3f5; | 
			
		||||
 | 
				    color: #212529; | 
			
		||||
 | 
				    border-bottom-left-radius: 4px; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-time { | 
			
		||||
 | 
				    font-size: 0.75rem; | 
			
		||||
 | 
				    color: rgba(255, 255, 255, 0.8); | 
			
		||||
 | 
				    margin-top: 4px; | 
			
		||||
 | 
				    text-align: right; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-item.bot .message-time { | 
			
		||||
 | 
				    color: rgba(0, 0, 0, 0.6); | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.input-area { | 
			
		||||
 | 
				    display: flex; | 
			
		||||
 | 
				    align-items: center; | 
			
		||||
 | 
				    gap: 12px; | 
			
		||||
 | 
				    padding: 16px; | 
			
		||||
 | 
				    border-top: 1px solid #e0e0e0; | 
			
		||||
 | 
				    background: white; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.message-input { | 
			
		||||
 | 
				    flex: 1; | 
			
		||||
 | 
				    padding: 10px 16px; | 
			
		||||
 | 
				    border: 1px solid #e0e0e0; | 
			
		||||
 | 
				    border-radius: 20px; | 
			
		||||
 | 
				    resize: none; | 
			
		||||
 | 
				    max-height: 120px; | 
			
		||||
 | 
				    font-family: inherit; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.send-button { | 
			
		||||
 | 
				    display: flex; | 
			
		||||
 | 
				    align-items: center; | 
			
		||||
 | 
				    justify-content: center; | 
			
		||||
 | 
				    width: 100px; | 
			
		||||
 | 
				    /* 调整宽度以适应文字 */ | 
			
		||||
 | 
				    height: 40px; | 
			
		||||
 | 
				    border: none; | 
			
		||||
 | 
				    border-radius: 20px; | 
			
		||||
 | 
				    /* 调整圆角 */ | 
			
		||||
 | 
				    background: #007bff; | 
			
		||||
 | 
				    color: white; | 
			
		||||
 | 
				    cursor: pointer; | 
			
		||||
 | 
				    transition: all 0.3s ease; | 
			
		||||
 | 
				    /* 添加过渡效果 */ | 
			
		||||
 | 
				    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); | 
			
		||||
 | 
				    /* 添加阴影 */ | 
			
		||||
 | 
				    font-size: 16px; | 
			
		||||
 | 
				    /* 调整字体大小 */ | 
			
		||||
 | 
				    font-weight: 600; | 
			
		||||
 | 
				    /* 调整字体粗细 */ | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.send-button:hover { | 
			
		||||
 | 
				    background: #0056b3; | 
			
		||||
 | 
				    transform: translateY(-2px); | 
			
		||||
 | 
				    /* 悬停时向上移动 */ | 
			
		||||
 | 
				    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); | 
			
		||||
 | 
				    /* 悬停时增加阴影 */ | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				/* 新增加载状态样式 */ | 
			
		||||
 | 
				.loading-state { | 
			
		||||
 | 
				    background: #ccc; | 
			
		||||
 | 
				    cursor: not-allowed; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.send-button-content { | 
			
		||||
 | 
				    display: flex; | 
			
		||||
 | 
				    align-items: center; | 
			
		||||
 | 
				    justify-content: center; | 
			
		||||
 | 
				    gap: 8px; | 
			
		||||
 | 
				    /* 调整文字和图标间距 */ | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				/* .send-button { | 
			
		||||
 | 
				    display: flex; | 
			
		||||
 | 
				    align-items: center; | 
			
		||||
 | 
				    justify-content: center; | 
			
		||||
 | 
				    width: 40px; | 
			
		||||
 | 
				    height: 40px; | 
			
		||||
 | 
				    border: none; | 
			
		||||
 | 
				    border-radius: 50%; | 
			
		||||
 | 
				    background: #007bff; | 
			
		||||
 | 
				    color: white; | 
			
		||||
 | 
				    cursor: pointer; | 
			
		||||
 | 
				    transition: background 0.2s; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.send-button:hover { | 
			
		||||
 | 
				    background: #0056b3; | 
			
		||||
 | 
				} */ | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.send-button svg { | 
			
		||||
 | 
				    width: 20px; | 
			
		||||
 | 
				    height: 20px; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.overlay { | 
			
		||||
 | 
				    position: absolute; | 
			
		||||
 | 
				    top: 0; | 
			
		||||
 | 
				    left: 0; | 
			
		||||
 | 
				    width: 100%; | 
			
		||||
 | 
				    height: 100%; | 
			
		||||
 | 
				    background-color: rgba(255, 255, 255, 0.8); | 
			
		||||
 | 
				    /* 透明度 50% 的白色背景 */ | 
			
		||||
 | 
				    display: flex; | 
			
		||||
 | 
				    justify-content: center; | 
			
		||||
 | 
				    align-items: center; | 
			
		||||
 | 
				    z-index: 1; | 
			
		||||
 | 
				    /* 确保覆盖层在聊天框上方 */ | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.overlay-content { | 
			
		||||
 | 
				    font-size: 36px; | 
			
		||||
 | 
				    font-weight: bold; | 
			
		||||
 | 
				    color: #f60707; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				</style> | 
			
		||||
@ -0,0 +1,75 @@ | 
			
		|||||
 | 
				import { defineConfig, loadEnv  } from 'vite' | 
			
		||||
 | 
				import vue from '@vitejs/plugin-vue' | 
			
		||||
 | 
				import path from 'path' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// https://vite.dev/config/
 | 
			
		||||
 | 
				export default defineConfig(({ mode }) => { | 
			
		||||
 | 
				  // 加载对应模式的环境变量
 | 
			
		||||
 | 
				  const env = loadEnv(mode, process.cwd()); | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  let config = { | 
			
		||||
 | 
				    plugins: [vue()], | 
			
		||||
 | 
				    resolve: { | 
			
		||||
 | 
				      // 配置别名
 | 
			
		||||
 | 
				      alias: { | 
			
		||||
 | 
				        '@': path.resolve(__dirname, 'src'), | 
			
		||||
 | 
				      }, | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    css: { | 
			
		||||
 | 
				      preprocessorOptions: { | 
			
		||||
 | 
				        // 配置全局样式
 | 
			
		||||
 | 
				        scss: { | 
			
		||||
 | 
				          additionalData: `@import "@/styles/variables.scss";`, | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				      }, | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				  }; | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  if (mode === 'development') { | 
			
		||||
 | 
				    // 开发模式下的配置
 | 
			
		||||
 | 
				    config = { | 
			
		||||
 | 
				      ...config, | 
			
		||||
 | 
				      server: { | 
			
		||||
 | 
				        host: env.VITE_DEV_HOST || '0.0.0.0', // 允许通过网络访问,从环境变量获取host
 | 
			
		||||
 | 
				        port: parseInt(env.VITE_DEV_PORT, 10) || 8080, // 自定义端口,从环境变量获取port
 | 
			
		||||
 | 
				        open: true, // 自动打开浏览器
 | 
			
		||||
 | 
				        // 配置代理
 | 
			
		||||
 | 
				        proxy: { | 
			
		||||
 | 
				          '/api': { | 
			
		||||
 | 
				            target: env.VITE_DEV_API_URL, | 
			
		||||
 | 
				            changeOrigin: true, | 
			
		||||
 | 
				            rewrite: (path) => path.replace(/^\/api/, ''), | 
			
		||||
 | 
				          }, | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				      }, | 
			
		||||
 | 
				    }; | 
			
		||||
 | 
				  } else if (mode === 'production') { | 
			
		||||
 | 
				    // 生产模式下的配置
 | 
			
		||||
 | 
				    config = { | 
			
		||||
 | 
				      ...config, | 
			
		||||
 | 
				      build: { | 
			
		||||
 | 
				        terserOptions: { | 
			
		||||
 | 
				          compress: { | 
			
		||||
 | 
				            drop_console: env.VITE_BUILD_DROP_CONSOLE === 'true', // 移除 console 语句,从环境变量获取是否移除console
 | 
			
		||||
 | 
				          }, | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        // 配置打包输出目录
 | 
			
		||||
 | 
				        outDir: 'dist', | 
			
		||||
 | 
				        // 配置资源文件名格式
 | 
			
		||||
 | 
				        assetsDir: 'assets', | 
			
		||||
 | 
				        rollupOptions: { | 
			
		||||
 | 
				          output: { | 
			
		||||
 | 
				            // 手动分割代码
 | 
			
		||||
 | 
				            manualChunks(id) { | 
			
		||||
 | 
				              if (id.includes('node_modules')) { | 
			
		||||
 | 
				                return id.toString().split('node_modules/')[1].split('/')[0].toString(); | 
			
		||||
 | 
				              } | 
			
		||||
 | 
				            }, | 
			
		||||
 | 
				          }, | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				      }, | 
			
		||||
 | 
				    }; | 
			
		||||
 | 
				  } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				  return config; | 
			
		||||
 | 
				}) | 
			
		||||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue