Небольшой проект с полным стеком, созданный с помощью корзины семейства Vue

внешний интерфейс

Запуск проекта

# Клон к местному Git clone git@github.com: hanxueqing / douban-movie.git

# устанавливаем зависимости установка нпм

# Запускаем локальный сервер localhost:8080 пряжа служить

# опубликовать среду пряжа строить

Разработка проекта

1. Установите леса vue-cli3

В настоящее время основной тенденцией является использование проектов разработки переднего плана. То есть нам нужно использовать некоторые инструменты для создания среды разработки Vue. В общем, мы используем веб-пакет для сборки. Здесь мы напрямую используем официальный vue- основанный на веб-пакете.Инструмент создания лесов: vue-cli.

(1) Глобальная установка WebPack

cnpm install webpack -g

(2) Установить пряжу глобально

cnpm install yarn  -g

(3) Установите vue-cli глобально

cnpm install -g @vue/cli

OR

yarn global add @vue/cli

(4) Просмотр результатов установки

Мой компьютер был установлен, выполните команду последовательно:

node -v
yarn -v
vue -V (注意这里是大写的“V”)

Примечание: требуется node.js версии 8 или 8+.

Если появится соответствующий номер версии, установка прошла успешно.


2. Используйте vue-cli для сборки проекта

(1) Создать проект

vue create douban(项目名称)

(Обратите внимание, что имя здесь не может быть заглавным, в случае ошибки будет сообщено

Извините, имя больше не может содержать заглавные буквы)

Первая vue-модель — это сохраненная мной настройка, которой не было, когда я создавал ее в первый раз. По умолчанию — это настройка по умолчанию, которая установит для вас модули babel и eslint.Мы выбираем третье значение «Вручную выберите функции», чтобы настроить его вручную.


Введите пробел, чтобы выбрать текущий вариант, и введите a, чтобы выбрать все.

(Жирным шрифтом выделен элемент конфигурации, который необходимо выбрать)

BabelПосле установки этого модуля вы сможете распознавать синтаксис ES6, иначе можно будет распознать только синтаксис ES5.

TypeScript — это надмножество JavaScript, поддерживающее стандарт ECMAScript 6.

Поддержка прогрессивного веб-приложения (PWA)Прогрессивная улучшенная технология внешнего интерфейса веб-страницы PWA — это специализированная дисциплина, которая может выполнять функции автономного хранилища. Доступ к этой странице также можно получить, когда мобильный телефон отключен от Интернета. В настоящее время мы не используем ее. , поэтому мы не будем устанавливать его первым.

Routerмаршрутизация

Vuexглобальное управление состоянием

CSS Pre-processorsЯзык предварительной обработки CSS

Инструмент форматирования Linter / Formatter, помогающий нам писать лучший код

Модульное тестирование Модульное тестирование

E2E Testing

После выбора этих четырех элементов введите Enter и нажмите Enter.

Следующие несколько конфигураций:

  1. Использовать ли режим истории для настройки маршрутизации, введитеn, Войти

  2. Поскольку мы только что выбрали язык предварительной обработки CSS, здесь мы выбираемSass/SCSS (с прокруткой узла)Эта стабильная версия

  3. Где вам нужно хранить эти ваши конфигурационные файлы, мы выбираемin package.json

  4. Наконец, хотите ли вы сохранить эти конфигурации в качестве предустановки, чтобы применить их к будущим файлам? После сохранения вам не нужно вручную настраивать эти параметры. Вы можете использовать их непосредственно в будущем. Я сохранил их раньше, поэтому я выберите их в первую очередь.n.


После нажатия Enter он перейдет в состояние загрузки, а скорость зависит от вашей текущей скорости интернета.


После успешной установки нам будет предложено войти в папку douban и запустить yarn serve, чтобы начать прослушивание.


(2) Загрузите скомпилированный файл

Скопируйте скомпилированный файл Vue.config.js в папку douban.

3. Организуйте папку src

(1) маршрутизатор

Создайте новую папку маршрутизатора, перетащите в нее файл router.js, переименуйте его в index.js и удалите содержимое в маршрутах.


(2) просмотры

Удалите два файла .vue в папке представлений, удалите изображение логотипа в папке ресурсов и удалите компонент HelloWorld в папке компонентов.

(3) App.vue

Стиль файла App.vue удаляется, а содержимое div с идентификатором app удаляется.


(4) Создать новую страницу в представлениях

Создайте домашнюю страницу, книгу, видео, трансляцию, группу и мою страницу последовательно в папке представлений и создайте в папке файл index.vue.Файл с суффиксом vue состоит из трех частей: шаблона, сценария и Webpack анализирует .vue Файл загружается с помощью загрузчика vue-loader.

(5) Настройте маршрутизацию в маршрутизаторе

В папке маршрутизатора создайте домашнюю страницу, видео книги, трансляцию, группу и файл маршрутизации my page js по очереди.Каждый маршрут настроен с именем, чтобы мы могли найти этот компонент маршрутизации через именованную маршрутизацию в будущем, и наконец, в index.js Представьте.

(6) Представлено в App.vue

(7) Напишите файл стиля

Создайте новую папку таблиц стилей

В процессе упаковки webpack не будет обрабатывать подчеркнутые файлы, чтобы избежать повторной упаковки.

Создайте по очереди: _base.scss базовый стиль

_commons.scss общие стили

_mixins.scss смешивает стили

_reset.scss сброс стилей

Наконец, последовательно импортируйте их в папку main.scss.

@import "_base.scss";
@import "_mixins.scss";
@import "_reset.scss";
@import "_commons.scss";

Внедрите стили как модуль в main.js

//引入main.scss文件
import "./stylesheets/main.scss"

4. Компонент панели вкладок

(1) Слот-слот: именованный слот, анонимный слот

Во-первых, мы вводим значок нормального состояния и значок выбранного состояния по очереди, пишем стиль и называем значок нормального состояния normalImg и значок выбранного состояния как activeImg, Здесь мы используем слот слота. Напишите тег слота в дочернем компоненте и назначьте значение имени, поскольку именованный слот вы можете передать в контенте, который будет вставлен в родительский компонент.

Сборка:

<span><slot name = "normalImg"></slot></span>
<span><slot name = "activeImg"></slot></span>

Родительский компонент:

<TabItem>
            <img slot = "normalImg" src = "../../assets/ic_tab_home_normal.png" alt = "">
            <img slot = "activeImg" src = "../../assets/ic_tab_home_active.png" alt = "">
</TabItem>

Реализация первого шага Нажмите значок, чтобы переключиться на обычный стиль, чтобы выбрать шаблон, вам нужно установить свойство FLAG для управления отображением NormalImgG и ActiveIMG, здесь мы используем инструкции V-IF и V-ELSE.

<span v-if = "!flag"><slot name = "normalImg"></slot></span>
<span v-else><slot name = "activeImg"></slot></span

(3) Два способа передачи значений родительскими компонентами дочерним компонентам: привязка событий и пользовательские события

Передайте уникальное значение атрибута txt, mark, sel и метод changeSelected дочернему компоненту в родительском компоненте, а родительский компонент передает выбранное значение для sel через метод привязки атрибута:

Родительский компонент:

<TabItem txt = "我的" mark = "mine" :sel = "selected" :changeSelected = "changeSelected">

Подкомпоненты в свою очередь получают:

props:["txt","mark","sel","changeSelected"]

В дочернем компоненте, привязав изменение события щелчка, родительский компонент изменит выбранное значение, а затем передаст его дочернему компоненту и определит возвращаемое значение, сопоставив значение метки, тем самым контролируя текущее состояние значка.

Дочерний компонент связывает событие щелчка, чтобы вызвать метод изменения:

<div class = "tabitem" @click = "change">

Способ замены подкомпонента:

change(){
            //让父组件去更改sel值
            this.changeSelected(this.mark);
        }

Родительский компонент изменяет значение sel:

changeSelected(val){
            this.selected = val;
        }

Родительский компонент также может использовать пользовательское событие для передачи метода дочернему компоненту.Дочерний компонент больше не должен получать его через реквизиты, а напрямую использует this.$emit в методе.Первый параметр привязан к самому родителю , Имя события, второй параметр — это параметр, передаваемый родителю, через этот метод метод родительского компонента срабатывает в дочернем компоненте.

Родительский компонент:

<TabItem txt = "小组" mark = "group" :sel = "selected" @changeSelected = "changeSelected">

Сборка:

change(){
            //自定义事件通信方法
            this.$emit("changeSelected",this.mark)
        }

(4) вычисляемые свойства

Подкомпонент выносит решение и возвращает значение falg.Поскольку он должен зависеть от внешних значений для изменения, мы пишем флаг в вычисляемом свойстве:

computed:{
        flag(){
            if(this.mark === this.sel){
                return true
            }
            return false
        }
    }

(5) Программная навигация

Благодаря программной навигации в подкомпоненте, в соответствии с различными атрибутами меток, сопоставлением различных маршрутов и щелчком значка, чтобы перейти на соответствующую страницу.

change(){
            //自定义事件通信方法
            this.$emit("changeSelected",this.mark)
            //编程式导航
            this.$router.push("/" + this.mark)
        }

Поскольку мы написали Tabbar в App.Vue раньше, он создан только один раз и появится на всех страницах, но нам не нужны Tabbar на некоторых страницах, поэтому мы не можем написать его в глобальном языке, какую страницу нужна на какой странице Введено, в настоящее время мы обнаружим, что, хотя мы можем нажать, чтобы перейти на страницу, значок внизу нельзя нажать, чтобы изменить цвет. Это связано с тем, что каждый Tabbar прыжка прошел через процесс создания к разрушению, так что в На этот раз мы его больше нельдем нельзя назначить фиксированное значение в данных.

Оригинальный код:

data(){
        return{
            selected:"home"
        }
    }

Так как мы устанавливаем атрибут имени для каждого маршрута при настройке маршрута ранее, мы можем получить его значение имени в $route, когда мы печатаем это.Мы присваиваем это значение имени данным и передаем его непосредственно дочернему компоненту. нет необходимости передавать метод снова.


Используйте this.$route.name, чтобы определить, какой элемент в родительском компоненте выбран.

Родительский компонент:

data(){
        return{
            selected:this.$route.name,
        }
    }

Записывать все импортированные данные субкомпонента в шаблон явно громоздко и раздуто, в это время мы можем записать данные в объект данных, а затем с помощью инструкции цикла v-for загрузить их на страницу по очереди:

В data создайте массив нижних колонтитулов и поместите данные объекта в массив:

footers:[
                {id:1,txt:"首页",mark:"home",normalImg:"../../assets/ic_tab_home_normal.png",activeImg:"../../assets/ic_tab_home_active.png"},
                {id:2,txt:"书影音",mark:"audio",normalImg:"../../assets/ic_tab_audio_normal.png",activeImg:"../../assets/ic_tab_audio_active.png"},
                {id:3,txt:"广播",mark:"broadcast",normalImg:"../../assets/ic_tab_broadcast_normal.png",activeImg:"../../assets/ic_tab_broadcast_active.png"},
                {id:4,txt:"小组",mark:"group",normalImg:"../../assets/ic_tab_group_normal.png",activeImg:"../../assets/ic_tab_group_active.png"},
                {id:5,txt:"我的",mark:"mine",normalImg:"../../assets/ic_tab_mine_normal.png",activeImg:"../../assets/ic_tab_mine_active.png"}
            ]

(6) v-для инструкции

Прокрутите нижние колонтитулы в теге TabItem:

<TabItem
            v-for="foot in footers"
            :key = "foot.id"
            :txt = "foot.txt"
            :sel = "selected"
            :mark = "foot.mark"
        >
            <img slot = "normalImg" :src = "foot.normalImg" alt = "">
            <img slot = "activeImg" :src = "foot.activeImg" alt = "">
        </TabItem>

Но когда вы обновите веб-страницу в это время, вы обнаружите, что значки не могут быть загружены.Причина в том, что веб-пакет не распознает наш относительный путь в процессе упаковки и выводит содержимое в кавычках, как оно есть.


Нам нужно использовать требуемый путь загрузки, чтобы импортировать изображение как модуль.

{id:1,txt:"首页",mark:"home",normalImg:require("../../assets/ic_tab_home_normal.png"),activeImg:require("../../assets/ic_tab_home_active.png")}

А webpack преобразует введенное нами изображение в base64-битный формат, что эквивалентно размещению фрагмента текста на веб-странице, и при повторном доступе к нему не нужно отправлять запрос на удаленку.


После преобразования его объем станет больше, но он не будет намного больше, чем раньше.Время сетевого запроса будет уменьшено, а время станет равным 0 мс, что ускорит эффективность доступа и улучшит взаимодействие с пользователем. Это в основном для маленьких картинок, меньше 20кб.


5. 404 страницы

(1) перенаправление: перенаправление маршрута

Когда мы заходим на несуществующий путь, перенаправляем страницу на notfound, то есть когда пользователь вводит путь, которого нет в таблице маршрутизации, он всегда будет переходить на страницу 404.

Создайте новую папку Notfound в представлениях, напишите страницу 404 в index.vue и настройте маршрутизацию в роутере.

Напишите в index.js:

 routes: [
    {path:"/",redirect:"/home"},
    home,audio,broadcast,group,mine,
    {path:"/notfound",component:()=>import("@/views/Notfound")},
    {path:"*",redirect:"/notfound"},
  ]

Звездочка обозначает любой путь. При настройке {path:"*",redirect:"/notfound"} нужно ставить в конце, иначе все пути будут переходить на страницу 404, а маршрут будет искать страницы по порядку сверху до дна.

(2) router-link: метка маршрутизации

Добавьте функцию, чтобы нажать, чтобы вернуться на домашнюю страницу

<router-link to = "/">点我返回首页</router-link>

6. Напишите заголовок заголовка

(1) rem: адаптивный макет

Здесь мы используем rem для расчета размера и реализации адаптивного макета на мобильной стороне.

Сначала создаем новую папку modules, пишем rem.js, в основном iphone6

document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + "px"
window.onresize = function(){
    document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + "px"
}

Добавьте файл rem.js в main.js.

//引入rem.js文件
import "./modules/rem.js"

(2) font-awesome: библиотека значков шрифтов

Загрузите пакет файлов css с официального сайта font-awesome, поместите его в общую папку и введите стиль в index.html.


Используйте удивительный шрифт:

<i :class = "['fa','fa-' + home]"></i>

Теперь мы хотим понять, что при изменении маршрута информация в заголовке меняется динамически.Сначала мы изменим содержимое на эту форму:

В шаблоне:

<div class = "left">
            <i :class = "['fa','fa-' + icon]"></i>
            <span>{{title}}</span>
        </div>

В данных:

data(){
        return{
            icon:"home",
            title:"豆瓣首页",
        }
    },

В это время вам нужно использовать роутинг гуард.Когда маршрут переключается, вы можете сделать некоторую бизнес-логику.Во-первых, вам нужно ввести глобальный маршрут на страницу:

import router from  "@/router"

Напишите глобальную предмаршрутную защиту router.beforeEach и используйте оператор switch для соответствия:

created(){
        router.beforeEach((to,from,next)=>{
            switch(to.name){
                case "home":
                    this.title = "豆瓣首页"
                    this.icon = "home"
                    break;
                case "audio":
                    this.title = "豆瓣影音"
                    this.icon = "audio-description"
                    break;
                case "broadcast":
                    this.title = "豆瓣广播"
                    this.icon = "caret-square-o-left"
                    break;
                case "group":
                    this.title = "豆瓣小组"
                    this.icon = "group"
                    break;
                case "mine":
                    this.title = "豆瓣我的"
                    this.icon = "cog"
                    break;
                default:
                    break;

            }
            next();
        })
    }

Поскольку мы написали компонент Header в глобальном приложении, он будет отображаться на каждой странице, но мы хотим, чтобы он отображался только при совпадении указанного маршрута.На данный момент нам нужно установить значение параметра isShow для управления им показать и скрыть.

В данных:

data(){
        return{
            icon:"home",
            title:"豆瓣首页",
            isShow:true
        }
    }

В коммутаторе: значение ISShow, которое соответствует корпусу, и значение по умолчанию назначается значение ISShow для false.

switch(to.name){
                case "home":
                    this.title = "豆瓣首页"
                    this.icon = "home"
                    this.isShow = true
                    break;
                case "audio":
                    this.title = "豆瓣影音"
                    this.icon = "audio-description"
                    this.isShow = true
                    break;
                case "broadcast":
                    this.title = "豆瓣广播"
                    this.icon = "caret-square-o-left"
                    this.isShow = true
                    break;
                case "group":
                    this.title = "豆瓣小组"
                    this.icon = "group"
                    this.isShow = true
                    break;
                case "mine":
                    this.title = "豆瓣我的"
                    this.icon = "cog"
                    this.isShow = true
                    break;
                default:
                    this.isShow = false
                    break;

            }

Затем добавьте v-if в div и присвойте значение isShow

<div class = "app-header" v-if = "isShow">

Таким образом, есть два способа отображения и скрытия компонентов:

(1) Цитируйте везде, где это необходимо, например Tabbar.

(2) Коммутатор взаимодействует с защитой маршрутизации для принятия решений и использует v-if для отображения того, где это необходимо, например в заголовке.

7. Баннерная карусель

(1) swiper: карусельный компонент

Сначала установите и загрузите два модуля swiper, axios:

cnpm i swiper -S或者yarn add swiper -S

cnpm i axios -S 

Лучше не смешивать их.Когда вы используете cnpm в начале, вы всегда используете cnpm для установки

Отображение успешной установки


Внедрить стиль swiper в main.js

//引入swiper.min.css样式文件
import 'swiper/dist/css/swiper.min.css'

Создайте новую папку Banner в компоненте компонентов, напишите файл index.vue и введите swiper:

import Swiper from "swiper"

Записывайте содержимое swiper и отображайте изображения баннеров на странице в цикле.

<div class = "swiper-container">
        <div class = "swiper-wrapper">
            <div
                class = 'swiper-slide'
                v-for = "banner in banners"
                :key = "banner.id"
            >
                <img width = "100%" :src = "getImages(banner.images.small)" alt = "">
            </div>
        </div>
        <div class = "swiper-pagination"></div>

Поскольку мы используем API Douban, мы столкнемся с проблемой на картинке 403. Конкретные решения см. в этой статье:

blog.CSDN.net/Военные академии/Ах…

Статья предоставляет нам способ решения этой проблемы, здесь мы представляем его

methods:{
        // 解决403图片缓存问题
        getImages( _url ){
            if( _url !== undefined ){
                let _u = _url.substring( 7 );
                return 'https://images.weserv.nl/?url=' + _u;
            }
        }
    },

Так как мы будем использовать эту функцию позже, пропишите ее в модуле для экспорта:

export default (_url) => {
    if (_url !== undefined) {
        let _u = _url.substring(7);
        return 'https://images.weserv.nl/?url=' + _u;
    }
    return true
}

Когда вы используете его позже, вам нужно только импортировать:

import getImages from "@/modules/getImg"

Затем зарегистрируйте метод в методах:

 methods:{
        getImages
    }

При его использовании напишите имя функции напрямую и передайте src изображения в качестве параметра:

<img width = "100%" :src = "getImages(movie.images.small)" alt = "">

Далее мы создаем экземпляр Swiper

new Swiper(".home-banner",{
                    loop:true,
                    pagination:{
                        el:".swiper-pagination"
                    }
                })

В это время будет явление, что царапины не двигаются.Причина в том, чтоИзначально в этом месте не было данных swiper-slide, после того как мы отправили ajax запрос, он динамически сгенерировал шесть swiperslides, данные баннеров сразу изменились, а внутри был сгенерирован новый виртуальный дом для сравнения с последней структурой виртуального дома. , а затем сгенерировать новый реальный DOM. Этот процесс требует времени, но мы создаем его экземпляр немедленно, поэтому создание экземпляра завершится спустя много времени после рендеринга реального DOM.

РешениеМы можем избежать этой проблемы, дождавшись рендеринга нового реального DOM, потому что данные изменились.

(2) функция this.$nextTick

Таким образом, нам нужно написать процесс создания экземпляра в функции обратного вызова this.$nextTick.Операция в этой функции заключается в ожидании, пока реальный DOM, отображаемый новым виртуальным DOM на странице, вызванной обновлением данных, не будет фактически отображен. Проще говоря, это подождать, пока страница не будет полностью отрисована, прежде чем создавать ее экземпляр.

this.$nextTick(()=>{//在这个函数里面,因为数据改变导致页面生成新的真实dom,全部渲染完成了
                new Swiper(".home-banner",{
                    loop:true,
                    pagination:{
                        el:".swiper-pagination"
                    }
                })
            })

Эффект бегущей карусели баннеров:


(3) axios: отправлять данные асинхронного запроса ajax

Axios — это HTTP-библиотека на основе обещаний, которую можно использовать в браузерах и node.js.

Его основные функции:

  • Создать из браузераXMLHttpRequests
  • Создано из node.jshttpпросить
  • Обещанная поддержка API
  • Перехват запросов и ответов
  • Преобразование данных запроса и данных ответа
  • Запрос на отмену
  • Автоматически преобразовывать данные JSON
  • Защита поддержки клиентовXSRF

Поскольку есть много мест, где необходимо использовать запросы данных, каждый раз импортировать очень проблематично.Вы можете напрямую привязать axios к прототипу атрибута прототипа vue, а затем получить к нему доступ через this.$http:

//引入axios
import axios from "axios"
Vue.prototype.$http = axios;

(4) Решение междоменного: обратный прокси

Поскольку мы доступны к изображению удаленного порта локально, будут проблемы с перекрестными домонтами. Здесь мы решаем перекрестную проблему через обратный прокси и настроить прокси в файле конфигурации Vue.config.js.

proxy: {//反向代理的方式解决跨域
            "/api":{
                target:"http://47.96.0.211:9000", //目标域名
                changeOrigin:true,//是否改变域名
                pathRewrite:{//以什么开头
                    "^/api":""
                }
            }
        }, // 设置代理

Когда будет выполнен окончательный доступ, /api будет автоматически очищен, а следующий путь будет присоединен к целевому доменному имени:

this.$http.get("/api/db/in_theaters",{

После изменения файла конфигурации нам нужно перезапустить прослушиватель вручную!

(5) загрузка изображений

При первом входе на страницу данные не загружаются, а отображается пустой блок. Здесь можно добавить диаграмму загрузки, чтобы напомнить пользователю, что данные загружаются. Когда данные загружаются, они будут скрыты для улучшения пользовательский опыт.

Загрузочное изображение помещается в папку с ресурсами, и это загрузочное изображение также можно использовать в другом месте, поэтому мы вводим его в общедоступном стиле:

.loading{
    background:url(../assets/index.svg) no-repeat center;
    background-size:10%;
    height:2.4rem
}

После этого вам нужно только добавить имя класса загрузки в метку.Поскольку мы установили начальное значение баннера в нулевое значение в данных, вы можете определить отображение и скрытие метки в соответствии со значением баннера и v-если/v-иначе команда:

<div class = "loading" v-if = "!banners"></div>
        <div class = "swiper-wrapper" v-else>

Баннерный компонент представлен на главной странице

<Banner></Banner>

Эффект запуска схемы загрузки:


8. Домашний список

(1) фильтр: фильтр

Нам нужно написать компонент MovieBox на домашней странице, вложить в него компонент MovieItem, запросить данные в MovieBox и передать их в MovieItem в виде атрибутов.

data(){
        return{
            movies:null
        }
    },
    created(){
        this.$http.get("/api/db/in_theaters",{
            params:{
                limit:6
            }
        }).then(res=>{
            this.movies = res.data.object_list
        })
    }

MovieItem получает объект фильмов:

props:{
        movie:Object
    }

Вставьте данные на страницу:

<div class = "movieitem">
        <div class = "main_block">
            <div class = "img-box">
                <img width = "100%" :src = "getImages(movie.images.small)" alt = "">
            </div>
            <div class = "info">
                <div class = "info-left">
                    <div class = "title line-ellipsis">{{movie.title}}</div>
                    <div class = "detail">
                        <div class = "count line-height">播放量<span class = "sc">{{movie.collect_count | filterData}}</span></div>
                        <div class = "actor line-height line-ellipsis">
                            <span class = "a_title">导演:</span>
                            <span class = "a_star line-ellipsis">{{movie.directors[0].name}}</span>
                        </div>
                        <div class = "rating line-height">评分:{{movie.rating.average}}</div>
                    </div>
                </div>

                <div class = "info-right"> 
                    <div class = "btn">
                        <span>购票</span>
                    </div>
                </div>
            </div>
        </div>
    </div>

Зациклить рендеринг на странице в MovieBox:

<div class = "moviebox">
        <div class="loading" v-if="!movies"></div>
        <MovieItem
            v-else
            v-for = "movie in movies"
            :key = "movie.id"
            :movie = "movie"
        ></MovieItem>
    </div>

Мы запрашиваем данные у бэкэнда, и иногда это не соответствует нашим реальным потребностям.Например, громкость воспроизведения возвращает большое число, которое неудобно читать и смотреть.В это время нам нужно установить фильтр фильтра для обработки данные в нужный нам формат. Здесь мы устанавливаем метод filterData.Если полученные данные больше 10000, мы делим текущее число на 10000, сохраняем один десятичный знак с помощью .toFixed(), а затем объединяем «десять тысяч» для отображения на странице. Фильтр должен иметь возвращаемое значение.После обработки нам нужно вернуть данные data.

filters:{
        filterData(data){
            // console.log(typeof data) //num数字类型
            if(data > 10000){
                data = data /10000;
                data = data.toFixed(1)
                data += "万"
            }
            return data;
        }
    }

Используйте «|» в компоненте:

<div class = "count line-height">播放量<span class = "sc">{{movie.collect_count | filterData}}</span></div>

(2) mint-ui: библиотека пользовательского интерфейса

Установите мятный интерфейс:

cnpm i mint-ui -S

После успешной установки выдает:


Официальный веб-сайт предоставляет два способа импортировать компоненты Mint-ui. Первый - это чтобы импортировать компоненты Mint-ui полностью, что является относительно большим по размеру. Второй - это для импорта спроса. Только необходимые компоненты импортируются. Отказ



При использовании второго метода вам также необходимо установить плагин:

npm install babel-plugin-component -D


Затем добавьте в файл babel.config.js следующее:

"plugins": [["component", 
    {
      "libraryName": "mint-ui",
      "style": true
    }
  ]]

Вставьте сюда небольшую ошибку. Если при установке модуля babel вы столкнулись со следующими проблемами, обратитесь к этой статье для решения:

сегмент fault.com/ah/119000001…


(3) Lazyload: ленивая загрузка компонентов

Реализовать ленивую загрузку

Сначала представьте модуль ленивой загрузки в Mint-UI.

//引入mint-ui相关的模块
import {Lazyload} from "mint-ui"
Vue.use(InfiniteScroll);

Измените атрибут src изображения на директиву v-lazy.

<img width = "100%" v-lazy = "getImages(movie.images.small)" alt = "">

Таким образом, страница может достичь эффекта ленивой загрузки при рендеринге изображения и загружать изображение только тогда, когда пользователь скользит вниз и ему необходимо загрузить изображение.


Выгруженные изображения, реальный атрибут src временно хранится в data-src


(4) InfiniteScroll: бесконечная загрузка

Mint-ui также предоставляет плагин для реализации функции загрузки подтягиваний, который называется InfiniteScorll, на который все еще ссылаются в main.js.

//引入mint-ui相关的模块
import { Lazyload, InfiniteScroll } from "mint-ui"
Vue.use(Lazyload);
Vue.use(InfiniteScroll);

Добавьте директиву v-infinite-scroll к самому внешнему тегу div в модуле MovieBox, чтобы использовать бесконечную прокрутку.

<div class = "moviebox"
        v-infinite-scroll="loadMore"
        infinite-scroll-disabled="loading"
        infinite-scroll-distance="10"
    >


v-infinite-scroll: метод, который будет запущен, когда расстояние прокрутки меньше порогового значения, значение по умолчанию — false.

бесконечная прокрутка отключена: если true, бесконечная прокрутка не будет запущена.

бесконечная прокрутка: пороговое значение расстояния прокрутки (в пикселях), которое запускает метод загрузки.

бесконечная прокрутка-немедленная проверка: если это правда, директива проверит, нужно ли выполнять метод загрузки сразу после привязки директивы к элементу. Полезно, когда содержимое может не помещаться в контейнер в исходном состоянии. , значение по умолчанию — true, если изменить на false, метод loadMore не будет выполняться

В методах пишем метод loadMore для реализации бесконечной прокрутки

methods:{
        loadMore(){
            console.log("loadmore")
            }
        },
    }

Пока включена бесконечная прокрутка, метод loadMore будет выполняться один раз по умолчанию при создании страницы, поэтому мы инкапсулируем код, написанный в созданном ранее, в метод getMovies, записываем его в методы и запускаем его в loadMore.

methods:{
        loadMore(){
            console.log("loadMore")
            this.getMovies();
        },
        getMovies(){
            this.$http.get("/api/db/in_theaters",{
            params:{
                limit:6
            }
            }).then(res=>{
                this.movies = res.data.object_list
            })
        }
    }

Когда значение бесконечной прокрутки отключено равно false, будет запущена бесконечная прокрутка.Мы назначаем ей загрузку, поэтому нам нужно определить загрузку в данных и присвоить значение false, чтобы она запускала бесконечную прокрутку по умолчанию.

data(){
        return{
            movies:null,
            loading:false,//默认触发无限滚动
        }
    }

Когда мы продолжаем скользить вниз, нам нужно запросить параметры второй страницы, поэтому нам нужно использовать атрибут страницы.Когда мы запрашиваем данные, нам нужно реализовать динамическую загрузку, поэтому нам нужно вынести лимит и страницу и настроить их отдельно.

data(){
        return{
            movies:null,
            loading:false,//默认触发无限滚动
            limit:6,
            page:1,
        }
    }

Используйте деструктурирование для назначения параметрам и выполнения page++ в функции .then

getMovies(){
            // 解构的写法
            let{page,limit} = this;
            this.$http.get("/api/db/in_theaters",{
            params:{
                limit,
                page
            }
            }).then(res=>{
                this.movies = res.data.object_list
                this.page++
            })
        }

На этот раз мы обнаружили, что данные, запрошенные позже, перезаписывают предыдущие данные.Эффект, которого мы хотим добиться, заключается в накоплении на основе первой страницы, поэтому нам нужно инициализировать фильмы в пустой массив.

movies:[],

Используйте метод concat массива, чтобы соединить массив на исходной основе и вернуть новый массив для назначения фильмам.

//在原来的基础上拼接数组,并且返回新数组
                this.movies = this.movies.concat(res.data.object_list)

На данный момент мы можем добиться эффекта скольжения вниз для загрузки нового контента, но возникает другая проблема: из результата запроса данных несколько раз срабатывает один и тот же метод loadMore.


И нам нужно запросить только один раз, поэтому нам нужно временно отключить loadMore, когда мы запрашиваем данные

this.loading = true;

Когда данные вернутся, снова включите их в функции .then.

this.loading = false;

Таким образом, метод бесконечной прокрутки не будет запускаться часто, но теперь, даже если после этого данных нет, он все равно будет отправлять запросы данных, поэтому нам нужно установить параметр, чтобы отслеживать, есть ли больше данных, подсказывать пользователю, когда нет данных, и предотвратить последующие действия

hasMore:true //是否有更多数据,默认有更多数据

Напишите суждение в функции .then, превышает ли предельное время страницы общее количество, если оно больше, чем значение hasMore, присваивается значение false, а возврат false предотвращает последующие операции, этот шаг необходимо разместить перед страницей++

if(this.limit * this.page >= res.data.total){ //判断是否有更多数据
                        this.hasMovies = false //没有更多数据时将hasMovies赋值为false,返回false,不执行接下来的page++
                        return false
                    }
this.page++

Напишите суждение в loadMore. Если hasMore имеет значение false, бесконечная прокрутка будет отключена, и в то же время будет остановлен метод getMovies. Нет необходимости отправлять ajax-запросы, и метод loadMore не будет часто запускаться. .

loadMore(){
            if(!this.hasMore){
                this.loading = true //没有更多数据的时候关闭无限滚动,同时返回false,不执行接下来的操作
                return false
            }
            this.getMovies();
        }


(5) Toast: компонент всплывающего окна

Чтобы предоставить пользователю хороший пользовательский интерфейс, необходимо дать пользователю подсказку при загрузке данных.В это время используется компонент всплывающего окна Toast, и модуль сначала загружается в MovieBox, потому что

Toast

нельзя использовать как глобальные переменные, только мгновенные

представлять

.

import {Toast} from "mint-ui"

продолжительность — это количество миллисекунд, которое длится. Если вы хотите, чтобы он отображался все время, вам нужно всего лишь присвоить ему значение -1. ​​Выполнение метода Toast вернет экземпляр Toast. У каждого экземпляра есть метод close, чтобы вручную закрыть Тост.


Перед запросом данных поместите Toast, чтобы сообщить пользователю, что данные загружаются

let instance = Toast({
                message: '正在加载中...',
                duration: -1,
                iconClass:"fa fa-cog fa-spin"
            })

После завершения загрузки вызовите метод close в функции .then, чтобы закрыть всплывающее окно.

instance.close();

Так как я сделал ошибку, используя стиль toast в mint-ui, вместо этого я использовал Vant.Это легкая и надежная библиотека мобильных компонентов Vue.Настройки параметров аналогичны mint-ui, который определяется vant.Toast:

const toast1 = vant.Toast.loading({
                message: '正在加载中',
                duration: 0,
            })

Когда продолжительность равна 0, она не будет закрыта автоматически, присвойте ей константу и закройте через метод .clear().

toast1.clear();


Эффект всплывающего окна тоста:


9. Переключение вкладок

(1) наблюдать за изменениями данных

Нам нужно добавить горячую и предстоящую вкладку на домашнюю страницу, использовать метод динамической загрузки данных и определить массив навигации в данных.

data(){
      return{
        type:"in_theaters",
        navs:[
          {id:1,title:"正在热映",type:"in_theaters"},
          {id:2,title:"即将上映",type:"coming_soon"},
        ],
      }
    }

Прокрутите массив в диапазоне, отобразите данные на странице, добавьте «активное» имя класса и метод события щелчка. Когда мышь щелкает параметр, измените значение типа, и класс судит, является ли тип равно nav.type, когда оно равно Добавить активный стиль выбора, чтобы добиться эффекта, при котором выбранный параметр становится выбранным состоянием.

<div class = "navbar"">
          <span
            v-for = "nav in navs"
            :key = "nav.id"
            :class  = "{'active':type === nav.type}"
            @click = "type = nav.type"
          >{{nav.title}}</span>
        </div>

Поскольку мы используем адрес /api/db/in_theaters для запроса данных в MovieBox, мы можем запросить только те данные, которые отображаются сейчас.Нам нужно передать значение типа в MovieBox

<MovieBox :type = "type"></MovieBox>

MovieBox получить

props:["type"],

Затем измените intheaters на this.type, чтобы реализовать интерфейс запроса динамического переключения.

this.$http.get("/api/db/" + this.type,{

Если вы хотите отображать разные данные, когда вы нажимаете для переключения, вам нужно позволить компоненту MovieBox выполнять последующие операции в соответствии с изменением значения типа.В настоящее время нам нужно использовать часы для отслеживания изменения введите значение и сделайте что-нибудь, когда значение типа изменится.Для обработки бизнес-логики очистите исходный массив фильмов, запустите страницу с первой страницы, присвойте hasMore значение true, а затем повторно запустите метод getMovies, чтобы запросить дополнительные данные.

watch:{
        type(){
            this.movies = [];
            this.page = 1;
            this.hasMore = true;
            this.getMovies();
        }
    }

Эффект переключения вкладок:


10. Фиксированная панель опций

(1) созданная функция ловушки

Когда страница прокручивается до определенной высоты, нам нужно зафиксировать панель параметров.Для достижения этого эффекта нам сначала нужно определить данные isfixed в данных, начальное значение равно false

isfixed:false,

При прокрутке до определенной высоты значение равно true, класс динамически привязывается к панели параметров и содержимому страницы, а также добавляется фиксированный стиль позиционирования.

<div class = "tab" :class = "{fixedTop:isfixed}">
<div :class = "{fixedBox:isfixed}">

Мы слушаем событие прокрутки в созданной функции, чтобы получить высоту прокрутки страницы, в то же время, чтобы избежать многократного срабатывания этого события, это событие срабатывает только тогда, когда высота прокрутки больше 50 и значение isfixed является ложным.

 created(){//初始化一些生命周期相关的事件
      window.addEventListener("scroll",e=>{
        //获取滚动高度
let sTop = document.documentElement.scrollTop || document.body.scrollTop;
        if(sTop >= 50 && !this.isfixed){
          this.isfixed = true;
        }else if(sTop < 50 && this.isfixed){
          this.isfixed = false;
        }
    })

Теперь можно прокручивать на определенное расстояние и панель опций фиксирована.Однако мы обнаружим, что этот эффект также срабатывает при прокрутке других страниц, поэтому нам нужно написать эту функцию в методах и переопределить метод listenScroll():

methods:{
      listenScroll(e){
        //获取滚动高度
        let sTop = document.documentElement.scrollTop || document.body.scrollTop;
        if(sTop >= 330 && !this.isfixed){
          this.isfixed = true;
        }else if(sTop < 300 && this.isfixed){
          this.isfixed = false;
        }
      }
    }

Добавьте этот метод в созданный:

created(){//初始化一些生命周期相关的事件
      window.addEventListener("scroll",this.listenScroll)
    },

(2) функция ловушки beforeDestory

Затем уничтожьте этот метод при выходе с текущей страницы:

beforeDestory(){//组件替换,相当于组件被销毁的时候,执行销毁操作
      window.removeEventListener("scroll",this.listenScroll)
    },

Таким образом, это не будет мешать бизнес-логике других страниц.

11. Кэш записи

(1) keep-alive: кеш-тег

Когда мы переключаемся между компонентами, мы хотим сохранить состояние просмотра домашней страницы и не допускать повторного рендеринга.На данный момент нам нужно использовать тег keep-alive для переноса содержимого, которое необходимо кэшировать, и использовать включить атрибут, чтобы выбрать компонент, имя которого соответствует

<keep-alive include = "home">
      <router-view></router-view>
</keep-alive>

В этот момент наш домашний компонент будет закэширован, поэтому созданная им функция будет выполняться только один раз, она не будет уничтожена, и метод beforeDestory не будет выполняться, поэтому наши предыдущие методы нужно прописать в двух жизненных циклах активируется и деактивируется в функции крюка.

(2) активированные и деактивированные функции жизненного цикла

activated а также deactivated только в Выполняется во вложенных компонентах внутри дерева.

activated(){
      window.addEventListener("scroll",this.listenScroll)
    },
    deactivated(){
      window.removeEventListener("scroll",this.listenScroll)
    }

В это время мы обнаружим проблему.Когда мы выходим из этого компонента, он будет выполнять запрос данных в один момент, поэтому нам нужно присвоить значение this.loading в true в методе deactived, закрыть бесконечную прокрутку, и включите его снова в активированном.

activated(){
      window.addEventListener("scroll",this.listenScroll)
      this.loading = false;//开启无限滚动
    },
    deactivated(){
      window.removeEventListener("scroll",this.listenScroll)
      this.loading = true;//关闭无限滚动
    }

Входя от других компонентов по мере необходимости для этого. Официальное повторное назначение является ложным, в противном случае он был фиксированным позиционированием.

deactivated(){
      window.removeEventListener("scroll",this.listenScroll)
      this.isfixed = false;
      this.loading = true;//关闭无限滚动
    }

(3) функция ловушки beforeRouteLeave

Мы получаем высоту прокрутки через document.documentElement.scrollTop в beforeRouteLeave

beforeRouteLeave(to,from,next){
      this.homeTop = document.documentElement.scrollTop;
      next();
    }

(4) метод прокрутки

Добавьте метод window.scrollTo для записи положения полосы прокрутки.

activated(){
      window.addEventListener("scroll",this.listenScroll)
      window.scrollTo(0,this.homeTop)
    }

Или вы также можете перейти к записи положения полосы прокрутки, назначив document.documentElement.scrollTop для this.homeTop

document.documentElement.scrollTop = this.homeTop

эффект кэширования тегов keep-alive:


12. Вернуться к началу

(1) Пользовательские инструкции

Нам нужно добавить компонент возврата к началу на домашнюю страницу и определить параметр isShow в данных с помощью инструкции v-if для управления отображением и скрытием кнопки возврата к началу, которая не отображается в первый по умолчанию.

data(){
        return {
            isShow:false
        }
    }

Поскольку мы добавили keep-alive в родительский компонент, мы также можем использовать активированные и деактивированные методы в дочернем компоненте или добавить метод listenScroll в методы, чтобы указать, что он будет отображаться при прокрутке до 200 пикселей.

methods:{
        listenScroll(){
            let sTop = document.documentElement.scrollTop || document.body.scrollTop
            //避免重复触发
            if(sTop >= 200 && !this.isShow){
                this.isShow = true;
            }else if(sTop < 200 && this.isShow){
                this.isShow = false;
            }
        },
    }

Добавление и удаление операций в двух функциях ловушек.

activated(){
        window.addEventListener("scroll",this.listenScroll)
    },
    deactivated(){
        window.removeEventListener("scroll",this.listenScroll)
    }

Нам нужно реализовать операцию нажатия на кнопку для возврата наверх, нам нужно привязать событие клика к кнопке

<div class="back-top-box" v-if = "isShow" @click = "backTop">

Также напишите метод backTop в методах

backTop(){
            window.scrollTo(0,0)
        }

Однако, если вы также можете дважды щелкнуть панель параметров, чтобы вернуться к началу страницы, повторное связывание событий будет проблематичным.В настоящее время мы можем написать этот метод как пользовательскую команду, а затем добавить команду в метка этой функции.

То есть инкапсулировать пользовательские инструкции в модули и получить тип времени через binding.arg.По умолчанию это click:

//v-backtop 就可以实现返回顶部功能
import Vue from "vue"
Vue.directive("backtop",{
    bind(el,binding,vnode){
        let eventType = binding.arg || "click";
        el.addEventListener(eventType,e=>{
            window.scrollTo(0,0)
        })
    }
})

Зарегистрироваться в main.js

//引入directive
import "./modules/directive"

Добавьте пользовательскую команду к кнопке, чтобы нажать кнопку «Вернуться к началу».

<div class="back-top-box" v-if = "isShow" v-backtop>

Добавьте пользовательскую команду на панель параметров, чтобы дважды щелкнуть ее, чтобы вернуться наверх

<div class = "tab" :class = "{fixedTop:isfixed}" v-backtop:dblclick>

Возврат к верхнему бегущему эффекту:


13. Страница сведений о фильме

(1) Динамическая маршрутизация

Создайте страницу MovieDetail и настройте динамическую маршрутизацию через: id

export default {
    name:"moviedetail",
    path:"/moviedetail/:id",
    component:()=>import("@/views/Movie/MovieDetail")
}

Ввести и зарегистрировать в указателе,

import moviedetail from "./moviedetail"

Добавьте ссылку на маршрутизатор в MovieItem и динамически передайте параметр movie.id

<router-link 
        :to = "{name:'moviedetail',params:{id:movie.id}}"
        tag = "div"
        class = "main_block">

Назначить полученные res.data фильму

getMovie(){
            this.$http.get("/api/db/movie_detail/" + this.$route.params.id).then(res => {
                this.movie = res.data
            })
        }

Динамически отображаем нужные нам данные на странице, затем добавляем компонент Header в Mint-UI, назначаем title для movie.title, добавляем кнопку возврата и вносим компонент Header в main.js.

import { Lazyload, InfiniteScroll, Header, Button } from "mint-ui"
Vue.component("mt-header", Header);
Vue.component("mt-button", Button);

Вставьте тег mt-header на страницу, и теперь вы можете щелкнуть, чтобы перейти на соответствующую страницу сведений о фильме.

<mt-header fixed :title="movie.title">
                <router-link to="/" slot="left">
                    <mt-button icon="back">返回</mt-button>
                </router-link>
                <mt-button icon="more" slot="right"></mt-button>
            </mt-header>

Эффект запуска страницы сведений:


14. Эффект корзины покупок

(1) официальный инструмент управления состоянием vuex

Глобальный инструмент управления состоянием, предоставляемый vue, который в основном имеет дело с разделением состояния между несколькими компонентами в проекте.

Vuex — это официальный инструмент управления состоянием Vue.Что такое состояние? У нас есть концепция во фронтенд-разработке: data-driven, любое отображение на странице отличается, должен быть кусок данных для управления, и эти данные еще называют состоянием.

во вью. Передача данных и обмен данными между компонентами происходят часто, а функция связи между родительско-дочерними компонентами и неродительско-дочерними компонентами относительно завершена, но единственная трудность заключается в обмене данными между несколькими компонентами, который обрабатывается vuex.

Далее нам нужно реализовать эффект корзины покупок, сначала настройте нижнюю панель навигации:

Создайте еще два компонента в компоненте шахты, одна — страница автомобиля корзины покупок, а другая — страница списка покупок.В маршрутизации шахты настройте вторичную маршрутизацию.

//配置二级路由
    children:[
        {path:"",redirect:"list"},
        {path:"list",component:()=>import("@/views/Mine/List"),name:"list"},
        {path:"car",component:()=>import("@/views/Mine/Car"),name:"car"},
    ]

Используйте router-view в моем, чтобы отобразить страницу вторичного компонента, введите нижний навигационный компонент панели вкладок в mint-ui и зарегистрируйте его в main.js.

//引入mint-ui相关的模块
import { Lazyload, InfiniteScroll, Header, Button, Tabbar, TabItem } from "mint-ui"
Vue.component("mt-tabbar", Tabbar);
Vue.component("mt-tab-item", TabItem);

Обратитесь к моей странице, динамически загружайте данные об элементе на страницу, добавьте ссылку на маршрутизатор, чтобы реализовать функцию перехода по клику.

<mt-tabbar>
           <mt-tab-item
            v-for="nav in navs"
            :key="nav.id"
           >
            <router-link :to = "{name:nav.name}" active-class = "active">
              <img :src = "nav.src">
              {{nav.title}}
            </router-link>
           </mt-tab-item>
 </mt-tabbar>

Вы можете установить плагин Vue Devtools для просмотра статуса vuex.



(2) Создание vuex

  1. Создать магазин:
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import state from "./state"
import getters from "./getters"
import mutations from "./mutations"
import actions from "./actions"
export default new Vuex.Store({
    state,
    getters,
    mutations,
    actions 
})
  1. установить состояние

state — это чистый объект с некоторыми монтированиями состояния, объявляющий часть общих данных

export default {
    num:0//声明一条共享数据
}
  1. Настройте хранилище в корневом экземпляре

Таким образом, мы можем использовать this.$store в любом компоненте, чтобы использовать API магазина.


  1. Использовать состояние в компонентах

Потому что доступ к хранилищу в компоненте можно получить через this.$store

Таким образом, мы также можем использовать данные, управляемые в состоянии, через this.$store.state и записывать их в данные.

data(){
        return{
            num:this.$store.state.num
        }
    }

После этого вы можете получить данные через {{num}}

Но мы обнаружили, что таким образом при изменении данных состояния компонент vue не будет перерисовываться.

То есть, если мы хотим использовать его в компонентах, нам нужно использовать вычисляемые свойства (computed).

computed:{
    num(){
        return this.$store.state.num
    }
}

Этот способ записи очень скучный, и если вы используете больше состояний, это будет казаться избыточным, поэтому vuex предоставляет вспомогательную функцию mapState, которая поможет нам получить и использовать состояние, сохраненное в хранилище vuex в компоненте, но mapState также является объектом. , Объекты не могут вкладываться друг в друга

computed:{
        mapState({
            num:state => state.num
        })
    }

Итак, мы можем написать:

computed:mapState(["num"])

Или используйте mapState для расширения:

computed:{
        ...mapState({
            num:state => state.num
        })
    }

Но если у компонента уже есть данные num, а имя данных в состоянии также называется num, то это будет конфликт.В это время мы можем дать состоянию псевдоним, когда компонент использует состояние:

computed:{
        ...mapState({
            _num:state => state.num //相当于通过this.$store.state.num来获取num的方法
        })
    }
  1. getters

Иногда нам нужно получить новое состояние в соответствии с определенным состоянием в состоянии.Например, у нас есть число в состоянии, а в некоторых компонентах нам нужно использовать состояние, которое в два раза больше числа.Мы можем передать геттеры создавать.

export default{
    doubleNum(state){
        return state.num*2
    }
}

После создания получить данные внутрь через this.$store.getters.doubleNum в компоненте

Конечно, vuex также предоставляет вспомогательную функцию mapGetters, чтобы помочь нам использовать состояние в геттерах в компоненте, и используемый метод точно такой же, как у mapState, который можно назначить после расширения.

import {mapState,mapGetters} from "vuex"
export default {
    computed:{
        ...mapState({
            _num:state => state.num //相当于通过this.$store.state.num来获取num的方法
        }),
        ...mapGetters(["doubleNum"])
    }

При использовании пишите имя метода прямо в двойных скобках

{{doubleNum}}
  1. Используйте мутации для изменения состояния

Мы не можем изменить состояние непосредственно в компоненте: this.$store.state.num=2, но для изменения необходимо использовать мутации, мутации также являются чистым объектом, который содержит множество методов для изменения состояния.

export default{
    changeNum(state) {
        state.num++
    }
}

Формальные параметры этих методов получают состояние и изменяются в теле функции, при этом данные, используемые компонентом, также будут изменены, реализуя отзывчивость.

Но мы не можем напрямую вызвать метод мутации, нам нужно использовать this.$store.commit для вызова, первый параметр — это имя вызываемого метода, а второй параметр — переданный параметр.

methods:{
        changeNum(){
            this.$store.commit("changeNum")
        }
    }

Vuex предоставляет метод mapMutations, который помогает нам вызывать метод мутации в компоненте.Метод такой же, как у mapState и mapGetters, но его нужно прописать в методах.

methods:{
        ...mapMutations(["changeNum"])
    }
  1. Определите имя метода как константу

Чтобы предотвратить изменение имени метода, мы обычно определяем его отдельно в константном файле const.js и вводим его при использовании.

import { CHANGE_NUM} from "./const"
export default{
    CHANGE_NUM(state) {
        state.num++
    }
}

Но после этого определения можно использовать только имя CHANGE_NUM.При изменении имени в константе оно не будет меняться синхронно, поэтому нам нужно заключить его в квадратные скобки и сделать переменной [CHANGE_NUM], что нам удобно поддерживать и управлять.

//引入
import {CHANGE_NUM} from "@/store/const"
//注册
methods:{
        ...mapMutations([CHANGE_NUM])
    }
//使用
<button @click = "CHANGE_NUM">点击NUM!</button>
  1. Используйте действия для обработки асинхронных операций

Действие аналогично МУТАЦИИ, отличие заключается в следующем: Действие отправляет мутацию вместо прямого изменения статуса. Действие может включать любую асинхронную операцию.

То есть, если есть такое требование: после асинхронного процесса для изменения состояния мы должны сначала вызвать действия в компоненте для выполнения асинхронных действий, а затем действия вызывают мутацию для изменения данных

import { RANDOM_NUM } from "./const"
export default{
    getNumFromBackend(store){//actions一般做异步请求获取数据,然后派发mutations里面具体的更改状态的方法
        setTimeout(()=>{
            //获取一个随机值100
            let randomNum = Math.floor(Math.random()*100)
            store.commit(RANDOM_NUM, randomNum)//actions中的store参数用来派发mutations里面具体的方法,才能去更改state的值
        },1000)
    }
}

Мутации получают случайные числа от действий и присваивают их состоянию

import { CHANGE_NUM, RANDOM_NUM} from "./const"
export default{
    [CHANGE_NUM](state) {
        state.num++
    },
    [RANDOM_NUM](state,randomNum){
        state.num = randomNum//给状态赋值随机数
    }
}

Нажмите кнопку на странице внешнего интерфейса, чтобы вызвать событие

<button @click = "getRandom">随机出现num值!</button>

После изменения состояния выполните рендеринг и повторите рендеринг представления, чтобы получить последнее состояние.

getRandom(){
            //派发action
            this.$store.dispatch("getNumFromBackend")
        }

Вызвать метод действий в компоненте через метод this.$store.dispatch

Конечно, вы также можете использовать mapActions, чтобы помочь в использовании

...mapActions(["getNumFromBackend"])

При использовании метода прямого письма в названии действий к

<button @click = "getNumFromBackend">随机出现num值!</button>
  1. Используйте модули для создания модульных подразделений

Когда информации о данных много, мы помещаем файлы, принадлежащие одному и тому же модулю, в модуль для управления, создаем папку myNum и помещаем действия, const, геттеры, мутации и состояние непосредственно в myNum для управления.

Создайте файл index.js в файле myNum для экспорта каждого модуля.

import state from "./state"
import mutations from "./mutations"
import actions from "./actions"
import getters from "./getters"
export default{
    state,mutations,actions,getters
}

Повторно представлен в index.js

import myNum from "./myNum"
export default new Vuex.Store({
    modules:{
     myNum
    }
})

Теперь мы хотим изменить ссылочный путь на странице внешнего интерфейса.

import {CHANGE_NUM} from "@/store/myNum/const"

Мы не можем получить числовое значение через state.num


Нам нужно вложить myNum еще на один уровень снаружи

...mapState({
            _num:state => state.myNum.num
        })

(3) Далее мы реализуем эффект корзины для покупок.

Сначала создайте папку myCar в магазине, затем создайте

  1. state.js: хранить общие данные

  2. action.js: делать асинхронные запросы

  3. const.js: имя константы

  4. Mutations.js: методы изменения состояния

  5. геттеры: отправка нового состояния на основе состояния

  6. index.js: делать резюме

Введите эти файлы последовательно в index.js

import state from "./state"
import mutations from "./mutations"
import actions from "./actions"
import getters from "./getters"
export default{
    state,mutations,actions,getters
}

Внесите индекс в myCar в store/index.js

import myNum from "./myNum"
import myCar from "./myCar"
export default new Vuex.Store({
    modules:{
        myNum,
        myCar
    }
})

Сначала определите пустой массив cars в state.js.

export default {
    cars:[]//声明一条共享数据
}

Таким образом мы можем получить данные автомобилей в состоянии, которое по умолчанию пусто.


Здесь мы используем компонент ячейки в mint-ui для создания страницы и переноса каждого предмета покупок. Сначала зарегистрируйте его в main.js

import { Lazyload, InfiniteScroll, Header, Button, Tabbar, TabItem,Cell} from "mint-ui"
Vue.component("mt-cell", Cell);

Затем мы можем использовать его MT-Cell

<mt-cell
            title = '标题文字'
            value = '带链接'
            label = "描述信息"
        >
        <img slot = "icon" src = "" alt = "">
        </mt-cell>

Поместите полученные данные о товарах для покупок в goods.json в API в общедоступной статической папке.

{
    "dealList":[
        {
            "firstTitle": "单人",
            "title":"46oz原味爆米花1桶+22oz可乐1杯",
            "price": 33,
            "dealId": 100154273,
            "imageUrl":"https://p0.meituan.net/movie/5c30ed6dc1e3b99345c18454f69c4582176824.jpg@388w_388h_1e_1c",
            "curNumberDesc": "已售379"
        },
        {
            "firstTitle": "单人",
            "title": "46oz原味爆米花1桶+22oz雪碧1杯",
            "price": 33,
            "dealId": 100223426,
            "imageUrl": "https://p0.meituan.net/movie/5c30ed6dc1e3b99345c18454f69c4582176824.jpg@388w_388h_1e_1c",
            "curNumberDesc": "已售12"
        },
        {
            "firstTitle": "单人",
            "title": "进口食品1份",
            "price": 8.89,
            "dealId": 100212615,
            "imageUrl": "https://p1.meituan.net/movie/21f1d203838577db9ef915b980867acc203978.jpg@750w_750h_1e_1c",
            "curNumberDesc": "已售8"
        },
        {
            "firstTitle": "双人",
            "title": "85oz原味爆米花1桶+22oz可乐两杯",
            "price": 44,
            "dealId": 100152611,
            "imageUrl": "https://p0.meituan.net/movie/bf014964c24ca2ef107133eaed75a6e5191344.jpg@388w_388h_1e_1c",
            "curNumberDesc": "已售647"
        },
        {
            "firstTitle": "双人",
            "title": "85oz原味爆米花1桶+22oz雪碧两杯",
            "price": 44,
            "dealId": 100223425,
            "imageUrl": "https://p0.meituan.net/movie/bf014964c24ca2ef107133eaed75a6e5191344.jpg@388w_388h_1e_1c",
            "curNumberDesc": "已售6"
        },
        {
            "firstTitle": "多人",
            "title": "85oz原味爆米花1桶+22oz可乐2杯+冰川时代水1瓶",
            "price": 55,
            "dealId": 100152612,
            "imageUrl": "https://p1.meituan.net/movie/c89df7bf2b1b02cbb326b06ecbbf1ddf203619.jpg@388w_388h_1e_1c",
            "curNumberDesc": "已售89"
        }

    ]
}

Сначала объявите данные для хранения продукта.

data(){
        return{
            goods:[],
        }
    }

Определите метод getGoods для асинхронного запроса данных

methods:{
        getGoods(){
            this.$http.get("/api/goods.json").then(res=>{
                console.log(res)
            })
        }
    },

Вызовите это в функции хука жизненного цикла

created(){
        this.getGoods()
    }

Вы можете получить данные по res и присвоить их товарам

getGoods(){
            this.$http.get("/api/goods.json").then(res=>{
                // console.log(res)
                this.goods = res.data.dealList;
            })
        }

Рендеринг цикла в mt-cell

<mt-cell
            :title="good.title"
            :label="'¥'+good.price"
            v-for="good in goods" 
            :key="good.dealId"
        >
        <mt-button type = "danger" size = "small" @click = "addGoodInCar(good)">购买</mt-button>
        <div class = "firstTitle">{{good.firstTitle}}</div>
        <img width ="70" height = "70" slot="icon" :src="good.imageUrl" alt="">
        </mt-cell>

Реальный запрос информации о корзине должен быть асинхронно запрошен из фона.Теперь мы полагаемся на локальное хранилище в качестве фона для имитации фоновой базы данных и хранения данных о наших автомобилях.

function getCar(){
    return JSON.parse(localStorage.cars ? localStorage.cars : "[]")
}

Теперь напишите метод addGoodInCar для добавления товара в корзину в action.js.

addGoodInCar(store,goodInfo){//添加商品到购物车
        setTimeout(()=>{
            //获取后台返回来的购物车
            let cars = getCar();//[{}]
            let isHas = cars.some(item => {//判断原来的购物车是否有这个商品
                if (item.dealId === goodInfo.dealId){//如果相等代表添加进来的是同一个商品
                item.num++//商品数++
                return true;}
            })
            if (!isHas) {//说明购物车没有此商品
                goodInfo.num = 1;//商品数赋值为1
                cars.push(goodInfo)//将商品添加到cars数据中
            }
            //通知后台更改cars
            localStorage.cars = JSON.stringify(cars)
            //前段需要通过mutations具体的方法去更改state里面的cars
            store.commit(SYNC_UPDATE, cars)
        },1000)
    }

объявить константу в const

const SYNC_UPDATE = "SYNC_UPDATE"
export { SYNC_UPDATE }

Способ определения данных обновления в мутациях

import { SYNC_UPDATE} from "./const"
export default{
    [SYNC_UPDATE](state,newCar){
        state.cars = newCar;
    }
}

Вызвать этот метод в действиях

store.commit(SYNC_UPDATE, cars)

Вызовите этот метод на странице интерфейса

import {mapActions} from "vuex"

methods:{
        ...mapActions(["addGoodInCar"])
    }

Добавьте событие клика на mt-button и передайте хороший параметр в прошлое

<mt-button type = "danger" size = "small" @click = "addGoodInCar(good)">购买</mt-button>

Нажмите кнопку на странице, через секунду получите информацию о продукте в автомобилях, нажмите еще раз, атрибут num ++ станет 2.


Напишите метод для получения корзины покупок в действиях

initCar(store){
        //获取购物车
        let cars = getCar()
        store.commit(SYNC_UPDATE,cars)
    }

Вызывается в созданной функции ловушки в глобальном App.js, чтобы другие компоненты могли получать данные об автомобилях, а данные, совместно используемые несколькими компонентами, управляются в vuex.

created(){
      //让页面走一下首页,触发Header中的router.beforeEach函数,不然头部会重复出现
      this.$router.push("/")
      //初始化购物车
      this.$store.dispatch("initCar")
    }

Обратитесь к списку и начните писать страницу car.vue. Ей не нужно запрашивать данные, а нужно только отображать данные. Вы можете использовать mapState, чтобы помочь в отображении данных в состоянии.

import {mapState} from "vuex"
export default { 
    computed:{
        ...mapState({
            cars:state=>state.myCar.cars
        })
    }
}

Приведенный выше цикл v-for получает данные непосредственно от автомобилей.

v-for="good in cars" 

Изменить кнопки на «+» и «-»

<mt-button type = "danger" size = "small" @click = "addGoodInCar(good)">+</mt-button>
<mt-button type = "danger" size = "small" @click = "addGoodInCar(good)">-</mt-button>

Способ внедрения addgoodincar

import {mapState,mapActions} from "vuex"
export default { 
    computed:{
        ...mapState({
            cars:state=>state.myCar.cars
        })
    },
    methods:{
        ...mapActions(["addGoodInCar"]])
    }
}

Напишите как уменьшать товары в Действиях

reduceGoodInCar(store,goodInfo){
        //获取后台返回来的购物车
        let cars = getCar();
        cars = cars.filter(item=>{
            if (item.dealId === goodInfo.dealId){
                item.num--
            }
            return true;
        })
        //通知后台更改cars
        localStorage.cars = JSON.stringify(cars)
        //前段需要通过mutations具体的方法去更改state里面的cars
        store.commit(SYNC_UPDATE, cars)
    }

Добавить суждение после элемента--, когда число меньше или равно 0, напрямую вернуть false для организации последующих операций

if(item.num<=0) return false

Далее мы добавим функцию для расчета общей цены на странице.Поскольку мы должны полагаться на изменение количества и цены за единицу для расчета общей цены, нам нужно написать этот метод в геттерах.

export default{
    computedTotal(state){
        let cars = state.cars;//在同一个module里面可以直接state.cars获取
        let total = {price:0,num:0} //声明一个total对象存放总价和数量
        cars.forEach(item=>{
            total.price += item.price * item.num; //总价等于商品价格乘以数量
            total.num += item.num //总数累加
        })
        return total //返回total对象
    }
}

Используйте mapGetters, чтобы помочь представить метод calculatedTotal.

import {mapState,mapActions,mapGetters} from "vuex"
export default { 
    computed:{
        ...mapState({
            cars:state=>state.myCar.cars
        }),
        ...mapGetters(["computedTotal"])
    },
    methods:{
        ...mapActions(["addGoodInCar","reduceGoodInCar"])
    }
}

Нажмите, чтобы увеличить количество продуктов, вы обнаружите, что общее количество иногда имеет много знаков после запятой, нам нужно обработать полученные данные о ценах.

total.price = total.price.toFixed(2)//向上取整,并保留两位小数

На главной странице мы управляем отображением и сокрытием информации о товаре через v-if и v-else.При отсутствии товара отображается тег ap, чтобы напомнить пользователю об отсутствии товара, и предоставляется router-link чтобы вернуться на страницу со списком продуктов.

<p v-if = "cars.length === 0">
            没有商品了
            <router-link to = "/mine/list">点击此处购买商品</router-link>
        </p>
        <div v-else>
            <mt-cell
                :title="good.title"
                :label="'¥'+good.price+'*'+good.num" 
                v-for="good in cars" 
                :key="good.dealId"
            >
            <mt-button type = "danger" size = "small" @click = "addGoodInCar(good)">+</mt-button>
            <mt-button type = "danger" size = "small" @click = "reduceGoodInCar(good)">-</mt-button>
            <div class = "firstTitle">{{good.firstTitle}}</div>
            <img width ="70" height = "70" slot="icon" :src="good.imageUrl" alt="">
            </mt-cell>
        </div>

Эффект работы корзины:


15. Собирайтесь и выходите в интернет

(1) Изменить файл конфигурации

Найдите файл конфигурации Vue-config.js проекта и измените publicPath: в module.exports на: '/v-douban/'


При этом путь локального запроса тоже нужен /v-douban


(2) Файл пакета

воплощать в жизньyarn buildУпакован в расст

(3) Подключитесь к FTP-серверу и измените nginx.

Войдите в каталог /usr/local/nginx/conf и перенесите файл nginx.config в локальный.



Измените файл nginx.config, чтобы настроить прокси интерфейса данных.

location /api/db {
            proxy_pass http://47.96.0.211:9000/db;
        } 

        location /data/my {
            proxy_pass http://118.31.109.254:8088/my;
        } 

        location /douban/my {
            proxy_pass http://47.111.166.60:9000/my;
        } 

Загрузите новый файл nginx.config на сервер, перезаписав исходный файл.


Подключитесь к базе данных на терминале и перезапустите сервер nginx.

./nginx -s reload


Поскольку мы используем путь https для этой конфигурации, нам нужно включить функцию SSL

Вы можете обратиться к этой статье:

Блог woowoo.cn на.com/Рыбы любят C…

После завершения установки проверьте, обновлен ли файл конфигурации:


Перейдите в каталог /usr/local/nginx/html, чтобы создать папку v-douban.


Загрузите все файлы из упакованной папки dist на сервер


После завершения передачи доступ к запущенному проекту можно будет получить на веб-странице.http://39.96.84.220/v-douban