mirror of
https://github.com/grey-cat-1908/formaptix-web.git
synced 2024-09-22 19:21:59 +03:00
base for view && base for scale question type
This commit is contained in:
parent
924d7ec691
commit
751f9eabad
18 changed files with 3652 additions and 1011 deletions
|
@ -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'
|
||||
}
|
||||
}
|
|
@ -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
3403
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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",
|
||||
|
|
12
src/App.vue
12
src/App.vue
|
@ -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>
|
|
@ -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>
|
||||
|
|
99
src/components/forms/Scale.vue
Normal file
99
src/components/forms/Scale.vue
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
35
src/utils/http.ts
Normal 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
5
src/views/Auth.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script setup lang="ts"></script>
|
||||
|
||||
<template></template>
|
||||
|
||||
<style scoped></style>
|
|
@ -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
63
src/views/form/View.vue
Normal 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>
|
|
@ -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",
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue