Merge pull request #1328 from microsoft/upgrade-quiz-app

upgrading app to vue 3
This commit is contained in:
chris 2024-09-02 19:26:17 +01:00 коммит произвёл GitHub
Родитель 115a56dbca fbe78fffc4
Коммит d0cf3ac4eb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
33 изменённых файлов: 1715 добавлений и 19760 удалений

11
quiz-app/.eslintrc.cjs Normal file
Просмотреть файл

@ -0,0 +1,11 @@
/* eslint-env node */
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

Просмотреть файл

@ -1,8 +0,0 @@
module.exports = {
extends: [
'plugin:vue/vue3-recommended',
],
rules: {
'vue/multi-word-component-names': 'off'
}
}

29
quiz-app/.gitignore поставляемый
Просмотреть файл

@ -1 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.vscode
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

5
quiz-app/.vscode/settings.json поставляемый
Просмотреть файл

@ -1,5 +0,0 @@
{
"staticWebApps.appSubpath": "/",
"staticWebApps.apiSubpath": "api",
"staticWebApps.outputSubpath": "dist"
}

Просмотреть файл

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Arpan Adhikari
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Просмотреть файл

@ -1,113 +1,35 @@
# Quizzes
# Quiz app
These quizzes are the pre- and post-lecture quizzes for the web development for beginners curriculum at https://aka.ms/webdev-beginners
This quiz app helps you display questions and answers in a quiz format.
## Project setup
## Install the app
```
Run `npm install` to install the dependencies.
```sh
npm install
```
### Compiles and hot-reloads for development
## Build the app
```
npm run serve
```
To build the solution, run `npm run build`.
### Compiles and minifies for production
```
```sh
npm run build
```
### Lints and fixes files
## Run
To run the solution, run `npm run dev`.
```bash
npm run dev
```
## Lint
To ensure the code is linted, run `npm run lint`.
```bash
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
Credits: Thanks to the original version of this quiz app: https://github.com/arpan45/simple-quiz-vue
## Deploying to Azure
Heres a step-by-step guide to help you get started:
1. Fork the a GitHub Repository
Ensure your static web app code is in your GitHub repository. Fork this repository.
2. Create an Azure Static Web App
- Create and [Azure account](http://azure.microsoft.com)
- Go to the [Azure portal](https://portal.azure.com)
- Click on “Create a resource” and search for “Static Web App”.
- Click “Create”.
3. Configure the Static Web App
- Basics: Subscription: Select your Azure subscription.
- Resource Group: Create a new resource group or use an existing one.
- Name: Provide a name for your static web app.
- Region: Choose the region closest to your users.
- #### Deployment Details:
- Source: Select “GitHub”.
- GitHub Account: Authorize Azure to access your GitHub account.
- Organization: Select your GitHub organization.
- Repository: Choose the repository containing your static web app.
- Branch: Select the branch you want to deploy from.
- #### Build Details:
- Build Presets: Choose the framework your app is built with (e.g., React, Angular, Vue, etc.).
- App Location: Specify the folder containing your app code (e.g., / if its in the root).
- API Location: If you have an API, specify its location (optional).
- Output Location: Specify the folder where the build output is generated (e.g., build or dist).
4. Review and Create
Review your settings and click “Create”. Azure will set up the necessary resources and create a GitHub Actions workflow in your repository.
5. GitHub Actions Workflow
Azure will automatically create a GitHub Actions workflow file in your repository (.github/workflows/azure-static-web-apps-<name>.yml). This workflow will handle the build and deployment process.
6. Monitor the Deployment
Go to the “Actions” tab in your GitHub repository.
You should see a workflow running. This workflow will build and deploy your static web app to Azure.
Once the workflow completes, your app will be live on the provided Azure URL.
### Example Workflow File
Heres an example of what the GitHub Actions workflow file might look like:
name: Azure Static Web Apps CI/CD
```
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
jobs:
build_and_deploy_job:
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- uses: actions/checkout@v2
- name: Build And Deploy
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
app_location: "/quiz-app # App source code path"
api_location: ""API source code path optional
output_location: "dist" #Built app content directory - optional
```
### Additional Resources
- [Azure Static Web Apps Documentation](https://learn.microsoft.com/azure/static-web-apps/getting-started)
- [GitHub Actions Documentation](https://docs.github.com/actions/use-cases-and-examples/deploying/deploying-to-azure-static-web-app)

13
quiz-app/index.html Normal file
Просмотреть файл

@ -0,0 +1,13 @@
<!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">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
quiz-app/jsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

20375
quiz-app/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,48 +1,22 @@
{
"name": "quizzes",
"version": "0.1.0",
"name": "quizapp",
"version": "1.0.0",
"private": true,
"engines": {
"npm": ">=9.0.0",
"node": ">=18.0.0"
},
"type": "module",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-i18n": "^8.28.2",
"vue-router": "^3.2.0"
"vue": "^3.4.29",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-service": "~5.0.8",
"babel-eslint": "^10.1.0",
"eslint": "^7.5.0",
"eslint-plugin-vue": "^9.14.1",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
"@vitejs/plugin-vue": "^5.0.5",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"vite": "^5.3.1"
}
}

Двоичные данные
quiz-app/public/favicon.ico

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 17 KiB

После

Ширина:  |  Высота:  |  Размер: 4.2 KiB

Просмотреть файл

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

Просмотреть файл

@ -1,8 +0,0 @@
{
"routes": [
{
"route": "/*",
"serve": "/index.html"
}
]
}

Просмотреть файл

@ -1,116 +1,84 @@
<template>
<div>
<nav>
<router-link
class="navlink"
to="/"
>
Home
</router-link>
<label for="locale">locale</label>
<select v-model="locale">
<option
v-for="localeName in availableLocales"
:key="localeName"
>
{{ localeName }}
</option>
</select>
</nav>
<div id="app">
<h1>{{ $t("title") }}</h1>
<router-view>
<Quiz />
</router-view>
</div>
</div>
</template>
<script>
import Quiz from "@/components/Quiz.vue";
import messages from "@/assets/translations";
export default {
name: "App",
i18n: { messages },
components: {
Quiz,
},
data() {
return {
locale: "en",
availableLocales: Object.keys(messages).sort(),
};
},
watch: {
locale(val) {
this.$root.$i18n.locale = val;
},
},
created() {
if (this.$route.query.loc) {
this.locale = this.$route.query.loc;
}
},
};
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import QuizGreeting from './components/QuizGreeting.vue'
</script>
<style>
html {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #252d4a;
<template>
<header>
<div class="wrapper">
<QuizGreeting msg="Quiz app" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
</div>
</header>
<RouterView />
</template>
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
nav {
background-color: #252d4a;
padding: 1em;
margin-bottom: 20px;
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
nav a {
color: white;
text-align: right;
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
.link {
display: list-item;
nav a:first-of-type {
border: 0;
}
h1,
h2,
h3,
.message {
text-align: center;
}
.error {
color: red;
}
.card {
width: 60%;
border: #252d4a solid;
border-radius: 5px;
margin: auto;
padding: 1em;
}
.btn {
min-width: 50%;
font-size: 16px;
text-align: center;
cursor: pointer;
margin-bottom: 5px;
width: 50%;
font-size: 16px;
color: #ffffff;
background-color: #252d4a;
border-radius: 5px;
padding: 5px;
justify-content: flex-start;
align-items: center;
}
.ans-btn {
justify-content: center;
@media (min-width: 1024px) {
header {
display: flex;
margin: 4px auto;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
</style>

Просмотреть файл

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

После

Ширина:  |  Высота:  |  Размер: 276 B

Просмотреть файл

@ -0,0 +1,35 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}

Просмотреть файл

@ -1,84 +0,0 @@
<template>
<div class="card">
<div
v-for="q in questions"
:key="q.id"
>
<div v-if="route == q.id">
<h2>{{ q.title }}</h2>
<hr>
<h3
v-if="complete"
class="message"
>
{{ $t("complete") }}
</h3>
<div v-else>
<h3
v-if="error"
class="error"
>
{{ $t("error") }}
</h3>
<h2>
{{ q.quiz[currentQuestion].questionText }}
</h2>
<div>
<button
v-for="(option, index) in q.quiz[currentQuestion].answerOptions"
:key="index"
class="btn ans-btn"
@click="handleAnswerClick(option.isCorrect)"
>
{{ option.answerText }}
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import messages from "@/assets/translations";
export default {
name: "Quiz",
data() {
return {
currentQuestion: 0,
complete: false,
error: false,
route: "",
locale: "",
};
},
computed: {
questions() {
return this.$t("quizzes");
},
},
i18n: { messages },
created() {
this.route = this.$route.params.id;
this.locale = this.$route.query.loc;
},
methods: {
handleAnswerClick(isCorrect) {
this.error = false;
let nextQuestion = this.currentQuestion + 1;
if (isCorrect == "true") {
//always 3 questions per quiz
if (nextQuestion < 3) {
this.currentQuestion = nextQuestion;
} else {
this.complete = true;
}
} else {
this.error = true;
}
},
},
};
</script>

Просмотреть файл

@ -0,0 +1,128 @@
<style scoped>
.button {
margin: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.button:hover {
background-color: black;
color: white;
}
.error {
color: red;
background-color: pink;
}
</style>
<template>
<div class="card">
Back to <router-link to="/">Quizzes</router-link>
</div>
<div class="card">
<div
v-for="q in questions"
:key="q.id"
>
<div v-if="route == q.id">
<h2>{{ q.title }}</h2>
<hr>
<h3
v-if="complete"
class="message"
>
{{ file.complete }}
</h3>
<div v-else>
<h3
v-if="error"
class="error"
>
{{ file.error }}
</h3>
<h2>
{{ q.quiz[currentQuestion].questionText }}
</h2>
<div>
<button
v-for="(option, index) in q.quiz[currentQuestion].answerOptions"
:key="index"
class="btn ans-btn button"
@click="handleAnswerClick(option.isCorrect)"
>
{{ option.answerText }}
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
function getFileByLocale(locale) {
// loop keys in messages, if key is the start of locale, return that file
let file = {
complete: "Complete",
error: "Error",
quizzes: [],
title: "Default Quiz",
};
for (let key in messages) {
if (locale.startsWith(key)) {
file = messages[key];
break;
}
}
return file;
}
import messages from "@/assets/translations";
let locale = navigator.language || navigator.userLanguage;
console.log("locale", locale);
console.log("messages", messages);
export default {
name: "QuizComponent",
data() {
return {
currentQuestion: 0,
complete: false,
error: false,
route: "",
locale: "",
file: getFileByLocale(locale),
};
},
computed: {
questions() {
return this.file.quizzes;
},
},
created() {
this.route = this.$route.params.id;
this.locale = this.$route.query.loc;
},
methods: {
handleAnswerClick(isCorrect) {
this.error = false;
let nextQuestion = this.currentQuestion + 1;
if (isCorrect == "true") {
//always 3 questions per quiz
if (nextQuestion < 3) {
this.currentQuestion = nextQuestion;
} else {
this.complete = true;
}
} else {
this.error = true;
}
},
},
};
</script>

Просмотреть файл

@ -0,0 +1,42 @@
<script setup>
defineProps({
msg: {
type: String,
required: true
}
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Welcome to the quiz app! This is a simple quiz app that allows you to take quizzes and test your knowledge.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

Просмотреть файл

@ -0,0 +1,59 @@
<style>
.question {
margin: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>
<template>
<div class="question" v-for="q in questions" :key="q.id" >
<router-link
:key="q.id"
:to="`quiz/${q.id}`"
class="link"
>
{{ q.title }}
</router-link>
</div>
</template>
<script>
import messages from "@/assets/translations";
function getFileByLocale(locale) {
// loop keys in messages, if key is the start of locale, return that file
let file = {
complete: "Complete",
error: "Error",
quizzes: [],
title: "Default Quiz",
};
for (let key in messages) {
if (locale.startsWith(key)) {
file = messages[key];
break;
}
}
return file;
}
let locale = navigator.language || navigator.userLanguage;
export default {
name: "QuizzesComponent",
data() {
return {
file: getFileByLocale(locale),
};
},
computed: {
questions() {
return this.file.quizzes;
},
},
i18n: { messages },
};
</script>

Просмотреть файл

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

Просмотреть файл

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

Просмотреть файл

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

Просмотреть файл

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

Просмотреть файл

@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

Просмотреть файл

@ -1,14 +1,11 @@
import Vue from 'vue';
import App from './App.vue';
Vue.config.productionTip = false;
import router from './router';
import './assets/main.css'
import VueI18n from 'vue-i18n';
Vue.use(VueI18n);
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const i18n = new VueI18n({
locale: 'en',
fallbackLocale: 'en',
});
const app = createApp(App)
new Vue({ i18n, router, render: (h) => h(App) }).$mount('#app');
app.use(router)
app.mount('#app')

Просмотреть файл

@ -1,34 +1,29 @@
import Vue from 'vue';
import Router from 'vue-router';
import Home from '@/views/Home.vue';
import Quiz from '@/components/Quiz.vue';
import NotFound from '@/views/NotFound.vue';
Vue.use(Router);
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import QuizComponent from '@/components/QuizComponent.vue'
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: Home,
component: HomeView
},
{
path: '/quiz/:id',
name: 'Quiz',
component: Quiz,
name: 'quiz-detail',
component: QuizComponent
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound,
meta: { title: 'Not Found' },
},
],
});
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue')
}
]
})
router.beforeEach((to, from, next) => {
next();
});
export default router;
export default router

Просмотреть файл

@ -0,0 +1,16 @@
<template>
<div class="about">
<h1>Quiz app</h1>
This is the about page for the quiz app.
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>

Просмотреть файл

@ -1,26 +0,0 @@
<template>
<div>
<router-link
v-for="q in questions"
:key="q.id"
:to="`quiz/${q.id}`"
class="link"
>
{{ q.title }}
</router-link>
</div>
</template>
<script>
import messages from "@/assets/translations";
export default {
name: "Home",
computed: {
questions() {
return this.$t("quizzes");
},
},
i18n: { messages },
};
</script>

Просмотреть файл

@ -0,0 +1,9 @@
<script setup>
import QuizzesComponent from '@/components/QuizzesComponent.vue';
</script>
<template>
<main>
<QuizzesComponent />
</main>
</template>

Просмотреть файл

@ -1,5 +0,0 @@
<template>
<div>
<p>Sorry, wrong number!</p>
</div>
</template>

16
quiz-app/vite.config.js Normal file
Просмотреть файл

@ -0,0 +1,16 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})