После большого успеха использования DotNetCore MVC для создания приложений Vue я хотел попробовать Vue Router для создания приложения SPA. Оказывается, это была простая задача.

Осталось только как-то защитить страницы vue на основе учетных данных для входа. Пожалуйста, дайте мне несколько советов по этому поводу!

Ключевым моментом снова является использование @ Html.Partial (components.cshtml или pages.cshtml) для импорта ваших отдельных страниц или компонентов. Это не совсем настоящие одностраничные компоненты Vue, но они очень близки. Поскольку я использую Vuetify в качестве глобального пользовательского интерфейса, мне не нужны разделы стилей в моих компонентах.

Полный исходный код примера проекта можно скачать здесь: https://www.dropbox.com/s/yoce5z3uwo98l9x/vuespa.zip?dl=0

И снова я использую страницу _layout для загрузки всех необходимых файлов CSS и JS из CDN. Поскольку мое приложение в любом случае не будет работать без подключения к Интернету, мне нравится использовать CDN вместо того, чтобы загружать и управлять всеми зависимостями. Однако на этот раз я использую только страницу _layout для настройки html-страницы, и все панели инструментов, страницы, компоненты и т. Д. Встраиваются в страницу index.cshtml.

_layout.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
    <title>@ViewData["Title"] SPA</title>
    <link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">
    <link href="https://unpkg.com/[email protected]/dist/vuetify.min.css" rel="stylesheet">
    <link href="css/site.css" rel="stylesheet">
@RenderSection("css", required: false)
</head>
<body>
    <style>
    </style>
<div class="container body-content">
        @RenderBody()
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/moment.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/vee-validate.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/vuetify.min.js"></script>
@RenderSection("js", required: false)
</body>
</html>

Теперь, используя страницу razor index.cshtml, мы создаем наше одностраничное приложение vuejs. На этой странице нам нужно встроить все страницы и компоненты vue с помощью Html.Partial (). Мы также добавляем маршруты для Vue Router.

index.cshtml

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}
<div id="app" v-cloak>
    <v-app>
        <spa-toolbar :title="msg"></spa-toolbar>
<router-view></router-view>
</v-app>
</div>
@section js {
    <script src="~/js/site.js"></script>
@Html.Partial("~/Pages/VueComponents/Toolbar.cshtml")
@Html.Partial("~/Pages/VuePages/Home.cshtml")
@Html.Partial("~/Pages/VuePages/Airports.cshtml")
@Html.Partial("~/Pages/VuePages/Components/AirportCard.cshtml")
<script>
        Vue.use(VueRouter);
        const routes = [
            { path: '/', component: spaHome },
            { path: '/airports', component: spaAirports }
        ]
        const router = new VueRouter({
            routes // short for `routes: routes`
        })
Vue.use(VeeValidate);
        var app = new Vue({
            el: '#app',
            watch: {
            },
            data: {
                msg: 'Welcome To Vue SPA'
            },
            methods: {
            },
            router
        }).$mount('#app')
    </script>
}

Toolbar.cshtml

<template id="spa-toolbar">
    <div>
        <v-toolbar dark color="primary" class="mb-2">
            <v-layout align-center>
                <v-toolbar-side-icon></v-toolbar-side-icon>
                <v-toolbar-title class="white--text">{{title}}</v-toolbar-title>
                <v-spacer></v-spacer>
                <v-toolbar-items>
                    <router-link to="/"><v-btn flat>Home</v-btn></router-link>
                    <router-link to="/airports"><v-btn flat>Airports</v-btn></router-link>
                </v-toolbar-items>
            </v-layout>
        </v-toolbar>
    </div>
</template>
<script>
    Vue.component('spa-toolbar', {
        template: '#spa-toolbar',
        props: ['title'],
        $_veeValidate: {
            validator: 'new'
        },
    })
</script>

Вот простая домашняя страница. На этом мы закончили. Страница аэропорта, включенная в проект, является примером страницы, которая вызывает API для получения данных JSON и использует компонент для визуализации данных.

Airports.cshtml

<template id="spa-airports">
    <div>
        <h3>Airport Delays</h3>
        <v-card class="mb-2">
            <v-card-media height="400"
                          src="https://dsx.weather.com/util/image/map/airport_delays_1280x720.jpg?v=ap&w=1280&h=720&api=7db9fe61-7414-47b5-9871-e17d87b8b6a0">
</v-card-media>
<v-card-text>
<v-layout row>
                    <v-flex xs12>
                        <v-select :items="airportsUS" v-on:input="Lookup"
                                  v-model="airportCode" autocomplete
                                  item-text="longname"
                                  item-value="iata"
                                  prepend-icon="clear"
                                  :prepend-icon-cb="ClearList()"
                                  label="Airports">
                        </v-select>
                    </v-flex>
                </v-layout>
            </v-card-text>
        </v-card>
        <card-airport :airport="airportData"></card-airport>
    </div>
</template>
<script>
    var spaAirports = Vue.component('Airports', {
        template: '#spa-airports',
        props: ['title'],
        $_veeValidate: {
            validator: 'new'
        },
        computed: {
            airportsUS: function() {
                var items = this.airportList.filter(function (item) {
                    item.longname = item.iata + ' - ' + item.name;
                    return item.iso == 'US';
                });
                return items;
            }
        },
        data: function () {
            return {
                airportCode: '',
                airportData: {
                    name: null, status: { reason: '' }
                },
                airportList: iata
            }
        },
        methods: {
            Lookup: function () {
                let self = this;
                let url = 'http://services.faa.gov/airport/status/' + this.airportCode + '?format=application/json';
                axios.get(url)
                    .then(function (response) {
                        self.airportData = response.data;
                        //console.log(response);
                        self.loading = false;
                    })
                    .catch(function (error) {
                        self.airportData = {
                            name: null, status: { reason: '' }
                        };
                        console.log('error', error);
                    });
            },
            ClearList: function() {
                console.log(this.airportCode, this.airportData);
                this.airportCode = '';
            }
        }
    })
</script>

AirportCard.cshtml

<template id="card-airport">
    <div v-if="airport.name!=null">
        <v-card dark :color="this.airport.delay==='true' ? 'error' : 'success' ">
            <v-card-text>
                <h4>{{airport.IATA}} {{airport.name}} {{airport.city}} {{airport.state}} {{airport.weather.meta.updated}}</h4>
                <v-spacer></v-spacer>
                <h5>
                    {{airport.status.reason}} {{airport.status.minDelay}}
                    {{airport.delay!='true' ? '' : ' to '}}
                    {{airport.status.maxDelay}}
                    {{airport.status.type}}
                    {{airport.status.trend}}
                </h5>
                <h5>
                    temp: {{airport.weather.temp}},
                    visibility: {{airport.weather.visibility}},
                    weather: {{airport.weather.weather}},
                    wind: {{airport.weather.wind}}
                </h5>
            </v-card-text>
</v-card>
    </div>
</template>
<script>
    Vue.component('card-airport', {
        template: '#card-airport',
        props: ['airport'],
        $_veeValidate: {
            validator: 'new'
        },
        methods: {
        }
    })
</script>

Так получилась верстка проекта.