From dec3dfdfe34c3950d92cef1087e91afe4a9ed1f1 Mon Sep 17 00:00:00 2001 From: fuenmao <980740792@qq.com> Date: Tue, 22 Sep 2026 15:03:13 +0800 Subject: [PATCH] 1111 --- index.html | 2 +- src/components/AppTabBar.vue | 133 ++++ src/router.ts | 8 +- src/services/health.ts | 81 +- src/styles.css | 201 ++++- src/views/AssessmentView.vue | 1281 +++++++++++++++++++++++++++++++ src/views/DashboardView.vue | 70 ++ src/views/DeviceControlView.vue | 931 ++++++++++++++++++++++ src/views/ProfileView.vue | 638 +++++++++++++++ 9 files changed, 3341 insertions(+), 4 deletions(-) create mode 100644 src/components/AppTabBar.vue create mode 100644 src/views/AssessmentView.vue create mode 100644 src/views/DeviceControlView.vue create mode 100644 src/views/ProfileView.vue diff --git a/index.html b/index.html index f06247f..93c0043 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + 常青树健康管理 diff --git a/src/components/AppTabBar.vue b/src/components/AppTabBar.vue new file mode 100644 index 0000000..ef46279 --- /dev/null +++ b/src/components/AppTabBar.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/router.ts b/src/router.ts index 555768c..ac7105a 100644 --- a/src/router.ts +++ b/src/router.ts @@ -2,6 +2,9 @@ import { createRouter, createWebHistory } from 'vue-router' import AuthView from './views/AuthView.vue' import OnboardingView from './views/OnboardingView.vue' import DashboardView from './views/DashboardView.vue' +import AssessmentView from './views/AssessmentView.vue' +import ProfileView from './views/ProfileView.vue' +import DeviceControlView from './views/DeviceControlView.vue' import { hasToken } from './services/auth' const router = createRouter({ @@ -10,7 +13,10 @@ const router = createRouter({ { path: '/', redirect: '/dashboard' }, { path: '/auth', component: AuthView, meta: { guest: true } }, { path: '/onboarding', component: OnboardingView }, - { path: '/dashboard', component: DashboardView } + { path: '/dashboard', component: DashboardView }, + { path: '/assessment', component: AssessmentView }, + { path: '/profile', component: ProfileView }, + { path: '/device-control', component: DeviceControlView } ] }) diff --git a/src/services/health.ts b/src/services/health.ts index 640beac..b6d28e8 100644 --- a/src/services/health.ts +++ b/src/services/health.ts @@ -40,11 +40,63 @@ export type HealthPlan = { items: PlanItem[] } +export type PersonalizedSceneConfig = { + targetHumidity: number + dehumidifierMode: string + fanLevel: string + enableUv: boolean + lightBrightnessPercent: number + lightColorTemperatureKelvin: number + mattressTargetTemp: number + mattressLeftFirmness: number + mattressRightFirmness: number + mattressHeatingLevel: number + mattressMode: string + enableFragrance: boolean + fragranceLevel: number + enableSpeakerSleepMode: boolean + speakerAudioMode: string + speakerVolumePercent: number +} + +export type HealthAssessmentAnswers = { + sleepDuration: string + fallingAsleepSpeed: string + temperaturePreference: string + nightAwakening: string + mattressPreference: string + exerciseFrequency: string + muscleFatigueArea: string + activeTimeOfDay: string + mealRegularity: string + flavorPreference: string + lateNightEating: string + dailyWaterIntake: string +} + +export type SaveHealthAssessmentInput = { + basicInfo: HealthProfile + answers: HealthAssessmentAnswers +} + +export type HealthAssessmentOutput = { + hasCompletedAssessment: boolean + basicInfo: HealthProfile + answers: HealthAssessmentAnswers + sceneConfig: PersonalizedSceneConfig + bmi?: number | null + bmiStatus: string +} + export type Dashboard = { user: HealthUser profile?: HealthProfile selectedModules: string[] currentPlan?: HealthPlan + hasCompletedAssessment: boolean + bmi?: number | null + bmiStatus?: string + sceneConfig?: PersonalizedSceneConfig } export type HealthDeviceCategory = @@ -131,6 +183,14 @@ export type HomeAssistantStatus = { sceneDevices: HomeAssistantSceneDevicesSummary environment: HomeAssistantEnvironmentData scenePresets?: HomeAssistantScenePreset[] + controlParameters?: Record +} + +export type HomeAssistantParameterState = { + entityId: string + state: string + available: boolean + attributes?: Record } export type HomeAssistantSceneAction = { @@ -150,6 +210,20 @@ export type HomeAssistantSceneResult = { actions: HomeAssistantSceneAction[] } +export type HomeAssistantSetParameterInput = { + deviceId: string + entityId?: string + parameterType: string + value: unknown +} + +export type HomeAssistantSetParameterOutput = { + success: boolean + deviceId: string + parameterType: string + message: string +} + export const healthApi = { register: (payload: { phone: string; password: string; displayName: string }) => http.post('/api/health/auth/register', payload), @@ -161,6 +235,9 @@ export const healthApi = { saveModules: (moduleCodes: string[]) => http.put('/api/health/modules', { moduleCodes }), generatePlan: () => http.post('/api/health/plans/generate'), + getAssessment: () => http.get('/api/health/assessment'), + saveAssessment: (payload: SaveHealthAssessmentInput) => + http.post('/api/health/assessment', payload), homeAssistantStatus: () => http.get('/api/health/home-assistant/status'), executeSleepScene: () => @@ -168,5 +245,7 @@ export const healthApi = { executeAllOffScene: () => http.post('/api/health/home-assistant/scenes/all-off'), switchDevice: (payload: { deviceId: string; turnOn: boolean; entityId?: string }) => - http.post('/api/health/home-assistant/devices/switch', payload) + http.post('/api/health/home-assistant/devices/switch', payload), + setParameter: (payload: HomeAssistantSetParameterInput) => + http.post('/api/health/home-assistant/devices/set-parameter', payload) } diff --git a/src/styles.css b/src/styles.css index 1e8508b..bb17ef0 100644 --- a/src/styles.css +++ b/src/styles.css @@ -11,7 +11,15 @@ } * { box-sizing: border-box; } -body { margin: 0; min-width: 320px; min-height: 100vh; background: #f2f0e9; } +body { margin: 0; min-width: 320px; min-height: 100vh; background: #e8e6df; } +#app { + max-width: 480px; + margin: 0 auto; + min-height: 100vh; + position: relative; + background: #f2f0e9; + box-shadow: 0 0 30px rgba(0, 0, 0, 0.08); +} button, input, textarea { font: inherit; } button { cursor: pointer; } @@ -772,3 +780,194 @@ button { cursor: pointer; } .scene-actions { width: 100%; margin-top: 6px; } .scene-actions button { flex: 1; } } + +.dashboard-page { + padding-bottom: 84px !important; +} + +/* 评估未填提醒卡片 */ +.assessment-guide-banner { + background: linear-gradient(135deg, #b37318, #92580c); + color: #fff; + border-radius: 18px; + padding: 16px; + margin-bottom: 14px; + box-shadow: 0 6px 18px rgba(179, 115, 24, 0.22); + cursor: pointer; + position: relative; + overflow: hidden; +} +.assessment-guide-banner::after { + content: ""; + position: absolute; + right: -20px; + bottom: -20px; + width: 100px; + height: 100px; + background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, transparent 70%); + border-radius: 50%; +} +.guide-badge { + display: inline-block; + background: rgba(255, 255, 255, 0.22); + color: #fff; + font-size: 10px; + font-weight: 700; + padding: 2px 8px; + border-radius: 10px; + margin-bottom: 8px; +} +.guide-body { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; +} +.guide-text h3 { + margin: 0 0 4px; + font-size: 15px; + font-weight: 700; +} +.guide-text p { + margin: 0; + font-size: 12px; + color: rgba(255, 255, 255, 0.88); + line-height: 1.4; +} +.guide-btn { + white-space: nowrap; + padding: 8px 14px; + border-radius: 16px; + border: 0; + background: #ffffff; + color: #92580c; + font-size: 12px; + font-weight: 700; + cursor: pointer; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); +} + +/* 个性化舒睡启动参数卡片 */ +.personalized-startup-card { + background: #ffffff; + border: 1px solid rgba(12, 80, 69, 0.12); + border-radius: 20px; + padding: 16px; + margin-bottom: 14px; + box-shadow: 0 4px 16px rgba(18, 63, 55, 0.04); +} +.customized-tag { + font-size: 11px; + color: #0c5045; + background: #eef6f3; + padding: 2px 8px; + border-radius: 8px; + font-weight: 600; +} +.startup-card-desc { + margin: 4px 0 12px; + font-size: 12px; + color: #6b7d76; + line-height: 1.5; +} +.startup-params-tags { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 14px; +} +.startup-param-badge { + background: #f7f6f2; + border: 1px solid rgba(18, 63, 55, 0.08); + border-radius: 10px; + padding: 8px 10px; +} +.startup-param-badge .lbl { + display: block; + font-size: 10px; + color: #849690; + margin-bottom: 2px; +} +.startup-param-badge strong { + font-size: 12px; + color: #173d36; +} +.detail-entry-bar { + display: flex; + align-items: center; + justify-content: space-between; + background: #f8f7f2; + border: 1px solid rgba(18, 63, 55, 0.08); + border-radius: 12px; + padding: 10px 12px; + margin-top: 12px; + cursor: pointer; + transition: all 0.2s; +} +.detail-entry-bar:active { + background: #f0eee7; +} +.entry-left { + display: flex; + align-items: center; + gap: 10px; +} +.entry-left .icon { + font-size: 18px; +} +.entry-left strong { + display: block; + font-size: 12px; + color: #173d36; +} +.entry-left small { + display: block; + font-size: 11px; + color: #82928c; +} +.detail-entry-bar .arrow { + color: #a0ada7; + font-size: 14px; +} + +/* 7 台设备入口条 */ +.all-control-banner { + display: flex; + align-items: center; + justify-content: space-between; + background: linear-gradient(135deg, #edf6f3, #e5f0ec); + border: 1px solid rgba(12, 80, 69, 0.16); + border-radius: 14px; + padding: 12px 14px; + margin-top: 14px; + cursor: pointer; + transition: all 0.2s; +} +.all-control-banner:active { + background: #dbeef7; +} +.ac-text { + display: flex; + align-items: center; + gap: 10px; +} +.ac-icon { + font-size: 20px; +} +.ac-text strong { + display: block; + font-size: 13px; + color: #0c5045; + font-weight: 700; +} +.ac-text small { + display: block; + font-size: 11px; + color: #55756d; + margin-top: 2px; +} +.ac-arrow { + font-size: 16px; + color: #0c5045; + font-weight: 700; +} diff --git a/src/views/AssessmentView.vue b/src/views/AssessmentView.vue new file mode 100644 index 0000000..adc7ccb --- /dev/null +++ b/src/views/AssessmentView.vue @@ -0,0 +1,1281 @@ + + + + + diff --git a/src/views/DashboardView.vue b/src/views/DashboardView.vue index c9241bc..086aa2b 100644 --- a/src/views/DashboardView.vue +++ b/src/views/DashboardView.vue @@ -2,6 +2,7 @@ import { computed, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import { Button as VanButton, showToast } from 'vant' +import AppTabBar from '../components/AppTabBar.vue' import { clearTokens } from '../services/auth' import { healthApi, @@ -278,6 +279,61 @@ onMounted(load)
正在读取今日健康方案…
+ + + diff --git a/src/views/DeviceControlView.vue b/src/views/DeviceControlView.vue new file mode 100644 index 0000000..a8189d8 --- /dev/null +++ b/src/views/DeviceControlView.vue @@ -0,0 +1,931 @@ + + + + + diff --git a/src/views/ProfileView.vue b/src/views/ProfileView.vue new file mode 100644 index 0000000..d8ff095 --- /dev/null +++ b/src/views/ProfileView.vue @@ -0,0 +1,638 @@ + + + + +