Используя Vue и помочь форма проверки с вес для возраста


Я рефакторинга очень старый веб-страницу, которая вычисляет болюс и вливание дозы препарата для детей от преждевременного до молодой взрослой жизни.

Одна из самых распространенных ошибок препарата случайное в 10 раз меньше или передозировка. Данный бланк должен как вес и возраст, чтобы произвести соответствующее диаграмме, казалось мудрым, чтобы искать статистические данные и разработать процентиль (кумулятивное стандартное нормальное распределение) вес по возрасту и применяются мягкие и жесткие ограничения, с флажком говорю, что значения, превышающие пределы были завизированы. Полный код (включая вспомогательные функции по ссылке ниже) на Гитхабе.

Вопросы:

  • Является широкое использование для упаковки данных p_varName & вычисленный геттеров & сеттеров шаблон для каждого "смотрел" переменная лучший подход, так что в любое время возраст, пол, вес или беременности изменений, центиль (+/- предупреждения по мере необходимости) обновляются?
  • Это правильная вещь, чтобы иметь диапазон процентили [предзагрузка] тревога как компонент? Это не будет использоваться, но он чувствовал себя чище, чтобы разделять код/цели. Есть ли лучший способ добиться этого разделения?
  • Это испускание 'validCentile' событие, когда действительность меняется в лучшую сторону - или я должен использовать слот с правильными свойство родительской области (весовой коэффициент)?
  • Все данные свойства являются "плоскими" - т. е. не вложенные - бы читабельность/debugability/общие утонченность быть улучшены свойства вложенности?

Любые мысли (в том числе не относящиеся к вышеуказанным пунктам) очень ценится.

<!-- src/components/weightage.vue -->
<template>
    <div class="weightAge">
        <fieldset class="form-group">
            <div class="form-row">
                <legend class="col-form-label col-sm-2 pt-0">Gender</legend>
                <div class="col-sm-10 gender">
                    <div class="form-check form-check-inline" id="male">
                        <input type="radio" name="gender" id="maleRadio" :value="true" class="form-check-input" v-model="isMale" />
                        <label class="form-check-label" for="maleRadio">
                            Male
                        </label>
                    </div>
                    <div class="form-check form-check-inline" id="female">
                        <input type="radio" name="gender" id="femaleRadio" :value="false" class="form-check-input" v-model="isMale" />
                        <label class="form-check-label" for="femaleRadio">
                            Female
                        </label>
                    </div>
                </div>
            </div>
        </fieldset>
        <div class="form-group form-row">
            <label class="col-sm-2 col-form-label" for="Weight" >Weight</label>
            <div class="input-group col-sm-10">
                <input id="Weight" type=number min="0.2" max="400" class="form-control" v-model.number="weight" required />
                <div class="input-group-append">
                    <div class="input-group-text">Kg</div>
                </div>
            </div>
        </div>
        <centile-range :lowerCentile="lowerCentile" :upperCentile="upperCentile"></centile-range>
        <div class="form-group form-row">
            <label class="col-sm-2 col-form-label" for="dob">Date of Birth</label>
            <div class="col-sm-10">
                <input class="form-control" type="date" :max="today" v-model="dob" id="dob" />
            </div>
            <span class="text-danger"></span>
        </div>
        <fieldset class="form-group">
            <div class="form-row">
                <legend class="col-form-label col-sm-2 pt-0">Age</legend>
                <div class="col-sm-10 age form-inline">
                        <div class="input-group mb-1">
                            <input type="number" step="1" min="0" max="130" v-model.number="years" id="age-years" class="form-control" />
                            <div class="input-group-append">
                                <div class="input-group-text">years</div>
                            </div>
                        </div>
                        <div class="input-group mb-1">
                            <input type="number" step="1" min="0" max="37" v-model.number="months" id="age-months" class="form-control" />
                            <div class="input-group-append">
                                <div class="input-group-text">months</div>
                            </div>
                        </div>
                        <div class="input-group mb-1">    
                            <input type="number" step="1" min="0" max="90" v-model.number="days" id="age-days" class="form-control" />
                            <div class="input-group-append">
                                <div class="input-group-text">days</div>
                            </div>
                        </div>
                </div>
            </div>
        </fieldset>
        <div class="form-group form-row">
            <label class="col-sm-2 col-form-label" for="GestationAtBirth" >Birth Gestation</label>
            <div class="input-group col-sm-10">
                <input id="GestationAtBirth" type=number min="23" max="43" step="1" class="form-control" v-model="gestation" required/>
                <div class="input-group-append">
                    <div class="input-group-text">weeks</div>
                </div>
            </div>
            <small id="nhiHelp" class="form-text text-muted">for checking weight is correct for age</small>
        </div>
    </div>
</template>

<script lang="ts">
import Vue from 'vue'
import * as moment from 'moment'
import * as ageHelper from './AgeHelper'
import { UKWeightData } from '../../CentileData/UkWeightData'
import './centilerange.vue'

const _wtCentiles = new UKWeightData(); 
export default Vue.extend({
    data:function(){
        return {
            p_weight: null as null | number,
            p_isMale: null as null | boolean,
            p_gestation: 40,
            today: moment().format(ageHelper.dateFormat),
            p_dob: '',
            p_years: null as null | number,
            p_months: null as null | number,
            p_days: null as null | number,
            ageDaysLb:null as null| number,
            ageDaysUb:null as null | number,
            lowerCentile:null as null | number,
            upperCentile: null as null | number
        }
    }, 
    //components:{centilerange},
    computed:{
        'weight': {
            get: function (this:any) {
                return this.p_weight;
            },
            set: function (newVal: any) {
                this.p_weight = newVal || newVal === 0
                    ?newVal as number
                    :null;
                this.setCentiles();
            }
        },
        'gestation': {
            get: function (this:any) {
                return this.p_gestation;
            },
            set: function (newVal: number) {
                this.p_gestation = newVal;
                this.setCentiles();
            }
        },
        'isMale': {
            get: function (this:any) {
                return this.p_isMale;
            },
            set: function (newVal: any) {
                this.p_isMale = typeof newVal  === 'boolean'
                    ?newVal
                    :null;
                this.setCentiles();
            }
        },
        'days': {
            get: function (this:any) {
                return this.p_days;
            },
            set: function (newVal: any) {
                this.p_days = newVal || newVal === 0
                    ?newVal as number
                    :null;
                this.setAgeBounds();
            }
        },
        'months': {
            get: function (this:any) {
                return this.p_months;
            },
            set: function (newVal: number | string) {
                this.p_months = newVal || newVal === 0
                    ?newVal as number
                    :null;
                this.setAgeBounds();
            }
        },
        'years': {
            get: function (this:any) {
                return this.p_years;
            },
            set: function (newVal: number | string) {
                this.p_years = newVal || newVal === 0
                    ?newVal as number
                    :null;
                this.setAgeBounds();
            }
        },
        'dob': {
            get: function (this:any) {
                return this.p_dob;
            },
            set: function (newVal: string) {
                this.p_dob = newVal;
                const ageData = ageHelper.daysOfAgeFromDob(newVal);
                if (ageData){
                    this.p_years = ageData.years;
                    this.p_months = ageData.months;
                    this.p_days = ageData.days;
                    this.ageDaysUb = this.ageDaysLb = ageData.totalDays;
                }
                this.setCentiles();
            }
        }
    },
    methods:{
        setAgeBounds(){
            let bounds = ageHelper.totalDaysOfAge(this.p_years, this.p_months, this.p_days);
            if (bounds === null){
                this.ageDaysLb = this.ageDaysUb = null;
            } else {
                this.ageDaysLb = bounds.Min;
                this.ageDaysUb = bounds.Max;
            }
            this.setCentiles();
        },
        setCentiles(){
            if (!this.p_weight || this.ageDaysLb===null){
                this.lowerCentile = this.upperCentile = null;
            } else {
                this.lowerCentile = 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysUb as number, this.p_isMale === false ? false : true, this.p_gestation);
                this.upperCentile = this.ageDaysUb === this.ageDaysLb && this.p_isMale !== null
                    ? this.lowerCentile
                    : 100 * _wtCentiles.cumSnormForAge(this.p_weight, this.ageDaysLb, !!this.p_isMale, this.p_gestation);
            }
        }
    },
    created: function () {
        let self = this;
        ageHelper.onNew('day', function (newDate) {
            self.today = newDate;
        })
    }
});
</script>

И дочерний компонент, который отображает центили, и, при необходимости, флажок для подтверждения каких-либо предупреждений:

<!-- src/components/centilerange.vue -->
<template>
    <div class="centile" v-show="lowerVal" :class="{alert: true, 'alert-info':!warnCrossed, 'alert-warning':warnCrossed && !limitCrossed, 'alert-danger':limitCrossed }">
        <span class="lower">{{lowerVal}}<sup>{{lowerSuffix}}</sup></span>
        <span v-if="upperVal!==lowerVal">
            - 
            <span class="upper">{{upperVal}}<sup>{{upperSuffix}}</sup></span>
        </span>
        <span class="centileDescr">
            centile
        </span>
        <div v-if="warnCrossed">
            only 1 in {{denominator}}
            <span v-if="largeNumWord">
                {{largeNumWord}} 
                <small>
                    (10<sup>{{largeNumExp10}}</sup>) 
                </small>
            </span>
            weigh {{moreLess}}. 
            <div v-if="!limitCrossed" class="form-check form-check-inline">
                <input type="checkbox" id="acceptCentile" :checked="acceptWarning" class="form-check-input" required/> 
                <label for="acceptCentile" class="form-check-label">I confirm this is the correct weight</label>
            </div>
        </div>
    </div>
</template>


<script lang="ts">
import Vue from 'vue'
import { largeNumberWords, getSuffix } from '../../Utilities/NumberToWords';

const warnCentileUbound = 99;
const warnCentileLbound = 1;
const limitCentileUbound = 100 - 1e-7;
const limitCentileLbound = 1e-12;
export default Vue.component("centile-range",{
    props:[
        'lowerCentile',
        'upperCentile'
    ],
    data:function(){
        return {
            p_warnCrossed: false,
            p_limitCrossed: false,
            p_isValid:false,
            p_acceptWarning:false,
            lowerVal: '',
            lowerSuffix: '',
            upperVal:'',
            upperSuffix:'',
            moreLess:'',
            denominator:'',
            largeNumWord:'',
            largeNumExp10:null as null | number
        }
    },
    computed:{
        limitCrossed:{
            get:function(this:any){
                return this.p_limitCrossed;
            },
            set:function(newVal:boolean){
                this.p_limitCrossed = newVal;
                this.setValidity();
            }
        },
        warnCrossed:{
            get:function(this:any){
                return this.p_warnCrossed;
            },
            set:function(newVal:boolean){
                this.p_warnCrossed = newVal;
                this.setValidity();
            }
        },
        acceptWarning:{
            get:function(this:any){
                return this.p_acceptWarning;
            },
            set:function(newVal:boolean){
                this.p_acceptWarning = newVal;
                this.setValidity();
            }
        }
    },
    watch:{
        lowerCentile:function(newVal:number | null){
            this.setWarnings();
            if (newVal || newVal === 0){
                let c = centileText(newVal);
                this.lowerVal = c.centile;
                this.lowerSuffix = c.suffix;
            } else {
                this.lowerVal = this.lowerSuffix = '';
            }
            //this.sameVal = this.lowerVal === this.upperVal;
        },
        upperCentile:function(newVal: number | null){
            this.setWarnings();
            if (newVal || newVal === 0){
                let c = centileText(newVal);
                this.upperVal = c.centile;
                this.upperSuffix = c.suffix;
            } else {
                this.upperVal = this.upperSuffix = '';
            }
            //this.sameVal = this.lowerVal === this.upperVal;
        }
    },
    methods:{
        setValidity(){
            const isValid = !(this.p_limitCrossed || (this.p_warnCrossed && !this.p_acceptWarning));
            const emit = this.p_isValid !== isValid;
            this.p_isValid = isValid;
            if (emit){
                this.$emit("validCentile", this.p_isValid);
            }
        },
        setWarnings(){
            const self = this;
            if (this.upperCentile === null && this.upperCentile === null){
                this.warnCrossed = this.limitCrossed = false;
                clearNum();
            } else {
                let minVal = this.lowerCentile === null
                    ? this.upperCentile
                    : this.lowerCentile;
                 let maxVal = this.upperCentile === null
                    ? this.lowerCentile as number
                    : this.upperCentile;
                this.limitCrossed = maxVal < limitCentileLbound || minVal >limitCentileUbound;
                this.warnCrossed = maxVal < warnCentileLbound || minVal > warnCentileUbound;
                if (this.limitCrossed || this.warnCrossed){
                    let denom;
                    if (maxVal < warnCentileLbound){
                        denom = 100/maxVal;
                        this.moreLess = "less";
                    } else {
                        denom = 100/(100-maxVal);
                        this.moreLess = "more";
                    }
                    let words = largeNumberWords(denom);
                    this.denominator = words.digits;
                    this.largeNumWord = words.suffixName;
                    this.largeNumExp10 = words.exp10;
                } else {
                    clearNum();
                }
            }

            function clearNum(){
                self.moreLess= self.denominator=self.largeNumWord='';
                self.largeNumExp10 = null;
            }
        }
    }
});

function centileText(centile:number){
    let l = Math.round(centile);
    if (l < 1){
        return {centile:"<1", suffix: "st" }
    }
    if (l >= 100){
        return {centile:">99", suffix: "th" }
    }
    return {centile:l.toString(), suffix: getSuffix(l)};
}
</script>


225
1
задан 7 февраля 2018 в 05:02 Источник Поделиться
Комментарии
1 ответ

Я начну, что я могу читать машинопись до определенной степени, но я никогда не использовал машинопись с Vue раньше, поэтому я не могу прокомментировать ваше использование машинописного текста здесь.

Побочные эффекты в вычисляемых свойств

Все вычисляемые свойства имеют побочные эффекты в их функции. Этот побочный эффект в расчете значения двух разных переменных. Старайтесь избегать этого: держите ваши вычисляемые свойства простых и только рассчитают , что ценность в том, что вычисление функции. Если другой переменной зависит от вашего вычисляемое свойство, просто положить, что один в вычисляемое свойство, а также. Вью будет обрабатывать зависимостей и пересчитать ее.

Если вам нужно иметь какой-то побочный эффект, например, чтобы вызвать расчет debounced за то, что занимает значительное количество времени или сделать вызов API, создать вместо сторожа:

import myApiCall from 'api/myApi';

// ES5 syntax
export default {
computed: {
somethingImportant() {
return Math.floor(Math.random() * 10);
}
},

watch: {
somethingImportant(newValue) {
myApiCall(newValue).then((result) => {
console.log(result);
});
}
}
}

Делая это таким образом, вы сделать код легче читать. Вычисляемое свойство теперь просто вычисляет что-то из других данных. Наблюдатель теперь заботится о всех побочных эффектах.

Ваши вопросы


Является широкое использование для упаковки данных p_varName & вычисленный геттеров &
сеттеры шаблон для каждого "смотрел" переменная лучший подход, так
что в любое время возраст, пол, вес или беременности изменений, процентиль
(+/- предупреждения по мере необходимости) обновляются?

См. выше. Лучшим подходом является использование вычисляемых свойств и наблюдателей.


Это правильная вещь, чтобы иметь диапазон процентили [предзагрузка] оповещение
компонент? Это не будет использоваться, но это было чище
отделять код/цели. Есть ли лучший способ для достижения
такое разделение?

Когда вы можете извлечь то, что чувствует себя "полной" из компонента и не обязательно пройти половину внутреннего состояния на этот компонент также извлекать такой части в отдельный компонент мог бы быть хорошим выбором. Если вам нужно передать значительный объем внутреннего состояния нового компонента, это означает, что компонент не как отдельные, как вы когда-то думали.

Извлечение компонента позволяет извлекать от своего внутреннего состояния далеко. Если все сделано правильно, это означает, что теперь у вас есть две более простые составные части, которые легче понять и смысл происходящего. Я думаю, что ваша добыча компонента centilerange является хорошим выбором.


Это испускание 'validCentile' событие, когда действительность меняется в лучшую
или я должен использовать слот с правильными собственность на родителей
сфера (значения)?

Если вам нужно передать данные от ребенка до прямого родителя, использовать события. Если вам нужно передать данные из компонента предка, или семьи, рассмотрите возможность использования либо мероприятие Автобус (например, фиктивная составляющая связана с this.$bus), или vuex магазине мутация.

Если вы используете value собственность и input событие, вы можете использовать v-model синтаксический сахар в родительский компонент. Если вы используете только случае передать значение обратно, это, наверное, лучший способ.


Все свойства данных "плоской", т. е. не вложенные - бы
читабельность//общие debugability утонченность быть улучшены путем вложения
свойства?

Это в основном личные предпочтения. Я бы порекомендовал группирования аналогичных свойств в одном объекте, чтобы избежать необходимости иметь постоянно растущий список свойств, которые вы должны пройти, чтобы дочерний объект. Прекрасным примером может быть объектом конфигурации. Имейте в виду, что нужно как-то задокументировать то, что требуется ключи и какие ключи являются необязательными в этот объект, и то, что они примерно означают. Так как вы передаете объект, эта информация не сразу понятно, как проходить отдельные свойства вокруг.

Некоторые другие отзывы

Вы используете один и тот же общий порядок составных "частей" (данных, методов, методов жизненного цикла), который является хорошей вещью.

Рассмотрим исключением встроенного объекта для определения класса, и переместить его на вычисляемое свойство вместо этого. Это позволит сделать его легче, чтобы добавить или удалить классы, и сделать его яснее, когда вы сделаете имена динамических классов, и сделает короче ваш HTML-строк. Рассмотреть вопрос о переносе атрибутов в новой линии, когда ваши линии слишком долго.

Рассмотрите возможность использования синтаксиса ES6 в другое. Я вижу, вы используете let/const и деструктурируется, но есть гораздо более приятные вещи в ЕС6. Такие вещи, как способ shorthands и операторов приходят на ум.

Рассмотрим факторинг из любой функции, которая не является экспортной составляющей в отдельный файл. Если вы не верите, что он принадлежит к внутренней структуре компонента, он, вероятно, не принадлежат в один файл компонента.

Второй компонент файл нуждается в некоторой очистке. Я не думаю, что надо определить функцию внутри setWarnings. Просто задайте его в methods объект и называют его this.clearNum(). Есть некоторые операторы, которые несовместимы пробелы вокруг них.

1
ответ дан 6 марта 2018 в 10:03 Источник Поделиться