Selaa lähdekoodia

feature(init): Реализована система навигации; рзаработаны первые компоненты; добавлена логика авторизации пользователя

horanchikk 1 vuosi sitten
vanhempi
commit
457f8aec9a

+ 25 - 0
.editorconfig

@@ -0,0 +1,25 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+[*.vue]
+indent_style = space
+indent_size = 2
+
+[*.css]
+indent_style = space
+indent_size = 2
+
+[*.html]
+indent_style = space
+indent_size = 2
+
+[*.js]
+indent_style = space
+indent_size = 2
+
+[*.ts]
+indent_style = space
+indent_size = 2

+ 1 - 0
.env.example

@@ -0,0 +1 @@
+API_URL=http://localhost:8000

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+# Nuxt dev/build outputs
+.output
+.data
+.nuxt
+.nitro
+.cache
+dist
+
+# Node dependencies
+node_modules
+
+# Logs
+logs
+*.log
+
+# Misc
+.DS_Store
+.fleet
+.idea
+
+# Local env files
+.env
+.env.*
+!.env.example
+
+ios
+android
+
+pnpm-lock.yaml

+ 10 - 1
README.md

@@ -1 +1,10 @@
-# ktc
+# Procollege-nuxt3
+
+## Работа в локальном окружении
+1. Установите pnpm: `npm -g i pnpm` 
+2. Инициализируйте пакеты: `pnpm i`
+3. Переименуйте `.env.example` в `.env`
+4. Запустите сервер, используя: `pnpm run dev`
+
+## Мобильное приложение
+- Синхронизация и сборка проекта под Android и IOS: `pnpm run sync`

+ 14 - 0
app.vue

@@ -0,0 +1,14 @@
+<template>
+  <NuxtLayout>
+    <NuxtPage />
+  </NuxtLayout>
+</template>
+
+<style>
+/* #__nuxt * {
+  border: 1px solid rgba(255,255,0,0.3)
+} */
+html, body {
+  overscroll-behavior-x: none;
+}
+</style>

+ 5 - 0
capacitor.config.json

@@ -0,0 +1,5 @@
+{
+  "appId": "io.ionic.starter",
+  "appName": "ktc-reborn",
+  "webDir": "dist"
+}

+ 15 - 0
components/Header.vue

@@ -0,0 +1,15 @@
+<template>
+    <header class="w-full flex justify-between gap-5 bg-stone-800 p-3 animate__animated animate__fadeInDown animate__faster">
+        <Icon @click="sideBar.show()" name="solar:hamburger-menu-outline" class="w-8 h-8 hover:opacity-50 cursor-pointer duration-150" />
+        <div class="flex-auto flex items-center font-semibold">
+            UserName
+        </div>
+        <Icon name="basil:other-2-outline" class="w-8 h-8 hover:opacity-50 cursor-pointer duration-150" />
+    </header>
+</template>
+
+<script setup lang="ts">
+import { useSideBar } from '~/store/useSideBar';
+
+const sideBar = useSideBar()
+</script>

+ 45 - 0
components/NavBar.vue

@@ -0,0 +1,45 @@
+<template>
+  <nav class="relative w-full h-20 flex items-end animate__animated animate__fadeInUp animate__faster">
+    <div class="w-full rounded-t-xl bg-stone-800 h-14"></div>
+    <div
+      class="top-0 left-0 absolute w-full h-full grid grid-cols-3 grid-rows-1 items-end"
+    >
+      <div
+        v-for="(item, idx) in items"
+        :key="idx"
+        class="h-full flex justify-center items-end text-center"
+      >
+        <div
+          @click="selectedIdx = idx"
+          class="flex flex-col items-center justify-center bg-stone-800 px-4 cursor-pointer duration-300"
+          :class="{
+            'h-20 rounded-t-full': idx === selectedIdx,
+            'h-14': idx !== selectedIdx,
+          }"
+        >
+          <Icon :name="item.icon" class="w-10 h-10 duration-300" />
+          <p :class="selectedIdx === idx ? 'opacity-100 mt-1 text-sm' : 'opacity-0 text-[0px] mt-0'" class="duration-300" v-text="item.name" />
+        </div>
+      </div>
+    </div>
+  </nav>
+</template>
+
+<script setup lang="ts">
+const items = ref([
+  {
+    name: "Новости",
+    icon: "hugeicons:news",
+  },
+  {
+    name: "Расписание",
+    icon: "mdi:timetable",
+  },
+  {
+    name: "Профиль",
+    icon: "ph:student-bold",
+  },
+]);
+
+const selectedIdx = ref(0);
+</script>

+ 23 - 0
components/SideBar.vue

@@ -0,0 +1,23 @@
+<template>
+    <div v-if="store.isRendered" ref="sideBarEl" class="w-screen h-screen fixed top-0 left-0 z-10">
+        <div @click="store.hide" :class="store.isVisible ? 'bg-opacity-50' : 'bg-opacity-0'" class="w-full h-full bg-black animate__animated animate__faster animate__fadeIn duration-500" />
+        <div  :class="store.isVisible ? 'animate__animated animate__faster animate__slideInLeft' : 'animate__animated animate__faster animate__slideOutLeft'" class="absolute top-0 w-3/4 h-full bg-stone-900" :style="`left: -${translateTo}px`"></div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { useSideBar } from '~/store/useSideBar';
+import { useSwipe } from '@vueuse/core'
+
+const store = useSideBar()
+const sideBarEl = ref(null)
+const translateTo = ref(null)
+const {direction} = useSwipe(sideBarEl, {
+    onSwipeEnd() {
+        if (direction.value === 'left')
+            store.hide()
+    }
+})
+</script>
+
+style

+ 6 - 0
composables/api.ts

@@ -0,0 +1,6 @@
+import { $fetch } from 'ofetch'
+
+const { API_URL } = useRuntimeConfig()
+const anal = $fetch.create({
+    baseURL: API_URL,
+})

+ 26 - 0
layouts/default.vue

@@ -0,0 +1,26 @@
+<template>
+  <div ref="globalWindow" class="flex flex-col w-screen h-screen bg-[#030100] text-white overflow-y-hidden">
+    <SideBar />
+    <Header />
+
+    <main class="flex-auto">
+      <slot />
+    </main>
+
+    <NavBar />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useSwipe } from '@vueuse/core'
+import { useSideBar } from '~/store/useSideBar';
+
+const store = useSideBar()
+const globalWindow = ref(null)
+const {direction, lengthX} = useSwipe(globalWindow, {
+    onSwipeEnd() {
+        if (direction.value === 'right')
+            store.show()
+
+    }
+})</script>

+ 10 - 0
layouts/none.vue

@@ -0,0 +1,10 @@
+<template>
+    <div class="flex flex-col w-screen h-screen bg-[#030100] text-white">
+  
+      <main class="flex-auto">
+        <slot />
+      </main>
+  
+    </div>
+  </template>
+  

+ 8 - 0
middleware/user-only.ts

@@ -0,0 +1,8 @@
+import { useUser } from "~/store/useUser";
+
+export default defineNuxtRouteMiddleware((to, from) => {
+  const user = useUser();
+
+  if (!JSON.parse(JSON.stringify(user.data)).access_token) 
+    return navigateTo("/auth", { redirectCode: 301 });
+});

+ 31 - 0
nuxt.config.ts

@@ -0,0 +1,31 @@
+export default defineNuxtConfig({
+  compatibilityDate: '2024-04-03',
+  devtools: { enabled: true },
+  modules: [
+    '@nuxtjs/tailwindcss',
+    '@nuxtjs/color-mode',
+    '@pinia/nuxt',
+    '@formkit/auto-animate/nuxt',
+    '@nuxt/icon',
+  ],
+  ssr: false,
+  
+  css: ['animate.css'],
+  tailwindcss: {
+    config: {
+      theme: {
+        extend: {
+          colors: {
+            'primary': "#4caf50"
+          }
+        }
+      }
+    }
+  },
+
+  runtimeConfig: {
+    public: {
+      API_URL: process.env.API_URL
+    }
+  },
+})

+ 37 - 0
package.json

@@ -0,0 +1,37 @@
+{
+  "name": "nuxt-app",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "build": "nuxt build",
+    "dev": "nuxt dev",
+    "generate": "nuxt generate",
+    "preview": "nuxt preview",
+    "postinstall": "nuxt prepare",
+    "sync": "nuxt build && nuxt generate && npx cap sync"
+  },
+  "dependencies": {
+    "@capacitor/android": "6.1.2",
+    "@capacitor/app": "6.0.1",
+    "@capacitor/core": "6.1.2",
+    "@capacitor/haptics": "6.0.1",
+    "@capacitor/ios": "6.1.2",
+    "@capacitor/keyboard": "6.0.2",
+    "@capacitor/status-bar": "6.0.1",
+    "@vueuse/core": "^11.0.3",
+    "animate.css": "^4.1.1",
+    "nuxt": "^3.13.0",
+    "ofetch": "^1.3.4",
+    "vue": "latest",
+    "vue-router": "latest"
+  },
+  "packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c",
+  "devDependencies": {
+    "@capacitor/cli": "6.1.2",
+    "@formkit/auto-animate": "^0.8.2",
+    "@nuxt/icon": "^1.5.1",
+    "@nuxtjs/color-mode": "^3.4.4",
+    "@nuxtjs/tailwindcss": "^6.12.1",
+    "@pinia/nuxt": "^0.5.4"
+  }
+}

+ 42 - 0
pages/auth.vue

@@ -0,0 +1,42 @@
+<template>
+    <div class="w-full h-full flex justify-center items-center">
+        <form @submit.prevent="auth" class="flex flex-col gap-3">
+            <h1 class="text-4xl font-light text-center">KTC Auth</h1>
+            <input class="bg-transparent border-[1px] border-white text-white text-lg outline-none p-3 rounded-md" placeholder="Логин" type="text" v-model="authData.login">
+            <input class="bg-transparent border-[1px] border-white text-white text-lg outline-none p-3 rounded-md" placeholder="Пароль" type="password" v-model="authData.password">
+            <button class="border-[1px] font-semibold text-md border-primary rounded-md flex gap-3 justify-center items-center duration-150" :class="isLoading ? 'h-10' : 'h-12'" type="submit">
+                <Icon name="svg-spinners:ring-resize" v-if="isLoading" class="w-6 h-6" />
+                <p class="text-lg">{{ isLoading ? 'Авторизация' : 'Войти в аккаунт' }}</p>
+            </button>
+        </form>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { $fetch } from 'ofetch'
+import { useUser } from '~/store/useUser';
+
+definePageMeta({
+    layout: 'none'
+})
+
+const { public: {API_URL} } = useRuntimeConfig()
+const user = useUser()
+const isLoading = ref(false)
+const router = useRouter()
+
+const authData = reactive({
+    login: "rp-09-21-1-morozovvv",
+    password: "whwye323"
+})
+async function auth() {
+    isLoading.value = true
+    const res = await $fetch(`${API_URL}/user/login`, {
+        method: "POST",
+        body: authData
+    })
+    user.setUserData(res)
+    router.push('/profile')
+    console.log('ok')
+}
+</script>

+ 7 - 0
pages/index.vue

@@ -0,0 +1,7 @@
+<template>
+    <div class="flex justify-center items-center">Redirecting...</div>
+</template>
+
+<script setup lang="ts">
+navigateTo('/auth')
+</script>

+ 3 - 0
pages/messenger/index.vue

@@ -0,0 +1,3 @@
+<template></template>
+
+<script setup lang="ts"></script>

+ 3 - 0
pages/news.vue

@@ -0,0 +1,3 @@
+<template></template>
+
+<script setup lang="ts"></script>

+ 3 - 0
pages/profile/grades.vue

@@ -0,0 +1,3 @@
+<template></template>
+
+<script setup lang="ts"></script>

+ 11 - 0
pages/profile/index.vue

@@ -0,0 +1,11 @@
+<template>
+    123{{ useUser().data }}
+</template>
+
+<script setup lang="ts">
+import { useUser } from '~/store/useUser';
+
+definePageMeta({
+    middleware: ["user-only"]
+})
+</script>

+ 3 - 0
pages/timetable.vue

@@ -0,0 +1,3 @@
+<template></template>
+
+<script setup lang="ts"></script>

+ 3 - 0
server/tsconfig.json

@@ -0,0 +1,3 @@
+{
+  "extends": "../.nuxt/tsconfig.server.json"
+}

+ 17 - 0
store/useSideBar.ts

@@ -0,0 +1,17 @@
+export const useSideBar = defineStore("useSideBar", () => {
+  const isVisible = ref(false);
+  const isRendered = ref(false)
+
+  function show() {
+    isRendered.value = true;
+    isVisible.value = true;
+  }
+  function hide() {
+    isVisible.value = false;
+    setTimeout(() => {
+      isRendered.value = false
+    }, 500);
+  }
+
+  return { isVisible, isRendered, show, hide };
+});

+ 14 - 0
store/useUser.ts

@@ -0,0 +1,14 @@
+interface TUserData {
+    user_id: number
+    token: string
+}
+
+export const useUser = defineStore("useUser", () => {
+    const data = ref({} as TUserData)
+
+    function setUserData(obj: TUserData) {
+        data.value = obj
+    }
+
+    return {data, setUserData}
+});

+ 4 - 0
tsconfig.json

@@ -0,0 +1,4 @@
+{
+  // https://nuxt.com/docs/guide/concepts/typescript
+  "extends": "./.nuxt/tsconfig.json"
+}