fix: 加载优化、vip状态优化、apikey优化

This commit is contained in:
Gsh
2025-08-04 23:11:42 +08:00
parent 0a2710b865
commit 65d5f5ae86
11 changed files with 172 additions and 93 deletions

View File

@@ -1,10 +1,14 @@
import type { ConfigEnv, PluginOption } from 'vite'; import type { ConfigEnv, PluginOption } from 'vite';
import path from 'node:path'; import path from 'node:path';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import { visualizer } from 'rollup-plugin-visualizer';
import UnoCSS from 'unocss/vite'; import UnoCSS from 'unocss/vite';
import AutoImport from 'unplugin-auto-import/vite'; import AutoImport from 'unplugin-auto-import/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite';
import viteCompression from 'vite-plugin-compression';
import envTyped from 'vite-plugin-env-typed'; import envTyped from 'vite-plugin-env-typed';
import createSvgIcon from './svg-icon'; import createSvgIcon from './svg-icon';
@@ -33,6 +37,26 @@ function plugins({ mode, command }: ConfigEnv): PluginOption[] {
dts: path.join(root, 'types', 'components.d.ts'), dts: path.join(root, 'types', 'components.d.ts'),
}), }),
createSvgIcon(command === 'build'), createSvgIcon(command === 'build'),
// ✅ Gzip 构建产物压缩(仅生产构建)
command === 'build'
&& viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz',
}),
// ✅ 构建分析图(仅生产构建)
command === 'build'
&& visualizer({
filename: './dist/stats.html',
open: false, // 打包后自动打开分析图true 可开启)
gzipSize: true,
brotliSize: true,
}),
]; ];
} }

View File

@@ -111,49 +111,19 @@
<body> <body>
<!-- 加载动画容器 --> <!-- 加载动画容器 -->
<div id="loader" class="loader-container"> <div id="yixinai-loader" class="loader-container">
<div class="loader-title">意心Ai</div> <div class="loader-title">意心Ai</div>
<div class="loader-subtitle">海外地址仅首次访问预计加载约10秒</div> <div class="loader-subtitle">海外地址仅首次访问预计加载约10秒</div>
<div class="loader-logo"> <div class="loader-logo">
<div class="pulse-box"></div> <div class="pulse-box"></div>
</div> </div>
<div class="loader-text" id="progress-text">0%</div>
<div class="loader-progress-bar">
<div id="progress-bar" class="loader-progress"></div>
</div>
</div> </div>
<!-- 加载进度脚本:放在 main.ts 之前,保证先执行 -->
<script>
// 立即执行函数改为更简单的写法,减少解析时间
(function(){
const bar = document.getElementById('progress-bar');
const text = document.getElementById('progress-text');
let progress = 0;
function update() {
progress = Math.min(progress + 2 + Math.random() * 3, 95);
bar.style.width = progress + '%';
text.textContent = Math.floor(progress) + '%';
if(progress < 95) requestAnimationFrame(update);
}
update();
window.finishLoading = function() {
bar.style.width = '100%';
text.textContent = '100%';
setTimeout(() => {
document.getElementById('loader').style.opacity = '0';
setTimeout(() => document.getElementById('loader').remove(), 500);
}, 300);
};
})();
</script>
<div id="app"></div> <div id="app"></div>
<script async type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View File

@@ -67,6 +67,7 @@
"postcss": "8.4.31", "postcss": "8.4.31",
"postcss-html": "1.5.0", "postcss-html": "1.5.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"rollup-plugin-visualizer": "^6.0.3",
"sass-embedded": "^1.89.2", "sass-embedded": "^1.89.2",
"stylelint": "^16.21.1", "stylelint": "^16.21.1",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
@@ -81,6 +82,7 @@
"unplugin-auto-import": "^19.3.0", "unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.8.0", "unplugin-vue-components": "^28.8.0",
"vite": "^6.3.5", "vite": "^6.3.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-env-typed": "^0.0.2", "vite-plugin-env-typed": "^0.0.2",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^3.0.1" "vue-tsc": "^3.0.1"

View File

@@ -117,6 +117,9 @@ importers:
prettier: prettier:
specifier: ^3.6.2 specifier: ^3.6.2
version: 3.6.2 version: 3.6.2
rollup-plugin-visualizer:
specifier: ^6.0.3
version: 6.0.3(rollup@4.41.1)
sass-embedded: sass-embedded:
specifier: ^1.89.2 specifier: ^1.89.2
version: 1.89.2 version: 1.89.2
@@ -159,6 +162,9 @@ importers:
vite: vite:
specifier: ^6.3.5 specifier: ^6.3.5
version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(yaml@2.8.0) version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(yaml@2.8.0)
vite-plugin-compression:
specifier: ^0.5.1
version: 0.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(yaml@2.8.0))
vite-plugin-env-typed: vite-plugin-env-typed:
specifier: ^0.0.2 specifier: ^0.0.2
version: 0.0.2(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(yaml@2.8.0)) version: 0.0.2(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(yaml@2.8.0))
@@ -1813,6 +1819,10 @@ packages:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
define-lazy-prop@2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
engines: {node: '>=8'}
define-properties@1.2.1: define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2731,6 +2741,11 @@ packages:
resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==} resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-docker@2.2.1:
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
engines: {node: '>=8'}
hasBin: true
is-extendable@0.1.1: is-extendable@0.1.1:
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -2859,6 +2874,10 @@ packages:
resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
is-wsl@2.2.0:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
isarray@1.0.0: isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@@ -3414,6 +3433,10 @@ packages:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
open@8.4.2:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -3859,6 +3882,19 @@ packages:
rfdc@1.4.1: rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rollup-plugin-visualizer@6.0.3:
resolution: {integrity: sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
rolldown: 1.x || ^1.0.0-beta
rollup: 2.x || 3.x || 4.x
peerDependenciesMeta:
rolldown:
optional: true
rollup:
optional: true
rollup@4.41.1: rollup@4.41.1:
resolution: {integrity: sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==} resolution: {integrity: sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -4106,6 +4142,10 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
source-map@0.7.6:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
space-separated-tokens@2.0.2: space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
@@ -4606,6 +4646,11 @@ packages:
vfile@6.0.3: vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vite-plugin-compression@0.5.1:
resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==}
peerDependencies:
vite: '>=2.0.0'
vite-plugin-env-typed@0.0.2: vite-plugin-env-typed@0.0.2:
resolution: {integrity: sha512-IloScAeDfhG81diZuvoozs73LvELSG7h3q5Eim035TUbFpou4FanMLpVZqD0lZNH2oMMHILbzx6udmkg3/NFbA==} resolution: {integrity: sha512-IloScAeDfhG81diZuvoozs73LvELSG7h3q5Eim035TUbFpou4FanMLpVZqD0lZNH2oMMHILbzx6udmkg3/NFbA==}
peerDependencies: peerDependencies:
@@ -6634,6 +6679,8 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
gopd: 1.2.0 gopd: 1.2.0
define-lazy-prop@2.0.0: {}
define-properties@1.2.1: define-properties@1.2.1:
dependencies: dependencies:
define-data-property: 1.1.4 define-data-property: 1.1.4
@@ -7800,6 +7847,8 @@ snapshots:
is-accessor-descriptor: 1.0.1 is-accessor-descriptor: 1.0.1
is-data-descriptor: 1.0.1 is-data-descriptor: 1.0.1
is-docker@2.2.1: {}
is-extendable@0.1.1: {} is-extendable@0.1.1: {}
is-extendable@1.0.1: is-extendable@1.0.1:
@@ -7909,6 +7958,10 @@ snapshots:
is-windows@1.0.2: {} is-windows@1.0.2: {}
is-wsl@2.2.0:
dependencies:
is-docker: 2.2.1
isarray@1.0.0: {} isarray@1.0.0: {}
isarray@2.0.5: {} isarray@2.0.5: {}
@@ -8668,6 +8721,12 @@ snapshots:
dependencies: dependencies:
mimic-function: 5.0.1 mimic-function: 5.0.1
open@8.4.2:
dependencies:
define-lazy-prop: 2.0.0
is-docker: 2.2.1
is-wsl: 2.2.0
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
deep-is: 0.1.4 deep-is: 0.1.4
@@ -9109,6 +9168,15 @@ snapshots:
rfdc@1.4.1: {} rfdc@1.4.1: {}
rollup-plugin-visualizer@6.0.3(rollup@4.41.1):
dependencies:
open: 8.4.2
picomatch: 4.0.3
source-map: 0.7.6
yargs: 17.7.2
optionalDependencies:
rollup: 4.41.1
rollup@4.41.1: rollup@4.41.1:
dependencies: dependencies:
'@types/estree': 1.0.7 '@types/estree': 1.0.7
@@ -9388,6 +9456,8 @@ snapshots:
source-map@0.6.1: {} source-map@0.6.1: {}
source-map@0.7.6: {}
space-separated-tokens@2.0.2: {} space-separated-tokens@2.0.2: {}
spawndamnit@3.0.1: spawndamnit@3.0.1:
@@ -9826,7 +9896,7 @@ snapshots:
unctx@2.4.1: unctx@2.4.1:
dependencies: dependencies:
acorn: 8.14.1 acorn: 8.15.0
estree-walker: 3.0.3 estree-walker: 3.0.3
magic-string: 0.30.17 magic-string: 0.30.17
unplugin: 2.3.5 unplugin: 2.3.5
@@ -9865,14 +9935,14 @@ snapshots:
unimport@5.0.1: unimport@5.0.1:
dependencies: dependencies:
acorn: 8.14.1 acorn: 8.15.0
escape-string-regexp: 5.0.0 escape-string-regexp: 5.0.0
estree-walker: 3.0.3 estree-walker: 3.0.3
local-pkg: 1.1.1 local-pkg: 1.1.1
magic-string: 0.30.17 magic-string: 0.30.17
mlly: 1.7.4 mlly: 1.7.4
pathe: 2.0.3 pathe: 2.0.3
picomatch: 4.0.2 picomatch: 4.0.3
pkg-types: 2.1.0 pkg-types: 2.1.0
scule: 1.3.0 scule: 1.3.0
strip-literal: 3.0.0 strip-literal: 3.0.0
@@ -10042,6 +10112,15 @@ snapshots:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
vfile-message: 4.0.2 vfile-message: 4.0.2
vite-plugin-compression@0.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(yaml@2.8.0)):
dependencies:
chalk: 4.1.2
debug: 4.4.1
fs-extra: 10.1.0
vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
vite-plugin-env-typed@0.0.2(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(yaml@2.8.0)): vite-plugin-env-typed@0.0.2(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(yaml@2.8.0)):
dependencies: dependencies:
handlebars: 4.7.8 handlebars: 4.7.8

View File

@@ -8,19 +8,15 @@ import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
import { useUserStore } from '@/stores'; import { useUserStore } from '@/stores';
import { useModelStore } from '@/stores/modules/model'; import { useModelStore } from '@/stores/modules/model';
import { isUserVip } from '@/utils/user';
const router = useRouter(); const router = useRouter();
// 用户角色
const isUserRoleVip = computed(() => {
const roles = userStore.userInfo?.roles ?? [];
return roles.some(role => role.roleCode === 'YiXinAi-Vip');
});
const userStore = useUserStore(); const userStore = useUserStore();
const modelStore = useModelStore(); const modelStore = useModelStore();
// 检查模型是否可用 // 检查模型是否可用
function isModelAvailable(item: GetSessionListVO) { function isModelAvailable(item: GetSessionListVO) {
return isUserRoleVip.value || item.modelId?.includes('DeepSeek-R1-0528') || userStore.userInfo?.user?.userName === 'cc'; return isUserVip() || item.modelId?.includes('DeepSeek-R1-0528') || userStore.userInfo?.user?.userName === 'cc';
} }
onMounted(async () => { onMounted(async () => {
@@ -64,22 +60,22 @@ function handleModelClick(item: GetSessionListVO) {
ElMessageBox.confirm( ElMessageBox.confirm(
` `
<div class="text-center leading-relaxed"> <div class="text-center leading-relaxed">
<h3 class="text-lg font-bold mb-3">${isUserRoleVip.value ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3> <h3 class="text-lg font-bold mb-3">${isUserVip() ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3>
<p class="mb-2"> <p class="mb-2">
${ ${
isUserRoleVip.value isUserVip()
? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!' ? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!'
: '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。' : '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'
} }
</p> </p>
${ ${
isUserRoleVip.value isUserVip()
? '<p class="text-sm text-gray-500">您可随时访问产品页面查看更多特权内容。</p>' ? '<p class="text-sm text-gray-500">您可随时访问产品页面查看更多特权内容。</p>'
: '<p class="text-sm text-gray-500">点击下方按钮,立即升级为 VIP 会员!</p>' : '<p class="text-sm text-gray-500">点击下方按钮,立即升级为 VIP 会员!</p>'
} }
</div> </div>
`, `,
isUserRoleVip.value ? '会员状态' : '会员尊享', isUserVip() ? '会员状态' : '会员尊享',
{ {
confirmButtonText: '前往产品页面', confirmButtonText: '前往产品页面',
cancelButtonText: '关闭', cancelButtonText: '关闭',
@@ -92,7 +88,7 @@ function handleModelClick(item: GetSessionListVO) {
.then(() => { .then(() => {
router.push({ router.push({
name: 'products', // 使用命名路由 name: 'products', // 使用命名路由
query: { from: isUserRoleVip.value ? 'vip' : 'user' }, // 可选:添加来源标识 query: { from: isUserVip() ? 'vip' : 'user' }, // 可选:添加来源标识
}); });
}) })
.catch(() => { .catch(() => {

View File

@@ -3,7 +3,7 @@ import { CircleCheck } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { applyApiKey, getApiKey, getRechargeLog } from '@/api/model/index.ts'; import { applyApiKey, getApiKey } from '@/api/model/index.ts';
import { isUserVip } from '@/utils/user'; import { isUserVip } from '@/utils/user';
const apiKey = ref(''); const apiKey = ref('');
@@ -29,8 +29,6 @@ const router = useRouter();
async function fetchApiKey() { async function fetchApiKey() {
try { try {
const res = await getApiKey(); const res = await getApiKey();
const res2 = await getRechargeLog();
console.log('re2', res2);
if (res.data?.apiKey) { if (res.data?.apiKey) {
apiKey.value = res.data.apiKey; apiKey.value = res.data.apiKey;
displayedKey.value = res.data.apiKey; displayedKey.value = res.data.apiKey;
@@ -43,7 +41,7 @@ async function fetchApiKey() {
// 领取密钥 // 领取密钥
async function handleClaim() { async function handleClaim() {
if (!isUserVip) { if (!isUserVip()) {
ElMessageBox.confirm( ElMessageBox.confirm(
` `
<div class="text-center leading-relaxed"> <div class="text-center leading-relaxed">
@@ -172,7 +170,7 @@ onMounted(async () => {
element-loading-background="rgba(122, 122, 122, 0.8)" element-loading-background="rgba(122, 122, 122, 0.8)"
> >
<!-- 未领取状态 --> <!-- 未领取状态 -->
<div v-if="!apiKey " class="unclaimed-state"> <div v-if="!apiKey" class="unclaimed-state">
<div class="gift-container" @click="handleClaim"> <div class="gift-container" @click="handleClaim">
<div class="gift-box" :class="{ opening: isOpening }"> <div class="gift-box" :class="{ opening: isOpening }">
<div class="gift-lid" /> <div class="gift-lid" />
@@ -239,7 +237,7 @@ onMounted(async () => {
</div> </div>
</div> </div>
<!-- 使用说明 --> <!-- 使用说明 -->
<div class="usage-guide"> <div v-if="apiKey" class="usage-guide">
<el-divider /> <el-divider />
<h3>使用说明</h3> <h3>使用说明</h3>
<div class="guide-content"> <div class="guide-content">

View File

@@ -5,16 +5,12 @@ import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
import { useUserStore } from '@/stores'; import { useUserStore } from '@/stores';
import { useSessionStore } from '@/stores/modules/session'; import { useSessionStore } from '@/stores/modules/session';
import { userProfilePicture } from '@/utils/user'; import { getUserProfilePicture, isUserVip } from '@/utils/user';
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const sessionStore = useSessionStore(); const sessionStore = useSessionStore();
const userRole = computed(() => {
const roles = userStore.userInfo?.roles ?? [];
return roles.some(role => role.roleCode === 'YiXinAi-Vip') ? 'vip' : 'user';
});
// const src = computed( // const src = computed(
// () => userStore.userInfo?.avatar ?? 'https://avatars.githubusercontent.com/u/76239030', // () => userStore.userInfo?.avatar ?? 'https://avatars.githubusercontent.com/u/76239030',
@@ -126,26 +122,25 @@ function handleClick(item: any) {
} }
function openVipGuide() { function openVipGuide() {
const isVip = userRole.value === 'vip' || userStore.userInfo?.user?.userName === 'cc';
ElMessageBox.confirm( ElMessageBox.confirm(
` `
<div class="text-center leading-relaxed"> <div class="text-center leading-relaxed">
<h3 class="text-lg font-bold mb-3">${isVip ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3> <h3 class="text-lg font-bold mb-3">${isUserVip() ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3>
<p class="mb-2"> <p class="mb-2">
${ ${
isVip isUserVip()
? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!' ? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!'
: '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。' : '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'
} }
</p> </p>
${ ${
isVip isUserVip()
? '<p class="text-sm text-gray-500">您可随时访问产品页面查看更多特权内容。</p>' ? '<p class="text-sm text-gray-500">您可随时访问产品页面查看更多特权内容。</p>'
: '<p class="text-sm text-gray-500">点击下方按钮,立即升级为 VIP 会员!</p>' : '<p class="text-sm text-gray-500">点击下方按钮,立即升级为 VIP 会员!</p>'
} }
</div> </div>
`, `,
isVip ? '会员状态' : '会员尊享', isUserVip() ? '会员状态' : '会员尊享',
{ {
confirmButtonText: '前往产品页面', confirmButtonText: '前往产品页面',
cancelButtonText: '关闭', cancelButtonText: '关闭',
@@ -158,7 +153,7 @@ function openVipGuide() {
.then(() => { .then(() => {
router.push({ router.push({
name: 'products', // 使用命名路由 name: 'products', // 使用命名路由
query: { from: userRole.value }, // 可选:添加来源标识 query: { from: isUserVip() ? 'vip' : 'user' }, // 可选:添加来源标识
}); });
}) })
.catch(() => { .catch(() => {
@@ -180,7 +175,7 @@ function openVipGuide() {
<!-- 角色展示 --> <!-- 角色展示 -->
<div> <div>
<span <span
v-if="userRole === 'vip'" v-if="isUserVip()"
class="inline-block px-2 py-0.5 text-xs text-yellow-700 bg-yellow-100 rounded-full font-semibold" class="inline-block px-2 py-0.5 text-xs text-yellow-700 bg-yellow-100 rounded-full font-semibold"
> >
YiXinAI-VIP YiXinAI-VIP
@@ -206,20 +201,20 @@ function openVipGuide() {
:popover-style="popoverStyle" :popover-style="popoverStyle"
> >
<template #trigger> <template #trigger>
<el-avatar :src="userProfilePicture" :size="28" fit="fit" shape="circle" /> <el-avatar :src="getUserProfilePicture()" :size="28" fit="fit" shape="circle" />
</template> </template>
<div class="popover-content-box shadow-lg"> <div class="popover-content-box shadow-lg">
<!-- 用户信息 --> <!-- 用户信息 -->
<div class="user-info-box flex items-center gap-8px p-8px rounded-lg mb-2"> <div class="user-info-box flex items-center gap-8px p-8px rounded-lg mb-2">
<el-avatar :src="userProfilePicture" :size="32" fit="fit" shape="circle" /> <el-avatar :src="getUserProfilePicture()" :size="32" fit="fit" shape="circle" />
<div class="flex flex-col text-sm"> <div class="flex flex-col text-sm">
<div class="font-semibold text-gray-800"> <div class="font-semibold text-gray-800">
{{ userStore.userInfo?.user.nick ?? '未登录用户' }} {{ userStore.userInfo?.user.nick ?? '未登录用户' }}
</div> </div>
<div class="text-xs text-gray-500"> <div class="text-xs text-gray-500">
<span <span
v-if="userRole === 'vip'" v-if="isUserVip()"
class="inline-block px-2 py-0.5 text-xs text-yellow-700 bg-yellow-100 rounded-full font-semibold" class="inline-block px-2 py-0.5 text-xs text-yellow-700 bg-yellow-100 rounded-full font-semibold"
> >
YiXinAI-VIP YiXinAI-VIP

View File

@@ -17,9 +17,27 @@ const designStore = useDesignStore();
/** 获取布局格式 */ /** 获取布局格式 */
const layout = computed((): LayoutType => designStore.layout); const layout = computed((): LayoutType => designStore.layout);
onMounted(() => { onMounted(() => {
console.log('111--'); // 更好的做法是等待所有资源加载
// 通知 index.html 的 loading 动画进度拉满并淡出 window.addEventListener('load', () => {
(window as any)?.finishLoading(); const loader = document.getElementById('yixinai-loader');
if (loader) {
loader.style.opacity = '0';
setTimeout(() => {
loader.style.display = 'none';
}, 500); // 匹配过渡时间
}
});
// 设置超时作为兜底
setTimeout(() => {
const loader = document.getElementById('yixinai-loader');
if (loader) {
loader.style.opacity = '0';
setTimeout(() => {
loader.style.display = 'none';
}, 500);
}
}, 500); // 最多显示0.5秒
}); });
</script> </script>

View File

@@ -17,7 +17,7 @@ import { useChatStore } from '@/stores/modules/chat';
import { useFilesStore } from '@/stores/modules/files'; import { useFilesStore } from '@/stores/modules/files';
import { useModelStore } from '@/stores/modules/model'; import { useModelStore } from '@/stores/modules/model';
import { useUserStore } from '@/stores/modules/user'; import { useUserStore } from '@/stores/modules/user';
import { systemProfilePicture, userProfilePicture } from '@/utils/user.ts'; import { getUserProfilePicture, systemProfilePicture } from '@/utils/user.ts';
import '@/styles/github-markdown.css'; import '@/styles/github-markdown.css';
import '@/styles/yixin-markdown.scss'; import '@/styles/yixin-markdown.scss';
@@ -233,7 +233,7 @@ function addMessage(message: string, isUser: boolean) {
const obj: MessageItem = { const obj: MessageItem = {
key: i, key: i,
avatar: isUser avatar: isUser
? userProfilePicture ? getUserProfilePicture()
: systemProfilePicture, : systemProfilePicture,
avatarSize: '32px', avatarSize: '32px',
role: isUser ? 'user' : 'assistant', role: isUser ? 'user' : 'assistant',

View File

@@ -1,18 +1,12 @@
import type { ChatMessageVo } from '@/api/chat/types'; import type { ChatMessageVo } from '@/api/chat/types';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { getChatList } from '@/api'; import { getChatList } from '@/api';
import { systemProfilePicture, userProfilePicture } from '@/utils/user.ts'; import { getUserProfilePicture, systemProfilePicture } from '@/utils/user.ts';
import { useUserStore } from './user'; import { useUserStore } from './user';
export const useChatStore = defineStore('chat', () => { export const useChatStore = defineStore('chat', () => {
const userStore = useUserStore(); const userStore = useUserStore();
// 用户头像
const avatar = computed(() => {
const userInfo = userStore.userInfo;
return userInfo?.avatar || 'https://avatars.githubusercontent.com/u/76239030?v=4';
});
// 是否开启深度思考 // 是否开启深度思考
const isDeepThinking = ref<boolean>(false); const isDeepThinking = ref<boolean>(false);
@@ -35,7 +29,7 @@ export const useChatStore = defineStore('chat', () => {
// variant: 'shadow', // variant: 'shadow',
// shape: 'corner', // shape: 'corner',
avatar: isUser avatar: isUser
? userProfilePicture ? getUserProfilePicture()
: systemProfilePicture, : systemProfilePicture,
avatarSize: '32px', avatarSize: '32px',
typing: false, typing: false,

View File

@@ -1,18 +1,21 @@
import { useUserStore } from '@/stores/index.js'; import { useUserStore } from '@/stores/index.js';
const userStore = useUserStore(); // 判断是否是 VIP 用户
export function isUserVip(): boolean {
const userStore = useUserStore();
console.log('isUserVip----', userStore);
// 获取用户角色信息 const userRoles = userStore.userInfo?.roles ?? [];
const userRoles = userStore.userInfo?.roles ?? []; return userRoles.some((role: any) => role.roleCode === 'YiXinAi-Vip');
const isUserVip = userRoles.some((role: any) => role.roleCode === 'YiXinAi-Vip'); }
// 用户头像 // 用户头像
const userProfilePicture = userStore.userInfo?.user?.icon ? `${import.meta.env.VITE_WEB_BASE_API}/file/${userStore.userInfo.user.icon}` : `/images/user.png`; export function getUserProfilePicture(): string {
// 系统头像 const userStore = useUserStore();
const systemProfilePicture = `/images/logo.png`; return userStore.userInfo?.user?.icon
? `${import.meta.env.VITE_WEB_BASE_API}/file/${userStore.userInfo.user.icon}`
: `/images/user.png`;
}
export { // 系统头像(可以常量)
isUserVip, export const systemProfilePicture = `/images/logo.png`;
systemProfilePicture,
userProfilePicture,
};