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

@ -1,9 +1,9 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title> <title>Vite App</title>
</head> </head>
<body> <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", "preview": "vite preview",
"build-only": "vite build", "build-only": "vite build",
"type-check": "vue-tsc --build --force", "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" "format": "prettier --write src"
}, },
"dependencies": { "dependencies": {
@ -18,15 +17,10 @@
"vue-router": "^4.3.3" "vue-router": "^4.3.3"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/node": "^20.14.5", "@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.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", "@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"npm-run-all2": "^6.2.0", "npm-run-all2": "^6.2.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"typescript": "~5.4.0", "typescript": "~5.4.0",

View file

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

View file

@ -1,11 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import {getTitle} from "@/utils/env"; import { getTitle } from '@/utils/env'
</script> </script>
<template> <template>
<h1>{{ getTitle() }}</h1> <h1>{{ getTitle() }}</h1>
</template> </template>
<style scoped> <style scoped></style>
</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({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: "/", path: '/',
name: "Home", name: 'Home',
component: () => import("@/views/Index.vue"), component: () => import('@/views/Index.vue')
},
{
path: '/form/view',
name: 'View Form',
component: () => import('@/views/form/View.vue')
} }
], ],
scrollBehavior(to) { scrollBehavior(to) {
if (to.hash) { if (to.hash) {
return { return {
top: 120, top: 120,
el: to.hash, el: to.hash
}; }
} else { } else {
return { return {
top: 0, top: 0
}; }
} }
}, }
}); })
export default router; export default router

View file

@ -44,10 +44,10 @@ textarea {
margin: 0; margin: 0;
} }
input[type="search"]::-webkit-search-decoration, input[type='search']::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button, input[type='search']::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-results-button, input[type='search']::-webkit-search-results-button,
input[type="search"]::-webkit-search-results-decoration { input[type='search']::-webkit-search-results-decoration {
-webkit-appearance: none; -webkit-appearance: none;
} }

View file

@ -1,4 +1,3 @@
export function getTitle () { 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'
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 setup lang="ts"></script>
</script>
<template> <template></template>
</template>
<style scoped> <style scoped></style>
</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", "extends": "@vue/tsconfig/tsconfig.dom.json",
"include": [ "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"env.d.ts", "exclude": ["src/**/__tests__/*"],
"src/**/*",
"src/**/*.vue"
],
"exclude": [
"src/**/__tests__/*"
],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",

View file

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

939
yarn.lock

File diff suppressed because it is too large Load diff