change page|
Gitea Actions Requirements / Explore-Gitea-Actions (push) Waiting to run Details

This commit is contained in:
Kseninia Mikhaylova 2024-04-27 15:46:55 +03:00
parent 56dcd41052
commit 0f63e6efe4
12 changed files with 374 additions and 262 deletions

View File

@ -7,6 +7,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
Home: typeof import('./src/components/Home.vue')['default']
IMdi3dRotation: typeof import('~icons/mdi/3d-rotation')['default']
IMdiCard: typeof import('~icons/mdi/card')['default']
IMdiClarify: typeof import('~icons/mdi/clarify')['default']
@ -14,6 +15,7 @@ declare module 'vue' {
IMdiIcon: typeof import('~icons/mdi/icon')['default']
IMdiPrinter: typeof import('~icons/mdi/printer')['default']
IMdiVideo3d: typeof import('~icons/mdi/video3d')['default']
Projects: typeof import('./src/components/Projects.vue')['default']
RandomIcon: typeof import('./src/components/RandomIcon.vue')['default']
}
}

View File

@ -13,7 +13,8 @@
"pinia": "^2.1.7",
"reset-css": "^5.0.2",
"vue": "^3.4.21",
"vue-3d-model": "^2.0.0-alpha.4"
"vue-3d-model": "^2.0.0-alpha.4",
"vue-router": "^4.3.2"
},
"devDependencies": {
"@iconify-json/mdi": "^1.1.66",
@ -2284,6 +2285,20 @@
"vue": ">=3.0.0"
}
},
"node_modules/vue-router": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz",
"integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==",
"dependencies": {
"@vue/devtools-api": "^6.5.1"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/vue-template-compiler": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",

View File

@ -14,7 +14,8 @@
"pinia": "^2.1.7",
"reset-css": "^5.0.2",
"vue": "^3.4.21",
"vue-3d-model": "^2.0.0-alpha.4"
"vue-3d-model": "^2.0.0-alpha.4",
"vue-router": "^4.3.2"
},
"devDependencies": {
"@iconify-json/mdi": "^1.1.66",

View File

@ -1,129 +1,7 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { ModelFbx } from 'vue-3d-model';
import type { UseSwipeDirection } from '@vueuse/core';
import { useSwipe } from '@vueuse/core';
import RandomIcon from './components/RandomIcon.vue';
import { useProductStore } from './stores/product';
import type { ProductInfo } from './stores/product';
import Fireworks from '@fireworks-js/vue';
import type { FireworksOptions } from '@fireworks-js/vue'
const IMAGE_URL = import.meta.env.VITE_IMAGE_URL ?? window.location.origin
type StateType = {
active_product?: ProductInfo
show_model: boolean,
show_fireworks: boolean
}
const products = useProductStore()
const state: StateType = reactive({
active_product: undefined,
show_model: false,
show_fireworks: false
})
const reset = () => {
state.active_product = undefined
state.show_model = false
state.show_fireworks = false
}
const setActive = (id: number) => {
state.active_product = products.list.find(el => el.id == id)
state.show_model = false
state.show_fireworks = false
}
const toggleShowCanvas = () => {
state.show_model = !state.show_model
}
const target = ref<HTMLElement | null>(null)
useSwipe(target, {
onSwipeEnd(_: TouchEvent, direction: UseSwipeDirection) {
if (state.show_model) {
return
}
const index = products.list.findIndex(el => el.id == state.active_product?.id)
if (direction === 'right') {
if (index == products.list.length - 1) {
setActive(products.list[0].id)
} else {
setActive(products.list[index + 1].id)
}
} else if (direction === 'left') {
if (index == 0) {
setActive(products.list[products.list.length - 1].id)
} else {
setActive(products.list[index - 1].id)
}
}
}
})
const fw = ref<InstanceType<typeof Fireworks>>()
const options = ref<FireworksOptions>({
lineStyle: 'square',
intensity: 50,
lineWidth: {
explosion: { min: 7, max: 10 },
trace: { min: 7, max: 10 },
}
})
onMounted(async () => {
products.getData()
})
</script>
<template>
<div class="container">
<div class="sidebar">
<ul class="menu">
<li v-for="item in products.list">
<RandomIcon v-if="item.id === state.active_product?.id" />
<a @click.stop.prevent="setActive(item.id)" :href="item.id.toString()">
{{ item.title }}
</a>
</li>
</ul>
</div>
<div class="header"><span class="logo-header" @click="reset">Проекты Кустарщины</span></div>
<div class="main product" v-if="state.active_product" ref="target">
<div class="product-image" v-if="!state.show_model && state.active_product.image1">
<img :style="{
clipPath: `polygon(
0% 10%, 10% 0%,
90% 0%, 100% 10%,
100% 90%, 90% 100%,
10% 100%, 0% 90%
)`}" :src="`${IMAGE_URL}${state.active_product.image1}`" />
</div>
<div class="product-description" v-if="!state.show_model">
{{ state.active_product.description }}
</div>
<a class="product-model-icon" v-if="state.active_product.model3d" @click.stop.prevent="toggleShowCanvas">
<i-mdi-video-3d v-if="!state.show_model" />
<i-mdi-file v-else />
</a>
<model-fbx v-if="state.show_model && state.active_product.model3d" class="product-model"
:src="`${IMAGE_URL}/${state.active_product.model3d.replace('/back', '')}`" :backgroundAlpha="0"></model-fbx>
</div>
<div class="main" v-else>
<img class="logo-img" src="./assets/logo_color.png"
@click="() => { state.show_fireworks = !state.show_fireworks }" />
<Fireworks ref="fw" v-if="state.show_fireworks" :autostart="true" :options="options" :style="{
top: 0,
left: 0,
width: '100%',
height: '100%',
position: 'fixed',
zIndex: -1,
pointerEvents: 'none',
}" />
</div>
</div>
<RouterView />
</template>

View File

@ -0,0 +1,27 @@
@import '../../node_modules/reset-css/sass/reset';
.container {
display: grid;
grid-template-columns: 25vw 1fr;
grid-template-rows: 15vh 1fr;
gap: 0px 0px;
grid-template-areas:
"header header"
"sidebar content";
min-height: 100vh;
padding: 0 2rem;
}
.sidebar {
grid-area: sidebar;
}
.header {
grid-area: header;
}
.main {
grid-area: content;
}

View File

@ -0,0 +1,36 @@
@font-face {
font-family: 'open-sans';
src: url('../fonts/open-sans.ttf');
}
$accentColor: rgb(126, 126, 223);
.container {
font-family: 'open-sans';
}
.header {
display: flex;
justify-content: center;
font-size: 6rem;
font-weight: 800;
}
.main {
ul {
font-size: 4rem;
li {
padding: 2rem;
}
a {
text-decoration: none;
color: initial;
&:hover,
&:active {
color: $accentColor;
}
}
}
}

View File

@ -1,136 +1 @@
@import '../../node_modules/reset-css/sass/reset';
$blackColor: #181818;
$accentColor: #ef570c;
$redColor: #f83300;
@font-face {
font-family: 'logo';
src: url('../fonts/logo.ttf');
}
@font-face {
font-family: 'main';
src: url('../fonts/main.otf');
}
body {
font-family: 'main';
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%), radial-gradient(at top center, rgba(255, 255, 255, 0.40) 0%, rgba(0, 0, 0, 0.40) 120%) $blackColor;
background-blend-mode: multiply, multiply;
color: #fff;
user-select: none;
}
.container {
display: grid;
grid-template-columns: 25vw 1fr;
grid-template-rows: 15vh 1fr;
gap: 0px 0px;
grid-template-areas:
"header header"
"sidebar content";
min-height: 100vh;
padding: 0 2rem;
}
.sidebar {
grid-area: sidebar;
}
.header {
grid-area: header;
justify-content: center;
}
.main {
grid-area: content;
}
.main,
.header,
.sidebar {
display: flex;
align-items: center;
}
.menu {
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
font-size: 2.5rem;
li {
padding: 0.5rem 0;
a {
text-decoration: none;
&,
&:hover,
&:active,
&:visited {
color: $accentColor;
}
}
}
}
.model {
max-width: 33vw;
}
.product {
gap: 1rem;
position: relative;
&-image {
flex-basis: 50%;
flex-shrink: 0;
}
&-description {
font-size: 1.75rem;
line-height: 1.4;
}
&-model {
max-height: 80%;
&-icon {
position: absolute;
font-size: 4rem;
right: 0;
top: 0;
color: $accentColor;
z-index: 2;
}
}
}
.logo {
&-header {
font-family: 'logo';
font-size: 4rem;
padding: 1rem;
background: linear-gradient(45deg, $redColor, $accentColor);
background-clip: text;
-webkit-text-fill-color: transparent;
}
&-img {
width: 54%;
display: block;
margin: auto;
}
}
img {
max-width: 100%;
}
@import 'grid.scss';

View File

@ -0,0 +1,110 @@
$blackColor: #181818;
$accentColor: #ef570c;
$redColor: #f83300;
@font-face {
font-family: 'logo';
src: url('../fonts/logo.ttf');
}
@font-face {
font-family: 'main';
src: url('../fonts/main.otf');
}
.container {
font-family: 'main';
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%), radial-gradient(at top center, rgba(255, 255, 255, 0.40) 0%, rgba(0, 0, 0, 0.40) 120%) $blackColor;
background-blend-mode: multiply, multiply;
color: #fff;
user-select: none;
}
.header {
justify-content: center;
}
.main,
.header,
.sidebar {
display: flex;
align-items: center;
}
.menu {
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
font-size: 2.5rem;
li {
padding: 0.5rem 0;
a {
text-decoration: none;
&,
&:hover,
&:active,
&:visited {
color: $accentColor;
}
}
}
}
.model {
max-width: 33vw;
}
.product {
gap: 1rem;
position: relative;
&-image {
flex-basis: 50%;
flex-shrink: 0;
}
&-description {
font-size: 1.75rem;
line-height: 1.4;
}
&-model {
max-height: 80%;
&-icon {
position: absolute;
font-size: 4rem;
right: 0;
top: 0;
color: $accentColor;
z-index: 2;
}
}
}
.logo {
&-header {
font-family: 'logo';
font-size: 4rem;
padding: 1rem;
background: linear-gradient(45deg, $redColor, $accentColor);
background-clip: text;
-webkit-text-fill-color: transparent;
}
&-img {
width: 54%;
display: block;
margin: auto;
}
}
img {
max-width: 100%;
}

View File

@ -0,0 +1,24 @@
<style lang="scss" scoped>
@import '../assets/home.scss';
</style>
<template>
<div class="container">
<div class="header">
Выберите шаблон (демо)
</div>
<div class="sidebar"></div>
<div class="main">
<ul>
<li>
<RouterLink to="projects">Проекты</RouterLink>
</li>
<li>
<a href="https://timesheet.kustarshina.ru/">Табель рабочего времени</a>
</li>
<li>
<a href="https://zoo.svs-tech.pro/">Билетная система зоопарка</a>
</li>
</ul>
</div>
</div>
</template>

View File

@ -0,0 +1,137 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { ModelFbx } from 'vue-3d-model';
import type { UseSwipeDirection } from '@vueuse/core';
import { useSwipe } from '@vueuse/core';
import Fireworks from '@fireworks-js/vue';
import type { FireworksOptions } from '@fireworks-js/vue'
import RandomIcon from '../components/RandomIcon.vue';
import { useProductStore } from '../stores/product';
import type { ProductInfo } from '../stores/product';
const IMAGE_URL = import.meta.env.VITE_IMAGE_URL ?? window.location.origin
type StateType = {
active_product?: ProductInfo
show_model: boolean,
show_fireworks: boolean
}
const products = useProductStore()
const state: StateType = reactive({
active_product: undefined,
show_model: false,
show_fireworks: false
})
const reset = () => {
state.active_product = undefined
state.show_model = false
state.show_fireworks = false
}
const setActive = (id: number) => {
state.active_product = products.list.find(el => el.id == id)
state.show_model = false
state.show_fireworks = false
}
const toggleShowCanvas = () => {
state.show_model = !state.show_model
}
const target = ref<HTMLElement | null>(null)
useSwipe(target, {
onSwipeEnd(_: TouchEvent, direction: UseSwipeDirection) {
if (state.show_model) {
return
}
const index = products.list.findIndex(el => el.id == state.active_product?.id)
if (direction === 'right') {
if (index == products.list.length - 1) {
setActive(products.list[0].id)
} else {
setActive(products.list[index + 1].id)
}
} else if (direction === 'left') {
if (index == 0) {
setActive(products.list[products.list.length - 1].id)
} else {
setActive(products.list[index - 1].id)
}
}
}
})
const fw = ref<InstanceType<typeof Fireworks>>()
const options = ref<FireworksOptions>({
lineStyle: 'square',
intensity: 50,
lineWidth: {
explosion: { min: 7, max: 10 },
trace: { min: 7, max: 10 },
}
})
onMounted(async () => {
products.getData()
})
</script>
<style lang="scss" scoped>
@import '../assets/projects.scss';
</style>
<template>
<div class="container">
<div class="sidebar">
<ul class="menu">
<li v-for="item in products.list">
<RandomIcon v-if="item.id === state.active_product?.id" />
<a @click.stop.prevent="setActive(item.id)" :href="item.id.toString()">
{{ item.title }}
</a>
</li>
</ul>
</div>
<div class="header"><span class="logo-header" @click="reset">Проекты Кустарщины</span></div>
<div class="main product" v-if="state.active_product" ref="target">
<div class="product-image" v-if="!state.show_model && state.active_product.image1">
<img :style="{
clipPath: `polygon(
0% 10%, 10% 0%,
90% 0%, 100% 10%,
100% 90%, 90% 100%,
10% 100%, 0% 90%
)`}" :src="`${IMAGE_URL}${state.active_product.image1}`" />
</div>
<div class="product-description" v-if="!state.show_model">
{{ state.active_product.description }}
</div>
<a class="product-model-icon" v-if="state.active_product.model3d" @click.stop.prevent="toggleShowCanvas">
<i-mdi-video-3d v-if="!state.show_model" />
<i-mdi-file v-else />
</a>
<model-fbx v-if="state.show_model && state.active_product.model3d" class="product-model"
:src="`${IMAGE_URL}/${state.active_product.model3d.replace('/back', '')}`"
:backgroundAlpha="0"></model-fbx>
</div>
<div class="main" v-else>
<img class="logo-img" src="../assets/logo_color.png"
@click="() => { state.show_fireworks = !state.show_fireworks }" />
<Fireworks ref="fw" v-if="state.show_fireworks" :autostart="true" :options="options" :style="{
top: 0,
left: 0,
width: '100%',
height: '100%',
position: 'fixed',
zIndex: -1,
pointerEvents: 'none',
}" />
</div>
</div>
</template>

Binary file not shown.

View File

@ -1,10 +1,27 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createWebHistory, createRouter } from 'vue-router'
import './assets/main.scss'
import App from './App.vue'
import Home from './components/Home.vue'
import Projects from './components/Projects.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/projects', component: Projects },
]
const router = createRouter({
history: createWebHistory(),
routes,
})
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')