generated from VLADIMIR/template_frontend
fixes
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
# Сборка и зависимости
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Генерируемые и lock-файлы
|
||||||
|
package-lock.json
|
||||||
|
src/api/generated
|
||||||
|
proto
|
||||||
|
|
||||||
|
# Документы, которые форматируются в отдельных задачах / вручную
|
||||||
|
AGENTS.md
|
||||||
|
README.md
|
||||||
|
docker-compose.yml
|
||||||
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="">
|
<html lang="">
|
||||||
<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>Вечерний детектив</title>
|
<title>Вечерний детектив</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DGX5lmmj.js"></script>
|
<script type="module" crossorigin src="/assets/index-DIvjQ2Ad.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Dd0gvwlX.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DlrAb_dw.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||||
import { globalIgnores } from 'eslint/config'
|
import { globalIgnores } from 'eslint/config'
|
||||||
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
import simpleImportSort from 'eslint-plugin-simple-import-sort'
|
||||||
import unusedImports from 'eslint-plugin-unused-imports';
|
import unusedImports from 'eslint-plugin-unused-imports'
|
||||||
import pluginVue from 'eslint-plugin-vue'
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
|
|
||||||
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||||
@@ -41,7 +41,7 @@ export default defineConfigWithVueTs(
|
|||||||
'@typescript-eslint/no-unused-vars': 'off',
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
|
||||||
// Включаем правила плагина
|
// Включаем правила плагина
|
||||||
'unused-imports/no-unused-imports': 'error', // 🔧 автофикс
|
'unused-imports/no-unused-imports': 'error', // 🔧 автофикс
|
||||||
'unused-imports/no-unused-vars': [
|
'unused-imports/no-unused-vars': [
|
||||||
'warn',
|
'warn',
|
||||||
{
|
{
|
||||||
@@ -52,5 +52,5 @@ export default defineConfigWithVueTs(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-4
@@ -1,9 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="">
|
<html lang="">
|
||||||
<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>Вечерний детектив</title>
|
<title>Вечерний детектив</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Generated
+28
@@ -40,6 +40,7 @@
|
|||||||
"prettier": "3.9.4",
|
"prettier": "3.9.4",
|
||||||
"stylelint": "^17.14.1",
|
"stylelint": "^17.14.1",
|
||||||
"stylelint-config-standard-scss": "^17.0.0",
|
"stylelint-config-standard-scss": "^17.0.0",
|
||||||
|
"stylelint-order": "^8.1.1",
|
||||||
"ts-proto": "^2.12.0",
|
"ts-proto": "^2.12.0",
|
||||||
"typescript": "~6.0.3",
|
"typescript": "~6.0.3",
|
||||||
"vfonts": "^0.0.3",
|
"vfonts": "^0.0.3",
|
||||||
@@ -5321,6 +5322,16 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-sorting": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-TXbU+h6vVRW+86c/+ewhWq9k7pr7ijASTnepVhCQiC87zAOTkvB1v2dHyWP+ggstSTX/PNvjzS+IOqzejndz9w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.4.20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss-value-parser": {
|
"node_modules/postcss-value-parser": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
@@ -5915,6 +5926,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stylelint-order": {
|
||||||
|
"version": "8.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-8.1.1.tgz",
|
||||||
|
"integrity": "sha512-LqsEB6VggJuu5v10RtkrQsBObcdwBE7GuAOlwfc/LR3VL/w8UqKX2BOLIjhyGt0Gne/njo7gRNGiJAKhfmPMNw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"postcss": "^8.5.8",
|
||||||
|
"postcss-sorting": "^10.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"stylelint": "^16.18.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/stylelint-scss": {
|
"node_modules/stylelint-scss": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-7.2.0.tgz",
|
||||||
|
|||||||
+2
-1
@@ -11,7 +11,7 @@
|
|||||||
"type-check": "vue-tsc --build",
|
"type-check": "vue-tsc --build",
|
||||||
"lint": "eslint . --fix",
|
"lint": "eslint . --fix",
|
||||||
"lint:css": "stylelint \"src/**/*.{css,scss,vue}\" --fix",
|
"lint:css": "stylelint \"src/**/*.{css,scss,vue}\" --fix",
|
||||||
"format": "prettier --write src/",
|
"format": "prettier --write .",
|
||||||
"proto:gen": "protoc --typescript-http_out=src/api/generated --proto_path=proto proto/main.proto"
|
"proto:gen": "protoc --typescript-http_out=src/api/generated --proto_path=proto proto/main.proto"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
"prettier": "3.9.4",
|
"prettier": "3.9.4",
|
||||||
"stylelint": "^17.14.1",
|
"stylelint": "^17.14.1",
|
||||||
"stylelint-config-standard-scss": "^17.0.0",
|
"stylelint-config-standard-scss": "^17.0.0",
|
||||||
|
"stylelint-order": "^8.1.1",
|
||||||
"ts-proto": "^2.12.0",
|
"ts-proto": "^2.12.0",
|
||||||
"typescript": "~6.0.3",
|
"typescript": "~6.0.3",
|
||||||
"vfonts": "^0.0.3",
|
"vfonts": "^0.0.3",
|
||||||
|
|||||||
@@ -11,5 +11,3 @@ import { RouterView } from 'vue-router'
|
|||||||
</n-message-provider>
|
</n-message-provider>
|
||||||
</n-config-provider>
|
</n-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ export function getArchiveClient() {
|
|||||||
return buildArchiveClient(authStore.refreshTokenAction, authStore.getToken)
|
return buildArchiveClient(authStore.refreshTokenAction, authStore.getToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildArchiveClient(
|
function buildArchiveClient(refreshTokens: () => Promise<boolean>, getToken: () => string | null) {
|
||||||
refreshTokens: () => Promise<boolean>,
|
|
||||||
getToken: () => string | null,
|
|
||||||
) {
|
|
||||||
async function request(path: string, init: RequestInit): Promise<Response> {
|
async function request(path: string, init: RequestInit): Promise<Response> {
|
||||||
const headers: Record<string, string> = {}
|
const headers: Record<string, string> = {}
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
@@ -59,7 +56,8 @@ function buildArchiveClient(
|
|||||||
}
|
}
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
const filename =
|
const filename =
|
||||||
parseContentDispositionFilename(response.headers.get('Content-Disposition')) || `scenario-${id}.zip`
|
parseContentDispositionFilename(response.headers.get('Content-Disposition')) ||
|
||||||
|
`scenario-${id}.zip`
|
||||||
return { blob, filename }
|
return { blob, filename }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-9
@@ -51,18 +51,13 @@
|
|||||||
*::before,
|
*::before,
|
||||||
*::after {
|
*::after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
|
||||||
color: var(--color-text);
|
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
transition:
|
color: var(--color-text);
|
||||||
color 0.5s,
|
|
||||||
background-color 0.5s;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-family:
|
font-family:
|
||||||
Inter,
|
Inter,
|
||||||
-apple-system,
|
-apple-system,
|
||||||
@@ -77,7 +72,12 @@ body {
|
|||||||
'Helvetica Neue',
|
'Helvetica Neue',
|
||||||
sans-serif;
|
sans-serif;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
text-rendering: optimizelegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
line-height: 1.6;
|
||||||
|
min-height: 100vh;
|
||||||
|
text-rendering: optimizelegibility;
|
||||||
|
transition:
|
||||||
|
color 0.5s,
|
||||||
|
background-color 0.5s;
|
||||||
|
}
|
||||||
|
|||||||
+10
-10
@@ -1,9 +1,9 @@
|
|||||||
@import './base.css';
|
@import './base.css';
|
||||||
|
|
||||||
body {
|
body {
|
||||||
overflow: hidden;
|
|
||||||
color: #eee;
|
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
|
color: #eee;
|
||||||
|
overflow: hidden;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,9 +21,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.center-block-custom {
|
.center-block-custom {
|
||||||
|
height: calc(100vh - 150px);
|
||||||
margin-top: 70px;
|
margin-top: 70px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: calc(100vh - 150px);
|
|
||||||
|
|
||||||
/* Firefox */
|
/* Firefox */
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
@@ -35,13 +35,13 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.width700 {
|
.width700 {
|
||||||
padding: 10px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.width1200 {
|
.width1200 {
|
||||||
padding: 10px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-table {
|
.display-table {
|
||||||
@@ -52,20 +52,20 @@ body {
|
|||||||
|
|
||||||
@media (width >=1024px) {
|
@media (width >=1024px) {
|
||||||
.center-block-custom {
|
.center-block-custom {
|
||||||
margin: 70px auto 0;
|
|
||||||
height: calc(100dvh - 70px);
|
height: calc(100dvh - 70px);
|
||||||
|
margin: 70px auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.width700 {
|
.width700 {
|
||||||
width: 700px;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
width: 700px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.width1200 {
|
.width1200 {
|
||||||
width: 1200px;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
width: 1200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center-middle-block-custom {
|
.center-middle-block-custom {
|
||||||
|
|||||||
@@ -12,23 +12,23 @@
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.belt-block {
|
.belt-block {
|
||||||
height: 100%;
|
|
||||||
background-color: rgb(0 0 0);
|
background-color: rgb(0 0 0);
|
||||||
background-image: url("@/assets/images/belt.png");
|
background-image: url('@/assets/images/belt.png');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shadow {
|
.shadow {
|
||||||
height: 10px;
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
background-color: black;
|
background-color: black;
|
||||||
|
height: 10px;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shadow-top {
|
.shadow-top {
|
||||||
top: 0;
|
|
||||||
box-shadow: 0 -5px 10px black;
|
box-shadow: 0 -5px 10px black;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shadow-bottom {
|
.shadow-bottom {
|
||||||
|
|||||||
@@ -14,110 +14,115 @@ const scenarios = ref<Scenario[]>([])
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
async function getScenarios() {
|
async function getScenarios() {
|
||||||
scenarios.value = []
|
scenarios.value = []
|
||||||
const res = await client.GetScenariosCatalog({})
|
const res = await client.GetScenariosCatalog({})
|
||||||
scenarios.value = res.scenarios!
|
scenarios.value = res.scenarios!
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toScenarioInfo(id: number) {
|
async function toScenarioInfo(id: number) {
|
||||||
router.push('/scenarios/' + id)
|
router.push('/scenarios/' + id)
|
||||||
}
|
}
|
||||||
|
|
||||||
getScenarios()
|
getScenarios()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="center-block-custom">
|
<div class="center-block-custom">
|
||||||
<div class="width1200 display-table">
|
<div class="width1200 display-table">
|
||||||
<div class="scenario-block" v-for="scenario in scenarios" @click="toScenarioInfo(scenario.id!)"
|
<div
|
||||||
v-bind:key="scenario.id">
|
class="scenario-block"
|
||||||
<div class="scenario-image-block scenario-image" :style="{ backgroundImage: `url(${scenario.image})` }">
|
v-for="scenario in scenarios"
|
||||||
</div>
|
@click="toScenarioInfo(scenario.id!)"
|
||||||
|
v-bind:key="scenario.id"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="scenario-image-block scenario-image"
|
||||||
|
:style="{ backgroundImage: `url(${scenario.image})` }"
|
||||||
|
></div>
|
||||||
|
|
||||||
<div class="scenario-content-block">
|
<div class="scenario-content-block">
|
||||||
<p class="scenario-title">{{ scenario.name }}</p>
|
<p class="scenario-title">{{ scenario.name }}</p>
|
||||||
|
|
||||||
<p class="scenario-description">{{ scenario.description }}</p>
|
<p class="scenario-description">{{ scenario.description }}</p>
|
||||||
|
|
||||||
<p class="scenario-label">
|
<p class="scenario-label">
|
||||||
<n-tag :bordered="false" type="success" class="status-block">Автор: {{
|
<n-tag :bordered="false" type="success" class="status-block"
|
||||||
scenario.author?.username
|
>Автор: {{ scenario.author?.username }}</n-tag
|
||||||
}}</n-tag>
|
>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<HeaderMenu active="catalog" />
|
<HeaderMenu active="catalog" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.scenario-block {
|
.scenario-block {
|
||||||
position: relative;
|
/* background-color: #553333; */
|
||||||
margin: 10px;
|
background-color: #222;
|
||||||
width: 160px;
|
border: 2px solid #222;
|
||||||
height: 300px;
|
border-radius: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 12px;
|
height: 300px;
|
||||||
|
margin: 10px;
|
||||||
/* background-color: #553333; */
|
position: relative;
|
||||||
background-color: #222;
|
width: 160px;
|
||||||
border: 2px solid #222;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-content-block {
|
.scenario-content-block {
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-block:hover {
|
.scenario-block:hover {
|
||||||
color: #63e2b7;
|
border: 2px solid #63e2b7;
|
||||||
border: 2px solid #63e2b7;
|
color: #63e2b7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-image-block {
|
.scenario-image-block {
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
height: 160px;
|
border-radius: 10px;
|
||||||
border-radius: 10px;
|
height: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-image {
|
.scenario-image {
|
||||||
background-size: cover;
|
background-position: center;
|
||||||
background-position: center;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-title {
|
.scenario-title {
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-description {
|
.scenario-description {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-block {
|
.status-block {
|
||||||
position: absolute;
|
bottom: 15px;
|
||||||
bottom: 15px;
|
position: absolute;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width >=1024px) {
|
@media (width >=1024px) {
|
||||||
.scenario-block {
|
.scenario-block {
|
||||||
width: 250px;
|
height: 400px;
|
||||||
height: 400px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-image-block {
|
.scenario-image-block {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-content-block {
|
.scenario-content-block {
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-description {
|
.scenario-description {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+21
-19
@@ -72,7 +72,9 @@ onMounted(async () => {
|
|||||||
·
|
·
|
||||||
<RouterLink to="/privacy-policy" class="docs-link">Политика конфиденциальности</RouterLink>
|
<RouterLink to="/privacy-policy" class="docs-link">Политика конфиденциальности</RouterLink>
|
||||||
·
|
·
|
||||||
<RouterLink to="/consent" class="docs-link">Согласие на обработку персональных данных</RouterLink>
|
<RouterLink to="/consent" class="docs-link"
|
||||||
|
>Согласие на обработку персональных данных</RouterLink
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,17 +106,17 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.paper {
|
.paper {
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
padding: 32px 28px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-image: url('@/assets/images/paper_white.jpg');
|
background-image: url('@/assets/images/paper_white.jpg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
color: #1d1d1d;
|
border-radius: 3px;
|
||||||
box-shadow: 0 0 12px rgb(0 0 0 / 70%);
|
box-shadow: 0 0 12px rgb(0 0 0 / 70%);
|
||||||
|
color: #1d1d1d;
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
padding: 32px 28px;
|
||||||
|
position: relative;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paper::-webkit-scrollbar {
|
.paper::-webkit-scrollbar {
|
||||||
@@ -122,44 +124,44 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.doc-text {
|
.doc-text {
|
||||||
font-family: 'font_old_typer';
|
font-family: 'font_old_typer', serif;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
white-space: pre-wrap;
|
overflow-wrap: anywhere;
|
||||||
word-break: break-word;
|
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paper-shadow {
|
.paper-shadow {
|
||||||
|
background-image: url('@/assets/images/paper_white.jpg');
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 3px;
|
||||||
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-image: url('@/assets/images/paper_white.jpg');
|
|
||||||
background-size: cover;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.paper-shadow-left {
|
.paper-shadow-left {
|
||||||
|
box-shadow: 0 0 10px rgb(0 0 0 / 60%);
|
||||||
|
filter: brightness(60%);
|
||||||
left: 0;
|
left: 0;
|
||||||
transform: rotate(-2.2deg);
|
transform: rotate(-2.2deg);
|
||||||
filter: brightness(60%);
|
|
||||||
box-shadow: 0 0 10px rgb(0 0 0 / 60%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.paper-shadow-right {
|
.paper-shadow-right {
|
||||||
|
box-shadow: 0 0 10px rgb(0 0 0 / 60%);
|
||||||
|
filter: brightness(75%);
|
||||||
right: 0;
|
right: 0;
|
||||||
transform: rotate(2.2deg);
|
transform: rotate(2.2deg);
|
||||||
filter: brightness(75%);
|
|
||||||
box-shadow: 0 0 10px rgb(0 0 0 / 60%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.docs-links {
|
.docs-links {
|
||||||
|
color: #999;
|
||||||
|
font-size: 15px;
|
||||||
margin-top: 28px;
|
margin-top: 28px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 15px;
|
|
||||||
color: #999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.docs-link {
|
.docs-link {
|
||||||
|
|||||||
@@ -2,31 +2,31 @@
|
|||||||
import { Footsteps } from '@vicons/ionicons5'
|
import { Footsteps } from '@vicons/ionicons5'
|
||||||
import { ContactMailRound } from '@vicons/material'
|
import { ContactMailRound } from '@vicons/material'
|
||||||
import { Icon } from '@vicons/utils'
|
import { Icon } from '@vicons/utils'
|
||||||
import { type PropType, ref } from 'vue';
|
import { type PropType, ref } from 'vue'
|
||||||
|
|
||||||
import BeltBlock from './BeltBlock.vue';
|
import BeltBlock from './BeltBlock.vue'
|
||||||
import HeaderText from './HeaderText.vue';
|
import HeaderText from './HeaderText.vue'
|
||||||
import MetalPlate from './MetalPlate.vue';
|
import MetalPlate from './MetalPlate.vue'
|
||||||
|
|
||||||
const code = ref('')
|
const code = ref('')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
addAction: {
|
addAction: {
|
||||||
type: Function as PropType<(param: string) => void>,
|
type: Function as PropType<(param: string) => void>,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
allPlacesCount: {
|
allPlacesCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
newPlacesCount: {
|
newPlacesCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
gameStatus: {
|
gameStatus: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
async function addClickButton() {
|
async function addClickButton() {
|
||||||
@@ -51,7 +51,6 @@ function getTextForCounter(count: number): string[] {
|
|||||||
|
|
||||||
return [text1, text2]
|
return [text1, text2]
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -62,7 +61,6 @@ function getTextForCounter(count: number): string[] {
|
|||||||
|
|
||||||
<div class="center-block-700">
|
<div class="center-block-700">
|
||||||
<div class="controller">
|
<div class="controller">
|
||||||
|
|
||||||
<div class="counters-block">
|
<div class="counters-block">
|
||||||
<div class="counter-block">
|
<div class="counter-block">
|
||||||
<div class="places-text">
|
<div class="places-text">
|
||||||
@@ -70,7 +68,13 @@ function getTextForCounter(count: number): string[] {
|
|||||||
<Footsteps />
|
<Footsteps />
|
||||||
</Icon>
|
</Icon>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(n, index) in getTextForCounter(allPlacesCount)" :key="index" class="places-text places-text-number">{{ n }}</div>
|
<div
|
||||||
|
v-for="(n, index) in getTextForCounter(allPlacesCount)"
|
||||||
|
:key="index"
|
||||||
|
class="places-text places-text-number"
|
||||||
|
>
|
||||||
|
{{ n }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="counter-block">
|
<div class="counter-block">
|
||||||
<div class="places-text">
|
<div class="places-text">
|
||||||
@@ -78,19 +82,37 @@ function getTextForCounter(count: number): string[] {
|
|||||||
<ContactMailRound />
|
<ContactMailRound />
|
||||||
</Icon>
|
</Icon>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(n, index) in getTextForCounter(newPlacesCount)" :key="index" class="places-text places-text-number">{{ n }}</div>
|
<div
|
||||||
|
v-for="(n, index) in getTextForCounter(newPlacesCount)"
|
||||||
|
:key="index"
|
||||||
|
class="places-text places-text-number"
|
||||||
|
>
|
||||||
|
{{ n }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="game-input">
|
<div class="game-input">
|
||||||
<input id="run" class="game-input-run" v-model="code" type="text"
|
<input
|
||||||
|
id="run"
|
||||||
|
class="game-input-run"
|
||||||
|
v-model="code"
|
||||||
|
type="text"
|
||||||
:placeholder="props.gameStatus == 'run' ? 'Место назначения' : 'Ходы запрещены'"
|
:placeholder="props.gameStatus == 'run' ? 'Место назначения' : 'Ходы запрещены'"
|
||||||
:disabled="props.gameStatus != 'run'">
|
:disabled="props.gameStatus != 'run'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="game-button-run-shadow"></div>
|
<div class="game-button-run-shadow"></div>
|
||||||
<button class="game-button-run" type="submit" :disabled="code.length == 0" v-on:click="addClickButton">
|
<button
|
||||||
|
class="game-button-run"
|
||||||
|
type="submit"
|
||||||
|
:disabled="code.length == 0"
|
||||||
|
v-on:click="addClickButton"
|
||||||
|
>
|
||||||
<HeaderText v-if="props.gameStatus == 'run'">Поехали</HeaderText>
|
<HeaderText v-if="props.gameStatus == 'run'">Поехали</HeaderText>
|
||||||
<HeaderText v-if="props.gameStatus == 'pause' || props.gameStatus == 'draft'">Ждём</HeaderText>
|
<HeaderText v-if="props.gameStatus == 'pause' || props.gameStatus == 'draft'"
|
||||||
|
>Ждём</HeaderText
|
||||||
|
>
|
||||||
<HeaderText v-if="props.gameStatus == 'finish'">Всё</HeaderText>
|
<HeaderText v-if="props.gameStatus == 'finish'">Всё</HeaderText>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,27 +123,27 @@ function getTextForCounter(count: number): string[] {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.form-custom {
|
.form-custom {
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
color: white;
|
color: white;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-form {
|
.input-form {
|
||||||
height: 76px;
|
height: 76px;
|
||||||
position: relative;
|
|
||||||
z-index: 1000;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 1920px;
|
max-width: 1920px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controller-metal {
|
.controller-metal {
|
||||||
width: 30px;
|
|
||||||
height: calc(100% + 2px);
|
height: calc(100% + 2px);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controller-metal-left {
|
.controller-metal-left {
|
||||||
@@ -138,23 +160,23 @@ function getTextForCounter(count: number): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.game-input {
|
.game-input {
|
||||||
|
height: 50px;
|
||||||
|
left: 15px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 14px;
|
top: 14px;
|
||||||
left: 15px;
|
|
||||||
height: 50px;
|
|
||||||
width: calc(100% - 150px - 25px);
|
width: calc(100% - 150px - 25px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-input-run {
|
.game-input-run {
|
||||||
height: 100%;
|
background-color: transparent;
|
||||||
width: 100%;
|
background-image: url('@/assets/images/input_center.png');
|
||||||
padding-left: 27px;
|
|
||||||
background-image: url("@/assets/images/input_center.png");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
font-family: 'font_old_typer', serif;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-family: 'font_old_typer';
|
height: 100%;
|
||||||
background-color: transparent;
|
padding-left: 27px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-input-run::placeholder {
|
.game-input-run::placeholder {
|
||||||
@@ -167,27 +189,27 @@ function getTextForCounter(count: number): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.game-button-run-shadow {
|
.game-button-run-shadow {
|
||||||
|
box-shadow: -5px 5px 10px black;
|
||||||
|
height: 80px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: -5px;
|
top: -5px;
|
||||||
height: 80px;
|
|
||||||
width: 150px;
|
width: 150px;
|
||||||
box-shadow: -5px 5px 10px black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-button-run {
|
.game-button-run {
|
||||||
background-image: url("@/assets/images/button.png");
|
background-color: transparent;
|
||||||
|
background-image: url('@/assets/images/button.png');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
border: 0;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
height: 80px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: -5px;
|
top: -5px;
|
||||||
height: 80px;
|
|
||||||
width: 155px;
|
width: 155px;
|
||||||
border: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-button-run {
|
.game-button-run {
|
||||||
@@ -196,35 +218,35 @@ function getTextForCounter(count: number): string[] {
|
|||||||
|
|
||||||
.new-places-label,
|
.new-places-label,
|
||||||
.all-places-label {
|
.all-places-label {
|
||||||
position: absolute;
|
|
||||||
height: 48px;
|
height: 48px;
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-places-label {
|
.new-places-label {
|
||||||
top: 14px;
|
|
||||||
left: 85px;
|
left: 85px;
|
||||||
|
top: 14px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.all-places-label {
|
.all-places-label {
|
||||||
top: 14px;
|
|
||||||
left: 15px;
|
left: 15px;
|
||||||
|
top: 14px;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.places-text {
|
.places-text {
|
||||||
background-color: #d1cebd;
|
background-color: #d1cebd;
|
||||||
border: 1px solid #9e9982;
|
background-image: url('@/assets/images/paper.jpg');
|
||||||
color: #111;
|
|
||||||
font-size: 1.4em;
|
|
||||||
font-family: 'font_old_typer';
|
|
||||||
border-radius: 7px;
|
|
||||||
margin: 0 1px;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
background-image: url("@/assets/images/paper.jpg");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
border: 1px solid #9e9982;
|
||||||
|
border-radius: 7px;
|
||||||
box-shadow: 0 -2px 5px black;
|
box-shadow: 0 -2px 5px black;
|
||||||
|
color: #111;
|
||||||
|
display: inline-block;
|
||||||
|
font-family: 'font_old_typer', serif;
|
||||||
|
font-size: 1.4em;
|
||||||
|
margin: 0 1px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.places-text-number {
|
.places-text-number {
|
||||||
@@ -232,10 +254,10 @@ function getTextForCounter(count: number): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.counters-block {
|
.counters-block {
|
||||||
position: absolute;
|
|
||||||
top: -40px;
|
|
||||||
left: 23px;
|
left: 23px;
|
||||||
margin: 20px 10px 20px 5px;
|
margin: 20px 10px 20px 5px;
|
||||||
|
position: absolute;
|
||||||
|
top: -40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-block {
|
.counter-block {
|
||||||
@@ -244,15 +266,15 @@ function getTextForCounter(count: number): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-for-counter {
|
.icon-for-counter {
|
||||||
|
margin: 0 4px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
margin: 0 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width >=1025px) {
|
@media (width >=1025px) {
|
||||||
.center-block-700 {
|
.center-block-700 {
|
||||||
width: 700px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
width: 700px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+633
-561
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui'
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
import { getAuthClient } from '@/api/auth_client';
|
import { getAuthClient } from '@/api/auth_client'
|
||||||
import type { Game } from '@/api/generated/crabs/evening_detective_server';
|
import type { Game } from '@/api/generated/crabs/evening_detective_server'
|
||||||
import HeaderMenu from '@/components/HeaderMenu.vue'
|
import HeaderMenu from '@/components/HeaderMenu.vue'
|
||||||
|
|
||||||
const client = getAuthClient()
|
const client = getAuthClient()
|
||||||
@@ -48,15 +48,15 @@ async function toGame(id: number) {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.game-block {
|
.game-block {
|
||||||
|
background-color: #222;
|
||||||
|
border-radius: 10px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 10px;
|
|
||||||
background-color: #222;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-block:hover {
|
.game-block:hover {
|
||||||
cursor: pointer;
|
|
||||||
color: #63e2b7;
|
color: #63e2b7;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-title {
|
.game-title {
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ const goToLogout = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openMenu = ref(false)
|
const openMenu = ref(false)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -53,26 +52,54 @@ const openMenu = ref(false)
|
|||||||
<MenuOutlined />
|
<MenuOutlined />
|
||||||
</Icon>
|
</Icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="left-group menu-buttons-block" :class="!openMenu ? 'menu-buttons-block-active' : ''">
|
<div
|
||||||
<div class="menu-item-block" :class="{ active: props.active == 'main' }" @click="router.push('/')">
|
class="left-group menu-buttons-block"
|
||||||
|
:class="!openMenu ? 'menu-buttons-block-active' : ''"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-item-block"
|
||||||
|
:class="{ active: props.active == 'main' }"
|
||||||
|
@click="router.push('/')"
|
||||||
|
>
|
||||||
Главная
|
Главная
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item-block" :class="{ active: props.active == 'office' }" v-if="authStore.hasRole('user')"
|
<div
|
||||||
@click="router.push('/office')">
|
class="menu-item-block"
|
||||||
|
:class="{ active: props.active == 'office' }"
|
||||||
|
v-if="authStore.hasRole('user')"
|
||||||
|
@click="router.push('/office')"
|
||||||
|
>
|
||||||
Кабинет
|
Кабинет
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item-block" :class="{ active: props.active == 'games' }" v-if="hasPermission('games_page')"
|
<div
|
||||||
@click="router.push('/games')">
|
class="menu-item-block"
|
||||||
|
:class="{ active: props.active == 'games' }"
|
||||||
|
v-if="hasPermission('games_page')"
|
||||||
|
@click="router.push('/games')"
|
||||||
|
>
|
||||||
Игры
|
Игры
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item-block" :class="{ active: props.active == 'catalog' }" @click="router.push('/catalog')">
|
<div
|
||||||
|
class="menu-item-block"
|
||||||
|
:class="{ active: props.active == 'catalog' }"
|
||||||
|
@click="router.push('/catalog')"
|
||||||
|
>
|
||||||
Каталог
|
Каталог
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item-block" :class="{ active: props.active == 'scenarios' }" v-if="hasPermission('scenarios_page')" @click="router.push('/scenarios')">
|
<div
|
||||||
|
class="menu-item-block"
|
||||||
|
:class="{ active: props.active == 'scenarios' }"
|
||||||
|
v-if="hasPermission('scenarios_page')"
|
||||||
|
@click="router.push('/scenarios')"
|
||||||
|
>
|
||||||
Мои сценарии
|
Мои сценарии
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item-block" :class="{ active: props.active == 'users' }" v-if="hasPermission('users_page')"
|
<div
|
||||||
@click="router.push('/users')">
|
class="menu-item-block"
|
||||||
|
:class="{ active: props.active == 'users' }"
|
||||||
|
v-if="hasPermission('users_page')"
|
||||||
|
@click="router.push('/users')"
|
||||||
|
>
|
||||||
Пользователи
|
Пользователи
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,31 +108,33 @@ const openMenu = ref(false)
|
|||||||
<div v-if="!authStore.isAuthenticated" class="menu-item-block right" @click="goToLogin">
|
<div v-if="!authStore.isAuthenticated" class="menu-item-block right" @click="goToLogin">
|
||||||
Войти
|
Войти
|
||||||
</div>
|
</div>
|
||||||
<div v-if="authStore.isAuthenticated" class="menu-item-block right" @click="goToLogout">Выйти</div>
|
<div v-if="authStore.isAuthenticated" class="menu-item-block right" @click="goToLogout">
|
||||||
|
Выйти
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.menu-block {
|
.menu-block {
|
||||||
|
align-items: center;
|
||||||
|
background-color: #222;
|
||||||
|
display: flex;
|
||||||
|
height: 70px;
|
||||||
|
justify-content: space-between;
|
||||||
|
left: 0;
|
||||||
|
padding: 20px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 70px;
|
|
||||||
background-color: #222;
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item-block {
|
.menu-item-block {
|
||||||
display: inline-block;
|
|
||||||
margin-left: 10px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 5px;
|
display: inline-block;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item-block:hover {
|
.menu-item-block:hover {
|
||||||
@@ -129,14 +158,14 @@ const openMenu = ref(false)
|
|||||||
|
|
||||||
@media (width < 1024px) {
|
@media (width < 1024px) {
|
||||||
.menu-buttons-block {
|
.menu-buttons-block {
|
||||||
position: fixed;
|
background-color: #222;
|
||||||
top: 70px;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #222;
|
left: 0;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
|
position: fixed;
|
||||||
|
top: 70px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-buttons-block div {
|
.menu-buttons-block div {
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.text-with-font {
|
.text-with-font {
|
||||||
font-family: 'font_old_typer';
|
|
||||||
color: #bfa07d;
|
color: #bfa07d;
|
||||||
|
font-family: 'font_old_typer', serif;
|
||||||
|
font-size: 22px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
font-size: 22px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -60,10 +60,12 @@ const removeAudio = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Просмотр вступления в редакторе: клик — в режим редактирования -->
|
<!-- Просмотр вступления в редакторе: клик — в режим редактирования -->
|
||||||
<div v-if="mode == 'show-editor'" class="place-block-hover show-editor-block" @click="updateMode('edit')">
|
<div
|
||||||
<div class="message-header">
|
v-if="mode == 'show-editor'"
|
||||||
Вступление
|
class="place-block-hover show-editor-block"
|
||||||
</div>
|
@click="updateMode('edit')"
|
||||||
|
>
|
||||||
|
<div class="message-header">Вступление</div>
|
||||||
<hr class="hr" />
|
<hr class="hr" />
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
<div v-if="hasAudio" class="intro-audio-block">
|
<div v-if="hasAudio" class="intro-audio-block">
|
||||||
@@ -78,14 +80,23 @@ const removeAudio = () => {
|
|||||||
<div v-if="mode == 'edit'" class="place-block">
|
<div v-if="mode == 'edit'" class="place-block">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<p class="settings-header">Текст вступления</p>
|
<p class="settings-header">Текст вступления</p>
|
||||||
<n-input v-model:value="props.introduction!.text" type="textarea"
|
<n-input
|
||||||
|
v-model:value="props.introduction!.text"
|
||||||
|
type="textarea"
|
||||||
:autosize="{ minRows: 4, maxRows: 12 }"
|
:autosize="{ minRows: 4, maxRows: 12 }"
|
||||||
placeholder="Дорогие сыщики! В этом деле вам предстоит..." />
|
placeholder="Дорогие сыщики! В этом деле вам предстоит..."
|
||||||
|
/>
|
||||||
|
|
||||||
<p class="settings-header">Аудио вступления</p>
|
<p class="settings-header">Аудио вступления</p>
|
||||||
<div class="intro-edit-audio">
|
<div class="intro-edit-audio">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<audio v-if="hasAudio" :src="audioUrl" controls preload="none" class="intro-audio"></audio>
|
<audio
|
||||||
|
v-if="hasAudio"
|
||||||
|
:src="audioUrl"
|
||||||
|
controls
|
||||||
|
preload="none"
|
||||||
|
class="intro-audio"
|
||||||
|
></audio>
|
||||||
<n-text v-else depth="3">Аудио не задано</n-text>
|
<n-text v-else depth="3">Аудио не задано</n-text>
|
||||||
<n-button @click="removeAudio()" :disabled="!hasAudio">Убрать аудио</n-button>
|
<n-button @click="removeAudio()" :disabled="!hasAudio">Убрать аудио</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
@@ -126,17 +137,17 @@ const removeAudio = () => {
|
|||||||
/* Панели в стиле блоков точек (см. PlaceBlock.vue) */
|
/* Панели в стиле блоков точек (см. PlaceBlock.vue) */
|
||||||
.place-block-hover,
|
.place-block-hover,
|
||||||
.place-block {
|
.place-block {
|
||||||
|
background-color: #222;
|
||||||
|
border: 1px solid #222;
|
||||||
|
border-radius: 10px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #222;
|
|
||||||
background-color: #222;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.place-block-hover:hover {
|
.place-block-hover:hover {
|
||||||
|
border: 1px solid #63e2b7;
|
||||||
color: #63e2b7;
|
color: #63e2b7;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid #63e2b7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-editor-block {
|
.show-editor-block {
|
||||||
@@ -144,9 +155,9 @@ const removeAudio = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hr {
|
.hr {
|
||||||
margin: 10px 0;
|
|
||||||
border: dashed 1px;
|
border: dashed 1px;
|
||||||
border-color: #eee;
|
border-color: #eee;
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-header {
|
.message-header {
|
||||||
@@ -172,16 +183,16 @@ const removeAudio = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.intro-audio {
|
.intro-audio {
|
||||||
width: 100%;
|
|
||||||
max-width: 460px;
|
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
max-width: 460px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.intro-edit-audio {
|
.intro-edit-audio {
|
||||||
|
align-items: flex-start;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
align-items: flex-start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.slot-block {
|
.slot-block {
|
||||||
|
|||||||
@@ -84,9 +84,15 @@ async function sendNewPassword() {
|
|||||||
<n-tab-pane name="signin" tab="Вход">
|
<n-tab-pane name="signin" tab="Вход">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<div class="form-label">Почта</div>
|
<div class="form-label">Почта</div>
|
||||||
<n-auto-complete v-model:value="email" :input-props="{
|
<n-auto-complete
|
||||||
autocomplete: 'disabled',
|
v-model:value="email"
|
||||||
}" :options="options" placeholder="detective@mail.ru" clearable />
|
:input-props="{
|
||||||
|
autocomplete: 'disabled',
|
||||||
|
}"
|
||||||
|
:options="options"
|
||||||
|
placeholder="detective@mail.ru"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
<div class="form-label">Пароль</div>
|
<div class="form-label">Пароль</div>
|
||||||
<n-input v-model:value="password" type="password" placeholder="********" />
|
<n-input v-model:value="password" type="password" placeholder="********" />
|
||||||
<div class="form-button-wrapper">
|
<div class="form-button-wrapper">
|
||||||
@@ -105,13 +111,21 @@ async function sendNewPassword() {
|
|||||||
<div class="form-label">Позывной</div>
|
<div class="form-label">Позывной</div>
|
||||||
<n-input v-model:value="username" type="text" placeholder="Шерлок" />
|
<n-input v-model:value="username" type="text" placeholder="Шерлок" />
|
||||||
<div class="form-label">Почта</div>
|
<div class="form-label">Почта</div>
|
||||||
<n-auto-complete v-model:value="email" :input-props="{
|
<n-auto-complete
|
||||||
autocomplete: 'disabled',
|
v-model:value="email"
|
||||||
}" :options="options" placeholder="detective@mail.ru" clearable />
|
:input-props="{
|
||||||
|
autocomplete: 'disabled',
|
||||||
|
}"
|
||||||
|
:options="options"
|
||||||
|
placeholder="detective@mail.ru"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
<div class="form-label">
|
<div class="form-label">
|
||||||
<n-checkbox v-model:checked="acceptTerms">
|
<n-checkbox v-model:checked="acceptTerms">
|
||||||
Я принимаю условия
|
Я принимаю условия
|
||||||
<a href="/user-agreement" target="_blank" class="docs-link">пользовательского соглашения</a>
|
<a href="/user-agreement" target="_blank" class="docs-link"
|
||||||
|
>пользовательского соглашения</a
|
||||||
|
>
|
||||||
</n-checkbox>
|
</n-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-label">
|
<div class="form-label">
|
||||||
@@ -124,7 +138,9 @@ async function sendNewPassword() {
|
|||||||
<div class="form-label">
|
<div class="form-label">
|
||||||
<n-button
|
<n-button
|
||||||
@click="signup"
|
@click="signup"
|
||||||
:disabled="username.length == 0 || email.length == 0 || !acceptTerms || !acceptPrivacy"
|
:disabled="
|
||||||
|
username.length == 0 || email.length == 0 || !acceptTerms || !acceptPrivacy
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<span v-if="!authStore.isLoading"> Регистрация </span>
|
<span v-if="!authStore.isLoading"> Регистрация </span>
|
||||||
<span v-else> Подождите... </span>
|
<span v-else> Подождите... </span>
|
||||||
@@ -137,9 +153,15 @@ async function sendNewPassword() {
|
|||||||
<n-tab-pane name="new-password" tab="Новый пароль">
|
<n-tab-pane name="new-password" tab="Новый пароль">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<div class="form-label">Почта</div>
|
<div class="form-label">Почта</div>
|
||||||
<n-auto-complete v-model:value="email" :input-props="{
|
<n-auto-complete
|
||||||
autocomplete: 'disabled',
|
v-model:value="email"
|
||||||
}" :options="options" placeholder="detective@mail.ru" clearable />
|
:input-props="{
|
||||||
|
autocomplete: 'disabled',
|
||||||
|
}"
|
||||||
|
:options="options"
|
||||||
|
placeholder="detective@mail.ru"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
<div class="form-button-wrapper">
|
<div class="form-button-wrapper">
|
||||||
<div class="form-label">
|
<div class="form-label">
|
||||||
<n-button @click="sendNewPassword" :disabled="email.length == 0">
|
<n-button @click="sendNewPassword" :disabled="email.length == 0">
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
/* код */
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="main">
|
|
||||||
<div class="message-cloud-3"></div>
|
|
||||||
<div class="message-cloud-2"></div>
|
|
||||||
<div class="message-cloud">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.main {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-cloud, .message-cloud-2, .message-cloud-3 {
|
|
||||||
background-color: bisque;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-cloud {
|
|
||||||
margin: 15px 0;
|
|
||||||
padding: 16px;
|
|
||||||
font-family: main;
|
|
||||||
color: black;
|
|
||||||
line-height: 20px;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 900;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-cloud,
|
|
||||||
.message-cloud-2,
|
|
||||||
.message-cloud-3 {
|
|
||||||
border-radius: 5px;
|
|
||||||
background-image: url("@/assets/images/paper.jpg");
|
|
||||||
background-size: cover;
|
|
||||||
display: flow-root;
|
|
||||||
box-shadow: 0 0 5px black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-cloud-2,
|
|
||||||
.message-cloud-3 {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-cloud-2 {
|
|
||||||
transform: rotate(-3deg);
|
|
||||||
filter: brightness(50%);
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-cloud-3 {
|
|
||||||
transform: rotate(2deg);
|
|
||||||
filter: brightness(80%);
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -17,7 +17,9 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-cloud, .message-cloud-2, .message-cloud-3 {
|
.message-cloud,
|
||||||
|
.message-cloud-2,
|
||||||
|
.message-cloud-3 {
|
||||||
background-color: bisque;
|
background-color: bisque;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,31 +37,31 @@
|
|||||||
.message-cloud,
|
.message-cloud,
|
||||||
.message-cloud-2,
|
.message-cloud-2,
|
||||||
.message-cloud-3 {
|
.message-cloud-3 {
|
||||||
border-radius: 5px;
|
background-image: url('@/assets/images/paper.jpg');
|
||||||
background-image: url("@/assets/images/paper.jpg");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
display: flow-root;
|
border-radius: 5px;
|
||||||
box-shadow: 0 0 5px black;
|
box-shadow: 0 0 5px black;
|
||||||
|
display: flow-root;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-cloud-2,
|
.message-cloud-2,
|
||||||
.message-cloud-3 {
|
.message-cloud-3 {
|
||||||
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-cloud-2 {
|
.message-cloud-2 {
|
||||||
transform: rotate(-3deg);
|
|
||||||
filter: brightness(50%);
|
filter: brightness(50%);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
transform: rotate(-3deg);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-cloud-3 {
|
.message-cloud-3 {
|
||||||
transform: rotate(2deg);
|
|
||||||
filter: brightness(80%);
|
filter: brightness(80%);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
transform: rotate(2deg);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="plate-block">
|
<div class="plate-block">
|
||||||
@@ -16,34 +14,34 @@
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.plate-block {
|
.plate-block {
|
||||||
position: relative;
|
|
||||||
box-shadow: 0 0 10px black;
|
box-shadow: 0 0 10px black;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metal-plate-block {
|
.metal-plate-block {
|
||||||
height: 100%;
|
background-image: url('@/assets/images/metal.png');
|
||||||
background-image: url("@/assets/images/metal.png");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
position: relative;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pin {
|
.pin {
|
||||||
width: 7px;
|
background-image: url('@/assets/images/pin.png');
|
||||||
|
background-size: cover;
|
||||||
height: 7px;
|
height: 7px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-image: url("@/assets/images/pin.png");
|
width: 7px;
|
||||||
background-size: cover;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pin-top-left {
|
.pin-top-left {
|
||||||
top: 3px;
|
|
||||||
left: 3px;
|
left: 3px;
|
||||||
|
top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pin-top-right {
|
.pin-top-right {
|
||||||
top: 3px;
|
|
||||||
right: 3px;
|
right: 3px;
|
||||||
|
top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pin-bottom-right {
|
.pin-bottom-right {
|
||||||
|
|||||||
+150
-142
@@ -33,178 +33,186 @@ type Tip = {
|
|||||||
*/
|
*/
|
||||||
function getDetectiveTip(): Tip {
|
function getDetectiveTip(): Tip {
|
||||||
// ----- 1. Получаем текущую дату и определяем "настроение" -----
|
// ----- 1. Получаем текущую дату и определяем "настроение" -----
|
||||||
const now = new Date();
|
const now = new Date()
|
||||||
const dayOfWeek = now.getDay(); // 0 - Воскресенье, 1 - Понедельник, ...
|
const dayOfWeek = now.getDay() // 0 - Воскресенье, 1 - Понедельник, ...
|
||||||
const dayOfMonth = now.getDate();
|
const dayOfMonth = now.getDate()
|
||||||
const month = now.getMonth() + 1; // Январь = 1, Декабрь = 12
|
const month = now.getMonth() + 1 // Январь = 1, Декабрь = 12
|
||||||
|
|
||||||
const isMonday = dayOfWeek === 1;
|
const isMonday = dayOfWeek === 1
|
||||||
const isFriday13 = dayOfWeek === 5 && dayOfMonth === 13;
|
const isFriday13 = dayOfWeek === 5 && dayOfMonth === 13
|
||||||
const isNewYear = (month === 1 && dayOfMonth === 1) || (month === 12 && dayOfMonth === 31);
|
const isNewYear = (month === 1 && dayOfMonth === 1) || (month === 12 && dayOfMonth === 31)
|
||||||
const isSummer = month >= 6 && month <= 8;
|
const isSummer = month >= 6 && month <= 8
|
||||||
const isWinter = month === 12 || month <= 2;
|
const isWinter = month === 12 || month <= 2
|
||||||
const isFullMoonJoke = dayOfMonth % 5 === 0; // Просто для разнообразия
|
const isFullMoonJoke = dayOfMonth % 5 === 0 // Просто для разнообразия
|
||||||
|
|
||||||
// ----- 2. База из 100 советов -----
|
// ----- 2. База из 100 советов -----
|
||||||
const tips: string[] = [
|
const tips: string[] = [
|
||||||
// 1-10
|
// 1-10
|
||||||
"Всегда носи с собой запасную ручку — признания любят записывать в самый неподходящий момент.",
|
'Всегда носи с собой запасную ручку — признания любят записывать в самый неподходящий момент.',
|
||||||
"Если подозреваемый слишком вежлив, он либо дипломат, либо прячет труп в гараже.",
|
'Если подозреваемый слишком вежлив, он либо дипломат, либо прячет труп в гараже.',
|
||||||
"Кофе в участке — это не напиток, это яд для улик. Пей только свой.",
|
'Кофе в участке — это не напиток, это яд для улик. Пей только свой.',
|
||||||
"Следы на месте преступления всегда ведут к тому, кто забыл закрыть дверь.",
|
'Следы на месте преступления всегда ведут к тому, кто забыл закрыть дверь.',
|
||||||
"Лучший способ разговорить молчуна — предложить ему чай с тремя ложками сахара.",
|
'Лучший способ разговорить молчуна — предложить ему чай с тремя ложками сахара.',
|
||||||
"Если всё указывает на дворецкого, ищи того, кто нанимал дворецкого.",
|
'Если всё указывает на дворецкого, ищи того, кто нанимал дворецкого.',
|
||||||
"Записывай каждую мелочь. Особенно цвет носков убитого.",
|
'Записывай каждую мелочь. Особенно цвет носков убитого.',
|
||||||
"Не верь алиби, которое начинается со слов 'Я бы никогда...'",
|
"Не верь алиби, которое начинается со слов 'Я бы никогда...'",
|
||||||
"Преступник всегда возвращается на место преступления. Или за забытым телефоном.",
|
'Преступник всегда возвращается на место преступления. Или за забытым телефоном.',
|
||||||
"Ищи того, кому есть что терять, кроме своего времени.",
|
'Ищи того, кому есть что терять, кроме своего времени.',
|
||||||
|
|
||||||
// 11-20
|
// 11-20
|
||||||
"Если женщина плачет в кабинете — дай ей платок. Если мужчина — дай ему стакан воды.",
|
'Если женщина плачет в кабинете — дай ей платок. Если мужчина — дай ему стакан воды.',
|
||||||
"Самое опасное оружие — не пистолет, а бухгалтерская книга.",
|
'Самое опасное оружие — не пистолет, а бухгалтерская книга.',
|
||||||
"Собака всегда знает убийцу. Но она не говорит, потому что у неё контракт.",
|
'Собака всегда знает убийцу. Но она не говорит, потому что у неё контракт.',
|
||||||
"Проверяй чеки, билеты и смс. Ложь не терпит документации.",
|
'Проверяй чеки, билеты и смс. Ложь не терпит документации.',
|
||||||
"Если у тела нет врагов — ты плохо искал врагов.",
|
'Если у тела нет врагов — ты плохо искал врагов.',
|
||||||
"Лупа нужна не для увеличения улик, а для того, чтобы выглядеть умнее.",
|
'Лупа нужна не для увеличения улик, а для того, чтобы выглядеть умнее.',
|
||||||
"Интуиция — это когда ты уже знаешь ответ, но хочешь убедиться, что не ошибся.",
|
'Интуиция — это когда ты уже знаешь ответ, но хочешь убедиться, что не ошибся.',
|
||||||
"Подозревай всех, даже самого себя. Иногда преступник — это ты в прошлом.",
|
'Подозревай всех, даже самого себя. Иногда преступник — это ты в прошлом.',
|
||||||
"Хороший детектив видит улики. Великий — видит их отсутствие.",
|
'Хороший детектив видит улики. Великий — видит их отсутствие.',
|
||||||
"Никогда не допрашивай свидетеля на голодный желудок. И себя тоже.",
|
'Никогда не допрашивай свидетеля на голодный желудок. И себя тоже.',
|
||||||
|
|
||||||
// 21-30
|
// 21-30
|
||||||
"Если дело зашло в тупик, переверни бумаги вверх ногами.",
|
'Если дело зашло в тупик, переверни бумаги вверх ногами.',
|
||||||
"Самый ценный свидетель — это камера наблюдения, у которой сломана защита.",
|
'Самый ценный свидетель — это камера наблюдения, у которой сломана защита.',
|
||||||
"Не ищи мотив. Ищи возможность.",
|
'Не ищи мотив. Ищи возможность.',
|
||||||
"Преступник не оставляет следов. Он оставляет выбор.",
|
'Преступник не оставляет следов. Он оставляет выбор.',
|
||||||
"Если дверь была закрыта изнутри — ищи люк, который открывается снаружи.",
|
'Если дверь была закрыта изнутри — ищи люк, который открывается снаружи.',
|
||||||
"Лучший детектив — тот, кого не замечают в толпе.",
|
'Лучший детектив — тот, кого не замечают в толпе.',
|
||||||
"Учись читать по губам. Пригодится на допросах с адвокатом.",
|
'Учись читать по губам. Пригодится на допросах с адвокатом.',
|
||||||
"Запах духов на месте преступления важнее запаха пороха.",
|
'Запах духов на месте преступления важнее запаха пороха.',
|
||||||
"Если ты нашел отпечаток пальца — проверь, не твой ли он.",
|
'Если ты нашел отпечаток пальца — проверь, не твой ли он.',
|
||||||
"Раскрыть дело — это искусство. Не раскрыть — тоже искусство, но другое.",
|
'Раскрыть дело — это искусство. Не раскрыть — тоже искусство, но другое.',
|
||||||
|
|
||||||
// 31-40
|
// 31-40
|
||||||
"Никогда не спорь с баллистикой. Она всегда права, даже если ты не согласен.",
|
'Никогда не спорь с баллистикой. Она всегда права, даже если ты не согласен.',
|
||||||
"В каждом деле есть одна странная деталь. Найди её — и пазл сложится.",
|
'В каждом деле есть одна странная деталь. Найди её — и пазл сложится.',
|
||||||
"Если преступник умный — он оставит ложный след. Если глупый — он оставит следы в соцсетях.",
|
'Если преступник умный — он оставит ложный след. Если глупый — он оставит следы в соцсетях.',
|
||||||
"Лучший друг детектива — тишина. Второй друг — кофе.",
|
'Лучший друг детектива — тишина. Второй друг — кофе.',
|
||||||
"Если свидетель говорит слишком много, он врет. Если молчит — он тоже врет.",
|
'Если свидетель говорит слишком много, он врет. Если молчит — он тоже врет.',
|
||||||
"Преступление редко бывает идеальным. Идеальным бывает только алиби жены.",
|
'Преступление редко бывает идеальным. Идеальным бывает только алиби жены.',
|
||||||
"Всегда смотри на часы на трупе. Возможно, они показывают время, когда он перестал верить в людей.",
|
'Всегда смотри на часы на трупе. Возможно, они показывают время, когда он перестал верить в людей.',
|
||||||
"Воры любят темноту. Детективы — свет. А маньяки — полную луну.",
|
'Воры любят темноту. Детективы — свет. А маньяки — полную луну.',
|
||||||
"Если ты поймал преступника, не радуйся раньше времени — может, он просто лжец.",
|
'Если ты поймал преступника, не радуйся раньше времени — может, он просто лжец.',
|
||||||
"В каждом городе есть одно место, где собираются все сплетники. Начни оттуда.",
|
'В каждом городе есть одно место, где собираются все сплетники. Начни оттуда.',
|
||||||
|
|
||||||
// 41-50
|
// 41-50
|
||||||
"Не верь в совпадения. Но верь в удачу.",
|
'Не верь в совпадения. Но верь в удачу.',
|
||||||
"Если у тебя нет улик — придумай их. Но только мысленно, чтобы знать, где искать.",
|
'Если у тебя нет улик — придумай их. Но только мысленно, чтобы знать, где искать.',
|
||||||
"Жертва всегда знала убийцу. Даже если она не знала, что знает.",
|
'Жертва всегда знала убийцу. Даже если она не знала, что знает.',
|
||||||
"Проверяй алиби на разрыв. Если оно выдерживает — ты ошибся.",
|
'Проверяй алиби на разрыв. Если оно выдерживает — ты ошибся.',
|
||||||
"Самый страшный враг детектива — не маньяк, а его собственная лень.",
|
'Самый страшный враг детектива — не маньяк, а его собственная лень.',
|
||||||
"Если преступник оставил записку — прочитай её. И сожги, чтобы никто не узнал твой почерк.",
|
'Если преступник оставил записку — прочитай её. И сожги, чтобы никто не узнал твой почерк.',
|
||||||
"Всегда носи с собой фонарик. Даже днём. Для эффекта.",
|
'Всегда носи с собой фонарик. Даже днём. Для эффекта.',
|
||||||
"В деле важно всё: от укуса комара до размера обуви.",
|
'В деле важно всё: от укуса комара до размера обуви.',
|
||||||
"Если ты думаешь, что дело простое — значит, ты не додумал.",
|
'Если ты думаешь, что дело простое — значит, ты не додумал.',
|
||||||
"Убийца всегда ищет внимания. Или наоборот, избегает его.",
|
'Убийца всегда ищет внимания. Или наоборот, избегает его.',
|
||||||
|
|
||||||
// 51-60
|
// 51-60
|
||||||
"Стук в дверь в полночь — это либо полиция, либо заказчик.",
|
'Стук в дверь в полночь — это либо полиция, либо заказчик.',
|
||||||
"Никогда не давай обещаний родственникам жертвы. Только факты.",
|
'Никогда не давай обещаний родственникам жертвы. Только факты.',
|
||||||
"Если у тебя есть три версии — выбирай четвёртую.",
|
'Если у тебя есть три версии — выбирай четвёртую.',
|
||||||
"Лучшее оружие — это бумага. На ней можно написать что угодно.",
|
'Лучшее оружие — это бумага. На ней можно написать что угодно.',
|
||||||
"Когда ты смотришь на улику, смотри на неё глазами преступника.",
|
'Когда ты смотришь на улику, смотри на неё глазами преступника.',
|
||||||
"Все любят детективы, пока не становятся подозреваемыми.",
|
'Все любят детективы, пока не становятся подозреваемыми.',
|
||||||
"Следы крови важны. Но важнее — следы лжи.",
|
'Следы крови важны. Но важнее — следы лжи.',
|
||||||
"Если ты не спишь третьи сутки, значит, ты либо гений, либо дурак.",
|
'Если ты не спишь третьи сутки, значит, ты либо гений, либо дурак.',
|
||||||
"Проверяй каждого, кто говорит 'Я ничего не видел'.",
|
"Проверяй каждого, кто говорит 'Я ничего не видел'.",
|
||||||
"В каждом деле есть один человек, который знает всё. И он не говорит.",
|
'В каждом деле есть один человек, который знает всё. И он не говорит.',
|
||||||
|
|
||||||
// 61-70
|
// 61-70
|
||||||
"Не суди преступника по одежде. Суди по тому, как он её снимал.",
|
'Не суди преступника по одежде. Суди по тому, как он её снимал.',
|
||||||
"Дыра в алиби — это как дыра в лодке. Рано или поздно она утонет.",
|
'Дыра в алиби — это как дыра в лодке. Рано или поздно она утонет.',
|
||||||
"Если преступник использует перчатки — он профессионал. Если нет — он любитель.",
|
'Если преступник использует перчатки — он профессионал. Если нет — он любитель.',
|
||||||
"Запомни: первая версия — всегда неверная.",
|
'Запомни: первая версия — всегда неверная.',
|
||||||
"Если у тебя кончились идеи, иди в бар. Там всегда есть кто-то, кто расскажет тебе дело.",
|
'Если у тебя кончились идеи, иди в бар. Там всегда есть кто-то, кто расскажет тебе дело.',
|
||||||
"Свидетель с фотороботом — это как слепой с компасом.",
|
'Свидетель с фотороботом — это как слепой с компасом.',
|
||||||
"Никогда не смейся над уликами. Они могут обидеться и исчезнуть.",
|
'Никогда не смейся над уликами. Они могут обидеться и исчезнуть.',
|
||||||
"Если ты нашёл оружие — не трогай его. Иначе твои отпечатки будут на нём.",
|
'Если ты нашёл оружие — не трогай его. Иначе твои отпечатки будут на нём.',
|
||||||
"Преступник умён, но ты умнее. Потому что ты пьёшь правильный кофе.",
|
'Преступник умён, но ты умнее. Потому что ты пьёшь правильный кофе.',
|
||||||
"Всегда пересматривай видеозаписи. Особенно в два раза медленнее.",
|
'Всегда пересматривай видеозаписи. Особенно в два раза медленнее.',
|
||||||
|
|
||||||
// 71-80
|
// 71-80
|
||||||
"Если на месте преступления есть кот — он видел всё. Но он не скажет.",
|
'Если на месте преступления есть кот — он видел всё. Но он не скажет.',
|
||||||
"Лучший способ поймать преступника — пригласить его на обед.",
|
'Лучший способ поймать преступника — пригласить его на обед.',
|
||||||
"Если подозреваемый улыбается, он либо невиновен, либо психопат.",
|
'Если подозреваемый улыбается, он либо невиновен, либо психопат.',
|
||||||
"Проверяй мусор. В нём всегда есть что-то полезное.",
|
'Проверяй мусор. В нём всегда есть что-то полезное.',
|
||||||
"Никогда не доверяй красивым историям. Только уродливым фактам.",
|
'Никогда не доверяй красивым историям. Только уродливым фактам.',
|
||||||
"Если ты нашёл улику слишком легко — это ловушка.",
|
'Если ты нашёл улику слишком легко — это ловушка.',
|
||||||
"Убийца всегда оставляет послание. Даже если это просто тишина.",
|
'Убийца всегда оставляет послание. Даже если это просто тишина.',
|
||||||
"Если ты запнулся на допросе — делай вид, что так и задумано.",
|
'Если ты запнулся на допросе — делай вид, что так и задумано.',
|
||||||
"Помни: все врут. Даже я тебе советую.",
|
'Помни: все врут. Даже я тебе советую.',
|
||||||
"Если ты боишься темноты — стань детективом. Страх пройдёт.",
|
'Если ты боишься темноты — стань детективом. Страх пройдёт.',
|
||||||
|
|
||||||
// 81-90
|
// 81-90
|
||||||
"Самые лучшие признания случаются под утро, когда все устали.",
|
'Самые лучшие признания случаются под утро, когда все устали.',
|
||||||
"Если преступник использовал компьютер, значит, он либо хакер, либо дурак.",
|
'Если преступник использовал компьютер, значит, он либо хакер, либо дурак.',
|
||||||
"Следы шин — это как подпись на дороге.",
|
'Следы шин — это как подпись на дороге.',
|
||||||
"Никогда не забывай про запах. Он помнит дольше, чем люди.",
|
'Никогда не забывай про запах. Он помнит дольше, чем люди.',
|
||||||
"Если ты не знаешь, что делать — съешь шоколадку.",
|
'Если ты не знаешь, что делать — съешь шоколадку.',
|
||||||
"В каждом деле есть одна минута слабости. Найди её.",
|
'В каждом деле есть одна минута слабости. Найди её.',
|
||||||
"Преступник всегда нервничает. Даже если он не нервничает.",
|
'Преступник всегда нервничает. Даже если он не нервничает.',
|
||||||
"Если у тебя есть подозреваемый и нет улик — подожди. Улики появятся.",
|
'Если у тебя есть подозреваемый и нет улик — подожди. Улики появятся.',
|
||||||
"Не верь в чудеса. Верь в логику.",
|
'Не верь в чудеса. Верь в логику.',
|
||||||
"Если ты устал — возьми отпуск. Преступление никуда не денется.",
|
'Если ты устал — возьми отпуск. Преступление никуда не денется.',
|
||||||
|
|
||||||
// 91-100
|
// 91-100
|
||||||
"Запомни: самый опасный преступник — тот, кто читал детективы.",
|
'Запомни: самый опасный преступник — тот, кто читал детективы.',
|
||||||
"Если ты думаешь, что всё кончено — значит, ты пропустил поворот.",
|
'Если ты думаешь, что всё кончено — значит, ты пропустил поворот.',
|
||||||
"Следы на полу важнее следов в душе.",
|
'Следы на полу важнее следов в душе.',
|
||||||
"Если преступник оставил окурок — он курил. Или хотел, чтобы ты так думал.",
|
'Если преступник оставил окурок — он курил. Или хотел, чтобы ты так думал.',
|
||||||
"Хороший детектив всегда голоден. Это помогает мыслить.",
|
'Хороший детектив всегда голоден. Это помогает мыслить.',
|
||||||
"Не суди по первому впечатлению. Суди по второму, когда увидишь его в наручниках.",
|
'Не суди по первому впечатлению. Суди по второму, когда увидишь его в наручниках.',
|
||||||
"Если у тебя есть тайный агент — не доверяй ему.",
|
'Если у тебя есть тайный агент — не доверяй ему.',
|
||||||
"В деле всегда есть тот, кто знает больше, чем говорит.",
|
'В деле всегда есть тот, кто знает больше, чем говорит.',
|
||||||
"Если ты не можешь найти убийцу — стань им (в мыслях).",
|
'Если ты не можешь найти убийцу — стань им (в мыслях).',
|
||||||
"И последнее: всегда проверяй, закрыл ли ты дверь участка."
|
'И последнее: всегда проверяй, закрыл ли ты дверь участка.',
|
||||||
];
|
]
|
||||||
|
|
||||||
// ----- 3. Шутки, зависящие от даты -----
|
// ----- 3. Шутки, зависящие от даты -----
|
||||||
const dateJokes: string[] = [];
|
const dateJokes: string[] = []
|
||||||
|
|
||||||
if (isMonday) {
|
if (isMonday) {
|
||||||
dateJokes.push("☕ Понедельник: Сегодня даже преступники не хотят работать. Проверь тех, кто был слишком бодр.");
|
dateJokes.push(
|
||||||
|
'☕ Понедельник: Сегодня даже преступники не хотят работать. Проверь тех, кто был слишком бодр.',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (isFriday13) {
|
if (isFriday13) {
|
||||||
dateJokes.push("🔮 Пятница 13-е: Если улики ведут к чёрной кошке, ищи чёрного кота с алиби.");
|
dateJokes.push('🔮 Пятница 13-е: Если улики ведут к чёрной кошке, ищи чёрного кота с алиби.')
|
||||||
}
|
}
|
||||||
if (isNewYear) {
|
if (isNewYear) {
|
||||||
dateJokes.push("🎄 Новый год: Свидетель в костюме Деда Мороза врёт. Настоящий Дед Мороз всегда в отпуске.");
|
dateJokes.push(
|
||||||
|
'🎄 Новый год: Свидетель в костюме Деда Мороза врёт. Настоящий Дед Мороз всегда в отпуске.',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (isSummer) {
|
if (isSummer) {
|
||||||
dateJokes.push("☀️ Лето: В жару преступники забывают улики в карманах, потому что снимают пиджаки.");
|
dateJokes.push(
|
||||||
|
'☀️ Лето: В жару преступники забывают улики в карманах, потому что снимают пиджаки.',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (isWinter) {
|
if (isWinter) {
|
||||||
dateJokes.push("❄️ Зима: Следы на снегу ведут к тому, у кого самые холодные руки.");
|
dateJokes.push('❄️ Зима: Следы на снегу ведут к тому, у кого самые холодные руки.')
|
||||||
}
|
}
|
||||||
if (isFullMoonJoke) {
|
if (isFullMoonJoke) {
|
||||||
dateJokes.push("🌕 Полнолуние: Если подозреваемый воет на луну, он либо оборотень, либо фанат рока.");
|
dateJokes.push(
|
||||||
|
'🌕 Полнолуние: Если подозреваемый воет на луну, он либо оборотень, либо фанат рока.',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- 4. Объединяем все советы и выбираем случайный -----
|
// ----- 4. Объединяем все советы и выбираем случайный -----
|
||||||
const allTips = tips.concat(dateJokes);
|
const allTips = tips.concat(dateJokes)
|
||||||
const randomTip = allTips[Math.floor(Math.random() * allTips.length)];
|
const randomTip = allTips[Math.floor(Math.random() * allTips.length)]
|
||||||
|
|
||||||
// ----- 5. Красивый заголовок -----
|
// ----- 5. Красивый заголовок -----
|
||||||
const headers = [
|
const headers = [
|
||||||
"🔍 Заметки сыщика:",
|
'🔍 Заметки сыщика:',
|
||||||
"📓 Из дневника инспектора:",
|
'📓 Из дневника инспектора:',
|
||||||
"🕵️ Мудрость Шерлока:",
|
'🕵️ Мудрость Шерлока:',
|
||||||
"🧠 Гениально и просто:",
|
'🧠 Гениально и просто:',
|
||||||
"📌 Правило большого пальца:",
|
'📌 Правило большого пальца:',
|
||||||
"☕ Кофейная истина:"
|
'☕ Кофейная истина:',
|
||||||
];
|
]
|
||||||
|
|
||||||
const header = headers[Math.floor(Math.random() * headers.length)];
|
const header = headers[Math.floor(Math.random() * headers.length)]
|
||||||
|
|
||||||
return { header: header, text: randomTip }
|
return { header: header, text: randomTip }
|
||||||
}
|
}
|
||||||
@@ -232,7 +240,7 @@ async function confirmDeleteAccount() {
|
|||||||
<template>
|
<template>
|
||||||
<div class="center-block-custom">
|
<div class="center-block-custom">
|
||||||
<div class="width700">
|
<div class="width700">
|
||||||
<n-alert :title="tip.header" :show-icon="false" type="info" style="margin-bottom: 20px;">
|
<n-alert :title="tip.header" :show-icon="false" type="info" style="margin-bottom: 20px">
|
||||||
{{ tip.text }}
|
{{ tip.text }}
|
||||||
</n-alert>
|
</n-alert>
|
||||||
|
|
||||||
@@ -257,8 +265,8 @@ async function confirmDeleteAccount() {
|
|||||||
class="delete-modal"
|
class="delete-modal"
|
||||||
>
|
>
|
||||||
<p class="delete-modal-text">
|
<p class="delete-modal-text">
|
||||||
Это действие необратимо. Для подтверждения удаления введите пароль от учётной записи —
|
Это действие необратимо. Для подтверждения удаления введите пароль от учётной записи — он
|
||||||
он понадобится, чтобы убедиться, что аккаунт удаляет его владелец.
|
понадобится, чтобы убедиться, что аккаунт удаляет его владелец.
|
||||||
</p>
|
</p>
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="deletePassword"
|
v-model:value="deletePassword"
|
||||||
@@ -285,45 +293,45 @@ async function confirmDeleteAccount() {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.danger-zone {
|
.danger-zone {
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16px;
|
|
||||||
margin-top: 40px;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
border: 1px solid rgb(224 108 117 / 40%);
|
border: 1px solid rgb(224 108 117 / 40%);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 40px;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger-zone-title {
|
.danger-zone-title {
|
||||||
|
color: #e06c75;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #e06c75;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger-zone-hint {
|
.danger-zone-hint {
|
||||||
margin-top: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #999;
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-modal-text {
|
.delete-modal-text {
|
||||||
margin: 0 0 16px;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #bbb;
|
color: #bbb;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-modal-error {
|
.delete-modal-error {
|
||||||
margin: 12px 0 0;
|
|
||||||
color: #e06c75;
|
color: #e06c75;
|
||||||
|
margin: 12px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-modal-buttons {
|
.delete-modal-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
justify-content: flex-end;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+311
-177
@@ -2,18 +2,28 @@
|
|||||||
import { Key24Regular } from '@vicons/fluent'
|
import { Key24Regular } from '@vicons/fluent'
|
||||||
import { LockOutlined } from '@vicons/material'
|
import { LockOutlined } from '@vicons/material'
|
||||||
import { Icon } from '@vicons/utils'
|
import { Icon } from '@vicons/utils'
|
||||||
import { NButton, NCard, NDynamicInput, NInput, NInputGroup, NModal, NSpace, NSwitch, NTag, NText, NUpload, NUploadDragger, type UploadFileInfo } from 'naive-ui'
|
import {
|
||||||
import { computed, type PropType,ref } from 'vue'
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NDynamicInput,
|
||||||
|
NInput,
|
||||||
|
NInputGroup,
|
||||||
|
NModal,
|
||||||
|
NSpace,
|
||||||
|
NSwitch,
|
||||||
|
NTag,
|
||||||
|
NText,
|
||||||
|
NUpload,
|
||||||
|
NUploadDragger,
|
||||||
|
type UploadFileInfo,
|
||||||
|
} from 'naive-ui'
|
||||||
|
import { computed, type PropType, ref } from 'vue'
|
||||||
|
|
||||||
import type { Application, Door, Key, Place } from '@/api/generated/crabs/evening_detective_server'
|
import type { Application, Door, Key, Place } from '@/api/generated/crabs/evening_detective_server'
|
||||||
import type { EditableApplication } from '@/types/editor'
|
import type { EditableApplication } from '@/types/editor'
|
||||||
import {
|
import { applicationFileName, applicationFileType, applicationFileURL } from '@/utils/application'
|
||||||
applicationFileName,
|
|
||||||
applicationFileType,
|
|
||||||
applicationFileURL,
|
|
||||||
} from '@/utils/application'
|
|
||||||
|
|
||||||
import MessagePaper from './MessagePaper.vue';
|
import MessagePaper from './MessagePaper.vue'
|
||||||
|
|
||||||
// Точка сценария + служебное поле fileList (выбранные файлы для загрузки картинки)
|
// Точка сценария + служебное поле fileList (выбранные файлы для загрузки картинки)
|
||||||
type PlaceWithFileList = Place & {
|
type PlaceWithFileList = Place & {
|
||||||
@@ -39,13 +49,13 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
addAction: {
|
addAction: {
|
||||||
type: Function as PropType<(param: string) => void>,
|
type: Function as PropType<(param: string) => void>,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:newPlaceCode': [value: string],
|
'update:newPlaceCode': [value: string]
|
||||||
'update:newPlaceName': [value: string],
|
'update:newPlaceName': [value: string]
|
||||||
'update:newPlaceText': [value: string],
|
'update:newPlaceText': [value: string]
|
||||||
'update:mode': [value: string]
|
'update:mode': [value: string]
|
||||||
}>()
|
}>()
|
||||||
const updateCode = (code: string) => {
|
const updateCode = (code: string) => {
|
||||||
@@ -193,73 +203,113 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="mode == 'mini-add'" class="place-block-hover show-editor-block" @click="updateMode('add')">
|
<div
|
||||||
|
v-if="mode == 'mini-add'"
|
||||||
|
class="place-block-hover show-editor-block"
|
||||||
|
@click="updateMode('add')"
|
||||||
|
>
|
||||||
<p class="place-image-plus">+</p>
|
<p class="place-image-plus">+</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="mode == 'add'" class="place-block">
|
<div v-if="mode == 'add'" class="place-block">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input :style="{ width: '20%' }" :value="newPlaceCode" @update:value="updateCode" type="text"
|
<n-input
|
||||||
placeholder="Ы-1" />
|
:style="{ width: '20%' }"
|
||||||
<n-input :style="{ width: '80%' }" :value="newPlaceName" @update:value="updateName" type="text"
|
:value="newPlaceCode"
|
||||||
placeholder="Дом №Ы" />
|
@update:value="updateCode"
|
||||||
|
type="text"
|
||||||
|
placeholder="Ы-1"
|
||||||
|
/>
|
||||||
|
<n-input
|
||||||
|
:style="{ width: '80%' }"
|
||||||
|
:value="newPlaceName"
|
||||||
|
@update:value="updateName"
|
||||||
|
type="text"
|
||||||
|
placeholder="Дом №Ы"
|
||||||
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
<n-input :value="newPlaceText" @update:value="updateText" type="textarea" :autosize="{ minRows: 3 }"
|
<n-input
|
||||||
placeholder="В начале было слово..." />
|
:value="newPlaceText"
|
||||||
|
@update:value="updateText"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 3 }"
|
||||||
|
placeholder="В начале было слово..."
|
||||||
|
/>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="mode == 'show-editor'" class="place-block-hover show-editor-block" @click="updateMode('edit')">
|
<div
|
||||||
<div class="message-header">
|
v-if="mode == 'show-editor'"
|
||||||
{{ props.place?.code }}: {{ props.place?.name }}
|
class="place-block-hover show-editor-block"
|
||||||
</div>
|
@click="updateMode('edit')"
|
||||||
|
>
|
||||||
|
<div class="message-header">{{ props.place?.code }}: {{ props.place?.name }}</div>
|
||||||
<hr class="hr" />
|
<hr class="hr" />
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
<div v-if="props.place?.image">
|
<div v-if="props.place?.image">
|
||||||
<div class="message-image-border"
|
<div
|
||||||
@click.stop="openImageView(props.place?.name || '', props.place?.image || '')">
|
class="message-image-border"
|
||||||
<img :src="props.place?.image" class="message-image">
|
@click.stop="openImageView(props.place?.name || '', props.place?.image || '')"
|
||||||
|
>
|
||||||
|
<img :src="props.place?.image" class="message-image" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ props.place?.text }}
|
{{ props.place?.text }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="door in props.place?.doors" v-bind:key="door.code" class="door-block">
|
<div v-for="door in props.place?.doors" v-bind:key="door.code" class="door-block">
|
||||||
{{ door.name }} <span v-for="key in door.keys" :key="key.name" class="key-block">
|
{{ door.name }}
|
||||||
|
<span v-for="key in door.keys" :key="key.name" class="key-block">
|
||||||
<Icon class="lock-icon">
|
<Icon class="lock-icon">
|
||||||
<LockOutlined />
|
<LockOutlined />
|
||||||
</Icon> {{ key.name }}
|
</Icon>
|
||||||
|
{{ key.name }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="hr" v-if="props.place?.applications?.length" />
|
<hr class="hr" v-if="props.place?.applications?.length" />
|
||||||
<div class="message-footer application-block" v-for="application in props.place?.applications"
|
<div
|
||||||
v-bind:key="application.name">
|
class="message-footer application-block"
|
||||||
|
v-for="application in props.place?.applications"
|
||||||
|
v-bind:key="application.name"
|
||||||
|
>
|
||||||
<div class="application-name">Приложение: {{ application.name }}</div>
|
<div class="application-name">Приложение: {{ application.name }}</div>
|
||||||
<div v-if="applicationFileType(application) == 'image' && application.image" class="application-image-border"
|
<div
|
||||||
@click.stop="openApplicationView(application)">
|
v-if="applicationFileType(application) == 'image' && application.image"
|
||||||
<img :src="applicationFileURL(application.image)" class="application-image">
|
class="application-image-border"
|
||||||
|
@click.stop="openApplicationView(application)"
|
||||||
|
>
|
||||||
|
<img :src="applicationFileURL(application.image)" class="application-image" />
|
||||||
</div>
|
</div>
|
||||||
<audio v-else-if="applicationFileType(application) == 'audio' && application.image"
|
<audio
|
||||||
:src="applicationFileURL(application.image)" controls preload="none" class="application-audio"
|
v-else-if="applicationFileType(application) == 'audio' && application.image"
|
||||||
@click.stop></audio>
|
:src="applicationFileURL(application.image)"
|
||||||
<a v-else-if="applicationFileType(application) == 'pdf' && application.image"
|
controls
|
||||||
:href="applicationFileURL(application.image)" target="_blank" rel="noopener" class="application-pdf-link"
|
preload="none"
|
||||||
@click.stop>
|
class="application-audio"
|
||||||
|
@click.stop
|
||||||
|
></audio>
|
||||||
|
<a
|
||||||
|
v-else-if="applicationFileType(application) == 'pdf' && application.image"
|
||||||
|
:href="applicationFileURL(application.image)"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="application-pdf-link"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
PDF
|
PDF
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="gray-block">
|
<p class="gray-block">
|
||||||
<span v-if="props.place?.hidden">
|
<span v-if="props.place?.hidden"> Скрытая точка </span>
|
||||||
Скрытая точка
|
|
||||||
</span>
|
|
||||||
<span v-for="key in props.place?.keys" :key="key.name" class="key-block">
|
<span v-for="key in props.place?.keys" :key="key.name" class="key-block">
|
||||||
<Icon class="lock-icon">
|
<Icon class="lock-icon">
|
||||||
<Key24Regular />
|
<Key24Regular />
|
||||||
</Icon> {{ key.name }}
|
</Icon>
|
||||||
|
{{ key.name }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -273,22 +323,31 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
<hr class="show-hr" />
|
<hr class="show-hr" />
|
||||||
<div class="message-show-content font">
|
<div class="message-show-content font">
|
||||||
<div v-if="place?.image?.length">
|
<div v-if="place?.image?.length">
|
||||||
<div class="message-image-border"
|
<div
|
||||||
@click="openImageView(place?.name || '', place?.image || '')">
|
class="message-image-border"
|
||||||
|
@click="openImageView(place?.name || '', place?.image || '')"
|
||||||
|
>
|
||||||
<img v-bind:src="place?.image" class="message-image" />
|
<img v-bind:src="place?.image" class="message-image" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Если у точки нет своей картинки, первая картинка-улика занимает
|
<!-- Если у точки нет своей картинки, первая картинка-улика занимает
|
||||||
её место в самом начале текста -->
|
её место в самом начале текста -->
|
||||||
<div v-else-if="firstImageApplication" class="message-image-border"
|
<div
|
||||||
@click="openApplicationView(firstImageApplication)">
|
v-else-if="firstImageApplication"
|
||||||
|
class="message-image-border"
|
||||||
|
@click="openApplicationView(firstImageApplication)"
|
||||||
|
>
|
||||||
<img :src="applicationFileURL(firstImageApplication.image)" class="message-image" />
|
<img :src="applicationFileURL(firstImageApplication.image)" class="message-image" />
|
||||||
</div>
|
</div>
|
||||||
{{ textBeforeApplications }}
|
{{ textBeforeApplications }}
|
||||||
|
|
||||||
<!-- Остальные улики-картинки встроены в поток текста: блок справа, текст обтекает его -->
|
<!-- Остальные улики-картинки встроены в поток текста: блок справа, текст обтекает его -->
|
||||||
<div v-if="flowApplications.length" class="show-applications-block">
|
<div v-if="flowApplications.length" class="show-applications-block">
|
||||||
<div class="show-application" v-for="application in flowApplications" :key="application.name">
|
<div
|
||||||
|
class="show-application"
|
||||||
|
v-for="application in flowApplications"
|
||||||
|
:key="application.name"
|
||||||
|
>
|
||||||
<!-- Картинка-улика без заголовка, просто фото внизу справа -->
|
<!-- Картинка-улика без заголовка, просто фото внизу справа -->
|
||||||
<div class="application-image-block" @click="openApplicationView(application)">
|
<div class="application-image-block" @click="openApplicationView(application)">
|
||||||
<img :src="applicationFileURL(application.image)" class="application-image" />
|
<img :src="applicationFileURL(application.image)" class="application-image" />
|
||||||
@@ -299,25 +358,41 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
{{ textAfterApplications }}
|
{{ textAfterApplications }}
|
||||||
</div>
|
</div>
|
||||||
<hr class="show-hr" v-if="props.place?.doors?.length" />
|
<hr class="show-hr" v-if="props.place?.doors?.length" />
|
||||||
<div v-for="door in props.place?.doors" :key="door.code" class="button-dialog font"
|
<div
|
||||||
v-on:click="addAction!(door.code!)">
|
v-for="door in props.place?.doors"
|
||||||
<div class="button-dialog-text">
|
:key="door.code"
|
||||||
{{ door.code }} {{ door.name }}
|
class="button-dialog font"
|
||||||
</div>
|
v-on:click="addAction!(door.code!)"
|
||||||
|
>
|
||||||
|
<div class="button-dialog-text">{{ door.code }} {{ door.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Аудио и PDF-улики (и улики без файла): в самом низу после
|
<!-- Аудио и PDF-улики (и улики без файла): в самом низу после
|
||||||
пунктирной линии; PDF открывается по нажатию на название -->
|
пунктирной линии; PDF открывается по нажатию на название -->
|
||||||
<hr class="show-hr" v-if="bottomApplications.length" />
|
<hr class="show-hr" v-if="bottomApplications.length" />
|
||||||
<div class="message-show-footer font" v-for="application in bottomApplications" :key="application.name">
|
<div
|
||||||
<a v-if="applicationFileType(application) == 'pdf' && application.image"
|
class="message-show-footer font"
|
||||||
:href="applicationFileURL(application.image)" target="_blank" rel="noopener" class="pdf-application-name">
|
v-for="application in bottomApplications"
|
||||||
|
:key="application.name"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
v-if="applicationFileType(application) == 'pdf' && application.image"
|
||||||
|
:href="applicationFileURL(application.image)"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="pdf-application-name"
|
||||||
|
>
|
||||||
Приложение: {{ application.name }}
|
Приложение: {{ application.name }}
|
||||||
</a>
|
</a>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="application-label">Приложение: {{ application.name }}</div>
|
<div class="application-label">Приложение: {{ application.name }}</div>
|
||||||
<audio v-if="applicationFileType(application) == 'audio' && application.image"
|
<audio
|
||||||
:src="applicationFileURL(application.image)" controls preload="none" class="application-audio"></audio>
|
v-if="applicationFileType(application) == 'audio' && application.image"
|
||||||
|
:src="applicationFileURL(application.image)"
|
||||||
|
controls
|
||||||
|
preload="none"
|
||||||
|
class="application-audio"
|
||||||
|
></audio>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</MessagePaper>
|
</MessagePaper>
|
||||||
@@ -335,21 +410,42 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
<div v-if="mode == 'edit'" class="place-block">
|
<div v-if="mode == 'edit'" class="place-block">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input :style="{ width: '20%' }" v-model:value="props.place!.code" type="text" placeholder="Ы-1" />
|
<n-input
|
||||||
<n-input :style="{ width: '80%' }" v-model:value="props.place!.name" type="text" placeholder="Дом №Ы" />
|
:style="{ width: '20%' }"
|
||||||
|
v-model:value="props.place!.code"
|
||||||
|
type="text"
|
||||||
|
placeholder="Ы-1"
|
||||||
|
/>
|
||||||
|
<n-input
|
||||||
|
:style="{ width: '80%' }"
|
||||||
|
v-model:value="props.place!.name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Дом №Ы"
|
||||||
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
<n-input v-model:value="props.place!.text" type="textarea" :autosize="{ minRows: 3 }"
|
<n-input
|
||||||
placeholder="В начале было слово..." />
|
v-model:value="props.place!.text"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 3 }"
|
||||||
|
placeholder="В начале было слово..."
|
||||||
|
/>
|
||||||
|
|
||||||
<p class="settings-header">Картинка</p>
|
<p class="settings-header">Картинка</p>
|
||||||
<div>
|
<div>
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<img :src="props.place!.image" class="message-edit-image">
|
<img :src="props.place!.image" class="message-edit-image" />
|
||||||
<n-button @click="props.place!.image = '', props.place!.fileList = undefined">Убрать картинку</n-button>
|
<n-button @click="((props.place!.image = ''), (props.place!.fileList = undefined))"
|
||||||
|
>Убрать картинку</n-button
|
||||||
|
>
|
||||||
</n-space>
|
</n-space>
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<n-input v-model:value="props.place!.image" type="text" placeholder="Не задано" disabled />
|
<n-input
|
||||||
|
v-model:value="props.place!.image"
|
||||||
|
type="text"
|
||||||
|
placeholder="Не задано"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
<n-upload directory-dnd :max="1" v-model:file-list="props.place!.fileList">
|
<n-upload directory-dnd :max="1" v-model:file-list="props.place!.fileList">
|
||||||
<n-upload-dragger>
|
<n-upload-dragger>
|
||||||
<n-text style="font-size: 16px">
|
<n-text style="font-size: 16px">
|
||||||
@@ -363,12 +459,8 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
|
|
||||||
<p class="settings-header">Видимость точки</p>
|
<p class="settings-header">Видимость точки</p>
|
||||||
<n-switch v-model:value="props.place!.hidden">
|
<n-switch v-model:value="props.place!.hidden">
|
||||||
<template #checked>
|
<template #checked> Скрытая точка </template>
|
||||||
Скрытая точка
|
<template #unchecked> Обычная точка </template>
|
||||||
</template>
|
|
||||||
<template #unchecked>
|
|
||||||
Обычная точка
|
|
||||||
</template>
|
|
||||||
</n-switch>
|
</n-switch>
|
||||||
|
|
||||||
Улики
|
Улики
|
||||||
@@ -385,21 +477,41 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
{{ applicationFileType(value) || 'файл' }}
|
{{ applicationFileType(value) || 'файл' }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</n-text>
|
</n-text>
|
||||||
<n-button size="small" quaternary @click="removeApplicationFile(editableApplication(value))">
|
<n-button
|
||||||
|
size="small"
|
||||||
|
quaternary
|
||||||
|
@click="removeApplicationFile(editableApplication(value))"
|
||||||
|
>
|
||||||
Убрать файл
|
Убрать файл
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
<img v-if="applicationFileType(value) == 'image'" :src="applicationFileURL(value.image)"
|
<img
|
||||||
class="application-edit-image" />
|
v-if="applicationFileType(value) == 'image'"
|
||||||
<audio v-else-if="applicationFileType(value) == 'audio'" :src="applicationFileURL(value.image)"
|
:src="applicationFileURL(value.image)"
|
||||||
controls preload="none" class="application-edit-audio"></audio>
|
class="application-edit-image"
|
||||||
<a v-else-if="applicationFileType(value) == 'pdf'" :href="applicationFileURL(value.image)"
|
/>
|
||||||
target="_blank" rel="noopener" class="application-pdf-link">
|
<audio
|
||||||
|
v-else-if="applicationFileType(value) == 'audio'"
|
||||||
|
:src="applicationFileURL(value.image)"
|
||||||
|
controls
|
||||||
|
preload="none"
|
||||||
|
class="application-edit-audio"
|
||||||
|
></audio>
|
||||||
|
<a
|
||||||
|
v-else-if="applicationFileType(value) == 'pdf'"
|
||||||
|
:href="applicationFileURL(value.image)"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="application-pdf-link"
|
||||||
|
>
|
||||||
PDF
|
PDF
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<n-upload :max="1" v-model:file-list="editableApplication(value).fileList"
|
<n-upload
|
||||||
accept="image/*,application/pdf,audio/*">
|
:max="1"
|
||||||
|
v-model:file-list="editableApplication(value).fileList"
|
||||||
|
accept="image/*,application/pdf,audio/*"
|
||||||
|
>
|
||||||
<n-button size="small">Загрузить файл улики</n-button>
|
<n-button size="small">Загрузить файл улики</n-button>
|
||||||
</n-upload>
|
</n-upload>
|
||||||
</div>
|
</div>
|
||||||
@@ -410,18 +522,27 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
<n-dynamic-input v-model:value="props.place!.doors" :on-create="onCreateDoor">
|
<n-dynamic-input v-model:value="props.place!.doors" :on-create="onCreateDoor">
|
||||||
<template #create-button-default> Добавить действие </template>
|
<template #create-button-default> Добавить действие </template>
|
||||||
<template #action="{ index, create, remove }">
|
<template #action="{ index, create, remove }">
|
||||||
<div style="display: flex; flex-direction: column;">
|
<div style="display: flex; flex-direction: column">
|
||||||
<n-button @click="() => create(index)" class="plus-minus-buttons">+</n-button>
|
<n-button @click="() => create(index)" class="plus-minus-buttons">+</n-button>
|
||||||
<n-button @click="() => remove(index)" class="plus-minus-buttons">-</n-button>
|
<n-button @click="() => remove(index)" class="plus-minus-buttons">-</n-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default="{ value: door }">
|
<template #default="{ value: door }">
|
||||||
<div style="display: flex; flex-direction: column; width: 100%">
|
<div style="display: flex; flex-direction: column; width: 100%">
|
||||||
|
|
||||||
<div class="door-add-block">
|
<div class="door-add-block">
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input :style="{ width: '30%' }" v-model:value="door.code" type="text" placeholder="Код" />
|
<n-input
|
||||||
<n-input :style="{ width: '70%' }" v-model:value="door.name" type="text" placeholder="Название" />
|
:style="{ width: '30%' }"
|
||||||
|
v-model:value="door.code"
|
||||||
|
type="text"
|
||||||
|
placeholder="Код"
|
||||||
|
/>
|
||||||
|
<n-input
|
||||||
|
:style="{ width: '70%' }"
|
||||||
|
v-model:value="door.name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Название"
|
||||||
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
|
|
||||||
<p class="need-keys-text">Требуемые ключи</p>
|
<p class="need-keys-text">Требуемые ключи</p>
|
||||||
@@ -429,14 +550,13 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
<n-dynamic-input v-model:value="door.keys" :on-create="onCreateKey">
|
<n-dynamic-input v-model:value="door.keys" :on-create="onCreateKey">
|
||||||
<template #create-button-default> Добавить ключ </template>
|
<template #create-button-default> Добавить ключ </template>
|
||||||
<template #default="{ value: key }">
|
<template #default="{ value: key }">
|
||||||
<div style="display: flex; align-items: center; width: 100%">
|
<div style="align-items: center; display: flex; width: 100%">
|
||||||
<n-input v-model:value="key.name" type="text" placeholder="Название" />
|
<n-input v-model:value="key.name" type="text" placeholder="Название" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</n-dynamic-input>
|
</n-dynamic-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</n-dynamic-input>
|
</n-dynamic-input>
|
||||||
@@ -445,7 +565,7 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
<n-dynamic-input v-model:value="props.place!.keys" :on-create="onCreateKey">
|
<n-dynamic-input v-model:value="props.place!.keys" :on-create="onCreateKey">
|
||||||
<template #create-button-default> Добавить ключ </template>
|
<template #create-button-default> Добавить ключ </template>
|
||||||
<template #default="{ value: key }">
|
<template #default="{ value: key }">
|
||||||
<div style="display: flex; align-items: center; width: 100%">
|
<div style="align-items: center; display: flex; width: 100%">
|
||||||
<n-input v-model:value="key.name" type="text" placeholder="Название" />
|
<n-input v-model:value="key.name" type="text" placeholder="Название" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -459,22 +579,37 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
|
|
||||||
<!-- Просмотр улики / картинки точки -->
|
<!-- Просмотр улики / картинки точки -->
|
||||||
<n-modal v-model:show="showViewApplicationModal" :mask-closable="true">
|
<n-modal v-model:show="showViewApplicationModal" :mask-closable="true">
|
||||||
<n-card style="width: min(94vw, 1200px)" :bordered="false" role="dialog" aria-modal="true"
|
<n-card
|
||||||
:content-style="{ padding: '14px' }">
|
style="width: min(94vw, 1200px)"
|
||||||
|
:bordered="false"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
:content-style="{ padding: '14px' }"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
{{ viewApplication?.name || viewImage?.title }}
|
{{ viewApplication?.name || viewImage?.title }}
|
||||||
</template>
|
</template>
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-button quaternary circle @click="closeApplicationView()"> x </n-button>
|
<n-button quaternary circle @click="closeApplicationView()"> x </n-button>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="viewApplication && applicationFileType(viewApplication) == 'image'"
|
<div
|
||||||
class="view-application-image-block">
|
v-if="viewApplication && applicationFileType(viewApplication) == 'image'"
|
||||||
|
class="view-application-image-block"
|
||||||
|
>
|
||||||
<img :src="applicationFileURL(viewApplication.image)" class="view-application-image" />
|
<img :src="applicationFileURL(viewApplication.image)" class="view-application-image" />
|
||||||
</div>
|
</div>
|
||||||
<iframe v-else-if="viewApplication && applicationFileType(viewApplication) == 'pdf'"
|
<iframe
|
||||||
:src="applicationFileURL(viewApplication.image)" class="view-application-pdf"></iframe>
|
v-else-if="viewApplication && applicationFileType(viewApplication) == 'pdf'"
|
||||||
<audio v-else-if="viewApplication && applicationFileType(viewApplication) == 'audio'"
|
:src="applicationFileURL(viewApplication.image)"
|
||||||
:src="applicationFileURL(viewApplication.image)" controls autoplay class="view-application-audio"></audio>
|
class="view-application-pdf"
|
||||||
|
></iframe>
|
||||||
|
<audio
|
||||||
|
v-else-if="viewApplication && applicationFileType(viewApplication) == 'audio'"
|
||||||
|
:src="applicationFileURL(viewApplication.image)"
|
||||||
|
controls
|
||||||
|
autoplay
|
||||||
|
class="view-application-audio"
|
||||||
|
></audio>
|
||||||
<div v-else-if="viewImage" class="view-application-image-block">
|
<div v-else-if="viewImage" class="view-application-image-block">
|
||||||
<img :src="viewImage.url" class="view-application-image" />
|
<img :src="viewImage.url" class="view-application-image" />
|
||||||
</div>
|
</div>
|
||||||
@@ -484,33 +619,33 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.hr {
|
.hr {
|
||||||
margin: 10px 0;
|
|
||||||
border: dashed 1px;
|
border: dashed 1px;
|
||||||
|
|
||||||
/* border-color: black; */
|
/* border-color: black; */
|
||||||
border-color: #eee;
|
border-color: #eee;
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.place-block-hover,
|
.place-block-hover,
|
||||||
.place-block {
|
.place-block {
|
||||||
|
background-color: #222;
|
||||||
|
border: 1px solid #222;
|
||||||
|
border-radius: 10px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #222;
|
|
||||||
background-color: #222;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.place-block-hover:hover {
|
.place-block-hover:hover {
|
||||||
|
border: 1px solid #63e2b7;
|
||||||
color: #63e2b7;
|
color: #63e2b7;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid #63e2b7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.place-image-plus {
|
.place-image-plus {
|
||||||
font-size: 30px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
font-size: 30px;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-editor-block {
|
.show-editor-block {
|
||||||
@@ -523,13 +658,13 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gray-block {
|
.gray-block {
|
||||||
margin-top: 5px;
|
|
||||||
color: #777;
|
color: #777;
|
||||||
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-block {
|
.application-block {
|
||||||
margin-right: 5px;
|
|
||||||
color: burlywood;
|
color: burlywood;
|
||||||
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-name {
|
.application-name {
|
||||||
@@ -537,25 +672,25 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.application-image-border {
|
.application-image-border {
|
||||||
width: 40%;
|
background-image: url('@/assets/images/paper_white.jpg');
|
||||||
max-width: 120px;
|
|
||||||
float: left;
|
|
||||||
padding: 7px;
|
|
||||||
margin-right: 15px;
|
|
||||||
background-image: url("@/assets/images/paper_white.jpg");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
box-shadow: 0 3px 15px rgb(98 98 98);
|
box-shadow: 0 3px 15px rgb(98 98 98);
|
||||||
transform: rotate(-3deg);
|
|
||||||
cursor: zoom-in;
|
cursor: zoom-in;
|
||||||
|
float: left;
|
||||||
|
margin-right: 15px;
|
||||||
|
max-width: 120px;
|
||||||
|
padding: 7px;
|
||||||
|
transform: rotate(-3deg);
|
||||||
|
width: 40%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* В редакторе улики показываются друг под другом:
|
/* В редакторе улики показываются друг под другом:
|
||||||
картинка улики не плавает, а идёт под своим названием */
|
картинка улики не плавает, а идёт под своим названием */
|
||||||
.message-footer .application-image-border {
|
.message-footer .application-image-border {
|
||||||
float: none;
|
float: none;
|
||||||
width: 120px;
|
|
||||||
max-width: 120px;
|
|
||||||
margin: 4px 0 10px;
|
margin: 4px 0 10px;
|
||||||
|
max-width: 120px;
|
||||||
|
width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-image {
|
.application-image {
|
||||||
@@ -563,26 +698,26 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.application-image-block {
|
.application-image-block {
|
||||||
margin: 14px 0 5px;
|
background-image: url('@/assets/images/paper_white.jpg');
|
||||||
padding: 7px;
|
|
||||||
max-width: 120px;
|
|
||||||
background-image: url("@/assets/images/paper_white.jpg");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
box-shadow: 0 3px 15px rgb(98 98 98);
|
box-shadow: 0 3px 15px rgb(98 98 98);
|
||||||
cursor: zoom-in;
|
cursor: zoom-in;
|
||||||
|
margin: 14px 0 5px;
|
||||||
|
max-width: 120px;
|
||||||
|
padding: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-audio {
|
.application-audio {
|
||||||
width: 100%;
|
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-pdf-link {
|
.application-pdf-link {
|
||||||
|
color: var(--second-color);
|
||||||
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
color: var(--second-color);
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-application-image-block {
|
.view-application-image-block {
|
||||||
@@ -590,14 +725,14 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.view-application-image {
|
.view-application-image {
|
||||||
max-width: 100%;
|
|
||||||
max-height: 72vh;
|
max-height: 72vh;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-application-pdf {
|
.view-application-pdf {
|
||||||
width: 100%;
|
|
||||||
height: 72vh;
|
|
||||||
border: none;
|
border: none;
|
||||||
|
height: 72vh;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-application-audio {
|
.view-application-audio {
|
||||||
@@ -605,22 +740,22 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.application-edit-file {
|
.application-edit-file {
|
||||||
|
background-color: #222;
|
||||||
|
border-radius: 5px;
|
||||||
margin: 8px 0 4px;
|
margin: 8px 0 4px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #222;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-edit-image {
|
.application-edit-image {
|
||||||
width: 100%;
|
|
||||||
max-width: 120px;
|
|
||||||
margin-top: 6px;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
margin-top: 6px;
|
||||||
|
max-width: 120px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-edit-audio {
|
.application-edit-audio {
|
||||||
width: 100%;
|
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-label {
|
.application-label {
|
||||||
@@ -628,19 +763,19 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.door-block {
|
.door-block {
|
||||||
|
background-color: #222;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #eee;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 10px 5px 10px 0;
|
margin: 10px 5px 10px 0;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #222;
|
|
||||||
color: #eee;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.door-add-block {
|
.door-add-block {
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
|
border-radius: 5px;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.key-block {
|
.key-block {
|
||||||
@@ -669,30 +804,30 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
|
|
||||||
.plus-minus-buttons {
|
.plus-minus-buttons {
|
||||||
border: 1px solid #444;
|
border: 1px solid #444;
|
||||||
padding: 0 13px;
|
border-radius: 40px;
|
||||||
margin: 0 0 10px 5px;
|
|
||||||
text-align: center;
|
|
||||||
color: #eee;
|
color: #eee;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
border-radius: 40px;
|
margin: 0 0 10px 5px;
|
||||||
|
padding: 0 13px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plus-minus-buttons:hover {
|
.plus-minus-buttons:hover {
|
||||||
cursor: pointer;
|
|
||||||
color: #63e2b7;
|
color: #63e2b7;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-image-border {
|
.message-image-border {
|
||||||
width: 40%;
|
background-image: url('@/assets/images/paper_white.jpg');
|
||||||
max-width: 200px;
|
|
||||||
float: left;
|
|
||||||
padding: 7px;
|
|
||||||
margin-right: 15px;
|
|
||||||
background-image: url("@/assets/images/paper_white.jpg");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
box-shadow: 0 3px 15px rgb(98 98 98);
|
box-shadow: 0 3px 15px rgb(98 98 98);
|
||||||
transform: rotate(-3deg);
|
|
||||||
cursor: zoom-in;
|
cursor: zoom-in;
|
||||||
|
float: left;
|
||||||
|
margin-right: 15px;
|
||||||
|
max-width: 200px;
|
||||||
|
padding: 7px;
|
||||||
|
transform: rotate(-3deg);
|
||||||
|
width: 40%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-image {
|
.message-image {
|
||||||
@@ -700,8 +835,8 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-edit-image {
|
.message-edit-image {
|
||||||
width: 135px;
|
|
||||||
height: 135px;
|
height: 135px;
|
||||||
|
width: 135px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-header {
|
.message-header {
|
||||||
@@ -710,9 +845,9 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-footer {
|
.message-footer {
|
||||||
padding-right: 50px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: burlywood;
|
color: burlywood;
|
||||||
|
font-weight: 400;
|
||||||
|
padding-right: 50px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,15 +857,15 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.collapse-icon {
|
.collapse-icon {
|
||||||
position: absolute;
|
background-image: url('@/assets/images/collapse.png');
|
||||||
top: 12px;
|
|
||||||
right: 12px;
|
|
||||||
width: 45px;
|
|
||||||
height: 28px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-image: url("@/assets/images/collapse.png");
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 28px;
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 12px;
|
||||||
|
width: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapse-icon-up {
|
.collapse-icon-up {
|
||||||
@@ -738,9 +873,9 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.show-hr {
|
.show-hr {
|
||||||
margin: 10px 0;
|
|
||||||
border: dashed 1px;
|
border: dashed 1px;
|
||||||
border-color: black;
|
border-color: black;
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font {
|
.font {
|
||||||
@@ -749,43 +884,42 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-show-content {
|
.message-show-content {
|
||||||
font-weight: 500;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
|
|
||||||
/* BFC: плавающий блок улик остаётся внутри текста и не наезжает
|
/* BFC: плавающий блок улик остаётся внутри текста и не наезжает
|
||||||
на следующие секции (двери/действия) при коротком тексте */
|
на следующие секции (двери/действия) при коротком тексте */
|
||||||
display: flow-root;
|
display: flow-root;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PDF-улики внизу после пунктирной линии */
|
/* PDF-улики внизу после пунктирной линии */
|
||||||
.message-show-footer {
|
.message-show-footer {
|
||||||
padding-right: 50px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--second-color);
|
color: var(--second-color);
|
||||||
|
font-weight: 400;
|
||||||
|
padding-right: 50px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pdf-application-name {
|
.pdf-application-name {
|
||||||
color: var(--second-color);
|
color: var(--second-color);
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Улики встроены в поток текста: блок прижат к правому краю,
|
/* Улики встроены в поток текста: блок прижат к правому краю,
|
||||||
текст обтекает его с левой стороны */
|
текст обтекает его с левой стороны */
|
||||||
.show-applications-block {
|
.show-applications-block {
|
||||||
float: right;
|
align-items: flex-end;
|
||||||
margin: 4px 0 10px 14px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-end;
|
float: right;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
margin: 4px 0 10px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-application {
|
.show-application {
|
||||||
|
align-items: flex-end;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-end;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,8 +928,8 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.show-application .application-audio {
|
.show-application .application-audio {
|
||||||
width: 260px;
|
|
||||||
margin: 4px 0 0;
|
margin: 4px 0 0;
|
||||||
|
width: 260px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-application .application-pdf-link {
|
.show-application .application-pdf-link {
|
||||||
@@ -803,16 +937,16 @@ const bottomApplications = computed<Application[]>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button-dialog {
|
.button-dialog {
|
||||||
display: inline-block;
|
background-image: url('@/assets/images/belt.png');
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-right: 10px;
|
|
||||||
background-image: url("@/assets/images/belt.png");
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
border-radius: 5px;
|
||||||
color: #bfa07d;
|
color: #bfa07d;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-dialog-text {
|
.button-dialog-text {
|
||||||
|
|||||||
@@ -44,28 +44,49 @@ const updateMode = (mode: string) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="mode == 'mini-add'" class="place-block-hover show-editor-block" @click="updateMode('add')">
|
<div
|
||||||
|
v-if="mode == 'mini-add'"
|
||||||
|
class="place-block-hover show-editor-block"
|
||||||
|
@click="updateMode('add')"
|
||||||
|
>
|
||||||
<p class="place-image-plus">+</p>
|
<p class="place-image-plus">+</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="mode == 'add'" class="place-block">
|
<div v-if="mode == 'add'" class="place-block">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input :style="{ width: '25%' }" :value="newQuestionCode" @update:value="updateCode" type="text"
|
<n-input
|
||||||
placeholder="В-1" />
|
:style="{ width: '25%' }"
|
||||||
<n-input :style="{ width: '75%' }" :value="newQuestionText" @update:value="updateText" type="text"
|
:value="newQuestionCode"
|
||||||
placeholder="Кто украл пастуха?" />
|
@update:value="updateCode"
|
||||||
|
type="text"
|
||||||
|
placeholder="В-1"
|
||||||
|
/>
|
||||||
|
<n-input
|
||||||
|
:style="{ width: '75%' }"
|
||||||
|
:value="newQuestionText"
|
||||||
|
@update:value="updateText"
|
||||||
|
type="text"
|
||||||
|
placeholder="Кто украл пастуха?"
|
||||||
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
<n-input :value="newQuestionAnswer" @update:value="updateAnswer" type="textarea" :autosize="{ minRows: 2 }"
|
<n-input
|
||||||
placeholder="Правильный ответ (виден только автору и организаторам)" />
|
:value="newQuestionAnswer"
|
||||||
|
@update:value="updateAnswer"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 2 }"
|
||||||
|
placeholder="Правильный ответ (виден только автору и организаторам)"
|
||||||
|
/>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="mode == 'show-editor'" class="place-block-hover show-editor-block" @click="updateMode('edit')">
|
<div
|
||||||
<div class="message-header">
|
v-if="mode == 'show-editor'"
|
||||||
{{ props.question?.code }}: {{ props.question?.text }}
|
class="place-block-hover show-editor-block"
|
||||||
</div>
|
@click="updateMode('edit')"
|
||||||
|
>
|
||||||
|
<div class="message-header">{{ props.question?.code }}: {{ props.question?.text }}</div>
|
||||||
<hr class="hr" />
|
<hr class="hr" />
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
<span v-if="props.question?.answer">Ответ: {{ props.question.answer }}</span>
|
<span v-if="props.question?.answer">Ответ: {{ props.question.answer }}</span>
|
||||||
@@ -76,14 +97,27 @@ const updateMode = (mode: string) => {
|
|||||||
<div v-if="mode == 'edit'" class="place-block">
|
<div v-if="mode == 'edit'" class="place-block">
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input :style="{ width: '25%' }" v-model:value="props.question!.code" type="text" placeholder="В-1" />
|
<n-input
|
||||||
<n-input :style="{ width: '75%' }" v-model:value="props.question!.text" type="text"
|
:style="{ width: '25%' }"
|
||||||
placeholder="Кто украл пастуха?" />
|
v-model:value="props.question!.code"
|
||||||
|
type="text"
|
||||||
|
placeholder="В-1"
|
||||||
|
/>
|
||||||
|
<n-input
|
||||||
|
:style="{ width: '75%' }"
|
||||||
|
v-model:value="props.question!.text"
|
||||||
|
type="text"
|
||||||
|
placeholder="Кто украл пастуха?"
|
||||||
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
|
|
||||||
<p class="settings-header">Правильный ответ</p>
|
<p class="settings-header">Правильный ответ</p>
|
||||||
<n-input v-model:value="props.question!.answer" type="textarea" :autosize="{ minRows: 2 }"
|
<n-input
|
||||||
placeholder="Правильный ответ (виден только автору и организаторам)" />
|
v-model:value="props.question!.answer"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 2 }"
|
||||||
|
placeholder="Правильный ответ (виден только автору и организаторам)"
|
||||||
|
/>
|
||||||
<n-text class="hint" depth="3">
|
<n-text class="hint" depth="3">
|
||||||
Смена кода вопроса не переносит ответы команд, отправленные под старым кодом.
|
Смена кода вопроса не переносит ответы команд, отправленные под старым кодом.
|
||||||
</n-text>
|
</n-text>
|
||||||
@@ -98,24 +132,24 @@ const updateMode = (mode: string) => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.place-block-hover,
|
.place-block-hover,
|
||||||
.place-block {
|
.place-block {
|
||||||
|
background-color: #222;
|
||||||
|
border: 1px solid #222;
|
||||||
|
border-radius: 10px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #222;
|
|
||||||
background-color: #222;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.place-block-hover:hover {
|
.place-block-hover:hover {
|
||||||
|
border: 1px solid #63e2b7;
|
||||||
color: #63e2b7;
|
color: #63e2b7;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid #63e2b7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.place-image-plus {
|
.place-image-plus {
|
||||||
font-size: 30px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
font-size: 30px;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-editor-block {
|
.show-editor-block {
|
||||||
@@ -123,9 +157,9 @@ const updateMode = (mode: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hr {
|
.hr {
|
||||||
margin: 10px 0;
|
|
||||||
border: dashed 1px;
|
border: dashed 1px;
|
||||||
border-color: #eee;
|
border-color: #eee;
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-header {
|
.message-header {
|
||||||
|
|||||||
@@ -16,64 +16,70 @@ const route = useRoute()
|
|||||||
const scenarioId = route.params.id
|
const scenarioId = route.params.id
|
||||||
|
|
||||||
const scenario = ref<Scenario>({
|
const scenario = ref<Scenario>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
story: undefined,
|
story: undefined,
|
||||||
author: undefined,
|
author: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
updatedAt: undefined,
|
updatedAt: undefined,
|
||||||
createdAt: undefined,
|
createdAt: undefined,
|
||||||
publishedAt: undefined,
|
publishedAt: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getScenario(id: number) {
|
async function getScenario(id: number) {
|
||||||
const res = await client.GetScenario({ id: id })
|
const res = await client.GetScenario({ id: id })
|
||||||
if (res.error != '') {
|
if (res.error != '') {
|
||||||
message.error(res.error!)
|
message.error(res.error!)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
scenario.value = res.scenario!
|
scenario.value = res.scenario!
|
||||||
}
|
}
|
||||||
getScenario(Number(scenarioId))
|
getScenario(Number(scenarioId))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="center-block-custom">
|
<div class="center-block-custom">
|
||||||
<div class="width700">
|
<div class="width700">
|
||||||
<div class="scenario-title">
|
<div class="scenario-title">
|
||||||
{{ scenario.name }}
|
{{ scenario.name }}
|
||||||
</div>
|
</div>
|
||||||
<img class="scenario-image" :src="scenario.image">
|
<img class="scenario-image" :src="scenario.image" />
|
||||||
<div class="scenario-description">
|
<div class="scenario-description">
|
||||||
{{ scenario.description }}
|
{{ scenario.description }}
|
||||||
</div>
|
</div>
|
||||||
<div class="scenario-author">
|
<div class="scenario-author">
|
||||||
<n-tag :bordered="false" type="success" class="status-block">Автор: {{ scenario.author?.username
|
<n-tag :bordered="false" type="success" class="status-block"
|
||||||
}}</n-tag>
|
>Автор: {{ scenario.author?.username }}</n-tag
|
||||||
</div>
|
>
|
||||||
<IntroBlock
|
</div>
|
||||||
v-if="scenario.story?.introduction && (scenario.story.introduction.text || scenario.story.introduction.audio)"
|
<IntroBlock
|
||||||
:introduction="scenario.story.introduction" mode="show" />
|
v-if="
|
||||||
</div>
|
scenario.story?.introduction &&
|
||||||
|
(scenario.story.introduction.text || scenario.story.introduction.audio)
|
||||||
|
"
|
||||||
|
:introduction="scenario.story.introduction"
|
||||||
|
mode="show"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<HeaderMenu active="catalog" />
|
<HeaderMenu active="catalog" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.scenario-title {
|
.scenario-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-image {
|
.scenario-image {
|
||||||
width: 100%;
|
margin-bottom: 10px;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
margin-bottom: 10px;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-description {
|
.scenario-description {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,18 @@
|
|||||||
import { Settings } from '@vicons/carbon'
|
import { Settings } from '@vicons/carbon'
|
||||||
import { Icon } from '@vicons/utils'
|
import { Icon } from '@vicons/utils'
|
||||||
import { NButton, type UploadFileInfo, useMessage } from 'naive-ui'
|
import { NButton, type UploadFileInfo, useMessage } from 'naive-ui'
|
||||||
import { NAlert, NCard, NFlex, NInput, NModal, NSpace, NTag, NText, NUpload, NUploadDragger } from 'naive-ui'
|
import {
|
||||||
|
NAlert,
|
||||||
|
NCard,
|
||||||
|
NFlex,
|
||||||
|
NInput,
|
||||||
|
NModal,
|
||||||
|
NSpace,
|
||||||
|
NTag,
|
||||||
|
NText,
|
||||||
|
NUpload,
|
||||||
|
NUploadDragger,
|
||||||
|
} from 'naive-ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
@@ -15,7 +26,12 @@ import PlaceBlock from '@/components/PlaceBlock.vue'
|
|||||||
import QuestionBlock from '@/components/QuestionBlock.vue'
|
import QuestionBlock from '@/components/QuestionBlock.vue'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import type { EditableApplication, EditableIntroduction, EditablePlace, EditableQuestion } from '@/types/editor'
|
import type {
|
||||||
|
EditableApplication,
|
||||||
|
EditableIntroduction,
|
||||||
|
EditablePlace,
|
||||||
|
EditableQuestion,
|
||||||
|
} from '@/types/editor'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const client = getAuthClient()
|
const client = getAuthClient()
|
||||||
@@ -104,7 +120,7 @@ async function addGame(name: string, id: number) {
|
|||||||
name: ('Тестовая игра: ' + name).trim(),
|
name: ('Тестовая игра: ' + name).trim(),
|
||||||
description: 'Тестовая игра',
|
description: 'Тестовая игра',
|
||||||
startAt: now,
|
startAt: now,
|
||||||
scenarioId: id
|
scenarioId: id,
|
||||||
})
|
})
|
||||||
if (resAddGame.error != '') {
|
if (resAddGame.error != '') {
|
||||||
message.error(resAddGame.error!)
|
message.error(resAddGame.error!)
|
||||||
@@ -118,7 +134,7 @@ async function addGame(name: string, id: number) {
|
|||||||
message.error(resAddTeam.error!)
|
message.error(resAddTeam.error!)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
router.push("/team-story/" + resAddTeam.id! + '?password=' + resAddTeam.password!)
|
router.push('/team-story/' + resAddTeam.id! + '?password=' + resAddTeam.password!)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function draftScenario(id: number) {
|
async function draftScenario(id: number) {
|
||||||
@@ -220,12 +236,12 @@ function uint8ToBase64(uint8Array: Uint8Array) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fromCharCodeSafe(arr: Uint8Array) {
|
function fromCharCodeSafe(arr: Uint8Array) {
|
||||||
let result = '';
|
let result = ''
|
||||||
for (let i = 0; i < arr.length; i += 50000) {
|
for (let i = 0; i < arr.length; i += 50000) {
|
||||||
const chunk = arr.slice(i, i + 50000);
|
const chunk = arr.slice(i, i + 50000)
|
||||||
result += String.fromCharCode(...chunk);
|
result += String.fromCharCode(...chunk)
|
||||||
}
|
}
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = ref('')
|
const code = ref('')
|
||||||
@@ -233,7 +249,6 @@ const name = ref('')
|
|||||||
const text = ref('')
|
const text = ref('')
|
||||||
const mode = ref('mini-add')
|
const mode = ref('mini-add')
|
||||||
|
|
||||||
|
|
||||||
async function addPlace() {
|
async function addPlace() {
|
||||||
const res = await client.AddScenarioPlace({
|
const res = await client.AddScenarioPlace({
|
||||||
id: scenario.value.id,
|
id: scenario.value.id,
|
||||||
@@ -283,7 +298,7 @@ async function addNotFoundDoors(scenario: Scenario, place: EditablePlace) {
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
applications: undefined,
|
applications: undefined,
|
||||||
doors: undefined,
|
doors: undefined,
|
||||||
keys: undefined
|
keys: undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (res.error != '') {
|
if (res.error != '') {
|
||||||
@@ -306,7 +321,10 @@ async function updatePlace(place: EditablePlace) {
|
|||||||
if (file == null) {
|
if (file == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const uploaded = await uploadFile(file, 'scenarios_' + scenario.value.id + '_place_' + place.code + '_' + file.name)
|
const uploaded = await uploadFile(
|
||||||
|
file,
|
||||||
|
'scenarios_' + scenario.value.id + '_place_' + place.code + '_' + file.name,
|
||||||
|
)
|
||||||
if (uploaded.filename === '') {
|
if (uploaded.filename === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -448,7 +466,8 @@ async function updateIntro() {
|
|||||||
if (file == null) {
|
if (file == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
audio = (await uploadFile(file, 'scenarios_' + scenario.value.id + '_intro_' + file.name)).filename
|
audio = (await uploadFile(file, 'scenarios_' + scenario.value.id + '_intro_' + file.name))
|
||||||
|
.filename
|
||||||
if (audio === '') {
|
if (audio === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -487,11 +506,11 @@ function getRandomComplimentForNastya() {
|
|||||||
{ text: 'Настя, твоя энергия заряжает батарейки!', nameForm: 'Настя' },
|
{ text: 'Настя, твоя энергия заряжает батарейки!', nameForm: 'Настя' },
|
||||||
{ text: 'С Настей хочется проводить каждую минуту.', nameForm: 'Настей' },
|
{ text: 'С Настей хочется проводить каждую минуту.', nameForm: 'Настей' },
|
||||||
{ text: 'Настенька, ты — самое красивое, что случилось в этом мире.', nameForm: 'Настенька' },
|
{ text: 'Настенька, ты — самое красивое, что случилось в этом мире.', nameForm: 'Настенька' },
|
||||||
{ text: 'У Насти золотое сердце.', nameForm: 'Насти' }
|
{ text: 'У Насти золотое сердце.', nameForm: 'Насти' },
|
||||||
];
|
]
|
||||||
|
|
||||||
const randomIndex = Math.floor(Math.random() * compliments.length);
|
const randomIndex = Math.floor(Math.random() * compliments.length)
|
||||||
return compliments[randomIndex].text;
|
return compliments[randomIndex].text
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -499,16 +518,25 @@ function getRandomComplimentForNastya() {
|
|||||||
<div class="center-block-custom">
|
<div class="center-block-custom">
|
||||||
<div class="width700">
|
<div class="width700">
|
||||||
<div class="places-container">
|
<div class="places-container">
|
||||||
<n-alert v-if="scenario.status == 'public'" title="Сценарий опубликован" type="warning"
|
<n-alert
|
||||||
style="margin-bottom: 20px;">
|
v-if="scenario.status == 'public'"
|
||||||
|
title="Сценарий опубликован"
|
||||||
|
type="warning"
|
||||||
|
style="margin-bottom: 20px"
|
||||||
|
>
|
||||||
Не рекомендуется редактировать сценарий когда он опубликован.
|
Не рекомендуется редактировать сценарий когда он опубликован.
|
||||||
</n-alert>
|
</n-alert>
|
||||||
|
|
||||||
<n-alert v-if="authStore.user?.id == 2" :title="getRandomComplimentForNastya()" type="info"
|
<n-alert
|
||||||
style="margin-bottom: 20px;"></n-alert>
|
v-if="authStore.user?.id == 2"
|
||||||
|
:title="getRandomComplimentForNastya()"
|
||||||
|
type="info"
|
||||||
|
style="margin-bottom: 20px"
|
||||||
|
></n-alert>
|
||||||
|
|
||||||
<div class="settings-block">
|
<div class="settings-block">
|
||||||
Точек: {{ scenario.story?.places?.length }}, Вопросов: {{ scenario.story?.questions?.length }}
|
Точек: {{ scenario.story?.places?.length }}, Вопросов:
|
||||||
|
{{ scenario.story?.questions?.length }}
|
||||||
<n-button quaternary @click="showSettingsModal = true" class="settings-button">
|
<n-button quaternary @click="showSettingsModal = true" class="settings-button">
|
||||||
<Icon class="settings-icon">
|
<Icon class="settings-icon">
|
||||||
<Settings />
|
<Settings />
|
||||||
@@ -526,33 +554,51 @@ function getRandomComplimentForNastya() {
|
|||||||
|
|
||||||
<div class="questions-header">Вопросы дела</div>
|
<div class="questions-header">Вопросы дела</div>
|
||||||
|
|
||||||
<QuestionBlock v-model:mode="questionMode" v-model:newQuestionCode="newQuestionCode"
|
<QuestionBlock
|
||||||
v-model:newQuestionText="newQuestionText" v-model:newQuestionAnswer="newQuestionAnswer">
|
v-model:mode="questionMode"
|
||||||
|
v-model:newQuestionCode="newQuestionCode"
|
||||||
|
v-model:newQuestionText="newQuestionText"
|
||||||
|
v-model:newQuestionAnswer="newQuestionAnswer"
|
||||||
|
>
|
||||||
<n-flex justify="end">
|
<n-flex justify="end">
|
||||||
<n-button @click="cancelAddQuestion()">Отмена</n-button>
|
<n-button @click="cancelAddQuestion()">Отмена</n-button>
|
||||||
<n-button @click="addQuestion()" :disabled="!newQuestionCode">Добавить вопрос</n-button>
|
<n-button @click="addQuestion()" :disabled="!newQuestionCode">Добавить вопрос</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</QuestionBlock>
|
</QuestionBlock>
|
||||||
|
|
||||||
<QuestionBlock :question="question" v-model:mode="question.mode" v-for="question in editorQuestions"
|
<QuestionBlock
|
||||||
v-bind:key="question.oldCode ?? question.code ?? ''">
|
:question="question"
|
||||||
|
v-model:mode="question.mode"
|
||||||
|
v-for="question in editorQuestions"
|
||||||
|
v-bind:key="question.oldCode ?? question.code ?? ''"
|
||||||
|
>
|
||||||
<n-flex justify="end">
|
<n-flex justify="end">
|
||||||
<n-button @click="deleteQuestion(question)" type="error" ghost>Удалить</n-button>
|
<n-button @click="deleteQuestion(question)" type="error" ghost>Удалить</n-button>
|
||||||
<n-button @click="cancelUpdateQuestion(question)">Отмена</n-button>
|
<n-button @click="cancelUpdateQuestion(question)">Отмена</n-button>
|
||||||
<n-button @click="updateQuestion(question)" :disabled="question.code == ''">Сохранить</n-button>
|
<n-button @click="updateQuestion(question)" :disabled="question.code == ''"
|
||||||
|
>Сохранить</n-button
|
||||||
|
>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</QuestionBlock>
|
</QuestionBlock>
|
||||||
|
|
||||||
<PlaceBlock v-model:mode="mode" v-model:newPlaceCode="code" v-model:newPlaceName="name"
|
<PlaceBlock
|
||||||
v-model:newPlaceText="text">
|
v-model:mode="mode"
|
||||||
|
v-model:newPlaceCode="code"
|
||||||
|
v-model:newPlaceName="name"
|
||||||
|
v-model:newPlaceText="text"
|
||||||
|
>
|
||||||
<n-flex justify="end">
|
<n-flex justify="end">
|
||||||
<n-button @click="cancelAddPlace()">Отмена</n-button>
|
<n-button @click="cancelAddPlace()">Отмена</n-button>
|
||||||
<n-button @click="addPlace()" :disabled="!code">Добавить точку</n-button>
|
<n-button @click="addPlace()" :disabled="!code">Добавить точку</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</PlaceBlock>
|
</PlaceBlock>
|
||||||
|
|
||||||
<PlaceBlock :place="place" v-model:mode="place.mode" v-for="place in editorPlaces"
|
<PlaceBlock
|
||||||
v-bind:key="place.oldCode ?? place.code ?? ''">
|
:place="place"
|
||||||
|
v-model:mode="place.mode"
|
||||||
|
v-for="place in editorPlaces"
|
||||||
|
v-bind:key="place.oldCode ?? place.code ?? ''"
|
||||||
|
>
|
||||||
<n-flex justify="end">
|
<n-flex justify="end">
|
||||||
<n-button @click="deletePlace(place)" type="error" ghost>Удалить</n-button>
|
<n-button @click="deletePlace(place)" type="error" ghost>Удалить</n-button>
|
||||||
<n-button @click="cancelUpdatePlace(place)">Отмена</n-button>
|
<n-button @click="cancelUpdatePlace(place)">Отмена</n-button>
|
||||||
@@ -565,20 +611,39 @@ function getRandomComplimentForNastya() {
|
|||||||
|
|
||||||
<!-- Окно настроек -->
|
<!-- Окно настроек -->
|
||||||
<n-modal v-model:show="showSettingsModal">
|
<n-modal v-model:show="showSettingsModal">
|
||||||
<n-card style="width: 600px" title="Настройки сценария" :bordered="false" size="huge" role="dialog"
|
<n-card
|
||||||
aria-modal="true">
|
style="width: 600px"
|
||||||
|
title="Настройки сценария"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-button @click="showSettingsModal = false"> x </n-button>
|
<n-button @click="showSettingsModal = false"> x </n-button>
|
||||||
</template>
|
</template>
|
||||||
<n-space vertical>
|
<n-space vertical>
|
||||||
<p>Название</p>
|
<p>Название</p>
|
||||||
<n-input v-model:value="scenario.name" type="text" placeholder='Дело №0 "Следствие ведут овечки"' />
|
<n-input
|
||||||
|
v-model:value="scenario.name"
|
||||||
|
type="text"
|
||||||
|
placeholder='Дело №0 "Следствие ведут овечки"'
|
||||||
|
/>
|
||||||
|
|
||||||
<p class="settings-header">Описание</p>
|
<p class="settings-header">Описание</p>
|
||||||
<n-input v-model:value="scenario.description" type="textarea" placeholder="Убийство пастуха..." />
|
<n-input
|
||||||
|
v-model:value="scenario.description"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="Убийство пастуха..."
|
||||||
|
/>
|
||||||
|
|
||||||
<p class="settings-header">Картинка</p>
|
<p class="settings-header">Картинка</p>
|
||||||
<n-input v-model:value="scenario.image" type="text" placeholder="default_scenario.png" disabled />
|
<n-input
|
||||||
|
v-model:value="scenario.image"
|
||||||
|
type="text"
|
||||||
|
placeholder="default_scenario.png"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
<n-upload directory-dnd :max="1" v-model:file-list="fileList">
|
<n-upload directory-dnd :max="1" v-model:file-list="fileList">
|
||||||
<n-upload-dragger>
|
<n-upload-dragger>
|
||||||
<n-text style="font-size: 16px">
|
<n-text style="font-size: 16px">
|
||||||
@@ -592,9 +657,15 @@ function getRandomComplimentForNastya() {
|
|||||||
<hr class="settings-hr" />
|
<hr class="settings-hr" />
|
||||||
<p class="settings-header">Архив сценария</p>
|
<p class="settings-header">Архив сценария</p>
|
||||||
<p class="settings-hint">
|
<p class="settings-hint">
|
||||||
Скачайте сценарий ZIP-архивом (scenario.json + изображения) и импортируйте его в другом аккаунте.
|
Скачайте сценарий ZIP-архивом (scenario.json + изображения) и импортируйте его в другом
|
||||||
|
аккаунте.
|
||||||
</p>
|
</p>
|
||||||
<n-button :loading="isDownloadingArchive" :disabled="!scenario.id" ghost @click="downloadArchive()">
|
<n-button
|
||||||
|
:loading="isDownloadingArchive"
|
||||||
|
:disabled="!scenario.id"
|
||||||
|
ghost
|
||||||
|
@click="downloadArchive()"
|
||||||
|
>
|
||||||
Скачать архив (.zip)
|
Скачать архив (.zip)
|
||||||
</n-button>
|
</n-button>
|
||||||
|
|
||||||
@@ -607,23 +678,45 @@ function getRandomComplimentForNastya() {
|
|||||||
<hr class="settings-hr" />
|
<hr class="settings-hr" />
|
||||||
<p>Публикация</p>
|
<p>Публикация</p>
|
||||||
<p>
|
<p>
|
||||||
<n-tag v-if="scenario.status == 'public'" :bordered="false" type="success" class="status-block">
|
<n-tag
|
||||||
|
v-if="scenario.status == 'public'"
|
||||||
|
:bordered="false"
|
||||||
|
type="success"
|
||||||
|
class="status-block"
|
||||||
|
>
|
||||||
Опубликовано
|
Опубликовано
|
||||||
</n-tag>
|
</n-tag>
|
||||||
<n-tag v-if="scenario.status == 'draft'" :bordered="false" type="warning" class="status-block">
|
<n-tag
|
||||||
|
v-if="scenario.status == 'draft'"
|
||||||
|
:bordered="false"
|
||||||
|
type="warning"
|
||||||
|
class="status-block"
|
||||||
|
>
|
||||||
В разработке
|
В разработке
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Введите название сценария <n-text class="name-text" strong>{{ scenario.name }}</n-text>
|
Введите название сценария <n-text class="name-text" strong>{{ scenario.name }}</n-text>
|
||||||
</p>
|
</p>
|
||||||
<n-input v-model:value="statusScenarioName" type="text" placeholder='Дело №0 "Следствие ведут овечки"' />
|
<n-input
|
||||||
<n-button v-if="scenario.status != 'public'" @click="publicScenario(scenario.id!)" ghost
|
v-model:value="statusScenarioName"
|
||||||
:disabled="statusScenarioName != scenario.name">
|
type="text"
|
||||||
|
placeholder='Дело №0 "Следствие ведут овечки"'
|
||||||
|
/>
|
||||||
|
<n-button
|
||||||
|
v-if="scenario.status != 'public'"
|
||||||
|
@click="publicScenario(scenario.id!)"
|
||||||
|
ghost
|
||||||
|
:disabled="statusScenarioName != scenario.name"
|
||||||
|
>
|
||||||
Опубликовать сценарий
|
Опубликовать сценарий
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button v-if="scenario.status != 'draft'" @click="draftScenario(scenario.id!)" ghost
|
<n-button
|
||||||
:disabled="statusScenarioName != scenario.name">
|
v-if="scenario.status != 'draft'"
|
||||||
|
@click="draftScenario(scenario.id!)"
|
||||||
|
ghost
|
||||||
|
:disabled="statusScenarioName != scenario.name"
|
||||||
|
>
|
||||||
Снять сценарий с публикации
|
Снять сценарий с публикации
|
||||||
</n-button>
|
</n-button>
|
||||||
|
|
||||||
@@ -633,9 +726,17 @@ function getRandomComplimentForNastya() {
|
|||||||
Введите название сценария
|
Введите название сценария
|
||||||
<n-text class="name-text" strong>{{ scenario.name }}</n-text> чтобы удалить его
|
<n-text class="name-text" strong>{{ scenario.name }}</n-text> чтобы удалить его
|
||||||
</p>
|
</p>
|
||||||
<n-input v-model:value="deletedScenarioName" type="text" placeholder='Дело №0 "Следствие ведут овечки"' />
|
<n-input
|
||||||
<n-button @click="deleteScenario(scenario.id!)" type="error" ghost
|
v-model:value="deletedScenarioName"
|
||||||
:disabled="deletedScenarioName != scenario.name">
|
type="text"
|
||||||
|
placeholder='Дело №0 "Следствие ведут овечки"'
|
||||||
|
/>
|
||||||
|
<n-button
|
||||||
|
@click="deleteScenario(scenario.id!)"
|
||||||
|
type="error"
|
||||||
|
ghost
|
||||||
|
:disabled="deletedScenarioName != scenario.name"
|
||||||
|
>
|
||||||
Удалить сценарий
|
Удалить сценарий
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
@@ -662,9 +763,9 @@ function getRandomComplimentForNastya() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.name-text {
|
.name-text {
|
||||||
padding: 3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: rgb(255 255 255 / 10%);
|
background-color: rgb(255 255 255 / 10%);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-header {
|
.settings-header {
|
||||||
@@ -681,9 +782,9 @@ function getRandomComplimentForNastya() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.questions-header {
|
.questions-header {
|
||||||
margin: 30px 0 5px;
|
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
margin: 30px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.places-container {
|
.places-container {
|
||||||
|
|||||||
@@ -2,7 +2,18 @@
|
|||||||
import { Archive } from '@vicons/carbon'
|
import { Archive } from '@vicons/carbon'
|
||||||
import { Icon } from '@vicons/utils'
|
import { Icon } from '@vicons/utils'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui'
|
||||||
import { NButton, NCard, NFlex, NInput, NModal, NTag, NText, NUpload, NUploadDragger, type UploadFileInfo } from 'naive-ui'
|
import {
|
||||||
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NFlex,
|
||||||
|
NInput,
|
||||||
|
NModal,
|
||||||
|
NTag,
|
||||||
|
NText,
|
||||||
|
NUpload,
|
||||||
|
NUploadDragger,
|
||||||
|
type UploadFileInfo,
|
||||||
|
} from 'naive-ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
@@ -97,17 +108,34 @@ getScenarios()
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scenario-block" v-for="scenario in scenarios" @click="toScenarioEditor(scenario.id!)"
|
<div
|
||||||
v-bind:key="scenario.id">
|
class="scenario-block"
|
||||||
<div class="scenario-image-block scenario-image" :style="{ backgroundImage: `url(${scenario.image})` }"></div>
|
v-for="scenario in scenarios"
|
||||||
|
@click="toScenarioEditor(scenario.id!)"
|
||||||
|
v-bind:key="scenario.id"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="scenario-image-block scenario-image"
|
||||||
|
:style="{ backgroundImage: `url(${scenario.image})` }"
|
||||||
|
></div>
|
||||||
|
|
||||||
<div class="scenario-content-block">
|
<div class="scenario-content-block">
|
||||||
<p class="scenario-title">{{ scenario.name }}</p>
|
<p class="scenario-title">{{ scenario.name }}</p>
|
||||||
<p class="scenario-label">
|
<p class="scenario-label">
|
||||||
<n-tag v-if="scenario.status == 'public'" :bordered="false" type="success"
|
<n-tag
|
||||||
class="status-block">Опубликовано</n-tag>
|
v-if="scenario.status == 'public'"
|
||||||
<n-tag v-if="scenario.status == 'draft'" :bordered="false" type="warning" class="status-block">В
|
:bordered="false"
|
||||||
разработке</n-tag>
|
type="success"
|
||||||
|
class="status-block"
|
||||||
|
>Опубликовано</n-tag
|
||||||
|
>
|
||||||
|
<n-tag
|
||||||
|
v-if="scenario.status == 'draft'"
|
||||||
|
:bordered="false"
|
||||||
|
type="warning"
|
||||||
|
class="status-block"
|
||||||
|
>В разработке</n-tag
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,12 +144,22 @@ getScenarios()
|
|||||||
|
|
||||||
<!-- Окно создания сценария -->
|
<!-- Окно создания сценария -->
|
||||||
<n-modal v-model:show="showAddScenarioModal">
|
<n-modal v-model:show="showAddScenarioModal">
|
||||||
<n-card style="width: 600px" title="Создание сценария" :bordered="false" size="huge" role="dialog"
|
<n-card
|
||||||
aria-modal="true">
|
style="width: 600px"
|
||||||
|
title="Создание сценария"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-button @click="showAddScenarioModal = false"> x </n-button>
|
<n-button @click="showAddScenarioModal = false"> x </n-button>
|
||||||
</template>
|
</template>
|
||||||
<n-input v-model:value="newScenarioName" type="text" placeholder='Дело №0 "Следствие ведут овечки"' />
|
<n-input
|
||||||
|
v-model:value="newScenarioName"
|
||||||
|
type="text"
|
||||||
|
placeholder='Дело №0 "Следствие ведут овечки"'
|
||||||
|
/>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-flex justify="end">
|
<n-flex justify="end">
|
||||||
<n-button @click="addScenario(newScenarioName)" :disabled="newScenarioName.length == 0">
|
<n-button @click="addScenario(newScenarioName)" :disabled="newScenarioName.length == 0">
|
||||||
@@ -134,28 +172,39 @@ getScenarios()
|
|||||||
|
|
||||||
<!-- Окно импорта сценария из архива -->
|
<!-- Окно импорта сценария из архива -->
|
||||||
<n-modal v-model:show="showImportModal">
|
<n-modal v-model:show="showImportModal">
|
||||||
<n-card style="width: 600px" title="Импорт сценария из архива" :bordered="false" size="huge" role="dialog"
|
<n-card
|
||||||
aria-modal="true">
|
style="width: 600px"
|
||||||
|
title="Импорт сценария из архива"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-button @click="resetImport()"> x </n-button>
|
<n-button @click="resetImport()"> x </n-button>
|
||||||
</template>
|
</template>
|
||||||
<p class="import-hint">
|
<p class="import-hint">
|
||||||
Выберите ZIP-архив сценария (файл scenario.json + папка images). Архив можно получить
|
Выберите ZIP-архив сценария (файл scenario.json + папка images). Архив можно получить через
|
||||||
через «Скачать архив» в настройках сценария.
|
«Скачать архив» в настройках сценария.
|
||||||
</p>
|
</p>
|
||||||
<n-upload :max="1" v-model:file-list="importFileList"
|
<n-upload
|
||||||
accept=".zip,application/zip,application/x-zip-compressed,application/octet-stream">
|
:max="1"
|
||||||
|
v-model:file-list="importFileList"
|
||||||
|
accept=".zip,application/zip,application/x-zip-compressed,application/octet-stream"
|
||||||
|
>
|
||||||
<n-upload-dragger>
|
<n-upload-dragger>
|
||||||
<n-text style="font-size: 16px">
|
<n-text style="font-size: 16px"> Щелкните или перетащите ZIP-архив в эту область </n-text>
|
||||||
Щелкните или перетащите ZIP-архив в эту область
|
|
||||||
</n-text>
|
|
||||||
</n-upload-dragger>
|
</n-upload-dragger>
|
||||||
</n-upload>
|
</n-upload>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-flex justify="end">
|
<n-flex justify="end">
|
||||||
<n-button @click="resetImport()">Отмена</n-button>
|
<n-button @click="resetImport()">Отмена</n-button>
|
||||||
<n-button type="primary" :loading="isImporting" :disabled="importFileList.length == 0"
|
<n-button
|
||||||
@click="importScenario()">
|
type="primary"
|
||||||
|
:loading="isImporting"
|
||||||
|
:disabled="importFileList.length == 0"
|
||||||
|
@click="importScenario()"
|
||||||
|
>
|
||||||
Импортировать
|
Импортировать
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
@@ -168,16 +217,15 @@ getScenarios()
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.scenario-block {
|
.scenario-block {
|
||||||
position: relative;
|
|
||||||
margin: 10px;
|
|
||||||
width: 160px;
|
|
||||||
height: 300px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 12px;
|
|
||||||
|
|
||||||
/* background-color: #553333; */
|
/* background-color: #553333; */
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
border: 2px solid #222;
|
border: 2px solid #222;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 300px;
|
||||||
|
margin: 10px;
|
||||||
|
position: relative;
|
||||||
|
width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-content-block {
|
.scenario-content-block {
|
||||||
@@ -185,22 +233,22 @@ getScenarios()
|
|||||||
}
|
}
|
||||||
|
|
||||||
.scenario-block:hover {
|
.scenario-block:hover {
|
||||||
color: #63e2b7;
|
|
||||||
border: 2px solid #63e2b7;
|
border: 2px solid #63e2b7;
|
||||||
|
color: #63e2b7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-image-block {
|
.scenario-image-block {
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
height: 160px;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
height: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-image-plus {
|
.scenario-image-plus {
|
||||||
font-size: 120px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
|
display: flex;
|
||||||
|
font-size: 120px;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.import-icon svg {
|
.import-icon svg {
|
||||||
@@ -214,8 +262,8 @@ getScenarios()
|
|||||||
}
|
}
|
||||||
|
|
||||||
.scenario-image {
|
.scenario-image {
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-title {
|
.scenario-title {
|
||||||
@@ -223,15 +271,15 @@ getScenarios()
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-block {
|
.status-block {
|
||||||
position: absolute;
|
|
||||||
bottom: 15px;
|
bottom: 15px;
|
||||||
|
position: absolute;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width >=1024px) {
|
@media (width >=1024px) {
|
||||||
.scenario-block {
|
.scenario-block {
|
||||||
width: 250px;
|
|
||||||
height: 400px;
|
height: 400px;
|
||||||
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenario-image-block {
|
.scenario-image-block {
|
||||||
|
|||||||
+256
-222
@@ -1,19 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NButton, NConfigProvider, NFlex, NInput, NQrCode } from 'naive-ui'
|
import { NButton, NConfigProvider, NFlex, NInput, NQrCode } from 'naive-ui'
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui'
|
||||||
import { computed, nextTick, ref } from 'vue';
|
import { computed, nextTick, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import { getAuthClient } from '@/api/auth_client';
|
import { getAuthClient } from '@/api/auth_client'
|
||||||
import type { Game, Introduction, Place, Question } from '@/api/generated/crabs/evening_detective_server';
|
import type {
|
||||||
|
Game,
|
||||||
|
Introduction,
|
||||||
|
Place,
|
||||||
|
Question,
|
||||||
|
} from '@/api/generated/crabs/evening_detective_server'
|
||||||
import GameInputForm from '@/components/GameInputForm.vue'
|
import GameInputForm from '@/components/GameInputForm.vue'
|
||||||
import HeaderMenu from '@/components/HeaderMenu.vue'
|
import HeaderMenu from '@/components/HeaderMenu.vue'
|
||||||
import IntroBlock from '@/components/IntroBlock.vue'
|
import IntroBlock from '@/components/IntroBlock.vue'
|
||||||
import PlaceBlock from '@/components/PlaceBlock.vue'
|
import PlaceBlock from '@/components/PlaceBlock.vue'
|
||||||
import { useSimplePolling } from '@/composables/useSimplePolling';
|
import { useSimplePolling } from '@/composables/useSimplePolling'
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
import MessagePaper from './MessagePaper.vue';
|
import MessagePaper from './MessagePaper.vue'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const client = getAuthClient()
|
const client = getAuthClient()
|
||||||
@@ -24,22 +29,22 @@ const password = route.query.password
|
|||||||
|
|
||||||
const newPlacesCount = ref(0)
|
const newPlacesCount = ref(0)
|
||||||
|
|
||||||
const scrollContainer = ref<HTMLDivElement | null>();
|
const scrollContainer = ref<HTMLDivElement | null>()
|
||||||
|
|
||||||
const story = ref<AdvancedStory>({
|
const story = ref<AdvancedStory>({
|
||||||
introduction: undefined,
|
introduction: undefined,
|
||||||
places: [],
|
places: [],
|
||||||
questions: []
|
questions: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const game = ref<Game | undefined>({
|
const game = ref<Game | undefined>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
description: undefined,
|
description: undefined,
|
||||||
startAt: undefined,
|
startAt: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
scenario: undefined,
|
scenario: undefined,
|
||||||
teams: undefined
|
teams: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const code = ref('')
|
const code = ref('')
|
||||||
@@ -47,134 +52,135 @@ const code = ref('')
|
|||||||
const url = window.location.href
|
const url = window.location.href
|
||||||
|
|
||||||
const isAtBottom = () => {
|
const isAtBottom = () => {
|
||||||
const container = scrollContainer.value;
|
const container = scrollContainer.value
|
||||||
if (!container) return false;
|
if (!container) return false
|
||||||
|
|
||||||
return container.scrollHeight - container.scrollTop - container.clientHeight < 1;
|
return container.scrollHeight - container.scrollTop - container.clientHeight < 1
|
||||||
};
|
}
|
||||||
|
|
||||||
async function getStory() {
|
async function getStory() {
|
||||||
const oldStory = story.value
|
const oldStory = story.value
|
||||||
|
|
||||||
const res = await client.GetTeamStory({
|
const res = await client.GetTeamStory({
|
||||||
id: Number(teamId),
|
id: Number(teamId),
|
||||||
password: password as string
|
password: password as string,
|
||||||
})
|
})
|
||||||
if (res.error != '') {
|
if (res.error != '') {
|
||||||
message.error(res.error!)
|
message.error(res.error!)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
game.value = res.game
|
game.value = res.game
|
||||||
|
|
||||||
const wasAtBottom = isAtBottom();
|
const wasAtBottom = isAtBottom()
|
||||||
|
|
||||||
if (wasAtBottom) {
|
if (wasAtBottom) {
|
||||||
newPlacesCount.value = 0
|
newPlacesCount.value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldCount = story.value.places.length
|
const oldCount = story.value.places.length
|
||||||
|
|
||||||
story.value = {
|
story.value = {
|
||||||
introduction: res.story!.introduction,
|
introduction: res.story!.introduction,
|
||||||
places: res.story!.places?.map((item, i) => {
|
places:
|
||||||
console.log(oldStory?.places[i]?.mode)
|
res.story!.places?.map((item, i) => {
|
||||||
return {
|
console.log(oldStory?.places[i]?.mode)
|
||||||
place: item,
|
return {
|
||||||
mode: oldStory?.places[i]?.mode || 'show'
|
place: item,
|
||||||
}
|
mode: oldStory?.places[i]?.mode || 'show',
|
||||||
}) || [],
|
}
|
||||||
questions: res.story!.questions || []
|
}) || [],
|
||||||
}
|
questions: res.story!.questions || [],
|
||||||
|
}
|
||||||
|
|
||||||
const newCount = story.value.places.length
|
const newCount = story.value.places.length
|
||||||
|
|
||||||
if (newCount > oldCount && oldCount != 0) {
|
if (newCount > oldCount && oldCount != 0) {
|
||||||
newPlacesCount.value += newCount - oldCount
|
newPlacesCount.value += newCount - oldCount
|
||||||
}
|
}
|
||||||
|
|
||||||
await nextTick();
|
await nextTick()
|
||||||
if (wasAtBottom && oldCount != 0) {
|
if (wasAtBottom && oldCount != 0) {
|
||||||
const container = scrollContainer.value;
|
const container = scrollContainer.value
|
||||||
if (!container) return false;
|
if (!container) return false
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getStory()
|
getStory()
|
||||||
getAnswers()
|
getAnswers()
|
||||||
|
|
||||||
async function addAction(newCode: string) {
|
async function addAction(newCode: string) {
|
||||||
const res = await client.AddTeamAction({
|
const res = await client.AddTeamAction({
|
||||||
id: Number(teamId),
|
id: Number(teamId),
|
||||||
password: password as string,
|
password: password as string,
|
||||||
code: newCode
|
code: newCode,
|
||||||
})
|
})
|
||||||
if (res.error != '') {
|
if (res.error != '') {
|
||||||
message.error(res.error!)
|
message.error(res.error!)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await getStory()
|
await getStory()
|
||||||
code.value = ''
|
code.value = ''
|
||||||
|
|
||||||
await nextTick();
|
await nextTick()
|
||||||
newPlacesCount.value = 0
|
newPlacesCount.value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdvancedStory = {
|
type AdvancedStory = {
|
||||||
introduction?: Introduction;
|
introduction?: Introduction
|
||||||
places: AdvancedPlace[];
|
places: AdvancedPlace[]
|
||||||
questions: Question[];
|
questions: Question[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdvancedPlace = {
|
type AdvancedPlace = {
|
||||||
place: Place;
|
place: Place
|
||||||
mode: string;
|
mode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ответы команды на вопросы дела: код вопроса -> текст ответа
|
// Ответы команды на вопросы дела: код вопроса -> текст ответа
|
||||||
const answers = ref<Record<string, string>>({})
|
const answers = ref<Record<string, string>>({})
|
||||||
|
|
||||||
async function getAnswers() {
|
async function getAnswers() {
|
||||||
const res = await client.GetTeamAnswers({
|
const res = await client.GetTeamAnswers({
|
||||||
id: Number(teamId),
|
id: Number(teamId),
|
||||||
password: password as string
|
password: password as string,
|
||||||
})
|
})
|
||||||
if (res.error != '') {
|
if (res.error != '') {
|
||||||
message.error(res.error!)
|
message.error(res.error!)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const next: Record<string, string> = {}
|
const next: Record<string, string> = {}
|
||||||
for (const answer of res.answers || []) {
|
for (const answer of res.answers || []) {
|
||||||
next[answer.questionCode || ''] = answer.answer || ''
|
next[answer.questionCode || ''] = answer.answer || ''
|
||||||
}
|
}
|
||||||
answers.value = next
|
answers.value = next
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitAnswers() {
|
async function submitAnswers() {
|
||||||
const res = await client.SubmitTeamAnswers({
|
const res = await client.SubmitTeamAnswers({
|
||||||
id: Number(teamId),
|
id: Number(teamId),
|
||||||
password: password as string,
|
password: password as string,
|
||||||
answers: (story.value.questions || []).map((question) => ({
|
answers: (story.value.questions || []).map((question) => ({
|
||||||
questionCode: question.code,
|
questionCode: question.code,
|
||||||
answer: answers.value[question.code || ''] || ''
|
answer: answers.value[question.code || ''] || '',
|
||||||
}))
|
})),
|
||||||
})
|
})
|
||||||
if (res.error != '') {
|
if (res.error != '') {
|
||||||
message.error(res.error!)
|
message.error(res.error!)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
message.success('Ответы сохранены')
|
message.success('Ответы сохранены')
|
||||||
await getAnswers()
|
await getAnswers()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отвечать можно только пока игра идёт (run/pause); после завершения
|
// Отвечать можно только пока игра идёт (run/pause); после завершения
|
||||||
// ответы фиксируются для оценки организатором.
|
// ответы фиксируются для оценки организатором.
|
||||||
const canAnswer = computed(() => {
|
const canAnswer = computed(() => {
|
||||||
return game.value?.status == 'run' || game.value?.status == 'pause'
|
return game.value?.status == 'run' || game.value?.status == 'pause'
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasQuestions = computed(() => {
|
const hasQuestions = computed(() => {
|
||||||
return (story.value.questions?.length || 0) > 0
|
return (story.value.questions?.length || 0) > 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// Поля ввода и кнопка на светлом бумажном листе: тёмная тема naive-ui
|
// Поля ввода и кнопка на светлом бумажном листе: тёмная тема naive-ui
|
||||||
@@ -183,188 +189,216 @@ const hasQuestions = computed(() => {
|
|||||||
// «чернильной» рамкой (бумага просвечивает), шрифт наследуется
|
// «чернильной» рамкой (бумага просвечивает), шрифт наследуется
|
||||||
// печатной машинки от родителя.
|
// печатной машинки от родителя.
|
||||||
const paperControlsOverrides = {
|
const paperControlsOverrides = {
|
||||||
Input: {
|
Input: {
|
||||||
border: '1px dashed black',
|
border: '1px dashed black',
|
||||||
borderDisabled: '1px dashed black',
|
borderDisabled: '1px dashed black',
|
||||||
borderFocus: '1px solid black',
|
borderFocus: '1px solid black',
|
||||||
borderHover: '1px dashed black',
|
borderHover: '1px dashed black',
|
||||||
boxShadowFocus: '0 0 0 2px rgb(99 226 183 / 30%)',
|
boxShadowFocus: '0 0 0 2px rgb(99 226 183 / 30%)',
|
||||||
caretColor: '#63e2b7',
|
caretColor: '#63e2b7',
|
||||||
color: 'transparent',
|
color: 'transparent',
|
||||||
colorDisabled: 'transparent',
|
colorDisabled: 'transparent',
|
||||||
colorFocus: 'transparent',
|
colorFocus: 'transparent',
|
||||||
fontSizeMedium: '18px',
|
fontSizeMedium: '18px',
|
||||||
placeholderColor: 'black',
|
placeholderColor: 'black',
|
||||||
placeholderColorDisabled: 'black',
|
placeholderColorDisabled: 'black',
|
||||||
textColor: 'black',
|
textColor: 'black',
|
||||||
textColorDisabled: 'black',
|
textColorDisabled: 'black',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteLast() {
|
async function deleteLast() {
|
||||||
const res = await client.DeleteLastTeamAction({
|
const res = await client.DeleteLastTeamAction({
|
||||||
id: Number(teamId),
|
id: Number(teamId),
|
||||||
})
|
})
|
||||||
if (res.error != '') {
|
if (res.error != '') {
|
||||||
message.error(res.error!)
|
message.error(res.error!)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await getStory()
|
await getStory()
|
||||||
code.value = ''
|
code.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
useSimplePolling(() => {
|
useSimplePolling(() => {
|
||||||
getStory()
|
getStory()
|
||||||
}, 2000);
|
}, 2000)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="center-block-custom bottom-margin smooth-scroll" ref="scrollContainer">
|
<div class="center-block-custom bottom-margin smooth-scroll" ref="scrollContainer">
|
||||||
<div class="width700">
|
<div class="width700">
|
||||||
<GameInputForm :addAction="addAction" :allPlacesCount="story.places.length" :newPlacesCount="newPlacesCount"
|
<GameInputForm
|
||||||
:gameStatus="game?.status || ''"></GameInputForm>
|
:addAction="addAction"
|
||||||
|
:allPlacesCount="story.places.length"
|
||||||
|
:newPlacesCount="newPlacesCount"
|
||||||
|
:gameStatus="game?.status || ''"
|
||||||
|
></GameInputForm>
|
||||||
|
|
||||||
<IntroBlock
|
<IntroBlock
|
||||||
v-if="game?.status && game.status != 'draft' && story.introduction && (story.introduction.text || story.introduction.audio)"
|
v-if="
|
||||||
:introduction="story.introduction" mode="show">
|
game?.status &&
|
||||||
</IntroBlock>
|
game.status != 'draft' &&
|
||||||
|
story.introduction &&
|
||||||
|
(story.introduction.text || story.introduction.audio)
|
||||||
|
"
|
||||||
|
:introduction="story.introduction"
|
||||||
|
mode="show"
|
||||||
|
>
|
||||||
|
</IntroBlock>
|
||||||
|
|
||||||
<MessagePaper v-if="hasQuestions && game?.status && game.status != 'draft'" class="questions-block">
|
<MessagePaper
|
||||||
<div class="font">Вопросы дела</div>
|
v-if="hasQuestions && game?.status && game.status != 'draft'"
|
||||||
<hr class="questions-hr" />
|
class="questions-block"
|
||||||
<p class="questions-hint font">
|
>
|
||||||
Ответьте на вопросы дела. Ответы можно менять, пока игра идёт.
|
<div class="font">Вопросы дела</div>
|
||||||
</p>
|
<hr class="questions-hr" />
|
||||||
<n-config-provider :theme-overrides="paperControlsOverrides">
|
<p class="questions-hint font">
|
||||||
<div v-for="question in story.questions" :key="question.code" class="question-item font">
|
Ответьте на вопросы дела. Ответы можно менять, пока игра идёт.
|
||||||
<div class="question-text">{{ question.code }}: {{ question.text }}</div>
|
</p>
|
||||||
<n-input v-model:value="answers[question.code || '']" type="textarea"
|
<n-config-provider :theme-overrides="paperControlsOverrides">
|
||||||
:autosize="{ minRows: 2, maxRows: 6 }" :disabled="!canAnswer" placeholder="Ваш ответ..." />
|
<div v-for="question in story.questions" :key="question.code" class="question-item font">
|
||||||
</div>
|
<div class="question-text">{{ question.code }}: {{ question.text }}</div>
|
||||||
<n-flex v-if="canAnswer" justify="end" class="questions-actions">
|
<n-input
|
||||||
<button type="button" class="paper-button" @click="submitAnswers()">Сохранить ответы</button>
|
v-model:value="answers[question.code || '']"
|
||||||
</n-flex>
|
type="textarea"
|
||||||
</n-config-provider>
|
:autosize="{ minRows: 2, maxRows: 6 }"
|
||||||
</MessagePaper>
|
:disabled="!canAnswer"
|
||||||
|
placeholder="Ваш ответ..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<n-flex v-if="canAnswer" justify="end" class="questions-actions">
|
||||||
|
<button type="button" class="paper-button" @click="submitAnswers()">
|
||||||
|
Сохранить ответы
|
||||||
|
</button>
|
||||||
|
</n-flex>
|
||||||
|
</n-config-provider>
|
||||||
|
</MessagePaper>
|
||||||
|
|
||||||
<PlaceBlock :place="advancedPlace.place" v-model:mode="advancedPlace.mode"
|
<PlaceBlock
|
||||||
v-for="advancedPlace in story.places" v-bind:key="advancedPlace.place.code" :addAction="addAction">
|
:place="advancedPlace.place"
|
||||||
</PlaceBlock>
|
v-model:mode="advancedPlace.mode"
|
||||||
|
v-for="advancedPlace in story.places"
|
||||||
|
v-bind:key="advancedPlace.place.code"
|
||||||
|
:addAction="addAction"
|
||||||
|
>
|
||||||
|
</PlaceBlock>
|
||||||
|
|
||||||
<MessagePaper class="qr-main-block" v-if="story.places.length == 0 && game?.status == 'draft'">
|
<MessagePaper
|
||||||
<n-space vertical>
|
class="qr-main-block"
|
||||||
<h2 class="qr-main-block-text">Это код этого дела</h2>
|
v-if="story.places.length == 0 && game?.status == 'draft'"
|
||||||
<p class="qr-main-block-text">Поделись с командой</p>
|
>
|
||||||
<div class="qr-block">
|
<n-space vertical>
|
||||||
<n-qr-code :value="url" :size="300" :padding="0" />
|
<h2 class="qr-main-block-text">Это код этого дела</h2>
|
||||||
</div>
|
<p class="qr-main-block-text">Поделись с командой</p>
|
||||||
<p class="qr-main-block-text">Игра скоро начнется</p>
|
<div class="qr-block">
|
||||||
</n-space>
|
<n-qr-code :value="url" :size="300" :padding="0" />
|
||||||
</MessagePaper>
|
</div>
|
||||||
|
<p class="qr-main-block-text">Игра скоро начнется</p>
|
||||||
|
</n-space>
|
||||||
|
</MessagePaper>
|
||||||
|
|
||||||
<div v-if="authStore.hasRole('author') && story.places.length > 0" class="edit-panel">
|
<div v-if="authStore.hasRole('author') && story.places.length > 0" class="edit-panel">
|
||||||
<n-flex justify="end">
|
<n-flex justify="end">
|
||||||
<n-button @click="deleteLast">Удалить последний ход</n-button>
|
<n-button @click="deleteLast">Удалить последний ход</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<HeaderMenu active="games" />
|
<HeaderMenu active="games" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.bottom-margin {
|
.bottom-margin {
|
||||||
height: calc(100dvh - 70px - 76px);
|
height: calc(100dvh - 70px - 76px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.smooth-scroll {
|
.smooth-scroll {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-panel {
|
.edit-panel {
|
||||||
margin: 30px 0;
|
background-color: #222;
|
||||||
padding: 20px;
|
border-radius: 10px;
|
||||||
background-color: #222;
|
margin: 30px 0;
|
||||||
border-radius: 10px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-main-block {
|
.qr-main-block {
|
||||||
display: flex;
|
align-items: center;
|
||||||
justify-content: center;
|
display: flex;
|
||||||
align-items: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.questions-hr {
|
.questions-hr {
|
||||||
margin: 10px 0;
|
border: dashed 1px;
|
||||||
border: dashed 1px;
|
border-color: black;
|
||||||
border-color: black;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.questions-hint {
|
.questions-hint {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.question-item {
|
.question-item {
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.question-text {
|
.question-text {
|
||||||
margin-bottom: 6px;
|
font-weight: 500;
|
||||||
font-weight: 500;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.questions-actions {
|
.questions-actions {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Кнопка действия на бумажном листе — как «двери» в точках:
|
/* Кнопка действия на бумажном листе — как «двери» в точках:
|
||||||
текстура ремня, бронзовый текст печатной машинки */
|
текстура ремня, бронзовый текст печатной машинки */
|
||||||
.paper-button {
|
.paper-button {
|
||||||
display: inline-block;
|
background-image: url('@/assets/images/belt.png');
|
||||||
margin-top: 10px;
|
background-size: cover;
|
||||||
padding: 5px 12px;
|
border: 0;
|
||||||
border: 0;
|
border-radius: 5px;
|
||||||
border-radius: 5px;
|
color: #bfa07d;
|
||||||
background-image: url('@/assets/images/belt.png');
|
cursor: pointer;
|
||||||
background-size: cover;
|
display: inline-block;
|
||||||
color: #bfa07d;
|
font-family: 'font_old_typer', serif;
|
||||||
font-family: 'font_old_typer', serif;
|
font-size: 16px;
|
||||||
font-size: 16px;
|
font-weight: 600;
|
||||||
font-weight: 600;
|
margin-top: 10px;
|
||||||
cursor: pointer;
|
padding: 5px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paper-button:hover {
|
.paper-button:hover {
|
||||||
color: #63e2b7;
|
color: #63e2b7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font {
|
.font {
|
||||||
font-family: 'font_old_typer', serif;
|
font-family: 'font_old_typer', serif;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-main-block-text {
|
.qr-main-block-text {
|
||||||
font-family: 'font_old_typer', serif;
|
font-family: 'font_old_typer', serif;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.qr-main-block-text {
|
p.qr-main-block-text {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-block {
|
.qr-block {
|
||||||
display: inline-block;
|
background-color: white;
|
||||||
background-color: white;
|
display: inline-block;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width >=1024px) {
|
@media (width >=1024px) {
|
||||||
.bottom-margin {
|
.bottom-margin {
|
||||||
height: calc(100dvh - 70px - 76px);
|
height: calc(100dvh - 70px - 76px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -44,29 +44,30 @@ async function deleteRole(userId: number, role: string) {
|
|||||||
<div class="center-block-custom">
|
<div class="center-block-custom">
|
||||||
<div class="width700">
|
<div class="width700">
|
||||||
<div v-for="user in users" v-bind:key="user.id" class="user-block">
|
<div v-for="user in users" v-bind:key="user.id" class="user-block">
|
||||||
<div class="user-field">
|
<div class="user-field">Позывной: {{ user.username }}</div>
|
||||||
Позывной: {{ user.username }}
|
<div class="user-field">Почта: {{ user.email }}</div>
|
||||||
</div>
|
|
||||||
<div class="user-field">
|
|
||||||
Почта: {{ user.email }}
|
|
||||||
</div>
|
|
||||||
<div class="user-field">
|
<div class="user-field">
|
||||||
<n-space>
|
<n-space>
|
||||||
Роли: <n-tag :bordered="false" v-for="role in user.roles" v-bind:key="role">
|
Роли:
|
||||||
|
<n-tag :bordered="false" v-for="role in user.roles" v-bind:key="role">
|
||||||
{{ role }}
|
{{ role }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
<n-flex justify="end" class="roles-buttons-block">
|
<n-flex justify="end" class="roles-buttons-block">
|
||||||
|
<n-button @click="addRole(user.id!, 'author')" v-if="!user.roles?.includes('author')"
|
||||||
<n-button @click="addRole(user.id!, 'author')" v-if="!user.roles?.includes('author')">Сделать
|
>Сделать автором</n-button
|
||||||
автором</n-button>
|
>
|
||||||
<n-button @click="deleteRole(user.id!, 'author')" v-else>Забрать роль автора</n-button>
|
<n-button @click="deleteRole(user.id!, 'author')" v-else>Забрать роль автора</n-button>
|
||||||
|
|
||||||
<n-button @click="addRole(user.id!, 'organizer')" v-if="!user.roles?.includes('organizer')">Сделать
|
<n-button
|
||||||
организатором</n-button>
|
@click="addRole(user.id!, 'organizer')"
|
||||||
<n-button @click="deleteRole(user.id!, 'organizer')" v-else>Забрать роль организатора</n-button>
|
v-if="!user.roles?.includes('organizer')"
|
||||||
|
>Сделать организатором</n-button
|
||||||
|
>
|
||||||
|
<n-button @click="deleteRole(user.id!, 'organizer')" v-else
|
||||||
|
>Забрать роль организатора</n-button
|
||||||
|
>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import { onMounted, onUnmounted,ref } from 'vue';
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
export function useSimplePolling(callback: () => void, interval = 5000) {
|
export function useSimplePolling(callback: () => void, interval = 5000) {
|
||||||
const isActive = ref(true);
|
const isActive = ref(true)
|
||||||
let timerId: number | null = null;
|
let timerId: number | null = null
|
||||||
|
|
||||||
const start = () => {
|
const start = () => {
|
||||||
isActive.value = true;
|
isActive.value = true
|
||||||
callback();
|
callback()
|
||||||
timerId = window.setInterval(callback, interval);
|
timerId = window.setInterval(callback, interval)
|
||||||
};
|
}
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
isActive.value = false;
|
isActive.value = false
|
||||||
if (timerId) {
|
if (timerId) {
|
||||||
clearInterval(timerId);
|
clearInterval(timerId)
|
||||||
timerId = null;
|
timerId = null
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
onMounted(start);
|
onMounted(start)
|
||||||
onUnmounted(stop);
|
onUnmounted(stop)
|
||||||
|
|
||||||
return { isActive, start, stop };
|
return { isActive, start, stop }
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -1,6 +1,11 @@
|
|||||||
import type { UploadFileInfo } from 'naive-ui'
|
import type { UploadFileInfo } from 'naive-ui'
|
||||||
|
|
||||||
import type { Application, Introduction, Place, Question } from '@/api/generated/crabs/evening_detective_server'
|
import type {
|
||||||
|
Application,
|
||||||
|
Introduction,
|
||||||
|
Place,
|
||||||
|
Question,
|
||||||
|
} from '@/api/generated/crabs/evening_detective_server'
|
||||||
|
|
||||||
// Служебные поля интерфейса, которых нет в сгенерированной схеме API
|
// Служебные поля интерфейса, которых нет в сгенерированной схеме API
|
||||||
// (типы из генератора — type-алиасы, их нельзя расширить через declare module).
|
// (типы из генератора — type-алиасы, их нельзя расширить через declare module).
|
||||||
|
|||||||
@@ -3,7 +3,5 @@ import CatalogPage from '@/components/CatalogPage.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CatalogPage />
|
<CatalogPage />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css"></style>
|
|
||||||
|
|||||||
@@ -3,7 +3,5 @@ import GamePage from '@/components/GamePage.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<GamePage />
|
<GamePage />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css"></style>
|
|
||||||
|
|||||||
@@ -5,5 +5,3 @@ import GamesPage from '@/components/GamesPage.vue'
|
|||||||
<template>
|
<template>
|
||||||
<GamesPage />
|
<GamesPage />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css"></style>
|
|
||||||
|
|||||||
+7
-11
@@ -1,7 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import HeaderMenu from '@/components/HeaderMenu.vue'
|
import HeaderMenu from '@/components/HeaderMenu.vue'
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -9,9 +7,7 @@ import HeaderMenu from '@/components/HeaderMenu.vue'
|
|||||||
<div>
|
<div>
|
||||||
<h1 class="font-text">Вечерний детектив</h1>
|
<h1 class="font-text">Вечерний детектив</h1>
|
||||||
<h3 class="font-text">Дело №0</h3>
|
<h3 class="font-text">Дело №0</h3>
|
||||||
<p class="content-text">
|
<p class="content-text">Поиграем?</p>
|
||||||
Поиграем?
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -20,26 +16,26 @@ import HeaderMenu from '@/components/HeaderMenu.vue'
|
|||||||
|
|
||||||
<style lang="css">
|
<style lang="css">
|
||||||
.font-text {
|
.font-text {
|
||||||
font-family: 'font_old_typer';
|
font-family: 'font_old_typer', serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-text {
|
.content-text {
|
||||||
position: relative;
|
|
||||||
top: 100px;
|
|
||||||
right: 0;
|
|
||||||
float: right;
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
right: 0;
|
||||||
|
top: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width >=1024px) {
|
@media (width >=1024px) {
|
||||||
.font-text {
|
.font-text {
|
||||||
position: relative;
|
|
||||||
left: -50px;
|
left: -50px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-text {
|
.content-text {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 100px;
|
|
||||||
right: -50px;
|
right: -50px;
|
||||||
|
top: 100px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,6 +6,5 @@ import LoginPage from '@/components/LoginPage.vue'
|
|||||||
<template>
|
<template>
|
||||||
<LoginPage />
|
<LoginPage />
|
||||||
|
|
||||||
|
|
||||||
<HeaderMenu />
|
<HeaderMenu />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ import ScenarioPage from '@/components/ScenarioPage.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ScenarioPage />
|
<ScenarioPage />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ import TeamStoryPage from '@/components/TeamStoryPage.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<TeamStoryPage />
|
<TeamStoryPage />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
|
plugins: ['stylelint-order'],
|
||||||
extends: [
|
extends: [
|
||||||
'stylelint-config-standard-scss', // Базовые правила для SCSS и CSS
|
'stylelint-config-standard-scss', // Базовые правила для SCSS и CSS
|
||||||
],
|
],
|
||||||
@@ -14,4 +15,4 @@ export default {
|
|||||||
'order/properties-alphabetical-order': true, // Сортировка свойств
|
'order/properties-alphabetical-order': true, // Сортировка свойств
|
||||||
// 'selector-class-pattern': '^[a-z][a-zA-Z0-9]+$', // Названия классов в camelCase
|
// 'selector-class-pattern': '^[a-z][a-zA-Z0-9]+$', // Названия классов в camelCase
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|||||||
+2
-5
@@ -6,13 +6,10 @@ import vueDevTools from 'vite-plugin-vue-devtools'
|
|||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [vue(), vueDevTools()],
|
||||||
vue(),
|
|
||||||
vueDevTools(),
|
|
||||||
],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user