feat: 增加AI模块功能

This commit is contained in:
2026-09-03 13:05:31 +08:00
parent 389db4f279
commit 7ba7dbc942
10 changed files with 262 additions and 37 deletions

View File

@@ -5,14 +5,15 @@
:safe-area-inset-bottom="true"
custom-style="border-radius: 32rpx 32rpx 0 0; padding: 40rpx 32rpx"
>
<view class="flex flex-col max-h-[70vh] overflow-y-auto">
<view class="mb-4 text-center text-base text-gray-700 font-semibold">
编辑个人信息
</view>
<wd-form :model="profileStore.profileForm" layout="horizontal" title-width="80" size="large" border>
<wd-form-item title="昵称" prop="name">
<wd-form-item title="昵称">
<wd-input v-model="profileStore.profileForm.name" placeholder="请输入昵称" />
</wd-form-item>
<wd-form-item title="性别" prop="gender">
<wd-form-item title="性别">
<wd-radio-group v-model="profileStore.profileForm.gender" type="button">
<wd-radio value="male">
@@ -22,12 +23,58 @@
</wd-radio>
</wd-radio-group>
</wd-form-item>
<wd-form-item title="身高" prop="height">
<wd-form-item title="出生年份">
<wd-input-number
v-model="profileStore.profileForm.birthYear"
:min="1"
:max="3000"
:step="1"
/>
</wd-form-item>
<wd-form-item title="身高">
<view class="flex items-center gap-2">
<wd-input v-model.number="profileStore.profileForm.height" type="number" placeholder="请输入身高" />
<wd-input-number
v-model="profileStore.profileForm.height"
:min="1"
:max="300"
:step="1"
/>
<text class="shrink-0 text-sm text-gray-400">cm</text>
</view>
</wd-form-item>
<wd-form-item title="职业性质">
<wd-radio-group v-model="profileStore.profileForm.occupation" type="button">
<wd-radio
v-for="item in occupationColumns"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</wd-radio>
</wd-radio-group>
</wd-form-item>
<wd-form-item title="疾病史">
<wd-checkbox-group v-model="profileStore.profileForm.condition" type="button">
<wd-checkbox
v-for="item in conditionColumns"
:key="item.value"
:name="item.value"
>
{{ item.label }}
</wd-checkbox>
</wd-checkbox-group>
</wd-form-item>
<wd-form-item title="健康目标">
<wd-radio-group v-model="profileStore.profileForm.goal" type="button">
<wd-radio
v-for="item in goalColumns"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</wd-radio>
</wd-radio-group>
</wd-form-item>
</wd-form>
<view class="flex w-full items-center justify-evenly">
@@ -38,6 +85,7 @@
保存
</wd-button>
</view>
</view>
</wd-popup>
</template>
@@ -47,6 +95,26 @@ import { useProfileStore } from '@/store/profile'
const profileStore = useProfileStore()
const occupationColumns = [
{ label: '久坐少动', value: '久坐少动' },
{ label: '体力活动', value: '体力活动' },
{ label: '作息紊乱', value: '作息紊乱' }
]
const conditionColumns = [
{ label: '高血压', value: '高血压' },
{ label: '糖尿病', value: '糖尿病' },
{ label: '高血脂', value: '高血脂' },
{ label: '脂肪肝', value: '脂肪肝' }
]
const goalColumns = [
{ label: '控制体重', value: '控制体重' },
{ label: '均衡饮食', value: '均衡饮食' },
{ label: '改善睡眠', value: '改善睡眠' },
{ label: '缓解压力', value: '缓解压力' }
]
function cancelClick() {
profileStore.showProfilePopup = false
}

View File

@@ -0,0 +1,35 @@
<template>
<wd-popup
v-model="recordStore.showAIResultPopup"
position="bottom"
:safe-area-inset-bottom="true"
:close-on-click-modal="false"
custom-style="border-radius: 32rpx 32rpx 0 0; padding: 40rpx 32rpx"
>
<view class="flex flex-col align-center gap-2 max-h-[70vh] overflow-y-auto">
<view class="flex items-center justify-between mb-4">
<text class="text-lg font-bold">AI 健康分析</text>
<wd-icon name="close" size="20px" @click="recordStore.showAIResultPopup = false" />
</view>
<view class="bg-blue-50 rounded-lg">
<text class="text-gray-800 leading-relaxed whitespace-pre-wrap">{{ recordStore.aiResult }}</text>
</view>
<view class="flex w-full items-center justify-evenly">
<wd-button type="info" @click="recordStore.showAIResultPopup = false">
取消
</wd-button>
<wd-button type="primary">
分享
</wd-button>
</view>
</view>
</wd-popup>
</template>
<script setup lang="ts">
import { useRecordStore } from '@/store/record'
const recordStore = useRecordStore()
</script>

View File

@@ -80,7 +80,7 @@
<template #title>
<view class="wd-card__title-text flex items-center justify-between">
<text>🔥 健康资讯</text>
<text class="text-11px text-[#999]">🔄 每日更新</text>
<text class="text-11px text-[#999]">每日更新</text>
</view>
</template>
@@ -111,7 +111,7 @@
<template #title>
<view class="wd-card__title-text flex items-center justify-between">
<text>🌿 养生食谱</text>
<text class="text-11px text-[#999]">🔄 每日更新</text>
<text class="text-11px text-[#999]">每日更新</text>
</view>
</template>

View File

@@ -1,6 +1,10 @@
<template>
<wd-config-provider :theme-vars="getTheme(appStore.themeColor)">
<view class="min-h-screen flex flex-col" :style="getThemeStyle[appStore.themeColor]">
<view class="min-h-screen flex flex-col position" :style="getThemeStyle[appStore.themeColor]">
<liu-drag-button :background="getThemePrimaryColor[appStore.themeColor]" @clickBtn="AIClick">
<wd-text color="#fff" size="18px" text="AI"/>
</liu-drag-button>
<wd-segmented v-model:value="appStore.viewRecordType" :options="viewTypeSegmentList"
@change="handleChangeSegment"
custom-style="align-self: center; width: 60%; margin: 10px;">
@@ -81,6 +85,16 @@
:max-date="Date.now()"
@confirm="onDateConfirm"
/>
<wd-dialog />
<wd-overlay :show="recordStore.isAnalyzing">
<view class="flex flex-col items-center justify-center h-full">
<wd-loading type="spinner" color="#FFF" >AI 正在分析中...</wd-loading>
</view>
</wd-overlay>
<ai-result-popup/>
</view>
</wd-config-provider>
</template>
@@ -101,6 +115,9 @@ import type { ICalendarSelect, IGroupHealthRecord, IHealthRecord } from "@/type/
import { useAppStore } from "@/store/app";
import { getTheme, getThemePrimaryColor, getThemeStyle } from "@/utils/themes";
import { share } from '@/utils/share'
import { useDialog } from '@wot-ui/ui'
import AiResultPopup from "@/components/Record/AiResultPopup.vue";
import { useRecordStore } from '@/store/record'
const { onShareAppMessage } = share()
onShareAppMessage()
@@ -115,6 +132,7 @@ const calendarSelect = ref<ICalendarSelect[]>([])
const selectDate = ref<string>(getDateStr(Date.now()))
const appStore = useAppStore()
const recordStore = useRecordStore()
interface SegmentOption {
value: string
@@ -140,6 +158,7 @@ const displayDate = computed(() => {
})
function loadListRecords() {
selectMonth.value = getMonthStr(currentDate.value)
const all = getMonthRecords(getMonthStr(currentDate.value), currentRecordTypeSegment.value)
records.value = all
grouped.value = groupByDate(all)
@@ -226,6 +245,68 @@ function deleteClick(item: IHealthRecord) {
},
})
}
const dialog = useDialog()
const AIClick = () => {
dialog
.confirm({
title: `开始分析${selectMonth.value}记录数据,本次分析将消耗 100 积分,是否继续?`,
})
.then(async () => {
recordStore.isAnalyzing = true
recordStore.aiResult = ''
// 模拟请求延迟
await new Promise(r => setTimeout(r, 2000))
recordStore.isAnalyzing = false
// 模拟返回结果
recordStore.aiResult = generateMockResult()
recordStore.showAIResultPopup = true
})
.catch(() => {
console.log('点击了取消')
})
}
function generateMockResult() {
return `📊 ${selectMonth.value}健康分析
• 本月共记录 23 天,覆盖率 74%
• 平均睡眠 6.5 小时,低于目标 7.5 小时
• 血压整体平稳,但有 3 天偏高(周一、周三)
• 建议:增加周末睡眠时间,注意周一血压波动
• 本月共记录 23 天,覆盖率 74%
• 平均睡眠 6.5 小时,低于目标 7.5 小时
• 血压整体平稳,但有 3 天偏高(周一、周三)
• 建议:增加周末睡眠时间,注意周一血压波动
• 本月共记录 23 天,覆盖率 74%
• 平均睡眠 6.5 小时,低于目标 7.5 小时
• 血压整体平稳,但有 3 天偏高(周一、周三)
• 建议:增加周末睡眠时间,注意周一血压波动
• 本月共记录 23 天,覆盖率 74%
• 平均睡眠 6.5 小时,低于目标 7.5 小时
• 血压整体平稳,但有 3 天偏高(周一、周三)
• 建议:增加周末睡眠时间,注意周一血压波动
• 本月共记录 23 天,覆盖率 74%
• 平均睡眠 6.5 小时,低于目标 7.5 小时
• 血压整体平稳,但有 3 天偏高(周一、周三)
• 建议:增加周末睡眠时间,注意周一血压波动
• 本月共记录 23 天,覆盖率 74%
• 平均睡眠 6.5 小时,低于目标 7.5 小时
• 血压整体平稳,但有 3 天偏高(周一、周三)
• 建议:增加周末睡眠时间,注意周一血压波动
• 本月共记录 23 天,覆盖率 74%
• 平均睡眠 6.5 小时,低于目标 7.5 小时
• 血压整体平稳,但有 3 天偏高(周一、周三)
• 建议:增加周末睡眠时间,注意周一血压波动
• 本月共记录 23 天,覆盖率 74%
• 平均睡眠 6.5 小时,低于目标 7.5 小时
• 血压整体平稳,但有 3 天偏高(周一、周三)
• 建议:增加周末睡眠时间,注意周一血压波动
`
}
</script>
<style scoped lang="scss">

View File

@@ -443,3 +443,10 @@ function handleChangeBloodSugar() {
getBloodSugarChartData()
}
</script>
<style scoped lang="scss">
:deep(.wd-segmented__item) {
font-size: 12px;
line-height: 20px;
}
</style>

View File

@@ -11,7 +11,7 @@
{{ profile.name || '未设置姓名' }}
</text>
<text class="mt-0.5 text-xs text-gray-400">
{{ genderText }} · {{ profile.height ? `${profile.height} cm` : '身高未填' }}
{{ genderText }} · {{ ageText }} · {{ heightText }}
</text>
</view>
<view class="ml-auto">
@@ -109,6 +109,21 @@ const genderText = computed(() => {
return '性别未填'
})
const ageText = computed(() => {
const year = profile.value.birthYear
if (!year) return '年龄未填'
const age = new Date().getFullYear() - year
return `${age}`
})
const heightText = computed(() => {
if (profile.value.height)
return `${profile.value.height} cm`
return '身高未填'
})
onShow(() => {
appStore.points = getPoints()
appStore.recordCount = getRecordCount()
@@ -121,7 +136,8 @@ watch(() => profileStore.isRefresh, () => {
function openProfile() {
Object.assign(profileStore.profileForm, profile.value)
profileStore.profileForm.height = 170
profileStore.profileForm.birthYear = 1990
profileStore.showProfilePopup = true
}

View File

@@ -16,7 +16,11 @@ export const useProfileStore = defineStore('profile', {
showAboutPopup: false,
profileForm: {
gender: '',
height: 0,
height: 170,
birthYear: 1990,
occupation: '',
condition: [],
goal: '',
name: '',
} as IProfile,
themeColor: 'purple',

View File

@@ -50,6 +50,9 @@ export const useRecordStore = defineStore('record', {
time: Date.now(),
},
isRecord: false,
isAnalyzing: false,
showAIResultPopup: false,
aiResult: ''
}),
actions: {
openWeight() {

View File

@@ -1,7 +1,14 @@
export interface IProfile {
name: string
gender: 'male' | 'female' | ''
birthYear: number | null
height: number | null
// 职业性质
occupation: string
// 疾病史
condition: string[]
// 健康目标
goal: string
}
export interface IOverview {

View File

@@ -11,6 +11,10 @@ function getEmptyProfile(): IProfile {
return {
gender: '',
height: null,
birthYear: null,
occupation: '',
condition: [],
goal: '',
name: '',
}
}