This commit is contained in:
lv 2025-08-19 20:08:31 +08:00
parent ca619070f0
commit 4e5b99e5d9
47 changed files with 4477 additions and 0 deletions

24
.gitignore vendored Normal file
View File

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

View File

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

3
.vite/deps/package.json Normal file
View File

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

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# 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.
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).

77
eslint.config.ts Normal file
View File

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

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

1918
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
package.json Normal file
View File

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

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

227
src/App.vue Normal file
View File

@ -0,0 +1,227 @@
<script setup lang="ts">
import BackToTop from './components/common/BackToTop.vue'
import Navbar from './components/common/Navbar.vue'
import AboutUs from './components/company/AboutUs.vue'
import TeamMembers from './components/team/TeamMembers.vue'
import ProjectShowcase from './components/projects/ProjectShowcase.vue'
import ContactForm from './components/contact/ContactForm.vue'
import CompanyTimeline from './components/timeline/CompanyTimeline.vue'
import { useLanguageStore } from './store/language'
// store
const languageStore = useLanguageStore()
// 使使API
import { provide } from 'vue'
//
provide('t', (key: string) => languageStore.t(key))
</script>
<template>
<div id="app">
<Navbar />
<BackToTop />
<!-- 首页横幅 -->
<section id="home" class="hero-section">
<div class="hero-content">
<h1>Professional Software Solutions</h1>
<p>We deliver innovative technology solutions for businesses</p>
<div class="cta-buttons">
<a href="#about" class="btn-primary">{{ languageStore.t('nav.about') }}</a>
<a href="#contact" class="btn-secondary">{{ languageStore.t('nav.contact') }}</a>
</div>
</div>
</section>
<!-- 工作室介绍 -->
<AboutUs />
<!-- 团队介绍 -->
<TeamMembers />
<!-- 成功案例 -->
<ProjectShowcase />
<!-- 公司历程 -->
<CompanyTimeline />
<!-- 联系我们 -->
<ContactForm />
<!-- 页脚 -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<p>&copy; {{ new Date().getFullYear() }} Company Name. All rights reserved.</p>
<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="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"><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>
</footer>
</div>
</template>
<style scoped>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
.hero-section {
height: 100vh;
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
align-items: center;
justify-content: center;
text-align: center;
background-image: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('https://picsum.photos/id/180/1920/1080');
background-size: cover;
background-position: center;
background-attachment: fixed;
position: relative;
overflow: hidden;
}
.hero-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, var(--primary-color) 0%, transparent 70%);
opacity: 0.5;
z-index: 1;
}
.hero-content {
max-width: 800px;
padding: 2rem;
position: relative;
z-index: 2;
animation: fadeInUp 1s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.hero-content h1 {
font-size: 3.5rem;
margin-bottom: 1rem;
background: linear-gradient(90deg, white, var(--primary-light));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
line-height: 1.1;
letter-spacing: -1px;
}
.hero-content p {
font-size: 1.2rem;
margin-bottom: 2.5rem;
color: rgba(255, 255, 255, 0.9);
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.cta-buttons {
display: flex;
justify-content: center;
gap: 1rem;
}
.btn-primary, .btn-secondary {
padding: 0.8rem 2rem;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: var(--transition);
display: inline-block;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
border: none;
box-shadow: var(--shadow);
}
.btn-primary:hover {
background-color: var(--primary-dark);
transform: translateY(-3px);
box-shadow: var(--shadow-lg);
}
.btn-secondary {
background-color: transparent;
color: white;
border: 2px solid white;
}
.btn-secondary:hover {
background-color: white;
color: var(--bg-color);
transform: translateY(-3px);
}
.footer {
background-color: #1a1a2e;
color: white;
padding: 2rem 0;
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.social-links {
display: flex;
gap: 1rem;
}
.social-links a {
color: white;
transition: color 0.3s;
}
.social-links a:hover {
color: #4cc9f0;
}
@media (max-width: 768px) {
.hero-content h1 {
font-size: 2.5rem;
}
.hero-content p {
font-size: 1rem;
}
.footer-content {
flex-direction: column;
text-align: center;
gap: 1rem;
}
}
</style>

BIN
src/assets/members1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
src/assets/members2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
src/assets/members3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
src/assets/members4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
src/assets/members5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
src/assets/project1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 KiB

BIN
src/assets/project2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 KiB

BIN
src/assets/project3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
src/assets/project4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 KiB

BIN
src/assets/project5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

1
src/assets/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

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

View File

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

View File

@ -0,0 +1,308 @@
<template>
<header class="navbar-container" :class="{ scrolled: isScrolled }">
<div class="container">
<div class="logo">
<a href="#home">{{ t('nav.home') }}</a>
</div>
<nav>
<ul class="nav-links">
<li><a href="#home">{{ t('nav.home') }}</a></li>
<li><a href="#about">{{ t('nav.about') }}</a></li>
<li><a href="#team">{{ t('nav.team') }}</a></li>
<li><a href="#projects">{{ t('nav.projects') }}</a></li>
<li><a href="#contact">{{ t('nav.contact') }}</a></li>
</ul>
</nav>
<div class="language-switcher">
<button @click="toggleLanguage" class="language-btn">
{{ currentLanguage === 'zh' ? '日本語' : '中文' }}
</button>
</div>
<div class="mobile-menu-btn">
<button @click="toggleMobileMenu">
<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">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
</div>
</div>
<div class="mobile-menu" v-if="isMobileMenuOpen">
<ul class="mobile-nav-links">
<li><a href="#home" @click="closeMobileMenu">{{ t('nav.home') }}</a></li>
<li><a href="#about" @click="closeMobileMenu">{{ t('nav.about') }}</a></li>
<li><a href="#team" @click="closeMobileMenu">{{ t('nav.team') }}</a></li>
<li><a href="#projects" @click="closeMobileMenu">{{ t('nav.projects') }}</a></li>
<li><a href="#contact" @click="closeMobileMenu">{{ t('nav.contact') }}</a></li>
<li>
<button @click="toggleLanguage" class="mobile-language-btn">
{{ currentLanguage === 'zh' ? '日本語' : '中文' }}
</button>
</li>
</ul>
</div>
</header>
</template>
<script setup lang="ts">
import { ref, computed, inject, onMounted, onUnmounted } from 'vue'
import { useLanguageStore } from '../../store/language'
export interface NavbarProps {
// props
}
const store = useLanguageStore()
const currentLanguage = computed(() => store.currentLanguage)
const isMobileMenuOpen = ref(false)
const isScrolled = ref(false)
//
const t = inject<(key: string) => string>('t') || ((key) => key)
//
const handleScroll = () => {
isScrolled.value = window.scrollY > 50
}
//
onMounted(() => {
window.addEventListener('scroll', handleScroll)
})
//
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
const toggleLanguage = () => {
store.toggleLanguage()
}
const toggleMobileMenu = () => {
isMobileMenuOpen.value = !isMobileMenuOpen.value
}
const closeMobileMenu = () => {
isMobileMenuOpen.value = false
}
</script>
<style scoped>
.navbar-container {
background-color: var(--bg-color);
color: var(--text-color);
padding: 1rem 0;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
box-shadow: var(--shadow);
transition: var(--transition);
}
.navbar-container.scrolled {
/* background-color: rgba(26, 26, 46, 0.95);
padding: 0.8rem 0; */
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -0.5px;
}
.logo a {
color: var(--text-color);
text-decoration: none;
display: flex;
align-items: center;
gap: 0.5rem;
}
.nav-links {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.nav-links li {
margin-left: 2rem;
position: relative;
}
.nav-links a {
color: var(--text-color);
text-decoration: none;
transition: var(--transition);
font-weight: 500;
position: relative;
}
.nav-links a::after {
content: '';
position: absolute;
bottom: -4px;
left: 0;
width: 0;
height: 2px;
background-color: var(--primary-color);
transition: var(--transition);
}
.nav-links a:hover {
color: var(--primary-color);
}
.nav-links a:hover::after {
width: 100%;
}
.language-switcher {
margin-left: 1rem;
}
.language-btn {
background-color: transparent;
color: var(--text-color);
border: 1px solid var(--border-color);
padding: 0.5rem 1rem;
cursor: pointer;
transition: var(--transition);
border-radius: 6px;
font-weight: 500;
}
.language-btn:hover {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
transform: translateY(-2px);
}
.mobile-menu-btn {
display: none;
background-color: transparent;
border: none;
color: var(--text-color);
cursor: pointer;
padding: 0.5rem;
border-radius: 6px;
transition: var(--transition);
}
.mobile-menu-btn:hover {
background-color: var(--bg-card);
}
.mobile-menu {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: var(--bg-color);
padding: 1rem;
display: none;
box-shadow: var(--shadow-lg);
border-top: 1px solid var(--border-color);
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.mobile-nav-links {
list-style: none;
padding: 0;
margin: 0;
}
.mobile-nav-links li {
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
.mobile-nav-links li:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.mobile-nav-links a {
color: var(--text-color);
text-decoration: none;
display: block;
padding: 0.5rem 0;
font-weight: 500;
transition: var(--transition);
}
.mobile-nav-links a:hover {
color: var(--primary-color);
padding-left: 5px;
}
.mobile-language-btn {
background-color: transparent;
color: var(--text-color);
border: 1px solid var(--border-color);
padding: 0.5rem 1rem;
cursor: pointer;
width: 100%;
border-radius: 6px;
font-weight: 500;
transition: var(--transition);
}
.mobile-language-btn:hover {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
@media (max-width: 768px) {
.nav-links {
display: none;
}
.language-switcher {
display: none;
}
.mobile-menu-btn {
display: block;
}
.mobile-menu {
display: block;
}
.navbar-container.scrolled {
padding: 0;
}
.navbar-container{
padding: 0;
}
}
</style>

View File

@ -0,0 +1,262 @@
<template>
<section id="about" class="about-section">
<div class="container">
<h2 class="section-title">{{ t('about.title') }}</h2>
<div class="about-content">
<p class="intro-text">{{ t('about.content') }}</p>
<div class="mission-vision">
<div class="mission">
<h3>{{ t('about.mission') }}</h3>
<p>{{ t('about.missionContent') }}</p>
</div>
<div class="vision">
<h3>{{ t('about.vision') }}</h3>
<p>{{ t('about.visionContent') }}</p>
</div>
</div>
<div class="advantages">
<h3>{{ t('about.advantages') }}</h3>
<div class="advantages-grid">
<div class="advantage-card">
<div class="advantage-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
<polyline points="2 17 12 22 22 17"></polyline>
<polyline points="2 12 12 17 22 12"></polyline>
</svg>
</div>
<h4>{{ t('about.advantage1') }}</h4>
<p>Our team consists of highly skilled professionals with extensive experience in software development.</p>
</div>
<div class="advantage-card">
<div class="advantage-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</div>
<h4>{{ t('about.advantage2') }}</h4>
<p>We prioritize our clients' needs and work closely with them to achieve their business objectives.</p>
</div>
<div class="advantage-card">
<div class="advantage-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z"></path>
<path d="M2 17l10 5 10-5"></path>
<path d="M2 12l10 5 10-5"></path>
</svg>
</div>
<h4>{{ t('about.advantage3') }}</h4>
<p>We constantly explore new technologies and methodologies to provide innovative solutions.</p>
</div>
<div class="advantage-card">
<div class="advantage-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
</div>
<h4>{{ t('about.advantage4') }}</h4>
<p>We deliver projects on time and within budget, ensuring high quality and reliability.</p>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { inject } from 'vue'
// import { useLanguageStore } from '../../store/language'
//
// const store = useLanguageStore()
//
const t = inject<(key: string) => string>('t') || ((key) => key)
</script>
<style scoped>
.about-section {
padding: 5rem 0;
background-color: var(--bg-color);
position: relative;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
}
.section-title {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
color: var(--text-color);
position: relative;
display: inline-block;
left: 50%;
transform: translateX(-50%);
}
.section-title::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 4px;
background-color: var(--primary-color);
border-radius: 2px;
}
.about-content {
max-width: 1000px;
margin: 0 auto;
}
.intro-text {
font-size: 1.1rem;
line-height: 1.8;
margin-bottom: 3rem;
text-align: center;
color: var(--text-muted);
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.mission-vision {
display: flex;
justify-content: space-between;
margin-bottom: 4rem;
gap: 2rem;
}
.mission, .vision {
flex: 1;
background-color: var(--bg-card);
padding: 2.5rem;
border-radius: 12px;
box-shadow: var(--shadow);
transition: var(--transition);
}
.mission:hover, .vision:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-lg);
}
.mission h3, .vision h3 {
color: var(--text-color);
margin-bottom: 1.5rem;
font-size: 1.75rem;
position: relative;
display: inline-block;
}
.mission h3::after, .vision h3::after {
content: '';
position: absolute;
bottom: -8px;
left: 0;
width: 40px;
height: 3px;
background-color: var(--primary-color);
border-radius: 2px;
}
.mission p, .vision p {
color: var(--text-muted);
line-height: 1.7;
}
.advantages {
margin-top: 4rem;
}
.advantages h3 {
text-align: center;
color: var(--text-color);
margin-bottom: 3rem;
font-size: 1.8rem;
}
.advantages-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2.5rem;
}
.advantage-card {
background-color: var(--bg-card);
padding: 2.5rem;
border-radius: 12px;
box-shadow: var(--shadow);
text-align: center;
transition: var(--transition);
position: relative;
overflow: hidden;
}
.advantage-card:hover {
transform: translateY(-8px);
box-shadow: var(--shadow-lg);
}
.advantage-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 5px;
background: linear-gradient(90deg, var(--primary-color), var(--primary-light));
}
.advantage-icon {
color: var(--primary-color);
margin-bottom: 1.5rem;
font-size: 1.5rem;
transition: var(--transition);
}
.advantage-card:hover .advantage-icon {
transform: scale(1.1);
}
.advantage-card h4 {
color: var(--text-color);
margin-bottom: 1rem;
font-size: 1.3rem;
font-weight: 600;
}
.advantage-card p {
color: var(--text-muted);
line-height: 1.6;
}
@media (max-width: 768px) {
.mission-vision {
flex-direction: column;
}
.section-title {
font-size: 2rem;
}
.mission, .vision {
padding: 2rem;
}
.advantages-grid {
gap: 1.5rem;
}
}
</style>

View File

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

View File

@ -0,0 +1,189 @@
<template>
<section id="projects" class="projects-section">
<div class="container">
<h2 class="section-title">{{ t('projects.title') }}</h2>
<div class="projects-grid">
<div v-for="project in projects" :key="project.id" class="project-card">
<div class="project-image">
<img :src="project.image" alt="t(project.title)" />
</div>
<div class="project-info">
<h3>{{ t(project.title) }}</h3>
<p>{{ t(project.description) }}</p>
<button class="view-details-btn">{{ t('projects.viewDetails') }}</button>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { ref, inject } from 'vue'
export interface Project {
id: number
title: string
description: string
image: string
detailLink: string
details: string
}
// src/assets
// 1@src*
// 2{ eager: true }
const imageModules = import.meta.glob('../../assets/project*.png', { eager: true })
// { ID: }
const imageMap: Record<number, string> = {}
for (const path in imageModules) {
// ID project1.pngproject2.png...
const match = path.match(/project(\d+)\.png/)
if (match) {
const id = Number(match[1])
// Vite default
imageMap[id] = (imageModules[path] as { default: string }).default
}
}
//
const projects = ref<Project[]>([
{
id: 1,
title: "projects.project1.title",
description: "projects.project1.description",
image: imageMap[1], // ID1
detailLink: "https://www.jal.co.jp/jp/ja/?city=TYO", // 1
details: 'This project involved developing a full-featured e-commerce platform with payment integration, inventory management, and customer relationship management.',
},
{
id: 2,
title: "projects.project2.title",
description: "projects.project2.description",
image: imageMap[2], // ID2
detailLink: "https://agmiru.com/", // 2
details: 'We developed a secure mobile banking app with features like account management, fund transfers, bill payments, and financial analytics.',
},
{
id: 3,
title: "projects.project3.title",
description: "projects.project3.description",
image: imageMap[3], // ID3
detailLink: "project3", // 3
details: 'This project involved creating a healthcare management system that streamlines patient registration, appointment scheduling, medical records management, and billing.',
},
{
id: 4,
title: "projects.project4.title",
description: "projects.project4.description",
image: imageMap[4], // ID4
detailLink: "project4", // 4
details: 'We developed an ERP system that integrates various business functions including finance, human resources, supply chain, and customer relationship management.',
},
{
id: 5,
title: "projects.project5.title",
description: "projects.project5.description",
image: imageMap[5], // ID5
detailLink: "https://maps.gsi.go.jp/#5/36.104611/140.084556/&base=std&ls=std&disp=1&vs=c1g1j0h0k0l0u0t0z0r0s0m0f1", // 5
details: 'We developed an ERP system that integrates various business functions including finance, human resources, supply chain, and customer relationship management.',
},
])
//
const t = inject<(key: string) => string>('t') || ((key) => key)
</script>
<style scoped>
.projects-section {
padding: 5rem 0;
background-color: #f8f9fa;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
}
.section-title {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
color: #1a1a2e;
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.project-card {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s;
}
.project-card:hover {
transform: translateY(-5px);
}
.project-image {
width: 100%;
height: 200px;
overflow: hidden;
}
.project-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
.project-card:hover .project-image img {
transform: scale(1.05);
}
.project-info {
padding: 1.5rem;
}
.project-info h3 {
color: #1a1a2e;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.project-info p {
color: #666;
line-height: 1.6;
margin-bottom: 1.5rem;
text-align: left; /* 添加此属性,强制内容左对齐 */
}
.view-details-btn {
background-color: #4cc9f0;
color: white;
border: none;
padding: 0.8rem 1.5rem;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.view-details-btn:hover {
background-color: #4361ee;
}
@media (max-width: 768px) {
.section-title {
font-size: 2rem;
}
}
</style>

View File

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

View File

@ -0,0 +1,188 @@
<template>
<section id="timeline" class="timeline-section">
<div class="container">
<h2 class="section-title">{{ t('timeline.title') }}</h2>
<div class="timeline">
<div v-for="(event, index) in timelineEvents" :key="event.id" class="timeline-item" :class="{ 'timeline-item-right': index % 2 === 1 }">
<div class="timeline-content">
<div class="timeline-date">{{ event.date }}</div>
<h3>{{ event.title }}</h3>
<p>{{ event.description }}</p>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { ref, inject } from 'vue'
export interface TimelineEvent {
id: number
date: string
title: string
description: string
}
// 线
const timelineEvents = ref<TimelineEvent[]>([
{
id: 1,
date: '2015',
title: 'Company Founded',
description: 'Our company was founded with a vision to provide innovative software solutions.',
},
{
id: 2,
date: '2016',
title: 'First Project',
description: 'We successfully completed our first major project for a leading client.',
},
{
id: 3,
date: '2018',
title: 'Team Expansion',
description: 'Our team grew to 20 employees with expertise in various technologies.',
},
{
id: 4,
date: '2020',
title: 'Product Launch',
description: 'We launched our first proprietary software product.',
},
{
id: 5,
date: '2022',
title: 'International Expansion',
description: 'We expanded our operations to serve clients worldwide.',
},
{
id: 6,
date: '2023',
title: 'Industry Recognition',
description: 'Our company received multiple awards for excellence in software development.',
},
])
//
const t = inject<(key: string) => string>('t') || ((key) => key)
</script>
<style scoped>
.timeline-section {
padding: 5rem 0;
background-color: #f8f9fa;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
}
.section-title {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
color: #1a1a2e;
}
.timeline {
position: relative;
max-width: 1200px;
margin: 0 auto;
}
.timeline::after {
content: '';
position: absolute;
width: 6px;
background-color: #4cc9f0;
top: 0;
bottom: 0;
left: 50%;
margin-left: -3px;
}
.timeline-item {
padding: 10px 40px;
position: relative;
width: 50%;
box-sizing: border-box;
}
.timeline-item-right {
left: 50%;
}
.timeline-content {
padding: 20px 30px;
background-color: white;
position: relative;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.timeline-date {
color: #4cc9f0;
font-weight: bold;
margin-bottom: 10px;
}
.timeline-content h3 {
color: #1a1a2e;
margin-bottom: 10px;
}
.timeline-content p {
color: #666;
line-height: 1.6;
}
/* 时间线节点样式 */
.timeline-item::after {
content: '';
position: absolute;
width: 25px;
height: 25px;
background-color: white;
border: 4px solid #4cc9f0;
top: 15px;
border-radius: 50%;
z-index: 1;
}
.timeline-item-right::after {
left: -17px;
}
.timeline-item::after {
right: -16px;
}
@media (max-width: 768px) {
.section-title {
font-size: 2rem;
}
.timeline::after {
left: 31px;
}
.timeline-item {
width: 100%;
padding-left: 70px;
padding-right: 25px;
}
.timeline-item-right {
left: 0;
}
.timeline-item::after,
.timeline-item-right::after {
left: 15px;
}
}
</style>

21
src/i18n.ts Normal file
View File

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

44
src/locales/en.ts Normal file
View File

@ -0,0 +1,44 @@
export default {
nav: {
home: 'Home',
about: 'About Us',
team: 'Our Team',
projects: 'Projects',
contact: 'Contact Us',
},
about: {
title: 'About Us',
mission: 'Our Mission',
vision: 'Our Vision',
advantages: 'Our Advantages',
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.',
visionContent: 'To become a leading global provider of software development services, recognized for our technical excellence and customer satisfaction.',
advantage1: 'Technical Excellence',
advantage2: 'Customer-Centric Approach',
advantage3: 'Innovation',
advantage4: 'Reliability',
},
team: {
title: 'Our Team',
memberRole: 'Role',
memberBio: 'Bio',
},
projects: {
title: 'Our Projects',
viewDetails: 'View Details',
},
contact: {
title: 'Contact Us',
name: 'Your Name',
email: 'Your Email',
subject: 'Subject',
message: 'Your Message',
send: 'Send Message',
success: 'Message sent successfully!',
error: 'Failed to send message. Please try again.',
},
timeline: {
title: 'Company History',
},
}

91
src/locales/ja.ts Normal file
View File

@ -0,0 +1,91 @@
export default {
nav: {
home: 'ホーム',
about: '会社概要',
team: 'チーム紹介',
projects: 'プロジェクト',
contact: 'お問い合わせ',
},
about: {
title: '会社概要',
mission: '私たちの使命',
vision: '私たちのビジョン',
advantages: '私たちの強み',
content: '私たちは、お客様に高品質なソリューションを提供することに専念するプロフェッショナルなソフトウェア開発会社です。',
missionContent: 'ビジネスの成長とデジタル変革を推進する革新的な技術ソリューションを提供します。',
visionContent: '技術的卓越性と顧客満足度で認められる、世界をリードするソフトウェア開発サービスプロバイダーになること。',
advantage1: '技術的卓越性',
advantage2: '顧客中心のアプローチ',
advantage3: '革新',
advantage4: '信頼性',
},
team: {
title: 'チーム紹介',
memberRole: '役職',
memberBio: 'プロフィール',
member1: {name: "陳 迪",role: "スタジオ代表",
bio:`日本での留学及び職務経験があります。秋田大学・名古屋大学大学院に学び、卒業後は日本の大手商社に入社し、国際的なビジネス環境の中で
BPO
SEBSEPM
Web `},
member2: {name: "梁 偉",role: "技術統括責任者",
bio:`10 年以上の Web 開発経験を持ち、長年にわたり対日プロジェクトに専念しています。フロントエンドの Vue、React、JavaScript 及び HTML5、
JavaPython Spring BootDjango
ECサイト
`},
member3: {name: "趙元博",role: "開発エンジニア",
bio:`8 年以上の Web 開発経験を持ち、長年にわたり対日プロジェクトのバックエンド開発を専門としてきました。。Java、PHP、Python、Node.js などのバックエンド
Spring BootDjangoExpress 使
EC 調 MOM API開発
`},
member4: {name: "張世超",role: "開発エンジニア",
bio:`長年にわたり Web フロントエンド開発に深く携わり、対日プロジェクトを専門として参画してきました。H5、CSS3、Flex を駆使した画面実装から、jQuery と JS を組み合わせた動的処理開発までを得意とし、
TypeScriptEs6 node.js Vue2.0/Vue3.0 + ElementUI
Flutter使用UIコンポーネントライブラリの活用やSVN/Gitによるバージョン管理を徹底することで`},
member5: {name: "張志華",role: "開発エンジニア",
bio:`長年のWebフルスタック開発経験を持ち、一貫して対日プロジェクトに従事してきました。フロントエンド領域では H5、CSS3、Flex レイアウトを基盤とした堅実な画面実装、jQueryからTypeScript、Es6、node.jsまで幅広く対応、
VueReactAngular UIライブラリの実務経験が豊富です
JavaPython Spring BootDjango SVNGit
`},
},
projects: {
title: 'プロジェクト',
viewDetails: '詳細を見る',
project1: {
title: "航空券管理システム",
description: "当システムは、フライト情報の照会、座席予約、オンライン決済、電子チケット管理および変更・払い戻し機能を統合しており、航空会社、旅行代理店および旅行者に効率的で便利なワンストップの航空券サービスを提供します。これにより、航空券販売および管理の自動化と情報化を実現し、業務運営の効率とユーザー体験を向上させます。",
},
project2: {
title: "農作物管理・買取システム",
description: "本システムは農家と仕入れ業者を結びつけ、農産物の管理、天気予報、病害虫管理、情報発信、オンライン商談、注文管理、品質追跡および電子決済などのサービスを提供します。情報の壁を打破し、取引プロセスを最適化することで、農産物の円滑な流通を促進し、農業の生産性向上と農家の収入増加を支援します。",
},
project3: {
title: "勤怠管理システム",
description: "当システムは、勤怠管理端末やモバイル端末など複数の方法で従業員の出退勤時間、休暇、残業などの情報を記録し、勤怠データを自動的に集計・分析して勤怠レポートを生成します。これにより、人事管理プロセスを簡素化し、勤怠の正確性と公正性を確保し、給与計算および業績評価に信頼性の高い根拠を提供します。",
},
project4: {
title: "工業生産管理システム",
description: "当システムは、マーケティング管理、生産計画、資材管理、工程管理、設備監視および品質管理などの各工程を統合し、生産プロセスの可視化と細やかな管理を実現します。リソースの最適な配分を図り、生産効率と製品品質を向上させ、運営コストを削減することで、企業のスマート化進展を推進します。",
},
project5: {
title: "地図拡張システム",
description: "本システムは、プロフェッショナル向けに設計された地図拡張ツールです。利用者は地図の閲覧や情報検索だけでなく、多彩な操作を地図上で実行可能。特定エリアやルートのマーキング機能に加え、2地点間の正確な距離測定や指定領域の面積計算も可能です。さらに強力な比較機能を搭載し、専門家の業務をサポートする高精度なデータを提供します。",
},
},
contact: {
title: 'お問い合わせ',
name: 'お名前',
email: 'メールアドレス',
subject: '件名',
message: 'メッセージ',
send: '送信',
success: 'メッセージが正常に送信されました!',
error: 'メッセージの送信に失敗しました。もう一度お試しください。',
},
timeline: {
title: '会社の沿革',
},
}

86
src/locales/zh.ts Normal file
View File

@ -0,0 +1,86 @@
export default {
nav: {
home: '首页',
about: '关于我们',
team: '团队介绍',
projects: '成功案例',
contact: '联系我们',
},
about: {
title: '关于我们',
mission: '我们的使命',
vision: '我们的愿景',
advantages: '我们的优势',
content: '我们是一家专业的软件开发团队,致力于为客户提供高质量的解决方案。',
missionContent: '提供创新的技术解决方案,推动业务增长和数字化转型。',
visionContent: '成为全球领先的软件开发服务提供商,以技术卓越和客户满意度著称。',
advantage1: '技术卓越',
advantage2: '以客户为中心',
advantage3: '创新精神',
advantage4: '可靠性',
},
team: {
title: '我们的团队',
memberRole: '职位',
memberBio: '简介',
member1: {
name: "陈迪",role: "工作室总负责人",
bio:`拥有日本留学与职业背景:先后就读于秋田大学及名古屋大学大学院,毕业后入职日本大型商社,在国际化商业环境中积累了宝贵的实战经验,
BPO
SEBSE及 PM Web
`},
member2: {name: "梁伟",role: "技术总负责人",
bio:`拥有 10 年以上 web 开发经验,长期专注对日项目。精通前端 Vue、React、JavaScript 及 HTML5后端 Java、Python 及 Spring Boot、
Django
`},
member3: {name: "赵元博",role: "技术开发者",
bio:`拥有 8年以上 web 开发经验,长期深耕对日项目后端开发。精通 Java、PHPPython、Node.js 等后端语言,熟练运用 Spring Boot、
DjangoExpress MOM系统等项目的后端架构搭建
`},
member4: {name: "张世超",role: "技术开发者",
bio:`深耕 web 前端开发多年,长期参与对日项目。精通 H5、CSS3、Flex 布局,熟练运用 jQuery 与 JS 配合开发,深谙 TypeScript、Es6 及 node.js。
使 Vue2.0~/Vue3.0~+ ElementUI app Flutter UI SVNGit
`},
member5: {name: "张志华",role: "技术开发者",
bio:`多年 web 全栈开发经验,长期投身对日项目。前端精通 H5、CSS3、Flex 布局,熟练运用 jQuery、TypeScript、Es6 及 node.js掌握 Vue、
ReactAngular UI JavaPython Spring BootDjango SVNGit
`},
},
projects: {
title: '成功案例',
viewDetails: '查看详情',
project1: {
title: "机票管理系统",
description: "该系统集成了航班信息查询、座位预订、在线支付、电子客票管理及退改签功能,为航空公司、旅行社和旅客提供高效、便捷的一站式票务服务,实现机票销售与管理的自动化和信息化,提升运营效率与用户体验。",
},
project2: {
title: "农作物交易系统",
description: "本系统连接农户与采购商,提供农产品管理,天气预报,病虫害管理,信息发布、在线洽谈、订单管理、质量追溯与电子结算等服务,打破信息壁垒,优化交易流程,促进农产品高效流通,助力农业增效、农户增收。",
},
project3: {
title: "出勤管理系统",
description: "系统通过考勤机、移动端等多方式记录员工上下班时间、请假、加班等信息,自动统计分析出勤数据,生成考勤报表,简化人事管理流程,确保考勤准确公正,为薪资计算和绩效考核提供可靠依据。",
},
project4: {
title: "工业生产管理系统",
description: "本系统是一款专业的生产加工行业的数字化系统,整合营销管理,生产计划、物料管理、工艺流程、设备监控与质量控制等环节,实现生产过程的可视化、精细化管理,优化资源配置,提高生产效率与产品质量,降低运营成本,推动企业智能化升级。",
},
project5: {
title: "地图扩展系统",
description: "本系统是为专业人士打造的地图扩展工具。操作者不仅能进行地图阅览与信息查询,还可在地图上开展丰富操作;支持对特定区域、线路等进行标记,能够精准测量地图上任意两点间的距离以及特定区域的面积;同时具备强大的对比功能,为专业人士提供精确数据支撑。",
},
},
contact: {
title: '联系我们',
name: '您的姓名',
email: '您的邮箱',
subject: '主题',
message: '您的留言',
send: '发送留言',
success: '留言发送成功!',
error: '发送留言失败,请重试。',
},
timeline: {
title: '公司历程',
},
}

16
src/main.ts Normal file
View File

@ -0,0 +1,16 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 引入Pinia
import { createPinia } from 'pinia'
// 引入TDesign UI组件库
import TDesign from 'tdesign-vue-next'
import 'tdesign-vue-next/es/style/index.css'
const app = createApp(App)
// 使用Pinia
app.use(createPinia())
// 使用TDesign
app.use(TDesign)
app.mount('#app')

29
src/router/index.ts Normal file
View File

@ -0,0 +1,29 @@
// import { createRouter, createWebHistory } from 'vue-router';
// import { useLanguageStore } from '@/store/language';
// const router = createRouter({
// history: createWebHistory(),
// routes: [
// // 其他路由...
// {
// path: '/project3',
// name: 'Project3Detail',
// component: () => {
// const store = useLanguageStore();
// const lang = store.language;
// return import(`@/views/ProjectDetail/Project3${lang.toUpperCase()}.vue`);
// },
// },
// {
// path: '/project4',
// name: 'Project4Detail',
// component: () => {
// const store = useLanguageStore();
// const lang = store.language;
// return import(`@/views/ProjectDetail/Project4${lang.toUpperCase()}.vue`);
// },
// },
// ],
// });
// export default router;

View File

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

50
src/store/language.ts Normal file
View File

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

178
src/style.css Normal file
View File

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

13
src/vite-env.d.ts vendored Normal file
View File

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

23
src/vue-shims.d.ts vendored Normal file
View File

@ -0,0 +1,23 @@
// src/vue-shims.d.ts
// Vue 3核心API类型声明
declare module 'vue' {
export * from '@vue/runtime-dom';
export { default } from '@vue/runtime-dom';
}
// 为Composition API添加类型声明
declare module '@vue/runtime-dom' {
export function ref<T>(value: T): import('@vue/reactivity').Ref<T>;
export function inject<T>(key: string | Symbol, defaultValue?: T): T | undefined;
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 defineProps<Props>(): Props;
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 watch<T>(source: T | (() => T), callback: (newValue: T, oldValue: T) => void): void;
export function onMounted(callback: () => void): void;
export function onUnmounted(callback: () => void): void;
export function onUpdated(callback: () => void): void;
export function onBeforeUpdate(callback: () => void): void;
}

15
tsconfig.app.json Normal file
View File

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

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

25
tsconfig.node.json Normal file
View File

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

1
tsconfig.tsbuildinfo Normal file
View File

@ -0,0 +1 @@
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/locales/en.ts","./src/locales/ja.ts","./src/locales/zh.ts","./src/services/emailservice.ts","./src/store/language.ts","./src/app.vue","./src/components/helloworld.vue","./src/components/common/navbar.vue","./src/components/company/aboutus.vue","./src/components/contact/contactform.vue","./src/components/projects/projectshowcase.vue","./src/components/team/teammembers.vue","./src/components/timeline/companytimeline.vue","./node_modules/tdesign-vue-next/global.d.ts"],"errors":true,"version":"5.9.2"}