mirror of
https://github.com/grey-cat-1908/formaptix-web.git
synced 2024-11-11 18:47:27 +03:00
better validation process
This commit is contained in:
parent
e8c8ca137d
commit
f5680504ac
7 changed files with 146 additions and 115 deletions
|
@ -1,13 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="rating">
|
<div class="rating">
|
||||||
<div class="rating-labels">
|
|
||||||
<div class="rating-labels-info">
|
|
||||||
<PhCaretCircleUpDown :size="23" class="rating-labels-info--sign" />
|
|
||||||
<div class="rating-labels-info--text">
|
|
||||||
Выберите от {{ minLabel }} до {{ maxLabel }} вариантов
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rating-options">
|
<div class="rating-options">
|
||||||
<label v-for="n in range" :key="n" class="rating-option">
|
<label v-for="n in range" :key="n" class="rating-option">
|
||||||
<input
|
<input
|
||||||
|
@ -85,23 +77,6 @@ function cancelSelection() {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
//align-items: center;
|
//align-items: center;
|
||||||
|
|
||||||
&-labels {
|
|
||||||
&-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0 11px;
|
|
||||||
color: var(--color-description);
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
|
|
||||||
&--sign {
|
|
||||||
min-width: 23px;
|
|
||||||
min-height: 23px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-options {
|
&-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="selector">
|
<div class="selector">
|
||||||
|
<div class="selector-labels">
|
||||||
|
<div class="selector-labels-info">
|
||||||
|
<PhCaretCircleUpDown :size="23" class="selector-labels-info--sign" />
|
||||||
|
<div class="selector-labels-info--text">
|
||||||
|
Выберите от {{ minValues }} до {{ Math.min(maxValues, options.length) }} {{ normalizeCountForm(Math.min(maxValues, options.length), ['варианта', 'вариантов', 'вариантов']) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="selector-options">
|
<div class="selector-options">
|
||||||
<div
|
<div
|
||||||
v-for="(option, index) in options"
|
v-for="(option, index) in options"
|
||||||
|
@ -19,7 +27,8 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { PhXCircle } from '@phosphor-icons/vue'
|
import { PhCaretCircleUpDown, PhXCircle } from '@phosphor-icons/vue'
|
||||||
|
import { normalizeCountForm } from '@/utils/formation'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
minValues: {
|
minValues: {
|
||||||
|
@ -69,12 +78,12 @@ function validateSelection() {
|
||||||
error.value = ''
|
error.value = ''
|
||||||
|
|
||||||
if (selectedIndexes.value.length < props.minValues) {
|
if (selectedIndexes.value.length < props.minValues) {
|
||||||
error.value = `Необходимо выбрать минимум ${props.minValues} вариантов.`
|
error.value = `Необходимо выбрать минимум ${props.minValues} ${normalizeCountForm(props.minValues, ['вариант', 'варианта', 'вариантов']) }.`
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.maxValues && selectedIndexes.value.length > props.maxValues) {
|
if (props.maxValues && selectedIndexes.value.length > props.maxValues) {
|
||||||
error.value = `Необходимо выбрать не больше ${props.maxValues} вариантов.`
|
error.value = `Необходимо выбрать не больше ${props.maxValues} ${normalizeCountForm(props.maxValues, ['варианта', 'вариантов', 'вариантов']) }.`
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +100,23 @@ watch(selectedIndexes, validateSelection)
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
&-labels {
|
||||||
|
&-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0 11px;
|
||||||
|
color: var(--color-description);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
&--sign {
|
||||||
|
min-width: 23px;
|
||||||
|
min-height: 23px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-options {
|
&-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { PhXCircle } from '@phosphor-icons/vue'
|
import { PhXCircle } from '@phosphor-icons/vue'
|
||||||
|
import { validateSNILS, validateTIN } from '@/utils/validators'
|
||||||
|
import { normalizeCountForm } from '@/utils/formation'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
minLength: {
|
minLength: {
|
||||||
|
@ -47,92 +49,19 @@ const emit = defineEmits(['input'])
|
||||||
const inputValue = ref(props.value)
|
const inputValue = ref(props.value)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
|
|
||||||
function validateTIN(value) {
|
|
||||||
const len = value.length
|
|
||||||
if (len !== 10 && len !== 12) return false
|
|
||||||
|
|
||||||
const digits = value.split('').map(Number)
|
|
||||||
|
|
||||||
if (len === 10) {
|
|
||||||
const checksum =
|
|
||||||
((2 * digits[0] +
|
|
||||||
4 * digits[1] +
|
|
||||||
10 * digits[2] +
|
|
||||||
3 * digits[3] +
|
|
||||||
5 * digits[4] +
|
|
||||||
9 * digits[5] +
|
|
||||||
4 * digits[6] +
|
|
||||||
6 * digits[7] +
|
|
||||||
8 * digits[8]) %
|
|
||||||
11) %
|
|
||||||
10
|
|
||||||
return digits[9] === checksum
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len === 12) {
|
|
||||||
const checksum1 =
|
|
||||||
((7 * digits[0] +
|
|
||||||
2 * digits[1] +
|
|
||||||
4 * digits[2] +
|
|
||||||
10 * digits[3] +
|
|
||||||
3 * digits[4] +
|
|
||||||
5 * digits[5] +
|
|
||||||
9 * digits[6] +
|
|
||||||
4 * digits[7] +
|
|
||||||
6 * digits[8] +
|
|
||||||
8 * digits[9]) %
|
|
||||||
11) %
|
|
||||||
10
|
|
||||||
const checksum2 =
|
|
||||||
((3 * digits[0] +
|
|
||||||
7 * digits[1] +
|
|
||||||
2 * digits[2] +
|
|
||||||
4 * digits[3] +
|
|
||||||
10 * digits[4] +
|
|
||||||
3 * digits[5] +
|
|
||||||
5 * digits[6] +
|
|
||||||
9 * digits[7] +
|
|
||||||
4 * digits[8] +
|
|
||||||
6 * digits[9] +
|
|
||||||
8 * digits[10]) %
|
|
||||||
11) %
|
|
||||||
10
|
|
||||||
return digits[10] === checksum1 && digits[11] === checksum2
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateSNILS(value) {
|
|
||||||
if (value.length !== 11) return false
|
|
||||||
|
|
||||||
const digits = value.slice(0, 9).split('').map(Number)
|
|
||||||
const checksum = digits.reduce((sum, digit, index) => sum + digit * (9 - index), 0)
|
|
||||||
|
|
||||||
let controlNumber = checksum % 101
|
|
||||||
if (controlNumber === 100 || controlNumber === 101) {
|
|
||||||
controlNumber = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return controlNumber === Number(value.slice(-2))
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateInput() {
|
function validateInput() {
|
||||||
error.value = ''
|
error.value = ''
|
||||||
|
|
||||||
if (props.minLength && inputValue.value.length < props.minLength) {
|
if (props.isRequired || inputValue.value) {
|
||||||
error.value = `Минимальная длина ${props.minLength} символов`
|
if (props.minLength && inputValue.value.length < props.minLength) {
|
||||||
return
|
error.value = `Минимальная длина - ${props.minLength} ${normalizeCountForm(props.minValues, ['символ', 'символа', 'символов']) }`
|
||||||
}
|
}
|
||||||
|
if (props.validator === 1 && !validateTIN(inputValue.value)) {
|
||||||
if (props.validator === 1 && !validateTIN(inputValue.value)) {
|
error.value = 'Некорректный ИНН'
|
||||||
error.value = 'Некорректный ИНН'
|
}
|
||||||
return
|
if (props.validator === 2 && !validateSNILS(inputValue.value)) {
|
||||||
}
|
error.value = 'Некорректный СНИЛС'
|
||||||
|
}
|
||||||
if (props.validator === 2 && !validateSNILS(inputValue.value)) {
|
|
||||||
error.value = 'Некорректный СНИЛС'
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('input', inputValue.value)
|
emit('input', inputValue.value)
|
||||||
|
|
8
src/stores/formView.ts
Normal file
8
src/stores/formView.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useFormViewStore = defineStore('formView', () => {
|
||||||
|
const error = ref('');
|
||||||
|
|
||||||
|
return { error }
|
||||||
|
})
|
4
src/utils/formation.ts
Normal file
4
src/utils/formation.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export function normalizeCountForm (number: number, words_arr: Array<string>) {
|
||||||
|
let options = [2, 0, 1, 1, 1, 2];
|
||||||
|
return words_arr[(number % 100 > 4 && number % 100 < 20) ? 2 : options[(number % 10 < 5) ? number % 10 : 5]];
|
||||||
|
}
|
69
src/utils/validators.ts
Normal file
69
src/utils/validators.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
export function validateTIN(value: String) {
|
||||||
|
const len = value.length
|
||||||
|
if (len !== 10 && len !== 12) return false
|
||||||
|
|
||||||
|
const digits = value.split('').map(Number)
|
||||||
|
|
||||||
|
if (len === 10) {
|
||||||
|
const checksum =
|
||||||
|
((2 * digits[0] +
|
||||||
|
4 * digits[1] +
|
||||||
|
10 * digits[2] +
|
||||||
|
3 * digits[3] +
|
||||||
|
5 * digits[4] +
|
||||||
|
9 * digits[5] +
|
||||||
|
4 * digits[6] +
|
||||||
|
6 * digits[7] +
|
||||||
|
8 * digits[8]) %
|
||||||
|
11) %
|
||||||
|
10
|
||||||
|
return digits[9] === checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len === 12) {
|
||||||
|
const checksum1 =
|
||||||
|
((7 * digits[0] +
|
||||||
|
2 * digits[1] +
|
||||||
|
4 * digits[2] +
|
||||||
|
10 * digits[3] +
|
||||||
|
3 * digits[4] +
|
||||||
|
5 * digits[5] +
|
||||||
|
9 * digits[6] +
|
||||||
|
4 * digits[7] +
|
||||||
|
6 * digits[8] +
|
||||||
|
8 * digits[9]) %
|
||||||
|
11) %
|
||||||
|
10
|
||||||
|
const checksum2 =
|
||||||
|
((3 * digits[0] +
|
||||||
|
7 * digits[1] +
|
||||||
|
2 * digits[2] +
|
||||||
|
4 * digits[3] +
|
||||||
|
10 * digits[4] +
|
||||||
|
3 * digits[5] +
|
||||||
|
5 * digits[6] +
|
||||||
|
9 * digits[7] +
|
||||||
|
4 * digits[8] +
|
||||||
|
6 * digits[9] +
|
||||||
|
8 * digits[10]) %
|
||||||
|
11) %
|
||||||
|
10
|
||||||
|
return digits[10] === checksum1 && digits[11] === checksum2
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateSNILS(value: String) {
|
||||||
|
if (value.length !== 11) return false
|
||||||
|
|
||||||
|
const digits = value.slice(0, 9).split('').map(Number)
|
||||||
|
const checksum = digits.reduce((sum, digit, index) => sum + digit * (9 - index), 0)
|
||||||
|
|
||||||
|
let controlNumber = checksum % 101
|
||||||
|
if (controlNumber === 100 || controlNumber === 101) {
|
||||||
|
controlNumber = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return controlNumber === Number(value.slice(-2))
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ import FormNotFound from '@/components/FormNotFound.vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import { PhInfo, PhCardsThree } from '@phosphor-icons/vue'
|
import { PhInfo, PhCardsThree } from '@phosphor-icons/vue'
|
||||||
|
import { validateSNILS, validateTIN } from '@/utils/validators'
|
||||||
|
import { useFormViewStore } from '@/stores/formView'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
@ -19,6 +21,8 @@ const answers = ref([])
|
||||||
const isFormNotFound = ref(true)
|
const isFormNotFound = ref(true)
|
||||||
const isSent = ref(false)
|
const isSent = ref(false)
|
||||||
|
|
||||||
|
const formViewStore = useFormViewStore;
|
||||||
|
|
||||||
async function prepareNewPage() {
|
async function prepareNewPage() {
|
||||||
currentPage.value = data.value.pages[currentPageNumber.value]
|
currentPage.value = data.value.pages[currentPageNumber.value]
|
||||||
currentPage.value.questions.forEach((q) => {
|
currentPage.value.questions.forEach((q) => {
|
||||||
|
@ -34,7 +38,14 @@ function beforeSubmitValidate() {
|
||||||
const question = currentPage.value.questions[question_id]
|
const question = currentPage.value.questions[question_id]
|
||||||
const answer = answers.value[question.id]
|
const answer = answers.value[question.id]
|
||||||
|
|
||||||
if (question.question_type === 2) {
|
if (!answer) return false;
|
||||||
|
if (question.required && !(answer.value || answer.values)) return false;
|
||||||
|
|
||||||
|
if (question.question_type === 1 && answer.value) {
|
||||||
|
if (!question.validator && question.min_length && answer.value.length < question.min_length) return false;
|
||||||
|
if (question.validator === 1 && !validateTIN(answer.value)) return false;
|
||||||
|
if (question.validator === 2 && !validateSNILS(answer.value)) return false;
|
||||||
|
} else if (question.question_type === 2) {
|
||||||
return question.required && answer.values.length >= question.min_values
|
return question.required && answer.values.length >= question.min_values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +58,14 @@ async function submitForm() {
|
||||||
currentPageNumber.value += 1
|
currentPageNumber.value += 1
|
||||||
await prepareNewPage()
|
await prepareNewPage()
|
||||||
} else {
|
} else {
|
||||||
|
for (let page of data.value.pages) {
|
||||||
|
page.questions.forEach((q) => {
|
||||||
|
let answer = answers.value[q.id];
|
||||||
|
if (!q.required && !(answer.value || answer.values)) {
|
||||||
|
delete answers.value[q.id];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
await makeAPIRequest(
|
await makeAPIRequest(
|
||||||
'/answer/create',
|
'/answer/create',
|
||||||
'POST',
|
'POST',
|
||||||
|
@ -81,8 +100,8 @@ onMounted(async () => {
|
||||||
<div v-else class="view-form">
|
<div v-else class="view-form">
|
||||||
<div class="view-form-title view-form-container default-card">
|
<div class="view-form-title view-form-container default-card">
|
||||||
<div class="view-form-info">
|
<div class="view-form-info">
|
||||||
<PhCardsThree :size="23" class="view-form-info--sign" />
|
<PhCardsThree :size="23" class="view-form-info--sign" :class="{ 'form-red': formViewStore.error }" />
|
||||||
<div class="view-form-info--text">Страница 1 из 1</div>
|
<div class="view-form-info--text">Страница {{ currentPageNumber + 1 }} из {{ data.pages.length }}</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="form-title">{{ data.name }}</h2>
|
<h2 class="form-title">{{ data.name }}</h2>
|
||||||
<p class="form-description">{{ currentPage.text }}</p>
|
<p class="form-description">{{ currentPage.text }}</p>
|
||||||
|
@ -94,6 +113,7 @@ onMounted(async () => {
|
||||||
<h3 class="form-q-title">{{ question.label }}</h3>
|
<h3 class="form-q-title">{{ question.label }}</h3>
|
||||||
<p class="form-q-description">{{ question.description }}</p>
|
<p class="form-q-description">{{ question.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<img v-if="question.image_url" :src="question.image_url" alt="image by user" />
|
||||||
<TextQuestion
|
<TextQuestion
|
||||||
v-if="question.question_type === 1"
|
v-if="question.question_type === 1"
|
||||||
:minLength="question.min_length"
|
:minLength="question.min_length"
|
||||||
|
|
Loading…
Reference in a new issue