Selaa lähdekoodia

feat: 完成喂养计划,宠物信息填充

IlhamTahir 1 vuosi sitten
vanhempi
commit
6dfcc0076a

+ 1 - 1
pages.config.ts

@@ -61,7 +61,7 @@ export default defineUniPages({
       type: 'page',
     },
     {
-      path: 'pages/feed-calculator/components/FeedFlogan',
+      path: 'pages/feed-calculator/components/FeedSlogan',
       type: 'page',
     },
     {

+ 2 - 0
src/auto-imports.d.ts

@@ -190,6 +190,7 @@ declare global {
   const useEventSource: typeof import('@vueuse/core')['useEventSource']
   const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
   const useFavicon: typeof import('@vueuse/core')['useFavicon']
+  const useFeedingPlanStore: typeof import('./stores/feeding-plan')['useFeedingPlanStore']
   const useFetch: typeof import('@vueuse/core')['useFetch']
   const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
   const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
@@ -507,6 +508,7 @@ declare module 'vue' {
     readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
     readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
     readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
+    readonly useFeedingPlanStore: UnwrapRef<typeof import('./stores/feeding-plan')['useFeedingPlanStore']>
     readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
     readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
     readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>

+ 5 - 0
src/components.d.ts

@@ -7,6 +7,11 @@ export {}
 
 declare module 'vue' {
   export interface GlobalComponents {
+    Cell: typeof import('./components/Cell.vue')['default']
+    CellGroup: typeof import('./components/CellGroup.vue')['default']
+    PickerDate: typeof import('./components/PickerDate.vue')['default']
+    PickerItem: typeof import('./components/PickerItem.vue')['default']
+    PopupInput: typeof import('./components/PopupInput.vue')['default']
     TabBar: typeof import('./components/TabBar.vue')['default']
     TitleBar: typeof import('./components/TitleBar.vue')['default']
   }

+ 32 - 0
src/components/Cell.vue

@@ -0,0 +1,32 @@
+<script setup lang="ts">
+import right from '@/static/icons/right.svg'
+
+withDefaults(defineProps<{
+  title: string
+  border?: boolean
+}>(), {
+  border: false,
+})
+</script>
+
+<template>
+  <view
+    class="h-[56px] bg-[white] flex items-center p-4 justify-between" :class="{
+      'border-[#E5E5E5] border-b-[0.5px]': border,
+    }"
+  >
+    <view class="text-base">
+      {{ title }}
+    </view>
+    <view class="flex-1 flex justify-center items-center gap-3">
+      <view class="flex-1 flex justify-end">
+        <slot />
+      </view>
+      <image :src="right" class="w-[24px] h-[24px]" />
+    </view>
+  </view>
+</template>
+
+<style scoped>
+
+</style>

+ 13 - 0
src/components/CellGroup.vue

@@ -0,0 +1,13 @@
+<script setup lang="ts">
+
+</script>
+
+<template>
+  <view class="w-full bg-[#fff] shadow-cell-group rounded-3 overflow-hidden cell-group">
+    <slot />
+  </view>
+</template>
+
+<style scoped>
+
+</style>

+ 24 - 0
src/components/PickerDate.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+defineProps<{
+  modelValue: string
+}>()
+const emits = defineEmits<{
+  'update:modelValue': [string]
+}>()
+
+function bindDateChange(e: { detail: { value: string } }) {
+  emits('update:modelValue', e.detail.value)
+}
+</script>
+
+<template>
+  <picker mode="date" :value="modelValue" @change="bindDateChange">
+    <view class="">
+      {{ modelValue }}
+    </view>
+  </picker>
+</template>
+
+<style scoped>
+
+</style>

+ 38 - 0
src/components/PickerItem.vue

@@ -0,0 +1,38 @@
+<script setup lang="ts">
+export interface PickerItemOption {
+  label: string
+  value: string | number
+}
+const props = defineProps<{
+  modelValue: string | number
+  options: PickerItemOption[]
+}>()
+
+const emits = defineEmits<{
+  'update:modelValue': [string | number]
+}>()
+
+function bindPickerChange(e: { detail: { value: number | string } }) {
+  const currentIndex = e.detail.value as number
+  emits('update:modelValue', props.options[currentIndex].value)
+}
+
+const index = ref(0)
+
+watch(() => props.modelValue, (currentValue) => {
+  const currentIndex = props.options.findIndex(option => option.value === currentValue)
+  index.value = currentIndex === -1 ? 0 : currentIndex
+}, { immediate: true })
+</script>
+
+<template>
+  <picker :value="index" :range="options" range-key="label" @change="bindPickerChange">
+    <view>
+      {{ options[index].label }}
+    </view>
+  </picker>
+</template>
+
+<style scoped>
+
+</style>

+ 58 - 0
src/components/PopupInput.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import type { UniPopupDialogInstance } from '@uni-helper/uni-types'
+import type { UniPopupInstance } from '@uni-helper/uni-ui-types'
+
+const props = withDefaults(defineProps<{
+  modelValue: string | number
+  placeholder?: string
+  suffix?: string
+}>(), {
+  placeholder: '请输入内容',
+})
+
+const emits = defineEmits<{
+  'update:modelValue': [string | number]
+}>()
+
+const uniPopupRef = ref<UniPopupInstance | null>(null)
+const uniPopupDialogRef = ref<UniPopupDialogInstance | null>(null)
+
+function dialogInputConfirm(value: any) {
+  if (value === '') {
+    uni.showToast({
+      title: props.placeholder,
+      icon: 'none',
+    })
+    return
+  }
+  emits('update:modelValue', value as (string | number))
+  if (uniPopupRef.value && uniPopupRef.value.close) {
+    uniPopupRef.value.close()
+  }
+}
+
+function handleClick() {
+  if (uniPopupRef.value && uniPopupRef.value.open) {
+    uniPopupRef.value.open()
+  }
+}
+</script>
+
+<template>
+  <view class="w-full" @click="handleClick">
+    {{ modelValue }}
+    <text v-if="suffix">
+      {{ suffix }}
+    </text>
+  </view>
+  <uni-popup ref="uniPopupRef" type="dialog">
+    <uni-popup-dialog
+      ref="uniPopupDialogRef" mode="input" title="输入内容" :value="modelValue" :placeholder="placeholder"
+      @confirm="dialogInputConfirm"
+    />
+  </uni-popup>
+</template>
+
+<style scoped>
+
+</style>

+ 32 - 2
src/model/pet.ts

@@ -2,7 +2,7 @@ import type { TraceableModel } from '@/model/base'
 
 export interface Pet extends TraceableModel {
   birthday: string
-  bodyType: string
+  bodyType: PetBodyType
   gender: number
   isActive: boolean
   isLactation: boolean
@@ -10,10 +10,40 @@ export interface Pet extends TraceableModel {
   isSterilization: boolean
   name: string
   photo: string
-  type: string
+  type: PetType
   weight: number
 }
 
 export interface CreatePetRequest extends Omit<Pet, keyof TraceableModel> {
 
 }
+
+export enum PetType {
+  CAT = 'cat',
+  DOG = 'dog',
+}
+
+export enum PetBodyType {
+  // 极度消瘦
+  EXTREMELY_THIN = 'extremely-thin',
+  // 非常瘦
+  VERY_THIN = 'very-thin',
+  // 消瘦
+  THIN = 'thin',
+  // 偏轻
+  UNDERWEIGHT = 'underweight',
+  // 理想体重
+  IDEAL = 'ideal',
+  // 偏重
+  OVERWEIGHT = 'overweight',
+  // 肥胖
+  OBESE = 'obese',
+  // 极度肥胖
+  EXTREMELY_OBESE = 'extremely-obese',
+}
+
+export enum Gender {
+  MALE,
+  FEMALE,
+  UNKNOWN,
+}

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

@@ -1,6 +1,5 @@
 <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'
 

+ 28 - 33
src/pages/start-filing/index.vue

@@ -1,30 +1,18 @@
 <script setup lang="ts">
-import FeedFlogan from '@/pages/feed-calculator/components/FeedFlogan.vue'
-import right from '@/static/image/pet-parameters/right.png'
+import PickerItem from '@/components/PickerItem.vue'
+import FeedSlogan from '@/pages/feed-calculator/components/FeedFlogan.vue'
 import edit from '@/static/image/start-filing/edit.png'
+import { useFeedingPlanStore } from '@/stores/feeding-plan'
 import ToolApi from '@/utils'
 
-interface RecordList {
-  title: string
-  options: string[]
-  aimIndex: number
-}
-
 const safeHeight = ToolApi.getSafeHeight()
-const recordList = ref<RecordList[]>([
-  { title: '宠物名字', options: ['子龙'], aimIndex: 0 },
-  { title: '品种', options: ['好猫'], aimIndex: 0 },
-  { title: '性别', options: ['男孩'], aimIndex: 0 },
-  { title: '出生日期', options: ['2月2日'], aimIndex: 0 },
-  { title: '体重', options: ['2kg'], aimIndex: 0 },
-])
-const feedFloganBottom = ref<number>(13)
-function handleAimChange(e: CustomEvent, index: number) {
-  recordList.value[index].aimIndex = e.detail.value
-}
+
+const feedSloganBottom = ref<number>(13)
 function handleNext() {
   uni.navigateTo({ url: '/pages/feed-calculator/index' })
 }
+
+const { pet, petTypeOptions, genderOptions } = useFeedingPlanStore()
 </script>
 
 <template>
@@ -34,19 +22,26 @@ function handleNext() {
         <image :src="edit" class="w-[10px] h-[10px]" />
       </view>
     </view>
-    <view v-for="(item, index) in recordList" :key="index" :style="{ marginTop: index === 0 ? '28px' : index === 1 ? '16px' : '', borderRadius: index === 0 ? '12px' : index === 1 ? '12px 12px 0 0' : index === recordList.length - 1 ? '0 0 12px 12px' : '0px' }" class="w-[calc(100% - 32px)] mx-[16px] h-[56px] bg-[white] flex items-center card_border_bottom p-4 justify-between">
-      <view class="whitespace-nowrap">
-        {{ item.title }}
-      </view>
-
-      <view class="flex justify-center items-center">
-        <picker :value="item.aimIndex" :range="item.options" @change="handleAimChange($event, index)">
-          <view class="uni-input">
-            {{ item.options[item.aimIndex] }}
-          </view>
-        </picker>
-        <image :src="right" class="w-[24px] h-[24px]" />
-      </view>
+    <view class="w-full px-4 flex flex-col gap-4 mt-[28px]">
+      <CellGroup class="w-full bg-[#fff] shadow-cell-group rounded-3 overflow-hidden">
+        <Cell title="宠物名称">
+          <PopupInput v-model="pet.name" placeholder="请输入宠物名称" />
+        </Cell>
+      </CellGroup>
+      <CellGroup class="w-full bg-[#fff] shadow-cell-group rounded-3 overflow-hidden">
+        <Cell title="品种" border>
+          <PickerItem v-model="pet.type" :options="petTypeOptions" />
+        </Cell>
+        <Cell title="性别" border>
+          <PickerItem v-model="pet.gender" :options="genderOptions" />
+        </Cell>
+        <Cell title="出生日期" border>
+          <PickerDate v-model="pet.birthday" />
+        </Cell>
+        <Cell title="体重">
+          <PopupInput v-model="pet.weight" placeholder="请输入宠物体重" suffix="kg" />
+        </Cell>
+      </CellGroup>
     </view>
 
     <view class="flex items-center justify-center">
@@ -54,7 +49,7 @@ function handleNext() {
         下一题
       </button>
     </view>
-    <FeedFlogan :bottom="feedFloganBottom" />
+    <FeedSlogan :bottom="feedSloganBottom" />
   </view>
 </template>
 

+ 3 - 0
src/static/icons/right.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd" d="M9.6907 18.6889L8.31184 17.31L13.6224 11.9994L8.31184 6.68887L9.6907 5.31001L16.3801 11.9994L9.6907 18.6889Z" fill="black" fill-opacity="0.4"/>
+</svg>

+ 34 - 0
src/stores/feeding-plan.ts

@@ -0,0 +1,34 @@
+import { type CreatePetRequest, Gender, PetBodyType, PetType } from '@/model/pet'
+import { defineStore } from 'pinia'
+
+export const useFeedingPlanStore = defineStore('feeding-plan', () => {
+  const petTypeOptions = [
+    { value: PetType.CAT, label: '猫猫' },
+    { value: PetType.DOG, label: '狗狗' },
+  ]
+
+  const genderOptions = [
+    { value: Gender.MALE, label: '男孩' },
+    { value: Gender.FEMALE, label: '女孩' },
+  ]
+
+  const pet = ref <CreatePetRequest>({
+    birthday: new Date().toISOString().split('T')[0],
+    bodyType: PetBodyType.IDEAL,
+    gender: Gender.MALE,
+    isActive: false,
+    isLactation: false,
+    isPregnant: false,
+    isSterilization: false,
+    name: '子龙',
+    photo: '',
+    type: PetType.CAT,
+    weight: 0,
+  })
+
+  return {
+    pet,
+    petTypeOptions,
+    genderOptions,
+  }
+})

+ 3 - 0
tailwind.config.ts

@@ -17,6 +17,9 @@ const theme: Config['theme'] = {
     primary: '#4545E5',
     default: '#828282',
   },
+  boxShadow: {
+    'cell-group': '0px 4px 12px 0px rgba(0, 0, 0, 0.10)',
+  },
 }
 if (isMp || isQuickapp)
   theme.screens = {}

+ 3 - 1
tsconfig.json

@@ -10,7 +10,9 @@
       "@dcloudio/types",
       "@mini-types/alipay",
       "miniprogram-api-typings",
-      "@uni-helper/uni-types"
+      "@uni-helper/uni-types",
+      "@uni-helper/uni-app-types",
+      "@uni-helper/uni-ui-types"
     ],
     "sourceMap": true
   },