base for view && base for scale question type

This commit is contained in:
grey-cat-1908 2024-08-20 21:39:20 +03:00
parent 924d7ec691
commit 751f9eabad
18 changed files with 3652 additions and 1011 deletions

View file

@ -1,15 +0,0 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

View file

@ -5,4 +5,4 @@
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}
}

View file

@ -1,9 +1,9 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>

3403
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,6 @@
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint formaptix-web --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src"
},
"dependencies": {
@ -18,15 +17,10 @@
"vue-router": "^4.3.3"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"npm-run-all2": "^6.2.0",
"prettier": "^3.2.5",
"typescript": "~5.4.0",

View file

@ -1,6 +1,6 @@
<template>
<div class="root" id="root">
<Header/>
<Header />
<router-view v-slot="{ Component }">
<div class="layout">
<transition name="page" mode="out-in">
@ -20,23 +20,23 @@ import Header from '@/components/Header.vue'
.page-leave-active {
transition-timing-function: ease;
transition-delay: 15ms;
transition-duration: .15s;
transition-duration: 0.15s;
transition-property: opacity;
overflow: hidden
overflow: hidden;
}
.page-enter-from,
.page-leave-to {
opacity: 0
opacity: 0;
}
.root {
display: flex;
flex-direction: column;
min-height: 100vh
min-height: 100vh;
}
.layout {
display: flex;
flex-direction: column;
width: 100%;
flex: 1
flex: 1;
}
</style>
</style>

View file

@ -1,11 +1,9 @@
<script setup lang="ts">
import {getTitle} from "@/utils/env";
import { getTitle } from '@/utils/env'
</script>
<template>
<h1>{{ getTitle() }}</h1>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -0,0 +1,99 @@
<template>
<div class="rating-component">
<div class="labels">
<span>{{ minLabel }}</span>
<span>{{ maxLabel }}</span>
</div>
<div class="rating-options">
<label v-for="n in range" :key="n" class="rating-option">
<input
type="radio"
:value="n"
v-model="selectedValue"
:required="isRequired"
@change="updateValue"
/>
<span>{{ n }}</span>
</label>
</div>
<button v-if="selectedValue !== null" @click="cancelSelection">Отменить выбор</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
min: {
type: Number,
required: true
},
max: {
type: Number,
required: true
},
minLabel: {
type: String,
required: true
},
maxLabel: {
type: String,
required: true
},
value: {
type: Number,
default: null
},
isRequired: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['input'])
const selectedValue = ref(props.value)
const range = computed(() => {
return Array.from({ length: props.max - props.min + 1 }, (_, i) => props.min + i)
})
function updateValue() {
emit('input', selectedValue.value)
}
function cancelSelection() {
selectedValue.value = null
emit('input', selectedValue.value)
}
</script>
<style scoped>
.rating-component {
display: flex;
flex-direction: column;
align-items: center;
}
.labels {
display: flex;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
}
.rating-options {
display: flex;
justify-content: space-between;
width: 100%;
}
.rating-option {
display: flex;
flex-direction: column;
align-items: center;
}
button {
margin-top: 10px;
}
</style>

View file

@ -1,26 +1,31 @@
import { createRouter, createWebHistory } from "vue-router";
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "Home",
component: () => import("@/views/Index.vue"),
path: '/',
name: 'Home',
component: () => import('@/views/Index.vue')
},
{
path: '/form/view',
name: 'View Form',
component: () => import('@/views/form/View.vue')
}
],
scrollBehavior(to) {
if (to.hash) {
return {
top: 120,
el: to.hash,
};
el: to.hash
}
} else {
return {
top: 0,
};
top: 0
}
}
},
});
}
})
export default router;
export default router

View file

@ -44,10 +44,10 @@ textarea {
margin: 0;
}
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-results-button,
input[type="search"]::-webkit-search-results-decoration {
input[type='search']::-webkit-search-decoration,
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-results-button,
input[type='search']::-webkit-search-results-decoration {
-webkit-appearance: none;
}
@ -78,4 +78,4 @@ body,
&-narrow {
max-width: 1440px;
}
}
}

View file

@ -1,4 +1,3 @@
export function getTitle () {
console.log(import.meta.env.VITE_APP_NAME)
return import.meta.env.VITE_APP_NAME ? import.meta.env.VITE_APP_NAME: 'Formaptix';
}
export function getTitle() {
return import.meta.env.VITE_APP_NAME ? import.meta.env.VITE_APP_NAME : 'Formaptix'
}

35
src/utils/http.ts Normal file
View file

@ -0,0 +1,35 @@
export function makeAPIRequest(
path: string = '',
method: string = 'GET',
query: any = {},
body: any = {},
useAuthorization: boolean = false
): Promise<any> {
return new Promise(async (resolve, _) => {
let options: any = {
method,
headers: {
'Content-Type': 'application/json'
}
}
if (useAuthorization) options.headers.Authorization = `${localStorage.getItem('auth_token')}`
if (method !== 'GET') {
options.body = JSON.stringify(body)
}
let finalPath = import.meta.env.VITE_API_URL + path
if (query) {
finalPath += '?' + new URLSearchParams(query)
}
const r = await fetch(finalPath, options)
.then((r) => r.json())
.catch((err) => {
console.error(err)
return resolve({ error: 'CRITICAL_ERROR' })
})
return resolve(r.data)
})
}

5
src/views/Auth.vue Normal file
View file

@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template></template>
<style scoped></style>

View file

@ -1,9 +1,5 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
</template>
<template></template>
<style scoped>
</style>
<style scoped></style>

63
src/views/form/View.vue Normal file
View file

@ -0,0 +1,63 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { makeAPIRequest } from '@/utils/http'
import Scale from '@/components/forms/Scale.vue'
const data = ref({})
const currentPageNumber = ref(0)
const currentPage = ref({})
const answers = ref([])
async function prepareNewPage() {
currentPage.value = data.value.pages[currentPageNumber.value]
currentPage.value.questions.forEach((q) => {
answers.value[q.id] = {
question_id: q.id,
question_type: q.question_type
}
})
}
async function submitForm() {
if (currentPageNumber.value !== data.value.pages.length - 1) {
currentPageNumber.value += 1
await prepareNewPage()
} else {
await makeAPIRequest(
'/answer/create',
'POST',
{ form_id: 1 },
{
values: Array.from(Object.keys(answers.value).map((val) => answers.value[val]))
}
)
}
}
onMounted(async () => {
data.value = await makeAPIRequest('/form/get', 'GET', { id: 1 })
await prepareNewPage()
})
</script>
<template>
<h2>{{ data.name }}</h2>
<p>{{ currentPage.text }}</p>
<form @submit.prevent="submitForm">
<div class="" v-for="question in currentPage.questions">
<Scale
:min="question.min_value"
:max="question.max_value"
:minLabel="question.min_label"
:maxLabel="question.max_label"
:isRequired="question.required"
v-model="answers[question.id].value"
@input="answers[question.id].value = $event"
/>
</div>
<button type="submit">Отправить</button>
</form>
</template>
<style scoped></style>

View file

@ -1,13 +1,7 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": [
"env.d.ts",
"src/**/*",
"src/**/*.vue"
],
"exclude": [
"src/**/__tests__/*"
],
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",

View file

@ -5,9 +5,7 @@ import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))

939
yarn.lock

File diff suppressed because it is too large Load diff