Compare commits

..

No commits in common. "main" and "feature/project_dedtail" have entirely different histories.

38 changed files with 3523 additions and 4668 deletions

48
.gitignore vendored
View File

@ -1,24 +1,24 @@
# Logs # Logs
logs logs
*.log *.log
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
node_modules node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
.idea .idea
.DS_Store .DS_Store
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?

View File

@ -1,8 +1,8 @@
{ {
"hash": "fe1d0f26", "hash": "fe1d0f26",
"configHash": "71314853", "configHash": "71314853",
"lockfileHash": "2984ce1a", "lockfileHash": "2984ce1a",
"browserHash": "72fdc116", "browserHash": "72fdc116",
"optimized": {}, "optimized": {},
"chunks": {} "chunks": {}
} }

View File

@ -1,3 +1,3 @@
{ {
"type": "module" "type": "module"
} }

View File

@ -1,5 +1,5 @@
# Vue 3 + TypeScript + Vite # Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript 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. This template should help get you started developing with Vue 3 and TypeScript 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 the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup). Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

View File

@ -1,79 +1,77 @@
import js from "@eslint/js"; import js from "@eslint/js";
import globals from "globals"; import globals from "globals";
import tseslint from "typescript-eslint"; import tseslint from "typescript-eslint";
import pluginVue from "eslint-plugin-vue"; import pluginVue from "eslint-plugin-vue";
import markdown from "@eslint/markdown"; import markdown from "@eslint/markdown";
import css from "@eslint/css"; import css from "@eslint/css";
import { defineConfig } from "eslint/config"; import { defineConfig } from "eslint/config";
export default defineConfig([ export default defineConfig([
// 解决TypeScript-ESLint与ESLint 9.x类型不兼容问题 // 解决TypeScript-ESLint与ESLint 9.x类型不兼容问题
{ settings: {} }, { settings: {} },
// 基础配置 // 基础配置
{ {
files: ["**/*.{js,mjs,cjs,ts,mts,cts,vue}"], files: ["**/*.{js,mjs,cjs,ts,mts,cts,vue}"],
plugins: { js }, plugins: { js },
extends: ["js/recommended"], extends: ["js/recommended"],
languageOptions: { languageOptions: {
globals: { ...globals.browser, ...globals.node }, globals: { ...globals.browser, ...globals.node },
ecmaVersion: 'latest', ecmaVersion: 'latest',
sourceType: 'module' sourceType: 'module'
} }
}, },
// TypeScript配置 // TypeScript配置
...tseslint.configs.recommended.map(config => ({ ...tseslint.configs.recommended.map(config => ({
...config, ...config,
// 显式指定parser以解决类型不兼容问题 // 显式指定parser以解决类型不兼容问题
languageOptions: { ...config.languageOptions, parser: tseslint.parser } languageOptions: { ...config.languageOptions, parser: tseslint.parser }
})), })),
// Vue 3配置 // Vue 3配置
...pluginVue.configs['flat/recommended'], ...pluginVue.configs['flat/recommended'],
// Vue文件特定配置 // Vue文件特定配置
{ {
files: ["**/*.vue"], files: ["**/*.vue"],
languageOptions: { languageOptions: {
parserOptions: { parserOptions: {
parser: tseslint.parser, parser: tseslint.parser,
project: './tsconfig.json' project: './tsconfig.json'
} }
} }
}, },
// Markdown配置 // Markdown配置
{ {
files: ["**/*.md"], files: ["**/*.md"],
plugins: { markdown }, plugins: { markdown },
language: "markdown/commonmark", language: "markdown/commonmark",
extends: ["markdown/recommended"] extends: ["markdown/recommended"]
}, },
// CSS配置 // CSS配置
{ {
files: ["**/*.css"], files: ["**/*.css"],
plugins: { css }, plugins: { css },
language: "css/css", language: "css/css",
extends: ["css/recommended"] extends: ["css/recommended"]
}, },
// 自定义规则 // 自定义规则
{ {
rules: { rules: {
'vue/multi-word-component-names': 'off', // 允许单字组件名 'vue/multi-word-component-names': 'off', // 允许单字组件名
'@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': 'off', // 关闭未使用变量检查 '@typescript-eslint/no-unused-vars': 'off', // 修改此处,允许声明变量未使用
'@typescript-eslint/no-unused-vars-experimental': 'off', // 关闭实验性检查 'vue/no-v-model-argument': 'off', // 允许v-model参数
'vue/no-unused-vars': 'off', // 关闭Vue未使用变量检查 'vue/no-async-in-computed-properties': 'off', // 禁用有问题的规则
'vue/no-v-model-argument': 'off', // 允许v-model参数 'vue/no-child-content': 'off'// 禁用另一个有问题的规则
'vue/no-async-in-computed-properties': 'off', // 禁用有问题的规则 }
'vue/no-child-content': 'off'// 禁用另一个有问题的规则 },
}
}, // 忽略node_modules和dist目录
{
// 忽略node_modules和dist目录 ignores: ['node_modules/**', 'dist/**', 'public/**']
{ }
ignores: ['node_modules/**', 'dist/**', 'public/**', , 'src/components/chat/ChatAi.ts', 'src/components/chat/MockSSEResponse.ts', 'src/components/chat/ChatAi.vue', 'src/components/chat/ChatAi.tsx', 'src/components/chat/ChatAi.jsx', 'src/components/chat/ChatAi.js', 'src/components/chat/**', 'src/components/chat/ChatAi.js'] ]);
}
]);

View File

@ -2,11 +2,11 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/images/logo.png" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<!--<link rel="preload" as="image" href="/src/assets/images/hero-bg.jpg" fetchpriority="high">--> <!--<link rel="preload" as="image" href="/src/assets/images/hero-bg.jpg" fetchpriority="high">-->
<link rel="preload" as="image" href="images/logo.png" fetchpriority="high"> <link rel="preload" as="image" href="images/hero-bg.jpg" fetchpriority="high">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>优阅工作室</title> <title>Vite + Vue + TS</title>
</head> </head>

4144
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,28 @@
{ {
"name": "company_website", "name": "company_website",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc -b && vite build", "build": "vue-tsc -b && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@tdesign-vue-next/chat": "^0.4.5", "@types/node": "^24.2.1",
"@types/node": "^24.2.1", "@vue/language-core": "^3.0.5",
"@vue/language-core": "^3.0.5", "axios": "^1.11.0",
"axios": "^1.11.0", "pinia": "^3.0.3",
"pinia": "^3.0.3", "tdesign-vue-next": "^1.15.2",
"tdesign-vue-next": "^1.15.2", "vue": "^3.5.18",
"vue": "^3.5.18", "vue-i18n": "^11.1.11",
"vue-i18n": "^11.1.11", "vue-router": "^4.5.1"
"vue-router": "^4.5.1" },
}, "devDependencies": {
"devDependencies": { "@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.7.0",
"@vue/tsconfig": "^0.7.0", "typescript": "~5.8.3",
"less": "^4.4.1", "vite": "^7.1.2",
"less-loader": "^12.3.0", "vue-tsc": "^3.0.5"
"typescript": "~5.8.3", }
"vite": "^7.1.2", }
"vue-tsc": "^3.0.5"
}
}

View File

@ -1,172 +1,170 @@
<!-- src/App.vue --> <!-- src/App.vue -->
<template> <template>
<div id="app"> <div id="app">
<!-- 公共组件 hideHeader false 时显示 --> <!-- 公共组件 hideHeader false 时显示 -->
<Navbar v-if="!route.meta.hideHeader" /> <Navbar v-if="!route.meta.hideHeader" />
<BackToTop v-if="!route.meta.hideHeader" /> <BackToTop v-if="!route.meta.hideHeader" />
<!-- 路由出口根据当前路由渲染对应组件 --> <!-- 路由出口根据当前路由渲染对应组件 -->
<!-- 首页路由渲染 HomePage.vue详情页路由渲染 Project3Zh.vue --> <!-- 首页路由渲染 HomePage.vue详情页路由渲染 Project3Zh.vue -->
<router-view /> <router-view />
<!-- 公共页脚 hideHeader false 时显示 --> <!-- 公共页脚 hideHeader false 时显示 -->
<footer class="footer" v-if="!route.meta.hideHeader"> <footer class="footer" v-if="!route.meta.hideHeader">
<div class="container"> <div class="container">
<div class="footer-content"> <div class="footer-content">
<p>&copy; {{ new Date().getFullYear() }} Company Name. All rights reserved.</p> <p>&copy; {{ new Date().getFullYear() }} Company Name. All rights reserved.</p>
<div class="social-links"> <div class="social-links">
<!-- 社交图标代码不变 --> <!-- 社交图标代码不变 -->
<a href="#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg></a> <a href="#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg></a>
<a href="#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg></a> <a href="#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg></a>
<a href="#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line></svg></a> <a href="#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line></svg></a>
<a href="#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg></a> <a href="#"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg></a>
</div> </div>
</div> </div>
</div> </div>
</footer> </footer>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import BackToTop from './components/common/BackToTop.vue' import BackToTop from './components/common/BackToTop.vue'
import Navbar from './components/common/Navbar.vue' import Navbar from './components/common/Navbar.vue'
import { useLanguageStore } from './store/language' import { useLanguageStore } from './store/language'
import { provide } from 'vue' import { provide } from 'vue'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
// store
const languageStore = useLanguageStore()
// store provide('t', (key: string) => languageStore.t(key))
const languageStore = useLanguageStore()
provide('t', (key: string) => languageStore.t(key)) //
const route = useRoute();
//
const route = useRoute();
</script>
</script> <style scoped>
#app {
<style scoped> font-family: 'Avenir', Helvetica, Arial, sans-serif;
#app { -webkit-font-smoothing: antialiased;
font-family: 'Avenir', Helvetica, Arial, sans-serif; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; color: #2c3e50;
-moz-osx-font-smoothing: grayscale; }
color: #2c3e50;
}
@keyframes fadeInUp {
from {
@keyframes fadeInUp { opacity: 0;
from { transform: translateY(30px);
opacity: 0; }
transform: translateY(30px); to {
} opacity: 1;
to { transform: translateY(0);
opacity: 1; }
transform: translateY(0); }
}
} .hero-content h1 {
font-size: 3.5rem;
.hero-content h1 { margin-bottom: 1rem;
font-size: 3.5rem; background: linear-gradient(90deg, white, var(--primary-light));
margin-bottom: 1rem; -webkit-background-clip: text;
background: linear-gradient(90deg, white, var(--primary-light)); background-clip: text;
-webkit-background-clip: text; color: transparent;
background-clip: text; line-height: 1.1;
color: transparent; letter-spacing: -1px;
line-height: 1.1; }
letter-spacing: -1px;
} .hero-content p {
font-size: 1.2rem;
.hero-content p { margin-bottom: 2.5rem;
font-size: 1.2rem; color: rgba(255, 255, 255, 0.9);
margin-bottom: 2.5rem; max-width: 600px;
color: rgba(255, 255, 255, 0.9); margin-left: auto;
max-width: 600px; margin-right: auto;
margin-left: auto; }
margin-right: auto;
} .cta-buttons {
display: flex;
.cta-buttons { justify-content: center;
display: flex; gap: 1rem;
justify-content: center; }
gap: 1rem;
} .btn-primary, .btn-secondary {
padding: 0.8rem 2rem;
.btn-primary, .btn-secondary { border-radius: 8px;
padding: 0.8rem 2rem; text-decoration: none;
border-radius: 8px; font-weight: 600;
text-decoration: none; transition: var(--transition);
font-weight: 600; display: inline-block;
transition: var(--transition); text-transform: uppercase;
display: inline-block; letter-spacing: 0.5px;
text-transform: uppercase; }
letter-spacing: 0.5px;
} .btn-primary {
background-color: var(--primary-color);
.btn-primary { color: white;
background-color: var(--primary-color); border: none;
color: white; box-shadow: var(--shadow);
border: none; }
box-shadow: var(--shadow);
} .btn-primary:hover {
background-color: var(--primary-dark);
.btn-primary:hover { transform: translateY(-3px);
background-color: var(--primary-dark); box-shadow: var(--shadow-lg);
transform: translateY(-3px); }
box-shadow: var(--shadow-lg);
} .btn-secondary {
background-color: transparent;
.btn-secondary { color: white;
background-color: transparent; border: 2px solid white;
color: white; }
border: 2px solid white;
} .btn-secondary:hover {
background-color: white;
.btn-secondary:hover { color: var(--bg-color);
background-color: white; transform: translateY(-3px);
color: var(--bg-color); }
transform: translateY(-3px);
} .footer {
background-color: #1a1a2e;
.footer { color: white;
background-color: #1a1a2e; padding: 2rem 0;
color: white; }
padding: 2rem 0;
} .footer-content {
display: flex;
.footer-content { justify-content: space-between;
display: flex; align-items: center;
justify-content: space-between; }
align-items: center;
} .social-links {
display: flex;
.social-links { gap: 1rem;
display: flex; }
gap: 1rem;
} .social-links a {
color: white;
.social-links a { transition: color 0.3s;
color: white; }
transition: color 0.3s;
} .social-links a:hover {
color: #4cc9f0;
.social-links a:hover { }
color: #4cc9f0;
} @media (max-width: 768px) {
.hero-content h1 {
@media (max-width: 768px) { font-size: 2.5rem;
.hero-content h1 { }
font-size: 2.5rem;
} .hero-content p {
font-size: 1rem;
.hero-content p { }
font-size: 1rem;
} .footer-content {
flex-direction: column;
.footer-content { text-align: center;
flex-direction: column; gap: 1rem;
text-align: center; }
gap: 1rem; }
} </style>
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -1,41 +1,41 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
defineProps<{ msg: string }>() defineProps<{ msg: string }>()
const count = ref(0) const count = ref(0)
</script> </script>
<template> <template>
<h1>{{ msg }}</h1> <h1>{{ msg }}</h1>
<div class="card"> <div class="card">
<button type="button" @click="count++">count is {{ count }}</button> <button type="button" @click="count++">count is {{ count }}</button>
<p> <p>
Edit Edit
<code>components/HelloWorld.vue</code> to test HMR <code>components/HelloWorld.vue</code> to test HMR
</p> </p>
</div> </div>
<p> <p>
Check out Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank" <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a >create-vue</a
>, the official Vue + Vite starter >, the official Vue + Vite starter
</p> </p>
<p> <p>
Learn more about IDE Support for Vue in the Learn more about IDE Support for Vue in the
<a <a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support" href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank" target="_blank"
>Vue Docs Scaling up Guide</a >Vue Docs Scaling up Guide</a
>. >.
</p> </p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p> <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template> </template>
<style scoped> <style scoped>
.read-the-docs { .read-the-docs {
color: #888; color: #888;
} }
</style> </style>

View File

@ -1,291 +0,0 @@
// 定义类型接口
interface FetchSSEOptions {
success?: (data: any) => void;
fail?: () => void;
complete?: (isOk: boolean, msg?: string) => void;
url?: string;
}
interface SSEEvent {
type: string | null;
data: any;
}
const Api = {
// CHAT_AI_URL: 'http://localhost:8180/chat/generateStreamFlex'
CHAT_AI_URL: '/rag/query_rag'
};
import axios from 'axios'
const getAi=(msg: any):any => {
return axios.get(Api.CHAT_AI_URL, {
params: { message: msg },
headers: { Accept: 'text/event-stream' },
responseType: 'stream'
});
}
export class MockSSEResponse {
private controller!: ReadableStreamDefaultController<Uint8Array>;
private encoder = new TextEncoder();
private stream: ReadableStream<Uint8Array>;
private error: boolean;
private currentPhase: 'reasoning' | 'content' = 'reasoning';
private data: {
reasoning: string;
content: string;
};
private delay: number;
constructor(
data: {
reasoning: string; // 推理内容
content: string; // 正式内容
},
delay: number = 100,
error = false,
) {
this.data = data;
this.delay = delay;
this.error = error;
this.stream = new ReadableStream({
start: (controller) => {
this.controller = controller;
if (!this.error) {
// 如果不是错误情况,则开始推送数据
setTimeout(() => this.pushData(), this.delay); // 延迟开始推送数据
}
},
cancel() {},
});
}
private pushData() {
try {
if (this.currentPhase === 'reasoning') {
// 推送推理内容
if (this.data.reasoning.length > 0) {
const chunk = JSON.stringify({
delta: {
reasoning_content: this.data.reasoning.slice(0, 1),
content: '',
},
finished: false,
});
this.controller.enqueue(this.encoder.encode(chunk));
this.data.reasoning = this.data.reasoning.slice(1);
// 设置下次推送
setTimeout(() => this.pushData(), this.delay);
} else {
// 推理内容推送完成,切换到正式内容
this.currentPhase = 'content';
setTimeout(() => this.pushData(), this.delay); // 立即开始推送正式内容
return;
}
}
if (this.currentPhase === 'content') {
// 推送正式内容
if (this.data.content.length > 0) {
const chunk = JSON.stringify({
delta: {
reasoning_content: '',
content: this.data.content.slice(0, 1),
},
finished: this.data.content.length === 1, // 最后一个字符时标记完成
});
this.controller.enqueue(this.encoder.encode(chunk));
this.data.content = this.data.content.slice(1);
// 设置下次推送
setTimeout(() => this.pushData(), this.delay);
} else {
// const finalPayload = JSON.stringify({
// delta: {
// reasoning_content: '',
// content: '',
// },
// finished: true,
// });
// this.controller.enqueue(this.encoder.encode(`${finalPayload}`));
// 全部内容推送完成
setTimeout(() => this.controller.close(), this.delay);
}
}
} catch {}
}
getResponse(_msg: String): Promise<Response> {
return new Promise((resolve) => {
resolve(getAi(_msg));
});
}
// 封装SSE连接 - 添加降级处理
connectSSE = (url: string, params: any, onMessage?: (data: string) => void, onError?: (error: Event) => void) => {
try {
// 构建带参数的URL
const queryString = Object.keys(params)
.map((key: string) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key]?.message || params[key])}`)
.join('&');
// const API_BASE_URL = 'http://localhost:8080';
const API_BASE_URL = '';
const fullUrl = `${API_BASE_URL}${url}?${queryString}`;
// 检查 EventSource 是否可用
if (typeof EventSource === 'undefined') {
throw new Error('EventSource not supported');
}
// 创建EventSource
const eventSource = new EventSource(fullUrl);
eventSource.onmessage = (event: MessageEvent) => {
const { data } = event;
if (onMessage) {
if (data === '[DONE]') {
onMessage('[DONE]');
} else {
onMessage(data);
}
}
};
eventSource.onerror = (error: Event) => {
if (onError) {
onError(error);
}
eventSource.close();
};
return eventSource;
} catch (error) {
// 降级处理:使用模拟数据
console.warn('EventSource 不可用,使用模拟数据');
// 模拟 SSE 行为
const mockData = [
"您好!",
"我是AI助手。",
"正在处理您的问题...",
"根据分析,",
"这是详细的回答。",
"希望能帮到您!",
"[DONE]"
];
let index = 0;
const interval = setInterval(() => {
if (index < mockData.length) {
if (onMessage) onMessage(mockData[index]);
index++;
} else {
clearInterval(interval);
}
}, 500);
// 返回一个可以关闭的模拟对象
return {
close: () => clearInterval(interval),
readyState: 1
} as EventSource;
}
};
// AI超级智能体聊天 - 实际使用 connectSSE 方法
chatWithManus = (message: any, onMessage?: (data: string) => void, onError?: (error: Event) => void) => {
return this.connectSSE('/rag/query_rag', { message }, onMessage, onError);
};
}
export const fetchSSE = async (options: FetchSSEOptions = {}) => {
const { success, fail, complete, url } = options;
// fetch请求流式接口url需传入接口url和参数
if (!url) {
complete?.(false, 'URL is required');
return;
}
const responsePromise = fetch(url).catch((e: any) => {
const msg = e.toString() || '流式接口异常';
complete?.(false, msg);
return Promise.reject(e);
});
responsePromise
.then((response) => {
if (!response?.ok) {
complete?.(false, response.statusText);
fail?.();
throw new Error('Request failed');
}
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (!reader) {
complete?.(false, 'No reader available');
return;
}
const bufferArr: string[] = [];
let dataText = '';
const event: SSEEvent = { type: null, data: null };
async function processText({ done, value }: ReadableStreamReadResult<Uint8Array>): Promise<void> {
if (done) {
complete?.(true);
return Promise.resolve();
}
const chunk = decoder.decode(value);
const buffers = chunk.toString().replace(/&nbsp;/g, ' ').split(/\r?\n/);
bufferArr.push(...buffers);
let i = 0;
while (i < bufferArr.length) {
const line = bufferArr[i];
if (line) {
dataText += line;
const response = line.slice(5);
if (response === '[DONE]') {
event.type = 'finish';
dataText = '';
} else {
event.type = 'delta';
event.data = response;
// const choices = JSON.parse(response.trim())?.choices?.[0];
// if (choices.finish_reason === 'stop') {
// event.type = 'finish';
// dataText = '';
// } else {
// event.type = 'delta';
// event.data = choices;
// }
}
}
if (event.type && event.data) {
try {
const jsonData = JSON.parse(event.data);
success?.(jsonData);
} catch (e) {
success?.(event.data);
}
event.type = null;
event.data = null;
}
bufferArr.splice(i, 1);
}
return reader!.read().then(processText);
}
return reader.read().then(processText);
})
.catch(() => {
// 处理整个链式调用过程中发生的任何错误
fail?.();
});
};

View File

@ -1,435 +0,0 @@
<template>
<t-space v-if="isVisible1" align="center" @click="visibleModelessDrag = true" class="ai_style" >
<img :src="aiImg" alt="勤怠登录页面" width="36px" height="36px">
</t-space>
<t-config-provider :global-config="globalConfig">
<t-dialog
v-model:visible="visibleModelessDrag"
:footer="false"
id ="abc"
:header="t('ai.title')"
mode="modeless"
showOverlay="true"
draggable
:on-confirm="() => (visibleModelessDrag = false)"
>
<template #body>
<t-chat
layout="both"
style="height: 600px;"
:z-index="3000"
:data="chatList"
:clear-history="chatList.length > 0 && !isStreamLoad"
:text-loading="loading"
:is-stream-load="isStreamLoad"
@on-action="operation"
@clear="clearConfirm"
>
<template #actions="{ item, index }">
<t-chat-action
:content="item.content"
:operation-btn="['good', 'bad', 'replay', 'copy']"
@operation="handleOperation"
/>
</template>
<template #footer>
<t-chat-input v-model="inputValue" :stop-disabled="isStreamLoad" @send="inputEnter" @stop="onStop"> </t-chat-input>
</template>
</t-chat>
</template>
</t-dialog>
</t-config-provider>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, inject, watch, computed } from 'vue'
import aiImg from '@/assets/ai_img.png'
import { useLanguageStore } from '../../store/language'
import { MockSSEResponse } from './ChatAi';
import { globalConfig } from '../../locales/globalConfig'
const visibleModelessDrag = ref(false);
const fetchCancel = ref<any>(null);
const loading = ref(false);
const isStreamLoad = ref(false);
const chatRef = ref<any>(null);
const isShowToBottom = ref(false);
const inputValue = ref('');
const store = useLanguageStore()
// /
const isVisible1 = ref(false)
const handleScroll1 = () => {
isVisible1.value = window.scrollY > 300
};
//
const t = inject<(key: string) => string>('t') || ((key: string) => key)
//
onMounted(() => {
window.addEventListener('scroll', handleScroll1)
})
// EventSource
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll1)
if (eventSource.value) {
eventSource.value.close()
}
})
//
const clearHistoryBtnText = computed(() => {
return t('ai.clearHistory')
})
//
const getFormattedDateTime = (): string => {
const now = new Date();
const year = now.getFullYear().toString().slice(-2);
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
return `[${year}-${month}-${day} ${hours}:${minutes}]`;
};
//
const chatList = ref<any[]>([
{
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'youyueAI',
datetime: getFormattedDateTime(),
content: t('ai.initContent'),
role: 'assistant',
}
]);
const handleOperation = function (type: string, options: any) {
console.log('handleOperation', type, options);
};
const operation = function (type: string, options: any) {
console.log(type, options);
};
const clearConfirm = function () {
chatList.value = [];
};
const onStop = function () {
if (fetchCancel.value) {
fetchCancel.value.controller.close();
loading.value = false;
}
};
const inputEnter = function (inputValue: string) {
if (isStreamLoad.value) {
return;
}
if (!inputValue) return;
const params = {
avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
name: '自己',
datetime: new Date().toDateString(),
content: inputValue,
role: 'user',
};
chatList.value.unshift(params);
//
const params2 = {
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
name: 'youyueAI',
datetime: new Date().toDateString(),
content: '',
role: 'assistant',
};
chatList.value.unshift(params2);
handleData(inputValue);
};
const fetchSSE = async (fetchFn: any, options: any) => {
const response = await fetchFn();
const { success, fail, complete } = options;
if (response.status !== 200) {
complete?.(false, response.statusText);
fail?.();
return;
}
};
//
interface ChatMessage {
role: string;
content: string;
reasoning?: string;
duration?: number;
}
//
const config = {
bufferSize: 5,
flushInterval: 10,
typingSpeed: 50,
};
// 使
const displayText = ref('');
const fullText = ref('');
const isLoading = ref(true);
const eventSource = ref<any>(null);
// 使
let animationFrameId: number | null = null;
const messageBuffer: string[] = [];
let isProcessing = false;
//
const processBuffer = async () => {
isProcessing = true;
const processNextMessage = () => {
if (messageBuffer.length === 0) {
isProcessing = false;
return;
}
const message = messageBuffer.shift();
if (message) {
typeCharacter(message);
}
animationFrameId = requestAnimationFrame(processNextMessage);
};
animationFrameId = requestAnimationFrame(processNextMessage);
};
//
const typeCharacter = (content: string) => {
const index = 0; // 使
setTimeout(() => {
if (chatList.value && chatList.value[0]) {
chatList.value[0].content += content.replace(/&nbsp;/g, ' ');
}
}, config.typingSpeed);
};
// 使 connectSSE
const handleData = async (inputVal?: string) => {
loading.value = true;
isStreamLoad.value = true;
// 使 MockSSEResponse connectSSE
const mockSSE = new MockSSEResponse({
reasoning: "正在分析问题...",
content: "正在连接服务器..."
});
// // 使 connectSSE
// eventSource.value = mockSSE.chatWithManus(
// inputVal,
// (data: string) => {
// //
// if (data === '[DONE]') {
// loading.value = false;
// isStreamLoad.value = false;
// } else {
// //
// if (chatList.value && chatList.value[0]) {
// //
// if (chatList.value[0].content === '' || chatList.value[0].content === '...') {
// chatList.value[0].content = data;
// } else {
// chatList.value[0].content += data;
// }
// }
// }
// },
// (error: Event) => {
// // console.error('SSE Error:', error);
// loading.value = false;
// isStreamLoad.value = false;
// // if (chatList.value && chatList.value[0]) {
// // chatList.value[0].content = '使AI...';
// // // AI
// // setTimeout(() => {
// // if (chatList.value && chatList.value[0]) {
// // chatList.value[0].content = `AI${inputVal}\n\n` +
// // `1. AI\n` +
// // `2. AI\n` +
// // `3. \n\n` +
// // ``;
// // }
// // }, 1000);
// // }
// }
// );
const message = {
message: inputValue.value,
};
const eventSource = mockSSE.chatWithManus(message);
eventSource.onmessage = (event) => {
const { data } = event;
if (data && data !== '[DONE]') {
// messageBuffer.push(data);
loading.value = false;
//
isStreamLoad.value = false;
// lastItem.reasoning += data.replaceAll('&nbsp;', ' ');
// lastItem.content += data.replaceAll('&nbsp;', ' ');
//
messageBuffer.push(data.replaceAll('&nbsp;', ' '));
//
if (!isProcessing) {
processBuffer();
}
// //
// const combinedText = messageBuffer.join('');
// //
// const lastChar = data.charAt(data.length - 1);
// const hasCompleteSentence = chineseEndPunctuation.includes(lastChar) || data.includes('\n\n');
// const isLongEnough = combinedText.length > 40;
// if (hasCompleteSentence || isLongEnough) {
// createBubble(combinedText);
// }
}
if (data === '[DONE]') {
//
// if (messageBuffer.length > 0) {
// const remainingContent = messageBuffer.join('');
// createBubble(remainingContent, 'ai-final');
// }
//
isStreamLoad.value = false;
loading.value = false;
//
// connectionStatus.value = 'disconnected';
eventSource.close();
}
};
// SSE
eventSource.onerror = (error) => {
//
isStreamLoad.value = false;
loading.value = false;
eventSource.close();
// console.error('SSE Error:', error);
// connectionStatus.value = 'error';
// eventSource.close();
// //
// if (messageBuffer.length > 0) {
// const remainingContent = messageBuffer.join('');
// createBubble(remainingContent, 'ai-error');
// }
};
//
// eventSource.close = () => {
// // EventSource
// eventSource.close();
// };
};
// watch
watch(
() => store.currentLanguage,
(newLang: string, oldLang: string) => {
// 使 newLang oldLang
console.log('Language changed from', oldLang, 'to', newLang);
chatList.value[0].content = t('ai.initContent');
}
);
</script>
<style scoped lang="less">
/* 应用滚动条样式 */
::-webkit-scrollbar-thumb {
background-color: var(--td-scrollbar-color);
}
::-webkit-scrollbar-thumb:horizontal:hover {
background-color: var(--td-scrollbar-hover-color);
}
::-webkit-scrollbar-track {
background-color: var(--td-scroll-track-color);
}
//
:global(.t-dialog__ctx.t-dialog__ctx--modeless .t-dialog) {
background-color: white;
}
:deep(.t-chat__text) {
text-align: left;
}
</style>
// style
<style>
.t-chat__actions-margin {
display: none;
}
.t-dialog__ctx .t-dialog__wrap {
z-index: 3001;
right: 10;
}
.t-dialog__ctx .t-dialog__position {
justify-content: right;
/* left: 86px; */
position: fixed;
right: 100px;
bottom: 52px;
}
.ai_style{
gap: 16px;
position: fixed;
bottom: 100px;
right: 2rem;
background-color: #d7e6ed;
width: 48px;
height: 48px;
border-radius: 50px;
display: flex;
justify-content: center;
align-items: center;
border: 2px solid #fff;
}
.ai_style:hover {
background-color: var(--primary-dark);
transform: translateY(-3px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
}
@media (max-width: 768px) {
.t-dialog__ctx .t-dialog__position {
justify-content: center;
/* left: 86px; */
position: unset;
}
}
</style>

View File

@ -1,76 +1,75 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue' import { ref, onMounted, onUnmounted } from 'vue'
// / // /
const isVisible = ref(false) const isVisible = ref(false)
// //
const handleScroll = () => { const handleScroll = () => {
// 300px // 300px
isVisible.value = window.scrollY > 300 isVisible.value = window.scrollY > 300
}
}
//
// const scrollToTop = () => {
const scrollToTop = () => { window.scrollTo({
window.scrollTo({ top: 0,
top: 0, behavior: 'smooth'
behavior: 'smooth' })
}) }
}
//
// onMounted(() => {
onMounted(() => { window.addEventListener('scroll', handleScroll)
window.addEventListener('scroll', handleScroll) })
})
//
// onUnmounted(() => {
onUnmounted(() => { window.removeEventListener('scroll', handleScroll)
window.removeEventListener('scroll', handleScroll) })
}) </script>
</script>
<template>
<template> <button
<button class="back-to-top"
class="back-to-top" v-if="isVisible"
v-if="isVisible" @click="scrollToTop"
@click="scrollToTop" aria-label="回到顶部"
aria-label="回到顶部" >
> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="18 15 12 9 6 15"></polyline>
<polyline points="18 15 12 9 6 15"></polyline> </svg>
</svg> </button>
</button> </template>
</template>
<style scoped>
<style scoped> .back-to-top {
.back-to-top { position: fixed;
position: fixed; bottom: 2rem;
bottom: 2rem; right: 2rem;
right: 2rem; background-color: var(--primary-color);
background-color: var(--primary-color); color: white;
color: white; width: 50px;
width: 50px; height: 50px;
height: 50px; border-radius: 50%;
border-radius: 50%; display: flex;
display: flex; align-items: center;
align-items: center; justify-content: center;
justify-content: center; border: none;
border: none; cursor: pointer;
cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transition: all 0.3s ease;
transition: all 0.3s ease; z-index: 1000;
z-index: 1000; }
}
.back-to-top:hover {
.back-to-top:hover { background-color: var(--primary-dark);
background-color: var(--primary-dark); transform: translateY(-3px);
transform: translateY(-3px); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); }
}
.back-to-top svg {
.back-to-top svg { width: 20px;
width: 20px; height: 20px;
height: 20px; }
}
</style> </style>

View File

@ -1,295 +1,295 @@
<template> <template>
<section id="contact" class="contact-section"> <section id="contact" class="contact-section">
<div class="container"> <div class="container">
<h2 class="section-title">{{ t('contact.title') }}</h2> <h2 class="section-title">{{ t('contact.title') }}</h2>
<div class="contact-container"> <div class="contact-container">
<div class="contact-form"> <div class="contact-form">
<form @submit.prevent="submitForm"> <form @submit.prevent="submitForm">
<div class="form-group"> <div class="form-group">
<label for="name">{{ t('contact.name') }}</label> <label for="name">{{ t('contact.name') }}</label>
<input <input
type="text" type="text"
id="name" id="name"
v-model="formData.name" v-model="formData.name"
required required
:placeholder="t('contact.name')" :placeholder="t('contact.name')"
/> />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">{{ t('contact.email') }}</label> <label for="email">{{ t('contact.email') }}</label>
<input <input
type="email" type="email"
id="email" id="email"
v-model="formData.email" v-model="formData.email"
required required
:placeholder="t('contact.email')" :placeholder="t('contact.email')"
/> />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="subject">{{ t('contact.subject') }}</label> <label for="subject">{{ t('contact.subject') }}</label>
<input <input
type="text" type="text"
id="subject" id="subject"
v-model="formData.subject" v-model="formData.subject"
required required
:placeholder="t('contact.subject')" :placeholder="t('contact.subject')"
/> />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="message">{{ t('contact.message') }}</label> <label for="message">{{ t('contact.message') }}</label>
<textarea <textarea
id="message" id="message"
v-model="formData.message" v-model="formData.message"
required required
rows="5" rows="5"
:placeholder="t('contact.message')" :placeholder="t('contact.message')"
></textarea> ></textarea>
</div> </div>
<button type="submit" class="submit-btn">{{ t('contact.send') }}</button> <button type="submit" class="submit-btn">{{ t('contact.send') }}</button>
</form> </form>
<div v-if="showSuccessMessage" class="success-message">{{ t('contact.success') }}</div> <div v-if="showSuccessMessage" class="success-message">{{ t('contact.success') }}</div>
<div v-if="showErrorMessage" class="error-message">{{ t('contact.error') }}</div> <div v-if="showErrorMessage" class="error-message">{{ t('contact.error') }}</div>
</div> </div>
<div class="contact-info"> <div class="contact-info">
<div class="info-item"> <div class="info-item">
<div class="info-icon"> <div class="info-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path> <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
<polyline points="22,6 12,13 2,6"></polyline> <polyline points="22,6 12,13 2,6"></polyline>
</svg> </svg>
</div> </div>
<div class="info-text"> <div class="info-text">
<h3>Email</h3> <h3>Email</h3>
<p>2187434671@qq.com</p> <p>2187434671@qq.com</p>
</div> </div>
</div> </div>
<div class="info-item"> <div class="info-item">
<div class="info-icon"> <div class="info-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path> <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
</svg> </svg>
</div> </div>
<div class="info-text"> <div class="info-text">
<h3>Phone</h3> <h3>Phone</h3>
<p>(86)15668601400</p> <p>(86)15668601400</p>
</div> </div>
</div> </div>
<div class="info-item"> <div class="info-item">
<div class="info-icon"> <div class="info-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="9" width="18" height="13" rx="2" ry="2"></rect> <rect x="3" y="9" width="18" height="13" rx="2" ry="2"></rect>
<path d="M7 10v3"></path> <path d="M7 10v3"></path>
<path d="M17 10v3"></path> <path d="M17 10v3"></path>
<path d="M9 22V12h6v10"></path> <path d="M9 22V12h6v10"></path>
</svg> </svg>
</div> </div>
<div class="info-text"> <div class="info-text">
<h3>Address</h3> <h3>Address</h3>
<p>No. 28, Dayou Tianyuan, Hi-tech Industrial Park, Dalian, Liaoning Province, China</p> <p>No. 28, Dayou Tianyuan, Hi-tech Industrial Park, Dalian, Liaoning Province, China</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, inject } from 'vue' import { ref, inject } from 'vue'
import { EmailService } from '../../services/emailService' import { EmailService } from '../../services/emailService'
export interface FormData { export interface FormData {
name: string name: string
email: string email: string
subject: string subject: string
message: string message: string
} }
const formData = ref<FormData>({ const formData = ref<FormData>({
name: '', name: '',
email: '', email: '',
subject: '', subject: '',
message: '', message: '',
}) })
const showSuccessMessage = ref(false) const showSuccessMessage = ref(false)
const showErrorMessage = ref(false) const showErrorMessage = ref(false)
const isSubmitting = ref(false) const isSubmitting = ref(false)
// //
const t = inject<(key: string) => string>('t') || ((key) => key) const t = inject<(key: string) => string>('t') || ((key) => key)
const submitForm = async () => { const submitForm = async () => {
isSubmitting.value = true isSubmitting.value = true
showSuccessMessage.value = false showSuccessMessage.value = false
showErrorMessage.value = false showErrorMessage.value = false
try { try {
const response = await EmailService.sendEmail(formData.value) const response = await EmailService.sendEmail(formData.value)
if (response.success) { if (response.success) {
showSuccessMessage.value = true showSuccessMessage.value = true
// //
formData.value = { formData.value = {
name: '', name: '',
email: '', email: '',
subject: '', subject: '',
message: '', message: '',
} }
// 5 // 5
setTimeout(() => { setTimeout(() => {
showSuccessMessage.value = false showSuccessMessage.value = false
}, 5000) }, 5000)
} else { } else {
showErrorMessage.value = true showErrorMessage.value = true
} }
} catch (error) { } catch (error) {
console.error('Error submitting form:', error) console.error('Error submitting form:', error)
showErrorMessage.value = true showErrorMessage.value = true
} finally { } finally {
isSubmitting.value = false isSubmitting.value = false
} }
} }
</script> </script>
<style scoped> <style scoped>
.contact-section { .contact-section {
padding: 5rem 0; padding: 5rem 0;
background-color: white; background-color: white;
} }
.container { .container {
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
padding: 0 1.5rem; padding: 0 1.5rem;
} }
.section-title { .section-title {
text-align: center; text-align: center;
font-size: 2.5rem; font-size: 2.5rem;
margin-bottom: 3rem; margin-bottom: 3rem;
color: #1a1a2e; color: #1a1a2e;
} }
.contact-container { .contact-container {
display: flex; display: flex;
gap: 3rem; gap: 3rem;
flex-wrap: wrap; flex-wrap: wrap;
} }
.contact-form { .contact-form {
flex: 1; flex: 1;
min-width: 300px; min-width: 300px;
background-color: #f8f9fa; background-color: #f8f9fa;
padding: 2rem; padding: 2rem;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
} }
.form-group { .form-group {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
color: #1a1a2e; color: #1a1a2e;
font-weight: bold; font-weight: bold;
text-align: left; /*label文字居左 */ text-align: left; /*label文字居左 */
display: block; /* 确保标签独占一行,更好地控制对齐 */ display: block; /* 确保标签独占一行,更好地控制对齐 */
} }
.form-group input, .form-group input,
.form-group textarea { .form-group textarea {
width: 100%; width: 100%;
padding: 0.8rem; padding: 0.8rem;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
font-size: 1rem; font-size: 1rem;
} }
.submit-btn { .submit-btn {
background-color: #4cc9f0; background-color: #4cc9f0;
color: white; color: white;
border: none; border: none;
padding: 0.8rem 1.5rem; padding: 0.8rem 1.5rem;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
transition: background-color 0.3s; transition: background-color 0.3s;
} }
.submit-btn:hover { .submit-btn:hover {
background-color: #4361ee; background-color: #4361ee;
} }
.success-message { .success-message {
margin-top: 1rem; margin-top: 1rem;
padding: 1rem; padding: 1rem;
background-color: #d4edda; background-color: #d4edda;
color: #155724; color: #155724;
border-radius: 4px; border-radius: 4px;
} }
.error-message { .error-message {
margin-top: 1rem; margin-top: 1rem;
padding: 1rem; padding: 1rem;
background-color: #f8d7da; background-color: #f8d7da;
color: #721c24; color: #721c24;
border-radius: 4px; border-radius: 4px;
} }
.contact-info { .contact-info {
flex: 1; flex: 1;
min-width: 300px; min-width: 300px;
} }
.info-item { .info-item {
display: flex; display: flex;
margin-bottom: 2rem; margin-bottom: 2rem;
align-items: flex-start; align-items: flex-start;
} }
/*email,phone,adress左对齐 */ /*email,phone,adress左对齐 */
.info-item .info-text { .info-item .info-text {
text-align: left; text-align: left;
} }
/* email,phone,adress的图标和内容整体向下移动 */ /* email,phone,adress的图标和内容整体向下移动 */
.contact-info { .contact-info {
margin-top: 30px; margin-top: 30px;
} }
.info-icon { .info-icon {
color: #4cc9f0; color: #4cc9f0;
margin-right: 1rem; margin-right: 1rem;
padding-top: 0.3rem; padding-top: 0.3rem;
} }
.info-text h3 { .info-text h3 {
color: #1a1a2e; color: #1a1a2e;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.info-text p { .info-text p {
color: #666; color: #666;
} }
.form-group input, .form-group input,
.form-group textarea { .form-group textarea {
width: calc(100% - 1.6rem); width: calc(100% - 1.6rem);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.section-title { .section-title {
font-size: 2rem; font-size: 2rem;
} }
.contact-container { .contact-container {
flex-direction: column; flex-direction: column;
} }
} }
</style> </style>

View File

@ -1,175 +1,175 @@
<template> <template>
<section id="team" class="team-section"> <section id="team" class="team-section">
<div class="container"> <div class="container">
<h2 class="section-title">{{ t('team.title') }}</h2> <h2 class="section-title">{{ t('team.title') }}</h2>
<div class="team-grid"> <div class="team-grid">
<div v-for="member in teamMembers" :key="member.id" class="team-card"> <div v-for="member in teamMembers" :key="member.id" class="team-card">
<div class="team-photo"> <div class="team-photo">
<img :src="member.photo" :alt="t(member.nameKey)" /> <img :src="member.photo" :alt="t(member.nameKey)" />
</div> </div>
<div class="team-info"> <div class="team-info">
<h3>{{ t(member.nameKey) }}</h3> <h3>{{ t(member.nameKey) }}</h3>
<p class="role">{{ t('team.memberRole') }}: {{ t(member.roleKey) }}</p> <p class="role">{{ t('team.memberRole') }}: {{ t(member.roleKey) }}</p>
<p class="bio">{{ t('team.memberBio') }}: {{ t(member.bioKey) }}</p> <p class="bio">{{ t('team.memberBio') }}: {{ t(member.bioKey) }}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, inject } from 'vue' import { ref, inject } from 'vue'
export interface TeamMember { export interface TeamMember {
id: number id: number
nameKey: string nameKey: string
roleKey: string roleKey: string
bioKey: string bioKey: string
photo: string photo: string
} }
// src/assets // src/assets
// 1@src* // 1@src*
// 2{ eager: true } // 2{ eager: true }
const imageModules = import.meta.glob('../../assets/members*.png', { eager: true }) const imageModules = import.meta.glob('../../assets/members*.png', { eager: true })
// { ID: } // { ID: }
const imageMap: Record<number, string> = {} const imageMap: Record<number, string> = {}
for (const path in imageModules) { for (const path in imageModules) {
// ID project1.pngproject2.png... // ID project1.pngproject2.png...
const match = path.match(/members(\d+)\.png/) const match = path.match(/members(\d+)\.png/)
if (match) { if (match) {
const id = Number(match[1]) const id = Number(match[1])
// Vite default // Vite default
imageMap[id] = (imageModules[path] as { default: string }).default imageMap[id] = (imageModules[path] as { default: string }).default
} }
} }
// //
const teamMembers = ref<TeamMember[]>([ const teamMembers = ref<TeamMember[]>([
{ {
id: 1, id: 1,
nameKey: "team.member1.name", nameKey: "team.member1.name",
roleKey: "team.member1.role", roleKey: "team.member1.role",
bioKey: "team.member1.bio", bioKey: "team.member1.bio",
photo: 'https://picsum.photos/id/1/300/300', photo: 'https://picsum.photos/id/1/300/300',
//photo: imageMap[1], //photo: imageMap[1],
}, },
{ {
id: 2, id: 2,
nameKey: "team.member2.name", nameKey: "team.member2.name",
roleKey: "team.member2.role", roleKey: "team.member2.role",
bioKey: "team.member2.bio", bioKey: "team.member2.bio",
photo: 'https://picsum.photos/id/2/300/300', photo: 'https://picsum.photos/id/2/300/300',
}, },
{ {
id: 3, id: 3,
nameKey: "team.member3.name", nameKey: "team.member3.name",
roleKey: "team.member3.role", roleKey: "team.member3.role",
bioKey: "team.member3.bio", bioKey: "team.member3.bio",
photo: 'https://picsum.photos/id/3/300/300', photo: 'https://picsum.photos/id/3/300/300',
}, },
{ {
id: 4, id: 4,
nameKey: "team.member4.name", nameKey: "team.member4.name",
roleKey: "team.member4.role", roleKey: "team.member4.role",
bioKey: "team.member4.bio", bioKey: "team.member4.bio",
photo: 'https://picsum.photos/id/4/300/300', photo: 'https://picsum.photos/id/4/300/300',
}, },
{ {
id: 5, id: 5,
nameKey: "team.member5.name", nameKey: "team.member5.name",
roleKey: "team.member5.role", roleKey: "team.member5.role",
bioKey: "team.member5.bio", bioKey: "team.member5.bio",
photo: 'https://picsum.photos/id/5/300/300', photo: 'https://picsum.photos/id/5/300/300',
}, },
]) ])
// const store = useLanguageStore() // const store = useLanguageStore()
// //
const t = inject<(key: string) => string>('t') || ((key) => key) const t = inject<(key: string) => string>('t') || ((key) => key)
</script> </script>
<style scoped> <style scoped>
.team-section { .team-section {
padding: 5rem 0; padding: 5rem 0;
background-color: white; background-color: white;
} }
.container { .container {
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
padding: 0 1.5rem; padding: 0 1.5rem;
} }
.section-title { .section-title {
text-align: center; text-align: center;
font-size: 2.5rem; font-size: 2.5rem;
margin-bottom: 3rem; margin-bottom: 3rem;
color: #1a1a2e; color: #1a1a2e;
} }
.team-grid { .team-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem; gap: 2rem;
} }
.team-card { .team-card {
background-color: #f8f9fa; background-color: #f8f9fa;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s; transition: transform 0.3s;
} }
.team-card:hover { .team-card:hover {
transform: translateY(-5px); transform: translateY(-5px);
} }
.team-photo { .team-photo {
width: 100%; width: 100%;
height: 200px; height: 200px;
overflow: hidden; overflow: hidden;
} }
.team-photo img { .team-photo img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
transition: transform 0.3s; transition: transform 0.3s;
} }
.team-card:hover .team-photo img { .team-card:hover .team-photo img {
transform: scale(1.05); transform: scale(1.05);
} }
.team-info { .team-info {
padding: 1.5rem; padding: 1.5rem;
} }
.team-info h3 { .team-info h3 {
color: #1a1a2e; color: #1a1a2e;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-size: 1.5rem; font-size: 1.5rem;
} }
.role { .role {
color: #4cc9f0; color: #4cc9f0;
font-weight: bold; font-weight: bold;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.bio { .bio {
color: #666; color: #666;
line-height: 1.6; line-height: 1.6;
text-align: left; /* 添加这一行,设置文本左对齐 */ text-align: left; /* 添加这一行,设置文本左对齐 */
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.section-title { .section-title {
font-size: 2rem; font-size: 2rem;
} }
} }
</style> </style>

View File

@ -1,21 +1,21 @@
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
import zh from './locales/zh' import zh from './locales/zh'
import en from './locales/en' import en from './locales/en'
import ja from './locales/ja' import ja from './locales/ja'
// 定义语言 messages // 定义语言 messages
const messages = { const messages = {
zh, zh,
en, en,
ja ja
} }
// 创建 i18n 实例 // 创建 i18n 实例
const i18n = createI18n({ const i18n = createI18n({
legacy: false, // 使用 Composition API必须设置为 false legacy: false, // 使用 Composition API必须设置为 false
locale: 'ja', // 默认语言 locale: 'zh', // 默认语言
fallbackLocale: 'zh', // 回退语言 fallbackLocale: 'ja', // 回退语言
messages messages
}) })
export default i18n export default i18n

View File

@ -1,51 +1,44 @@
import enUs from 'tdesign-vue-next/es/locale/en_US'; export default {
export default { nav: {
nav: { home: 'Home',
home: 'Home', about: 'About Us',
about: 'About Us', team: 'Our Team',
team: 'Our Team', projects: 'Projects',
projects: 'Projects', contact: 'Contact Us',
contact: 'Contact Us', },
}, about: {
about: { title: 'About Us',
title: 'About Us', mission: 'Our Mission',
mission: 'Our Mission', vision: 'Our Vision',
vision: 'Our Vision', advantages: 'Our Advantages',
advantages: 'Our Advantages', content: 'We are a professional software development company dedicated to providing high-quality solutions for our clients.',
content: 'We are a professional software development company dedicated to providing high-quality solutions for our clients.', missionContent: 'To deliver innovative technology solutions that drive business growth and digital transformation.',
missionContent: 'To deliver innovative technology solutions that drive business growth and digital transformation.', visionContent: 'To become a leading global provider of software development services, recognized for our technical excellence and customer satisfaction.',
visionContent: 'To become a leading global provider of software development services, recognized for our technical excellence and customer satisfaction.', advantage1: 'Technical Excellence',
advantage1: 'Technical Excellence', advantage2: 'Customer-Centric Approach',
advantage2: 'Customer-Centric Approach', advantage3: 'Innovation',
advantage3: 'Innovation', advantage4: 'Reliability',
advantage4: 'Reliability', },
}, team: {
team: { title: 'Our Team',
title: 'Our Team', memberRole: 'Role',
memberRole: 'Role', memberBio: 'Bio',
memberBio: 'Bio', },
}, projects: {
projects: { title: 'Our Projects',
title: 'Our Projects', viewDetails: 'View Details',
viewDetails: 'View Details', },
}, contact: {
contact: { title: 'Contact Us',
title: 'Contact Us', name: 'Your Name',
name: 'Your Name', email: 'Your Email',
email: 'Your Email', subject: 'Subject',
subject: 'Subject', message: 'Your Message',
message: 'Your Message', send: 'Send Message',
send: 'Send Message', success: 'Message sent successfully!',
success: 'Message sent successfully!', error: 'Failed to send message. Please try again.',
error: 'Failed to send message. Please try again.', },
}, timeline: {
timeline: { title: 'Company History',
title: 'Company History', },
},
ai:{
title: 'AIチャット',
initContent: 'こんにちは、何かお手伝いできることはありますか?',
clearHistory: '履歴をクリア',
},
...enUs.chat
} }

View File

@ -1,57 +0,0 @@
// 导入所需的语言包
import zhConfig from 'tdesign-vue-next/es/locale/zh_CN';
import enConfig from 'tdesign-vue-next/es/locale/en_US';
import jaConfig from 'tdesign-vue-next/es/locale/ja_JP';
import { merge } from 'lodash-es';
import { type GlobalConfigProvider } from 'tdesign-vue-next';
import { ref, watch, computed } from 'vue';
import { useLanguageStore } from '../store/language'
const store = useLanguageStore()
// 定义支持的语言类型
// export type Language = 'zh-CN' | 'en-US' | 'ja-JP';
export type Language = 'zh' | 'en' | 'ja';
// 明确类型为 Language
const currentLanguage = computed<Language>(() => store.currentLanguage);
// 基础自定义配置
const customConfig: GlobalConfigProvider = {
calendar: {},
table: {},
pagination: {},
chat:{},
// 可以添加更多自定义配置
};
// 语言包映射(使用 any 类型来绕过严格的类型检查)
const localeMap: Record<Language, any> = {
'zh': zhConfig,
'en': enConfig,
'ja': jaConfig,
};
// 响应式全局配置
const globalConfig = ref<GlobalConfigProvider>(
merge({}, localeMap[currentLanguage.value] || localeMap['zh'], customConfig) as GlobalConfigProvider
);
// 当语言变化时更新全局配置
watch(currentLanguage, (newLang) => {
// 更新全局配置
const lang = newLang as unknown as Language;
globalConfig.value = merge({}, localeMap[lang] || localeMap['zh'], customConfig) as GlobalConfigProvider;
console.log(globalConfig.value)
});
export {
currentLanguage,
globalConfig
};

View File

@ -1,5 +1,3 @@
import jaJP from 'tdesign-vue-next/es/locale/ja_JP';
export default { export default {
nav: { nav: {
name:'優閲スタジオ', name:'優閲スタジオ',
@ -119,13 +117,6 @@ export default {
timeline: { timeline: {
title: 'タイムライン', title: 'タイムライン',
}, },
ai:{
title: 'AIチャット',
initContent: 'こんにちは、何かお手伝いできることはありますか?',
clearHistory: '履歴をクリア',
},
chat: {
...jaJP.chat
}
} }

View File

@ -1,6 +1,3 @@
import znCh from 'tdesign-vue-next/es/locale/zh_CN';
export default { export default {
nav: { nav: {
name:'优阅工作室', name:'优阅工作室',
@ -116,13 +113,4 @@ export default {
timeline: { timeline: {
title: '项目经历', title: '项目经历',
}, },
ai:{
title: 'AI助手',
initContent: '我是你的ai助手有什么可以帮你的。',
clearHistory: "清空历史记录11",
},
chat: {
...znCh.chat
}
} }

View File

@ -1,33 +1,27 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import './style.css' import './style.css'
import App from './App.vue' import App from './App.vue'
import router from './router/index.ts' // 确保路径正确 import router from './router/index.ts' // 确保路径正确
// 引入Pinia
import { createPinia } from 'pinia'
// 引入TDesign UI组件库 // 引入Pinia
import TDesign from 'tdesign-vue-next' import { createPinia } from 'pinia'
import TDesignChat from '@tdesign-vue-next/chat'; // 引入chat组件 // 引入TDesign UI组件库
import 'tdesign-vue-next/es/style/index.css' import TDesign from 'tdesign-vue-next'
import 'tdesign-vue-next/es/style/index.css'
const app = createApp(App)
// 必须先 use(router),再挂载 const app = createApp(App)
app.use(router) // 必须先 use(router),再挂载
app.use(router)
// 使用Pinia
app.use(createPinia()) // 使用Pinia
app.use(createPinia())
// 使用TDesign
app.use(TDesign)
// 使用TDesign app.mount('#app')
app.use(TDesign)
app.use(TDesignChat) // 临时打印路由列表,确认配置是否生效
console.log('已注册的路由:', router.getRoutes().map(r => r.path));
app.mount('#app')
// 临时打印路由列表,确认配置是否生效
console.log('已注册的路由:', router.getRoutes().map(r => r.path));

View File

@ -1,57 +1,57 @@
// src/router/index.ts // src/router/index.ts
import { createRouter, createWebHistory} from 'vue-router'; import { createRouter, createWebHistory} from 'vue-router';
import Project3Jp from '@/views/ProjectDetail/Project3Jp.vue'; import Project3Jp from '@/views/ProjectDetail/Project3Jp.vue';
import Project3Zh from '@/views/ProjectDetail/Project3Zh.vue'; import Project3Zh from '@/views/ProjectDetail/Project3Zh.vue';
import Project4Jp from '@/views/ProjectDetail/Project4Jp.vue'; import Project4Jp from '@/views/ProjectDetail/Project4Jp.vue';
import Project4Zh from '@/views/ProjectDetail/Project4Zh.vue'; import Project4Zh from '@/views/ProjectDetail/Project4Zh.vue';
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes: [ routes: [
// 首页路由 // 首页路由
{ {
path: '/', path: '/',
name: 'Home', name: 'Home',
component: () => import('@/views/HomePage.vue'), component: () => import('@/views/HomePage.vue'),
meta: { hideHeader: false }, // 主页显示公共组件 meta: { hideHeader: false }, // 主页显示公共组件
}, },
// ✅ 项目详情页 // ✅ 项目详情页
// 项目3 - 中文 // 项目3 - 中文
{ {
path: '/project3/zh', path: '/project3/zh',
name: 'project3Detail', // 中文路由名称 name: 'project3Detail', // 中文路由名称
component: Project3Zh, component: Project3Zh,
meta: { hideHeader: true }, meta: { hideHeader: true },
}, },
// 项目3 - 日文 // 项目3 - 日文
{ {
path: '/project3/jp', path: '/project3/jp',
name: 'project3DetailJp', // 日文路由名称 name: 'project3DetailJp', // 日文路由名称
component: Project3Jp, component: Project3Jp,
meta: { hideHeader: true }, meta: { hideHeader: true },
}, },
// 项目4 - 中文 // 项目4 - 中文
{ {
path: '/project4/zh', path: '/project4/zh',
name: 'project4Detail', // 中文路由名称 name: 'project4Detail', // 中文路由名称
component: Project4Zh, component: Project4Zh,
meta: { hideHeader: true }, meta: { hideHeader: true },
}, },
// 项目4 - 日文 // 项目4 - 日文
{ {
path: '/project4/jp', path: '/project4/jp',
name: 'project4DetailJp', // 日文路由名称 name: 'project4DetailJp', // 日文路由名称
component: Project4Jp, component: Project4Jp,
meta: { hideHeader: true }, meta: { hideHeader: true },
}, },
// 其他路由... // 其他路由...
], ],
}); });
export default router; export default router;

View File

@ -1,40 +1,40 @@
import axios from 'axios' import axios from 'axios'
export class EmailService { export class EmailService {
static async sendEmail(data: { static async sendEmail(data: {
name: string name: string
email: string email: string
subject: string subject: string
message: string message: string
}): Promise<{ }): Promise<{
success: boolean success: boolean
message: string message: string
}> { }> {
try { try {
// 在实际应用中这里应该指向你的后端API // 在实际应用中这里应该指向你的后端API
// 为了演示,我们使用一个模拟的响应 // 为了演示,我们使用一个模拟的响应
// const response = await axios.post('/api/email', data) // const response = await axios.post('/api/email', data)
const response = await axios.post('/api/mail/send ', data) const response = await axios.post('/api/mail/send ', data)
console.log('Email sent:', response.data) console.log('Email sent:', response.data)
// 模拟成功响应 // 模拟成功响应
return { return {
success: true, success: true,
message: 'Email sent successfully', message: 'Email sent successfully',
} }
// 如果需要处理错误,可以取消上面的注释并使用下面的代码 // 如果需要处理错误,可以取消上面的注释并使用下面的代码
// return { // return {
// success: response.data.success, // success: response.data.success,
// message: response.data.message, // message: response.data.message,
// } // }
} catch (error) { } catch (error) {
console.error('Failed to send email:', error) console.error('Failed to send email:', error)
return { return {
success: false, success: false,
message: 'Failed to send email. Please try again later.', message: 'Failed to send email. Please try again later.',
} }
} }
} }
} }

10
src/shims-vue.d.ts vendored
View File

@ -1,6 +1,6 @@
// src/shims-vue.d.ts // src/shims-vue.d.ts
declare module '*.vue' { declare module '*.vue' {
import { DefineComponent } from 'vue' import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any> const component: DefineComponent<{}, {}, any>
export default component export default component
} }

View File

@ -1,49 +1,50 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import zh from '../locales/zh' import zh from '../locales/zh'
import ja from '../locales/ja' import ja from '../locales/ja'
// 获取浏览器语言
function getBrowserLanguage() { // 获取浏览器语言
// 获取浏览器主语言(如从 "zh-CN" 中提取 "zh-CN" 或从 "zh-TW" 中提取 "zh-TW" function getBrowserLanguage() {
const browserLang = navigator.language; // 获取浏览器主语言(如从 "zh-CN" 中提取 "zh-CN" 或从 "zh-TW" 中提取 "zh-TW"
if (browserLang.startsWith('zh-CN')) { const browserLang = navigator.language;
return 'zh'; if (browserLang.startsWith('zh-CN')) {
} return 'zh';
// 默认返回英语 }
return 'ja'; // 默认返回英语
} return 'en';
const lang = getBrowserLanguage() }
export const useLanguageStore = defineStore('language', { const lang = getBrowserLanguage()
state: () => ({ export const useLanguageStore = defineStore('language', {
currentLanguage: lang, // 默认中文 state: () => ({
messages: { currentLanguage: lang, // 默认中文
zh, messages: {
ja, zh,
}, ja,
}), },
getters: { }),
t: (state) => (key: string) => { getters: {
// 简单的键路径解析,如 'nav.home' t: (state) => (key: string) => {
const keys = key.split('.') // 简单的键路径解析,如 'nav.home'
let value = state.messages[state.currentLanguage] const keys = key.split('.')
let value = state.messages[state.currentLanguage]
for (const k of keys) {
if (value && typeof value === 'object' && k in value) { for (const k of keys) {
value = value[k] if (value && typeof value === 'object' && k in value) {
} else { value = value[k]
return key // 如果找不到翻译,返回原始键 } else {
} return key // 如果找不到翻译,返回原始键
} }
}
return value
}, return value
}, },
actions: { },
toggleLanguage() { actions: {
this.currentLanguage = this.currentLanguage === 'zh' ? 'ja' : 'zh' toggleLanguage() {
}, this.currentLanguage = this.currentLanguage === 'zh' ? 'ja' : 'zh'
setLanguage(lang: 'zh' | 'ja') { },
this.currentLanguage = lang setLanguage(lang: 'zh' | 'ja') {
}, this.currentLanguage = lang
}, },
},
}) })

View File

@ -1,178 +1,178 @@
:root { :root {
font-family: 'Inter', system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: 'Inter', system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.6; line-height: 1.6;
font-weight: 400; font-weight: 400;
color-scheme: light dark; color-scheme: light dark;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
background-color: #121212; background-color: #121212;
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
--primary-color: #3b82f6; --primary-color: #3b82f6;
--primary-light: #60a5fa; --primary-light: #60a5fa;
--primary-dark: #2563eb; --primary-dark: #2563eb;
--secondary-color: #10b981; --secondary-color: #10b981;
--text-color: #f3f4f6; --text-color: #f3f4f6;
--text-muted: #9ca3af; --text-muted: #9ca3af;
--bg-color: #121212; --bg-color: #121212;
--bg-card: #1e1e1e; --bg-card: #1e1e1e;
--border-color: #374151; --border-color: #374151;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--transition: all 0.3s ease; --transition: all 0.3s ease;
} }
a { a {
font-weight: 500; font-weight: 500;
color: var(--primary-light); color: var(--primary-light);
text-decoration: inherit; text-decoration: inherit;
transition: var(--transition); transition: var(--transition);
} }
a:hover { a:hover {
color: var(--primary-color); color: var(--primary-color);
text-decoration: underline; text-decoration: underline;
} }
body { body {
margin: 0; margin: 0;
font-family: inherit; font-family: inherit;
line-height: inherit; line-height: inherit;
color: var(--text-color); color: var(--text-color);
background-color: var(--bg-color); background-color: var(--bg-color);
scroll-behavior: smooth; scroll-behavior: smooth;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
margin: 0; margin: 0;
font-weight: 700; font-weight: 700;
line-height: 1.2; line-height: 1.2;
color: var(--text-color); color: var(--text-color);
} }
h1 { h1 {
font-size: 2.5rem; font-size: 2.5rem;
} }
h2 { h2 {
font-size: 2rem; font-size: 2rem;
} }
h3 { h3 {
font-size: 1.5rem; font-size: 1.5rem;
} }
button { button {
border-radius: 8px; border-radius: 8px;
border: 1px solid transparent; border: 1px solid transparent;
padding: 0.6em 1.2em; padding: 0.6em 1.2em;
font-size: 1em; font-size: 1em;
font-weight: 500; font-weight: 500;
font-family: inherit; font-family: inherit;
background-color: var(--primary-color); background-color: var(--primary-color);
color: white; color: white;
cursor: pointer; cursor: pointer;
transition: var(--transition); transition: var(--transition);
box-shadow: var(--shadow); box-shadow: var(--shadow);
} }
button:hover { button:hover {
background-color: var(--primary-dark); background-color: var(--primary-dark);
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
} }
button:focus, button:focus,
button:focus-visible { button:focus-visible {
outline: 2px solid var(--primary-light); outline: 2px solid var(--primary-light);
outline-offset: 2px; outline-offset: 2px;
} }
.card { .card {
padding: 2rem; padding: 2rem;
border-radius: 12px; border-radius: 12px;
background-color: var(--bg-card); background-color: var(--bg-card);
box-shadow: var(--shadow); box-shadow: var(--shadow);
transition: var(--transition); transition: var(--transition);
} }
.card:hover { .card:hover {
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
transform: translateY(-5px); transform: translateY(-5px);
} }
#app { #app {
max-width: 100%; max-width: 100%;
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 0;
text-align: center; text-align: center;
} }
.container { .container {
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
padding: 0 1.5rem; padding: 0 1.5rem;
} }
.section { .section {
padding: 5rem 0; padding: 5rem 0;
} }
.section-title { .section-title {
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
position: relative; position: relative;
display: inline-block; display: inline-block;
} }
.section-title::after { .section-title::after {
content: ''; content: '';
position: absolute; position: absolute;
bottom: -10px; bottom: -10px;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
width: 80px; width: 80px;
height: 4px; height: 4px;
background-color: var(--primary-color); background-color: var(--primary-color);
border-radius: 2px; border-radius: 2px;
} }
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
:root { :root {
color: #1f2937; color: #1f2937;
background-color: #ffffff; background-color: #ffffff;
--primary-color: #3b82f6; --primary-color: #3b82f6;
--primary-light: #60a5fa; --primary-light: #60a5fa;
--primary-dark: #2563eb; --primary-dark: #2563eb;
--secondary-color: #10b981; --secondary-color: #10b981;
--text-color: #1f2937; --text-color: #1f2937;
--text-muted: #6b7280; --text-muted: #6b7280;
--bg-color: #ffffff; --bg-color: #ffffff;
--bg-card: #f9fafb; --bg-card: #f9fafb;
--border-color: #e5e7eb; --border-color: #e5e7eb;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
} }
a:hover { a:hover {
color: var(--primary-color); color: var(--primary-color);
} }
button { button {
background-color: var(--primary-color); background-color: var(--primary-color);
color: white; color: white;
} }
} }
@media (max-width: 768px) { @media (max-width: 768px) {
h1 { h1 {
font-size: 2rem; font-size: 2rem;
} }
h2 { h2 {
font-size: 1.75rem; font-size: 1.75rem;
} }
h3 { h3 {
font-size: 1.25rem; font-size: 1.25rem;
} }
.section { .section {
padding: 3rem 0; padding: 3rem 0;
} }
} }

View File

@ -27,8 +27,6 @@
<!-- 联系我们 --> <!-- 联系我们 -->
<ContactForm /> <ContactForm />
<ChatAi />
</div> </div>
</template> </template>
@ -39,8 +37,6 @@ import TeamMembers from '@/components/team/TeamMembers.vue'
import ProjectShowcase from '@/components/projects/ProjectShowcase.vue' import ProjectShowcase from '@/components/projects/ProjectShowcase.vue'
import ContactForm from '@/components/contact/ContactForm.vue' import ContactForm from '@/components/contact/ContactForm.vue'
import CompanyTimeline from '@/components/timeline/CompanyTimeline.vue' import CompanyTimeline from '@/components/timeline/CompanyTimeline.vue'
import ChatAi from '@/components/chat/ChatAi.vue'
import { inject } from 'vue' import { inject } from 'vue'
// App.vue // App.vue
@ -49,6 +45,9 @@ const t = inject<(key: string) => string>('t') || ((key) => key)
<style scoped> <style scoped>
.hero-section { .hero-section {
height: 100vh; height: 100vh;
background-color: var(--bg-color); background-color: var(--bg-color);

View File

@ -1,45 +1,45 @@
<!-- src/views/ProjectDetail/Project3Zh.vue --> <!-- src/views/ProjectDetail/Project3Zh.vue -->
<template> <template>
<div class="container"> <div class="container">
<!-- 勤怠登録画面 --> <!-- 勤怠登録画面 -->
<h2>勤怠登録画面</h2> <h2>勤怠登録画面</h2>
<img :src="img1" alt="勤怠登录页面"> <img :src="img1" alt="勤怠登录页面">
<!-- 休暇申請画面 --> <!-- 休暇申請画面 -->
<h2>休暇申請画面</h2> <h2>休暇申請画面</h2>
<img :src="img2" alt="休假申请页面"> <img :src="img2" alt="休假申请页面">
<!-- 勤怠集計画面 --> <!-- 勤怠集計画面 -->
<h2>勤怠集計画面</h2> <h2>勤怠集計画面</h2>
<img :src="img3" alt="勤怠集计页面"> <img :src="img3" alt="勤怠集计页面">
</div> </div>
</template> </template>
<script setup> <script setup>
import img1 from '@/assets/project3/project3-1.png' import img1 from '@/assets/project3/project3-1.png'
import img2 from '@/assets/project3/project3-2.png' import img2 from '@/assets/project3/project3-2.png'
import img3 from '@/assets/project3/project3-3.png' import img3 from '@/assets/project3/project3-3.png'
// //
defineOptions({ defineOptions({
name: 'Project3Zh' name: 'Project3Zh'
}) })
</script> </script>
<style scoped> <style scoped>
.container { .container {
padding: 20px; padding: 20px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
h1, h2, h3 { h1, h2, h3 {
color: #333; color: #333;
} }
img { img {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
margin: 10px 0; margin: 10px 0;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
} }
</style> </style>

View File

@ -1,45 +1,45 @@
<!-- src/views/ProjectDetail/Project4Zh.vue --> <!-- src/views/ProjectDetail/Project4Zh.vue -->
<template> <template>
<div class="container"> <div class="container">
<!-- 勤怠登录页面 --> <!-- 勤怠登录页面 -->
<h2>勤怠登录页面</h2> <h2>勤怠登录页面</h2>
<img :src="img1" alt="勤怠登录页面"> <img :src="img1" alt="勤怠登录页面">
<!-- 休假申请页面 --> <!-- 休假申请页面 -->
<h2>休假申请页面</h2> <h2>休假申请页面</h2>
<img :src="img2" alt="休假申请页面"> <img :src="img2" alt="休假申请页面">
<!-- 勤怠集计页面 --> <!-- 勤怠集计页面 -->
<h2>勤怠集计页面</h2> <h2>勤怠集计页面</h2>
<img :src="img3" alt="勤怠集计页面"> <img :src="img3" alt="勤怠集计页面">
</div> </div>
</template> </template>
<script setup> <script setup>
import img1 from '@/assets/project3/project3-1.png' import img1 from '@/assets/project3/project3-1.png'
import img2 from '@/assets/project3/project3-2.png' import img2 from '@/assets/project3/project3-2.png'
import img3 from '@/assets/project3/project3-3.png' import img3 from '@/assets/project3/project3-3.png'
// //
defineOptions({ defineOptions({
name: 'Project3Zh' name: 'Project3Zh'
}) })
</script> </script>
<style scoped> <style scoped>
.container { .container {
padding: 20px; padding: 20px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
h1, h2, h3 { h1, h2, h3 {
color: #333; color: #333;
} }
img { img {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
margin: 10px 0; margin: 10px 0;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
} }
</style> </style>

View File

@ -1,50 +1,50 @@
<!-- src/views/ProjectDetail/Project4Zh.vue --> <!-- src/views/ProjectDetail/Project4Zh.vue -->
<template> <template>
<div class="container"> <div class="container">
<!-- 資材管理画面 --> <!-- 資材管理画面 -->
<h2>資材管理画面</h2> <h2>資材管理画面</h2>
<img :src="img1" alt="資材管理画面"> <img :src="img1" alt="資材管理画面">
<!-- 工程管理画面 --> <!-- 工程管理画面 -->
<h2>工程管理画面</h2> <h2>工程管理画面</h2>
<img :src="img2" alt="工程管理画面"> <img :src="img2" alt="工程管理画面">
<!-- 設備保全画面 --> <!-- 設備保全画面 -->
<h2>設備保全画面</h2> <h2>設備保全画面</h2>
<img :src="img3" alt="設備保全画面"> <img :src="img3" alt="設備保全画面">
<!-- 生産管理画面 --> <!-- 生産管理画面 -->
<h2>生産管理画面</h2> <h2>生産管理画面</h2>
<img :src="img4" alt="生産管理画面"> <img :src="img4" alt="生産管理画面">
</div> </div>
</template> </template>
<script setup> <script setup>
import img1 from '@/assets/project4/project4-1.png' import img1 from '@/assets/project4/project4-1.png'
import img2 from '@/assets/project4/project4-2.png' import img2 from '@/assets/project4/project4-2.png'
import img3 from '@/assets/project4/project4-3.png' import img3 from '@/assets/project4/project4-3.png'
import img4 from '@/assets/project4/project4-3.png' import img4 from '@/assets/project4/project4-3.png'
// //
defineOptions({ defineOptions({
name: 'Project3Zh' name: 'Project3Zh'
}) })
</script> </script>
<style scoped> <style scoped>
.container { .container {
padding: 20px; padding: 20px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
h1, h2, h3 { h1, h2, h3 {
color: #333; color: #333;
} }
img { img {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
margin: 10px 0; margin: 10px 0;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
} }
</style> </style>

View File

@ -1,50 +1,50 @@
<!-- src/views/ProjectDetail/Project4Zh.vue --> <!-- src/views/ProjectDetail/Project4Zh.vue -->
<template> <template>
<div class="container"> <div class="container">
<!-- 物料管理页面 --> <!-- 物料管理页面 -->
<h2>物料管理页面</h2> <h2>物料管理页面</h2>
<img :src="img1" alt="物料管理页面"> <img :src="img1" alt="物料管理页面">
<!-- 工艺管理页面 --> <!-- 工艺管理页面 -->
<h2>工艺管理页面</h2> <h2>工艺管理页面</h2>
<img :src="img2" alt="工艺管理页面"> <img :src="img2" alt="工艺管理页面">
<!-- 设备保养页面 --> <!-- 设备保养页面 -->
<h2>设备保养页面</h2> <h2>设备保养页面</h2>
<img :src="img3" alt="设备保养页面"> <img :src="img3" alt="设备保养页面">
<!-- 生产管理页面 --> <!-- 生产管理页面 -->
<h2>生产管理页面</h2> <h2>生产管理页面</h2>
<img :src="img4" alt="生产管理页面"> <img :src="img4" alt="生产管理页面">
</div> </div>
</template> </template>
<script setup> <script setup>
import img1 from '@/assets/project4/project4-1.png' import img1 from '@/assets/project4/project4-1.png'
import img2 from '@/assets/project4/project4-2.png' import img2 from '@/assets/project4/project4-2.png'
import img3 from '@/assets/project4/project4-3.png' import img3 from '@/assets/project4/project4-3.png'
import img4 from '@/assets/project4/project4-3.png' import img4 from '@/assets/project4/project4-3.png'
// //
defineOptions({ defineOptions({
name: 'Project4Zh' name: 'Project4Zh'
}) })
</script> </script>
<style scoped> <style scoped>
.container { .container {
padding: 20px; padding: 20px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
h1, h2, h3 { h1, h2, h3 {
color: #333; color: #333;
} }
img { img {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
margin: 10px 0; margin: 10px 0;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
} }
</style> </style>

24
src/vite-env.d.ts vendored
View File

@ -1,13 +1,13 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
// 为了在模板中使用 $t 函数,我们需要扩展 Vue 的类型定义 // 为了在模板中使用 $t 函数,我们需要扩展 Vue 的类型定义
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
interface ComponentCustomProperties { interface ComponentCustomProperties {
$t: (key: string) => string $t: (key: string) => string
} }
} }
// 为 TDesign 组件添加类型支持 // 为 TDesign 组件添加类型支持
declare module 'tdesign-vue-next' { declare module 'tdesign-vue-next' {
export * from 'tdesign-vue-next/es' export * from 'tdesign-vue-next/es'
} }

44
src/vue-shims.d.ts vendored
View File

@ -1,23 +1,23 @@
// src/vue-shims.d.ts // src/vue-shims.d.ts
// Vue 3核心API类型声明 // Vue 3核心API类型声明
declare module 'vue' { declare module 'vue' {
export * from '@vue/runtime-dom'; export * from '@vue/runtime-dom';
export { default } from '@vue/runtime-dom'; export { default } from '@vue/runtime-dom';
} }
// 为Composition API添加类型声明 // 为Composition API添加类型声明
declare module '@vue/runtime-dom' { declare module '@vue/runtime-dom' {
export function ref<T>(value: T): import('@vue/reactivity').Ref<T>; export function ref<T>(value: T): import('@vue/reactivity').Ref<T>;
export function inject<T>(key: string | Symbol, defaultValue?: T): T | undefined; export function inject<T>(key: string | Symbol, defaultValue?: T): T | undefined;
export function provide<T>(key: string | Symbol, value: T): void; export function provide<T>(key: string | Symbol, value: T): void;
export function createApp(rootComponent: import('@vue/runtime-core').Component): import('@vue/runtime-core').App; export function createApp(rootComponent: import('@vue/runtime-core').Component): import('@vue/runtime-core').App;
export function defineProps<Props>(): Props; export function defineProps<Props>(): Props;
export function defineEmits<Emits extends Record<string, (...args: any[]) => any>>(emits?: Emits): Emits; export function defineEmits<Emits extends Record<string, (...args: any[]) => any>>(emits?: Emits): Emits;
export function computed<T>(getter: () => T): import('@vue/reactivity').ComputedRef<T>; export function computed<T>(getter: () => T): import('@vue/reactivity').ComputedRef<T>;
export function watch<T>(source: T | (() => T), callback: (newValue: T, oldValue: T) => void): void; export function watch<T>(source: T | (() => T), callback: (newValue: T, oldValue: T) => void): void;
export function onMounted(callback: () => void): void; export function onMounted(callback: () => void): void;
export function onUnmounted(callback: () => void): void; export function onUnmounted(callback: () => void): void;
export function onUpdated(callback: () => void): void; export function onUpdated(callback: () => void): void;
export function onBeforeUpdate(callback: () => void): void; export function onBeforeUpdate(callback: () => void): void;
} }

View File

@ -1,15 +1,15 @@
{ {
"extends": "@vue/tsconfig/tsconfig.dom.json", "extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/stores/layout"] "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/stores/layout"]
} }

View File

@ -1,14 +1,7 @@
{ {
"files": [], "files": [],
"references": [ "references": [
{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" } { "path": "./tsconfig.node.json" }
], ]
"compilerOptions": { }
"moduleResolution": "Node",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*"]
}
}
}

View File

@ -1,25 +1,25 @@
{ {
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023", "target": "ES2023",
"lib": ["ES2023"], "lib": ["ES2023"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"moduleDetection": "force", "moduleDetection": "force",
"noEmit": true, "noEmit": true,
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
} }

View File

@ -1,18 +1,13 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import path from 'path' import path from 'path'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
resolve: { resolve: {
alias: { alias: {
'@': path.resolve(__dirname, 'src'), // 将 @ 指向 src 目录 '@': path.resolve(__dirname, 'src'), // 将 @ 指向 src 目录
}, },
}, },
server: { })
host: '0.0.0.0', // 监听所有网络接口
port: 5173, // 可自定义端口默认5173
open: false // 可选:是否自动打开浏览器
}
})