This commit is contained in:
sleepwithoutbz
2025-11-10 21:48:25 +08:00
parent de451f2aab
commit 29dbd5bc2f
84 changed files with 39338 additions and 123 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 407 KiB

View File

@@ -3,10 +3,12 @@
<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>
<p class="hero-description" >
{{ description }}
</p>
<button v-if="buttonshow" class="hero-btn" @click="goWaterlife">
>
{{ $t('learnMore') }}
{{ $t('enter') }}
</button>
</div>
</div>
@@ -23,7 +25,6 @@ function goWaterlife() {
})
}
const props = defineProps({
targetPath: {
type: String,
@@ -119,7 +120,6 @@ const props = defineProps({
line-height: 1.8;
margin-bottom: 30px;
color: #444;
font-style: italic;
}
.hero-btn {
@@ -151,40 +151,24 @@ const props = defineProps({
/* 响应式设计 */
@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: 500px;
}
.hero-content {
padding: 10px 10px;
max-width: 100%;
margin: 0;
}
.hero-title {
font-size: 1.8rem;
font-size: 1.6rem;
margin-bottom: 10px;
}
.hero-description {
font-size: 1rem;
}
.hero-btn {
padding: 10px 20px;
font-size: 1rem;
font-size: 0.9rem;
line-height: 1.8;
margin-bottom: 10px;
}
}
</style>

View File

@@ -0,0 +1,154 @@
<template>
<div class="hero-section">
<div class="hero-bg" :style="{ backgroundImage: 'url(' + backgroundImage + ')' }">
<button class="hero-btn" @click="goWaterlife">> {{ $t('enter') }}</button>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { defineProps } from 'vue'
// onMounted(() => {
// window.addEventListener('resize', updateWindowSize)
// })
// onUnmounted(() => {
// window.removeEventListener('resize', updateWindowSize)
// })
// const windowWidth = ref(window.innerWidth)
// const updateWindowSize = () => {
// windowWidth.value = window.innerWidth
// }
const router = useRouter()
function goWaterlife() {
router.push({
path: props.targetPath,
})
}
const props = defineProps({
targetPath: {
type: String,
required: false,
},
backgroundImage: {
type: String,
default: '',
},
})
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* 背景图标题组件样式 */
.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;
display: inline-flex;
align-items: center;
justify-content: center;
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: contain; */
background-size: cover;
background-position: center;
/* 背景图透明度50% */
/* opacity: 0.5; */
z-index: 1;
}
.hero-btn {
/* display: inline-flex;
align-items: center;
justify-content: center; */
padding: 12px 30px;
margin-top: 250px;
margin-right: 400px;
/* background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); */
background: rgb(108, 186, 254);
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: 200px;
}
.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: contain; */
background-size: scale-down;
background-position: center;
/* 背景图透明度50% */
/* opacity: 0.5; */
z-index: 1;
}
.hero-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 15px;
margin-top: 130px;
margin-right: 100px;
/* background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); */
background: rgba(108, 186, 254, 0.5);
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);
}
}
</style>

View File

@@ -4,8 +4,6 @@
<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 }]">
@@ -22,12 +20,16 @@
</nav>
<div class="language-switcher">
<router-link to="/store" class="nav-item">{{ $t('pages.store.name') }}</router-link>
<!-- 菜单按钮只在小屏显示 -->
<button class="mobile-menu-btn" @click="toggleMenu"> More</button>
<select v-model="$i18n.locale" class="lang-selector">
<!-- <select v-model="$i18n.locale" class="lang-selector">
<option value="zh">中文</option>
<option value="en">English</option>
</select>
</select> -->
<!-- 中英文滑块开关 -->
<!-- <input type="checkbox" class="checke" /> -->
<Switch></Switch>
</div>
</header>
</div>
@@ -36,8 +38,17 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Switch from './Switch.vue'
const { t } = useI18n()
const { t, locale } = useI18n()
const changeLanguage = () => {
if (locale.value == 'zh') {
locale.value = 'en'
} else {
locale.value = 'zh'
}
}
const menuItems = ref([
{ name: 'pages.mainpage.name', path: '/' },
@@ -47,8 +58,7 @@ const menuItems = ref([
{ name: 'pages.waterlife_bottle.name', path: '/waterlife_bottle' },
{ name: 'pages.aged.name', path: '/todo' },
{ name: 'pages.knowledge.name', path: '/knowledge' },
{ name: 'pages.contact_us.name', path: '/todo' },
{ name: 'pages.others.name', path: '/todo' },
{ name: 'pages.store.name', path: '/store' },
])
const isMenuOpen = ref(false)
@@ -105,7 +115,7 @@ body {
.company-name {
font-size: 1.6rem;
color: rgb(0, 0, 0);
font-weight: 700;
/* font-weight: 700; */
letter-spacing: 1px;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
}
@@ -201,8 +211,39 @@ body {
border: none;
color: rgb(0, 0, 0);
font-size: 1.5rem;
/* font-weight: 700; */
cursor: pointer;
}
/* 滑块样式 */
/* .checke {
position: relative;
-webkit-appearance: none;
width: 60px;
height: 30px;
line-height: 30px;
background: #0a7cff;
border-radius: 20px;
outline: none;
}
.checke:before {
position: absolute;
left: 0;
content: '';
width: 30px;
height: 30px;
border-radius: 50%;
background: #eee;
box-shadow: 0px 0px 5px #ddd;
transition: all 0.2s linear;
}
.checke:checked {
background: #18ff0a;
}
.checke:checked:before {
left: 30px;
transition: all 0.2s linear;
} */
/* 响应式设计 */
@media (max-width: 1024px) {
@@ -248,15 +289,24 @@ body {
}
.language-switcher {
align-items: center;
margin-left: auto;
margin-right: auto;
}
.mobile-menu-btn {
display: block;
background: none;
border: none;
color: rgb(0, 0, 0);
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
}
.company-name {
font-size: 1.4rem;
font-size: 1.3rem;
width: 70%;
}
}

View File

@@ -4,6 +4,31 @@
<div v-if="imageUrl" class="image-container">
<h3 class="image-title">{{ imagetitle }}</h3>
<img :src="imageUrl" :alt="imageAlt" class="section-image" />
<div class="mobile-image">
<img
src="/images/video_play.png"
alt="Video"
class="single-video-btn"
@click="((curVideoUrl = videoUrls[0].url), (showVideo = true))"
/>
<img :src="imageUrl" :alt="imageAlt" class="mobile-section-image" />
</div>
<div class="mobile-btns">
<!-- 第一个按钮放到了图片中央 -->
<button
v-for="(videoUrl, idx) in videoUrls"
:key="videoUrl.url"
class="video-btn"
:style="idx == 0 ? 'display:none;' : ''"
@click="((curVideoUrl = videoUrl.url), (showVideo = true))"
>
{{ videoUrl.display }}
<img src="/images/camera.png" alt="Video" class="video_icon" />
</button>
<button class="manual-btn" @click="downloadManual">
{{ $t(`operation.download_manual`) }}
</button>
</div>
</div>
<!-- 右侧产品参数 -->
@@ -16,33 +41,39 @@
/>
<!-- 右侧视频播放与手册下载 -->
<p v-if="ad" class="text1">{{ ad }}</p>
<p class="text2">
<a class="text2-intro"> {{ info }} </a>
<br />
<button
v-for="videoUrl in videoUrls"
:key="videoUrl"
class="video-btn"
@click="
showVideo = true,
curVideoUrl = videoUrl
"
>
{{ $t(`operation.watch_video_learn_more`) }}
<img src="/images/camera.png" alt="Video" class="video_icon" />
</button>
<button class="manual-btn" @click="downloadManual">
{{ $t(`operation.download_manual`) }}
</button>
</p>
<div class="text2">
<div class="text2-intro">
<h3>{{ info }}</h3>
</div>
<div class="text2-btn">
<button
v-for="videoUrl in videoUrls"
:key="videoUrl.url"
class="video-btn"
@click="((curVideoUrl = videoUrl.url), (showVideo = true))"
>
{{ videoUrl.display }}
<img src="/images/camera.png" alt="Video" class="video_icon" />
</button>
<button class="manual-btn" @click="downloadManual">
{{ $t(`operation.download_manual`) }}
</button>
</div>
</div>
<teleport to="body">
<div v-if="showVideo" class="video-modal-overlay" @click.self="showVideo = false">
<div v-show="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="curVideoUrl" type="video/mp4" />
<video
ref="videoRef"
controls
webkit-playsinline
style="width: 100%"
:src="curVideoUrl"
type="video/mp4"
>
您的浏览器不支持视频播放
</video>
</div>
@@ -53,17 +84,16 @@
</template>
<script setup lang="ts">
import { defineProps, ref } from 'vue'
import RowContent from './RowContent.vue'
import type { PropType } from 'vue'
import type { DetailItem } from '@/types/product'
import { useI18n } from 'vue-i18n'
const curVideoUrl = ref('')
import type { VideoUrl } from '@/types/VideoUrl'
import { defineProps, ref, nextTick, watch } from 'vue'
const props = defineProps({
videoUrls: {
type: Array as PropType<string[]>,
type: Array as PropType<VideoUrl[]>,
required: true,
},
detailList: {
@@ -104,6 +134,22 @@ const props = defineProps({
const showVideo = ref(false)
const curVideoUrl = ref('')
const videoRef = ref<HTMLVideoElement>()
watch(showVideo, async (val) => {
if (videoRef.value) {
if (!val) {
videoRef.value.pause()
} else {
// 等待video加载
await nextTick()
videoRef.value.play()
}
}
})
// 下载中英文手册
const { locale } = useI18n()
const downloadManual = () => {
@@ -129,6 +175,7 @@ const downloadManual = () => {
cursor: pointer;
transition: all 0.3s ease;
margin-top: 15px;
margin-right: 10px;
box-shadow: 0 4px 12px rgba(106, 17, 203, 0.2);
}
@@ -172,7 +219,7 @@ const downloadManual = () => {
cursor: pointer;
transition: all 0.3s ease;
margin-top: 15px;
margin-left: 15px;
margin-right: 10px;
box-shadow: 0 4px 12px rgba(106, 17, 203, 0.2);
}
.manual-btn:hover {
@@ -198,7 +245,7 @@ const downloadManual = () => {
border-radius: 12px;
overflow: hidden;
position: relative;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.8);
/* box-shadow: 0 0 20px rgba(0, 0, 0, 0.8); */
}
/* 视频全屏充满弹窗 */
@@ -302,6 +349,14 @@ h1:after {
border-radius: 8px;
}
.mobile-image {
display: none;
}
.mobile-btns {
display: none;
}
.section-image {
width: 100%;
max-width: 300px;
@@ -344,10 +399,10 @@ h1:after {
.text2-intro {
display: inline-flex;
flex-direction: column;
font-size: 1.4em;
font-size: 1.3em;
color: #2c3e50;
font-weight: 600;
padding: 15px;
padding: 10px;
max-width: 1000px;
/* background: linear-gradient(135deg, rgb(242, 242, 242) 0%, rgb(108, 185, 254) 100%); */
background: rgba(108, 186, 254, 0.5);
@@ -375,6 +430,23 @@ h1:after {
order: -1;
margin-bottom: 20px;
}
/* 弹窗 */
.video-modal-content {
width: auto;
height: auto;
border-radius: 2px;
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: contain;
}
}
@media (max-width: 768px) {
@@ -383,11 +455,103 @@ h1:after {
}
.text2 {
display: none;
font-size: 1.2em;
}
.image-title {
font-size: 1.3em;
}
.mobile-image {
display: flex;
position: relative;
}
.mobile-btns {
display: flex;
position: relative;
display: grid;
grid-template-columns: 1fr; /* 关键定义1列每列宽度为1等份剩余空间 */
gap: 10px; /* 设置行和列之间的间隙 */
}
.mobile-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;
}
.single-video-btn {
position: absolute;
top: 45%;
left: 45%;
width: 50px;
height: 50px;
}
.section-image {
display: none;
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;
}
/* 弹窗 */
.video-modal-content {
width: auto;
height: auto;
/* max-height: 90%; */
border-radius: 0px;
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: scale-down;
}
.text2 {
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.16);
margin-bottom: 25px;
overflow: hidden;
transition: all 0.3s ease;
padding: 0px;
}
.text2-intro {
display: flex;
align-items: center;
cursor: pointer;
font-size: 1.3rem;
/* background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); */
background: rgb(108, 186, 254);
color: rgb(255, 255, 255);
position: relative;
border-radius: 0px;
}
.text2-intro h3 {
align-items: center;
font-size: 1.1rem;
margin-top: 10px;
margin-bottom: 10px;
}
.text2-btn {
padding: 25px;
background: white;
}
}
</style>

View File

@@ -41,4 +41,54 @@ defineProps({
font-weight: normal;
color: #666;
}
/* 响应式设计 */
@media (max-width: 968px) {
.styled-text-container {
/* 设置整体段落的样式 */
font-size: 16px;
color: #333; /* 默认颜色 */
margin-top: 4px;
margin-bottom: 4px;
}
.first-part {
/* 前半部分样式:加粗、更大字号 */
font-weight: bold;
font-size: 18px;
color: #1a1a1a;
margin-right: 2em; /* 添加一些间距 */
}
.second-part {
/* 后半部分样式:普通字体、灰色 */
font-weight: normal;
color: #666;
margin-top: 10px;
}
}
@media (max-width: 768px) {
.styled-text-container {
/* 设置整体段落的样式 */
font-size: 16px;
color: #333; /* 默认颜色 */
margin-top: 2px;
margin-bottom: 2px;
}
.first-part {
/* 前半部分样式:加粗、更大字号 */
font-weight: bold;
font-size: 18px;
color: #1a1a1a;
margin-right: 2em; /* 添加一些间距 */
}
.second-part {
/* 后半部分样式:普通字体、灰色 */
font-weight: normal;
color: #666;
}
}
</style>

178
src/components/Switch.vue Normal file
View File

@@ -0,0 +1,178 @@
<template>
<div class="m-switch-wrap">
<div
@click="disabled ? (e) => e.preventDefault() : onSwitch()"
:class="['m-switch', { 'switch-checked': true, disabled: disabled }]"
>
<div :class="['u-switch-inner', checkedVal ? 'inner-checked' : 'inner-unchecked']">
{{ checkedVal ? 'En' : '中' }}
</div>
<div :class="['u-node', { 'node-checked': checkedVal }]"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, withDefaults } from 'vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
// 定义Props的接口类型
interface Props {
defaultChecked?: boolean
disabled?: boolean
}
// 使用withDefaults为props提供默认值
const props = withDefaults(defineProps<Props>(), {
defaultChecked: false,
disabled: false,
})
// 响应式状态使用ref并指定类型为boolean
const checkedVal = ref<boolean>(false)
// 初始化开关状态的函数
const initSwitcher = (): void => {
// 否则使用defaultChecked的默认值或传入值
checkedVal.value = props.defaultChecked
}
// 监听父组件传递的checked值的变化
watch(
() => {},
() => {
initSwitcher()
},
)
// 监听defaultChecked的变化仅在checked为null时生效
watch(
() => props.defaultChecked,
() => {},
)
// 切换开关状态的方法
const onSwitch = (): void => {
checkedVal.value = !checkedVal.value
if (locale.value == 'en') {
locale.value = 'zh'
} else {
locale.value = 'en'
}
}
// 组件挂载时初始化状态
initSwitcher()
</script>
<style scoped>
.m-switch-wrap {
height: 33px;
min-width: 66px;
display: inline-block;
.m-switch {
position: relative;
height: 33px;
color: rgba(0, 0, 0, 0.65);
font-size: 21px;
background: rgba(0, 0, 0, 0.25);
border-radius: 100px;
cursor: pointer;
transition: background 0.36s;
.u-switch-inner {
display: inline-block;
color: #fff;
font-size: 21x;
line-height: 33px;
padding: 0 12px;
transition: all 0.36s;
}
.inner-checked {
margin-right: 27px;
}
.inner-unchecked {
margin-left: 27px;
}
.u-node {
position: absolute;
top: 3px;
left: 3px;
width: 27px;
height: 27px;
background: #fff;
border-radius: 100%;
cursor: pointer;
transition: all 0.36s;
}
.node-checked {
left: 100%;
margin-left: -2px;
transform: translateX(-100%);
}
}
.switch-checked {
background: #1890ff;
}
.disabled {
cursor: not-allowed;
opacity: 0.4;
}
}
@media (max-width: 768px) {
.m-switch-wrap {
height: 22px;
min-width: 44px;
display: inline-block;
.m-switch {
position: relative;
height: 22px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
background: rgba(0, 0, 0, 0.25);
border-radius: 100px;
cursor: pointer;
transition: background 0.36s;
.u-switch-inner {
display: inline-block;
color: #fff;
font-size: 14px;
line-height: 22px;
padding: 0 8px;
transition: all 0.36s;
}
.inner-checked {
margin-right: 18px;
}
.inner-unchecked {
margin-left: 18px;
}
.u-node {
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
background: #fff;
border-radius: 100%;
cursor: pointer;
transition: all 0.36s;
}
.node-checked {
left: 100%;
margin-left: -2px;
transform: translateX(-100%);
}
}
.switch-checked {
background: #1890ff;
}
.disabled {
cursor: not-allowed;
opacity: 0.4;
}
}
}
</style>

View File

@@ -210,7 +210,7 @@ function toggleSection(section: keyof ProductInfoData) {
/* 响应式设计 */
@media (max-width: 768px) {
.card-header {
padding: 15px;
padding: 0px;
}
.card-header h3 {

View File

@@ -31,15 +31,9 @@
"mainpage": {
"name": "Main"
},
"contact_us": {
"name": "Contact Us"
},
"knowledge": {
"name": "Sea of Knowledge"
},
"others": {
"name": "Others"
},
"store": {
"name": "Store",
"buy": "Way to purchase",
@@ -69,7 +63,10 @@
},
"cyq": {
"video-urls": [
"/videos/productvideos/cyq/en.mp4"
{
"url": "/videos/productvideos/cyq/en.mp4",
"display": "Product Introduction"
}
],
"name": "Electric Water Flosser",
"notice": "1. Tap water only.\n2. Turn off the device promptly after the water tank is emptied to avoid dry running.",
@@ -96,7 +93,7 @@
},
{
"key": "Concentration Reaches A Steady State",
"value": "Municipal Tap Water"
"value": "8 seconds"
},
{
"key": "Tank Volume",
@@ -120,7 +117,10 @@
},
"cjb": {
"video-urls": [
"/videos/productvideos/cjb/en.mp4"
{
"url": "/videos/productvideos/cjb/en.mp4",
"display": "Product Introduction"
}
],
"name": "Hypochlorous Acid Bionic Sterilizing Cup",
"notice": "(1) Use immediately after preparation.\n(2) Water temperature exceeding 40°C is not recommended.",
@@ -163,9 +163,18 @@
},
"cjq": {
"video-urls": [
"/videos/productvideos/cjq/en.mp4",
"/videos/productvideos/cjq/en2.mp4",
"/videos/productvideos/cjq/en3.mp4"
{
"url": "/videos/productvideos/cjq/en.mp4",
"display": "Product Introduction"
},
{
"url": "/videos/productvideos/cjq/en2.mp4",
"display": "Tap Water Only"
},
{
"url": "/videos/productvideos/cjq/en3.mp4",
"display": "Add Special Electrolvte"
}
],
"name": "Multi-functional 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.\n2. After each use, clean the product promptly and keep it as dry as possible after cleaning.\n3. Do not modify, disassemble, or repair the product by yourself, as this may cause damage to the main unit.\n4. This product is prohibited from being immersed in water.\n5. 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.",
@@ -211,7 +220,7 @@
"value": "6-10ppm (tap water only); Approx.98ppm (with dedicated electrolyte)"
}
],
"info": "Hypochlorous Acid, Ultimate Travel Companion",
"info": "Hypochlorous Acid, Travel Companion",
"adshort": "Travel Companion"
},
"contact": {
@@ -241,7 +250,7 @@
"precautions": "Precautions"
},
"property": "Company Property",
"learnMore": "Learn More",
"enter": "Enter",
"address": "Company Address",
"contactname": "Contact Info",
"copyright": "Copyright"

View File

@@ -31,11 +31,8 @@
"mainpage": {
"name": "首页"
},
"contact_us": {
"name": "联系我们"
},
"knowledge": {
"name": "知识储备"
"name": "知识海洋"
},
"store": {
"name": "商城",
@@ -44,9 +41,6 @@
"TaoBao": "淘宝",
"RedNote": "小红书"
},
"others": {
"name": "其它"
},
"waterlife_flosser": {
"name": "冲牙器"
},
@@ -69,7 +63,10 @@
},
"cyq": {
"video-urls": [
"/videos/productvideos/cyq/zh.mp4"
{
"url": "/videos/productvideos/cyq/zh.mp4",
"display": "产品简介"
}
],
"name": "次氯酸冲牙器",
"notice": "1、水箱泵完后及时关机,避免空转运行。\n2、公司推崇马斯克的“第一性原理”理念,即产品从最基本的事实、规律和原理出发,不依赖传统经验或类比,通过逻辑推理或者演绎,进而得出结论和解决方法的思维方式。",
@@ -120,7 +117,10 @@
},
"cjb": {
"video-urls": [
"/videos/productvideos/cjb/zh.mp4"
{
"url": "/videos/productvideos/cjb/zh.mp4",
"display": "产品简介"
}
],
"name": "次氯酸仿生除菌杯",
"notice": "1即制即用,次氯酸易分解,不宜久存;\n2水温不建议超过40°C。",
@@ -167,9 +167,18 @@
},
"cjq": {
"video-urls": [
"/videos/productvideos/cjq/zh.mp4",
"/videos/productvideos/cjq/zh2.mp4",
"/videos/productvideos/cjq/zh3.mp4"
{
"url": "/videos/productvideos/cjq/zh.mp4",
"display": "产品简介"
},
{
"url": "/videos/productvideos/cjq/zh2.mp4",
"display": "仅自来水"
},
{
"url": "/videos/productvideos/cjq/zh3.mp4",
"display": "加入电解质"
}
],
"name": "多功能电解水除菌器",
"notice": "1、反应液现配现用,低浓度次氯酸建议在1小时内使用,中浓度室温、避光条件下可保存24小时。\n2、每次使用后,及时清洗,洗后尽量保持干燥。\n3、请勿自行改装、拆解、维修以免导致主机损坏。如因外力导致主机破损、脱落、无法工作时,请停止使用并且联系客服维修。\n4、本产品禁止放入水中。\n5、生成后的溶液禁止饮用,禁止与浓硫酸、洁厕灵等洗涤剂同时混用。",
@@ -241,7 +250,7 @@
"precautions": "注意事项"
},
"property": "公司专利",
"learnMore": "了解更多",
"enter": "进入",
"address": "公司地址",
"contactname": "联系方式",
"copyright": "版权所有"

4
src/types/VideoUrl.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
export type VideoUrl = {
url: string
display: string
}

View File

@@ -1,34 +1,54 @@
<template>
<div class="home-view">
<AboutSection
<AboutSectionDiffMobile
v-for="section in waterLIfeSections"
:key="section.title"
:reverse="false"
:title="''"
:description="''"
:background-image="section.backgroundImage + locale + '.jpg'"
:buttonshow="false"
:targetPath="''"
:background-image="
windowWidth > 768
? section.backgroundImage + locale + '.jpg'
: section.mobileBGImage + locale + '.jpg'
"
:targetPath="section.targetPath"
/>
<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"
class="elder"
:title="$t(aboutSections[0].title)"
:description="$t(aboutSections[0].description)"
:background-image="aboutSections[0].backgroundImage"
:buttonshow="aboutSections[0].buttonShow"
:targetPath="aboutSections[0].targetPath"
/>
<AboutSection
:title="$t(aboutSections[1].title)"
:description="$t(aboutSections[1].description)"
:background-image="aboutSections[1].backgroundImage"
:buttonshow="aboutSections[1].buttonShow"
:targetPath="aboutSections[1].targetPath"
/>
</div>
</template>
<script setup lang="ts">
import AboutSection from '@/components/AboutSection.vue'
import { ref } from 'vue'
import AboutSectionDiffMobile from '@/components/AboutSectionDiffMobile.vue'
import background_1 from '/images/background_1.jpg'
import background_2 from '/images/background_2.jpg'
import { useI18n } from 'vue-i18n'
import { ref, onMounted, onUnmounted } from 'vue'
onMounted(() => {
window.addEventListener('resize', updateWindowSize)
})
onUnmounted(() => {
window.removeEventListener('resize', updateWindowSize)
})
const windowWidth = ref(window.innerWidth)
const updateWindowSize = () => {
windowWidth.value = window.innerWidth
}
const { locale } = useI18n()
@@ -37,16 +57,22 @@ const waterLIfeSections = ref([
title: 'areas.flosser.name',
description: '',
backgroundImage: '/images/banner1_',
mobileBGImage: '/images/mobile/banner1_',
targetPath: '/waterlife_flosser',
},
{
title: 'areas.cup.name',
description: '',
backgroundImage: '/images/banner2_',
mobileBGImage: '/images/mobile/banner2_',
targetPath: '/waterlife_cup',
},
{
title: 'areas.bottle.name',
description: '',
backgroundImage: '/images/banner3_',
mobileBGImage: '/images/mobile/banner3_',
targetPath: '/waterlife_bottle',
},
])
@@ -55,7 +81,6 @@ const aboutSections = ref([
title: 'areas.aged.name',
description: 'areas.aged.intro',
backgroundImage: background_1,
reverse: false,
buttonShow: true,
targetPath: '/todo',
},
@@ -63,7 +88,6 @@ const aboutSections = ref([
title: 'company.name',
description: 'company.introduction',
backgroundImage: background_2,
reverse: true,
buttonShow: false,
targetPath: '',
},
@@ -71,7 +95,6 @@ const aboutSections = ref([
// title: 'property',
// description: 'company.property',
// backgroundImage: background_2,
// reverse: false,
// buttonShow: false,
// targetPath: '',
// },
@@ -82,4 +105,10 @@ const aboutSections = ref([
.home-view {
background-color: rgb(255, 255, 255);
}
@media (max-width: 768px) {
.elder {
display: none;
}
}
</style>

View File

@@ -185,4 +185,54 @@ const downloadRef = (filename: string) => {
.article-line:hover .type {
color: #0070c9;
}
@media (max-width: 768px) {
.article-list {
max-width: 90%;
min-width: 90%;
margin: 16px 16px;
color: #333;
}
/* 标题链接(包裹标题文本) */
.article-line {
display: flex;
align-items: center;
justify-content: space-between;
text-decoration: none;
color: #333;
font-size: 14px;
position: relative;
}
/* 标题文本:一行溢出省略 */
.article {
white-space: nowrap;
/* overflow: hidden; */
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
max-width: 100%;
font-size: 0.9rem;
line-height: 1.2;
color: #222;
}
.article-link {
content: '';
display: none;
flex: 0;
height: 0;
border-bottom: p1x dashed #300808; /* 点状样式,改为 solid / dashed / double 等即可 */
max-width: 0;
}
.type {
display: none;
/* white-space: nowrap; */
/* color: #888; */
/* font-size: 16px; */
max-width: 0;
}
}
</style>

View File

@@ -11,7 +11,7 @@
:info="$t(`${product.name}.info`)"
:detailList="product.detailList"
:imageUrl="product.imageUrl"
:videoUrls="tm(`${product.name}.video-urls`) as string[]"
:videoUrls="tm(`${product.name}.video-urls`) as VideoUrl[]"
/>
</div>
@@ -37,6 +37,7 @@ import { useI18n } from 'vue-i18n'
import seekDetail from '@/components/seekDetail.vue'
import cjqImage from '@/assets/water/cjq.png'
import type { DetailItem, ProductInfoData } from '@/types/product'
import type { VideoUrl } from '@/types/VideoUrl'
// import { useI18n } from 'vue-i18n'
// const { t } = useI18n()
@@ -151,7 +152,15 @@ const product = ref({
/* 响应式设计 */
@media (max-width: 768px) {
.detail-section {
width: 100%;
display: flex;
justify-content: center;
margin-top: 0;
}
.product-list {
display: none;
gap: 15px;
}

View File

@@ -11,7 +11,7 @@
:info="$t(`${product.name}.info`)"
:detailList="product.detailList"
:imageUrl="product.imageUrl"
:videoUrls="tm(`${product.name}.video-urls`) as string[]"
:videoUrls="tm(`${product.name}.video-urls`) as VideoUrl[]"
/>
</div>
@@ -37,6 +37,7 @@ import { useI18n } from 'vue-i18n'
import seekDetail from '@/components/seekDetail.vue'
import cjbImage from '@/assets/water/cjb.png'
import type { DetailItem, ProductInfoData } from '@/types/product'
import type { VideoUrl } from '@/types/VideoUrl'
// import { useI18n } from 'vue-i18n'
// const { t } = useI18n()
@@ -153,6 +154,7 @@ const product = ref({
@media (max-width: 768px) {
.product-list {
gap: 15px;
display: none;
}
.product-card {

View File

@@ -11,7 +11,7 @@
:info="$t(`${product.name}.info`)"
:detailList="product.detailList"
:imageUrl="product.imageUrl"
:videoUrls="tm(`${product.name}.video-urls`) as string[]"
:videoUrls="tm(`${product.name}.video-urls`) as VideoUrl[]"
/>
</div>
@@ -37,6 +37,7 @@ import { useI18n } from 'vue-i18n'
import seekDetail from '@/components/seekDetail.vue'
import cyqImage from '@/assets/water/cyq.png'
import type { DetailItem, ProductInfoData } from '@/types/product'
import type { VideoUrl } from '@/types/VideoUrl'
// import { useI18n } from 'vue-i18n'
// const { t } = useI18n()
@@ -155,6 +156,7 @@ const product = ref({
@media (max-width: 768px) {
.product-list {
gap: 15px;
display: none;
}
.product-card {