add icons and scroll

This commit is contained in:
2026-08-01 20:56:40 +07:00
parent f8b4b6c6a8
commit d84539f1d0
2 changed files with 156 additions and 5 deletions
+111 -1
View File
@@ -4,12 +4,24 @@ import MetalPlate from './MetalPlate.vue';
import HeaderText from './HeaderText.vue'; import HeaderText from './HeaderText.vue';
import { ref, type PropType } from 'vue'; import { ref, type PropType } from 'vue';
import { Footsteps } from '@vicons/ionicons5'
import { ContactMailRound } from '@vicons/material'
import { Icon } from '@vicons/utils'
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: {
type: Number,
required: true
},
newPlacesCount: {
type: Number,
required: true
} }
}) })
@@ -18,6 +30,24 @@ async function addClickButton() {
code.value = '' code.value = ''
} }
function getTextForCounter(count: number): string[] {
count = count % 100
const number1 = Math.floor(count / 10) % 10
const number2 = count % 10
let text1 = ''
let text2 = ''
if (number1 > 0) {
text1 = String(number1)
}
if (number2 > 0 || number1 > 0) {
text2 = String(number2)
}
return [text1, text2]
}
</script> </script>
<template> <template>
@@ -25,8 +55,31 @@ async function addClickButton() {
<BeltBlock class="input-form"> <BeltBlock class="input-form">
<MetalPlate class="controller-metal controller-metal-left"></MetalPlate> <MetalPlate class="controller-metal controller-metal-left"></MetalPlate>
<MetalPlate class="controller-metal controller-metal-right"></MetalPlate> <MetalPlate class="controller-metal controller-metal-right"></MetalPlate>
<div class="center-block-700"> <div class="center-block-700">
<div class="controller"> <div class="controller">
<div class="counters-block">
<div class="counter-block">
<div class="places-text">
<Icon class="icon-for-counter">
<Footsteps />
</Icon>
</div>
<div v-for="n in getTextForCounter(allPlacesCount)" class="places-text places-text-number">{{ n }}</div>
</div>
<div class="counter-block">
<div class="places-text">
<Icon class="icon-for-counter">
<ContactMailRound />
</Icon>
</div>
<div v-for="n in getTextForCounter(newPlacesCount)" class="places-text places-text-number">{{ n }}</div>
</div>
</div>
<div class="game-input"> <div class="game-input">
<input id="run" class="game-input-run" v-model="code" type="text" placeholder="Место назначения"> <input id="run" class="game-input-run" v-model="code" type="text" placeholder="Место назначения">
</div> </div>
@@ -135,6 +188,63 @@ async function addClickButton() {
cursor: pointer; cursor: pointer;
} }
.new-places-label,
.all-places-label {
position: absolute;
height: 48px;
}
.new-places-label {
top: 14px;
left: 85px;
width: 40px;
}
.all-places-label {
top: 14px;
left: 15px;
width: 60px;
}
.places-text {
background-color: #d1cebd;
border: 1px solid #9e9982;
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;
box-shadow: 0px 0px 5px black;
}
.places-text-number {
width: 18px;
}
.counters-block {
position: absolute;
top: -40px;
left: 23px;
margin: 20px 10px 20px 5px;
}
.counter-block {
display: inline-block;
margin: 5px;
}
.icon-for-counter {
position: relative;
top: 3px;
margin: 0 4px;
}
@media (min-width: 1025px) { @media (min-width: 1025px) {
.center-block-700 { .center-block-700 {
width: 700px; width: 700px;
+45 -4
View File
@@ -4,7 +4,7 @@ import type { Place, Story } from '@/api/generated/crabs/evening_detective_serve
import HeaderMenu from '@/components/HeaderMenu.vue' import HeaderMenu from '@/components/HeaderMenu.vue'
import { NCard, NFlex, NInput, NModal, NSpace, NText, NUpload, NUploadDragger, NAlert, NTag, NButton } from 'naive-ui' import { NCard, NFlex, NInput, NModal, NSpace, NText, NUpload, NUploadDragger, NAlert, NTag, NButton } from 'naive-ui'
import { useMessage } from 'naive-ui'; import { useMessage } from 'naive-ui';
import { ref } from 'vue'; import { nextTick, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import PlaceBlock from '@/components/PlaceBlock.vue' import PlaceBlock from '@/components/PlaceBlock.vue'
import GameInputForm from '@/components/GameInputForm.vue' import GameInputForm from '@/components/GameInputForm.vue'
@@ -16,12 +16,23 @@ const route = useRoute()
const teamId = route.params.id const teamId = route.params.id
const password = route.query.password const password = route.query.password
const newPlacesCount = ref(0)
const scrollContainer = ref<HTMLDivElement | null>();
const story = ref<AdvancedStory>({ const story = ref<AdvancedStory>({
places: [] places: []
}) })
const code = ref('') const code = ref('')
const isAtBottom = () => {
const container = scrollContainer.value;
if (!container) return false;
return container.scrollHeight - container.scrollTop - container.clientHeight < 1;
};
async function getStory() { async function getStory() {
const res = await client.GetTeamStory({ const res = await client.GetTeamStory({
id: Number(teamId), id: Number(teamId),
@@ -31,6 +42,15 @@ async function getStory() {
message.error(res.error!) message.error(res.error!)
return return
} }
const wasAtBottom = isAtBottom();
if (wasAtBottom) {
newPlacesCount.value = 0
}
const oldCount = story.value.places.length
story.value = { story.value = {
places: res.story!.places?.map((item) => { places: res.story!.places?.map((item) => {
return { return {
@@ -39,6 +59,19 @@ async function getStory() {
} }
}) || [] }) || []
} }
const newCount = story.value.places.length
if (newCount > oldCount && oldCount != 0) {
newPlacesCount.value += newCount - oldCount
}
await nextTick();
if (wasAtBottom && oldCount != 0) {
const container = scrollContainer.value;
if (!container) return false;
container.scrollTop = container.scrollHeight;
}
} }
getStory() getStory()
@@ -54,6 +87,9 @@ async function addAction(newCode: string) {
} }
await getStory() await getStory()
code.value = '' code.value = ''
await nextTick();
newPlacesCount.value = 0
} }
type AdvancedStory = { type AdvancedStory = {
@@ -68,13 +104,14 @@ type AdvancedPlace = {
useSimplePolling(() => { useSimplePolling(() => {
getStory() getStory()
}, 2000); }, 2000);
</script> </script>
<template> <template>
<div class="center-block-custom bottom-margin"> <div class="center-block-custom bottom-margin smooth-scroll" ref="scrollContainer">
<div class="width700"> <div class="width700">
<GameInputForm :addAction="addAction"></GameInputForm> <GameInputForm :addAction="addAction" :allPlacesCount="story.places.length"
:newPlacesCount="newPlacesCount"></GameInputForm>
<PlaceBlock :place="advancedPlace.place" v-model:mode="advancedPlace.mode" <PlaceBlock :place="advancedPlace.place" v-model:mode="advancedPlace.mode"
v-for="advancedPlace in story.places" v-bind:key="advancedPlace.place.code" :addAction="addAction"> v-for="advancedPlace in story.places" v-bind:key="advancedPlace.place.code" :addAction="addAction">
@@ -90,6 +127,10 @@ useSimplePolling(() => {
height: calc(100dvh - 70px - 76px); height: calc(100dvh - 70px - 76px);
} }
.smooth-scroll {
scroll-behavior: smooth;
}
@media (width >=1024px) { @media (width >=1024px) {
.bottom-margin { .bottom-margin {
height: calc(100dvh - 70px - 76px); height: calc(100dvh - 70px - 76px);