Pārlūkot izejas kodu

Merge branch 'feature/44-pet-parameters' of 1-bright/frontend-uniapp into main

依力 1 gadu atpakaļ
vecāks
revīzija
3442953505

+ 18 - 0
src/model/pet-manual.ts

@@ -32,3 +32,21 @@ export interface Recommends {
   id: string
   keyword: string
 }
+export interface PetCard {
+  image?: string
+  name: string
+}
+export interface StepInfo {
+  step: number
+  title: string
+}
+export interface FeedFormQuestions {
+  title: string
+  question: PetCard[]
+  formType: number
+}
+export interface UserList {
+  image: string
+  username: string
+  type: string[]
+}

+ 30 - 2
src/pages.json

@@ -7,11 +7,15 @@
   },
   "pages": [
     {
-      "path": "pages/feed-plan/index",
+      "path": "pages/home/index",
       "type": "page"
     },
     {
-      "path": "pages/home/index",
+      "path": "pages/feed-calculator/index",
+      "type": "page"
+    },
+    {
+      "path": "pages/feed-plan/index",
       "type": "page"
     },
     {
@@ -26,6 +30,30 @@
       "path": "pages/userInfo/index",
       "type": "page"
     },
+    {
+      "path": "pages/feed-calculator/components/FeedFlogan",
+      "type": "page"
+    },
+    {
+      "path": "pages/feed-calculator/components/FeedForm",
+      "type": "page"
+    },
+    {
+      "path": "pages/feed-calculator/components/FeedQuestionnaire",
+      "type": "page"
+    },
+    {
+      "path": "pages/feed-calculator/components/FeedStart",
+      "type": "page"
+    },
+    {
+      "path": "pages/feed-calculator/components/FeedStep",
+      "type": "page"
+    },
+    {
+      "path": "pages/feed-calculator/components/ProgressBar",
+      "type": "page"
+    },
     {
       "path": "pages/pet-manual/components/CardList",
       "type": "page"

+ 21 - 0
src/pages/feed-calculator/components/FeedFlogan.vue

@@ -0,0 +1,21 @@
+<script setup lang="ts">
+import feedSlogan from '@/static/image/feed-plan/feed-slogan.png'
+import ToolApi from '@/utils'
+
+const props = withDefaults(defineProps<{
+  bottom: number
+}>(), {
+  bottom: 70,
+})
+const safeBottomMenuHeight = ToolApi.getMenuButtonInfoHeight()
+</script>
+
+<template>
+  <view class="w-screen fixed left-0" :style="{ bottom: `${safeBottomMenuHeight + props.bottom}px` }">
+    <image :src="feedSlogan" class="w-[138px] h-[38px] mx-auto" />
+  </view>
+</template>
+
+<style scoped>
+
+</style>

+ 50 - 0
src/pages/feed-calculator/components/FeedForm.vue

@@ -0,0 +1,50 @@
+<script setup lang="ts">
+import type { PetCard } from '@/model/pet-manual'
+
+const props = withDefaults(defineProps<{
+  title: string
+  formType: number
+  answerList: PetCard[]
+}>(), {
+  title: '',
+  formType: 1,
+})
+const emits = defineEmits<{
+  next: [PetCard]
+}>()
+function handleChooseAnswer(answer: PetCard) {
+  emits('next', answer)
+}
+</script>
+
+<template>
+  <view class="w-full flex justify-center items-center text-[20px] font-bold" :style="{ marginTop: formType === 3 ? '29px' : '69px' }">
+    {{ props.title }}
+  </view>
+  <view v-if="formType === 1" class="flex w-full justify-center items-center mt-[37px] gap-3">
+    <view v-for="(item, index) in props.answerList" :key="index" class="w-[136px] h-[154px] bg-[white] rounded-lg flex flex-col items-center " @click="handleChooseAnswer(item)">
+      <image v-if="index === 0" :src="item?.image" class="w-[76px] h-[69px] mt-[40px]" />
+      <image v-else :src="item?.image" class="w-[96px] h-[73px] mt-[35px]" />
+      <text class="mt-3">
+        {{ item.name }}
+      </text>
+    </view>
+  </view>
+  <view v-if="formType === 2" class="flex flex-col w-full justify-center items-center mt-[37px] gap-4">
+    <view v-for="(item, index) in props.answerList" :key="index" class="w-[200px] h-[56px] bg-[white] rounded-3 flex items-center justify-center" @click="handleChooseAnswer(item)">
+      {{ item.name }}
+    </view>
+  </view>
+  <view v-if="formType === 3" class="flex flex-wrap w-full justify-center items-center mt-[39px] gap-2.5">
+    <view v-for="(item, index) in answerList" :key="index" class="w-[143px] h-[134px] bg-[white] rounded-5 flex flex-col items-center text-[12px]" @click="handleChooseAnswer(item)">
+      <image :src="item.image" class="w-[101px] h-[76px] mt-[21px]" />
+      <text class="mt-[13px]">
+        {{ item.name }}
+      </text>
+    </view>
+  </view>
+</template>
+
+<style scoped>
+
+</style>

+ 127 - 0
src/pages/feed-calculator/components/FeedQuestionnaire.vue

@@ -0,0 +1,127 @@
+<script setup lang="ts">
+import type { FeedFormQuestions, PetCard, StepInfo, UserList } from '@/model/pet-manual'
+import FeedSlogan from '@/pages/feed-calculator/components/FeedFlogan.vue'
+import FeedForm from '@/pages/feed-calculator/components/FeedForm.vue'
+import ProgressBar from '@/pages/feed-calculator/components/ProgressBar.vue'
+import avator from '@/static/image/pet-parameters/avator.png'
+import right from '@/static/image/pet-parameters/right.png'
+
+const props = defineProps<{
+  list: FeedFormQuestions[]
+  stepInfo: StepInfo[]
+  userList: UserList
+}>()
+const emits = defineEmits<{
+  back: [void]
+  step: [number]
+}>()
+const step = ref<number>(0)
+const addPage = ref<number>(1)
+const isUserPage = ref<boolean>(false)
+const aimList = ref<string[]>(['增肥', '减肥'])
+const aimIndex = ref<number>(0)
+const weightList = ref<string[]>(['6kg', '7kg'])
+const weightIndex = ref<number>(0)
+function handleAimChange(e: CustomEvent, type: string) {
+  if (type === 'aim') {
+    aimIndex.value = e.detail.value
+  }
+  else if (type === 'weight') {
+    weightIndex.value = e.detail.value
+  }
+}
+const progressNumber = computed<number>(() => {
+  return Math.ceil((step.value + 1) / (props.list.length + addPage.value) * 100)
+})
+const feedSloganBottom = computed<number>(() => {
+  return props.list[step.value]?.formType === 3 ? 40 : 70
+})
+const feedProgressTitle = computed<string>(() => {
+  return step.value === 0 ? '返回' : '上一题'
+})
+
+function handlePrevious() {
+  if (step.value === props.list.length - 1 + addPage.value) {
+    isUserPage.value = false
+  }
+  if (step.value > 0) {
+    step.value -= 1
+  }
+  else {
+    emits('back')
+  }
+}
+function handleAnswer(answer: PetCard) {
+  console.log(answer, '答案')
+  if (step.value === 2 || step.value === 7) {
+    emits('step', step.value)
+  }
+  if (step.value === props.list.length - 1) {
+    console.log(123)
+    isUserPage.value = true
+  }
+  if (step.value < props.list.length - 1 + addPage.value) {
+    step.value += 1
+    console.log(step.value, 'step')
+  }
+}
+</script>
+
+<template>
+  <ProgressBar :progress="progressNumber" :title-name="feedProgressTitle" @previous="handlePrevious" />
+  <FeedForm v-if="!isUserPage" :form-type="props.list[step].formType" :answer-list="props.list[step].question" :title="props.list[step].title" @next="handleAnswer" />
+  <FeedSlogan :bottom="feedSloganBottom" />
+  <view v-if="isUserPage">
+    <view class="w-[calc(100% - 32px)] h-[96px] bg-[white] rounded-3 mt-[31px] p-[16px] mx-[16px] flex flex-col justify-center">
+      <view class="flex gap-4">
+        <image :src="avator" class="w-[64px] h-[64px] rounded-full" />
+        <view class="mt-[5px] ">
+          <text class="font-bold text-[16px] ">
+            {{ props.userList.username }}
+          </text>
+          <view class="mt-[8px] flex gap-2">
+            <view v-for="(item, index) in props.userList.type" :key="index" class="text-[12px] text-[#999] bg-[#F3F3F3] rounded-[3px] py-[2px] px-[8px]">
+              {{ item }}
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="w-[calc(100% - 32px)] mx-[16px] h-[56px] bg-[white] rounded-t-3 mt-4 flex items-center card_border_bottom p-4 justify-between">
+      <view class="whitespace-nowrap">
+        喂养目标
+      </view>
+
+      <view class="flex justify-center items-center">
+        <picker :value="aimIndex" :range="aimList" @change="handleAimChange($event, 'aim')">
+          <view class="uni-input">
+            {{ aimList[aimIndex] }}
+          </view>
+        </picker>
+        <image :src="right" class="w-[24px] h-[24px]" />
+      </view>
+    </view>
+    <view class="w-[calc(100% - 32px)] mx-[16px] h-[56px] bg-[white] rounded-b-3 flex items-center p-4 justify-between">
+      <view>目标体重</view>
+      <view class="flex justify-center items-center">
+        <picker :value="weightIndex" :range="weightList" @change="handleAimChange($event, 'weight')">
+          <view class="uni-input">
+            {{ weightList[weightIndex] }}
+          </view>
+        </picker>
+        <image :src="right" class="w-[24px] h-[24px]" />
+      </view>
+    </view>
+    <view class="flex items-center justify-center">
+      <button class="w-[176px] h-[47px] flex items-center justify-center bg-[#4545E5] border-none text-[white] rounded-3xl mt-[210px]">
+        下一步
+      </button>
+    </view>
+  </view>
+</template>
+
+<style scoped>
+.card_border_bottom{
+  border-bottom: 1px solid #E5E5E5;
+}
+</style>

+ 36 - 0
src/pages/feed-calculator/components/FeedStart.vue

@@ -0,0 +1,36 @@
+<script setup lang="ts">
+import type { StepInfo } from '@/model/pet-manual'
+import FeedFlogan from '@/pages/feed-calculator/components/FeedFlogan.vue'
+import FeedStep from '@/pages/feed-calculator/components/FeedStep.vue'
+import feedTitle from '@/static/image/feed-plan/feed-title.png'
+
+const props = defineProps<{
+  stepInfo: StepInfo[]
+}>()
+const emits = defineEmits<{
+  ready: [void]
+}>()
+function handleReady() {
+  emits('ready')
+}
+</script>
+
+<template>
+  <view class="w-[273px] h-[125px] mx-auto mt-[105px]">
+    <image :src="feedTitle" class="w-full h-full" />
+  </view>
+
+  <view class="h-[121px] ml-15 mt-14">
+    <FeedStep :list="props.stepInfo" />
+  </view>
+  <FeedFlogan />
+  <view class="flex items-center justify-center">
+    <button class="w-[176px] h-[47px] flex items-center justify-center bg-[#4545E5] border-none text-[white] rounded-3xl mt-[87px]" @tap="handleReady">
+      准备好了
+    </button>
+  </view>
+</template>
+
+<style scoped>
+
+</style>

+ 57 - 0
src/pages/feed-calculator/components/FeedStep.vue

@@ -0,0 +1,57 @@
+<script setup lang="ts">
+const props = withDefaults(defineProps<{
+  list: Array<{
+    step: number
+    title: string
+  }>
+  stage: number
+}>(), {
+  stage: -1,
+})
+</script>
+
+<template>
+  <view v-for="(item, index) in props.list" :key="index">
+    <view class="flex">
+      <view class="flex items-center justify-center text-[white]" :class="props.stage === index ? 'step_select' : props.stage === -1 ? 'step' : 'step step_left' ">
+        {{ item.step }}
+      </view>
+      <view class="ml-1 font-pingfangsc tracking-[0.8px]" :class="props.stage === index ? 'step_title_select' : 'step_title' ">
+        {{ item.title }}
+      </view>
+    </view>
+    <view v-if="index !== props.list.length - 1" class="w-[1px] h-[13px] bg-[#999] mt-1 mb-1 step_line" :class="props.stage === -1 ? 'step_line' : 'step_line_select' " />
+  </view>
+</template>
+
+<style scoped>
+.step{
+  background: #999999;
+  width: 21px;
+  height: 21px;
+  border-radius: 30px;
+}
+.step_left{
+  margin-left: 3px;
+}
+.step_select{
+  background: #0052D9;
+  width: 27px;
+  height: 27px;
+  border-radius: 20px;
+}
+.step_title{
+  color: #999999;
+  font-size:16px;
+}
+.step_title_select{
+  color: #151515;
+  font-size: 20px;
+}
+.step_line{
+  margin-left: 10px;
+}
+.step_line_select{
+  margin-left: 13px;
+}
+</style>

+ 38 - 0
src/pages/feed-calculator/components/ProgressBar.vue

@@ -0,0 +1,38 @@
+<script setup lang="ts">
+import back from '@/static/image/pet-parameters/back.png'
+
+const props = withDefaults(defineProps<{
+  titleName: string
+  progress: number
+}>(), {
+  titleName: '返回',
+  progress: 0,
+})
+
+const emits = defineEmits<{
+  previous: [void]
+}>()
+
+function handleBack() {
+  emits('previous')
+}
+</script>
+
+<template>
+  <view class="m-4 flex items-center">
+    <image :src="back" class="w-[6px] h-[10px]" @click="handleBack" />
+    <text class="text-[12px] font-pingfangsc ml-1" @click="handleBack">
+      {{ props.titleName }}
+    </text>
+    <view class="flex-1 h-[6px] bg-[#E7E7E7] ml-3 rounded relative">
+      <view class="absolute h-[6px] bg-[#0052D9] rounded" :style="{ width: `${props.progress}%` }" />
+    </view>
+    <text class="text-[12px] font-pingfangsc ml-3">
+      {{ props.progress }}%
+    </text>
+  </view>
+</template>
+
+<style scoped>
+
+</style>

+ 102 - 0
src/pages/feed-calculator/index.vue

@@ -0,0 +1,102 @@
+<script setup lang="ts">
+import type { FeedFormQuestions, UserList } from '@/model/pet-manual'
+import FeedQuestionnaire from '@/pages/feed-calculator/components/FeedQuestionnaire.vue'
+import FeedStart from '@/pages/feed-calculator/components/FeedStart.vue'
+import FeedStep from '@/pages/feed-calculator/components/FeedStep.vue'
+import avator from '@/static/image/pet-parameters/avator.png'
+import cat from '@/static/image/pet-parameters/cat.png'
+import dog from '@/static/image/pet-parameters/dog.png'
+import emaciatedCat from '@/static/image/pet-parameters/form/emaciated_cat.png'
+import idealWightCat from '@/static/image/pet-parameters/form/idealweight_cat.png'
+import overWeightCat from '@/static/image/pet-parameters/form/overweight_cat.png'
+import skinnyCat from '@/static/image/pet-parameters/form/skinny_cat.png'
+import thinCat from '@/static/image/pet-parameters/form/thin_cat.png'
+import underWeightCat from '@/static/image/pet-parameters/form/underweight_cat.png'
+import ToolApi from '@/utils'
+
+interface StepInfo {
+  step: number
+  title: string
+}
+const titleName = ref<string>('喂养计算器')
+const safeHeight = ToolApi.getSafeHeight()
+const stage = ref<number>(0)
+const stepInfo = ref<StepInfo[]>([
+  { step: 1, title: '宠物基础信息' },
+  { step: 2, title: '身体状态信息' },
+  { step: 3, title: '计算喂养计划' },
+])
+const controlFormDisplay = ref({
+  display1: true,
+  display2: false,
+  display3: false,
+})
+const feedFormQuestions = ref<FeedFormQuestions[]>([
+  { title: '您的动物是?', question: [{ image: cat, name: '猫猫' }, { image: dog, name: '狗狗' }], formType: 1 },
+  { title: '猫咪的性别?', question: [{ name: '男孩' }, { name: '女孩' }], formType: 2 },
+  { title: '猫咪的年龄区间?', question: [{ name: '0-4个月' }, { name: '5-12个月' }], formType: 2 },
+  { title: '猫咪是否活跃?', question: [{ name: '活跃' }, { name: '不活跃' }], formType: 2 },
+  { title: '猫咪是否怀孕?', question: [{ name: '怀孕' }, { name: '未怀孕' }], formType: 2 },
+  { title: '猫咪是否绝育?', question: [{ name: '绝育' }, { name: '未绝育' }], formType: 2 },
+  { title: '猫咪是否在哺乳?', question: [{ name: '哺乳中' }, { name: '未哺乳' }], formType: 2 },
+  { title: '请选择猫咪的体型', question: [{ image: emaciatedCat, name: '极度消廋' }, { image: skinnyCat, name: '非常廋' }, { image: thinCat, name: '消瘦' }, { image: underWeightCat, name: '体重偏低' }, { image: idealWightCat, name: '理想体重' }, { image: overWeightCat, name: '体重偏重' }], formType: 3 },
+])
+const userList = ref<UserList>({
+  image: avator,
+  username: '子龙',
+  type: ['年龄', '品种', '体重'],
+})
+function reset() {
+  stage.value = 0
+  controlFormDisplay.value = {
+    display1: false,
+    display2: false,
+    display3: false,
+  }
+}
+function handleReady() {
+  reset()
+  titleName.value = '喂养问卷'
+  controlFormDisplay.value.display2 = true
+  setTimeout(() => {
+    reset()
+    controlFormDisplay.value.display3 = true
+  }, 3000)
+}
+function handleBackFeedStart() {
+  reset()
+  titleName.value = '喂养计算器'
+  controlFormDisplay.value.display1 = true
+}
+function handleStep(step: number) {
+  if (step === 2) {
+    handleReady()
+    stage.value = 1
+    controlFormDisplay.value.display2 = true
+  }
+  else if (step === 7) {
+    handleReady()
+    stage.value = 2
+    controlFormDisplay.value.display2 = true
+  }
+}
+</script>
+
+<template>
+  <TitleBar :title-name="titleName" />
+  <view class="flex flex-col bg-[#F5F6F7] overflow-y-auto" :style="`height:calc(100vh - ${safeHeight}px)`">
+    <view v-show="controlFormDisplay.display1">
+      <FeedStart :step-info="stepInfo" @ready="handleReady" />
+    </view>
+    <view v-show="controlFormDisplay.display2" class="flex justify-center items-center w-full h-full">
+      <FeedStep :list="stepInfo" :stage="stage" />
+    </view>
+    <view v-show="controlFormDisplay.display3">
+      <FeedQuestionnaire :list="feedFormQuestions" :step-info="stepInfo" :user-list="userList" @back="handleBackFeedStart" @step="handleStep" />
+    </view>
+  </view>
+</template>
+
+<style scoped>
+
+</style>

+ 8 - 1
src/pages/feed-plan/index.vue

@@ -1,8 +1,15 @@
 <script setup lang="ts">
+const titleName = ref<string>('喂养计划')
+function handleJumpPlan() {
+  uni.navigateTo({ url: '/pages/feed-calculator/index' })
+}
 </script>
 
 <template>
-  <view>喂养计划</view>
+  <TitleBar :title-name="titleName" />
+  <button class="h-16 flex items-center justify-center bg-[red]" @tap="handleJumpPlan">
+    跳转喂养计算器
+  </button>
 </template>
 
 <style scoped>

BIN
src/static/image/feed-plan/feed-slogan.png


BIN
src/static/image/feed-plan/feed-title.png


BIN
src/static/image/pet-parameters/avator.png


BIN
src/static/image/pet-parameters/back.png


BIN
src/static/image/pet-parameters/cat.png


BIN
src/static/image/pet-parameters/dog.png


BIN
src/static/image/pet-parameters/form/emaciated_cat.png


BIN
src/static/image/pet-parameters/form/idealweight_cat.png


BIN
src/static/image/pet-parameters/form/overweight_cat.png


BIN
src/static/image/pet-parameters/form/skinny_cat.png


BIN
src/static/image/pet-parameters/form/thin_cat.png


BIN
src/static/image/pet-parameters/form/underweight_cat.png


BIN
src/static/image/pet-parameters/right.png


+ 8 - 1
src/uni-pages.d.ts

@@ -4,10 +4,17 @@
 // Generated by vite-plugin-uni-pages
 
 interface NavigateToOptions {
-  url: "/pages/feed-plan/index" |
+  url: "/pages/feed-calculator/index" |
+       "/pages/feed-plan/index" |
        "/pages/home/index" |
        "/pages/me/index" |
        "/pages/pet-manual/index" |
+       "/pages/feed-calculator/components/FeedFlogan" |
+       "/pages/feed-calculator/components/FeedForm" |
+       "/pages/feed-calculator/components/FeedQuestionnaire" |
+       "/pages/feed-calculator/components/FeedStart" |
+       "/pages/feed-calculator/components/FeedStep" |
+       "/pages/feed-calculator/components/ProgressBar" |
        "/pages/setting/index" |
        "/pages/userInfo/index" |
        "/pages/pet-manual/components/CardList";

+ 4 - 1
src/utils/index.ts

@@ -4,7 +4,10 @@ function getSafeHeight(): number {
   const menuButtonInfoHeight: number = uni.getMenuButtonBoundingClientRect().height + 60
   return safeAreaInsetBottom + menuButtonInfoHeight
 }
-
+function getMenuButtonInfoHeight(): number {
+  return uni.getMenuButtonBoundingClientRect().height + 60
+}
 export default {
   getSafeHeight,
+  getMenuButtonInfoHeight,
 }