feat: 20250704

This commit is contained in:
mac·ufutx 2025-07-04 11:29:23 +08:00
parent 76c246deec
commit 09c3be2a0c
58 changed files with 2341 additions and 843 deletions

View File

@ -1,5 +1,2 @@
# .env.development (开发环境) # .env.development (开发环境)
VITE_API_BASE_URL = 'http://localhost:3000/api' VITE_API_BASE_URL = 'http://health.ufutx.net'
# .env.production (生产环境)
VITE_API_BASE_URL = 'https://your-api-domain.com/api'

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
# .env.production (生产环境「线上」)
VITE_API_BASE_URL = 'https://health.ufutx.com'

View File

@ -24,6 +24,11 @@ module.exports = {
}, },
plugins: ['vue', 'prettier', '@typescript-eslint'], // 显式注册所有插件 plugins: ['vue', 'prettier', '@typescript-eslint'], // 显式注册所有插件
rules: { rules: {
'vue/no-v-html': 'warn', // 从 error 改为 warn或 'off' 完全关闭)
// 限制 v-html 使用,仅允许明确标记为安全的内容
// 'vue/no-v-html': ['warn', { allow: ['trusted', 'safe'] }],
// 允许使用 router-link 和 router-view 组件 // 允许使用 router-link 和 router-view 组件
'vue/no-undef-components': [ 'vue/no-undef-components': [
'error', 'error',
@ -31,16 +36,12 @@ module.exports = {
ignorePatterns: [ ignorePatterns: [
'router-link', 'router-link',
'router-view', 'router-view',
'ElTabs',
'ElTabPane',
'ElButton',
'ElPagination',
// 添加其他使用的 Element Plus 组件 // 添加其他使用的 Element Plus 组件
'RouterLink', 'RouterLink',
'RouterView' 'RouterView',
'el-.*',
'El.*'
] ]
// 允许的组件前缀Element Plus 组件以 El 开头)
// allowComponentPrefixes: ['El']
} }
], ],
// 针对非 Vue 文件禁用 Vue 规则 // 针对非 Vue 文件禁用 Vue 规则
@ -48,6 +49,11 @@ module.exports = {
// 'error', // 'error',
// { ignorePatterns: ['^router$', '^store$'] } // 可选:忽略特定组件名称 // { ignorePatterns: ['^router$', '^store$'] } // 可选:忽略特定组件名称
// ], // ],
// -------------------- 解决 vue/valid-template-root --------------------
// 要求模板必须有且只有一个根元素(必填,否则组件报错)
'vue/valid-template-root': 'error',
// -------------------- 可选:关闭组件命名规则(如需) --------------------
'vue/multi-word-component-names': 'off', 'vue/multi-word-component-names': 'off',
// 'vue/multi-word-component-names': [ // 'vue/multi-word-component-names': [
// 'error', // 'error',

2
auto-imports.d.ts vendored
View File

@ -7,6 +7,8 @@
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope'] const EffectScope: typeof import('vue')['EffectScope']
const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const computed: typeof import('vue')['computed'] const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp'] const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef'] const customRef: typeof import('vue')['customRef']

8
components.d.ts vendored
View File

@ -9,14 +9,22 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
Footer: typeof import('./src/components/Footer.vue')['default'] Footer: typeof import('./src/components/Footer.vue')['default']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default'] HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
Marquee: typeof import('./src/components/Marquee.vue')['default']
Navbar: typeof import('./src/components/Navbar.vue')['default'] Navbar: typeof import('./src/components/Navbar.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
Test: typeof import('./src/components/Test.vue')['default'] Test: typeof import('./src/components/Test.vue')['default']
TextGenerateEffect: typeof import('./src/components/TextGenerateEffect.vue')['default']
} }
} }

View File

@ -3,6 +3,12 @@
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",
"sideEffects": [
"src/style.css",
"src/styles/global.less",
"src/styles/tailwindcss.css",
"element-plus/dist/index.css"
],
"scripts": { "scripts": {
"dev": "vite --host --port 9527", "dev": "vite --host --port 9527",
"build": "vue-tsc -b && vite build", "build": "vue-tsc -b && vite build",
@ -21,12 +27,15 @@
] ]
}, },
"dependencies": { "dependencies": {
"@vueuse/core": "^13.3.0", "@vueuse/core": "^13.4.0",
"@vueuse/motion": "^3.0.3",
"axios": "^1.9.0", "axios": "^1.9.0",
"echarts": "^5.6.0", "echarts": "^5.6.0",
"element-plus": "^2.10.1", "element-plus": "^2.10.1",
"motion-v": "^1.3.1",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"postcss-px-to-viewport": "^1.1.1", "postcss-px-to-viewport": "^1.1.1",
"swiper": "^11.2.10",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-i18n": "^9.8.0", "vue-i18n": "^9.8.0",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
@ -43,6 +52,8 @@
"@vue/eslint-config-typescript": "12.0.0", "@vue/eslint-config-typescript": "12.0.0",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.7.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"eslint": "8.57.1", "eslint": "8.57.1",
"eslint-config-prettier": "9.1.0", "eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
@ -55,8 +66,11 @@
"postcss-px-to-viewport-8-plugin": "^1.2.5", "postcss-px-to-viewport-8-plugin": "^1.2.5",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"sass": "^1.89.2", "sass": "^1.89.2",
"tailwind-merge": "^3.3.1",
"tw-animate-css": "^1.3.4",
"typescript": "5.1.6", "typescript": "5.1.6",
"unplugin-auto-import": "^19.3.0", "unplugin-auto-import": "^19.3.0",
"unplugin-element-plus": "^0.10.0",
"unplugin-vue-components": "^28.7.0", "unplugin-vue-components": "^28.7.0",
"vite": "^5.4.19", "vite": "^5.4.19",
"vite-plugin-html": "^3.2.2", "vite-plugin-html": "^3.2.2",

File diff suppressed because one or more lines are too long

View File

@ -21,10 +21,15 @@
<!-- 底部信息 --> <!-- 底部信息 -->
<div class="footer-bottom"> <div class="footer-bottom">
<p>公司地址深圳市南山区南山街道阳光科创中心B座33楼</p> <p @click="gotoMapFn(branchData.coord[1], branchData.coord[0], branchData.fullName)">
公司地址{{ branchData.address }}
</p>
<p> <p>
版权所有 <span @click="openReport('https://www.ufutx.com')">©友福同享(深圳)智能科技有限公司</span> 版权所有
<span @click="openReport('https://beian.miit.gov.cn/')">粤ICP备12008876号</span> <span @click="gotoMapFn(branchData.coord[1], branchData.coord[0], branchData.fullName)"
>©友福同享(深圳)智能科技有限公司</span
>
<span @click="openReport('https://beian.miit.gov.cn/')"> 粤ICP备12008876号</span>
</p> </p>
</div> </div>
</footer> </footer>
@ -33,20 +38,31 @@
<script setup lang="ts"> <script setup lang="ts">
// //
import { openExternalLink } from '@/utils/navigation.ts' import { openExternalLink } from '@/utils/navigation.ts'
import { gotoMapFn } from '@/utils/tools.ts'
const branchData = ref({
name: '深圳(总部)',
title: '深圳',
fullName: '友福同享(深圳)智能科技有限公司',
address: '深圳市南山区南山街道南山社区南新路阳光科创中心一期A座3301A',
phone: '0755-86701981',
mission: '集团总部,统筹全国健康服务战略',
coord: [113.91535, 22.51409], //
isHeadquarters: true,
labelPosition: 'bottom'
})
const qrItems = [ const qrItems = [
{ {
src: 'https://images.health.ufutx.com/202506/12/cc651222ac2e5f63185dec1f31d176ae.png', src: 'https://image.fulllinkai.com/202406/28/eb66da5936cf4dc21765f0d6672b88db.png',
alt: '友福同享二维码', alt: '友福同享二维码',
title: '友福同享' title: '友福同享'
}, },
{ {
src: 'https://images.health.ufutx.com/202506/12/cc651222ac2e5f63185dec1f31d176ae.png', // src: 'https://images.health.ufutx.com/202507/02/394fd3572a92341b538351db421444da.png', //
alt: '福恋二维码', alt: '福恋二维码',
title: '福恋' title: '福恋'
}, },
{ {
src: 'https://images.health.ufutx.com/202506/12/cc651222ac2e5f63185dec1f31d176ae.png', // src: 'https://image.fulllinkai.com/202406/28/8b042a92a2226ee618282e5a04e140ef.jpeg', //
alt: '友福公众号二维码', alt: '友福公众号二维码',
title: '友福公众号' title: '友福公众号'
} }
@ -60,7 +76,7 @@ const openReport = (URL: string) => {
<style scoped lang="less"> <style scoped lang="less">
.footer { .footer {
width: 100vw; width: 100%;
background-color: #0b101b; background-color: #0b101b;
color: #fff; color: #fff;
//padding: @space-xl @container-padding; //padding: @space-xl @container-padding;

113
src/components/Marquee.vue Normal file
View File

@ -0,0 +1,113 @@
<template>
<div ref="containerRef" :class="['marquee-container relative overflow-hidden', containerClass]">
<div
ref="contentRef"
class="marquee-content flex gap-[50px] whitespace-nowrap"
:style="{
'--marquee-gap': '50px',
'--initial-offset': initialOffset + 'px' //
}"
>
<div ref="originalRef" class="original-content flex gap-[50px]">
<slot />
</div>
<div class="clone-content flex gap-[50px]">
<slot />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, watchEffect } from 'vue'
// props
const props = withDefaults(
defineProps<{
duration: number
reverse?: boolean
repeatCount?: number | string
pauseOnHover?: boolean
containerClass?: string
initialOffset?: number // px
}>(),
{
reverse: false,
repeatCount: 3,
pauseOnHover: true,
initialOffset: 0, //
containerClass: '' //
}
)
const containerRef = ref<HTMLDivElement | null>(null)
const contentRef = ref<HTMLDivElement | null>(null)
const originalRef = ref<HTMLDivElement | null>(null)
const originalWidth = ref(0)
//
const calculateWidth = () => {
if (originalRef.value) {
originalWidth.value = originalRef.value.offsetWidth
}
}
//
const updateAnimation = () => {
if (!contentRef.value || !originalRef.value) return
//
const direction = props.reverse ? 'reverse' : ''
contentRef.value.style.animation = `marquee ${props.duration}s linear infinite ${direction}`
//
const style = document.createElement('style')
style.id = 'marquee-keyframes'
style.textContent = `
@keyframes marquee {
0% { transform: translateX(var(--initial-offset)); }
100% { transform: translateX(calc(var(--initial-offset) - ${originalWidth.value}px)); }
}
`
//
const existingStyle = document.head.querySelector('#marquee-keyframes')
if (existingStyle) document.head.removeChild(existingStyle)
document.head.appendChild(style)
}
//
onMounted(() => {
calculateWidth()
updateAnimation()
})
watchEffect(() => {
calculateWidth()
updateAnimation()
})
//
watchEffect(() => {
if (containerRef.value && props.pauseOnHover) {
containerRef.value.addEventListener('mouseenter', () => {
if (contentRef.value) contentRef.value.style.animationPlayState = 'paused'
})
containerRef.value.addEventListener('mouseleave', () => {
if (contentRef.value) contentRef.value.style.animationPlayState = 'running'
})
}
})
</script>
<style scoped lang="less">
.marquee-content {
display: flex;
flex-wrap: nowrap;
transition: transform 0.3s ease;
}
.original-content,
.clone-content {
display: flex;
flex-wrap: nowrap;
}
</style>

View File

@ -21,13 +21,17 @@
</nav> </nav>
<!-- 语言切换 --> <!-- 语言切换 -->
<div class="language-switch"><span>English</span> | <span>中文</span> | <span>繁體</span></div> <!-- <div class="language-switch"><span>中文</span></div>-->
<div class="language-switch" @click="changeLanguage">
<span>English</span> | <span>中文</span> | <span>繁體</span>
</div>
</div> </div>
</header> </header>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElNotification } from 'element-plus'
// import { computed } from 'vue' // import { computed } from 'vue'
const router = useRouter() const router = useRouter()
@ -42,7 +46,13 @@ const navItems = [
{ path: '/ecosystem', label: '生态合作' }, { path: '/ecosystem', label: '生态合作' },
{ path: '/about', label: '关于我们' } { path: '/about', label: '关于我们' }
] ]
const changeLanguage = () => {
ElNotification({
title: '系统提示',
message: '翻译文件正在处理中,请稍后重试~',
type: 'primary'
})
}
// //
const handleNavigate = (path: string) => { const handleNavigate = (path: string) => {
console.log(path) console.log(path)
@ -73,6 +83,7 @@ const isActive = (path: string) => {
z-index: 999; z-index: 999;
overflow: hidden; overflow: hidden;
} }
.navbar-logo { .navbar-logo {
width: 160px; width: 160px;
height: 29px; height: 29px;
@ -80,6 +91,7 @@ const isActive = (path: string) => {
//top: 0; //top: 0;
//left: 0; //left: 0;
} }
// //
.navbar-container { .navbar-container {
width: 100%; width: 100%;
@ -121,8 +133,10 @@ const isActive = (path: string) => {
// / hover // / hover
&.active, &.active,
&:hover { &:hover {
//&.active {
font-weight: bold; font-weight: bold;
color: @primary-color; color: @primary-color;
&::after { &::after {
width: 100%; // 线 width: 100%; // 线
} }
@ -132,7 +146,9 @@ const isActive = (path: string) => {
// //
.language-switch { .language-switch {
color: #666; width: 150px;
color: @text-color;
font-size: @font-size-md;
transition: color 0.3s ease; transition: color 0.3s ease;
&:hover { &:hover {

View File

@ -0,0 +1,50 @@
<template>
<div :class="cn('leading-snug tracking-wide', props.class)">
<div ref="scope">
<span v-for="(word, idx) in wordsArray" :key="word + idx" class="inline-block" :style="spanStyle">
{{ word }}&nbsp;
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, type HTMLAttributes, onMounted, ref } from 'vue'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<{
words: string
filter?: boolean
duration?: number
delay?: number
class: HTMLAttributes['class']
}>(),
{ duration: 0.7, delay: 0, filter: true }
)
const scope = ref(null)
const wordsArray = computed(() => props.words.split(' '))
const spanStyle = computed(() => ({
opacity: 0,
filter: props.filter ? 'blur(10px)' : 'none',
transition: `opacity ${props.duration}s, filter ${props.duration}s`
}))
onMounted(() => {
if (scope.value) {
const spans = (scope.value as HTMLElement).querySelectorAll('span')
setTimeout(() => {
spans.forEach((span: HTMLElement, index: number) => {
setTimeout(() => {
span.style.opacity = '1'
span.style.filter = props.filter ? 'blur(0px)' : 'none'
}, index * 200)
})
}, props.delay)
}
})
</script>

195
src/data/realCases.ts Normal file
View File

@ -0,0 +1,195 @@
export interface RealCase {
username: string
case: string
avatar: string
result: string
}
export const realCases: RealCase[] = [
{
username: '吴子昂',
case: '老家信号差,用离线功能下载健康课程,爷爷学八段锦时视频可放慢带字幕。商城买的太极服质量很好,数据等有网自动同步。',
avatar: 'https://picsum.photos/id/525/200/200',
result: '爷爷坚持3个月后血压从150/90mmHg降至135/85mmHg学会独立操作App。'
},
{
username: '郑雅琪',
case: '孕期用「胎动计数」功能监测32周时发现胎动异常App提醒就医。商城买的孕妇枕缓解腰痛产后用「泌乳曲线」记录。',
avatar: 'https://picsum.photos/id/526/200/200',
result: '顺产顺利奶量从300ml/天增至600ml/天,宝宝体重增长达标。'
},
{
username: '王大宇',
case: '冠心病术后用「心率预警」功能监测到静息心率连续3天超85次/分钟。商城买的心脏监测手环很准App建议就医调整用药。',
avatar: 'https://picsum.photos/id/527/200/200',
result: '及时发现药物副作用调整后心率稳定在70-75次/分钟,未再发生胸闷。'
},
{
username: '刘思涵',
case: '用「儿童健康」板块记录孩子发育AI发现大宝语言发育滞后推荐亲子阅读和语言游戏。商城买的绘本很适合孩子。',
avatar: 'https://picsum.photos/id/528/200/200',
result: '3个月后大宝词汇量从50个增至200个疫苗接种率100%,未再发生过敏反应。'
},
{
username: '赵晓鹏',
case: '外卖员开启「骑手模式」App根据接单量提醒补水休息。商城买的防水外卖箱很实用摔倒时路人通过App联系紧急联系人。',
avatar: 'https://picsum.photos/id/529/200/200',
result: '夏季未中暑摔伤后1小时内就医恢复周期缩短3天同行adoption率达60%。'
},
{
username: '孙梦琪',
case: '用睡眠分析功能监测发现夜间觉醒次数多与打鼾相关。商城买的止鼾器很有效App建议换枕头高度并推送白噪音。',
avatar: 'https://picsum.photos/id/530/200/200',
result: '入睡时间从60分钟缩至20分钟深睡眠时间延长1.2小时睡眠评分从65升至82。'
},
{
username: '钱建国',
case: '奶奶用「语音问诊」描述腿疼症状医生判断为关节炎并推荐药膏。商城买的关节贴缓解疼痛App查询医保报销比例和社保余额。',
avatar: 'https://picsum.photos/id/531/200/200',
result: '用药1周后疼痛缓解医保报销计算准确避免跑社保局3次。'
},
{
username: '高雨桐',
case: '瑜伽教练用体脂率数据和体态评估功能,为会员定制训练计划。商城买的瑜伽砖质量很好,通过班级群同步课程和饮食方案。',
avatar: 'https://picsum.photos/id/532/200/200',
result: '会员平均体脂率下降7%肌肉量增加2.3kg课程续费率提升40%。'
},
{
username: '罗子轩',
case: '用「家庭药箱」记录药品保质期,孩子误服成人药时,通过急救指南处理后送医。商城买的儿童退烧药很安全。',
avatar: 'https://picsum.photos/id/533/200/200',
result: '药品过期提醒准确率100%,误服事件处理及时,未造成不良后果。'
},
{
username: '谢雅楠',
case: '过敏性鼻炎患者用「季节健康」功能,接收花粉预警并学习社区脱敏方法。商城买的防过敏口罩很透气,坚持生理盐水洗鼻。',
avatar: 'https://picsum.photos/id/534/200/200',
result: '春季过敏症状减轻80%抗组胺药使用量减少60%。'
},
{
username: '杨明远',
case: '忙碌白领用「轻体检」功能5分钟完成心率、代谢检测得分偏低后App推荐并预约体检机构。商城买的体检套餐很划算。',
avatar: 'https://picsum.photos/id/535/200/200',
result: '及时发现血脂异常干预后甘油三酯从2.8mmol/L降至1.7mmol/L节省2小时找机构时间。'
},
{
username: '曹晓琳',
case: '为父母设置用药闹钟母亲忘买降压药时App跳转附近药店配送。商城买的血压计很精准同步推送关节炎护理知识。',
avatar: 'https://picsum.photos/id/536/200/200',
result: '用药依从性达100%血压稳定在130/80mmHg关节炎发作频率减少50%。'
},
{
username: '秦宇航',
case: '焦虑症患者定期做心理测评AI推荐正念冥想。商城买的减压魔方很解压社区匿名分享经验并监测睡眠改善情况。',
avatar: 'https://picsum.photos/id/537/200/200',
result: '焦虑量表评分从24分降至12分深睡眠时间延长1小时停药后未复发。'
},
{
username: '朱晓婷',
case: '失眠患者用睡眠分析发现夜间觉醒多通过App推荐的白噪音和泡脚方案调整。商城买的泡脚桶很舒适。',
avatar: 'https://picsum.photos/id/538/200/200',
result: '睡眠效率从65%提升至88%入睡潜伏期从60分钟缩至20分钟。'
},
{
username: '沈子昂',
case: '徒步时遇暴雨迷路,「紧急求助」功能发送定位,用「伤口处理」视频处理队友擦伤。商城买的户外急救包很齐全。',
avatar: 'https://picsum.photos/id/539/200/200',
result: '2小时内获救伤口处理规范未感染同行者均学会基础急救。'
},
{
username: '杨紫涵',
case: '宝妈用「疫苗提醒」功能记录宝宝接种时间10个月时AI发现漏种手足口疫苗。商城买的婴儿车很轻便推送社区医院预约链接。',
avatar: 'https://picsum.photos/id/560/200/200',
result: '宝宝疫苗接种率100%未患传染病辅食接受度从30%提升至80%。'
},
{
username: '李俊浩',
case: '高血压患者绑定电子血压计AI分析数据发现晨起血压偏高。商城买的降压茶很有效推荐「睡前1小时禁水」方案。',
avatar: 'https://picsum.photos/id/561/200/200',
result: '6个月后晨起血压从150/95mmHg降至135/85mmHg降压药剂量减少1/3。'
},
{
username: '张梦琪',
case: '瑜伽馆用「班级群」功能同步课程表AI体态评估帮学员纠正驼背。商城买的瑜伽垫防滑效果好每周推送针对性训练视频。',
avatar: 'https://picsum.photos/id/562/200/200',
result: '学员平均体态评分从62分升至85分课程续费率从50%提高至90%。'
},
{
username: '王浩宇',
case: '户外博主在无人区徒步,「紧急求助」发送卫星定位。商城买的卫星电话很可靠,用离线地图找到水源,「体能监测」提醒及时补充能量。',
avatar: 'https://picsum.photos/id/563/200/200',
result: '48小时内获救未出现脱水症状装备负重减少3kg徒步时间延长2天。'
},
{
username: '陈雨欣',
case: '抑郁症患者每周做心理测评AI推荐正念冥想和「阳光暴露」计划。商城买的抗抑郁书籍很有帮助社区匿名区分享康复日记。',
avatar: 'https://picsum.photos/id/564/200/200',
result: '6个月后抑郁量表评分从35分降至12分户外活动时间从每周2小时增至10小时。'
},
{
username: '赵天宇',
case: 'IT公司用「团队健康看板」监测员工久坐时长每小时推送拉伸提醒。商城买的颈椎按摩器员工很喜欢季度组织「健康挑战赛」。',
avatar: 'https://picsum.photos/id/565/200/200',
result: '员工腰肌劳损发病率从25%降至8%平均每日步数从4000步增至8000步。'
},
{
username: '林晓燕',
case: '更年期女性用睡眠监测发现潮热影响睡眠。商城买的更年期调理保健品很有效App推荐「大豆异黄酮食谱」和「睡前冷敷」法。',
avatar: 'https://picsum.photos/id/566/200/200',
result: '潮热次数从每晚5次减至1次睡眠时间从4小时延长至6.5小时。'
},
{
username: '郑博文',
case: '脂肪肝患者用饮食库记录每日摄入AI限制高脂食物。商城买的护肝片性价比高推荐「快走30分钟」计划社区分享「肝部按摩」视频。',
avatar: 'https://picsum.photos/id/567/200/200',
result: '3个月后转氨酶从80U/L降至45U/L肝脂肪浸润程度从中度转为轻度。'
},
{
username: '吴佳琪',
case: '护士用「轮班模式」调整睡眠提醒,夜班后推荐「光照疗法」补觉。商城买的遮光眼罩很实用,社区同行分享「护眼贴选购指南」。',
avatar: 'https://picsum.photos/id/568/200/200',
result: '睡眠效率从55%提升至82%干眼症症状评分从18分降至6分。'
},
{
username: '孙浩宇',
case: '为父母绑定智能手表心率100次/分钟时同步提醒子女。商城买的智能手环功能齐全用药闹钟每日8点弹窗+电话提醒。',
avatar: 'https://picsum.photos/id/569/200/200',
result: '父亲漏服药次数从每月4次降至0次2次心率异常及时就医避免严重后果。'
},
{
username: '刘思远',
case: '考研党用「专注模式」屏蔽娱乐软件每日6点推送起床提醒。商城买的记忆枕很舒适护眼功能每40分钟提醒远眺。',
avatar: 'https://picsum.photos/id/570/200/200',
result: '日均学习时长从6小时增至8小时视力从4.8恢复至5.0,未出现颈椎疼痛。'
},
{
username: '唐雨桐',
case: '乳腺癌术后用AI推荐的「肩部活动度训练」每日记录康复数据。商城买的术后康复内衣很舒适社区找到同类患者交流经验。',
avatar: 'https://picsum.photos/id/571/200/200',
result: '3个月后手臂上举角度从60°增至160°淋巴水肿发生率为0心理状态评分提升50%。'
},
{
username: '冯梓轩',
case: '餐厅老板用App管理员工健康证到期前30天提醒。商城买的食品检测试剂很精准批量导入员工信息后生成「健康档案看板」。',
avatar: 'https://picsum.photos/id/572/200/200',
result: '健康证过期率从30%降至0通过食药监检查员工工伤率减少40%。'
},
{
username: '黄梦琪',
case: '自闭症儿童妈妈用「感统训练游戏」视频教学。商城买的感统训练器材很专业App记录孩子眼神接触时长社区家长分享「社交故事」制作方法。',
avatar: 'https://picsum.photos/id/573/200/200',
result: '6个月后孩子眼神接触时长从5秒/次增至20秒/次,能听懂简单指令。'
},
{
username: '周博文',
case: '货车司机开启「疲劳驾驶监测」连续驾驶4小时强制提醒休息。商城买的腰部按摩靠垫很舒适离线地图提前预警施工路段。',
avatar: 'https://picsum.photos/id/574/200/200',
result: '未发生交通事故违章次数减少60%腰背痛发生率从70%降至20%。'
},
{
username: '徐雅婷',
case: '茶艺师用「护嗓计划」每日3次盐水漱口。商城买的专业护嗓茶效果好嗓音测评功能监测声带状态社区分享「润喉茶」配方。',
avatar: 'https://picsum.photos/id/575/200/200',
result: '声带小结缩小80%连续授课4小时嗓音无嘶哑客户满意度提升35%。'
}
]

182
src/data/userFeedbacks.ts Normal file
View File

@ -0,0 +1,182 @@
export interface UserFeedback {
username: string
feedback: string
avatar: string
}
export const userFeedbacks: UserFeedback[] = [
{
username: '周雨桐',
feedback:
'产后用了三个月AI催乳食谱和商城里的吸奶器帮我解决了哺乳难题社区背奶妈妈的经验超实用但视频加载偶尔卡顿希望优化网络适配。',
avatar: 'https://picsum.photos/id/510/200/200'
},
{
username: '马子昂',
feedback:
'作为糖友必须夸!血糖仪同步太方便,商城里的无糖食品选择多,社区糖友的杂粮饭配方救了我,但希望加「食物相克」提醒。',
avatar: 'https://picsum.photos/id/511/200/200'
},
{
username: '徐晓冉',
feedback:
'帮独居爷爷装的,语音助手太贴心了!说「查血压」就出结果,商城的电子血压计性价比高。守护模式让我上班安心,但希望增加「一键呼叫社区医生」按钮。',
avatar: 'https://picsum.photos/id/512/200/200'
},
{
username: '郭子轩',
feedback:
'程序员久坐党狂喜每小时拉伸提醒救了我的腰商城里的人体工学椅超棒但理疗馆推荐偶尔不准。云端同步换手机不丢数据这点比其他App强太多。',
avatar: 'https://picsum.photos/id/513/200/200'
},
{
username: '林巧巧',
feedback:
'孕期全靠它缓解焦虑!胎动计数比医院的胎心监护还方便,商城的防辐射服质量好。但社区广告有点多,希望减少干扰。',
avatar: 'https://picsum.photos/id/514/200/200'
},
{
username: '董子健',
feedback:
'健身党实测同步运动数据后AI调的训练计划比私教科学商城的蛋白粉价格实惠但步频提醒有点灵敏偶尔正常跑步也报警。',
avatar: 'https://picsum.photos/id/515/200/200'
},
{
username: '田晓梅',
feedback:
'管理全家健康档案太省心!商城的儿童益生菌效果好,但家庭共享权限不够细,老公想改娃的疫苗记录改不了,必须我操作。',
avatar: 'https://picsum.photos/id/516/200/200'
},
{
username: '冯建国',
feedback:
'退休后学健康知识就靠它!真人医生讲的颈椎操比朋友圈靠谱,商城的按摩器爸妈很喜欢。但视频不能倍速,对我们老人有点慢。',
avatar: 'https://picsum.photos/id/517/200/200'
},
{
username: '肖雅婷',
feedback:
'AI问诊比药店药师专业商城的护眼仪缓解了我的干眼症但夜间模式不够暗看久了还是累。希望加「常用药提醒」。',
avatar: 'https://picsum.photos/id/518/200/200'
},
{
username: '梁宇航',
feedback:
'程序员救星!商城的颈椎按摩仪超好用,但推荐的人体工学椅有点贵,希望加平价替代款。数据云端存换电脑不丢,全部门都被我安利了。',
avatar: 'https://picsum.photos/id/519/200/200'
},
{
username: '蒋欣怡',
feedback:
'过敏体质终于有救了!商城的防过敏面膜超赞,但口罩推荐链接太少,每次都要自己搜。社区同过敏原的人分享的食谱超实用。',
avatar: 'https://picsum.photos/id/520/200/200'
},
{
username: '宋明辉',
feedback:
'冠心病患者安心了!商城的心脏监测手环很准,但急救指南字体有点小,紧急时看不清。每周心脏报告比病历还全,子女看了也放心。',
avatar: 'https://picsum.photos/id/521/200/200'
},
{
username: '韩雨菲',
feedback:
'自由职业者平衡工作神器!商城的时间管理手册帮我提高效率,但时间管理提醒有点吵,希望能调音量。健康周报让我看到进步。',
avatar: 'https://picsum.photos/id/522/200/200'
},
{
username: '顾子轩',
feedback:
'异地看病一条龙服务太方便商城的旅行急救包很实用但医保报销计算不准上次多算了200。医生调电子档案不用重复说病史。',
avatar: 'https://picsum.photos/id/523/200/200'
},
{
username: '潘晓婷',
feedback:
'教师护嗓神器!商城的润喉糖效果好,但发声练习视频有点短,希望加完整版。社区老师分享的技巧让我讲课省力多了。',
avatar: 'https://picsum.photos/id/524/200/200'
},
{
username: '杨紫涵',
feedback:
'带娃神器商城的婴儿背带解放双手但疫苗提醒偶尔延迟。社区宝妈的辅食食谱超实用10个月宝宝终于肯吃蔬菜泥了。',
avatar: 'https://picsum.photos/id/540/200/200'
},
{
username: '李俊浩',
feedback:
'高血压患者用了半年,商城的电子血压计超准!但偶尔会漏传数据,得手动刷新。社区病友说的「芹菜汁降压法」试了有用。',
avatar: 'https://picsum.photos/id/541/200/200'
},
{
username: '张梦琪',
feedback: '瑜伽馆老板必装!商城的瑜伽垫质量超好,班级群同步课程表比微信群清楚,但学员打卡数据偶尔延迟。',
avatar: 'https://picsum.photos/id/542/200/200'
},
{
username: '王浩宇',
feedback:
'户外博主实测商城的户外急救包很全紧急求助功能在无人区也能发定位但离线地图精度一般上次走错路绕了2公里。',
avatar: 'https://picsum.photos/id/543/200/200'
},
{
username: '陈雨欣',
feedback: '抑郁症康复中,商城的减压香薰很助眠!心理测评量表很专业,但冥想音频有点少,希望加自然音效。',
avatar: 'https://picsum.photos/id/544/200/200'
},
{
username: '赵天宇',
feedback:
'IT公司采购给员工用商城的颈椎按摩器员工超喜欢久坐提醒让腰肌劳损发病率降了40%,但团队健康数据看板权限太严。',
avatar: 'https://picsum.photos/id/545/200/200'
},
{
username: '林晓燕',
feedback:
'更年期用它调睡眠,商城的褪黑素软糖效果好!但夜间模式切换有点卡顿,偶尔闪屏。社区阿姨分享的「大豆异黄酮食谱」试了三周。',
avatar: 'https://picsum.photos/id/546/200/200'
},
{
username: '郑博文',
feedback:
'脂肪肝患者靠它逆转商城的护肝片性价比高AI推的低脂食谱让我三个月转氨酶从80降到45但饮食库中餐馆菜太少。',
avatar: 'https://picsum.photos/id/547/200/200'
},
{
username: '吴佳琪',
feedback: '护士夜班必备商城的蒸汽眼罩缓解眼疲劳睡眠修复功能帮我补觉效率提高50%,但轮班模式切换后数据要手动改。',
avatar: 'https://picsum.photos/id/548/200/200'
},
{
username: '孙浩宇',
feedback: '给爸妈买的智能手表绑定后,商城的血压计质量很好!心率异常立刻提醒我,但手表续航有点短,得天天充。',
avatar: 'https://picsum.photos/id/549/200/200'
},
{
username: '刘思远',
feedback:
'考研党用它管理作息商城的护眼台灯很实用每天6点起床提醒比闹钟温柔但专注模式会被电话打断希望加「免打扰时段」。',
avatar: 'https://picsum.photos/id/550/200/200'
},
{
username: '唐雨桐',
feedback: '乳腺癌术后康复靠它商城的义乳很舒适AI推荐的康复操比医院教的详细练三个月手臂活动度恢复90%。',
avatar: 'https://picsum.photos/id/551/200/200'
},
{
username: '冯梓轩',
feedback:
'餐厅老板用它管理员工健康,商城的食品温度计很精准!食品从业者体检提醒超及时,但批量导入员工信息有点麻烦。',
avatar: 'https://picsum.photos/id/552/200/200'
},
{
username: '黄梦琪',
feedback:
'自闭症儿童妈妈福音商城的感统训练器材很专业AI推的「感统训练游戏」让孩子眼神接触多了但视频没有字幕。',
avatar: 'https://picsum.photos/id/553/200/200'
},
{
username: '周博文',
feedback: '货车司机必备!商城的腰部靠垫很舒适,疲劳驾驶提醒比副驾还灵,上次差点睡着被及时叫醒。但离线地图更新慢。',
avatar: 'https://picsum.photos/id/554/200/200'
}
]

View File

@ -7,8 +7,12 @@
<!-- <main class="layout-main">--> <!-- <main class="layout-main">-->
<!-- <slot></slot>--> <!-- <slot></slot>-->
<!-- </main>--> <!-- </main>-->
<router-view></router-view> <!-- <router-view></router-view>-->
<!-- 核心路由出口根据meta.keepAlive决定是否缓存 -->
<keep-alive>
<router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" />
<!-- 页脚 --> <!-- 页脚 -->
<Footer /> <Footer />
</div> </div>

View File

@ -1,42 +1,42 @@
// src/main.ts // src/main.ts
import { ViteSSG } from 'vite-ssg' import { ViteSSG } from 'vite-ssg'
import { createWebHistory } from 'vue-router' import { createWebHashHistory } from 'vue-router'
import App from './App.vue' import App from './App.vue'
import routes from './router/routes' import routes from './router/routes'
import i18n from './locales/i18n' // 导入i18n配置 import i18n from './locales/i18n' // 导入i18n配置
import './style.css' import './style.css'
import '@/styles/global.less' // 引入全局样式 import '@/styles/global.less' // 引入全局样式
import '@/styles/tailwindcss.less' // tailwindcss.less import '@/styles/tailwindcss.css' // 引入tailwindcss样式
// 修正:明确 meta.title 的类型为 string // 修正:明确 meta.title 的类型为 string
// 1. 引入 Element Plus 核心库和全局样式
// 引入 element-plus 核心库 import ElementPlus from 'element-plus'
// import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' // 手动引入默认样式(覆盖自动导入的样式冲突)
// 引入 element-plus 全局样式(可根据需求选择是否自定义主题,这里先引入默认样式) // 2. 引入国际化语言包(确保路径正确)
import 'element-plus/dist/index.css' import zhCn from 'element-plus/es/locale/lang/zh-cn'
// 如果你需要使用 Element Plus 提供的国际化i18n功能还需引入对应的语言包比如中文
// 修正:使用正确的语言包路径(从 es/locale/lang 导入)
// import zhCn from 'element-plus/es/locale/lang/zh-cn'
// 使用 ElementPlus 插件,并配置国际化等选项(这里以配置中文为例) // 使用 ElementPlus 插件,并配置国际化等选项(这里以配置中文为例)
import { MotionPlugin } from '@vueuse/motion'
declare module 'vue-router' { declare module 'vue-router' {
interface RouteMeta { interface RouteMeta {
title?: string // 明确指定为 string 类型 title?: string // 明确指定为 string 类型
requiresAuth?: boolean requiresAuth?: boolean
} }
} }
export const createApp = ViteSSG( export const createApp = ViteSSG(
App, App,
{ {
history: createWebHistory(), history: createWebHashHistory(),
routes, routes,
base: import.meta.env.BASE_URL || '/' base: import.meta.env.BASE_URL || '/'
}, },
ctx => { ctx => {
// 安装 i18n 插件 // 安装 i18n 插件
ctx.app.use(i18n) ctx.app.use(i18n)
// ctx.app.use(ElementPlus, { ctx.app.use(MotionPlugin)
// locale: zhCn ctx.app.use(ElementPlus, {
// }) locale: zhCn
})
// 路由守卫:设置页面标题 // 路由守卫:设置页面标题
ctx.router.beforeEach((to, _from, next) => { ctx.router.beforeEach((to, _from, next) => {

View File

@ -1,7 +1,11 @@
// src/router/index.ts // src/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import routes from './routes' import routes from './routes'
// import setPageTitle from '@/utils/set-page-title.ts'cd
// const isProduction = import.meta.env.MODE === 'production'
// const history = isProduction
// ? createWebHashHistory() // 线上:带#
// : createWebHistory() // 本地:无#
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes, routes,
@ -19,9 +23,18 @@ const router = createRouter({
}) })
router.beforeEach((_to, _from, next) => { router.beforeEach((_to, _from, next) => {
// 添加下划线 // setPageTitle(_to.meta.title)
// document.title = to.meta.title || '极简官网' // 确保meta.title类型正确
if (_to.name === 'ArticleDetail' && _from.name === 'News') {
// 清除News组件的缓存
const cachedComponents = getCurrentInstance()?.appContext.config.globalProperties.$keepAlive?.cache
if (cachedComponents) {
delete cachedComponents['News']
}
}
next() next()
}) })
// / 添加日志,验证路由模式
// console.log('路由模式:', router.history)
export default router export default router

View File

@ -15,14 +15,77 @@ const routes: RouteRecordRaw[] = [
path: '/', path: '/',
component: () => import('@/layout/Layout.vue'), // 动态导入 component: () => import('@/layout/Layout.vue'), // 动态导入
children: [ children: [
{ path: '', name: 'Home', component: () => import('@/views/Home/Home.vue') }, {
{ path: 'news', name: 'News', component: () => import('@/views/News/News.vue') }, path: '',
{ path: 'news/:id', name: 'ArticleDetail', component: () => import('@/views/News/ArticleDetail.vue') }, name: 'Home',
{ path: 'network', name: 'Network', component: () => import('@/views/Network/Network.vue') }, meta: {
{ path: 'dating', name: 'Dating', component: () => import('@/views/Dating/Dating.vue') }, keepAlive: false
{ path: 'app', name: 'App', component: () => import('@/views/App/App.vue') }, },
{ path: 'ecosystem', name: 'Ecosystem', component: () => import('@/views/Ecosystem/Ecosystem.vue') }, component: () => import('@/views/Home/Home.vue')
{ path: 'about', name: 'About', component: () => import('@/views/About/About.vue') } },
{
path: 'news',
name: 'News',
meta: {
title: '友福动态',
keepAlive: false
},
component: () => import('@/views/News/News.vue')
},
{
path: 'articleDetail/:id',
name: 'articleDetail',
meta: {
title: '文章详情',
keepAlive: false // 详情页不缓存
},
component: () => import('@/views/News/ArticleDetail.vue')
},
{
path: 'network',
name: 'Network',
meta: {
title: 'AI健康',
keepAlive: false
},
component: () => import('@/views/Network/Network.vue')
},
{
path: 'dating',
name: 'Dating',
meta: {
title: 'AI婚恋',
keepAlive: false
},
component: () => import('@/views/Dating/Dating.vue')
},
{
path: 'app',
name: 'App',
meta: {
title: '友福同享APP',
keepAlive: false
},
component: () => import('@/views/App/App.vue')
},
{
path: 'ecosystem',
name: 'Ecosystem',
meta: {
title: '生态合作',
keepAlive: false
},
component: () => import('@/views/Ecosystem/Ecosystem.vue')
},
{
path: 'about',
name: 'About',
meta: {
title: '关于我们',
keepAlive: false
},
component: () => import('@/views/About/About.vue')
}
] ]
}, },
// { // {

View File

@ -318,3 +318,36 @@ html, body {
.btn-glow:hover::before { .btn-glow:hover::before {
left: 100%; left: 100%;
} }
.btn-success-glow {
padding: 12px 24px;
border: none;
color: white;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
position: relative;
overflow: hidden;
transition:
transform 0.4s,
box-shadow 0.4s;
}
.btn-success-glow:hover {
transform: scale(1.05);
box-shadow: 0 0 20px #aaeec4;
}
.btn-success-glow::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
transition: 0.5s;
}
.btn-success-glow:hover::before {
left: 100%;
}

View File

@ -1,13 +1,13 @@
// src/utils/navigation.ts // src/utils/navigation.ts
import { useRouter } from 'vue-router' import router from '../router/index.ts' // 直接导入已创建的路由实例
import type { RouteLocationRaw } from 'vue-router'
/** /**
* *
* @param to - * @param to -
* @param replace - * @param replace -
*/ */
export const navigateTo = (to: string | { name: string }, replace = false) => { export const navigateTo = (to: RouteLocationRaw, replace = false) => {
const router = useRouter()
replace ? router.replace(to) : router.push(to) replace ? router.replace(to) : router.push(to)
} }
@ -23,14 +23,29 @@ export const openExternalLink = (url: string, target: '_blank' | '_self' = '_bla
window.location.href = url window.location.href = url
} }
} }
/**
*
* @param to -
*/
export const openNewWindow = (to: RouteLocationRaw) => {
console.log('路由实例:', router) // 应输出一个包含 routes、options 等属性的对象,而非 undefined
try {
// 使用导入的 router 实例调用 resolve
const routeLocation = router.resolve(to)
// 拼接完整URL当前域名 + 路由路径)
const fullUrl = `${window.location.origin}${routeLocation.href}`
// 新窗口打开
window.open(fullUrl, '_blank', 'noopener,noreferrer')
} catch (error) {
console.error('新窗口打开路由失败:', error)
}
}
/** /**
* *
*/ */
// export const goBack = () => { export const goBack = () => {
// useRouter().back() router.back()
// } }
//
// <template> // <template>
// <div> // <div>
// <button @click="goToProduct(123)">查看产品</button> // <button @click="goToProduct(123)">查看产品</button>

View File

@ -41,13 +41,13 @@ service.interceptors.request.use(
// 响应拦截器 // 响应拦截器
service.interceptors.response.use( service.interceptors.response.use(
(response: AxiosResponse<ApiResponse>) => { (response: AxiosResponse<any>) => {
const res = response.data const res = response.data
if (res.code !== 200) { if (res.code !== 0) {
showError(res.message || '请求失败') showError(res.message || '请求失败')
return Promise.reject(new Error(res.message || '请求失败')) return Promise.reject(new Error(res.message || '请求失败'))
} }
return res.data return res
}, },
(error: AxiosError<ApiResponse>) => { (error: AxiosError<ApiResponse>) => {
console.error('响应错误:', error) console.error('响应错误:', error)

View File

@ -0,0 +1,5 @@
const pageDefaultTitle = '友福同享智能科技官网'
export default function setPageTitle(routerTitle?: string): void {
console.log(routerTitle)
document.title = routerTitle ? `${routerTitle} | ${pageDefaultTitle}` : `${pageDefaultTitle}`
}

54
src/utils/tools.ts Normal file
View File

@ -0,0 +1,54 @@
import { openExternalLink } from '@/utils/navigation.ts'
/**
* FloveApp
* - APK文件
* - iOSApp Store
*/
export const downloadFloveApp = () => {
// 判断设备类型iOS/Android
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent)
// 根据设备选择链接
const url = isIOS
? 'https://apps.apple.com/cn/app/%E7%A6%8F%E6%81%8B/id1483551530' // 苹果App Store
: 'https://ufutx-image.oss-cn-shenzhen.aliyuncs.com/apk/flove_fulllink.apk' // 安卓APK
const link = document.createElement('a')
link.href = url
// 苹果跳转App Store无需指定download避免下载无效文件安卓指定文件名
if (!isIOS) {
link.download = 'flove_fulllink.apk'
}
// 处理跨域和安全配置
if (link.href.indexOf(location.origin) !== 0) {
link.target = '_blank' // 跨域链接打开新窗口,避免浏览器拦截
link.rel = 'noopener noreferrer' // 增强安全性,防止新窗口篡改原页面
}
// 触发下载/跳转
document.body.appendChild(link)
link.click()
// 延迟移除,避免部分浏览器因立即移除导致跳转失败
setTimeout(() => {
document.body.removeChild(link)
}, 100)
}
/**
*
* @param latitude
* @param longitude
* @param detail
*/
export const gotoMapFn = (latitude: any, longitude: any, detail: any) => {
console.log(latitude, longitude, detail)
openExternalLink(
// `http://maps.googleapis.com/maps/api/staticmap?center=${latitude},${longitude}&zoom=14&size=400x300&sensor=false`
// `http://maps.google.com/map/api/staticmap?center=${latitude},${longitude}&size=640*640&sensor=true`
`https://uri.amap.com/marker?position=${longitude},${latitude}&name=${detail}`,
'_blank'
// `http://api.map.baidu.com/marker?location=${latitude},${longitude}&name==${detail}&output=html`
)
}

View File

@ -34,7 +34,7 @@ import BranchDistribution from '@/views/About/sections/BranchDistribution.vue'
<style scoped lang="less"> <style scoped lang="less">
.about-page { .about-page {
width: 100%; //width: 100%;
overflow-x: hidden; //overflow-x: hidden;
} }
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<section class="banner"> <section class="banner">
<div class="banner-bg"> <div v-motion-fade-visible class="banner-bg">
<!-- 替换为实际背景图路径 --> <!-- 替换为实际背景图路径 -->
<img src="https://images.health.ufutx.com/202506/27/ae38015f070eeb51347359577acf1063.png" alt="Banner背景" /> <img src="https://images.health.ufutx.com/202506/27/ae38015f070eeb51347359577acf1063.png" alt="Banner背景" />
</div> </div>
@ -66,6 +66,7 @@
.banner-bg { .banner-bg {
width: 100%; width: 100%;
height: 830px;
overflow: hidden; overflow: hidden;
img { img {

View File

@ -64,7 +64,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue' import { ref, computed, onMounted, nextTick } from 'vue'
import BranchMap from './BranchMap.vue' import BranchMap from './BranchMap.vue'
import { openExternalLink } from '@/utils/navigation.ts' import { gotoMapFn } from '@/utils/tools.ts'
// 使ECharts // 使ECharts
const branchList = [ const branchList = [
{ {
@ -126,17 +126,6 @@ const handleMapClick = (index: number) => {
activeIndex.value = index activeIndex.value = index
} }
const gotoMapFn = (latitude: any, longitude: any, detail: any) => {
console.log(latitude, longitude, detail)
openExternalLink(
// `http://maps.googleapis.com/maps/api/staticmap?center=${latitude},${longitude}&zoom=14&size=400x300&sensor=false`
// `http://maps.google.com/map/api/staticmap?center=${latitude},${longitude}&size=640*640&sensor=true`
`https://uri.amap.com/marker?position=${longitude},${latitude}&name=${detail}`,
'_blank'
// `http://api.map.baidu.com/marker?location=${latitude},${longitude}&name==${detail}&output=html`
)
}
// //
const calculateSliderPosition = () => { const calculateSliderPosition = () => {
const activeTab = tabsRef.value[activeIndex.value] const activeTab = tabsRef.value[activeIndex.value]

View File

@ -7,7 +7,7 @@ import { ref, onMounted, watch, nextTick, onUnmounted } from 'vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import chinaJson from '@/assets/map/china.json' import chinaJson from '@/assets/map/china.json'
echarts.registerMap('china', chinaJson) echarts.registerMap('china', chinaJson as any)
const props = defineProps<{ const props = defineProps<{
branchList: Array<{ branchList: Array<{
@ -166,7 +166,8 @@ const updateChart = () => {
const headquartersIndex = getHeadquartersIndex() const headquartersIndex = getHeadquartersIndex()
const option = myChart.getOption() as echarts.EChartsOption const option = myChart.getOption() as echarts.EChartsOption
if (option.series && option.series[1]) { // if (option.series && option.series[1]) {
if (option.series && Array.isArray(option.series) && option.series[1]) {
option.series[1].data = props.branchList.map((item, index) => ({ option.series[1].data = props.branchList.map((item, index) => ({
...item, ...item,
value: item.coord, value: item.coord,

View File

@ -75,14 +75,14 @@ const timelineData = [
.timeline-header { .timeline-header {
margin-bottom: 50px; margin-bottom: 50px;
.main-title { .main-title {
font-size: 28px; font-size: 32px;
font-weight: bold; font-weight: bold;
color: #333; color: @text-color;
margin-bottom: 10px; margin-bottom: 10px;
} }
.sub-title { .sub-title {
font-size: 14px; font-size: 20px;
color: #999; color: @text-color-secondary;
} }
} }
} }

View File

@ -65,7 +65,7 @@ const valueTabs = [
title: '团队精神', title: '团队精神',
name: 'TeamSpirit', name: 'TeamSpirit',
content: ['开放沟通、互相尊重,人人都是关键角色', '注重跨部门协作,共同承担责任', '团队一起面对挑战、共享成功'], content: ['开放沟通、互相尊重,人人都是关键角色', '注重跨部门协作,共同承担责任', '团队一起面对挑战、共享成功'],
image: 'https://images.health.ufutx.com/202506/20/136362c2dff3844304dd1e96bd36ee03.png' image: 'https://images.health.ufutx.com/202507/02/eb71edac0faa7282c999b1d9ebbb5962.png'
}, },
{ {
title: '创始人精神', title: '创始人精神',

View File

@ -3,37 +3,83 @@
<h2 class="section-title">资质认证</h2> <h2 class="section-title">资质认证</h2>
<p class="section-subtitle">成立至今获得多项荣誉</p> <p class="section-subtitle">成立至今获得多项荣誉</p>
<div class="carousel-container"> <div class="carousel-container">
<!-- <button class="carousel-btn prev" @click="slide(-1)"></button>--> <div class="carousel-btn prev"></div>
<div class="certificate-section"> <div class="certificate-section">
<div class="certificate-list"> <div class="certificate-list">
<div <Marquee
v-for="(src, index) in certificateImgs" :duration="60"
:key="index" :reverse="false"
class="certificate-item" repeatCount="3"
@mouseenter="activeIndex = index" pause-on-hover
@mouseleave="activeIndex = -1" container-class="h-56 bg-white rounded-lg shadow-sm p-4"
:initial-offset="0"
> >
<img :src="src" alt="certificate" :class="{ 'scale-up': activeIndex === index }" /> <div
</div> v-for="(src, index) in certificateImgs"
:key="index"
class="certificate-item"
@mouseenter="activeIndex = index"
@mouseleave="activeIndex = -1"
>
<el-image
:ref="
(el: any) => {
imageRefs[index] = el
}
"
:src="src"
alt="certificate"
class="certificate-image"
:zoom-rate="1.2"
:preview-src-list="certificateImgs"
show-progress
preview-teleported
:initial-index="index"
:class="{ 'scale-up': activeIndex === index }"
/>
<div
v-show="activeIndex === index"
v-motion-pop-visible
class="certificate-preview"
@click.stop="handleClick(index)"
>
<p class="_text">预览</p>
</div>
</div>
</Marquee>
</div> </div>
</div> </div>
<!-- <button class="carousel-btn next" @click="slide(1)"></button>--> <div class="carousel-btn next"></div>
</div> </div>
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import Marquee from '@/components/Marquee.vue'
import type { ImageInstance } from 'element-plus'
// ref
const imageRefs = ref<(ImageInstance | null)[]>([])
const certificateImgs = [ const certificateImgs = [
'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png', 'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png',
'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png', 'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png',
'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png', // 'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png', //
'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png', 'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png',
'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png',
'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png',
'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png',
'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png' 'https://images.health.ufutx.com/202506/20/6972bf0f5da9af6ec4f2b8fba404d59e.png'
] ]
const activeIndex = ref(-1) const activeIndex = ref(-1)
//
const handleClick = (index: number) => {
const image = imageRefs.value[index]
if (image) {
image.showPreview() //
}
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@ -73,20 +119,19 @@ const activeIndex = ref(-1)
} }
.carousel-btn { .carousel-btn {
position: absolute; position: absolute;
top: 50%; top: 46%;
transform: translateY(-50%); //flex-shrink: 0;
background: rgba(0, 0, 0, 0.5); width: 45px;
color: #fff; height: 60px;
border: none; background-size: cover;
padding: 10px 15px;
cursor: pointer;
font-size: @font-size-lg;
&.prev { &.prev {
left: 20px; left: 70px;
background-image: url('https://images.health.ufutx.com/202507/02/9094a60740fb3dfd8f71cf5ebaeefcf7.png');
} }
&.next { &.next {
right: 20px; width: 45px;
right: 70px;
background-image: url('https://images.health.ufutx.com/202507/02/6e2c89ad18e8f8f813a71870e1ed473c.png');
} }
} }
} }
@ -94,43 +139,96 @@ const activeIndex = ref(-1)
//background: red; //background: red;
} }
.certificate-section { .certificate-section {
padding: 172px 0px; padding: 122px 0px;
&&::before {
content: '';
position: absolute;
top: 0;
height: 100%;
width: 60px;
z-index: 1;
pointer-events: none;
}
&&::before {
left: 160px;
//background: linear-gradient(to right, #dadddf, transparent);
}
//position: absolute; //position: absolute;
//top: 0; //top: 0;
.certificate-list { .certificate-list {
display: flex; max-width: 1561px;
height: 460px;
align-items: center; align-items: center;
justify-content: center; //background: red;
gap: 16px; margin: 0 auto;
margin: 0px auto; //margin-left: 100px;
box-sizing: border-box;
//
display: flex;
flex-direction: row; /* 明确水平方向 */
flex-wrap: nowrap; /* 禁止换行 */
overflow: hidden; /* 隐藏超出容器的部分 */
} }
.certificate-item { .certificate-item {
transition: all 0.3s ease; width: 288px; /* 固定宽度,确保能在一行放下多个 */
cursor: pointer; flex-shrink: 0; /* 禁止收缩,保证宽度不变 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); /* 增加轻微阴影,提升视觉层次 */
img { margin-right: 16px;
cursor: pointer; /* 提示可交互 */
position: relative;
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); /* 卡片整体过渡 */
.certificate-image {
width: 288px; width: 288px;
height: auto;
border: 4px solid transparent;
transform-origin: bottom center; /* &#x6838;&#x5FC3;&#xFF1A;&#x8BBE;&#x7F6E;&#x7F29;&#x653E;&#x539F;&#x70B9;&#x4E3A;&#x5E95;&#x90E8;&#x4E2D;&#x5FC3; */ transform-origin: bottom center; /* &#x6838;&#x5FC3;&#xFF1A;&#x8BBE;&#x7F6E;&#x7F29;&#x653E;&#x539F;&#x70B9;&#x4E3A;&#x5E95;&#x90E8;&#x4E2D;&#x5FC3; */
transition: transition:
transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94),
border-color 0.3s ease; border-color 0.3s ease;
} }
.certificate-preview {
position: absolute;
top: -20px;
left: 0;
width: 288px;
height: 100%;
display: flex;
justify-content: center;
opacity: 1;
//background: red;
align-items: center;
._text {
font-size: 18px;
color: white;
padding: 12px 32px;
border-radius: 50px;
background: rgba(0, 0, 0, 0.5);
}
}
} }
// + hover // + hover
.scale-up { .scale-up {
transform: scale(1.1); transform: scale(1.1);
//z-index: 10;
transform-origin: bottom center; /* 新增,设置缩放原点为底部中心 */ transform-origin: bottom center; /* 新增,设置缩放原点为底部中心 */
z-index: 10; //box-shadow: 0 8px 24px rgba(75, 137, 220, 0.2);
border-color: #4b89dc; }
box-shadow: 0 8px 24px rgba(75, 137, 220, 0.2); .scale-up-preview {
transition: all 0.3s ease;
opacity: 1;
//transform: translateY(0);
} }
} }
// 穿 Marquee
:deep(.marquee-container) {
/* 根据 Marquee 实际类名调整 */
pointer-events: auto !important;
}
//
:deep(.marquee-track) {
pointer-events: auto !important;
}
// //
@media (max-width: 768px) { @media (max-width: 768px) {
.certificate-list { .certificate-list {

View File

@ -15,17 +15,17 @@
<script setup lang="ts"> <script setup lang="ts">
const healthItems = [ const healthItems = [
{ {
icon: 'https://images.health.ufutx.com/202506/20/10c734494721ea0a60c3b9c3ebd82a60.png', icon: 'https://images.health.ufutx.com/202506/30/f0356813f3eda8e37f303a44ae3d70b8.png',
title: '生命科学+AI的深度融合', title: '生命科学+AI的深度融合',
desc: '让人体健康管理像科研一样自然' desc: '让人体健康管理像科研一样自然'
}, },
{ {
icon: 'https://images.health.ufutx.com/202506/20/10c734494721ea0a60c3b9c3ebd82a60.png', icon: 'https://images.health.ufutx.com/202506/30/cfb74da07ad90521f3a108c6450f88c7.png',
title: '万亿级产业新基建', title: '万亿级产业新基建',
desc: '从个人健康到城市级智慧医疗的完整生态' desc: '从个人健康到城市级智慧医疗的完整生态'
}, },
{ {
icon: 'https://images.health.ufutx.com/202506/20/10c734494721ea0a60c3b9c3ebd82a60.png', icon: 'https://images.health.ufutx.com/202506/30/572c91788ea274174a8bd8fe77e52c08.png',
title: '人人都拥有的数字健康管家', title: '人人都拥有的数字健康管家',
desc: '让真实有效的健康管理普惠全民' desc: '让真实有效的健康管理普惠全民'
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<section class="banner"> <section class="banner">
<div class="banner-bg"> <div v-motion-fade-visible class="banner-bg">
<!-- 替换为实际背景图路径 --> <!-- 替换为实际背景图路径 -->
<img src="https://images.health.ufutx.com/202506/13/19ee6e89c397511fef5ba05d9798cc76.png" alt="Banner背景" /> <img src="https://images.health.ufutx.com/202506/13/19ee6e89c397511fef5ba05d9798cc76.png" alt="Banner背景" />
</div> </div>
@ -66,6 +66,7 @@ const newsList = [
.banner-bg { .banner-bg {
width: 100%; width: 100%;
height: 830px;
overflow: hidden; overflow: hidden;
img { img {

View File

@ -21,8 +21,10 @@
<div class="device-info"> <div class="device-info">
<h3 class="device-title">{{ navList[activeIndex].center.title }}</h3> <h3 class="device-title">{{ navList[activeIndex].center.title }}</h3>
<h3 class="device-subtitle">{{ navList[activeIndex].center.subtitle }}</h3> <h3 class="device-subtitle">{{ navList[activeIndex].center.subtitle }}</h3>
<!-- eslint-disable-next-line vue/no-v-html -->
<p class="device-desc" v-html="navList[activeIndex].center.description"></p> <p class="device-desc" v-html="navList[activeIndex].center.description"></p>
<div class="download-btn" @click="openReport">立即下载</div> <div class="btn-success-glow download-btn" @click="openReport">立即下载</div>
</div> </div>
</div> </div>
</div> </div>
@ -38,7 +40,7 @@ const navList = ref([
{ {
text: '健康手环', text: '健康手环',
active: true, active: true,
icon: 'https://images.health.ufutx.com/202506/18/4aae244f94bbf8bf352e8ab8e2270a81.png', icon: 'https://images.health.ufutx.com/202507/02/4aae244f94bbf8bf352e8ab8e2270a81.png',
center: { center: {
navLabel: '健康手环', // navLabel: '健康手环', //
title: '健康手环', // title: '健康手环', //
@ -138,12 +140,12 @@ const handleSelect = (index: number) => {
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
opacity: 0.6; //opacity: 0.8;
transition: opacity 0.3s; transition: opacity 0.3s;
} }
.nav-text { .nav-text {
font-size: 26px; font-size: 20px;
color: @text-color-secondary; color: @text-color-secondary;
transition: color 0.3s; transition: color 0.3s;
} }
@ -228,7 +230,7 @@ const handleSelect = (index: number) => {
.download-btn { .download-btn {
display: inline-block; display: inline-block;
padding: 16px 30px; padding: 14px 30px;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
border-radius: 100px; border-radius: 100px;
@ -236,7 +238,6 @@ const handleSelect = (index: number) => {
background: #18ca6e; background: #18ca6e;
color: #fff; color: #fff;
font-size: 20px; font-size: 20px;
font-weight: bold;
} }
} }
} }

View File

@ -7,8 +7,10 @@
<div class="device-info"> <div class="device-info">
<h3 class="device-title">{{ props.device.title }}</h3> <h3 class="device-title">{{ props.device.title }}</h3>
<h3 class="device-subtitle">AI精准个性化健康方案服务</h3> <h3 class="device-subtitle">AI精准个性化健康方案服务</h3>
<!-- eslint-disable-next-line vue/no-v-html -->
<p class="device-desc" v-html="props.device.desc"></p> <p class="device-desc" v-html="props.device.desc"></p>
<div class="download-btn" @click="openReport">立即下载</div> <div class="btn-success-glow download-btn" @click="openReport">立即下载</div>
</div> </div>
</div> </div>
</section> </section>
@ -97,7 +99,7 @@ const openReport = () => {
.download-btn { .download-btn {
display: inline-block; display: inline-block;
padding: 16px 30px; padding: 14px 30px;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
border-radius: 100px; border-radius: 100px;
@ -105,7 +107,12 @@ const openReport = () => {
background: #18ca6e; background: #18ca6e;
color: #fff; color: #fff;
font-size: 20px; font-size: 20px;
font-weight: bold; //font-weight: bold;
&&:hover {
transform: scale(1.05);
box-shadow: 0 0 20px #18ca6e;
}
} }
} }
} }

View File

@ -8,7 +8,7 @@
跨出社交圈一分钟演讲<br /> 跨出社交圈一分钟演讲<br />
立即让未来的灵魂伴侣更了解你轻松打开话题 立即让未来的灵魂伴侣更了解你轻松打开话题
</p> </p>
<div class="download-btn" @click="openReport">立即下载</div> <div class="btn-success-glow download-btn" @click="openReport">立即下载</div>
</div> </div>
<div class="speech-img"> <div class="speech-img">
<!-- <img src="@/assets/speech-app.png" alt="演讲界面" />--> <!-- <img src="@/assets/speech-app.png" alt="演讲界面" />-->
@ -67,7 +67,7 @@ const openReport = () => {
.download-btn { .download-btn {
display: inline-block; display: inline-block;
padding: 16px 30px; padding: 14px 30px;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
border-radius: 100px; border-radius: 100px;
@ -75,7 +75,7 @@ const openReport = () => {
background: #18ca6e; background: #18ca6e;
color: #fff; color: #fff;
font-size: 20px; font-size: 20px;
font-weight: bold; //font-weight: bold;
} }
} }

View File

@ -3,43 +3,19 @@
<div class="tags-header"> <div class="tags-header">
<h2 class="tags-title">友福AI婚恋-专属单身标签</h2> <h2 class="tags-title">友福AI婚恋-专属单身标签</h2>
<p class="tags-subtitle">Right Beside You, They Love Each Other!</p> <p class="tags-subtitle">Right Beside You, They Love Each Other!</p>
<!-- <el-button type="primary" class="download-btn">立即下载</el-button>--> <div class="btn-glow download-btn" @click="downloadFloveApp()">立即下载</div>
<div class="btn-glow download-btn">立即下载</div>
<div class="tags-icon"> <div class="tags-icon">
<img src="https://images.health.ufutx.com/202506/18/3b16ca61dc63eb22c102aed934301c0a.png" alt="" /> <img src="https://images.health.ufutx.com/202506/18/3b16ca61dc63eb22c102aed934301c0a.png" alt="" />
</div> </div>
</div> </div>
<!-- <div class="tags-carousel">-->
<!-- <div class="tag-item" v-for="(item, i) in tags" :key="i">-->
<!-- <img :src="item.img" alt="单身标签" class="tag-img" />-->
<!-- <p class="tag-desc">{{ item.desc }}</p>-->
<!-- </div>-->
<!-- </div>-->
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 18 import { ElButton } from 'element-plus' import { downloadFloveApp } from '@/utils/tools.js'
// const tags = [
// {
// img: '@/assets/tag-1.png',
// desc: ' 27 '
// },
// {
// img: '@/assets/tag-2.png',
// desc: ' 30 '
// },
// {
// img: '@/assets/tag-3.png',
// desc: ' 25 '
// }
// ]
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@import '@/styles/global.less';
.ai-tags { .ai-tags {
padding: 100px 192px; padding: 100px 192px;
text-align: center; text-align: center;
@ -60,12 +36,12 @@
} }
.download-btn { .download-btn {
display: inline-block; display: inline-block;
padding: 16px 30px; padding: 14px 30px;
border-radius: 100px; border-radius: 100px;
background: var(--Style, #1060ff); background: var(--Style, #1060ff);
color: var(--ffffff, #fff); color: var(--ffffff, #fff);
font-size: 18px; font-size: 18px;
font-weight: 500; //font-weight: 500;
margin-bottom: 50px; margin-bottom: 50px;
} }
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<section class="banner"> <section class="banner">
<div class="banner-bg"> <div v-motion-fade-visible class="banner-bg">
<!-- 替换为实际背景图路径 --> <!-- 替换为实际背景图路径 -->
<img src="https://images.health.ufutx.com/202506/13/100bcb7820a3cac2f8de6f50d0404460.png" alt="Banner背景" /> <img src="https://images.health.ufutx.com/202506/13/100bcb7820a3cac2f8de6f50d0404460.png" alt="Banner背景" />
</div> </div>
@ -66,6 +66,7 @@
.banner-bg { .banner-bg {
width: 100%; width: 100%;
height: 830px;
overflow: hidden; overflow: hidden;
img { img {

View File

@ -22,24 +22,24 @@
<script setup lang="ts"> <script setup lang="ts">
const points = [ const points = [
{ {
icon: 'https://images.health.ufutx.com/202506/18/2bf6cfbc4fef2c4d8e276e95398405a0.png', icon: 'https://images.health.ufutx.com/202507/02/fb92625096f65beda47a97daaec64879.png',
title: '传统婚介困境', title: '传统婚介困境',
desc: '效率低/规则混乱' desc: '效率低/规则混乱'
}, },
{ {
icon: 'https://images.health.ufutx.com/202506/18/2bf6cfbc4fef2c4d8e276e95398405a0.png', icon: 'https://images.health.ufutx.com/202507/02/09d30d86a53a86ee26faa9be50bbd24c.png',
title: '资料真假难辨', title: '资料真假难辨',
desc: '生物特征+社交轨迹验证' desc: '生物特征核验+社交轨迹交叉验证'
}, },
{ {
icon: 'https://images.health.ufutx.com/202506/18/2bf6cfbc4fef2c4d8e276e95398405a0.png', icon: 'https://images.health.ufutx.com/202507/02/1a19cab6581b311e815002b859b8e440.png',
title: '匹配维度单一', title: '匹配维度单一',
desc: '多维度开放模型' desc: '多维度开放模型'
}, },
{ {
icon: 'https://images.health.ufutx.com/202506/18/2bf6cfbc4fef2c4d8e276e95398405a0.png', icon: 'https://images.health.ufutx.com/202507/02/83b119df621a38fd9c9d0199c54ed2fa.png',
title: '售后服务缺失', title: '售后服务缺失',
desc: '关系认证及延伸服务' desc: '关系认知疏导及关怀服务'
} }
] ]
</script> </script>

View File

@ -2,7 +2,7 @@
<section class="success-stories"> <section class="success-stories">
<h2 class="section-title">就在你身边他们相爱啦</h2> <h2 class="section-title">就在你身边他们相爱啦</h2>
<p class="section-subtitle">Right Beside You, They Love Each Other!</p> <p class="section-subtitle">Right Beside You, They Love Each Other!</p>
<div class="download-btn btn-glow">立即下载</div> <div class="download-btn btn-glow" @click="downloadFloveApp()">立即下载</div>
<div class="stories-icon"> <div class="stories-icon">
<img src="https://images.health.ufutx.com/202506/18/1a837037ce7fb8502a211a01e19fbf55.png" alt="" /> <img src="https://images.health.ufutx.com/202506/18/1a837037ce7fb8502a211a01e19fbf55.png" alt="" />
</div> </div>
@ -25,6 +25,7 @@
// { img: '@/assets/story-5.jpg' }, // { img: '@/assets/story-5.jpg' },
// { img: '@/assets/story-6.jpg' } // { img: '@/assets/story-6.jpg' }
// ] // ]
import { downloadFloveApp } from '@/utils/tools.ts'
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@ -49,12 +50,12 @@
.download-btn { .download-btn {
display: inline-block; display: inline-block;
padding: 16px 30px; padding: 14px 30px;
border-radius: 100px; border-radius: 100px;
background: var(--Style, #1060ff); background: var(--Style, #1060ff);
color: var(--ffffff, #fff); color: var(--ffffff, #fff);
font-size: 18px; font-size: 18px;
font-weight: 500; //font-weight: 500;
margin-bottom: 50px; margin-bottom: 50px;
} }

View File

@ -35,14 +35,14 @@ import TalentTraining from './sections/TalentTraining.vue'
import CityPartner from './sections/CityPartner.vue' import CityPartner from './sections/CityPartner.vue'
const cooperateData = { const cooperateData = {
title: '合作咨询', title: '合作咨询',
desc: '欢迎健康产业链上下游企业以及希望利用AI技术 升级健康产品与服务的企业与我们洽谈合作,共创产业新价值', desc: '欢迎健康产业链上下游企业以及希望利用AI技术<br> 升级健康产品与服务的企业与我们洽谈合作,共创产业新价值',
img: 'https://images.health.ufutx.com/202506/18/2e9c9d64bdcf03fbe5041720f03033ca.png' img: 'https://images.health.ufutx.com/202506/20/c60d98038ab065c2e92dc67b938d45e2.png'
} }
const cooperateDataV2 = { const cooperateDataV2 = {
title: '合作咨询', title: '合作咨询',
desc: '提供专属健康服务和解决方案', desc: '提供专属健康服务和解决方案',
img: 'https://images.health.ufutx.com/202506/18/2e9c9d64bdcf03fbe5041720f03033ca.png' img: 'https://images.health.ufutx.com/202506/20/c60d98038ab065c2e92dc67b938d45e2.png'
} }
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<section class="banner"> <section class="banner">
<div class="banner-bg"> <div v-motion-fade-visible class="banner-bg">
<!-- 替换为实际背景图路径 --> <!-- 替换为实际背景图路径 -->
<img src="https://images.health.ufutx.com/202506/13/7c87ebff15ce960d0e58473f401fa91b.png" alt="Banner背景" /> <img src="https://images.health.ufutx.com/202506/13/7c87ebff15ce960d0e58473f401fa91b.png" alt="Banner背景" />
</div> </div>
@ -66,6 +66,7 @@
.banner-bg { .banner-bg {
width: 100%; width: 100%;
height: 830px;
overflow: hidden; overflow: hidden;
img { img {

View File

@ -209,7 +209,7 @@ const currentIdx = ref(0)
margin-bottom: 20px; margin-bottom: 20px;
} }
.desc { .desc {
font-size: 16px; font-size: 20px;
color: @text-color-secondary; color: @text-color-secondary;
line-height: 34px; line-height: 34px;
white-space: pre-wrap; white-space: pre-wrap;

View File

@ -1,15 +1,17 @@
<template> <template>
<section class="cooperation-consult"> <section class="cooperation-consult" :style="{ backgroundImage: `url(${props.device.img})` }">
<div class="consult-content"> <div class="consult-content">
<p class="consult-text"> <!-- eslint-disable-next-line vue/no-v-html -->
{{ props.device.desc }} <p class="consult-text" v-html="props.device.desc"></p>
</p> <div class="consult-btn" @click="openCooperationDialog">{{ props.device.title }}</div>
<div class="consult-btn">{{ props.device.title }}</div>
</div> </div>
<CooperationDialog ref="cooperationDialogRef" />
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import CooperationDialog from '@/views/Ecosystem/sections/CooperationDialog.vue'
const props = defineProps({ const props = defineProps({
device: { device: {
type: Object, type: Object,
@ -17,10 +19,15 @@ const props = defineProps({
default: () => ({ default: () => ({
title: '合作咨询', title: '合作咨询',
desc: '欢迎健康产业链上下游企业以及希望利用AI技术 升级健康产品与服务的企业与我们洽谈合作,共创产业新价值', desc: '欢迎健康产业链上下游企业以及希望利用AI技术 升级健康产品与服务的企业与我们洽谈合作,共创产业新价值',
img: 'https://images.health.ufutx.com/202506/18/2e9c9d64bdcf03fbe5041720f03033ca.png' img: 'https://images.health.ufutx.com/202506/20/c60d98038ab065c2e92dc67b938d45e2.png'
}) })
} }
}) })
const cooperationDialogRef = ref<InstanceType<typeof CooperationDialog>>()
const openCooperationDialog = () => {
cooperationDialogRef.value?.openDialog()
}
// // 18 import { ElButton } from 'element-plus' // // 18 import { ElButton } from 'element-plus'
</script> </script>
@ -46,9 +53,14 @@ const props = defineProps({
color: #313fa8; color: #313fa8;
font-size: 20px; font-size: 20px;
font-weight: 500; font-weight: 500;
padding: 6px 36px; padding: 16px 36px;
border-radius: 100px; border-radius: 100px;
background: #fff; background: #fff;
transition: all 0.2s;
&:hover {
box-shadow: 0 0 10px rgba(189, 189, 189, 0.8);
transform: scale(1.1);
}
} }
} }
} }

View File

@ -0,0 +1,200 @@
<template>
<!-- 控制弹框显示与隐藏的变量假设为dialogVisible需要在script中定义 -->
<div class="custom-dialog-wrapper">
<el-dialog v-model="dialogVisible" width="600px" :show-close="false">
<template #header>
<div class="dialog-header">合作咨询</div>
</template>
<template #default>
<div class="form-container">
<el-form ref="ruleFormRef" label-width="80px" :label-position="labelPosition" :model="form" :rules="rules">
<el-form-item label="您的姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入联系人姓名" />
</el-form-item>
<el-form-item label="联系方式" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="您的需求" prop="demand">
<el-input
v-model="form.demand"
type="textarea"
:rows="4"
:autosize="{ minRows: 2, maxRows: 4 }"
placeholder="请写明您的具体需求,以便我们安排形影顾问与您联系"
/>
</el-form-item>
</el-form>
</div>
</template>
<template #footer>
<span class="dialog-footer">
<div class="cancel-btn" @click="dialogVisible = false">取消</div>
<el-button type="primary" class="confirm-btn" @click="handleSubmit(ruleFormRef)">提交</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
// ElDialogElForm 使
// import { ElDialog, ElForm, ElFormItem, ElInput, ElButton } from 'element-plus';
const labelPosition = ref('top')
import request from '@/utils/request'
import { ElMessageBox } from 'element-plus'
const dialogVisible = ref<boolean>(false)
const ruleFormRef = ref()
const form = reactive({
name: '',
mobile: '',
demand: ''
})
const rules = reactive<any>({
name: [{ required: true, message: 'Please enter the form information', trigger: 'blur' }],
mobile: [{ required: true, message: 'Please enter the form information', trigger: 'blur' }],
demand: [{ required: true, message: 'Please enter the form information', trigger: 'blur' }]
})
const handleSubmit = async (formEl: any) => {
// form
if (!formEl) return
await formEl.validate((valid: any, fields: any) => {
if (valid) {
console.log('提交的数据:', form)
try {
const res = request.post('/go/api/:plat/v1/consult/apply', form)
console.log('用户列表:', res)
ElMessageBox.confirm('提交成功!', {
showCancelButton: false,
type: 'success',
confirmButtonClass: 'confirmButtonClass'
})
.then(() => {
//
dialogVisible.value = false
//
form.name = ''
form.mobile = ''
form.demand = ''
})
.catch(() => {})
} catch (error) {
console.error('请求失败:', error)
}
} else {
console.log('error submit!', fields)
}
})
}
//
defineExpose({
openDialog: () => {
dialogVisible.value = true
}
})
</script>
<style lang="less">
.confirmButtonClass {
display: flex;
padding: 16px 50px;
justify-content: center;
border-radius: 10px;
border: 0.5px solid var(--1060-ff, #1060ff);
color: var(--1060-ff, #1060ff);
text-align: center;
font-size: 18px;
font-weight: 500;
}
.confirmButtonClass {
background: var(--1060-ff, #1060ff);
color: #fff;
}
</style>
<style scoped lang="less">
:deep(.el-dialog) {
border-radius: 20px;
padding: 50px 70px;
.form-container {
padding: 0;
}
.el-form-item {
margin-bottom: 30px;
}
.el-form-item__label {
font-size: 14px;
font-weight: bold;
line-height: 14px;
margin-bottom: 6px;
}
.el-input__wrapper,
.el-textarea__inner {
display: flex;
padding: 18px 16px;
align-items: center;
gap: 10px;
align-self: stretch;
border-radius: 10px;
border: 0.5px solid #b2b3b5;
box-shadow: none;
}
/* 在你的全局样式文件中 */
.el-input__inner::placeholder,
.el-textarea__inner::placeholder {
color: var(--b-2-b-3-b-5, #b2b3b5);
text-align: left;
font-size: 14px;
font-weight: 400;
}
.el-textarea__inner {
min-height: 142px !important;
}
}
.dialog-header {
font-size: 22px;
color: @text-color;
font-weight: 600;
text-align: center;
padding-bottom: 50px;
}
.form-container {
padding: 20px;
}
.dialog-footer {
display: flex;
justify-content: center;
gap: 30px;
.cancel-btn,
.confirm-btn {
display: flex;
width: 172px;
height: 57px;
padding: 16px 50px;
justify-content: center;
border-radius: 10px;
border: 0.5px solid var(--1060-ff, #1060ff);
color: var(--1060-ff, #1060ff);
text-align: center;
font-size: 18px;
font-weight: 500;
}
.confirm-btn {
background: var(--1060-ff, #1060ff);
color: #fff;
}
}
</style>

View File

@ -98,7 +98,7 @@ const trainings = [
} }
.item-desc { .item-desc {
text-align: left; text-align: left;
font-size: @font-size-sm; font-size: @font-size-lg;
color: @text-color-secondary; color: @text-color-secondary;
} }
} }

View File

@ -2,11 +2,11 @@
<div class="home-page"> <div class="home-page">
<!-- Banner 模块--> <!-- Banner 模块-->
<BannerCarousel /> <BannerCarousel />
<!-- 核心价值模块--> <!-- &lt;!&ndash; 核心价值模块&ndash;&gt;-->
<CoreValue /> <CoreValue />
<!-- 应用场景模块--> <!-- &lt;!&ndash; 应用场景模块&ndash;&gt;-->
<UseCases /> <UseCases />
<!-- 全球服务模块--> <!-- &lt;!&ndash; 全球服务模块&ndash;&gt;-->
<GlobalService /> <GlobalService />
<!-- 客户反馈模块--> <!-- 客户反馈模块-->
<CustomerFeedback /> <CustomerFeedback />
@ -15,13 +15,28 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts" name="Home">
import BannerCarousel from './sections/BannerCarousel.vue' import BannerCarousel from './sections/BannerCarousel.vue'
import CoreValue from './sections/CoreValue.vue' import CoreValue from './sections/CoreValue.vue'
import UseCases from './sections/UseCases.vue' import UseCases from './sections/UseCases.vue'
import GlobalService from './sections/GlobalService.vue' import GlobalService from './sections/GlobalService.vue'
import CustomerFeedback from './sections/CustomerFeedback.vue' import CustomerFeedback from './sections/CustomerFeedback.vue'
import Partners from './sections/Partners.vue' import Partners from './sections/Partners.vue'
// 1
onMounted(() => {
console.log('Home 页面首次加载(未从缓存中恢复)')
})
//
onActivated(() => {
console.log('Home 页面从缓存中激活(缓存生效)')
})
//
onDeactivated(() => {
console.log('Home 页面被缓存(缓存生效)')
})
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -1,6 +1,6 @@
<template> <template>
<section class="banner"> <section class="banner">
<div class="banner-bg"> <div v-motion-fade-visible class="banner-bg">
<!-- 替换为实际背景图路径 --> <!-- 替换为实际背景图路径 -->
<img src="https://images.health.ufutx.com/202506/12/e6ea04327d2b5dbd9e4ae441431018df.png" alt="Banner背景" /> <img src="https://images.health.ufutx.com/202506/12/e6ea04327d2b5dbd9e4ae441431018df.png" alt="Banner背景" />
</div> </div>
@ -28,7 +28,7 @@
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts" name="BannerCarousel">
import { ref } from 'vue' import { ref } from 'vue'
const activeIndex = ref<number | null>(null) const activeIndex = ref<number | null>(null)
@ -72,6 +72,7 @@ const newsList = [
.banner-bg { .banner-bg {
width: 100%; width: 100%;
height: 830px;
overflow: hidden; overflow: hidden;
img { img {

View File

@ -1,150 +1,179 @@
<template> <template>
<section class="feedback-section"> <section class="feedback-section">
<div class="feedback-title">客户反馈</div> <div class="feedback-title">客户反馈</div>
<!-- 跑马灯容器去掉换行强制内容单行显示 -->
<div class="feedback-list"> <div class="feedback-list">
<div v-for="(item, index) in feedbackList" :key="index" class="feedback-card"> <Marquee
<div class="avatar-container"> :duration="360"
<img :src="item.avatar" alt="avatar" class="avatar" /> :reverse="false"
repeatCount="3"
pause-on-hover
container-class="h-56 bg-white rounded-lg shadow-sm p-4"
>
<!-- 内容卡片直接作为Marquee的子元素不嵌套额外div -->
<div v-for="(item, index) in feedbackList[0]" :key="`${item.username}-${index}`" class="feedback-card">
<el-popover placement="right-end" title="" :width="320" trigger="hover" popper-class="custom-popover">
<template #default>
<div class="_userinfo">
<div class="_userPic"><img :src="item.avatar" alt="avatar" class="avatar" /></div>
<div class="_username">{{ item.username }}</div>
</div>
<TextGenerateEffect class="_comment" :words="item.feedback" />
</template>
<template #reference>
<div class="popover-container">
<div class="avatar-container">
<img :src="item.avatar" alt="avatar" class="avatar" />
</div>
<div class="feedback-info">
<p class="username">{{ item.username }}</p>
<p class="comment">{{ item.feedback }}</p>
</div>
</div>
</template>
</el-popover>
</div> </div>
<div class="feedback-info"> </Marquee>
<p class="username">{{ item.username }}</p> </div>
<p class="comment">{{ item.comment }}</p> <div class="feedback-list" style="margin-top: 40px">
<Marquee
:duration="360"
:reverse="true"
repeatCount="3"
pause-on-hover
container-class="h-56 bg-white rounded-lg shadow-sm p-4"
>
<!-- 内容卡片直接作为Marquee的子元素不嵌套额外div -->
<div v-for="(item, index) in feedbackList[1]" :key="`${item.username}-${index}`" class="feedback-card">
<el-popover placement="right-end" title="" :width="320" trigger="hover" popper-class="custom-popover">
<template #default>
<div class="_userinfo">
<div class="_userPic"><el-image :src="item.avatar" alt="avatar" class="avatar" /></div>
<div class="_username">{{ item.username }}</div>
</div>
<TextGenerateEffect class="_comment" :words="item.feedback" />
</template>
<template #reference>
<div class="popover-container">
<div class="avatar-container">
<img :src="item.avatar" alt="avatar" class="avatar" />
</div>
<div class="feedback-info">
<p class="username">{{ item.username }}</p>
<p class="comment">{{ item.feedback }}</p>
</div>
</div>
</template>
</el-popover>
</div> </div>
</div> </Marquee>
</div> </div>
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 使 // 使
const feedbackList = [ import Marquee from '@/components/Marquee.vue'
{ import TextGenerateEffect from '@/components/TextGenerateEffect.vue'
username: '章小样', import { userFeedbacks } from '@/data/userFeedbacks.ts'
comment: '操作简单,容易上手', // T
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png' function splitArrayRandomly<T>(arr: T[], ratio = 0.5): [T[], T[]] {
}, //
{ const shuffled = [...arr]
username: '章小样',
comment: '界面简约清晰,功能强大',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '预约操作简单方便,客户体验好评',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '操作简单,容易上手',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
}
]
</script>
// Fisher-Yates
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
}
// 50%
const splitIndex = Math.round(shuffled.length * ratio)
//
return [shuffled.slice(0, splitIndex), shuffled.slice(splitIndex)]
}
const feedbackList = reactive(splitArrayRandomly(userFeedbacks))
console.log('数组1:', feedbackList[0]) // : [3, 7, 2, 5]
</script>
<style scoped lang="less"> <style scoped lang="less">
// //
@bg-color: #f9fbff; @bg-color: #f9fbff;
@card-bg: #ffffff; @card-bg: #ffffff;
@radius: 12px; @radius: 12px;
@padding: 16px; @padding: 16px;
@gap: 50px;
@avatar-size: 60px; @avatar-size: 60px;
@subtext-color: #999; @subtext-color: #999;
@transition: all 0.3s ease; @transition: all 0.3s ease;
.feedback-section { .feedback-section {
background-color: @bg-color; background-color: @bg-color;
.px(100px); padding: 0 0 100px 0; /* 改用标准padding写法避免自定义.px()可能的问题 */
text-align: center; text-align: center;
.pb(100px); //background: bisque;
position: relative;
&&::before {
content: '';
position: absolute;
top: 0;
height: 100%;
width: 60px;
z-index: 1;
pointer-events: none;
}
&&::before {
left: 100px;
background: linear-gradient(to right, #f9fbff, transparent);
}
.feedback-title { .feedback-title {
font-size: 28px; font-size: 28px;
font-weight: 600; font-weight: 600;
color: @text-color; padding: 100px 0 60px;
.pb(60px);
.pt(100px);
} }
//
.feedback-list { .feedback-list {
//width: 100vw;
max-width: 1820px;
margin: 0 auto;
margin-left: 100px;
box-sizing: border-box;
//
padding: 10px;
display: flex; display: flex;
flex-wrap: wrap; flex-direction: row; /* 明确水平方向 */
justify-content: center; flex-wrap: nowrap; /* 禁止换行 */
gap: @gap; overflow: hidden; /* 隐藏超出容器的部分 */
} }
//
.feedback-card { .feedback-card {
display: flex; .feedback-popper {
align-items: center; border-radius: 320px !important;
padding: @padding; }
width: 320px; /* 固定宽度,确保能在一行放下多个 */
//max-width: 320px;
flex-shrink: 0; /* 禁止收缩,保证宽度不变 */
border-radius: 16px; border-radius: 16px;
background: var(--ffffff, #fff); background: #fff;
transition: @transition; //transition: @transition;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); /* 增加轻微阴影,提升视觉层次 */
margin-right: 50px;
cursor: pointer; /* 提示可交互 */
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); /* 卡片整体过渡 */
&:hover { &:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
} }
.popover-container {
padding: @padding;
display: flex; /* 卡片内部水平排列头像和文字 */
flex-direction: row; /* 明确水平方向 */
align-items: center;
}
.avatar-container { .avatar-container {
flex-shrink: 0; flex-shrink: 0;
margin-right: 12px; margin-right: 12px;
@ -154,33 +183,86 @@ const feedbackList = [
height: @avatar-size; height: @avatar-size;
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;
border: 2px solid #f0f0f0;
} }
} }
.feedback-info { .feedback-info {
text-align: left; text-align: left;
max-width: calc(100% - @avatar-size - 12px); /* 限制文本区域宽度 */
.username { .username {
font-size: 18px; font-size: 16px;
font-weight: 500; font-weight: 500;
color: @text-color; margin-bottom: 6px;
margin-bottom: 4px; white-space: nowrap; /* 用户名不换行 */
overflow: hidden;
text-overflow: ellipsis;
} }
.comment { .comment {
font-size: 14px; font-size: 14px;
color: @subtext-color; color: @subtext-color;
line-height: 1.4; line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2; /* 固定2行文本 */
-webkit-box-orient: vertical;
overflow: hidden;
/* 过渡动画重点控制max-height */
max-height: 42px; /* 14px*1.5*2=42px刚好2行高度 */
transition: max-height 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
} }
} }
} }
} }
// //
@media (max-width: 768px) { @media (max-width: 768px) {
.feedback-section {
padding: 0 20px 60px; /* 缩小移动端内边距 */
}
.feedback-card { .feedback-card {
width: 100%; width: 220px; /* 移动端缩小卡片宽度 */
max-width: 320px; }
}
</style>
<style lang="less">
.custom-popover {
/* 修改圆角(核心) */
border-radius: 18px !important; /* 根据需求调整数值如8px、16px */
/* 可选:修改边框 */
border: 1px solid #e5e7eb !important;
/* 可选:修改阴影 */
box-shadow: 0 4px 16px rgba(79, 79, 79, 0.08) !important;
/* 可选:修改内部边距 */
padding: 22px !important;
._userinfo {
.flex();
align-items: center;
.mb(12px);
}
._userPic {
width: 62px;
height: 62px;
border-radius: 50%;
overflow: hidden;
border: 4px solid #f9fbff;
box-shadow: 0 0 16px rgba(49, 49, 49, 0.18);
.mr(10px);
}
._username {
font-size: 20px;
font-weight: 600;
color: @text-color;
}
._comment {
font-size: 16px;
letter-spacing: 1px;
color: @text-color-secondary;
} }
} }
</style> </style>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<div></div>
</template>
<style scoped lang="less"></style>

View File

@ -2,11 +2,25 @@
<section class="scene-section"> <section class="scene-section">
<h2 class="scene-title">友福同享AI健康解决方案应用场景</h2> <h2 class="scene-title">友福同享AI健康解决方案应用场景</h2>
<div class="scene-list"> <div class="scene-list">
<div v-for="(item, index) in sceneList" :key="index" class="scene-item"> <!-- 遍历场景项通过selectedIndex控制默认选中状态 -->
<div
v-for="(item, index) in sceneList"
:key="index"
class="scene-item"
:class="{ active: selectedIndex === index }"
@mouseenter="selectedIndex = index"
@mouseleave="
() => {
if (index !== defaultIndex) selectedIndex = defaultIndex
}
"
>
<div class="scene-inner"> <div class="scene-inner">
<img :src="item.icon" alt="场景图标" class="scene-icon" /> <div class="scene-content">
<p class="scene-name">{{ item.name }}</p> <img :src="item.icon" alt="场景图标" class="scene-icon" />
<p class="scene-desc">{{ item.desc }}</p> <p class="scene-name">{{ item.name }}</p>
<p class="scene-desc">{{ item.desc }}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -14,33 +28,41 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
//
const sceneList = [ const sceneList = [
{ {
name: '地区政府/社区健康服务', name: '地区政府/社区健康服务',
desc: '提供社区健康管理解决方案,提升居民健康水平,降低医疗成本。', desc: '提供社区健康管理解决方案,提升居民健康水平,降低医疗成本。',
icon: 'https://images.health.ufutx.com/202506/12/d2b5ca33bd970f64a6301fa75ae2eb22.png' icon: 'https://images.health.ufutx.com/202506/30/87f66f9deca9c217d3f86d7d7c5d25c9.png'
}, },
{ {
name: '医院体检中心/健康管理机构', name: '医院体检中心/健康管理机构',
desc: '智能化健康评估系统,提升体检效率,优化健康管理流程。', desc: '智能化健康评估系统,提升体检效率,优化健康管理流程。',
icon: 'https://images.health.ufutx.com/202506/12/d2b5ca33bd970f64a6301fa75ae2eb22.png' icon: 'https://images.health.ufutx.com/202506/30/32b9067c68fc706ab7596c621720f847.png'
}, },
{ {
name: '企业员工健康管理', name: '企业员工健康管理', // 32
desc: '降低企业医疗成本,提升员工健康与生产力。', desc: '降低企业医疗成本,提升员工健康与生产力。',
icon: 'https://images.health.ufutx.com/202506/12/d2b5ca33bd970f64a6301fa75ae2eb22.png' icon: 'https://images.health.ufutx.com/202506/30/1de2a31aa67aa92ed7c5d59de0936bbf.png'
}, },
{ {
name: '健康产业链供应商', name: '健康产业链供应商',
desc: '连接健康产业上下游,提供精准数据分析和市场洞察。', desc: '连接健康产业上下游,提供精准数据分析和市场洞察。',
icon: 'https://images.health.ufutx.com/202506/12/d2b5ca33bd970f64a6301fa75ae2eb22.png' icon: 'https://images.health.ufutx.com/202506/30/ddbb65cf7004bb06982e0bbfa4e9bdb1.png'
}, },
{ {
name: '健康管理专业培训', name: '健康管理专业培训',
desc: '提供专业健康管理培训课程,培养行业人才,提升服务质量。', desc: '提供专业健康管理培训课程,培养行业人才,提升服务质量。',
icon: 'https://images.health.ufutx.com/202506/12/d2b5ca33bd970f64a6301fa75ae2eb22.png' icon: 'https://images.health.ufutx.com/202506/30/0bc22b00e61752cab79bad46318a680c.png'
} }
] ]
// 32
const defaultIndex = 2
//
const selectedIndex = ref(defaultIndex)
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@ -68,69 +90,88 @@ const sceneList = [
.scene-item { .scene-item {
width: 284px; width: 284px;
height: 336px; height: 336px;
perspective: 1000px; // 3D perspective: 1000px;
cursor: pointer; cursor: pointer;
transform: translateZ(0); /* 硬件加速 */
// /
&.active .scene-inner,
&:hover .scene-inner {
//box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
//transform: translateY(-10px) scale(1.03);
}
&.active .scene-content,
&:hover .scene-content {
//background-color: #e6f0ff;
background: linear-gradient(180deg, #fff 0%, #ecf2ff 100%);
}
&.active .scene-icon,
&:hover .scene-icon {
transform: scale(1.1);
margin-bottom: 15px;
}
&.active .scene-name,
&:hover .scene-name {
//color: @primary-color;
transform: translateY(5px);
}
&.active .scene-desc,
&:hover .scene-desc {
opacity: 1;
transform: translateY(0);
max-height: 100px;
}
.scene-inner { .scene-inner {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 0px 16px; //background-color: #fff;
background: linear-gradient(180deg, #fff 0%, #ecf2ff 100%);
border-radius: @border-radius-md;
overflow: hidden;
//box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.scene-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 16px; padding: 40px 16px;
background: linear-gradient(180deg, #fff 0%, #ecf2ff 100%); height: 100%;
border-radius: @border-radius-md; transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
transition: all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1); //
//
.scene-icon {
width: 150px;
transition: all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.scene-name {
font-size: @font-size-md;
font-weight: @font-weight-medium;
color: @text-color;
transition: all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.scene-desc {
font-size: @font-size-sm;
color: @text-color-light;
line-height: 1.4;
max-height: 0;
opacity: 0;
overflow: hidden;
transition: all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
margin-top: -10px; //
}
} }
// .scene-icon {
&:hover .scene-inner { width: 150px;
transform: translateY(-10px) scale(1.05); transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1); margin-bottom: 20px;
background: linear-gradient(180deg, #fff 0%, #e6f0ff 100%);
} }
&:hover .scene-icon { .scene-name {
transform: scale(1.1); font-size: @font-size-md;
margin-bottom: 8px; font-weight: @font-weight-medium;
color: @text-color;
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
margin-bottom: 10px;
} }
&:hover .scene-name { .scene-desc {
color: @primary-color; font-size: @font-size-sm;
transform: translateY(5px); color: @text-color-secondary;
} line-height: 1.4;
opacity: 0;
&:hover .scene-desc {
max-height: 100px;
opacity: 1;
transform: translateY(10px); transform: translateY(10px);
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
max-height: 0;
overflow: hidden;
margin-top: 10px;
} }
} }
} }

View File

@ -2,7 +2,26 @@
<section class="banner"> <section class="banner">
<div class="banner-bg"> <div class="banner-bg">
<!-- 替换为实际背景图路径 --> <!-- 替换为实际背景图路径 -->
<img src="https://images.health.ufutx.com/202506/13/c963e7b0e605f16d139fb88789f3a452.png" alt="Banner背景" /> <el-image
v-motion-fade-visible
lazy
src="https://images.health.ufutx.com/202507/02/c963e7b0e605f16d139fb88789f3a452.png"
>
<template #placeholder>
<div class="placeholder-content">
<p>加载中...</p>
</div>
</template>
<!-- 加载失败的占位内容 -->
<template #error>
<div class="error-content">
<i class="el-icon-picture-outline" />
<p>图片加载失败</p>
</div>
</template>
</el-image>
<!-- <img alt="Banner背景" />-->
</div> </div>
<div class="news-panel"> <div class="news-panel">
<div <div
@ -72,11 +91,12 @@ const newsList = [
.banner-bg { .banner-bg {
width: 100%; width: 100%;
height: 830px;
overflow: hidden; overflow: hidden;
img { img {
width: 100%; width: 100%;
height: auto; height: 100%;
display: block; display: block;
} }
} }
@ -141,7 +161,21 @@ const newsList = [
} }
} }
} }
:deep(.el-image__placeholder) {
/* 关键:占满图片容器 */
width: 100% !important;
height: 100% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
background-color: red; /* 可选:添加背景色 */
}
/* 自定义占位内容样式 */
.placeholder-content {
text-align: center;
color: #999;
}
// //
@media (max-width: 768px) { @media (max-width: 768px) {
.banner-content { .banner-content {

View File

@ -2,103 +2,103 @@
<section class="feedback-section"> <section class="feedback-section">
<div class="feedback-title">真实客户案例</div> <div class="feedback-title">真实客户案例</div>
<div class="feedback-list"> <div class="feedback-list">
<div v-for="(item, index) in feedbackList" :key="index" class="feedback-card"> <Marquee
<div class="avatar-container"> :duration="360"
<img :src="item.avatar" alt="avatar" class="avatar" /> :reverse="false"
repeatCount="3"
pause-on-hover
container-class="h-56 bg-white rounded-lg shadow-sm p-4"
:initial-offset="0"
>
<!-- 内容卡片直接作为Marquee的子元素不嵌套额外div -->
<div v-for="(item, index) in feedbackList[0]" :key="`${item.username}-${index}`" class="feedback-card">
<el-popover placement="right-end" :width="320" trigger="hover" popper-class="custom-popover">
<template #default>
<div class="_userinfo">
<div class="_userPic"><img :src="item.avatar" alt="avatar" class="avatar" /></div>
<div class="_username">{{ item.username }}</div>
</div>
<div class="_comment">{{ item.case }}</div>
</template>
<template #reference>
<div class="popover-container">
<div class="avatar-container">
<img :src="item.avatar" alt="avatar" class="avatar" />
</div>
<div class="feedback-info">
<!-- <p class="username">{{ item.username }}</p>-->
<p class="comment">{{ item.result }}</p>
</div>
</div>
</template>
</el-popover>
</div> </div>
<div class="feedback-info"> </Marquee>
<!-- <p class="username">{{ item.username }}</p>--> </div>
<p class="comment text-2-line-ellipsis">{{ item.comment }}</p> <div class="feedback-list" style="margin-top: 40px">
<Marquee
:duration="360"
:reverse="false"
repeatCount="3"
pause-on-hover
container-class="h-56 bg-white rounded-lg shadow-sm p-4"
:initial-offset="-300"
>
<!-- 内容卡片直接作为Marquee的子元素不嵌套额外div -->
<div v-for="(item, index) in feedbackList[1]" :key="`${item.username}-${index}`" class="feedback-card">
<el-popover placement="right-end" :width="320" trigger="hover" popper-class="custom-popover">
<template #default>
<div class="_userinfo">
<div class="_userPic"><img :src="item.avatar" alt="avatar" class="avatar" /></div>
<div class="_username">{{ item.username }}</div>
</div>
<div class="_comment">{{ item.case }}</div>
</template>
<template #reference>
<div class="popover-container">
<div class="avatar-container">
<img :src="item.avatar" alt="avatar" class="avatar" />
</div>
<div class="feedback-info">
<!-- <p class="username">{{ item.username }}</p>-->
<p class="comment">{{ item.result }}</p>
</div>
</div>
</template>
</el-popover>
</div> </div>
</div> </Marquee>
</div> </div>
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 使 // 使
const feedbackList = [ // import TextGenerateEffect from '@/components/TextGenerateEffect.vue'
{ import Marquee from '@/components/Marquee.vue'
username: '章小样', import { realCases } from '@/data/realCases.ts'
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png' // T
}, function splitArrayRandomly<T>(arr: T[], ratio = 0.5): [T[], T[]] {
{ //
username: '章小样', const shuffled = [...arr]
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png' // Fisher-Yates
}, for (let i = shuffled.length - 1; i > 0; i--) {
{ const j = Math.floor(Math.random() * (i + 1))
username: '章小样', ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '预约操作简单方便,客户体验好评',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '章小样',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
},
{
username: '派小喵萌宠',
comment: '交付与实施阶段很专业,坚持每天汇报进度和风险,并提出很多合理建议~',
avatar: 'https://images.health.ufutx.com/202506/12/1c0c9dad7586e6fd00ab781064acc822.png'
} }
]
// 50%
const splitIndex = Math.round(shuffled.length * ratio)
//
return [shuffled.slice(0, splitIndex), shuffled.slice(splitIndex)]
}
const feedbackList = reactive(splitArrayRandomly(realCases))
console.log('数组1:', feedbackList[0]) // : [3, 7, 2, 5]
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@ -112,66 +112,114 @@ const feedbackList = [
@subtext-color: #999; @subtext-color: #999;
@transition: all 0.3s ease; @transition: all 0.3s ease;
//
@bg-color: #f9fbff;
@card-bg: #ffffff;
@radius: 12px;
@padding: 16px;
@avatar-size: 60px;
@subtext-color: #999;
@transition: all 0.3s ease;
.feedback-section { .feedback-section {
background-color: @bg-color; background-color: @bg-color;
.px(100px); padding: 0 0 100px 0; /* 改用标准padding写法避免自定义.px()可能的问题 */
text-align: center; text-align: center;
.pb(100px); //background: bisque;
position: relative;
&&::before {
content: '';
position: absolute;
top: 0;
height: 100%;
width: 60px;
z-index: 1;
pointer-events: none;
}
&&::before {
left: 100px;
background: linear-gradient(to right, #f9fbff, transparent);
}
.feedback-title { .feedback-title {
font-size: 28px; font-size: 28px;
font-weight: 600; font-weight: 600;
color: @text-color; padding: 100px 0 60px;
.pb(60px);
.pt(100px);
} }
//
.feedback-list { .feedback-list {
//width: 100vw;
max-width: 1820px;
margin: 0 auto;
margin-left: 100px;
box-sizing: border-box;
//
padding: 10px;
display: flex; display: flex;
flex-wrap: wrap; flex-direction: row; /* 明确水平方向 */
justify-content: center; flex-wrap: nowrap; /* 禁止换行 */
gap: @gap; overflow: hidden; /* 隐藏超出容器的部分 */
} }
//
.feedback-card { .feedback-card {
transition: @transition; width: 362px; /* 固定宽度,确保能在一行放下多个 */
display: flex; //max-width: 320px;
padding: 16px; flex-shrink: 0; /* 禁止收缩,保证宽度不变 */
align-items: center;
gap: 20px;
border-radius: 100px; border-radius: 100px;
background: #f3f5f7; background: #f3f5f7;
//transition: @transition;
//box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); /* */
margin-right: 60px;
cursor: pointer; /* 提示可交互 */
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); /* 卡片整体过渡 */
&:hover { &:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
} }
.popover-container {
padding: @padding;
display: flex; /* 卡片内部水平排列头像和文字 */
flex-direction: row; /* 明确水平方向 */
align-items: center;
}
.avatar-container { .avatar-container {
flex-shrink: 0; flex-shrink: 0;
//margin-right: 12px; margin-right: 20px;
.avatar { .avatar {
width: @avatar-size; width: @avatar-size;
height: @avatar-size; height: @avatar-size;
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;
border: 2px solid #f0f0f0;
} }
} }
.feedback-info { .feedback-info {
text-align: left; text-align: left;
max-width: calc(100% - @avatar-size - 12px); /* 限制文本区域宽度 */
.username { .username {
font-size: 18px; font-size: 16px;
font-weight: 500; font-weight: 500;
color: @text-color; margin-bottom: 6px;
margin-bottom: 4px; white-space: nowrap; /* 用户名不换行 */
overflow: hidden;
text-overflow: ellipsis;
} }
.comment { .comment {
width: 250px;
font-size: 14px; font-size: 14px;
color: @text-color; color: @text-color;
line-height: 1.4; line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2; /* 固定2行文本 */
-webkit-box-orient: vertical;
overflow: hidden;
/* 过渡动画重点控制max-height */
max-height: 42px; /* 14px*1.5*2=42px刚好2行高度 */
transition: max-height 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
} }
} }
} }
@ -185,3 +233,42 @@ const feedbackList = [
} }
} }
</style> </style>
<style lang="less">
.custom-popover {
/* 修改圆角(核心) */
border-radius: 18px !important; /* 根据需求调整数值如8px、16px */
/* 可选:修改边框 */
border: 1px solid #e5e7eb !important;
/* 可选:修改阴影 */
box-shadow: 0 4px 16px rgba(79, 79, 79, 0.08) !important;
/* 可选:修改内部边距 */
padding: 22px !important;
._userinfo {
.flex();
align-items: center;
.mb(12px);
}
._userPic {
width: 62px;
height: 62px;
border-radius: 50%;
overflow: hidden;
border: 4px solid #f3f5f7;
box-shadow: 0 0 16px rgba(79, 79, 79, 0.08);
.mr(10px);
}
._username {
font-size: 20px;
font-weight: 600;
color: @text-color;
}
._comment {
font-size: 16px;
color: @text-color-secondary;
}
}
</style>

View File

@ -1,11 +1,11 @@
<template> <template>
<section class="core-value"> <section class="core-value">
<div class="container"> <div class="container">
<!-- <h3 class="section-title">核心价值</h3>--> <h3 class="section-title">友福七维AI健康修复体系</h3>
<!-- <p class="section-desc">友福同享AI健康解决方案应用场景</p>--> <p class="section-desc">七大核心维度</p>
<!-- 替换为实际图示路径 --> <!-- 替换为实际图示路径 -->
<img <img
src="https://images.health.ufutx.com/202506/17/28710422aa9cf68a38cfbd19abd7ebf0.png" src="https://images.health.ufutx.com/202507/02/37b46d4591aa3244f62320bd60e07549.png"
alt="七大核心维度" alt="七大核心维度"
class="diagram" class="diagram"
/> />
@ -32,12 +32,13 @@
font-weight: bold; font-weight: bold;
margin-bottom: 20px; margin-bottom: 20px;
color: @text-color; color: @text-color;
.mt(50px);
} }
.section-desc { .section-desc {
font-size: 20px; font-size: 20px;
color: @text-color-secondary; color: @text-color-secondary;
margin-bottom: 60px; //margin-bottom: 60px;
} }
.diagram { .diagram {

View File

@ -11,6 +11,7 @@
> >
<div class="scene-inner"> <div class="scene-inner">
<img :src="item.icon" alt="场景图标" class="scene-icon" /> <img :src="item.icon" alt="场景图标" class="scene-icon" />
<!-- eslint-disable-next-line vue/no-v-html -->
<p class="scene-name" v-html="item.name"></p> <p class="scene-name" v-html="item.name"></p>
<p v-if="activeIndex === index" class="scene-desc">{{ item.desc }}</p> <p v-if="activeIndex === index" class="scene-desc">{{ item.desc }}</p>
</div> </div>
@ -71,8 +72,8 @@ onMounted(() => {
text-align: center; text-align: center;
background-color: #f5f7fe; background-color: #f5f7fe;
.pt(80+42px); .pt(80+42px);
.pb(80-42px); //.pb(80-42px);
height: 580px;
.scene-list { .scene-list {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));

View File

@ -1,65 +1,153 @@
<template> <template>
<section class="article-content"> <section class="article-content">
<!-- 文章头部信息 --> <!-- 加载状态 -->
<div class="article-header"> <div v-if="loading" class="loading-container">
<h1 class="article-title">数字健康领域的长期主义者未来医疗100强友福同享持续领跑</h1> <p>加载中...</p>
<div class="meta-info"> </div>
<span>发布时间2025-06-20</span>
<span>浏览次数8604</span> <!-- 错误状态 -->
<div v-else-if="error" class="error-container">
<p>加载失败请稍后重试</p>
<button @click="fetchArticleDetail">重新加载</button>
</div>
<!-- 文章内容数据加载成功后显示 -->
<div v-else>
<!-- 文章头部信息 -->
<div class="article-header">
<h1 class="article-title">
<TextGenerateEffect :words="article.title" class="" />
</h1>
<div class="meta-info">
<span>发布时间{{ article.create_time }}</span>
<span>浏览次数{{ article.read_num }}</span>
</div>
</div> </div>
</div>
<!-- 封面图 --> <!-- 封面图 -->
<div class="article-banner"> <div v-motion-fade-visible class="article-banner">
<img src="https://images.health.ufutx.com/202506/20/9fd62f318b50764c02c58356dc426d44.png" alt="AI健康领域" /> <!-- <img :src="article.pic" alt="文章封面" class="banner-img" />-->
</div> </div>
<!-- 正文内容 --> <!-- 正文内容富文本渲染 -->
<div class="article-body">
<p>
1分钟演讲是一款创新的智能辅助应用专注于强化公众演讲技能助力用户高效构思并精准传达思想它集演讲与实战练习于一体为用户提供全方位一站式的自我提升平台1分钟演讲这个看似简短却充满力量的表达方式是真实自我与世界快速沟通的桥梁也是能快速缩短社交距离增进信任的神奇钥匙无论是事业发展社交拓展寻找灵魂伴侣还是个人品牌建设这一功能都能成为你强而有力的社交工具帮助你快速脱颖而出让更多人认识并记住你友福同享凭借多年在全病程管理AI医疗领域的卓越成就稳列未来医疗100强榜单第一梯队同时微脉自主研发的CareAI荣膺最佳数字技术产品获2025未来医疗100强创新奖
</p>
<p> <!-- eslint-disable-next-line vue/no-v-html -->
未来医疗100强榜单以前瞻性专业性和国际视野为核心被誉为医疗创新领域的风向标代表未来5-10年全球医疗产业的新势力与变革方向 <div class="article-body" v-html="article.content"></div>
</p>
<p>
让医疗服务不再难
是微脉始终如一的使命微脉从全人群全方位全周期出发专攻医疗服务的现有之短板和就医者的健康服务需求缺口补足诊后检后术后院后管理空白
</p>
<p>
在突破医疗健康行业各阶段难点拓展数字健康领域可能性上微脉做了许多从0到1的创造和尝试致力于打造中国特色的管理式医疗健康组织C-CMO
</p>
<p>
未来友福同享将持续深化"AI+全病程管理"创新模式助力公立医院高质量发展加快构建"预防-诊疗-康复"全周期健康服务生态让优质医疗服务惠及更多百姓
</p>
</div> </div>
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import request from '@/utils/request' // axios
import TextGenerateEffect from '@/components/TextGenerateEffect.vue'
// URL:plat:id
const route = useRoute()
// const plat = route.params.plat as string // "default"
const articleId = route.params.id as string // ID
//
const loading = ref(true) //
const error = ref(false) //
const article = ref({
title: '', //
create_time: '', // YYYY-MM-DD
read_num: 0, //
pic: '', // URL
content: '' // HTML
})
//
const fetchArticleDetail = async () => {
loading.value = true
error.value = false
try {
// platid
const res = await request.get(`/go/api/:plat/v1/article/${articleId}/detail`)
// { code: 0, data: { title, publishTime, views, cover, content } }
if (res.code === 0 && res.data) {
const data = res.data
//
article.value = data
} else {
throw new Error(res.message || '获取文章详情失败')
}
} catch (err: any) {
console.error('文章详情请求失败:', err)
error.value = true
ElMessage.error(err.message || '加载失败,请重试')
} finally {
loading.value = false
}
}
//
onMounted(() => {
fetchArticleDetail()
})
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.article-content { .article-content {
padding: 200px 192px 100px 192px; padding: 200px 192px 100px;
//max-width: 1200px;
margin: 0 auto; margin: 0 auto;
background: #fff; background: #fff;
text-align: left; text-align: left;
// max-width: 1920px;
//
.loading-container {
min-height: 500px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: @text-color-secondary;
}
//
.error-container {
min-height: 500px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
color: #f56c6c;
button {
padding: 6px 16px;
border: 1px solid @primary-color;
color: @primary-color;
background: transparent;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: @primary-color;
color: #fff;
}
}
}
//
.article-header { .article-header {
padding-bottom: 30px; padding-bottom: 30px;
border-bottom: 0.3px solid #b5b5b5; border-bottom: 0.3px solid #b5b5b5;
.article-title { .article-title {
font-size: 32px; font-size: 32px;
font-weight: 400; font-weight: 400;
color: @text-color; color: @text-color;
margin-bottom: 10px; margin-bottom: 10px;
line-height: 32px; line-height: 1.5;
} }
.meta-info { .meta-info {
font-size: 18px; font-size: 18px;
color: @text-color-secondary; color: @text-color-secondary;
@ -75,7 +163,8 @@
// //
.article-banner { .article-banner {
margin: 30px 0; margin: 30px 0;
img {
.banner-img {
width: 100%; width: 100%;
height: auto; height: auto;
border-radius: 8px; border-radius: 8px;
@ -83,28 +172,52 @@
} }
} }
// //
.article-body { .article-body {
font-weight: 400;
line-height: 35px;
p { p {
font-size: 16px; font-size: 16px;
color: #666; color: #666;
line-height: 1.8; line-height: 1.8;
margin-bottom: 24px; margin-bottom: 24px;
text-indent: 2em; // text-indent: 2em;
}
//
img {
max-width: 100%;
height: auto;
margin: 16px 0;
}
//
h2,
h3 {
margin: 24px 0 16px;
color: @text-color;
} }
} }
// //
@media (max-width: 768px) { @media (max-width: 768px) {
padding: 40px 20px; padding: 40px 20px;
.article-header { .article-header {
.article-title { .article-title {
font-size: 24px; font-size: 24px;
} }
.meta-info {
font-size: 14px;
flex-wrap: wrap;
gap: 8px 20px;
}
} }
.article-body { .article-body {
p { p {
text-indent: 0; // text-indent: 0;
font-size: 15px;
} }
} }
} }

View File

@ -1,131 +1,198 @@
<template> <template>
<!-- 整体布局导航 + Banner + 内容 + 页脚 -->
<div class="page-container"> <div class="page-container">
<!-- 已有Banner组件保持设计中的顶部视觉 --> <!-- 已有Banner组件 -->
<BannerCarousel /> <BannerCarousel />
<!-- 核心内容区 --> <!-- 核心内容区 -->
<div class="news-content"> <div class="news-content">
<!-- 标签切换友福动态 / 媒体报道 --> <!-- 标签切换动态加载 -->
<el-tabs v-model="activeTab" class="news-tabs" @tab-click="resetPage"> <el-tabs v-model="activeTab" class="news-tabs" @tab-change="resetPage">
<el-tab-pane label="友福动态" name="dynamic"></el-tab-pane> <el-tab-pane v-for="item in categories" :key="item.name" :label="item.name" :name="item.name">
<el-tab-pane label="媒体报道" name="report"></el-tab-pane> <template #label>
<div class="categories-label">{{ item.name }}</div>
</template>
</el-tab-pane>
</el-tabs> </el-tabs>
<!-- 新闻列表 --> <!-- 新闻列表数据加载后显示 -->
<div class="news-list"> <div v-if="!loading && newsData.length > 0" class="news-list">
<div v-for="(item, idx) in filteredNews" :key="idx" class="news-item"> <div v-for="(item, idx) in filteredNews" :key="idx" class="news-item">
<img :src="item.cover" alt="新闻封面" class="news-cover" /> <img :src="item.pic" alt="image" class="news-cover" />
<div class="news-info"> <div class="news-info">
<h3 class="news-title">{{ item.title }}</h3> <h3 class="news-title">{{ item.title }}</h3>
<h3 class="news-label">友福新闻</h3> <h3 class="news-label">{{ item.publisher }}</h3>
<div class="news-wrap" @click="goToArticle"> <div class="news-wrap" @click="openArticleInNewWindow(item.id)">
<div class="view-btn">查看详情</div> <div class="view-btn">查看详情</div>
<div class="news-date"> <div class="news-date">
<img <img
class="icon" class="icon"
src="https://images.health.ufutx.com/202506/13/c5e3552a870fd254610290bbabce5e30.png" src="https://images.health.ufutx.com/202506/13/c5e3552a870fd254610290bbabce5e30.png"
/> />
<p>{{ item.date }}</p> <p>{{ item.create_time }}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 无数据时显示占位图加载完成且数据为空 -->
<!-- 分页Element Plus 分页组件 --> <div v-else-if="!loading && newsData.length === 0" class="empty-state">
<img
src="https://images.health.ufutx.com/202507/02/259f20509b57b36199ef7619f8d63733.png"
alt="暂无数据"
class="empty-img"
/>
<p class="empty-text">暂无内容</p>
</div>
<!-- 加载状态 -->
<div v-else class="loading-container">
<!-- <el-spinner size="large"></el-spinner>-->
<p>加载中...</p>
</div>
<!-- 分页 -->
<div class="pagination-wrap"> <div class="pagination-wrap">
<el-pagination <el-pagination
:current-page="currentPage" :current-page="currentPage"
:page-sizes="[10, 20, 50]" :page-sizes="[10, 20, 50]"
:page-size="pageSize" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" background
layout="total,prev, pager, next"
:total="totalCount" :total="totalCount"
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handlePageChange" @current-change="handlePageChange"
></el-pagination> >
</el-pagination>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed, onMounted } from 'vue'
// import { ElMessage } from 'element-plus'
import BannerCarousel from '@/views/News/sections/BannerCarousel.vue' import BannerCarousel from '@/views/News/sections/BannerCarousel.vue'
import request from '@/utils/request' //
// //
const dynamicNews = [ // import { useRoute, useRouter } from 'vue-router'
{ import { openNewWindow } from '@/utils/navigation.ts'
cover: 'https://images.health.ufutx.com/202506/13/b604685780bdb4ea4906e751b14590ec.png', //
title: '体管理年政策驱动AI+全利管理重塑健康服务体系...',
date: '2025-06-02',
type: 'dynamic'
},
{
cover: 'https://images.health.ufutx.com/202506/13/73e74b753e7fc7c8b00cecc0535a6a91.png',
title: '权威媒体专访:友福同享的创新健康生态',
date: '2025-05-30',
type: 'report'
}
]
const reportNews = [
{
cover: 'https://images.health.ufutx.com/202506/13/73e74b753e7fc7c8b00cecc0535a6a91.png',
title: '媒体报道友福同享如何用AI颠覆健康管理',
date: '2025-06-01',
type: 'report'
}
]
// // const route = useRoute()
for (let i = 0; i < 15; i++) { // const router = useRouter()
const newsItem = {
cover: 'https://images.health.ufutx.com/202506/13/b604685780bdb4ea4906e751b14590ec.png',
title: `友福动态:健康管理服务升级发布会圆满成功 ${i + 1}`,
date: `2025-05-${28 - i}`,
type: 'dynamic'
}
//
dynamicNews.push(newsItem)
reportNews.push({ ...newsItem, type: 'report' }) // type
}
// //
const activeTab = ref('dynamic') // const activeTab = ref('') //
const currentPage = ref(1) const currentPage = ref(1) //
const pageSize = ref(10) const pageSize = ref(15) //
const totalCount = ref(502) // 502 const totalCount = ref(0) //
const loading = ref(false) //
const categories = ref<Array<any>>([]) //
const newsData = ref<Array<any>>([]) //
// //
const filteredNews = computed(() => { const filteredNews = computed(() => {
const list = activeTab.value === 'dynamic' ? dynamicNews : reportNews
//
const start = (currentPage.value - 1) * pageSize.value const start = (currentPage.value - 1) * pageSize.value
return list.slice(start, start + pageSize.value) return newsData.value.slice(start, start + pageSize.value)
}) })
import { useRouter } from 'vue-router'
// 1. // ID
const router = useRouter() const openArticleInNewWindow = (id: number) => {
openNewWindow(`/articleDetail/${id}`)
// 2.
const goToArticle = () => {
router.push({
path: '/news/12' //
})
} }
//
const fetchCategories = async () => {
loading.value = true
try {
// :plat
const res = await request.get(`/go/api/:plat/v1/article/category/list`)
if (res.data.length > 0) {
categories.value = res.data || []
//
if (categories.value.length > 0) {
activeTab.value = categories.value[0].name
}
} else {
ElMessage.error(res.message || '获取分类失败')
}
} catch (error: any) {
console.error('获取分类列表失败', error)
ElMessage.error('网络错误,请稍后重试')
} finally {
loading.value = false
}
}
//
const fetchNewsList = async (category_id: string, page: number) => {
loading.value = true
try {
// type
const params = {
category_id: category_id,
page
}
//
const res = await request.get(`/go/api/:plat/v1/article/list`, params)
if (res.code === 0) {
newsData.value = res.data.data || []
console.log(newsData.value)
totalCount.value = res.data.total_count || 0
} else {
ElMessage.error(res.message || '获取新闻失败')
}
} catch (error: any) {
console.error('获取新闻列表失败', error)
ElMessage.error('网络错误,请稍后重试')
} finally {
loading.value = false
}
}
//
const resetPage = () => {
currentPage.value = 1
if (activeTab.value) {
loadNewsData()
}
}
//
const loadNewsData = () => {
if (!activeTab.value) return
// nametype
console.log(activeTab.value)
const category = categories.value.find(item => item.name === activeTab.value)
if (category) {
fetchNewsList(category.id || '', currentPage.value)
}
}
// //
const handleSizeChange = (val: number) => { const handleSizeChange = (val: number) => {
pageSize.value = val pageSize.value = val
currentPage.value = 1 currentPage.value = 1
loadNewsData()
} }
const handlePageChange = (val: number) => { const handlePageChange = (val: number) => {
currentPage.value = val currentPage.value = val
loadNewsData()
} }
const resetPage = () => {
currentPage.value = 1 // //
} onMounted(async () => {
await fetchCategories()
if (activeTab.value) {
loadNewsData()
}
})
//
// watch(activeTab, () => {
// loadNewsData()
// })
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@ -136,18 +203,32 @@ const resetPage = () => {
background-color: @bg-color; background-color: @bg-color;
} }
/* 内容区容器 */
.news-content { .news-content {
max-width: 1920px; max-width: 1920px;
padding: 0 20px;
}
/* 加载状态样式 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
color: @text-color-secondary;
.el-spinner {
margin-bottom: 20px;
}
} }
/* 标签切换样式(还原设计的蓝色下划线) */ /* 标签切换样式(还原设计的蓝色下划线) */
.news-tabs { .news-tabs {
.pt(50px); padding-top: 50px;
//align-items: center; .categories-label {
font-size: 32px;
}
:deep(.el-tabs__nav-wrap) { :deep(.el-tabs__nav-wrap) {
//margin-bottom: 1.04167vw;
&:after { &:after {
content: ''; content: '';
position: absolute; position: absolute;
@ -169,7 +250,10 @@ const resetPage = () => {
:deep(.el-tabs__header) { :deep(.el-tabs__header) {
margin: 0; margin: 0;
width: 100%;
.el-tabs__nav { .el-tabs__nav {
justify-content: center;
width: 100%; width: 100%;
margin-bottom: 20px; margin-bottom: 20px;
@ -184,34 +268,43 @@ const resetPage = () => {
} }
} }
} }
}
.news-tabs { //
// @media (max-width: @tablet-breakpoint) {
:deep(.el-tabs__header) { :deep(.el-tabs__item) {
width: 100%; // margin-right: @space-md;
.el-tabs__nav { font-size: @font-size-lg;
justify-content: center; // }
:deep(.el-tabs__active-bar) {
bottom: -15px !important;
}
}
@media (max-width: @mobile-breakpoint) {
:deep(.el-tabs__item) {
margin-right: @space-sm;
font-size: @font-size-md;
}
:deep(.el-tabs__active-bar) {
bottom: -10px !important;
} }
} }
} }
/* 新闻列表 */ /* 新闻列表 */
.news-list { .news-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 50px; gap: 50px;
.px(192px); padding: 100px 192px 0;
.pt(100px);
.news-item { .news-item {
display: flex; display: flex;
align-items: center; align-items: center;
transition: @transition;
border-bottom: 0.3px solid #b5b5b5; border-bottom: 0.3px solid #b5b5b5;
.pb(50px); padding-bottom: 50px;
&:hover {
//box-shadow: @shadow-hover;
//transform: translateY(-2px);
}
.news-cover { .news-cover {
width: 500px; width: 500px;
@ -220,19 +313,12 @@ const resetPage = () => {
border-radius: @border-radius-md; border-radius: @border-radius-md;
margin-right: 30px; margin-right: 30px;
} }
.news-info { .news-info {
.news-wrap {
.flex-between();
}
width: 100%; width: 100%;
.pt(20px); padding-top: 20px;
text-align: left; text-align: left;
.news-label {
font-size: @font-size-sm;
color: @text-color-secondary;
.mt(13px);
.mb(76px);
}
.news-title { .news-title {
font-size: @font-size-lg; font-size: @font-size-lg;
font-weight: @font-weight-medium; font-weight: @font-weight-medium;
@ -240,231 +326,184 @@ const resetPage = () => {
margin-bottom: @space-sm; margin-bottom: @space-sm;
} }
.news-date { .news-label {
font-size: @font-size-sm; font-size: @font-size-sm;
color: @text-color-light; color: @text-color-secondary;
display: flex; // Flex margin-top: 13px;
align-items: center; // margin-bottom: 76px;
gap: 8px; }
.icon {
width: 18px; .news-wrap {
height: 18px; display: flex;
object-fit: contain; justify-content: space-between;
.view-btn {
padding: 6px 16px;
color: @primary-dark;
border-radius: 4px;
border: 1px solid var(--1060-ff, #1060ff);
cursor: pointer;
}
.news-date {
font-size: @font-size-sm;
color: @text-color-secondary;
display: flex;
align-items: center;
gap: 8px;
.icon {
width: 18px;
height: 18px;
object-fit: contain;
}
} }
} }
.view-btn { }
display: flex; }
padding: 6px 16px;
justify-content: center; //
align-items: center; @media (max-width: @tablet-breakpoint) {
gap: 10px; padding: @space-xl @space-md 0;
color: @primary-dark;
border-radius: 4px; .news-item {
border: 1px solid var(--1060-ff, #1060ff); flex-direction: column;
align-items: flex-start;
gap: @space-lg;
.news-cover {
width: 100%;
height: auto;
margin-right: 0;
margin-bottom: @space-lg;
}
.news-info .news-wrap {
flex-direction: column;
gap: @space-sm;
.view-btn {
align-self: flex-start;
}
.news-date {
order: -1;
}
}
}
}
@media (max-width: @mobile-breakpoint) {
padding: @space-lg @space-md 0;
gap: @space-xl;
.news-item {
padding-bottom: @space-lg;
.news-cover {
height: 180px;
}
.news-info .news-title {
font-size: @font-size-md;
}
.news-info .news-label {
margin-bottom: @space-md;
} }
} }
} }
} }
//// Less
//.vertical-center {
// display: flex; // Flex
// align-items: center; //
// gap: 8px; // margin
//
// .center-icon {
// width: 20px; //
// height: 20px;
// object-fit: contain; //
// }
//
// .center-text {
// font-size: @font-size-sm; //
// color: @text-color-light;
// }
//}
/* 分页样式 */ /* 分页样式 */
.pagination-wrap { .pagination-wrap {
margin-top: 50px; margin-top: 50px;
.pb(170px); padding-bottom: 170px;
display: flex; display: flex;
justify-content: center; justify-content: center;
:deep(.el-pager li.is-active) { :deep(.el-pager li.is-active) {
color: @primary-dark; color: @primary-dark;
} }
.el-pagination { .el-pagination {
.el-pagination__total { .el-pagination__total {
color: @text-color-light; color: @text-color-light;
} }
.el-pager li { .el-pager li.active {
&.active { background-color: @primary-color;
background-color: @primary-color; color: #fff;
color: #fff;
}
} }
.el-pagination__sizes { .el-pagination__sizes {
margin: 0 @space-md; margin: 0 @space-md;
} }
} }
}
/* 响应式适配(平板以下) */ //
//@media (max-width: @tablet-breakpoint) { @media (max-width: @mobile-breakpoint) {
// .news-item {
// flex-direction: column;
// align-items: flex-end;
// .news-cover {
// width: 100%;
// height: auto;
// margin: 0 0 @space-md 0;
// }
// }
//}
/* 响应式适配(细分平板和手机端) */
@media (max-width: @tablet-breakpoint) {
/* 平板端768px-1024px适配 */
.news-content {
padding: @space-xl @space-md; //
}
.news-tabs {
:deep(.el-tabs__item) {
margin-right: @space-md; //
font-size: @font-size-lg; //
}
:deep(.el-tabs__active-bar) {
bottom: -15px !important; // 线
}
}
.news-list {
.px(@space-lg); //
.pt(@space-xl); //
.news-item {
flex-direction: column;
align-items: flex-start; //
gap: @space-lg; //
.news-cover {
width: 100%;
height: auto;
margin-bottom: @space-lg; //
}
.news-info {
.news-title {
font-size: @font-size-md; //
}
.news-label {
.mb(@space-md); //
}
.news-wrap {
flex-direction: column; //
gap: @space-sm; //
.view-btn {
align-self: flex-start; //
}
.news-date {
order: -1; //
}
}
}
}
}
.pagination-wrap {
.el-pagination {
.el-pagination__sizes {
margin: 0 @space-sm; //
}
}
}
}
@media (max-width: @mobile-breakpoint) {
/* 手机端(<768px适配 */
.news-content {
padding: @space-lg; //
}
.news-tabs {
:deep(.el-tabs__item) {
margin-right: @space-sm; //
font-size: @font-size-md; //
}
:deep(.el-tabs__active-bar) {
bottom: -10px !important; // 线
}
}
.news-list {
.px(@space-md); //
gap: @space-xl; //
.news-item {
.pb(@space-lg); //
.news-cover {
height: 180px; //
}
.news-info {
.news-title {
font-size: @font-size-md; //
}
.news-label {
.mb(@space-md); //
}
.news-wrap {
.view-btn {
width: 80px; //
padding: @space-xs @space-sm; //
}
.news-date {
font-size: @font-size-xs; //
.icon {
width: 16px;
height: 16px;
}
}
}
}
}
}
/* 分页组件适配 */
.pagination-wrap {
.el-pagination { .el-pagination {
.el-pagination__total { .el-pagination__total {
font-size: @font-size-xs; // font-size: @font-size-xs;
} }
.el-pager li { .el-pager li {
width: 28px; width: 28px;
height: 28px; height: 28px;
line-height: 28px; line-height: 28px;
font-size: @font-size-xs; // font-size: @font-size-xs;
} }
.el-pagination__sizes { .el-pagination__sizes {
margin: 0 @space-xs; // margin: 0 @space-xs;
.el-input { .el-input {
font-size: @font-size-xs; // font-size: @font-size-xs;
} }
} }
} }
} }
} }
//
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100px 0; //
text-align: center;
.empty-img {
width: 300px; //
height: auto;
margin-bottom: 20px;
opacity: 0.8; //
}
.empty-text {
font-size: 20px;
color: @text-color-secondary;
.mt(10px);
}
//
@media (max-width: @mobile-breakpoint) {
.empty-img {
width: 200px; //
}
.empty-text {
font-size: @font-size-md;
}
}
}
.pagination {
margin-top: 20px;
text-align: right;
}
:deep(.el-pager li.is-active) {
background-color: #1665fc !important;
color: #ffffff !important;
}
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<section class="banner"> <section class="banner">
<div class="banner-bg"> <div v-motion-fade-visible class="banner-bg">
<!-- 替换为实际背景图路径 --> <!-- 替换为实际背景图路径 -->
<img src="https://images.health.ufutx.com/202506/13/25aeff6e015162aeb316983d8bd61558.png" alt="Banner背景" /> <img src="https://images.health.ufutx.com/202506/13/25aeff6e015162aeb316983d8bd61558.png" alt="Banner背景" />
</div> </div>
@ -66,6 +66,7 @@ const newsList = [
.banner-bg { .banner-bg {
width: 100%; width: 100%;
height: 830px;
overflow: hidden; overflow: hidden;
img { img {

View File

@ -2,43 +2,29 @@
"extends": "@vue/tsconfig/tsconfig.dom.json", "extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": { "compilerOptions": {
"baseUrl": "./", "baseUrl": "./",
"lib": [ "lib": ["ES2022", "DOM"],
"ES2022",
"DOM"
],
// DOM
"paths": { "paths": {
"@/*": [ "@/*": ["src/*"]
"src/*"
]
//
}, },
"jsx": "preserve", "jsx": "preserve",
// Vue JSX "types": ["vite/client", "vue", "vue-i18n"],
"types": [ "incremental": true,
"vite/client",
"vue","vue-i18n"
],
"incremental": true, //
// Vue
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
// "erasableSyntaxOnly": true, //
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
// "noUncheckedSideEffectImports": true
}, },
"include": [ "include": [
//
"auto-imports.d.ts",
//
"components.d.ts",
"src/**/*.ts", "src/**/*.ts",
"src/**/*.d.ts", "src/**/*.d.ts",
"src/**/*.tsx", "src/**/*.tsx",
"src/**/*.vue", "src/**/*.vue",
// Vue
"vite.config.ts", "vite.config.ts",
// Vite
"src/main.ts" "src/main.ts"
//
] ]
} }

View File

@ -8,10 +8,12 @@ import Components from 'unplugin-vue-components/vite'
// 引入 ElementPlus 相关解析器 // 引入 ElementPlus 相关解析器
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// import ElementPlus from 'unplugin-element-plus/vite'
import legacy from '@vitejs/plugin-legacy' import legacy from '@vitejs/plugin-legacy'
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
// ElementPlus(),
vue(), vue(),
// 仅在生产环境启用 legacy 插件 // 仅在生产环境启用 legacy 插件
@ -29,19 +31,31 @@ export default defineConfig({
// 自动导入 Vue 相关函数,以及 ElementPlus 的相关函数等 // 自动导入 Vue 相关函数,以及 ElementPlus 的相关函数等
resolvers: [ resolvers: [
ElementPlusResolver({ ElementPlusResolver({
importStyle: 'sass', importStyle: false, // 关闭自动导入样式(手动引入 index.css
directives: true // 导入指令 directives: true // 导入 Element Plus 指令(如 v-loading
}) })
] // ElementPlusResolver({
// importStyle: 'sass',
// directives: true // 导入指令
// })
],
dts: true // 生成 auto-imports.d.ts让 ESLint 识别自动导入的 API
}), }),
Components({ Components({
// 自动导入 ElementPlus 的组件
resolvers: [ resolvers: [
ElementPlusResolver({ ElementPlusResolver({
importStyle: 'sass', importStyle: false // 同上,避免与手动引入的样式冲突
directives: true
}) })
], ], // 自动解析 Element Plus 组件
dts: true, // 生成 components.d.ts关键让 ESLint 识别组件)
include: [/\.vue$/, /\.vue\?vue/, /\.tsx$/], // 确保覆盖所有组件文件
// 自动导入 ElementPlus 的组件
// resolvers: [
// ElementPlusResolver({
// importStyle: 'sass',
// directives: true
// })
// ],
dirs: ['src/components'] // 自动扫描组件目录 dirs: ['src/components'] // 自动扫描组件目录
}), }),
// 开发环境暂时禁用图片压缩插件 // 开发环境暂时禁用图片压缩插件
@ -82,13 +96,13 @@ export default defineConfig({
// external: ['element-plus', 'element-plus/dist/index.css'], // 排除 Element Plus // external: ['element-plus', 'element-plus/dist/index.css'], // 排除 Element Plus
// external: ['element-plus'], // 排除 Element Plus 从打包中 // external: ['element-plus'], // 排除 Element Plus 从打包中
output: { output: {
manualChunks: undefined // 取消手动分割,使用 Vite 自动策略 // manualChunks: undefined // 取消手动分割,使用 Vite 自动策略
// manualChunks(id) { manualChunks(id) {
// if (id.includes('node_modules')) { if (id.includes('node_modules')) {
// 将第三方库单独分包(如 axios、vue 等) // 将第三方库单独分包(如 axios、vue 等)
// return id.split('node_modules/')[1].split('/')[0] return id.split('node_modules/')[1].split('/')[0]
// } }
// } }
} }
} }
}, },