more question types support

This commit is contained in:
grey-cat-1908 2024-08-21 11:36:44 +03:00
parent 751f9eabad
commit 1e2ee1c2a3
4 changed files with 320 additions and 14 deletions

View file

@ -46,7 +46,7 @@ const props = defineProps({
}, },
isRequired: { isRequired: {
type: Boolean, type: Boolean,
default: false default: true
} }
}) })

View file

@ -0,0 +1,121 @@
<template>
<div class="selector-component">
<div class="options">
<div
v-for="(option, index) in options"
:key="index"
class="option"
@click="toggleSelection(index)"
:class="{ selected: isSelected(index) }"
>
<span>{{ option.label }}</span>
</div>
</div>
<div v-if="error" class="error">{{ error }}</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
minValues: {
type: Number,
default: 1
},
maxValues: {
type: Number,
default: null
},
options: {
type: Array,
required: true
},
value: {
type: Array,
default: () => []
},
isRequired: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['input'])
const selectedIndexes = ref([...props.value])
const error = ref('')
function toggleSelection(index) {
const isSelected = selectedIndexes.value.includes(index)
if (isSelected) {
selectedIndexes.value = selectedIndexes.value.filter((i) => i !== index)
} else if (!props.maxValues || selectedIndexes.value.length < props.maxValues) {
selectedIndexes.value.push(index)
}
validateSelection()
emit('input', selectedIndexes.value)
}
function isSelected(index) {
return selectedIndexes.value.includes(index)
}
function validateSelection() {
error.value = ''
if (selectedIndexes.value.length < props.minValues) {
error.value = `Необходимо выбрать минимум ${props.minValues} вариантов.`
return
}
if (props.maxValues && selectedIndexes.value.length > props.maxValues) {
error.value = `Необходимо выбрать не больше ${props.maxValues} вариантов.`
return
}
if (props.isRequired && selectedIndexes.value.length === 0) {
error.value = `Выбор обязателен.`
}
}
watch(selectedIndexes, validateSelection)
</script>
<style scoped>
.selector-component {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
.options {
display: flex;
flex-wrap: wrap;
}
.option {
margin: 5px;
padding: 10px;
border: 1px solid #ccc;
cursor: pointer;
display: flex;
align-items: center;
}
.option.selected {
border-color: #42b983;
background-color: #e6f7ff;
}
.option img {
max-width: 50px;
margin-right: 10px;
}
.error {
color: red;
margin-top: 5px;
}
</style>

View file

@ -0,0 +1,148 @@
<template>
<div class="text-question-component">
<input
type="text"
v-model="inputValue"
:maxlength="maxLength"
:required="isRequired"
@input="validateInput"
@blur="validateInput"
/>
<div v-if="error" class="error">{{ error }}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
minLength: {
type: Number,
default: null
},
maxLength: {
type: Number,
default: null
},
validator: {
type: Number,
default: null
},
value: {
type: String,
default: ''
},
isRequired: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['input'])
const inputValue = ref(props.value)
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() {
error.value = ''
if (props.minLength && inputValue.value.length < props.minLength) {
error.value = `Минимальная длина ${props.minLength} символов`
return
}
if (props.validator === 1 && !validateTIN(inputValue.value)) {
error.value = 'Некорректный ИНН'
return
}
if (props.validator === 2 && !validateSNILS(inputValue.value)) {
error.value = 'Некорректный СНИЛС'
return
}
emit('input', inputValue.value)
}
</script>
<style scoped>
.text-question-component {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
.error {
color: red;
margin-top: 5px;
}
</style>

View file

@ -1,7 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { makeAPIRequest } from '@/utils/http' import { makeAPIRequest } from '@/utils/http'
import Scale from '@/components/forms/Scale.vue' import Scale from '@/components/forms/ScaleQuestion.vue'
import TextQuestion from '@/components/forms/TextQuestion.vue'
import SelectorQuestion from '@/components/forms/SelectorQuestion.vue'
const data = ref({}) const data = ref({})
const currentPageNumber = ref(0) const currentPageNumber = ref(0)
@ -18,7 +20,20 @@ async function prepareNewPage() {
}) })
} }
function beforeSubmitValidate() {
for (let question_id of currentPage.value.questions.keys()) {
const question = currentPage.value.questions[question_id]
const answer = answers.value[question.id]
if (question.question_type === 2) {
return question.required && answer.values.length >= question.min_values
}
}
return true
}
async function submitForm() { async function submitForm() {
if (beforeSubmitValidate()) {
if (currentPageNumber.value !== data.value.pages.length - 1) { if (currentPageNumber.value !== data.value.pages.length - 1) {
currentPageNumber.value += 1 currentPageNumber.value += 1
await prepareNewPage() await prepareNewPage()
@ -32,6 +47,7 @@ async function submitForm() {
} }
) )
} }
}
} }
onMounted(async () => { onMounted(async () => {
@ -46,7 +62,28 @@ onMounted(async () => {
<form @submit.prevent="submitForm"> <form @submit.prevent="submitForm">
<div class="" v-for="question in currentPage.questions"> <div class="" v-for="question in currentPage.questions">
<h3>{{ question.label }}</h3>
<p>{{ question.description }}</p>
<TextQuestion
v-if="question.question_type === 1"
:minLength="question.min_length"
:maxLength="question.max_length"
:validator="question.validator"
:isRequired="question.required"
v-model="answers[question.id].value"
@input="answers[question.id].value = $event"
/>
<SelectorQuestion
v-if="question.question_type === 2"
:minValues="question.min_values"
:maxValues="question.max_values"
:options="question.options"
:isRequired="question.required"
v-model="answers[question.id].values"
@input="answers[question.id].values = $event"
/>
<Scale <Scale
v-if="question.question_type === 3"
:min="question.min_value" :min="question.min_value"
:max="question.max_value" :max="question.max_value"
:minLabel="question.min_label" :minLabel="question.min_label"