feat: 增加AI模块功能
This commit is contained in:
@@ -5,38 +5,86 @@
|
|||||||
:safe-area-inset-bottom="true"
|
:safe-area-inset-bottom="true"
|
||||||
custom-style="border-radius: 32rpx 32rpx 0 0; padding: 40rpx 32rpx"
|
custom-style="border-radius: 32rpx 32rpx 0 0; padding: 40rpx 32rpx"
|
||||||
>
|
>
|
||||||
<view class="mb-4 text-center text-base text-gray-700 font-semibold">
|
<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>
|
</view>
|
||||||
<wd-form-item title="昵称" prop="name">
|
<wd-form :model="profileStore.profileForm" layout="horizontal" title-width="80" size="large" border>
|
||||||
<wd-input v-model="profileStore.profileForm.name" placeholder="请输入昵称" />
|
<wd-form-item title="昵称">
|
||||||
</wd-form-item>
|
<wd-input v-model="profileStore.profileForm.name" placeholder="请输入昵称" />
|
||||||
<wd-form-item title="性别" prop="gender">
|
</wd-form-item>
|
||||||
<wd-radio-group v-model="profileStore.profileForm.gender" type="button">
|
<wd-form-item title="性别">
|
||||||
<wd-radio value="male">
|
<wd-radio-group v-model="profileStore.profileForm.gender" type="button">
|
||||||
男
|
<wd-radio value="male">
|
||||||
</wd-radio>
|
男
|
||||||
<wd-radio value="female">
|
</wd-radio>
|
||||||
女
|
<wd-radio value="female">
|
||||||
</wd-radio>
|
女
|
||||||
</wd-radio-group>
|
</wd-radio>
|
||||||
</wd-form-item>
|
</wd-radio-group>
|
||||||
<wd-form-item title="身高" prop="height">
|
</wd-form-item>
|
||||||
<view class="flex items-center gap-2">
|
<wd-form-item title="出生年份">
|
||||||
<wd-input v-model.number="profileStore.profileForm.height" type="number" placeholder="请输入身高" />
|
<wd-input-number
|
||||||
<text class="shrink-0 text-sm text-gray-400">cm</text>
|
v-model="profileStore.profileForm.birthYear"
|
||||||
</view>
|
:min="1"
|
||||||
</wd-form-item>
|
:max="3000"
|
||||||
</wd-form>
|
:step="1"
|
||||||
|
/>
|
||||||
|
</wd-form-item>
|
||||||
|
<wd-form-item title="身高">
|
||||||
|
<view class="flex items-center gap-2">
|
||||||
|
<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">
|
<view class="flex w-full items-center justify-evenly">
|
||||||
<wd-button type="info" @click="cancelClick">
|
<wd-button type="info" @click="cancelClick">
|
||||||
取消
|
取消
|
||||||
</wd-button>
|
</wd-button>
|
||||||
<wd-button type="primary" @click="saveClick">
|
<wd-button type="primary" @click="saveClick">
|
||||||
保存
|
保存
|
||||||
</wd-button>
|
</wd-button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</wd-popup>
|
</wd-popup>
|
||||||
</template>
|
</template>
|
||||||
@@ -47,6 +95,26 @@ import { useProfileStore } from '@/store/profile'
|
|||||||
|
|
||||||
const profileStore = useProfileStore()
|
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() {
|
function cancelClick() {
|
||||||
profileStore.showProfilePopup = false
|
profileStore.showProfilePopup = false
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/components/Record/AiResultPopup.vue
Normal file
35
src/components/Record/AiResultPopup.vue
Normal 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>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<template #title>
|
<template #title>
|
||||||
<view class="wd-card__title-text flex items-center justify-between">
|
<view class="wd-card__title-text flex items-center justify-between">
|
||||||
<text>🔥 健康资讯</text>
|
<text>🔥 健康资讯</text>
|
||||||
<text class="text-11px text-[#999]">🔄 每日更新</text>
|
<text class="text-11px text-[#999]">每日更新</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
<template #title>
|
<template #title>
|
||||||
<view class="wd-card__title-text flex items-center justify-between">
|
<view class="wd-card__title-text flex items-center justify-between">
|
||||||
<text>🌿 养生食谱</text>
|
<text>🌿 养生食谱</text>
|
||||||
<text class="text-11px text-[#999]">🔄 每日更新</text>
|
<text class="text-11px text-[#999]">每日更新</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<wd-config-provider :theme-vars="getTheme(appStore.themeColor)">
|
<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"
|
<wd-segmented v-model:value="appStore.viewRecordType" :options="viewTypeSegmentList"
|
||||||
@change="handleChangeSegment"
|
@change="handleChangeSegment"
|
||||||
custom-style="align-self: center; width: 60%; margin: 10px;">
|
custom-style="align-self: center; width: 60%; margin: 10px;">
|
||||||
@@ -81,6 +85,16 @@
|
|||||||
:max-date="Date.now()"
|
:max-date="Date.now()"
|
||||||
@confirm="onDateConfirm"
|
@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>
|
</view>
|
||||||
</wd-config-provider>
|
</wd-config-provider>
|
||||||
</template>
|
</template>
|
||||||
@@ -101,6 +115,9 @@ import type { ICalendarSelect, IGroupHealthRecord, IHealthRecord } from "@/type/
|
|||||||
import { useAppStore } from "@/store/app";
|
import { useAppStore } from "@/store/app";
|
||||||
import { getTheme, getThemePrimaryColor, getThemeStyle } from "@/utils/themes";
|
import { getTheme, getThemePrimaryColor, getThemeStyle } from "@/utils/themes";
|
||||||
import { share } from '@/utils/share'
|
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()
|
const { onShareAppMessage } = share()
|
||||||
onShareAppMessage()
|
onShareAppMessage()
|
||||||
@@ -115,6 +132,7 @@ const calendarSelect = ref<ICalendarSelect[]>([])
|
|||||||
const selectDate = ref<string>(getDateStr(Date.now()))
|
const selectDate = ref<string>(getDateStr(Date.now()))
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
const recordStore = useRecordStore()
|
||||||
|
|
||||||
interface SegmentOption {
|
interface SegmentOption {
|
||||||
value: string
|
value: string
|
||||||
@@ -140,6 +158,7 @@ const displayDate = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function loadListRecords() {
|
function loadListRecords() {
|
||||||
|
selectMonth.value = getMonthStr(currentDate.value)
|
||||||
const all = getMonthRecords(getMonthStr(currentDate.value), currentRecordTypeSegment.value)
|
const all = getMonthRecords(getMonthStr(currentDate.value), currentRecordTypeSegment.value)
|
||||||
records.value = all
|
records.value = all
|
||||||
grouped.value = groupByDate(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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -443,3 +443,10 @@ function handleChangeBloodSugar() {
|
|||||||
getBloodSugarChartData()
|
getBloodSugarChartData()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.wd-segmented__item) {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{{ profile.name || '未设置姓名' }}
|
{{ profile.name || '未设置姓名' }}
|
||||||
</text>
|
</text>
|
||||||
<text class="mt-0.5 text-xs text-gray-400">
|
<text class="mt-0.5 text-xs text-gray-400">
|
||||||
{{ genderText }} · {{ profile.height ? `${profile.height} cm` : '身高未填' }}
|
{{ genderText }} · {{ ageText }} · {{ heightText }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="ml-auto">
|
<view class="ml-auto">
|
||||||
@@ -109,6 +109,21 @@ const genderText = computed(() => {
|
|||||||
return '性别未填'
|
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(() => {
|
onShow(() => {
|
||||||
appStore.points = getPoints()
|
appStore.points = getPoints()
|
||||||
appStore.recordCount = getRecordCount()
|
appStore.recordCount = getRecordCount()
|
||||||
@@ -121,7 +136,8 @@ watch(() => profileStore.isRefresh, () => {
|
|||||||
|
|
||||||
function openProfile() {
|
function openProfile() {
|
||||||
Object.assign(profileStore.profileForm, profile.value)
|
Object.assign(profileStore.profileForm, profile.value)
|
||||||
|
profileStore.profileForm.height = 170
|
||||||
|
profileStore.profileForm.birthYear = 1990
|
||||||
profileStore.showProfilePopup = true
|
profileStore.showProfilePopup = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ export const useProfileStore = defineStore('profile', {
|
|||||||
showAboutPopup: false,
|
showAboutPopup: false,
|
||||||
profileForm: {
|
profileForm: {
|
||||||
gender: '',
|
gender: '',
|
||||||
height: 0,
|
height: 170,
|
||||||
|
birthYear: 1990,
|
||||||
|
occupation: '',
|
||||||
|
condition: [],
|
||||||
|
goal: '',
|
||||||
name: '',
|
name: '',
|
||||||
} as IProfile,
|
} as IProfile,
|
||||||
themeColor: 'purple',
|
themeColor: 'purple',
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ export const useRecordStore = defineStore('record', {
|
|||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
},
|
},
|
||||||
isRecord: false,
|
isRecord: false,
|
||||||
|
isAnalyzing: false,
|
||||||
|
showAIResultPopup: false,
|
||||||
|
aiResult: ''
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
openWeight() {
|
openWeight() {
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
export interface IProfile {
|
export interface IProfile {
|
||||||
name: string
|
name: string
|
||||||
gender: 'male' | 'female' | ''
|
gender: 'male' | 'female' | ''
|
||||||
|
birthYear: number | null
|
||||||
height: number | null
|
height: number | null
|
||||||
|
// 职业性质
|
||||||
|
occupation: string
|
||||||
|
// 疾病史
|
||||||
|
condition: string[]
|
||||||
|
// 健康目标
|
||||||
|
goal: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOverview {
|
export interface IOverview {
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ function getEmptyProfile(): IProfile {
|
|||||||
return {
|
return {
|
||||||
gender: '',
|
gender: '',
|
||||||
height: null,
|
height: null,
|
||||||
|
birthYear: null,
|
||||||
|
occupation: '',
|
||||||
|
condition: [],
|
||||||
|
goal: '',
|
||||||
name: '',
|
name: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user