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