Init commit.

This commit is contained in:
sleepwithoutbz
2025-10-02 22:58:32 +08:00
commit 4c71b3a711
169 changed files with 74075 additions and 0 deletions

10
src/App.vue Normal file
View File

@@ -0,0 +1,10 @@
<script setup lang="ts">
import NavBar from './components/NavBar.vue'
import FooterComponent from './components/FooterComponent.vue'
</script>
<template>
<NavBar></NavBar>
<router-view></router-view>
<FooterComponent> </FooterComponent>
</template>

11
src/__tests__/App.spec.ts Normal file
View File

@@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import App from '../App.vue'
describe('App', () => {
it('mounts renders properly', () => {
const wrapper = mount(App)
expect(wrapper.text()).toContain('You did it!')
})
})

Binary file not shown.

BIN
src/assets/water/cjb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/water/cjq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/water/cyq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
src/assets/water/cyq1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
src/assets/water/cyq2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
src/assets/water/cyq3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/assets/water/cyq4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
src/assets/water/cyqen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

BIN
src/assets/water/cyqzh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@@ -0,0 +1,219 @@
<template>
<div class="hero-section">
<div class="hero-bg" :style="{ backgroundImage: 'url(' + backgroundImage + ')' }"></div>
<div class="hero-content">
<h2 class="hero-title">{{ title }}</h2>
<p class="hero-description">{{ description }}</p>
<button v-if="buttonshow" class="hero-btn" @click="goWaterlife">
>
{{ $t('learnMore') }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
function goWaterlife() {
router.push({
path: props.targetPath,
})
}
const props = defineProps({
targetPath: {
type: String,
required: false,
},
buttonshow: {
type: Boolean,
default: false,
},
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
backgroundImage: {
type: String,
default: '',
},
})
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
padding: 20px;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.demo-section {
background: white;
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
padding: 30px;
margin-bottom: 30px;
}
.demo-section h2 {
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0;
display: flex;
align-items: center;
}
.demo-section h2 i {
margin-right: 10px;
color: #6a11cb;
}
/* 背景图标题组件样式 */
.hero-section {
position: relative;
width: 100%;
height: 500px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
border-radius: 12px;
margin: 30px 0;
}
.hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('https://images.unsplash.com/photo-1550751827-4bd374c3f58b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80');
background-size: cover;
background-position: center;
opacity: 0.5; /* 背景图透明度50% */
z-index: 1;
}
.hero-content {
position: relative;
z-index: 2;
text-align: center;
color: #2c3e50;
max-width: 800px;
padding: 40px;
border-radius: 12px;
backdrop-filter: blur(5px);
}
.hero-title {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 20px;
color: #2c3e50;
position: relative;
padding-bottom: 15px;
}
.hero-title:after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
border-radius: 3px;
}
.hero-description {
font-size: 1.3rem;
line-height: 1.8;
margin-bottom: 30px;
color: #444;
font-style: italic;
}
.hero-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 30px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(106, 17, 203, 0.2);
}
.hero-btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 16px rgba(106, 17, 203, 0.3);
}
.hero-btn i {
margin-right: 10px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.hero-section {
height: 400px;
}
.hero-content {
padding: 30px 20px;
margin: 0 15px;
}
.hero-title {
font-size: 2rem;
}
.hero-description {
font-size: 1.1rem;
}
}
@media (max-width: 480px) {
.hero-section {
height: 350px;
}
.hero-title {
font-size: 1.8rem;
}
.hero-description {
font-size: 1rem;
}
.hero-btn {
padding: 10px 20px;
font-size: 1rem;
}
}
</style>

View File

@@ -0,0 +1,85 @@
<template>
<p class="styled-text-container">
<span class="first-part">{{ firstPart }}</span>
<span class="second-part">{{ secondPart }}</span>
</p>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
// 使用 defineProps 接收来自父组件的数据
defineProps({
firstPart: {
type: String,
required: true,
},
secondPart: {
type: String,
required: true,
},
})
</script>
<style scoped>
.styled-text-container {
background: #f8f9ff;
border-radius: 10px;
padding: 20px;
transition: all 0.3s ease;
border-left: 4px solid #6a11cb;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.styled-text-container:hover {
background: #eef2ff;
transform: translateY(-3px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
}
.first-part {
font-weight: 700;
font-size: 1.2rem;
color: #2c3e50;
display: block;
margin-bottom: 8px;
position: relative;
padding-left: 15px;
}
.first-part:before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 8px;
height: 8px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
border-radius: 50%;
}
.second-part {
font-weight: 500;
color: #555;
line-height: 1.6;
padding-left: 15px;
border-left: 2px solid #2575fc;
margin-left: 3px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.component-grid {
grid-template-columns: 1fr;
}
.styled-text-container {
padding: 15px;
}
.first-part {
font-size: 1.1rem;
}
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<section>
<div class="content-container">
<h2 v-if="title" class="title">{{ title }}</h2>
<p class="text">{{ content }}</p>
</div>
</section>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
defineProps({
title: {
type: String,
required: false,
},
content: {
type: String,
required: true,
},
})
</script>
<style scoped>
.content-container {
display: flex;
flex-direction: column; /* 纵向排列 */
justify-content: space-between;
padding: 2rem 4rem; /* 内边距 */
margin: 3% 10%;
/* background-color: #7bb77f; */
}
.content-container .title {
font-size: 1.8rem;
font-weight: bold;
text-align: center; /* 水平居中 */
color: rgb(0, 3, 1);
}
.content-container .text {
font-size: 1.4rem;
line-height: 1.6;
color: #333;
text-indent: 2em; /* 缩进两个字符宽度 */
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<footer class="footer-contact">
<div class="footer-content">
<div class="footer-section">
<h3 class="footer-title">{{ $t('contactname') }}</h3>
<li>
<span>{{ $t('company.wholeName') }} </span>
</li>
<li>E-mail: ykl1979@163.com</li>
<!-- <li>
{{ $t('contact.email') }}
</li> -->
<li>
{{ $t('contact.phone') }}
</li>
</div>
<div class="footer-section">
<h3 class="footer-title">{{ $t('address') }}</h3>
<li>
{{ $t('contact.address') }}
</li>
</div>
</div>
<div class="footer-bottom">
<p>{{ $t('copyright') }} © 2025 {{ $t('company.wholeName') }} | 皖ICP备2020019089号-5</p>
</div>
</footer>
</template>
<script setup lang="ts"></script>
<style scoped>
li {
font-size: 1.3rem;
}
.footer-contact {
background: linear-gradient(135deg, #8d53cc 0%, #74a3f4 100%);
color: white;
padding: 50px 0 20px;
border-radius: 12px 12px 0 0;
box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.1);
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.footer-section {
padding: 20px;
}
.footer-title {
font-size: 1.4rem;
font-weight: 700;
margin-bottom: 20px;
position: relative;
padding-bottom: 10px;
display: flex;
align-items: center;
}
.footer-title:after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 40px;
height: 3px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
border-radius: 3px;
}
.footer-title i {
margin-right: 10px;
color: #6a11cb;
}
.footer-bottom {
max-width: 1200px;
margin: 30px auto 0;
padding: 20px;
text-align: center;
border-top: 1px solid rgba(255, 255, 255, 0.1);
font-size: 0.9rem;
color: #4f4f4f;
}
/* 响应式设计 */
@media (max-width: 768px) {
.footer-content {
grid-template-columns: 1fr;
}
.footer-section {
padding: 15px;
}
}
</style>

264
src/components/NavBar.vue Normal file
View File

@@ -0,0 +1,264 @@
<template>
<div class="home-view">
<header class="header">
<div class="logo-section">
<img src="../../public/images/logo.png" alt="公司Logo" class="logo" />
<span class="company-name">{{ $t('company.name') }}</span>
<button class="mobile-menu-btn" @click="toggleMenu"></button>
</div>
<!-- 菜单按钮只在小屏显示 -->
<nav :class="['nav-bar', { open: isMenuOpen }]">
<router-link to="/" class="nav-item">{{ $t('mainpage.name') }}</router-link>
<router-link to="/about" class="nav-item">{{ $t('about.name') }}</router-link>
<router-link to="/waterlife" class="nav-item">{{
$t('areas.individual.name')
}}</router-link>
<router-link to="/agedlife" class="nav-item">{{ $t('areas.aged.name') }}</router-link>
<router-link to="/" class="nav-item">{{ $t('media.name') }}</router-link>
<router-link to="/others" class="nav-item">{{ $t('others') }}</router-link>
</nav>
<div class="language-switcher">
<router-link to="/" class="nav-item">{{ $t('store') }}</router-link>
<select v-model="$i18n.locale" class="lang-selector">
<option value="zh">中文</option>
<option value="en">English</option>
</select>
</div>
</header>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const isMenuOpen = ref(false)
const toggleMenu = () => {
isMenuOpen.value = !isMenuOpen.value
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
padding: 20px;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 30px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
margin-bottom: 30px;
}
.logo-section {
display: flex;
align-items: center;
flex-shrink: 0;
}
.logo {
width: 60px;
height: 40px;
margin-right: 20px;
object-fit: contain;
background-color: white;
padding: 5px;
border-radius: 8px;
}
.company-name {
font-size: 1.6rem;
color: white;
font-weight: 700;
letter-spacing: 1px;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
}
.nav-bar {
display: flex;
gap: 5px;
flex-wrap: wrap;
justify-content: center;
flex-grow: 1;
margin: 0 40px;
}
.nav-item {
padding: 12px 20px;
text-decoration: none;
color: white;
font-weight: 600;
border-radius: 8px;
transition: all 0.3s ease;
display: flex;
align-items: center;
position: relative;
overflow: hidden;
}
.nav-item:before {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 3px;
background: white;
transition: all 0.3s ease;
transform: translateX(-50%);
border-radius: 3px;
}
.nav-item:hover {
background: rgba(255, 255, 255, 0.15);
transform: translateY(-2px);
}
.nav-item:hover:before {
width: 70%;
}
.nav-item.active {
background: rgba(255, 255, 255, 0.25);
}
.nav-item.active:before {
width: 70%;
}
.nav-item i {
margin-right: 8px;
font-size: 1.1rem;
}
.language-switcher {
display: flex;
align-items: center;
gap: 2rem;
}
.lang-selector {
padding: 10px 15px;
border: none;
border-radius: 8px;
background: rgba(32, 8, 117, 0.2);
color: white;
font-weight: 600;
cursor: pointer;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.lang-selector:hover {
background: rgba(11, 2, 57, 0.3);
}
.lang-selector option {
background: #706bac;
color: rgb(255, 255, 255);
}
.mobile-menu-btn {
display: none;
background: none;
border: none;
color: white;
font-size: 1.5rem;
cursor: pointer;
}
/* 响应式设计 */
@media (max-width: 1024px) {
.nav-bar {
margin: 0 20px;
}
.nav-item {
padding: 10px 15px;
font-size: 0.95rem;
}
}
@media (max-width: 768px) {
.header {
flex-wrap: wrap;
padding: 15px;
}
.logo-section {
margin-bottom: 15px;
width: 100%;
justify-content: center;
}
.nav-bar {
position: absolute;
top: 70px; /* header 高度 */
left: 0;
right: 0;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
flex-direction: column;
display: none;
z-index: 1000;
}
.nav-bar.open {
display: flex;
}
.language-switcher {
margin-left: auto;
}
.mobile-menu-btn {
display: block;
}
.company-name {
font-size: 1.4rem;
}
}
/* 演示内容样式 */
.demo-content {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
margin-top: 30px;
}
.demo-content h2 {
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0;
}
.demo-content p {
line-height: 1.8;
color: #444;
margin-bottom: 15px;
}
</style>

View File

@@ -0,0 +1,379 @@
<template>
<section class="two-column-section" :class="{ 'reverse-layout': reverse }">
<div class="product_details">
<RowContent
v-for="(item, index) in detailList"
:key="index"
:first-part="item.key"
:second-part="item.value"
/>
<p v-if="ad" class="text1">{{ ad }}</p>
<p class="text2">
{{ info }}
<br />
<button class="video-btn" @click="showVideo = true">Watch a video to learn more</button>
</p>
<teleport to="body">
<div v-if="showVideo" class="video-modal-overlay" @click.self="showVideo = false">
<div class="video-modal-content">
<button class="close-btn" @click="showVideo = false">×</button>
<video controls autoplay webkit-playsinline>
<source :src="videoUrl" type="video/mp4" />
您的浏览器不支持视频播放
</video>
</div>
</div>
</teleport>
</div>
<div v-if="imageUrl" class="image-container">
<h3 class="image-title">{{ imagetitle }}</h3>
<img :src="imageUrl" :alt="imageAlt" class="section-image" />
</div>
</section>
</template>
<script setup lang="ts">
import { defineProps, ref } from 'vue'
import RowContent from './RowContent.vue'
// const detailList = tm('cyq.detail') as DetailItem[]
import type { PropType } from 'vue'
interface DetailItem {
key: string
value: string
}
const showVideo = ref(false)
defineProps({
videoUrl: {
type: String,
required: true,
},
detailList: {
type: Array as PropType<DetailItem[]>,
required: false,
default: () => [],
},
imagetitle: { type: String, required: false },
ad: {
type: String,
required: false,
},
title: {
type: String,
required: true,
},
info: {
type: String,
required: false,
},
content: {
type: String,
required: true,
},
imageUrl: {
type: String,
required: false,
},
imageAlt: {
type: String,
default: 'Section image',
},
reverse: {
type: Boolean,
default: false,
},
})
</script>
<style scoped>
.video-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 24px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 15px;
box-shadow: 0 4px 12px rgba(106, 17, 203, 0.2);
}
.video-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(106, 17, 203, 0.3);
}
.video-modal-overlay {
display: none; /* 默认隐藏 */
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
/* 弹窗 */
.video-modal-content {
height: 80%; /* 屏幕一半高度 */
border-radius: 12px;
overflow: hidden;
position: relative;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.8);
}
/* 视频全屏充满弹窗 */
.video-modal-content video {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 关闭按钮 */
.close-btn {
position: absolute;
top: 15px;
right: 15px;
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.2);
color: white;
border: none;
border-radius: 50%;
font-size: 1.2rem;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
z-index: 10;
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: rotate(90deg);
}
h1 {
text-align: center;
margin: 30px 0;
color: #2c3e50;
font-weight: 700;
position: relative;
padding-bottom: 15px;
}
h1:after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
border-radius: 3px;
}
.demo-section {
background: white;
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
padding: 30px;
margin-bottom: 30px;
}
.demo-section h2 {
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0;
display: flex;
align-items: center;
}
.demo-section h2 i {
margin-right: 10px;
color: #6a11cb;
}
/* 双栏布局组件样式 */
.two-column-section {
width: 100%;
display: flex;
justify-content: space-between;
gap: 3rem;
padding: 30px;
/* background: white; */
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
margin: 20px 0;
transition: all 0.3s ease;
}
.two-column-section:hover {
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
transform: translateY(-5px);
}
.product_details {
flex: 0.6;
display: flex;
flex-direction: column;
justify-content: center;
gap: 15px;
}
.image-container {
flex: 0.35;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
}
.image-title {
font-style: italic;
font-size: 1.5em;
color: #2c3e50;
font-weight: 600;
text-align: center;
padding: 10px 20px;
background: linear-gradient(135deg, rgba(106, 17, 203, 0.1) 0%, rgba(37, 117, 252, 0.1) 100%);
border-radius: 8px;
}
.section-image {
width: 100%;
max-width: 300px;
height: auto;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.section-image:hover {
transform: scale(1.03);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
.two-column-section.reverse-layout {
flex-direction: row-reverse;
}
.text1 {
font-size: 1.1em;
color: #4a5568;
line-height: 1.7;
padding: 15px;
background: #f8f9ff;
border-radius: 8px;
border-left: 4px solid #6a11cb;
}
.text2 {
font-size: 1.4em;
color: #2c3e50;
font-weight: 600;
padding: 15px;
background: linear-gradient(135deg, rgba(106, 17, 203, 0.1) 0%, rgba(37, 117, 252, 0.1) 100%);
border-radius: 8px;
}
/* 响应式设计 */
@media (max-width: 968px) {
.two-column-section {
flex-direction: column;
padding: 20px;
}
.two-column-section.reverse-layout {
flex-direction: column;
}
.product_details,
.image-container {
flex: 1;
width: 100%;
}
.image-container {
order: -1;
margin-bottom: 20px;
}
}
@media (max-width: 768px) {
.two-column-section {
padding: 15px;
}
.text2 {
font-size: 1.2em;
}
.image-title {
font-size: 1.3em;
}
}
</style>
<!--
<style scoped>
.two-column-section {
width: 80vw;
height: 20vw;
display: flex;
justify-content: space-evenly; /* 水平居中整体 */
gap: 3rem; /* 内容和图片间距 */
padding: 5%;
align-items: center;
}
.product_details {
flex: 0.6; /* 平分可用宽度 */
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
gap: 1px; /* 设置子元素之间 5px 的间距 */
}
.image-container {
flex: 0.35; /* 平分可用宽度 */
display: flex;
flex-direction: column; /* 内部上下排列 */
align-items: center;
gap: 2%;
}
.image-title {
font-style: italic;
font-size: 1.5em;
}
.section-image {
width: 50%;
height: auto;
}
.two-column-section.reverse-layout {
flex-direction: row-reverse;
align-items: center; /* 保持垂直居中 */
}
.text1 {
font-size: 1em;
color: rgb(27, 23, 54);
text-indent: 2em;
}
.text2 {
/* 缩进量,可以根据需要调整 */
font-size: 1.4em;
text-indent: 2em;
}
</style> -->

View File

@@ -0,0 +1,44 @@
<template>
<p class="styled-text-container">
<span class="first-part">{{ firstPart }}</span>
<span class="second-part">{{ secondPart }}</span>
</p>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
// 使用 defineProps 接收来自父组件的数据
defineProps({
firstPart: {
type: String,
required: true,
},
secondPart: {
type: String,
required: true,
},
})
</script>
<style scoped>
.styled-text-container {
/* 设置整体段落的样式 */
font-size: 16px;
color: #333; /* 默认颜色 */
}
.first-part {
/* 前半部分样式:加粗、更大字号 */
font-weight: bold;
font-size: 18px;
color: #1a1a1a;
margin-right: 2em; /* 添加一些间距 */
}
.second-part {
/* 后半部分样式:普通字体、灰色 */
font-weight: normal;
color: #666;
}
</style>

View File

View File

@@ -0,0 +1,43 @@
<template>
<section class="max-w-4xl mx-auto p-6 space-y-8">
<!-- 标题 -->
<h1 class="text-2xl font-bold text-gray-800">产品详情</h1>
<!-- Notice -->
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 rounded-lg shadow">
<h2 class="text-lg font-semibold text-yellow-700 mb-2">注意事项</h2>
<p class="text-gray-700 whitespace-pre-line">{{ product.notice }}</p>
</div>
<!-- Usage -->
<div class="bg-blue-50 border-l-4 border-blue-400 p-4 rounded-lg shadow">
<h2 class="text-lg font-semibold text-blue-700 mb-2">使用方法</h2>
<p class="text-gray-700 whitespace-pre-line">{{ product.usage }}</p>
</div>
<!-- Features -->
<div class="bg-green-50 border-l-4 border-green-400 p-4 rounded-lg shadow">
<h2 class="text-lg font-semibold text-green-700 mb-2">功能特点</h2>
<p class="text-gray-700 whitespace-pre-line">{{ product.features }}</p>
</div>
<!-- Introduction -->
<div class="bg-purple-50 border-l-4 border-purple-400 p-4 rounded-lg shadow">
<h2 class="text-lg font-semibold text-purple-700 mb-2">产品介绍</h2>
<p class="text-gray-700 whitespace-pre-line">{{ product.introduction }}</p>
</div>
</section>
</template>
<script setup lang="ts">
const product = {
notice:
'1. Tap water only\n2. Turn off the device promptly after the water tank is emptied to avoid dry running.',
usage:
'1 Unscrew the water tank and add an appropriate amount of tap water;\n2Press the switch, and the blue light will turn on; after the teeth are flushed, press the switch again, and the device will shut down;\n3If the red indicator light is on, it indicates a low battery, and a timely charge is required; the charger can be a regular phone charger, and after charging, the green indicator light will turn on.',
features:
'Building on traditional cleaning methods, this approach integrates low-pressure electrolyzed water technology to create a unique "hypochlorous acid-microbubble cavitation" system. It not only mimick the bactericidal mechanism of white blood cells through hypochlorous acid but also harnesses the "cavitation effect" of nanoscale microbubbles, enabling secondary cleaning of oral bacteria and dirt.',
introduction:
'The product uses electrolysis technology to convert chlorine elements in tap water into low-concentration hypochloric acid (5-10 ppm), while simultaneously forming a high-speed liquid with microbubbles of 0-0.5 µm and producing a "cavitation effect." This unique "hypochloric acid-microbubble cavitation" method simulates the bactericidal mechanism of neutrophils while simultaneously utilizing the "cavitation effect" of nano-sized microbubbles, achieve secondary removal of oral bacteria and dirt.',
}
</script>

View File

@@ -0,0 +1,96 @@
<template>
<div class="product-info">
<section class="info-section intro-section">
<h2 class="section-title">Introduction</h2>
<p class="section-text">{{ productData.introduction }}</p>
</section>
<section class="info-section features-section">
<h2 class="section-title">Features</h2>
<p class="section-text">{{ productData.features }}</p>
</section>
<section class="info-section usage-section">
<h2 class="section-title">Usage Instructions</h2>
<p class="section-text">{{ productData.usage }}</p>
</section>
<section class="info-section notice-section">
<h2 class="section-title">Notice</h2>
<p class="section-text">{{ productData.notice }}</p>
</section>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
defineProps({
productData: {
type: Object,
required: true,
default: () => ({
notice: '',
usage: '',
features: '',
introduction: '',
}),
},
})
</script>
<style scoped>
.product-info {
/* Center and constrain content for readability on large screens */
max-width: 900px;
margin: 0 auto;
padding: 2rem;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
color: #333;
line-height: 1.6;
}
.info-section {
padding: 2rem;
margin-bottom: 2rem;
border-radius: 8px;
background-color: #f9f9f9;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: transform 0.3s ease;
}
.info-section:hover {
transform: translateY(-5px);
}
.section-title {
font-size: 2rem;
font-weight: bold;
color: #2c3e50;
margin-bottom: 1rem;
border-bottom: 2px solid #e0e0e0;
padding-bottom: 0.5rem;
}
.section-text {
font-size: 1rem;
white-space: pre-line; /* Preserves line breaks from source text */
}
/* Specific section styles for visual differentiation */
.intro-section {
background-color: #e8f5e9; /* Light green */
}
.features-section {
background-color: #e3f2fd; /* Light blue */
}
.usage-section {
background-color: #fff3e0; /* Light orange */
}
.notice-section {
background-color: #ffebee; /* Light red */
}
</style>

View File

@@ -0,0 +1,238 @@
<template>
<div class="product-detail">
<h2 class="section-title">{{ $t('product-details') }}</h2>
<!-- 产品介绍 -->
<div class="info-card">
<div class="card-header" @click="toggleSection('introduction')">
<h3>{{ $t('product-intro') }}</h3>
</div>
<div class="card-content" v-if="visibleSections.introduction">
<p>{{ productData.introduction }}</p>
</div>
</div>
<!-- 产品特点 -->
<div class="info-card" v-if="productData.features">
<div class="card-header" @click="toggleSection('features')">
<h3>{{ $t('product-feature') }}</h3>
</div>
<div class="card-content" v-if="visibleSections.features">
<p>{{ productData.features }}</p>
</div>
</div>
<!-- 使用方法 -->
<div class="info-card">
<div class="card-header" @click="toggleSection('usage')">
<h3>{{ $t('usage-instruction') }}</h3>
</div>
<div class="card-content" v-if="visibleSections.usage">
<!-- <div class="step-item" v-for="(step, index) in usageSteps" :key="index">
<div class="step-number">{{ index + 1 }}</div>
<div class="step-text">{{ step }}</div>
</div> -->
<p>{{ productData.usage }}</p>
</div>
</div>
<!-- 注意事项 -->
<div class="info-card">
<div class="card-header" @click="toggleSection('notice')">
<h3>{{ $t('precautions') }}</h3>
</div>
<div class="card-content" v-if="visibleSections.notice">
<!-- <div class="notice-item" v-for="(note, index) in noticePoints" :key="index">
<i class="fas fa-check-circle"></i>
<span>{{ note }}</span>
</div> -->
<p>{{ productData.notice }}</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { type PropType } from 'vue'
// 定义 props 接口
interface ProductInfoData {
notice: string
usage: string
features: string
introduction: string
}
// 接收 props使用 PropType 确保类型正确性
defineProps({
productData: {
type: Object as PropType<ProductInfoData>,
required: true,
},
})
// 使用 ref 跟踪可见部分,键名可以更精确
const visibleSections = ref({
introduction: true,
features: true,
usage: true,
notice: true,
})
function toggleSection(section: keyof ProductInfoData) {
visibleSections.value[section] = !visibleSections.value[section]
}
// 暴露给模板使用
</script>
<style scoped>
.product-detail {
margin: 30px 0;
width: 90%;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding-left: 5%;
padding-right: 5%;
}
.section-title {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
font-weight: 600;
position: relative;
padding-bottom: 15px;
}
.section-title:after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
border-radius: 3px;
}
.info-card {
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 25px;
overflow: hidden;
transition: all 0.3s ease;
}
.info-card:hover {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.12);
transform: translateY(-3px);
}
.card-header {
display: flex;
align-items: center;
cursor: pointer;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: rgb(255, 255, 255);
position: relative;
}
.card-header h3 {
margin-left: 15px;
font-weight: 600;
font-size: 1.2rem;
flex-grow: 1;
}
.toggle-icon {
font-size: 1.1rem;
transition: transform 0.3s ease;
}
.card-content {
padding: 25px;
background: white;
}
.step-item {
display: flex;
margin-bottom: 20px;
align-items: flex-start;
}
.step-number {
width: 30px;
height: 30px;
border-radius: 50%;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
flex-shrink: 0;
margin-right: 15px;
}
.step-text {
color: #444;
line-height: 1.6;
}
.notice-item {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 12px 15px;
background: #f8f9ff;
border-radius: 8px;
transition: all 0.3s ease;
}
.notice-item:hover {
background: #eef2ff;
transform: translateX(5px);
}
.notice-item i {
color: #8d45db;
margin-right: 15px;
font-size: 1.2rem;
}
.notice-item span {
color: #444;
line-height: 1.6;
}
.card-content p {
color: #201515;
line-height: 1.8;
text-align: justify;
}
/* 响应式设计 */
@media (max-width: 768px) {
.card-header {
padding: 15px;
}
.card-header h3 {
font-size: 1.1rem;
}
.card-content {
padding: 15px;
}
.step-item {
flex-direction: column;
}
.step-number {
margin-bottom: 10px;
margin-right: 0;
}
}
</style>

248
src/components/temp.html Normal file
View File

@@ -0,0 +1,248 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>背景图标题组件</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
padding: 20px;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.demo-section {
background: white;
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
padding: 30px;
margin-bottom: 30px;
}
.demo-section h2 {
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0;
display: flex;
align-items: center;
}
.demo-section h2 i {
margin-right: 10px;
color: #6a11cb;
}
/* 背景图标题组件样式 */
.hero-section {
position: relative;
width: 100%;
height: 500px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
border-radius: 12px;
margin: 30px 0;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
}
.hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('https://images.unsplash.com/photo-1550751827-4bd374c3f58b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80');
background-size: cover;
background-position: center;
opacity: 0.5; /* 背景图透明度50% */
z-index: 1;
}
.hero-content {
position: relative;
z-index: 2;
text-align: center;
color: #2c3e50;
max-width: 800px;
padding: 40px;
background: rgba(255, 255, 255, 0.85);
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(5px);
}
.hero-title {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 20px;
color: #2c3e50;
position: relative;
padding-bottom: 15px;
}
.hero-title:after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
border-radius: 3px;
}
.hero-description {
font-size: 1.2rem;
line-height: 1.8;
margin-bottom: 30px;
color: #444;
}
.hero-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 30px;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(106, 17, 203, 0.2);
}
.hero-btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 16px rgba(106, 17, 203, 0.3);
}
.hero-btn i {
margin-right: 10px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.hero-section {
height: 400px;
}
.hero-content {
padding: 30px 20px;
margin: 0 15px;
}
.hero-title {
font-size: 2rem;
}
.hero-description {
font-size: 1.1rem;
}
}
@media (max-width: 480px) {
.hero-section {
height: 350px;
}
.hero-title {
font-size: 1.8rem;
}
.hero-description {
font-size: 1rem;
}
.hero-btn {
padding: 10px 20px;
font-size: 1rem;
}
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<div class="demo-section">
<h2><i class="fas fa-image"></i> 背景图标题组件演示</h2>
<hero-section
title="创新科技,呵护健康"
description="我们的产品采用先进的电解水技术,能够有效清除口腔细菌,保护牙齿健康。结合微气泡空化效应和次氯酸杀菌技术,提供双重清洁保护,为您带来卓越的口腔护理体验。"
background-image="https://images.unsplash.com/photo-1550751827-4bd374c3f58b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80"
button-text="了解更多"
></hero-section>
</div>
</div>
</div>
<script>
const { createApp } = Vue
// 背景图标题组件
const HeroSection = {
props: {
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
backgroundImage: {
type: String,
default: '',
},
buttonText: {
type: String,
default: '了解更多',
},
},
template: `
<div class="hero-section">
<div class="hero-bg" :style="{ backgroundImage: 'url(' + backgroundImage + ')' }"></div>
<div class="hero-content">
<h2 class="hero-title">{{ title }}</h2>
<p class="hero-description">{{ description }}</p>
<button class="hero-btn">
<i class="fas fa-info-circle"></i>{{ buttonText }}
</button>
</div>
</div>
`,
}
// 主应用
const app = createApp({})
app.component('HeroSection', HeroSection)
app.mount('#app')
</script>
</body>
</html>

8
src/components/temp.vue Normal file
View File

@@ -0,0 +1,8 @@
<video
controls="controls"
autoplay="autoplay"
playsinline="true"
h5-playsinline="true"
webkit-playsinline="true"
src="https://usmile.oss-cn-shenzhen.aliyuncs.com/cnsrc/89SxNcwY.mp4"
></video>

148
src/locales/en.json Normal file
View File

@@ -0,0 +1,148 @@
{
"company": {
"name": "HefeiChoice Health Technology",
"wholeName": "HefeiChoice Health Technology",
"introduction": "HefeiChoice Health Technology Company was established in 2020 as a technology-oriented small and medium-sized enterprise (SME) specializing in the application of public health technologies, as well as product design, R&D, and produce. In 2022, the company was awarded the titles of \"National High-Tech Enterprise\" and \"Hefei Big Data Enterprise\", and obtained the qualification for \"Disinfection Equipment Production License\"",
"property": "Our company currently holds 21 patents including 1 invention, 8 utility models, 4 design patents, 7 software copyrights, and 1 trademark. Additionally, 3 invention patents are under substantive examination."
},
"areas": {
"name": "领域",
"individual": {
"name": "Water Life",
"intro": "In the field of personal protection, Hefei Qiaoshi Health Technology Company has ingeniously applied low-pressure electrolyzed water technology to simulate the oxidative sterilization mechanism of neutrophils (intracellular hypochlorous acid generation and bactericidal effects), establishing a comprehensive, multi-tiered product system centered on \"on-site production of hypochlorous acid\". The company has also pioneered the \"Oral Care New Concept\" based on the HefeiChoice Solution within the industry.",
"items": [
"Direct Utilization of Chlorine in Tap Water: By directly utilizing the chlorine elements in tap water, this technology converts them into low-concentration hypochlorous acid (5-10 ppm, pH≈7.0), combined with the \"cavitation effect\" of micro-nano bubbles, achieving highly efficient sterilization and cleaning of teeth and gums.",
"Use food raw materials as substrates to produce hypochlorous acid with varying concentrations (25-98 ppm) and pH levels (5.8-7.0) tailored to different scenarios. These meet the needs of daily oral hygiene, inhibiting dental caries and plaque, alleviating periodontal diseases, while extending to the cleaning and deodorization of dental appliances/braces, as well as travel-friendly personal care."
]
},
"aged": {
"name": "Elderly Care"
}
},
"cyq": {
"video-url": "/videos/productvideos/cyq/en.mp4",
"name": "Electric Water Flosser",
"notice": "1. Tap water only 2. Turn off the device promptly after the water tank is emptied to avoid dry running. ",
"usage": "1 Unscrew the water tank and add an appropriate amount of tap water;2Press the switch, and the blue light will turn on; after the teeth are flushed, press the switch again, and the device will shut down;3If the red indicator light is on, it indicates a low battery, and a timely charge is required; the charger can be a regular phone charger, and after charging, the green indicator light will turn on.",
"features": "Building on traditional cleaning methods, this approach integrates low-pressure electrolyzed water technology to create a unique \"hypochlorous acid-microbubble cavitation\" system. It not only mimick the bactericidal mechanism of white blood cells through hypochlorous acid but also harnesses the \"cavitation effect\" of nanoscale microbubbles, enabling secondary cleaning of oral bacteria and dirt.",
"introduction": "The product uses electrolysis technology to convert chlorine elements in tap water into low-concentration hypochloric acid (5-10 ppm), while simultaneously forming a high-speed liquid with microbubbles of 0-0.5 µm and producing a \"cavitation effect.\" This unique \"hypochloric acid-microbubble cavitation\" method simulates the bactericidal mechanism of neutrophils while simultaneously structural diagram.utilizing the \"cavitation effect\" of nano-sized microbubbles, achieve secondary removal of oral bacteria and dirt.",
"ad": "Hypochlorous Acid + Microbubble Cavitation, Instantly electrolyzes low-concentration hypochlorous acid upon adding water",
"detail": [
{
"key": "Product Name",
"value": "Electric Water Flosser"
},
{
"key": "Working Voltage",
"value": "20V"
},
{ "key": "Tank Volume", "value": "Approx.250ml" },
{
"key": "Applicable Water Source",
"value": "Municipal Tap Water"
},
{
"key": "Hypochlorous Acid Concentration",
"value": "6-10ppm"
}
],
"adshort": "Geek Clean World Bionic Water Flosser",
"info": " The hypochlorous acid water flosser combines the advantages of high-efficiency cleaning and sterilization. It utilizes high-pressure pulsed water jets to effectively remove debris from hard-to-reach areas such as between teeth that are difficult to reach with toothbrushes. Leveraging low-concentration (5ppm) hypochlorous acid, it can rapidly kill oral bacteria and effectively inhibit dental plaque."
},
"cjb": {
"video-url": "/videos/productvideos/cjb/en.mp4",
"name": "Hypochlorous Acid Bionic Sterilizing Cup",
"notice": "(1) Use immediately after preparation.(2) Water temperature exceeding 40°C is not recommended.",
"scenarios": "(1)Cleans the oral cavity, suppresses dental caries and plaque, and relieves periodontal conditions (2)Soaks toothbrushes, orthodontic aligners, dentures, and other oral tools to neutralize odors.",
"features": "(1) Mimics white blood cell bactericidal mechanism: generates hypochlorous acid (HOCl)—the most critical bactericidal agent produced by white blood cells—through in vitro synthesis. (2) Food-Grade Formulation: all ingredients are derived from food-grade materials, and the product has passed \"oral toxicity-free\" certification.",
"usage": "(1) Add a small packet of special electrolyte and water or purified water for approximately 250ml(2) Press the power switch; the blue indicator light turns on, and the device automatically stops after approximately 90 seconds of operation. The mouthwash solution is now ready.(3) If the blue indicator light starts flashing, it indicates that the battery is low, and a timely charge is required. The charger can be a regular phone charger; when fully charged, the red indicator light turns green.",
"introduction": "This product utilizes electrolysis technology with food ingredients to generate low-concentration hypochlorous acid (18-30 ppm). By mimicking the bactericidal mechanism of white blood cells(neutrophils), it effectively eliminates oral pathogens, inhibits dental caries, and alleviates periodontal diseases. Special electrolyte consists of food materials (common salt and PBS) and the patent application number for electrolyte formula is No: 202410555233.1 ",
"detail": [
{
"key": "Product Name",
"value": "Hypochlorous Acid Bionic Sterilizing Cup"
},
{
"key": "Working Voltage",
"value": "9V-12V"
},
{
"key": "Maximum power",
"value": "12W"
},
{
"key": "Working Volume",
"value": "Approx.250ml"
},
{
"key": "Hypochlorous Acid Concentration",
"value": "Approx.25ppm"
}
],
"info": "Generates hypochlorous acid through electrolyte electrolysis.",
"adshort": "Sterilizes efficiently, Safe and eco-friendly"
},
"cjq": {
"video-url": "/videos/productvideos/cjq/en.mp4",
"name": "Multifunctional Electrolyzed Water Sterilizer",
"notice": "1. Prepare the reaction solution on-site and use it immediately. Low-concentration hypochlorous acid is recommended to be used within 2 hour; medium-concentration hypochlorous acid can be stored for 12 hours at room temperature and away from light. 2. After each use, clean the product promptly and keep it as dry as possible after cleaning. 3. Do not modify, disassemble, or repair the product by yourself, as this may cause damage to the main unit. 4. This product is prohibited from being immersed in water. 5. The generated solution is forbidden to drink, and it is not allowed to be mixed with detergents such as concentrated sulfuric acid and toilet cleaners at the same time.",
"cleaning": "1.After using the device, rinse it with clean water promptly and shake it dry.2.A cathode scale will form on the electrodes after long-term use, which may affect the service life of the device. It is recommended to clean the electrodes once a week with special electrolyte.",
"usage": "1. Unscrew the spray head, pour in approximately 25ml tap water, then screw the spray head tightly.2. Press and hold the power button for 1 second to start the device; the blue indicator light will turn on and automatically turn off after 1.5 minutes of operation. If the blue indicator light flashes, it indicates low battery, and the device needs to be charged promptly.Ps(1) Preventive Disinfection: Use only tap water with no additives; simply start the device directly.(2) Emergency Disinfection: Before starting, pull out one special electrolyte tube from the bottom cover and pour into the spray bottle containing 25ml of tap water, shake well and then start the device.(3) When charging, the green indicator light at the bottom turns on; the indicator light turns off once charging is complete.",
"scenarios": "(1) Preventive Disinfection (Low-concentration)Apply to the surfaces of contact objects such as handrails of public transportation (e.g., buses, subways, shared bikes), elevators, and express parcels.(2) Emergency Disinfection (Medium-concentration) Apply to pathogen-contaminated items, epidemic-related objects, and contaminants from blood or body fluids of patients with infectious diseases.",
"introduction": "This device can directly utilize the chlorine element in tap water and convert it into low-concentration hypochlorous acid (5-10mg/L) through electrolysis; alternatively, it can use special electrolyte to generate medium-concentration hypochlorous acid (approximately 100mg/L) with pH around 5.8.",
"detail": [
{
"key": "Product Name",
"value": "Multifunctional Electrolyzed Water Sterilizer"
},
{
"key": "Applicable Water Source",
"value": "Municipal Tap Water"
},
{
"key": "Capacity",
"value": "Approx.30ml"
},
{
"key": "Hypochlorous Acid Concentration",
"value": "6-10ppm (tap water only) Approx.98ppm (with dedicated electrolyte)"
}
],
"info": "Hypochlorous Acid, Ultimate Travel Companion",
"adshort": "Travel Companion"
},
"contact": {
"phone": "Phone: 18133670714 (Same number for WeChat)",
"email": "E-mail: ykl1979163.com",
"address": "Room 805, Building 5, Anhui Shangrong Big Health Industrial Park, Intersection of Baogong Avenue and Dazhong Road, Longgang Comprehensive Economic Development Zone, Yaohai District, Hefei City, Anhui Province"
},
"about": {
"name": "About Us",
"info": ""
},
"mainpage": {
"name": "Main"
},
"media": {
"name": "Contact Us"
},
"product-feature": "Product Feature",
"product-intro": "Product Introduction",
"usage-instruction": "Usage Instructions",
"precautions": "Precautions",
"product-details": "Product Details",
"property": "Company Property",
"learnMore": "Learn More",
"address": "Company Address",
"contactname": "Contact Info",
"copyright": "Copyright",
"store": "Store",
"others": "Others"
}

149
src/locales/zh.json Normal file
View File

@@ -0,0 +1,149 @@
{
"company": {
"name": "合肥巧士健康科技",
"wholeName": "合肥巧士健康科技有限责任公司",
"introduction": "合肥巧士健康科技成立于 2020 年,是一家专注于公共卫生领域技术应用,产品设计 研发与生产的科技型中小企业。公司于 2022 年荣获“国家高新技术企业” “合肥市大数据企业”称号,并取得“消毒器械生产许可”资质。",
"property": "公司现拥有知识产权 21 项,其中发明 1 项 实用新型 8 项 外观专利 4项 软著7项 商标 1 项,另有 2 项实用新型与 3 项发明正在实审阶段。"
},
"areas": {
"name": "领域",
"individual": {
"name": "水生活产品系列",
"intro": "在个人防护领域,公司巧妙运用低压电解水技术,体外模拟白细胞氧化杀菌机制(胞内次氯酸生成与杀菌),建立起以“次氯酸“即制即用为核心的全方位 多层次产品体系,并在行业内率先提出基于巧士方案的“口腔护理新概念“:",
"items": [
"1 直接利用自来水氯元素,将其转化为低浓度次氯酸(浓度 5-10ppm,PH 约 7.0,结合微纳米气泡的“空化效应”,实现齿与龈的高效杀菌与清洁作用。",
"2.2 采用混合电解法,以食品原料为基材,根据不同场景制备出浓度 25-98ppm PH5.8-7.0 不等的次氯酸。在满足日常口腔清洁 抑制龋齿与菌斑 缓解牙周疾病的同时,延伸到牙具牙套的清洁与异味消除以及差旅出行的日护领域"
]
},
"aged": {
"name": "慢病养老系列"
}
},
"cyq": {
"video-url": "/videos/productvideos/cyq/zh.mp4",
"name": "次氯酸冲牙器",
"notice": "1、水箱泵完后及时关机,避免空转运行。2、公司推崇马斯克的“第一性原理”理念,即产品从最基本的事实、规律和原理出发,不依赖传统经验或类比,通过逻辑推理或者演绎,进而得出结论和解决方法的思维方式。",
"usage": "1拧下水箱,加适量自来水;2按下开关,蓝灯亮起;冲牙完毕后再次按下开关,设备关机;3如红色指示灯亮起,说明电量不足,须及时充电;充电器可为常规手机充电器,充电完成后绿色指示灯亮起。",
"features": "在传统清洁模式基础上,融合低压电解水技术,形成独特的“次氯酸-微气泡空化”方式,既能模拟白细胞次氯酸杀菌机制,又能发挥纳米级微气泡的“空化效应”,实现口腔病菌与污物的二次清洁。",
"introduction": "本产品通过电解技术,将自来水中的氯元素转化为低浓度次氯酸5-10ppm,同时形成0-0.5um 的微气泡高速液体流并产生“空化效应”。这种独特的“次氯酸-微气泡空化”方式,模拟白细胞次氯酸杀菌机制的同时,发挥纳米级微气泡的“空化效应”,实现口腔病菌与污物的二次清除。",
"ad": "次氯酸+微气泡空化反应,加水即刻电解出低浓度次氯酸",
"detail": [
{
"key": "产品名称",
"value": "电动冲牙器"
},
{
"key": "工作电压",
"value": "20V"
},
{
"key": "水箱容积",
"value": "约250ml"
},
{
"key": "适用水源",
"value": "市政自来水"
},
{
"key": "次氯酸浓度",
"value": "6-10ppm"
}
],
"adshort": "极客净界,仿生水牙线",
"info": "次氯酸冲牙器兼具高效清洁与杀菌优势,以高压脉冲水流强力清除牙缝等牙刷难及处的残渣,同时依托低浓度5ppm次氯酸快速杀灭口腔细菌,有效抑制牙菌斑。"
},
"cjb": {
"video-url": "/videos/productvideos/cjb/zh.mp4",
"name": "次氯酸仿生除菌杯",
"notice": "1即制即用,次氯酸易分解,不宜久存2水温不建议超过40°C。",
"usage": "1杯中倒入专用电解质,加自来水或纯净水约250ml;2按下开关,蓝灯亮起,工作约90秒后自动停止, 漱口液制备完毕。3如工作时蓝色指示灯开始闪烁,说明电量不足,须及时充电;充电器可为常规手机充电器,充电时红色指示灯亮起,充满后则绿色指示灯亮起。",
"scenarios": "1清洁口腔,抑制龋齿与菌斑 缓解牙周疾病。2浸泡牙刷 牙套 义齿等牙具,消除异味。",
"features": "1模拟白细胞杀菌机制,体外生成白细胞最重要的杀菌因子-次氯酸。2配方均源自食品材料,产物已通过“经口无毒”检测。",
"introduction": "本产品通过食品原料的电解技术,生成低浓度次氯酸18-30ppm,模拟白细胞杀菌机制发挥清除口腔病菌 抑制龋齿 缓解牙周疾病的作用。1清洁口腔,抑制龋齿与菌斑 缓解牙周疾病。2浸泡牙刷 牙套 义齿等牙具,消除异味。电解配方专利申请号2024105552331",
"detail": [
{
"key": "产品名称",
"value": "次氯酸仿生除菌杯"
},
{
"key": "工作电压",
"value": "9V-12V"
},
{
"key": "最大功率",
"value": "12W"
},
{
"key": "工作容积",
"value": "约250ml"
},
{
"key": "次氯酸浓度",
"value": "约25ppm"
}
],
"info": "电解质电解生成次氯酸",
"adshort": "高效除菌,安全环保"
},
"cjq": {
"video-url": "/videos/productvideos/cjq/zh.mp4",
"name": "多功能电解水除菌器",
"notice": "1、反应液现配现用,低浓度次氯酸建议在1小时内使用,中浓度室温、避光条件下可保存24小时。2、每次使用后,及时清洗,洗后尽量保持干燥。3、请勿自行改装、拆解、维修以免导致主机损坏。如因外力导致主机破损、脱落、无法工作时,请停止使用并且联系客服维修。4、本产品禁止放入水中。5、生成后的溶液禁止饮用,禁止与浓硫酸、洁厕灵等洗涤剂同时混用。",
"clean": "1、设备使用完毕后,及时清水冲洗、甩干。2、 电极长期使用将形成阴极垢,影响设备寿命,建议使用专用清洗剂每周清洗一次(专用清洗剂可与厂家联系)。",
"usage": "1. 拧开喷头,注入约25ml自来水离瓶肩1-2mm处,旋紧喷头。2. 长按开机键1秒,启动设备,蓝色指示灯亮起,工作1.5分钟后自动熄灭。若蓝色指示灯闪烁,提示电量不足,需及时充电。(1) 预防性消毒 仅用自来水,无添加物,直接启动设备即可。(2) 应急性消毒 启动前,从底盖拔出一支专用电解质盐管,用瓶盖量坑量取0.1克,倒入含25ml自来水的喷瓶中,充分摇匀溶解后,再启动设备。3. 充电时,底部绿色指示灯亮起,充电结束后指示灯熄灭。",
"introduction": "本设备可直接利用自来水中的氯元素,通过电解将其转化为低浓度次氯酸5-10mg/L或利用专用的电解质溶液,电解生成PH约5.8的中浓度次氯酸约100mg/L。",
"scenarios": "1预防性消毒低浓度次氯酸公交车 地铁 单车等公共交通工具扶把手,或电梯 快递件等接触物表面。2应急性消毒中高浓度次氯酸病菌感染物 涉疫物品 传染病患者血液或体液污染物等。",
"detail": [
{
"key": "产品名称",
"value": "多功能电解水除菌器"
},
{
"key": "适用水源",
"value": "市政自来水"
},
{
"key": "容量",
"value": "约30毫升"
},
{
"key": "次氯酸浓度",
"value": "6-10ppm (仅自来水) 约98ppm (专用电解质)"
}
],
"info": "差旅神器——现制现用小喷瓶",
"adshort": "差旅神器"
},
"contact": {
"phone": "联系方式\n电话18133670714微信同号",
"email": "E-mail: ykl1979@163.com",
"address": "地址:安徽省合肥市瑶海区龙岗综合经济开发区包公大道与大众路交口安徽尚荣大健康--产业园5栋805室"
},
"about": {
"name": "关于我们",
"info": ""
},
"mainpage": {
"name": "首页"
},
"media": {
"name": "联系我们"
},
"product-feature": "产品特点",
"product-intro": "产品介绍",
"usage-instruction": "使用方法",
"precautions": "注意事项",
"product-details": "产品详细信息",
"property": "公司专利",
"learnMore": "了解更多",
"address": "公司地址",
"contactname": "联系方式",
"copyright": "版权所有",
"store": "商城",
"others": "其他"
}

25
src/main.ts Normal file
View File

@@ -0,0 +1,25 @@
// import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createI18n } from 'vue-i18n' // node_modules\vue-i18n
import zh from './locales/zh.json'
import en from './locales/en.json'
import App from './App.vue'
import router from './router'
const app = createApp(App)
const i18n = createI18n({
locale: 'en', // 默认语言
fallbackLocale: 'zh', // 备用语言
messages: {
zh,
en,
},
})
app.use(i18n) // 将 i18n 实例添加到 Vue 应用中
app.use(createPinia())
app.use(router)
app.mount('#app')

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

@@ -0,0 +1,43 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: () => import('../views/HomeView.vue'),
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
{
path: '/waterlife',
name: 'waterlife',
component: () => import('../views/WaterLifeProducts.vue'),
},
{
path: '/agedlife',
name: 'agedlife',
component: () => import('../views/AgedLifeProducts.vue'),
},
{
path: '/media',
name: 'media',
component: () => import('../views/MediaView.vue'),
},
{
path: '/greenproducts',
name: 'media',
component: () => import('../views/MediaView.vue'),
},
],
scrollBehavior(to, from, savedPosition) {
// 始终滚动到页面顶部
return { top: 0, behavior: 'smooth' }
},
})
export default router

12
src/stores/counter.ts Normal file
View File

@@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

67
src/views/AboutView.vue Normal file
View File

@@ -0,0 +1,67 @@
<template>
<div class="home-view">
<AboutSection
v-for="section in aboutSections"
:key="section.title"
:reverse="section.reverse"
:title="$t(section.title)"
:description="$t(section.description)"
:background-image="section.backgroundImage"
:buttonshow="section.buttonShow"
:targetPath="section.targetPath"
/>
</div>
</template>
<!-- <script>
export default {
name: 'HomeView',
}
</script> -->
<script setup lang="ts">
import AboutSection from '@/components/AboutSection.vue'
import { ref } from 'vue'
import logoImage from '/images/logo.png'
import product2 from '/images/product2.png'
const aboutSections = ref([
{
title: 'company.name',
description: 'company.introduction',
backgroundImage: logoImage,
reverse: true,
buttonShow: false,
targetPath: '',
},
{
title: 'property',
description: 'company.property',
backgroundImage: '', // or whatever the default is
reverse: false,
buttonShow: false,
targetPath: '',
},
{
title: 'areas.individual.name',
description: 'areas.individual.intro',
backgroundImage: product2,
reverse: false,
buttonShow: true,
targetPath: '/waterlife',
},
{
title: 'areas.aged.name',
description: 'company.introduction',
backgroundImage: product2,
reverse: false,
buttonShow: true,
targetPath: '',
},
])
</script>
<style scoped>
.home-view {
background-color: rgb(241, 244, 247);
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div class="home-view">
<AboutSection
v-for="section in aboutSections"
:key="section.title"
:reverse="section.reverse"
:title="$t(section.title)"
:description="$t(section.description)"
:background-image="section.backgroundImage"
:buttonshow="section.buttonShow"
:targetPath="section.targetPath"
/>
</div>
</template>
<!-- <script>
export default {
name: 'HomeView',
}
</script> -->
<script setup lang="ts">
import AboutSection from '@/components/AboutSection.vue'
import { ref } from 'vue'
import logoImage from '/images/logo.png'
import product2 from '/images/product2.png'
const aboutSections = ref([
{
title: 'company.name',
description: 'company.introduction',
backgroundImage: logoImage,
reverse: true,
buttonShow: false,
targetPath: '',
},
{
title: 'property',
description: 'company.property',
backgroundImage: '', // or whatever the default is
reverse: false,
buttonShow: false,
targetPath: '',
},
{
title: 'areas.individual.name',
description: 'areas.individual.intro',
backgroundImage: product2,
reverse: false,
buttonShow: true,
targetPath: '/waterlife',
},
{
title: 'areas.aged.name',
description: 'company.introduction',
backgroundImage: product2,
reverse: false,
buttonShow: true,
targetPath: '',
},
])
</script>
<style scoped>
.home-view {
background-color: rgb(241, 244, 247);
}
</style>

67
src/views/HomeView.vue Normal file
View File

@@ -0,0 +1,67 @@
<template>
<div class="home-view">
<AboutSection
v-for="section in aboutSections"
:key="section.title"
:reverse="section.reverse"
:title="$t(section.title)"
:description="$t(section.description)"
:background-image="section.backgroundImage"
:buttonshow="section.buttonShow"
:targetPath="section.targetPath"
/>
</div>
</template>
<!-- <script>
export default {
name: 'HomeView',
}
</script> -->
<script setup lang="ts">
import AboutSection from '@/components/AboutSection.vue'
import { ref } from 'vue'
import logoImage from '/images/logo.png'
import product2 from '/images/product2.png'
const aboutSections = ref([
{
title: 'company.name',
description: 'company.introduction',
backgroundImage: logoImage,
reverse: true,
buttonShow: false,
targetPath: '',
},
{
title: 'property',
description: 'company.property',
backgroundImage: '', // or whatever the default is
reverse: false,
buttonShow: false,
targetPath: '',
},
{
title: 'areas.individual.name',
description: 'areas.individual.intro',
backgroundImage: product2,
reverse: false,
buttonShow: true,
targetPath: '/waterlife',
},
{
title: 'areas.aged.name',
description: 'company.introduction',
backgroundImage: product2,
reverse: false,
buttonShow: true,
targetPath: '',
},
])
</script>
<style scoped>
.home-view {
background-color: rgb(255, 255, 255);
}
</style>

67
src/views/MediaView.vue Normal file
View File

@@ -0,0 +1,67 @@
<template>
<div class="home-view">
<AboutSection
v-for="section in aboutSections"
:key="section.title"
:reverse="section.reverse"
:title="$t(section.title)"
:description="$t(section.description)"
:background-image="section.backgroundImage"
:buttonshow="section.buttonShow"
:targetPath="section.targetPath"
/>
</div>
</template>
<!-- <script>
export default {
name: 'HomeView',
}
</script> -->
<script setup lang="ts">
import AboutSection from '@/components/AboutSection.vue'
import { ref } from 'vue'
import logoImage from '/images/logo.png'
import product2 from '/images/product2.png'
const aboutSections = ref([
{
title: 'company.name',
description: 'company.introduction',
backgroundImage: logoImage,
reverse: true,
buttonShow: false,
targetPath: '',
},
{
title: 'property',
description: 'company.property',
backgroundImage: '', // or whatever the default is
reverse: false,
buttonShow: false,
targetPath: '',
},
{
title: 'areas.individual.name',
description: 'areas.individual.intro',
backgroundImage: product2,
reverse: false,
buttonShow: true,
targetPath: '/waterlife',
},
{
title: 'areas.aged.name',
description: 'company.introduction',
backgroundImage: product2,
reverse: false,
buttonShow: true,
targetPath: '',
},
])
</script>
<style scoped>
.home-view {
background-color: rgb(241, 244, 247);
}
</style>

View File

@@ -0,0 +1,283 @@
<template>
<div class="product-display">
<ProductDetail
v-for="product in products"
:key="product.name"
reverse
:title="$t(`${product.name}.name`)"
:content="$t(`${product.name}.info`)"
:imagetitle="$t(`${product.name}.name`)"
:info="$t(`${product.name}.info`)"
:detailList="product.detailList"
:imageUrl="product.imageUrl"
:videoUrl="$t(`${product.name}.video-url`)"
/>
</div>
<div class="product-move">
<div class="product-list">
<div
v-for="product in products"
:key="product.name"
class="product-card"
:class="{ active: selectedProduct === product.name }"
@click="selectProduct(product.name)"
>
<img :src="product.imageUrl" class="product-image" />
</div>
</div>
<div class="detail-section">
<transition name="fade" mode="out-in">
<seekDetail
v-if="selectedProductData"
:key="selectedProductData.name"
:productData="selectedProductData.infoData"
/>
</transition>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import ProductDetail from '@/components/ProductDetail.vue'
import { useI18n } from 'vue-i18n'
import seekDetail from '@/components/seekDetail.vue'
const { tm } = useI18n()
// import { useI18n } from 'vue-i18n'
// const { t } = useI18n()
interface DetailItem {
key: string
value: string
}
// products 数据
interface DetailItem {
key: string
value: string
}
interface ProductInfoData {
notice: string
usage: string
features: string
introduction: string
}
const selectedProductData = computed(() => {
return products.value.find((p) => p.name === selectedProduct.value)
})
import cyqImage from '@/assets/water/cyq.png'
import cjqImage from '@/assets/water/cjq.png'
import cjbImage from '@/assets/water/cjb.png'
const products = ref([
{
name: 'cyq',
detailList: tm('cyq.detail') as unknown as DetailItem[],
imageUrl: cyqImage,
infoData: tm('cyq') as unknown as ProductInfoData,
},
{
name: 'cjb',
detailList: tm('cyq.detail') as unknown as DetailItem[],
imageUrl: cjbImage,
infoData: tm('cjb') as unknown as ProductInfoData,
},
{
name: 'cjq',
detailList: tm('cyq.detail') as unknown as DetailItem[],
imageUrl: cjqImage,
infoData: tm('cjq') as unknown as ProductInfoData,
},
])
const selectedProduct = ref('')
// 切换产品方法
function selectProduct(productName: string) {
selectedProduct.value = productName
}
</script>
<style scoped>
.main-display {
}
/* 产品展示区域样式 */
.product-display {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 30px;
}
.product-move {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.product-list {
display: flex;
justify-content: center;
gap: 20px;
width: 100%;
}
.product-card {
display: flex;
justify-content: center;
align-items: center;
width: 20%;
height: auto;
background: white;
border-radius: 12px;
transition: all 0.3s ease;
cursor: pointer;
overflow: hidden;
position: relative;
}
.product-card:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(106, 17, 203, 0.1) 0%, rgba(37, 117, 252, 0.1) 100%);
opacity: 0;
transition: opacity 0.2s ease;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
.product-card:hover:before {
opacity: 1;
}
.product-card.active {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(106, 17, 203, 0.2);
}
.product-card.active:before {
opacity: 1;
}
.product-image {
width: 80%;
height: auto;
object-fit: contain;
transition: transform 0.3s ease;
}
.product-card:hover .product-image {
transform: scale(1.05);
}
.detail-section {
width: 100%;
display: flex;
justify-content: center;
margin-top: 20px;
}
/* 过渡动画 */
.fade-enter-active,
.fade-leave-active {
transition:
opacity 0.5s ease,
transform 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(20px);
}
/* 响应式设计 */
@media (max-width: 768px) {
.product-list {
gap: 15px;
}
.product-card {
width: 140px;
height: 140px;
}
.product-display {
gap: 30px;
}
}
@media (max-width: 480px) {
.product-list {
gap: 10px;
}
.product-card {
width: 110px;
height: 110px;
}
}
</style>
<!--
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.6s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.product-display {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 50px;
}
.product-move {
display: flex;
flex-direction: column; /* 内部上下排列 */
align-items: center;
gap: 2%;
}
.product-list {
display: flex;
gap: 1em;
}
.product-image {
height: auto;
width: 80%;
}
.product-card {
/* 开启 Flexbox */
display: flex;
justify-content: center;
align-items: center;
}
.product-card:hover,
.product-card.active {
box-shadow: 10px 10px 12px rgba(14, 70, 174, 0.15);
}
.detail-section {
display: flex;
flex-direction: column;
align-items: center;
}
</style> -->