Forráskód Böngészése

практика vue

Евгений Колесников 1 éve
szülő
commit
3d8576865f
6 módosított fájl, 251 hozzáadás és 4 törlés
  1. 63 0
      articles/vue_film.md
  2. 51 0
      articles/web_16.md
  3. 34 0
      articles/web_17.md
  4. 98 1
      articles/web_18.md
  5. BIN
      img/web_025.png
  6. 5 3
      readme.md

+ 63 - 0
articles/vue_film.md

@@ -0,0 +1,63 @@
+# Разработка мобильного приложения для управления коллекцией фильмов
+
+>Задание переделано из задания демо-экзамена по разработке мобильных приложений. Чтобы вёрстка на сайте выглядела как для мобильного устройства нужно в "инструменте разработчика" в браузере "показать панель инструментов устройства":
+>
+>![](../img/web_025.png)
+
+Необходимо корректно обрабатывать запросы к серверу. В случае получения ошибки от сервера или отсутствия соединения с сетью Интернет необходимо отобразить соответствующий текст ошибки с помощью диалогового окна.
+
+Необходимо следовать предложенному дизайну. Макеты приложения доступны по ссылке (launch screen делать не нужно):
+
+https://www.figma.com/file/wwDCPGa4AyPXdeuFnKU95M/KOD1.4-Variant2
+
+Описание протокола API доступно по ссылке:
+
+https://swagger.kolei.ru?url=https://cinema.kolei.ru/swagger/cinema.yml
+
+При разработке приложения используйте возможности **SPA** (разные страницы, переходы между ними) и **pinia** (хранение данных об авторизации, возможно кеширование списка фильмов)
+
+Необходимо реализовать следующий функционал:
+
+1. Создайте проект. [Настройте иконку приложения согласно макету](https://sky.pro/wiki/html/ispravlyaem-oshibku-404-pravilniy-import-favicon-ico-v-vue-js/). 
+
+1. Реализуйте экран `SignUp Screen` (регистрация) согласно макету:
+
+    * При нажатии на кнопку "Зарегистрироваться" необходимо проверять поля для ввода на пустоту, а также `email` на корректность (требования к `email` описаны в документации к API). При некорректном заполнении необходимо отобразить ошибку с помощью диалогового окна. Так же необходимо проверять равенство пароля и его повтора.
+
+        >Для продвинутых пользователей: желательно реализовать валидацию форм используя библиотеку валидации, например [Vuelidate](https://vuelidate-next.netlify.app/)
+
+    * При корректном заполнении формы необходимо отправлять запрос регистрации на сервер. При получении ошибки от сервера ее необходимо отобразить с помощью диалогового окна. При успешной регистрации нужно автоматически осуществить авторизацию и перейти на `Main Screen`.
+
+    * При нажатии на кнопку "У меня уже есть аккаунт" необходимо осуществлять переход на `SignIn Screen`.
+
+    * При первом запуске приложения должен отображаться `SignUp Screen`, при последующих - `SignIn Screen` (используйте **localStorage** для сохранения статуса входа). 
+
+        Используйте [условную маршрутизацию](./web_16.md#условная-маршрутизация)
+
+1. Реализуйте экран `SignIn Screen` (авторизация) согласно макету:
+
+    * При нажатии на кнопку "Войти" необходимо проверять поля для ввода на пустоту, а также `email` на корректность (требования к `email` описаны в документации к API). При некорректном заполнении необходимо отобразить ошибку с помощью диалогового окна. При корректном заполнении формы необходимо отправить на сервер соответствующий запрос.
+
+    * При нажатии на кнопку "Регистрация" необходимо осуществлять переход на `SignUp Screen`.
+
+    * При успешной авторизации необходимо осуществлять переход на экран `Main Screen`. При получении ошибки от сервера необходимо отобразить ее с помощью диалогового окна.
+
+1. Реализуйте экран `Main Screen` согласно макету:
+
+    * На экране необходимо отобразить обложки фильмов из подборки «new» (информацию о фильмах необходимо запрашивать с сервера). Обложки должны быть отображены в виде карусели, необходимо реализовать возможность пролистывания с помощью жеста **swipe**.
+
+        >Пример реализации карусели смотрите в [лекции "Карусель"](./web_18.md)
+
+    * При пролистывании обложек название фильма в верхней части экрана должно меняться.
+
+1. Реализуйте экран `Profile Screen` согласно макету:
+
+    * Данные о пользователе необходимо запрашивать с сервера.
+
+    * При нажатии на кнопку "Изменить" необходимо реализовать изменение аватара пользователя: 
+
+        - Пользователь выбирает фотографию. 
+        - Фотография отправляется на сервер.
+        - В случае успеха аватар пользователя заменяется на новый, в случае ошибки она отображается с помощью диалогового окна.
+
+    * При нажатии на кнопку "Выход" необходимо осуществлять переход на экран авторизации. 

+ 51 - 0
articles/web_16.md

@@ -93,6 +93,57 @@ export default router
 
 В массиве **routes** описаны маршруты, в которых указывается путь, название маршрута и компонент с содержимым для страницы
 
+### Условная маршрутизация
+
+Если нужно ограничить доступ к каким-то страницам только авторизованным пользователям, то можно настроить условную маршрутизацию.
+
+Для начала в [глобальном хранилище](./web_17.md) нужно описать переменные для хранения данных об авторизации, например:
+
+```js
+export const useApiStore = defineStore('api', () => {
+  const authData = ref(null)
+  const isAuthenticated = computed(() => authData.value != null)
+
+  return { authData, isAuthenticated }
+})
+```
+
+* В **authData** после успешной авторизации нужно записать данные пользователя.
+* Вычисляемое свойство **isAuthenticated** возвращает истину, если пользователь авторизован
+
+Затем в файле `src/router/index.js` реализуем метод _beforeEach_ экземпляра роутера:
+
+```js
+...
+
+const router = createRouter(...)
+
+router.beforeEach((to, from, next) => {
+  const api = useApiStore()
+  if (api.isAuthenticated) {
+    next()
+  } else if (to.path !== '/login') {
+    next({ path: '/login' })
+  } else {
+    next()
+  }
+})
+
+...
+```
+
+В параметрах этого метода:
+
+* **to** - целевой роут (куда пытаются перейти)
+* **from** - исходный роут (откуда переходят)
+* **next** - метод для продолжения обработки маршрута
+
+В приведённом варианте, если пользователь авторизован, то продолжаем процесс маршрутизации ничего не меняя.
+
+Если пользователь не авторизован и путь назначения не равен `/login`, то меняем маршрут на `/login` (`next({ path: '/login' })`)
+
+## Использование маршрутизации в шаблоне
+
 В "рыбе" приведены два разных варианта описания компонента для маршрута, но делают они одно и то же.
 
 В основном файле приложения (`App.vue`) тоже произошли изменения:

+ 34 - 0
articles/web_17.md

@@ -121,4 +121,38 @@ const { increment } = store
 </script>
 ```
 
+## Использование **localStorage** для хранения данных при перезапуске приложения
+
+В браузере есть два хранилища типа "ключ" - "значение" ("ключ" - строка, "значение" - скалярные данные (целое, строка и т.п.)): 
+
+* **localStorage** - постоянное хранилище (работает всегда)
+* **sessionStorage** - временное хранилище, пропадает при закрытии браузера
+
+Например, если нам нужно сохранить данные авторизации, то мы можем использовать **localStorage**:
+
+```js
+export const useApiStore = defineStore('api', () => {
+  const authData = ref(null)
+  const isAuthenticated = computed(() => authData.value != null)
+
+  // считываем данные из localStorage
+  const storedAuthData = localStorage.getItem('authData')
+  // если там что-то есть
+  if (storedAuthData != null) {
+    // десериализуем данные в нужный объект
+    authData.value = JSON.parse(storedAuthData)
+  }
+
+  function setAuthData (newAuthData) {
+    // при сохранении сериализуем (преобразуем в строку)
+    localStorage.setItem(
+      'authData', 
+      JSON.stringify(newAuthData)
+    )
+  }
+
+  return { authData, isAuthenticated, setAuthData }
+})
+```
+
 [Назад](./web_16.md) | [Дальше](./web_18.md)

+ 98 - 1
articles/web_18.md

@@ -1,7 +1,104 @@
 [К содержанию](../readme.md#введение-в-web-разработку)
 
+# Карусель
 
----
+>Будет использоваться в приложении "Управление коллекцией фильмов"
 
+На примере карусели разберёмся, как подключать дополнительные js-библиотеки
+
+Будем использовать [библиотеку **Swiper**](https://swiperjs.com/element) (можете и сами что-то написать или найти похожее)
+
+1. Установка библиотеки
+
+    Для **Swiper** есть NPM-пакет, установим его командой:
+
+    ```bash
+    npm install swiper
+    ```
+
+1. В компоненте в котором вы будете использовать карусель необходимо зарегистрировать компонент карусели
+
+    ```vue
+    <script setup>
+    import { register } from 'swiper/element/bundle'
+    register()
+    </script>
+    ```
+
+    >В доке ещё описан альтернативный вариант загрузки скрипта карусели из CDN. Если хотите использовать этот вариант, то скрипт нужно прописать в заголовок `index.html`
+    >
+    >Собственно первый вариант и делает динамическую загрузку js-скрипта, только не из CDN, а из локального пакета
+
+1. [Использование компонента карусели](https://swiperjs.com/element#usage-with-vue)
+
+    Можете попытаться разобраться сами, у компонента много разных настроек. Я ниже приведу вариант для карусели постеров (стили тут условные, вам под свою вёрстку надо будет поправить)
+
+    ```vue
+    <template>
+        {{ activeFilmName }}
+        <swiper-container
+            :slides-per-view="1"
+            :centered-slides="true"
+            style="width:100%;height:50vh"
+            @swiperslidechange="onSlideChange"
+        >
+            <swiper-slide
+                v-for="(film, index) in films"
+                :key="index"
+            >
+                <img 
+                :src="`https://cinema.kolei.ru/up/images/${film.poster}`" 
+                width="auto"
+                height="100%"
+                />
+            </swiper-slide>
+        </swiper-container>
+    </template>
+
+    <script setup>
+    import { ref } from "vue"
+    import { register } from 'swiper/element/bundle'
+
+    register()
+
+    const films = ref([])
+    const activeFilmName = ref('')
+
+    api.loadFilms().then(res => {
+        films.value = res
+        activeFilmName.value = films.value[0].name
+    })
+
+    function onSlideChange (event) {
+        // console.log('slide changed', event.detail[0].activeIndex)
+        activeFilmName.value = films.value[event.detail[0].activeIndex].name
+    }
+    </script>
+    ```
+
+    В примере я не показываю реализацию метода `api.loadFilms`, это вы должны будете реализовать самостоятельно в рамках практического занятия
+
+При установке внешних компонентов в консоли могут появиться **предупреждения**:
+
+```
+Failed to resolve component: swiper-slide
+If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement. 
+```
+
+На работоспособность они не влияют, но засоряют консоль. Чтобы сказать **vue**, чтобы он не обращал внимания на такие компоненты нужно в настройках (`vite.config.js`) добавить исключения:
+
+```js
+export default defineConfig({
+  plugins: [
+    vue({ 
+      template: {
+        compilerOptions: {
+          isCustomElement: (tag) => ['swiper-slide','swiper-container'].includes(tag),
+        }
+      }
+    }),
+    vueDevTools(),
+  ],
+```
 
 [Назад](./web_17.md) | [Дальше](./web_19.md)

BIN
img/web_025.png


+ 5 - 3
readme.md

@@ -388,9 +388,11 @@ https://office-menu.ru/uroki-sql Уроки SQL
 
 1. [Руководство пользователя](./articles/user_manual.md)
 
-### Практика, разработка мобильных приложений. Android Studio. Kotlin.
+### Практика, разработка Web-приложений. VueJs.
 
-1. [Основы языка Kotlin](./articles/kotlin.md)
+1. [Разработка мобильного приложения для управления коллекцией фильмов](./articles/vue_film.md)
+
+<!-- 1. [Основы языка Kotlin](./articles/kotlin.md)
 
 1. [Первый проект в Android Studio](./articles/android_studio.md)
 
@@ -420,7 +422,7 @@ https://office-menu.ru/uroki-sql Уроки SQL
 
 1. [Android TV](./articles/android_tv.md)
 
-1. [Разбор заданий прошлых лет](./articles/f6_demo_1.md)
+1. [Разбор заданий прошлых лет](./articles/f6_demo_1.md) -->
 
 <!--