Leon 2 år sedan
förälder
incheckning
acf8ad20fa

+ 1 - 0
src/app.config.ts

@@ -17,6 +17,7 @@ const appConfig = defineAppConfig({
     'pages/account/ProfileSetting',
     'pages/account/Setting',
     'pages/account/EditPage',
+    'pages/workout/WorkoutDetail'
   ],
   subPackages: [
     {

+ 6 - 0
src/app.scss

@@ -173,6 +173,12 @@ page {
     flex-shrink: 0;
 }
 
+.stepper_text {
+    font-size: 48px;
+    font-weight: 500;
+    width: 350px;
+    text-align: center;
+}
 
 
 // @media only screen and (-webkit-min-device-pixel-ratio: 2.0) {

+ 2 - 2
src/components/input/Stepper.tsx

@@ -7,7 +7,7 @@ import Taro from "@tarojs/taro";
 import { IconMinus, IconPlus } from "../basic/Icons";
 import { useTranslation } from "react-i18next";
 
-export default function Component(props: { child: any, minus: Function, plus: Function, disableMinus?: boolean, disablePlus?: boolean,themeColor:string }) {
+export default function Component(props: { children: any, minus: Function, plus: Function, disableMinus?: boolean, disablePlus?: boolean,themeColor:string }) {
     const [timer, setTimer] = useState<NodeJS.Timeout | null>(null);
     const {t} = useTranslation()
     useEffect(()=>{
@@ -80,7 +80,7 @@ export default function Component(props: { child: any, minus: Function, plus: Fu
         </View>
 
         {
-            props.child
+            props.children
         }
         <View className="stepper_item stepper_item_padding_left" onClick={tapPlus} onLongPress={longPlus} onTouchEnd={longPlusEnd}>
             <IconPlus color={props.themeColor} disable={props.disablePlus}/>

+ 9 - 8
src/components/layout/Modal.tsx

@@ -8,8 +8,9 @@ import Taro from '@tarojs/taro';
 export default function Modal(props: {
     children: React.ReactNode,
     testInfo?: React.ReactNode,
-    title?: string, dismiss: Function,
-    confirm: Function,
+    title?: string,
+    dismiss: Function,
+    confirm?: Function,
     themeColor?: string,
     modalType?: ModalType,
     cancelCatchMove?: boolean
@@ -57,21 +58,21 @@ export default function Modal(props: {
         }
     }
 
-    function dismiss(){
-        if (props.modalType==ModalType.center){
+    function dismiss() {
+        if (props.modalType == ModalType.center) {
             props.dismiss()
             return
         }
         setIsDismiss(true)
-        setTimeout(()=>{
+        setTimeout(() => {
             props.dismiss()
-        },250)
+        }, 250)
     }
 
     global.dismissModal = dismiss;
 
 
-    return <View className={isDismiss?'modal modal_dismiss':'modal'} catchMove onLongPress={longPress}>
+    return <View className={isDismiss ? 'modal modal_dismiss' : 'modal'} catchMove onLongPress={longPress}>
         <View style={{ flex: 1, width: 375, flexShrink: 0 }} onClick={(e) => {
             if (process.env.TARO_ENV == 'weapp') {
                 e.stopPropagation()
@@ -81,7 +82,7 @@ export default function Modal(props: {
                 props.testInfo ? props.testInfo : null
             }
         </View>
-        <View className={isDismiss?'modal_bottom_content modal_bottom_dismiss':'modal_bottom_content'} style={{ flexShrink: 0 }} onClick={onClick}>
+        <View className={isDismiss ? 'modal_bottom_content modal_bottom_dismiss' : 'modal_bottom_content'} style={{ flexShrink: 0 }} onClick={onClick}>
             {/* <Text className='modal_title' style={{ color: color }}>{props.title ? props.title : '测试标题 '}</Text> */}
             {
                 props.children

+ 1 - 0
src/context/themes/color.tsx

@@ -4,4 +4,5 @@ export enum ColorType {
     box = '#121212',
     ring = '#1c1c1c',
     food = '#FF7A4E',
+    workout = 'yellow',
 }

+ 34 - 43
src/features/trackSomething/components/Activity.tsx

@@ -12,13 +12,16 @@ import Layout from '@/components/layout/layout'
 import NoData from "@/components/view/NoData";
 import { ResultType, checkFail, checkRetry, checkStart, checkSuccess, resetStatus, setResult } from "@/store/action_results";
 import RequestType, { thirdPartRequest } from "@/services/thirdPartRequest";
-import { ModalType, NaviBarTitleShowType, TemplateType } from "@/utils/types";
+import { ModalType, NaviBarTitleShowType, TemplateType, WorkoutType } from "@/utils/types";
 import { useTranslation } from "react-i18next";
 import { jumpPage } from "@/features/trackTimeDuration/hooks/Common";
 import TitleView from "@/features/trackTimeDuration/components/TitleView";
 import './Activity.scss'
 import Modal from "@/components/layout/Modal";
 import MoveList from "./MoveList";
+import { ColorType } from "@/context/themes/color";
+import SetGoal from "@/features/workout/SetGoal";
+import Working from "@/features/workout/Working";
 // import { useNavigation } from "@react-navigation/native";
 
 let useNavigation;
@@ -43,8 +46,10 @@ export default function Component(props: any) {
     const [triggered, setTriggered] = useState(true)
     const [showErrorPage, setErrorPage] = useState(false)
     const [showModal, setShowModal] = useState(false)
-    const [loaded,setLoaded] = useState(false)
+    const [loaded, setLoaded] = useState(false)
     const [count, setCount] = useState(0)
+    const [isStart, setIsStart] = useState(false)
+    const [targetTime, setTargetTime] = useState(0)
     let navigation;
     if (useNavigation) {
         navigation = useNavigation()
@@ -339,15 +344,15 @@ export default function Component(props: any) {
         isEnable = true;
     }
 
-    function addBtnClick() {
-        setShowModal(true)
-    }
-
     function headerView() {
         return <TitleView title={t('page.activity.title')} showAddBtn={false}>
         </TitleView>
     }
 
+    function workout() {
+        setShowModal(true)
+    }
+
     function detail() {
         return <View className="activity_container">
             {
@@ -415,42 +420,22 @@ export default function Component(props: any) {
                     />
                 })
             }
-        </View>
-    }
 
-    function modalDetail() {
-        return <View>
-            <MoveList list={[
-          { id: 1, name: '集美仓' },
-          { id: 2, name: '海沧仓' },
-          { id: 3, name: '思明仓' },
-          { id: 4, name: '湖里仓' },
-          { id: 11, name: '集美仓1' },
-          { id: 21, name: '海沧仓1' },
-          { id: 31, name: '思明仓1' },
-          { id: 41, name: '湖里仓1' },
-          { id: 10, name: '集美仓2' },
-          { id: 20, name: '海沧仓2' },
-          { id: 30, name: '思明仓2' },
-          { id: 40, name: '湖里仓2' }
-        ]}
-        itemHeight={45}
-        renderItem={(item) => (
-          <View
-            style={{
-              height: 40,
-              lineHeight: '40px',
-              borderBottom: '1px solid #e8e8e8',
-              padding: '0 15px',
-              color:'#fff'
-            }}
-          >
-             {item.name}
-          </View>
-        )}
-        onchange={(e) => {
-          console.log(e)
-        }}/>
+            <MetricItem title='平板支撑'
+                // value={allowRun ? stepInfo ? (stepInfo as any).step : '' : '未开启'}
+                value={10}
+                unit={'分钟'}
+                desc={'昨天'}
+                btnText={'计时训练'}
+                isDisabled={false}
+                themeColor={ColorType.workout}
+                onClickDetail={() => { }}
+                showBadge={showErrorBadge && checkResult.type == 'idle'}
+                showDetail={false}
+                onClick={() => {
+                    workout()
+                }}
+            />
         </View>
     }
 
@@ -463,9 +448,15 @@ export default function Component(props: any) {
             titleShowStyle={NaviBarTitleShowType.scrollToShow}
         />
         {
-            showModal && <Modal modalType={ModalType.bottom} dismiss={() => setShowModal(false)} confirm={() => { }}>
+            showModal && <Modal dismiss={() => {
+                setIsStart(false)
+                setShowModal(false)
+            }} confirm={() => { }} modalType={ModalType.center}>
+                {
+                    !isStart && <SetGoal start={(count) => { setTargetTime(count); setIsStart(true) }} />
+                }
                 {
-                    modalDetail()
+                    isStart && <Working targetCount={targetTime} type={WorkoutType.multi}/>
                 }
             </Modal>
         }

+ 1 - 6
src/features/trackTimeDuration/components/Console.scss

@@ -1,11 +1,6 @@
 @import '@/utils/common.scss';
 
-.stepper_text {
-    font-size: 48px;
-    font-weight: 500;
-    width: 350px;
-    text-align: center;
-}
+
 
 .counter_text {
     font-size: 48px;

+ 2 - 2
src/features/trackTimeDuration/components/Console.tsx

@@ -493,7 +493,7 @@ export default function Component(props: { isNextStep?: boolean }) {
                 pointerEvents: time.status == 'WAIT_FOR_START' ? 'none' : 'all'
             }}>
                 {
-                    (time.status == 'WAIT_FOR_START') && <Stepper child={
+                    (time.status == 'WAIT_FOR_START') && <Stepper children={
                         <Text className="stepper_text" style={{ color: global.sleepColor ? global.sleepColor : ColorType.sleep, opacity: textAlpha(time) }} onClick={showDurationPicker}>{durationFormate()}</Text>
                     } minus={minus} plus={plus}
                         themeColor={global.sleepColor ? global.sleepColor : ColorType.sleep} disableMinus={disableMinus()} disablePlus={disablePlus()} />
@@ -568,7 +568,7 @@ export default function Component(props: { isNextStep?: boolean }) {
                     isLoaded && ongoing()
                 }
                 {
-                    (time.status == 'ONGOING1' || time.status == 'WAIT_FOR_START') && <Stepper child={
+                    (time.status == 'ONGOING1' || time.status == 'WAIT_FOR_START') && <Stepper children={
                         <Text className="stepper_text" style={{ color: textColor, opacity: textAlpha(time) }} onClick={showDurationPicker}>{durationFormate()}</Text>
                     } minus={minus} plus={plus}
                         themeColor={isFast ? global.fastColor ? global.fastColor : ColorType.fast : global.sleepColor ? global.sleepColor : ColorType.sleep} disableMinus={disableMinus()} disablePlus={disablePlus()} />

+ 54 - 0
src/features/workout/Result.tsx

@@ -0,0 +1,54 @@
+import { View,Text } from "@tarojs/components";
+import { useRouter } from "@tarojs/taro";
+import { useEffect, useState } from "react";
+
+export default function Component(){
+    const router = useRouter();
+    const [histories,setHistories] = useState<any>([])
+
+    useEffect(()=>{
+        var  list = JSON.parse(router.params.detail  as any)
+        if (list[list.length-2].type=='end'){
+            list.splice(list.length-1,1)
+        }
+        setHistories(list)
+    },[])
+
+    function twoTimeDuration(start, end) {
+        var time = Math.floor((end - start) / 1000);
+        const hours = Math.floor(time / 3600);
+        const minutes = Math.floor((time % 3600) / 60);
+        const seconds = Math.floor(time % 60);
+        var strDuration = ''
+        if (hours > 0) {
+            strDuration = `${hours}时`
+        }
+        if (minutes > 0) {
+            strDuration += `${minutes}分`
+        }
+        if (seconds > 0) {
+            strDuration += `${seconds}秒`
+        }
+        return strDuration.length==0?'1秒':strDuration
+    }
+
+    return <View  style={{color:'#fff',display:'flex',flexDirection:'column'}}>
+        <Text>训练组</Text>
+        {
+            histories.length>0 && <Text>共{histories[histories.length-1].index}组</Text>
+        }
+        
+
+        {
+            histories.map((item, index) => {
+                if (index == 0) {
+                    return <View />
+                }
+                return <View key={index} style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', width: '100%', color: '#fff' }}>
+                    <Text>{item.type == 'end' ? `第${item.index}组` : '组间休息'}</Text>
+                    <Text>{twoTimeDuration(histories[index - 1].time, item.time)}</Text>
+                </View>
+            })
+        }
+    </View>
+}

+ 101 - 0
src/features/workout/SetGoal.tsx

@@ -0,0 +1,101 @@
+import PickerViews from "@/components/input/PickerViews";
+import Stepper from "@/components/input/Stepper";
+import Modal from "@/components/layout/Modal";
+import { ColorType } from "@/context/themes/color";
+import { View, Text } from "@tarojs/components";
+import { useState } from "react";
+import { ChooseScenarioBtn } from "../common/SpecBtns";
+
+export default function Component(props: { start: Function }) {
+    const [goalTime, setGoalTime] = useState(15 * 60)
+    const [showPicker, setShowPicker] = useState(false)
+    const [sportPickerValue, setSportPickerValue] = useState([14])
+    function minus() {
+        var t = goalTime - 60
+        setSportPickerValue([t / 60 - 1])
+        setGoalTime(t)
+    }
+
+    function plus() {
+        var t = goalTime + 60
+        setSportPickerValue([t / 60 - 1])
+        setGoalTime(t)
+    }
+
+    function disableMinus() {
+        if (goalTime <= 60) {
+            return true
+        }
+        return false
+    }
+
+    function disablePlus() {
+        if (goalTime >= 60 * 60) {
+            return true
+        }
+        return false
+    }
+
+    function showDurationPicker() {
+        setShowPicker(true)
+
+    }
+
+    function durationChange(e) {
+        setShowPicker(false)
+        var index = e[0]
+        setGoalTime(60 * (index + 1))
+    }
+
+    function durationDatas() {
+        var minutes: string[] = []
+        for (let i = 1; i <= 60; i++) {
+            minutes.push(i + '分钟')
+        }
+        return [minutes]
+    }
+
+    function pickerContent() {
+        var color = ColorType.workout
+        var title = '设置时间'
+        return <View style={{ color: '#fff', backgroundColor: 'transparent' }}>
+            <PickerViews
+                onChange={durationChange}
+                items={durationDatas()}
+                value={sportPickerValue}
+                themeColor={color}
+                title={title}
+                showBtns={true}
+                onCancel={() => {
+                    setShowPicker(false)
+                }} />
+        </View>
+    }
+
+    function durationFormate() {
+        return `${parseInt(goalTime / 60 + '')}分钟`
+    }
+
+    function start() {
+        props.start(goalTime)
+    }
+
+    return <View style={{ color: ColorType.workout }}>
+        <Text>设置训练时长</Text>
+        <Stepper minus={minus} plus={plus}
+            themeColor={ColorType.workout}
+            disableMinus={disableMinus()}
+            disablePlus={disablePlus()} >
+            <Text className="stepper_text" style={{ color: ColorType.workout, opacity: 1 }} onClick={showDurationPicker}>{durationFormate()}</Text>
+        </Stepper>
+
+        <ChooseScenarioBtn onClick={start} title="开始计时" background={ColorType.workout} />
+        {
+            showPicker && <Modal dismiss={() => setShowPicker(false)} confirm={() => setShowPicker(false)}>
+                {
+                    pickerContent()
+                }
+            </Modal>
+        }
+    </View>
+}

+ 231 - 0
src/features/workout/Working.tsx

@@ -0,0 +1,231 @@
+import { ColorType } from "@/context/themes/color";
+import { TimeFormatter } from "@/utils/time_format";
+import { View, Text } from "@tarojs/components";
+import { useEffect, useState } from "react";
+import { ChooseScenarioBtn } from "../common/SpecBtns";
+import Taro from "@tarojs/taro";
+import { WorkoutType } from "@/utils/types";
+import Modal from "@/components/layout/Modal";
+import PickerViews from "@/components/input/PickerViews";
+
+var timer
+var lastStrTime
+export default function Component(props: { targetCount: number, type: WorkoutType }) {
+    const [index, setIndex] = useState(1)
+    const [count, setCount] = useState(0)
+    const [startTime, setStartTime] = useState(new Date().getTime())
+    const [tempTime, setTempTime] = useState(0)
+    // const [lastStrTime, setLastStrTime] = useState('')
+    const [groups, setGroups] = useState<any[]>([])
+    const [isDoing, setIsDoing] = useState(true)
+    const [showModal, setShowModal] = useState(false)
+
+    const items = [[1, 2, 3, 4, 5, 6]]
+    const [selIndex, setSelIndex] = useState([1])
+    const multiItems = [[10, 20, 30, 40], [1, 2, 3, 4, 5]]
+    const multiSel = [1, 1]
+
+    useEffect(() => {
+        timer = setInterval(() => {
+            setCount((count) => count + 1)
+        }, 1000)
+        setGroups([{
+            index: index,
+            time: startTime,
+            type: 'start'
+        }])
+        return () => clearInterval(timer)
+    }, [])
+
+    function resume() {
+        timer = setInterval(() => {
+            setCount((count) => count + 1)
+        }, 1000)
+    }
+
+    function planTime() {
+        var minutes = props.targetCount / 60
+        return minutes + '分钟'
+    }
+
+    function durationTime() {
+        var str = TimeFormatter.formateTimeNow(startTime)
+        // setLastStrTime(str)
+        lastStrTime = str
+        return str
+
+    }
+
+    function twoTimeDuration(start, end) {
+        var time = Math.floor((end - start) / 1000);
+        const hours = Math.floor(time / 3600);
+        const minutes = Math.floor((time % 3600) / 60);
+        const seconds = Math.floor(time % 60);
+        var strDuration = ''
+        if (hours > 0) {
+            strDuration = `${hours}时`
+        }
+        if (minutes > 0) {
+            strDuration += `${minutes}分`
+        }
+        if (seconds > 0) {
+            strDuration += `${seconds}秒`
+        }
+        return strDuration.length == 0 ? '1秒' : strDuration
+    }
+
+    function finish() {
+        if (props.type != WorkoutType.normal) {
+            setTempTime(new Date().getTime())
+            clearInterval(timer)
+            setShowModal(true)
+            return
+        }
+        var array = groups;
+        var time = new Date().getTime()
+        array.push({
+            index: index,
+            time: time,
+            type: 'end'
+        })
+        setIsDoing(false)
+        setStartTime(time)
+        setGroups(array)
+    }
+
+    function start() {
+        var array = groups;
+        var time = new Date().getTime()
+        array.push({
+            index: index + 1,
+            time: time,
+            type: 'start'
+        })
+        setStartTime(time)
+        setIndex(index + 1)
+        setIsDoing(true)
+        setGroups(array)
+    }
+
+    function terminal() {
+        Taro.showModal({
+            title: '提示',
+            content: '确认结束?',
+            success: function (res) {
+                if (res.confirm) {
+                    var array = groups;
+                    var time = new Date().getTime()
+                    array.push({
+                        index: index,
+                        time: time,
+                        type: 'end'
+                    })
+                    setIsDoing(false)
+                    setGroups(array)
+
+                    Taro.navigateTo({
+                        url: '/pages/workout/WorkoutDetail?detail=' + JSON.stringify(array)
+                    })
+                } else if (res.cancel) {
+                    console.log('用户点击取消')
+                }
+            }
+        })
+
+
+    }
+
+    function numChange(e) {
+        setShowModal(false)
+        var array = groups;
+        array.push({
+            index: index,
+            time: tempTime,
+            value: e.length>0?multiItems[0][e[0]]:items[e[0]],
+            value2:e.length>0?multiItems[1][e[1]]:null,
+            type: 'end'
+        })
+        setIsDoing(false)
+        setStartTime(tempTime)
+        setGroups(array)
+        resume()
+    }
+
+    function pickerContent() {
+        var color = ColorType.workout
+        var title = '本组训练'
+        return <View style={{ color: '#fff', backgroundColor: 'transparent' }}>
+            <PickerViews
+                onChange={numChange}
+                items={props.type == WorkoutType.number ? items : multiItems}
+                value={props.type == WorkoutType.number ? selIndex : multiSel}
+                themeColor={color}
+                title={title}
+                showBtns={true}
+                onCancel={() => {
+                    resume()
+                    setShowModal(false)
+                }} />
+        </View>
+    }
+
+    return <View style={{ color: ColorType.workout, flexDirection: 'column', display: 'flex' }}>
+        <Text>平板支撑</Text>
+        <Text>计时训练</Text>
+        {
+            groups.length > 1 && groups.map((item, index) => {
+                if (index == 0) {
+                    return <View />
+                }
+                return <View key={index} style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', width: '100%', color: '#fff' }}>
+                    <Text>{item.type == 'end' ? `第${item.index}组` : '组间休息'}</Text>
+                    {item.type == 'end' && props.type == WorkoutType.number && <Text style={{ color: ColorType.workout }}>{item.value}个</Text>}
+                    {item.type == 'end' && props.type == WorkoutType.multi && <Text style={{ color: ColorType.workout }}>{item.value}x{item.value2}个</Text>}
+                    <Text>{twoTimeDuration(groups[index - 1].time, item.time)}</Text>
+                </View>
+            })
+        }
+        {
+            isDoing && <View style={{ display: 'flex', flexDirection: 'column' }}>
+                <Text>第{index}组</Text>
+                <Text>{durationTime()}</Text>
+                <ChooseScenarioBtn onClick={finish} title="完成本组" background={ColorType.workout} />
+            </View>
+        }
+        {
+            !isDoing && <View style={{ display: 'flex', flexDirection: 'column' }}>
+                <Text>组间休息</Text>
+                <Text>{durationTime()}</Text>
+                <ChooseScenarioBtn onClick={start} title="开始下一组" background={ColorType.workout} />
+            </View>
+        }
+        <Text style={{ color: 'red' }} onClick={terminal}>结束训练</Text>
+        <View style={{ height: 50 }} />
+        <Text>总用时</Text>
+        <View style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', width: '100%' }}>
+            <View style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
+                <Text>计划用时</Text>
+                <Text>{planTime()}</Text>
+            </View>
+            <View style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
+                <Text>已进行</Text>
+                <Text>{groups.length > 0 && TimeFormatter.formateTimeNow(groups[0].time)}</Text>
+            </View>
+            <View style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
+                <Text>{groups.length > 0 && new Date().getTime() > groups[0].time + props.targetCount * 1000 ? '已超时' : '距结束'}</Text>
+                <Text>{groups.length > 0 && TimeFormatter.countdown(groups[0].time + props.targetCount * 1000)}</Text>
+            </View>
+        </View>
+        {
+            showModal && <Modal dismiss={() => {
+                setShowModal(false)
+                resume()
+            }}>
+                {
+                    pickerContent()
+                }
+            </Modal>
+        }
+
+    </View>
+}

+ 8 - 0
src/pages/workout/WorkoutDetail.tsx

@@ -0,0 +1,8 @@
+import Result from "@/features/workout/Result";
+import { View } from "@tarojs/components";
+
+export default function Page(){
+    return <View>
+        <Result />
+    </View>
+}

+ 0 - 0
src/services/workout.tsx


+ 0 - 0
src/store/workout.tsx


+ 8 - 0
src/utils/time_format.ts

@@ -303,6 +303,14 @@ export class TimeFormatter {
     return `${TimeFormatter.padZero(hours)}:${TimeFormatter.padZero(minutes)}:${TimeFormatter.padZero(seconds)}`;
   };
 
+  static workoutTime = (time: number): string => {
+    const hours = Math.floor(time / 3600);
+    const minutes = Math.floor((time % 3600) / 60);
+    const seconds = Math.floor(time % 60);
+    return `${TimeFormatter.padZero(hours)}:${TimeFormatter.padZero(minutes)}:${TimeFormatter.padZero(seconds)}`;
+
+  }
+
   static padZero = (num: number): string => {
     return num.toString().padStart(2, '0');
   };

+ 6 - 0
src/utils/types.ts

@@ -67,4 +67,10 @@ export enum MetricModalType {
     order = 'order',
     add = 'add',
     time = 'time',
+}
+
+export enum WorkoutType {
+    normal = 'normal',
+    number = 'number',
+    multi = 'multi'
 }